668c26e27c3ec55645933b2fde71667625f67d27
[lxde/lxpanel.git] / src / plugin.c
1 /**
2 * Copyright (c) 2006-2014 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include "private.h"
24
25 #include <gdk-pixbuf/gdk-pixbuf.h>
26 #include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
27 #include <gdk/gdk.h>
28 #include <string.h>
29 #include <stdlib.h>
30
31 #include "misc.h"
32 #include "bg.h"
33
34 #include <glib-object.h>
35 #include <glib/gi18n.h>
36 #include <libfm/fm-gtk.h>
37
38 //#define DEBUG
39 #include "dbg.h"
40
41 static void init_plugin_class_list(void);
42 static void plugin_class_unref(PluginClass * pc);
43
44 GQuark lxpanel_plugin_qinit;
45 GQuark lxpanel_plugin_qconf;
46 GQuark lxpanel_plugin_qdata;
47 GQuark lxpanel_plugin_qsize;
48 static GHashTable *_all_types = NULL;
49
50 /* Dynamic parameter for static (built-in) plugins must be FALSE so we will not try to unload them */
51 #define REGISTER_STATIC_PLUGIN_CLASS(pc) \
52 do {\
53 extern PluginClass pc;\
54 register_plugin_class(&pc, FALSE);\
55 } while (0)
56
57 /* The same for new plugins type - they will be not unloaded by FmModule */
58 #define REGISTER_STATIC_MODULE(pc) do { \
59 extern LXPanelPluginInit lxpanel_static_plugin_##pc; \
60 lxpanel_register_plugin_type(#pc, &lxpanel_static_plugin_##pc); } while (0)
61
62 static inline const LXPanelPluginInit *_find_plugin(const char *name)
63 {
64 return g_hash_table_lookup(_all_types, name);
65 }
66
67 static GtkWidget *_old_plugin_config(LXPanel *panel, GtkWidget *instance)
68 {
69 const LXPanelPluginInit *init = PLUGIN_CLASS(instance);
70 Plugin * plugin;
71
72 g_return_val_if_fail(init != NULL && init->new_instance == NULL, NULL);
73 plugin = lxpanel_plugin_get_data(instance);
74 if (plugin->class->config)
75 plugin->class->config(plugin, GTK_WINDOW(panel));
76 return NULL;
77 }
78
79 static void _old_plugin_reconfigure(LXPanel *panel, GtkWidget *instance)
80 {
81 const LXPanelPluginInit *init = PLUGIN_CLASS(instance);
82 Plugin * plugin;
83
84 g_return_if_fail(init != NULL && init->new_instance == NULL);
85 plugin = lxpanel_plugin_get_data(instance);
86 if (plugin->class->panel_configuration_changed)
87 plugin->class->panel_configuration_changed(plugin);
88 }
89
90 /* Register a PluginClass. */
91 static void register_plugin_class(PluginClass * pc, gboolean dynamic)
92 {
93 LXPanelPluginInit *init = g_new0(LXPanelPluginInit, 1);
94 init->_reserved1 = pc;
95 init->name = pc->name;
96 init->description = pc->description;
97 if (pc->config)
98 init->config = _old_plugin_config;
99 if (pc->panel_configuration_changed)
100 init->reconfigure = _old_plugin_reconfigure;
101 init->one_per_system = pc->one_per_system;
102 init->expand_available = pc->expand_available;
103 init->expand_default = pc->expand_default;
104 pc->dynamic = dynamic;
105 g_hash_table_insert(_all_types, g_strdup(pc->type), init);
106 }
107
108 /* Initialize the static plugins. */
109 static void init_plugin_class_list(void)
110 {
111 #ifdef STATIC_SEPARATOR
112 REGISTER_STATIC_MODULE(separator);
113 #endif
114
115 #ifdef STATIC_LAUNCHTASKBAR
116 REGISTER_STATIC_MODULE(launchtaskbar);
117 #endif
118
119 #ifdef STATIC_DCLOCK
120 REGISTER_STATIC_MODULE(dclock);
121 #endif
122
123 #ifdef STATIC_WINCMD
124 REGISTER_STATIC_MODULE(wincmd);
125 #endif
126
127 #ifdef STATIC_DIRMENU
128 REGISTER_STATIC_MODULE(dirmenu);
129 #endif
130
131 #ifdef STATIC_PAGER
132 REGISTER_STATIC_MODULE(pager);
133 #endif
134
135 #ifdef STATIC_TRAY
136 REGISTER_STATIC_MODULE(tray);
137 #endif
138
139 #ifndef DISABLE_MENU
140 #ifdef STATIC_MENU
141 REGISTER_STATIC_MODULE(menu);
142 #endif
143 #endif
144
145 #ifdef STATIC_SPACE
146 REGISTER_STATIC_MODULE(space);
147 #endif
148 }
149
150 /* Load a dynamic plugin. */
151 static void plugin_load_dynamic(const char * type, const gchar * path)
152 {
153 PluginClass * pc = NULL;
154
155 /* Load the external module. */
156 GModule * m = g_module_open(path, G_MODULE_BIND_LAZY);
157 if (m != NULL)
158 {
159 /* Formulate the name of the expected external variable of type PluginClass. */
160 char class_name[128];
161 g_snprintf(class_name, sizeof(class_name), "%s_plugin_class", type);
162
163 /* Validate that the external variable is of type PluginClass. */
164 gpointer tmpsym;
165 if (( ! g_module_symbol(m, class_name, &tmpsym)) /* Ensure symbol is present */
166 || ((pc = tmpsym) == NULL)
167 || (pc->structure_size != sizeof(PluginClass)) /* Then check versioning information */
168 || (pc->structure_version != PLUGINCLASS_VERSION)
169 || (strcmp(type, pc->type) != 0)) /* Then and only then access other fields; check name */
170 {
171 g_module_close(m);
172 g_warning("%s.so is not a lxpanel plugin", type);
173 return;
174 }
175
176 /* Register the newly loaded and valid plugin. */
177 pc->gmodule = m;
178 register_plugin_class(pc, TRUE);
179 pc->count = 1;
180 }
181 }
182
183 /* Unreference a dynamic plugin. */
184 static void plugin_class_unref(PluginClass * pc)
185 {
186 pc->count -= 1;
187
188 /* If the reference count drops to zero, unload the plugin if it is dynamic and has declared itself unloadable. */
189 if ((pc->count == 0)
190 && (pc->dynamic)
191 && ( ! pc->not_unloadable))
192 {
193 g_module_close(pc->gmodule);
194 }
195 }
196
197 /* Loads all available old type plugins. Should be removed in future releases. */
198 static void plugin_get_available_classes(void)
199 {
200 /* Initialize static plugins on first call. */
201 init_plugin_class_list();
202
203 #ifndef DISABLE_PLUGINS_LOADING
204 GDir * dir = g_dir_open(PACKAGE_LIB_DIR "/lxpanel/plugins", 0, NULL);
205 if (dir != NULL)
206 {
207 const char * file;
208 while ((file = g_dir_read_name(dir)) != NULL)
209 {
210 if (g_str_has_suffix(file, ".so"))
211 {
212 char * type = g_strndup(file, strlen(file) - 3);
213 if (_find_plugin(type) == NULL)
214 {
215 /* If it has not been loaded, do it. If successful, add it to the result. */
216 char * path = g_build_filename(PACKAGE_LIB_DIR "/lxpanel/plugins", file, NULL );
217 plugin_load_dynamic(type, path);
218 g_free(path);
219 }
220 g_free(type);
221 }
222 }
223 g_dir_close(dir);
224 }
225 #endif
226 }
227
228 /* Recursively set the background of all widgets on a panel background configuration change. */
229 void plugin_widget_set_background(GtkWidget * w, LXPanel * panel)
230 {
231 if (w != NULL)
232 {
233 Panel *p = panel->priv;
234 if (gtk_widget_get_has_window(w))
235 {
236 if ((p->background) || (p->transparent))
237 {
238 #if GTK_CHECK_VERSION(2, 20, 0)
239 if (gtk_widget_get_realized(w))
240 #else
241 if (GTK_WIDGET_REALIZED(w))
242 #endif
243 {
244 _panel_determine_background_pixmap(panel, w);
245 gdk_window_invalidate_rect(gtk_widget_get_window(w), NULL, TRUE);
246 }
247 }
248 else
249 {
250 /* Set background according to the current GTK style. */
251 gtk_widget_set_app_paintable(w, FALSE);
252 #if GTK_CHECK_VERSION(2, 20, 0)
253 if (gtk_widget_get_realized(w))
254 #else
255 if (GTK_WIDGET_REALIZED(w))
256 #endif
257 {
258 gdk_window_set_back_pixmap(gtk_widget_get_window(w), NULL, TRUE);
259 gtk_style_set_background(gtk_widget_get_style(w),
260 gtk_widget_get_window(w),
261 GTK_STATE_NORMAL);
262 }
263 }
264 }
265
266 /* Special handling to get tray icons redrawn. */
267 if (GTK_IS_SOCKET(w))
268 {
269 gtk_widget_hide(w);
270 gdk_window_process_all_updates();
271 gtk_widget_show(w);
272 gdk_window_process_all_updates();
273 }
274
275 /* Recursively process all children of a container. */
276 if (GTK_IS_CONTAINER(w))
277 gtk_container_foreach(GTK_CONTAINER(w), (GtkCallback) plugin_widget_set_background, panel);
278 }
279 }
280
281 /* Handler for "button_press_event" signal with Plugin as parameter.
282 * External so can be used from a plugin. */
283 static gboolean lxpanel_plugin_button_press_event(GtkWidget *plugin, GdkEventButton *event, LXPanel *panel)
284 {
285 if (event->button == 3 && /* right button */
286 (event->state & gtk_accelerator_get_default_mod_mask()) == 0) /* no key */
287 {
288 GtkMenu* popup = (GtkMenu*)lxpanel_get_plugin_menu(panel, plugin, FALSE);
289 gtk_menu_popup(popup, NULL, NULL, NULL, NULL, event->button, event->time);
290 return TRUE;
291 }
292 return FALSE;
293 }
294
295 /* for old plugins compatibility */
296 gboolean plugin_button_press_event(GtkWidget *widget, GdkEventButton *event, Plugin *plugin)
297 {
298 return lxpanel_plugin_button_press_event(plugin->pwid, event, PLUGIN_PANEL(plugin->pwid));
299 }
300
301 /* Helper for position-calculation callback for popup menus. */
302 void lxpanel_plugin_popup_set_position_helper(LXPanel * p, GtkWidget * near, GtkWidget * popup, gint * px, gint * py)
303 {
304 gint x, y;
305 GtkAllocation allocation;
306 GtkAllocation popup_req;
307
308 /* Get the allocation of the popup menu. */
309 gtk_widget_realize(popup);
310 gtk_widget_get_allocation(popup, &popup_req);
311 if (gtk_widget_is_toplevel(popup))
312 {
313 GdkRectangle extents;
314 /* FIXME: can we wait somehow for WM drawing decorations? */
315 gdk_window_process_all_updates();
316 gdk_window_get_frame_extents(gtk_widget_get_window(popup), &extents);
317 popup_req.width = extents.width;
318 popup_req.height = extents.height;
319 }
320
321 /* Get the origin of the requested-near widget in screen coordinates. */
322 gtk_widget_get_allocation(near, &allocation);
323 gdk_window_get_origin(gtk_widget_get_window(near), &x, &y);
324 if (!gtk_widget_get_has_window(near))
325 {
326 /* For non-window widgets allocation is given within the screen */
327 x += allocation.x;
328 y += allocation.y;
329 }
330
331 /* Dispatch on edge to lay out the popup menu with respect to the button.
332 * Also set "push-in" to avoid any case where it might flow off screen. */
333 switch (p->priv->edge)
334 {
335 case EDGE_TOP: y += allocation.height; break;
336 case EDGE_BOTTOM: y -= popup_req.height; break;
337 case EDGE_LEFT: x += allocation.width; break;
338 case EDGE_RIGHT: x -= popup_req.width; break;
339 }
340
341 /* Push onscreen. */
342 int screen_width = gdk_screen_width();
343 int screen_height = gdk_screen_height();
344 x = CLAMP(x, 0, screen_width - popup_req.width);
345 y = CLAMP(y, 0, screen_height - popup_req.height);
346 /* FIXME: take monitor area into account not just screen */
347
348 *px = x;
349 *py = y;
350 }
351
352 /* for old plugins compatibility -- popup_req is ignored here */
353 void plugin_popup_set_position_helper(Plugin * p, GtkWidget * near, GtkWidget * popup, GtkRequisition * popup_req, gint * px, gint * py)
354 {
355 lxpanel_plugin_popup_set_position_helper(p->panel->topgwin, near, popup, px, py);
356 }
357
358 /* Adjust the position of a popup window to ensure that it is not hidden by the panel.
359 * It is observed that some window managers do not honor the strut that is set on the panel. */
360 void lxpanel_plugin_adjust_popup_position(GtkWidget * popup, GtkWidget * parent)
361 {
362 gint x, y;
363
364 /* Calculate desired position for the popup. */
365 lxpanel_plugin_popup_set_position_helper(PLUGIN_PANEL(parent), parent,
366 popup, &x, &y);
367 /* Move the popup to position. */
368 gdk_window_move(gtk_widget_get_window(popup), x, y);
369 }
370
371 /* for old plugins compatibility */
372 void plugin_adjust_popup_position(GtkWidget * popup, Plugin * plugin)
373 {
374 lxpanel_plugin_adjust_popup_position(popup, plugin->pwid);
375 }
376
377 /* Open a specified path in a file manager. */
378 static gboolean _open_dir_in_file_manager(GAppLaunchContext* ctx, GList* folder_infos,
379 gpointer user_data, GError** err)
380 {
381 FmFileInfo *fi = folder_infos->data; /* only first is used */
382 GAppInfo *app = g_app_info_get_default_for_type("inode/directory", TRUE);
383 GFile *gf;
384 gboolean ret;
385
386 if (app == NULL)
387 {
388 g_set_error_literal(err, G_SHELL_ERROR, G_SHELL_ERROR_EMPTY_STRING,
389 _("No file manager is configured."));
390 return FALSE;
391 }
392 gf = fm_path_to_gfile(fm_file_info_get_path(fi));
393 folder_infos = g_list_prepend(NULL, gf);
394 ret = fm_app_info_launch(app, folder_infos, ctx, err);
395 g_list_free(folder_infos);
396 g_object_unref(gf);
397 g_object_unref(app);
398 return ret;
399 }
400
401 gboolean lxpanel_launch_path(LXPanel *panel, FmPath *path)
402 {
403 return fm_launch_path_simple(NULL, NULL, path, _open_dir_in_file_manager, NULL);
404 }
405
406 void lxpanel_plugin_show_config_dialog(GtkWidget* plugin)
407 {
408 const LXPanelPluginInit *init = PLUGIN_CLASS(plugin);
409 LXPanel *panel = PLUGIN_PANEL(plugin);
410 GtkWidget *dlg = panel->priv->plugin_pref_dialog;
411
412 if (dlg && g_object_get_data(G_OBJECT(dlg), "generic-config-plugin") == plugin)
413 return; /* configuration dialog is already shown for this widget */
414 g_return_if_fail(panel != NULL);
415 dlg = init->config(panel, plugin);
416 if (dlg)
417 _panel_show_config_dialog(panel, plugin, dlg);
418 }
419
420 #if GLIB_CHECK_VERSION(2, 32, 0)
421 static GRecMutex _mutex;
422 #else
423 static GStaticRecMutex _mutex = G_STATIC_REC_MUTEX_INIT;
424 #endif
425
426 #ifndef DISABLE_PLUGINS_LOADING
427 FM_MODULE_DEFINE_TYPE(lxpanel_gtk, LXPanelPluginInit, 1)
428
429 static gboolean fm_module_callback_lxpanel_gtk(const char *name, gpointer init, int ver)
430 {
431 /* ignore ver for now, only 1 exists */
432 return lxpanel_register_plugin_type(name, init);
433 }
434 #endif
435
436 static gboolean old_plugins_loaded = FALSE;
437
438 void _prepare_modules(void)
439 {
440 _all_types = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
441 lxpanel_plugin_qdata = g_quark_from_static_string("LXPanel::plugin-data");
442 lxpanel_plugin_qinit = g_quark_from_static_string("LXPanel::plugin-init");
443 lxpanel_plugin_qconf = g_quark_from_static_string("LXPanel::plugin-conf");
444 lxpanel_plugin_qsize = g_quark_from_static_string("LXPanel::plugin-size");
445 #ifndef DISABLE_PLUGINS_LOADING
446 fm_modules_add_directory(PACKAGE_LIB_DIR "/lxpanel/plugins");
447 fm_module_register_lxpanel_gtk();
448 #endif
449 }
450
451 void _unload_modules(void)
452 {
453 GHashTableIter iter;
454 gpointer key, val;
455
456 g_hash_table_iter_init(&iter, _all_types);
457 while(g_hash_table_iter_next(&iter, &key, &val))
458 {
459 register const LXPanelPluginInit *init = val;
460 if (init->new_instance == NULL) /* old type of plugin */
461 {
462 plugin_class_unref(init->_reserved1);
463 g_free(val);
464 }
465 }
466 g_hash_table_destroy(_all_types);
467 #ifndef DISABLE_PLUGINS_LOADING
468 fm_module_unregister_type("lxpanel_gtk");
469 #endif
470 old_plugins_loaded = FALSE;
471 }
472
473 gboolean lxpanel_register_plugin_type(const char *name, const LXPanelPluginInit *init)
474 {
475 const LXPanelPluginInit *data;
476
477 /* validate it */
478 if (init->new_instance == NULL || name == NULL || name[0] == '\0')
479 return FALSE;
480 #if GLIB_CHECK_VERSION(2, 32, 0)
481 g_rec_mutex_lock(&_mutex);
482 #else
483 g_static_rec_mutex_lock(&_mutex);
484 #endif
485 /* test if it's registered already */
486 data = _find_plugin(name);
487 if (data == NULL)
488 {
489 if (init->init)
490 init->init();
491 g_hash_table_insert(_all_types, g_strdup(name), (gpointer)init);
492 }
493 #if GLIB_CHECK_VERSION(2, 32, 0)
494 g_rec_mutex_unlock(&_mutex);
495 #else
496 g_static_rec_mutex_unlock(&_mutex);
497 #endif
498 return (data == NULL);
499 }
500
501 static void _old_plugin_save_hook(const config_setting_t * setting, FILE * f, gpointer user_data)
502 {
503 Plugin *pl = user_data;
504 PluginClass *pc = pl->class;
505 if (pc->save)
506 pc->save(pl, f);
507 }
508
509 /* This is called right before Plugin instance is destroyed */
510 static void _old_plugin_destroy(gpointer data)
511 {
512 Plugin *pl = data;
513
514 plugin_class_unref(pl->class);
515
516 /* Free the Plugin structure. */
517 g_free(pl);
518 }
519
520 static void _on_old_widget_destroy(GtkWidget *widget, Plugin *pl)
521 {
522 /* Never let run it again. */
523 g_signal_handlers_disconnect_by_func(widget, _on_old_widget_destroy, pl);
524 /* Run the destructor before destroying the top level widget.
525 * This prevents problems with the plugin destroying child widgets. */
526 pl->class->destructor(pl);
527 }
528
529 static void on_size_allocate(GtkWidget *widget, GdkRectangle *allocation, LXPanel *p)
530 {
531 GdkRectangle *alloc;
532
533 alloc = g_object_get_qdata(G_OBJECT(widget), lxpanel_plugin_qsize);
534 if (alloc->x == allocation->x && alloc->y == allocation->y &&
535 alloc->width == allocation->width && alloc->height == allocation->height)
536 return; /* not changed */
537 *alloc = *allocation;
538 /* g_debug("size-allocate on %s", PLUGIN_CLASS(widget)->name); */
539 _panel_queue_update_background(p);
540 // _queue_panel_calculate_size(p);
541 }
542
543 GtkWidget *lxpanel_add_plugin(LXPanel *p, const char *name, config_setting_t *cfg, gint at)
544 {
545 const LXPanelPluginInit *init;
546 GtkWidget *widget;
547 config_setting_t *s, *pconf;
548 gint expand, padding = 0, border = 0, i;
549
550 CHECK_MODULES();
551 if (!old_plugins_loaded)
552 plugin_get_available_classes();
553 old_plugins_loaded = TRUE;
554 init = _find_plugin(name);
555 if (init == NULL)
556 return NULL;
557 /* prepare widget settings */
558 if (!init->expand_available)
559 expand = 0;
560 else if ((s = config_setting_get_member(cfg, "expand")))
561 expand = config_setting_get_int(s);
562 else
563 expand = init->expand_default;
564 s = config_setting_get_member(cfg, "padding");
565 if (s)
566 padding = config_setting_get_int(s);
567 s = config_setting_get_member(cfg, "border");
568 if (s)
569 border = config_setting_get_int(s);
570 /* prepare config and create it if need */
571 s = config_setting_add(cfg, "", PANEL_CONF_TYPE_LIST);
572 for (i = 0; (pconf = config_setting_get_elem(s, i)); i++)
573 if (strcmp(config_setting_get_name(pconf), "Config") == 0)
574 break;
575 if (!pconf)
576 pconf = config_setting_add(s, "Config", PANEL_CONF_TYPE_GROUP);
577 /* If this plugin can only be instantiated once, count the instantiation.
578 * This causes the configuration system to avoid displaying the plugin as one that can be added. */
579 if (init->new_instance) /* new style of plugin */
580 {
581 widget = init->new_instance(p, pconf);
582 if (widget == NULL)
583 return widget;
584 /* always connect lxpanel_plugin_button_press_event() */
585 g_signal_connect(widget, "button-press-event",
586 G_CALLBACK(lxpanel_plugin_button_press_event), p);
587 if (init->button_press_event)
588 g_signal_connect(widget, "button-press-event",
589 G_CALLBACK(init->button_press_event), p);
590 }
591 else
592 {
593 Plugin *pl = g_new0(Plugin, 1);
594 PluginClass *pc = init->_reserved1;
595 char *conf = config_setting_to_string(pconf), *fp;
596
597 pl->class = pc;
598 pl->panel = p->priv;
599 widget = NULL;
600 fp = &conf[9]; /* skip "Config {\n" */
601 /* g_debug("created conf: %s",conf); */
602 /* Call the constructor.
603 * It is responsible for parsing the parameters, and setting "pwid" to the top level widget. */
604 if (pc->constructor(pl, &fp))
605 widget = pl->pwid;
606 g_free(conf);
607
608 if (widget == NULL) /* failed */
609 {
610 g_free(pl);
611 return widget;
612 }
613
614 pc->count += 1;
615 g_signal_connect(widget, "destroy", G_CALLBACK(_on_old_widget_destroy), pl);
616 config_setting_set_save_hook(pconf, _old_plugin_save_hook, pl);
617 lxpanel_plugin_set_data(widget, pl, _old_plugin_destroy);
618 }
619 gtk_widget_set_name(widget, name);
620 gtk_box_pack_start(GTK_BOX(p->priv->box), widget, expand, TRUE, padding);
621 gtk_container_set_border_width(GTK_CONTAINER(widget), border);
622 g_signal_connect(widget, "size-allocate", G_CALLBACK(on_size_allocate), p);
623 gtk_widget_show(widget);
624 g_object_set_qdata(G_OBJECT(widget), lxpanel_plugin_qconf, cfg);
625 g_object_set_qdata(G_OBJECT(widget), lxpanel_plugin_qinit, (gpointer)init);
626 g_object_set_qdata_full(G_OBJECT(widget), lxpanel_plugin_qsize,
627 g_new0(GdkRectangle, 1), g_free);
628 return widget;
629 }
630
631 /* transfer none - note that not all fields are valid there */
632 GHashTable *lxpanel_get_all_types(void)
633 {
634 return _all_types;
635 }