1cb76f841613be017945b8dddbd295b263e5efe7
[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 register_plugin_class(PluginClass * pc, gboolean dynamic);
42 static void init_plugin_class_list(void);
43 static void plugin_class_unref(PluginClass * pc);
44
45 GQuark lxpanel_plugin_qdata;
46 static GHashTable *_all_types = NULL;
47
48 /* Dynamic parameter for static (built-in) plugins must be FALSE so we will not try to unload them */
49 #define REGISTER_STATIC_PLUGIN_CLASS(pc) \
50 do {\
51 extern PluginClass pc;\
52 register_plugin_class(&pc, FALSE);\
53 } while (0)
54
55 /* The same for new plugins type - they will be not unloaded by FmModule */
56 #define REGISTER_STATIC_MODULE(pc) do { \
57 extern LXPanelPluginInit lxpanel_static_plugin_##pc; \
58 lxpanel_register_plugin_type(#pc, &lxpanel_static_plugin_##pc); } while (0)
59
60 static inline LXPanelPluginInit *_find_plugin(const char *name)
61 {
62 return g_hash_table_lookup(_all_types, name);
63 }
64
65 static void _old_plugin_config(Panel *panel, GtkWidget *instance, GtkWindow *parent)
66 {
67 LXPanelPluginInit *init = PLUGIN_CLASS(instance);
68 Plugin * plugin;
69
70 g_return_if_fail(init != NULL && init->new_instance == NULL);
71 plugin = lxpanel_plugin_get_data(instance);
72 if (plugin->class->config)
73 plugin->class->config(plugin, parent);
74 }
75
76 static void _old_plugin_reconfigure(Panel *panel, GtkWidget *instance)
77 {
78 LXPanelPluginInit *init = PLUGIN_CLASS(instance);
79 Plugin * plugin;
80
81 g_return_if_fail(init != NULL && init->new_instance == NULL);
82 plugin = lxpanel_plugin_get_data(instance);
83 if (plugin->class->panel_configuration_changed)
84 plugin->class->panel_configuration_changed(plugin);
85 }
86
87 /* Register a PluginClass. */
88 static void register_plugin_class(PluginClass * pc, gboolean dynamic)
89 {
90 LXPanelPluginInit *init = g_new0(LXPanelPluginInit, 1);
91 init->_reserved1 = pc;
92 init->name = pc->name;
93 init->description = pc->description;
94 if (pc->config)
95 init->config = _old_plugin_config;
96 if (pc->panel_configuration_changed)
97 init->reconfigure = _old_plugin_reconfigure;
98 init->one_per_system = pc->one_per_system;
99 init->expand_available = pc->expand_available;
100 init->expand_default = pc->expand_default;
101 pc->dynamic = dynamic;
102 g_hash_table_insert(_all_types, g_strdup(pc->type), init);
103 }
104
105 /* Initialize the static plugins. */
106 static void init_plugin_class_list(void)
107 {
108 #ifdef STATIC_SEPARATOR
109 REGISTER_STATIC_MODULE(separator);
110 #endif
111
112 #ifdef STATIC_LAUNCHBAR
113 REGISTER_STATIC_MODULE(launchbar);
114 #endif
115
116 #ifdef STATIC_LAUNCHTASKBAR
117 REGISTER_STATIC_PLUGIN_CLASS(launchtaskbar_plugin_class);
118 #endif
119
120 #ifdef STATIC_DCLOCK
121 REGISTER_STATIC_MODULE(dclock);
122 #endif
123
124 #ifdef STATIC_WINCMD
125 REGISTER_STATIC_MODULE(wincmd);
126 #endif
127
128 #ifdef STATIC_DIRMENU
129 REGISTER_STATIC_MODULE(dirmenu);
130 #endif
131
132 #ifdef STATIC_TASKBAR
133 REGISTER_STATIC_PLUGIN_CLASS(taskbar_plugin_class);
134 #endif
135
136 #ifdef STATIC_PAGER
137 REGISTER_STATIC_MODULE(pager);
138 #endif
139
140 #ifdef STATIC_TRAY
141 REGISTER_STATIC_PLUGIN_CLASS(tray_plugin_class);
142 #endif
143
144 #ifndef DISABLE_MENU
145 #ifdef STATIC_MENU
146 REGISTER_STATIC_PLUGIN_CLASS(menu_plugin_class);
147 #endif
148 #endif
149
150 #ifdef STATIC_SPACE
151 REGISTER_STATIC_MODULE(space);
152 #endif
153 }
154
155 /* Load a dynamic plugin. */
156 static void plugin_load_dynamic(const char * type, const gchar * path)
157 {
158 PluginClass * pc = NULL;
159
160 /* Load the external module. */
161 GModule * m = g_module_open(path, G_MODULE_BIND_LAZY);
162 if (m != NULL)
163 {
164 /* Formulate the name of the expected external variable of type PluginClass. */
165 char class_name[128];
166 g_snprintf(class_name, sizeof(class_name), "%s_plugin_class", type);
167
168 /* Validate that the external variable is of type PluginClass. */
169 gpointer tmpsym;
170 if (( ! g_module_symbol(m, class_name, &tmpsym)) /* Ensure symbol is present */
171 || ((pc = tmpsym) == NULL)
172 || (pc->structure_size != sizeof(PluginClass)) /* Then check versioning information */
173 || (pc->structure_version != PLUGINCLASS_VERSION)
174 || (strcmp(type, pc->type) != 0)) /* Then and only then access other fields; check name */
175 {
176 g_module_close(m);
177 ERR("%s.so is not a lxpanel plugin\n", type);
178 return;
179 }
180
181 /* Register the newly loaded and valid plugin. */
182 pc->gmodule = m;
183 register_plugin_class(pc, TRUE);
184 pc->count = 1;
185 }
186 }
187
188 /* Unreference a dynamic plugin. */
189 static void plugin_class_unref(PluginClass * pc)
190 {
191 pc->count -= 1;
192
193 /* If the reference count drops to zero, unload the plugin if it is dynamic and has declared itself unloadable. */
194 if ((pc->count == 0)
195 && (pc->dynamic)
196 && ( ! pc->not_unloadable))
197 {
198 g_module_close(pc->gmodule);
199 }
200 }
201
202 /* Loads all available old type plugins. Should be removed in future releases. */
203 static void plugin_get_available_classes(void)
204 {
205 /* Initialize static plugins on first call. */
206 init_plugin_class_list();
207
208 #ifndef DISABLE_PLUGINS_LOADING
209 GDir * dir = g_dir_open(PACKAGE_LIB_DIR "/lxpanel/plugins", 0, NULL);
210 if (dir != NULL)
211 {
212 const char * file;
213 while ((file = g_dir_read_name(dir)) != NULL)
214 {
215 if (g_str_has_suffix(file, ".so"))
216 {
217 char * type = g_strndup(file, strlen(file) - 3);
218 if (_find_plugin(type) == NULL)
219 {
220 /* If it has not been loaded, do it. If successful, add it to the result. */
221 char * path = g_build_filename(PACKAGE_LIB_DIR "/lxpanel/plugins", file, NULL );
222 plugin_load_dynamic(type, path);
223 g_free(path);
224 }
225 g_free(type);
226 }
227 }
228 g_dir_close(dir);
229 }
230 #endif
231 }
232
233 /* Recursively set the background of all widgets on a panel background configuration change. */
234 void plugin_widget_set_background(GtkWidget * w, Panel * p)
235 {
236 if (w != NULL)
237 {
238 if ( ! GTK_WIDGET_NO_WINDOW(w))
239 {
240 if ((p->background) || (p->transparent))
241 {
242 if (GTK_WIDGET_REALIZED(w))
243 {
244 panel_determine_background_pixmap(p, w, w->window);
245 gdk_window_invalidate_rect(w->window, 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_WIDGET_REALIZED(w))
253 {
254 gdk_window_set_back_pixmap(w->window, NULL, TRUE);
255 gtk_style_set_background(w->style, w->window, GTK_STATE_NORMAL);
256 }
257 }
258 }
259
260 /* Special handling to get tray icons redrawn. */
261 if (GTK_IS_SOCKET(w))
262 {
263 gtk_widget_hide(w);
264 gdk_window_process_all_updates();
265 gtk_widget_show(w);
266 gdk_window_process_all_updates();
267 }
268
269 /* Recursively process all children of a container. */
270 if (GTK_IS_CONTAINER(w))
271 gtk_container_foreach(GTK_CONTAINER(w), (GtkCallback) plugin_widget_set_background, p);
272 }
273 }
274
275 /* Handler for "button_press_event" signal with Plugin as parameter.
276 * External so can be used from a plugin. */
277 gboolean lxpanel_plugin_button_press_event(GtkWidget *plugin, GdkEventButton *event, Panel *panel)
278 {
279 if (event->button == 3) /* right button */
280 {
281 GtkMenu* popup = (GtkMenu*)lxpanel_get_plugin_menu(panel, plugin, FALSE);
282 gtk_menu_popup(popup, NULL, NULL, NULL, NULL, event->button, event->time);
283 return TRUE;
284 }
285 return FALSE;
286 }
287
288 /* for old plugins compatibility */
289 gboolean plugin_button_press_event(GtkWidget *widget, GdkEventButton *event, Plugin *plugin)
290 {
291 return lxpanel_plugin_button_press_event(plugin->pwid, event, PLUGIN_PANEL(plugin->pwid));
292 }
293
294 /* Helper for position-calculation callback for popup menus. */
295 void lxpanel_plugin_popup_set_position_helper(Panel * p, GtkWidget * near, GtkWidget * popup, GtkRequisition * popup_req, gint * px, gint * py)
296 {
297 /* Get the origin of the requested-near widget in screen coordinates. */
298 gint x, y;
299 gdk_window_get_origin(GDK_WINDOW(near->window), &x, &y);
300 if (x != near->allocation.x) x += near->allocation.x; /* Doesn't seem to be working according to spec; the allocation.x sometimes has the window origin in it */
301 if (y != near->allocation.y) y += near->allocation.y;
302
303 /* Dispatch on edge to lay out the popup menu with respect to the button.
304 * Also set "push-in" to avoid any case where it might flow off screen. */
305 switch (p->edge)
306 {
307 case EDGE_TOP: y += near->allocation.height; break;
308 case EDGE_BOTTOM: y -= popup_req->height; break;
309 case EDGE_LEFT: x += near->allocation.width; break;
310 case EDGE_RIGHT: x -= popup_req->width; break;
311 }
312 *px = x;
313 *py = y;
314 }
315
316 /* for old plugins compatibility */
317 void plugin_popup_set_position_helper(Plugin * p, GtkWidget * near, GtkWidget * popup, GtkRequisition * popup_req, gint * px, gint * py)
318 {
319 lxpanel_plugin_popup_set_position_helper(p->panel, near, popup, popup_req, px, py);
320 }
321
322 /* Adjust the position of a popup window to ensure that it is not hidden by the panel.
323 * It is observed that some window managers do not honor the strut that is set on the panel. */
324 void lxpanel_plugin_adjust_popup_position(GtkWidget * popup, GtkWidget * parent)
325 {
326 /* Initialize. */
327 Panel * p = PLUGIN_PANEL(parent);
328
329 /* Get the coordinates of the plugin top level widget. */
330 int x = p->cx + parent->allocation.x;
331 int y = p->cy + parent->allocation.y;
332
333 /* Adjust these coordinates according to the panel edge. */
334 switch (p->edge)
335 {
336 case EDGE_TOP:
337 y += parent->allocation.height;
338 break;
339 case EDGE_BOTTOM:
340 y -= popup->allocation.height;
341 break;
342 case EDGE_LEFT:
343 x += parent->allocation.width;
344 break;
345 case EDGE_RIGHT:
346 x -= popup->allocation.width;
347 break;
348 }
349
350 /* Clip the coordinates to ensure that the popup remains on screen. */
351 int screen_width = gdk_screen_width();
352 int screen_height = gdk_screen_height();
353 if ((x + popup->allocation.width) > screen_width) x = screen_width - popup->allocation.width;
354 if ((y + popup->allocation.height) > screen_height) y = screen_height - popup->allocation.height;
355
356 /* Move the popup to position. */
357 gdk_window_move(popup->window, x, y);
358 }
359
360 /* for old plugins compatibility */
361 void plugin_adjust_popup_position(GtkWidget * popup, Plugin * plugin)
362 {
363 lxpanel_plugin_adjust_popup_position(popup, plugin->pwid);
364 }
365
366 /* Open a specified path in a file manager. */
367 static gboolean _open_dir_in_file_manager(GAppLaunchContext* ctx, GList* folder_infos,
368 gpointer user_data, GError** err)
369 {
370 FmFileInfo *fi = folder_infos->data; /* only first is used */
371 GAppInfo *app = g_app_info_get_default_for_type("inode/directory", TRUE);
372 GFile *gf;
373 gboolean ret;
374
375 if (app == NULL)
376 {
377 g_set_error_literal(err, G_SHELL_ERROR, G_SHELL_ERROR_EMPTY_STRING,
378 _("No file manager is configured."));
379 return FALSE;
380 }
381 gf = fm_path_to_gfile(fm_file_info_get_path(fi));
382 folder_infos = g_list_prepend(NULL, gf);
383 ret = fm_app_info_launch(app, folder_infos, ctx, err);
384 g_list_free(folder_infos);
385 g_object_unref(gf);
386 g_object_unref(app);
387 return ret;
388 }
389
390 gboolean lxpanel_launch_path(Panel *panel, FmPath *path)
391 {
392 return fm_launch_path_simple(NULL, NULL, path, _open_dir_in_file_manager, NULL);
393 }
394
395 #if GLIB_CHECK_VERSION(2, 32, 0)
396 static GRecMutex _mutex;
397 #else
398 static GStaticRecMutex _mutex = G_STATIC_REC_MUTEX_INIT;
399 #endif
400
401 #ifndef DISABLE_PLUGINS_LOADING
402 FM_MODULE_DEFINE_TYPE(lxpanel_gtk, LXPanelPluginInit, 1)
403
404 static gboolean fm_module_callback_lxpanel_gtk(const char *name, gpointer init, int ver)
405 {
406 /* ignore ver for now, only 1 exists */
407 return lxpanel_register_plugin_type(name, init);
408 }
409 #endif
410
411 static gboolean old_plugins_loaded = FALSE;
412
413 void _prepare_modules(void)
414 {
415 _all_types = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
416 lxpanel_plugin_qdata = g_quark_from_static_string("LXPanel::plugin-data");
417 lxpanel_plugin_qinit = g_quark_from_static_string("LXPanel::plugin-init");
418 lxpanel_plugin_qconf = g_quark_from_static_string("LXPanel::plugin-conf");
419 lxpanel_plugin_qpanel = g_quark_from_static_string("LXPanel::plugin-panel");
420 #ifndef DISABLE_PLUGINS_LOADING
421 fm_modules_add_directory(PACKAGE_LIB_DIR "/lxpanel/plugins");
422 fm_module_register_lxpanel_gtk();
423 #endif
424 }
425
426 void _unload_modules(void)
427 {
428 GHashTableIter iter;
429 gpointer key, val;
430
431 #ifndef DISABLE_PLUGINS_LOADING
432 fm_module_unregister_type("lxpanel_gtk");
433 #endif
434 g_hash_table_iter_init(&iter, _all_types);
435 while(g_hash_table_iter_next(&iter, &key, &val))
436 {
437 register LXPanelPluginInit *init = val;
438 if (init->new_instance == NULL) /* old type of plugin */
439 {
440 plugin_class_unref(init->_reserved1);
441 g_free(val);
442 }
443 }
444 g_hash_table_destroy(_all_types);
445 old_plugins_loaded = FALSE;
446 }
447
448 gboolean lxpanel_register_plugin_type(const char *name, LXPanelPluginInit *init)
449 {
450 LXPanelPluginInit *data;
451
452 /* validate it */
453 if (init->new_instance == NULL || name == NULL || name[0] == '\0')
454 return FALSE;
455 #if GLIB_CHECK_VERSION(2, 32, 0)
456 g_rec_mutex_lock(&_mutex);
457 #else
458 g_static_rec_mutex_lock(&_mutex);
459 #endif
460 /* test if it's registered already */
461 data = _find_plugin(name);
462 if (data == NULL)
463 {
464 if (init->init)
465 init->init();
466 g_hash_table_insert(_all_types, g_strdup(name), init);
467 }
468 #if GLIB_CHECK_VERSION(2, 32, 0)
469 g_rec_mutex_unlock(&_mutex);
470 #else
471 g_static_rec_mutex_unlock(&_mutex);
472 #endif
473 return (data == NULL);
474 }
475
476 static void _old_plugin_save_hook(const config_setting_t * setting, FILE * f, gpointer user_data)
477 {
478 Plugin *pl = user_data;
479 PluginClass *pc = pl->class;
480 if (pc->save)
481 pc->save(pl, f);
482 }
483
484 /* This is called right before Plugin instance is destroyed */
485 static void _old_plugin_destroy(gpointer data)
486 {
487 Plugin *pl = data;
488
489 /* Run the destructor. */
490 pl->class->destructor(pl);
491 plugin_class_unref(pl->class);
492
493 /* Free the Plugin structure. */
494 g_free(pl);
495 }
496
497 //static void on_size_allocate(GtkWidget *widget, GdkRectangle *allocation, Panel *p)
498 //{
499 // _queue_panel_calculate_size(p);
500 //}
501
502 GtkWidget *lxpanel_add_plugin(Panel *p, const char *name, config_setting_t *cfg, gint at)
503 {
504 LXPanelPluginInit *init;
505 GtkWidget *widget;
506 config_setting_t *s, *pconf;
507 gint expand, padding = 0, border = 0, i;
508
509 CHECK_MODULES();
510 if (!old_plugins_loaded)
511 plugin_get_available_classes();
512 old_plugins_loaded = TRUE;
513 init = _find_plugin(name);
514 if (init == NULL)
515 return NULL;
516 /* prepare widget settings */
517 if (!init->expand_available)
518 expand = 0;
519 else if ((s = config_setting_get_member(cfg, "expand")))
520 expand = config_setting_get_int(s);
521 else
522 expand = init->expand_default;
523 s = config_setting_get_member(cfg, "padding");
524 if (s)
525 padding = config_setting_get_int(s);
526 s = config_setting_get_member(cfg, "border");
527 if (s)
528 border = config_setting_get_int(s);
529 /* prepare config and create it if need */
530 s = config_setting_add(cfg, "", PANEL_CONF_TYPE_LIST);
531 for (i = 0; (pconf = config_setting_get_elem(s, i)); i++)
532 if (strcmp(config_setting_get_name(pconf), "Config") == 0)
533 break;
534 if (!pconf)
535 pconf = config_setting_add(s, "Config", PANEL_CONF_TYPE_GROUP);
536 /* If this plugin can only be instantiated once, count the instantiation.
537 * This causes the configuration system to avoid displaying the plugin as one that can be added. */
538 if (init->new_instance) /* new style of plugin */
539 {
540 widget = init->new_instance(p, pconf);
541 if (widget == NULL)
542 return widget;
543 if (init->button_press_event)
544 g_signal_connect(widget, "button-press-event",
545 G_CALLBACK(init->button_press_event), p);
546 else
547 g_signal_connect(widget, "button-press-event",
548 G_CALLBACK(lxpanel_plugin_button_press_event), p);
549 }
550 else
551 {
552 Plugin *pl = g_new0(Plugin, 1);
553 PluginClass *pc = init->_reserved1;
554 char *conf = config_setting_to_string(pconf), *fp;
555
556 pl->class = pc;
557 pl->panel = p;
558 widget = NULL;
559 fp = &conf[9]; /* skip "Config {\n" */
560 /* g_debug("created conf: %s",conf); */
561 /* Call the constructor.
562 * It is responsible for parsing the parameters, and setting "pwid" to the top level widget. */
563 if (pc->constructor(pl, &fp))
564 widget = pl->pwid;
565 g_free(conf);
566
567 if (widget == NULL) /* failed */
568 {
569 g_free(pl);
570 return widget;
571 }
572
573 pc->count += 1;
574 config_setting_set_save_hook(pconf, _old_plugin_save_hook, pl);
575 lxpanel_plugin_set_data(widget, pl, _old_plugin_destroy);
576 }
577 gtk_widget_set_name(widget, init->name);
578 gtk_box_pack_start(GTK_BOX(p->box), widget, expand, TRUE, padding);
579 gtk_container_set_border_width(GTK_CONTAINER(widget), border);
580 // g_signal_connect(widget, "size-allocate", G_CALLBACK(on_size_allocate), p);
581 gtk_widget_show(widget);
582 g_object_set_qdata(G_OBJECT(widget), lxpanel_plugin_qconf, cfg);
583 g_object_set_qdata(G_OBJECT(widget), lxpanel_plugin_qinit, init);
584 g_object_set_qdata(G_OBJECT(widget), lxpanel_plugin_qpanel, p);
585 return widget;
586 }
587
588 /* transfer none - note that not all fields are valid there */
589 GHashTable *lxpanel_get_all_types(void)
590 {
591 return _all_types;
592 }