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