83440e812ecc7225a9f58a3615ca90220f22372e
[lxde/lxpanel.git] / src / plugin.c
1 /**
2 * Copyright (c) 2006 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 "plugin.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
36 //#define DEBUG
37 #include "dbg.h"
38
39 static GList * pcl = NULL; /* List of PluginClass structures */
40
41 static void register_plugin_class(PluginClass * pc, gboolean dynamic);
42 static void init_plugin_class_list(void);
43 static PluginClass * plugin_find_class(const char * type);
44 static PluginClass * plugin_load_dynamic(const char * type, const gchar * path);
45 static void plugin_class_unref(PluginClass * pc);
46
47 /* Dynamic parameter for static (built-in) plugins must be FALSE so we will not try to unload them */
48 #define REGISTER_STATIC_PLUGIN_CLASS(pc) \
49 do {\
50 extern PluginClass pc;\
51 register_plugin_class(&pc, FALSE);\
52 } while (0)
53
54 /* Register a PluginClass. */
55 static void register_plugin_class(PluginClass * pc, gboolean dynamic)
56 {
57 pcl = g_list_append(pcl, pc);
58 pc->dynamic = dynamic;
59 }
60
61 /* Initialize the static plugins. */
62 static void init_plugin_class_list(void)
63 {
64 #ifdef STATIC_SEPARATOR
65 REGISTER_STATIC_PLUGIN_CLASS(separator_plugin_class);
66 #endif
67
68 #ifdef STATIC_LAUNCHBAR
69 REGISTER_STATIC_PLUGIN_CLASS(launchbar_plugin_class);
70 #endif
71
72 #ifdef STATIC_LAUNCHTASKBAR
73 REGISTER_STATIC_PLUGIN_CLASS(launchtaskbar_plugin_class);
74 #endif
75
76 #ifdef STATIC_DCLOCK
77 REGISTER_STATIC_PLUGIN_CLASS(dclock_plugin_class);
78 #endif
79
80 #ifdef STATIC_WINCMD
81 REGISTER_STATIC_PLUGIN_CLASS(wincmd_plugin_class);
82 #endif
83
84 #ifdef STATIC_DIRMENU
85 REGISTER_STATIC_PLUGIN_CLASS(dirmenu_plugin_class);
86 #endif
87
88 #ifdef STATIC_TASKBAR
89 REGISTER_STATIC_PLUGIN_CLASS(taskbar_plugin_class);
90 #endif
91
92 #ifdef STATIC_PAGER
93 REGISTER_STATIC_PLUGIN_CLASS(pager_plugin_class);
94 #endif
95
96 #ifdef STATIC_TRAY
97 REGISTER_STATIC_PLUGIN_CLASS(tray_plugin_class);
98 #endif
99
100 #ifndef DISABLE_MENU
101 #ifdef STATIC_MENU
102 REGISTER_STATIC_PLUGIN_CLASS(menu_plugin_class);
103 #endif
104 #endif
105
106 #ifdef STATIC_SPACE
107 REGISTER_STATIC_PLUGIN_CLASS(space_plugin_class);
108 #endif
109 }
110
111 /* Look up a plugin class by name. */
112 static PluginClass * plugin_find_class(const char * type)
113 {
114 GList * tmp;
115 for (tmp = pcl; tmp != NULL; tmp = g_list_next(tmp))
116 {
117 PluginClass * pc = (PluginClass *) tmp->data;
118 if (g_ascii_strcasecmp(type, pc->type) == 0)
119 return pc;
120 }
121 return NULL;
122 }
123
124 /* Load a dynamic plugin. */
125 static PluginClass * plugin_load_dynamic(const char * type, const gchar * path)
126 {
127 PluginClass * pc = NULL;
128
129 /* Load the external module. */
130 GModule * m = g_module_open(path, G_MODULE_BIND_LAZY);
131 if (m != NULL)
132 {
133 /* Formulate the name of the expected external variable of type PluginClass. */
134 char class_name[128];
135 g_snprintf(class_name, sizeof(class_name), "%s_plugin_class", type);
136
137 /* Validate that the external variable is of type PluginClass. */
138 gpointer tmpsym;
139 if (( ! g_module_symbol(m, class_name, &tmpsym)) /* Ensure symbol is present */
140 || ((pc = tmpsym) == NULL)
141 || (pc->structure_size != sizeof(PluginClass)) /* Then check versioning information */
142 || (pc->structure_version != PLUGINCLASS_VERSION)
143 || (strcmp(type, pc->type) != 0)) /* Then and only then access other fields; check name */
144 {
145 g_module_close(m);
146 ERR("%s.so is not a lxpanel plugin\n", type);
147 return NULL;
148 }
149
150 /* Register the newly loaded and valid plugin. */
151 pc->gmodule = m;
152 register_plugin_class(pc, TRUE);
153 }
154 return pc;
155 }
156
157 /* Create an instance of a plugin with a specified name, loading it if external. */
158 Plugin * plugin_load(char * type)
159 {
160 /* Initialize static plugins on first call. */
161 if (pcl == NULL)
162 init_plugin_class_list();
163
164 /* Look up the PluginClass. */
165 PluginClass * pc = plugin_find_class(type);
166
167 #ifndef DISABLE_PLUGINS_LOADING
168 /* If not found and dynamic loading is available, try to locate an external plugin. */
169 if ((pc == NULL) && (g_module_supported()))
170 {
171 gchar path[PATH_MAX];
172 g_snprintf(path, sizeof(path), PACKAGE_LIB_DIR "/lxpanel/plugins/%s.so", type);
173 pc = plugin_load_dynamic(type, path);
174 }
175 #endif /* DISABLE_PLUGINS_LOADING */
176
177 /* If not found, return failure. */
178 if (pc == NULL)
179 return NULL;
180
181 /* Instantiate the plugin */
182 Plugin * plug = g_new0(Plugin, 1);
183 plug->class = pc;
184 pc->count += 1;
185 return plug;
186 }
187
188 /* Configure and start a plugin by calling its constructor. */
189 int plugin_start(Plugin * pl, char ** fp)
190 {
191 /* Call the constructor.
192 * It is responsible for parsing the parameters, and setting "pwid" to the top level widget. */
193 if ( ! pl->class->constructor(pl, fp))
194 return 0;
195
196 /* If this plugin can only be instantiated once, count the instantiation.
197 * This causes the configuration system to avoid displaying the plugin as one that can be added. */
198 if (pl->class->one_per_system)
199 pl->class->one_per_system_instantiated = TRUE;
200
201 /* If the plugin has a top level widget, add it to the panel's container. */
202 if (pl->pwid != NULL)
203 {
204 gtk_widget_set_name(pl->pwid, pl->class->type);
205 gtk_box_pack_start(GTK_BOX(pl->panel->box), pl->pwid, pl->expand, TRUE, pl->padding);
206 gtk_container_set_border_width(GTK_CONTAINER(pl->pwid), pl->border);
207 gtk_widget_show(pl->pwid);
208 }
209 return 1;
210 }
211
212 /* Unload a plugin if initialization fails. */
213 void plugin_unload(Plugin * pl)
214 {
215 plugin_class_unref(pl->class);
216 g_free(pl);
217 }
218
219 /* Delete a plugin. */
220 void plugin_delete(Plugin * pl)
221 {
222 Panel * p = pl->panel;
223 PluginClass * pc = pl->class;
224
225 /* If a plugin configuration dialog is open, close it. */
226 if (p->plugin_pref_dialog != NULL)
227 {
228 gtk_widget_destroy(p->plugin_pref_dialog);
229 p->plugin_pref_dialog = NULL;
230 }
231
232 /* Run the destructor and then destroy the top level widget.
233 * This prevents problems with the plugin destroying child widgets. */
234 pc->destructor(pl);
235 if (pl->pwid != NULL)
236 gtk_widget_destroy(pl->pwid);
237
238 /* Data structure bookkeeping. */
239 pc->one_per_system_instantiated = FALSE;
240 plugin_class_unref(pc);
241
242 /* Free the Plugin structure. */
243 g_free(pl);
244 }
245
246 /* Unreference a dynamic plugin. */
247 static void plugin_class_unref(PluginClass * pc)
248 {
249 pc->count -= 1;
250
251 /* If the reference count drops to zero, unload the plugin if it is dynamic and has declared itself unloadable. */
252 if ((pc->count == 0)
253 && (pc->dynamic)
254 && ( ! pc->not_unloadable))
255 {
256 pcl = g_list_remove(pcl, pc);
257 g_module_close(pc->gmodule);
258 }
259 }
260
261 /* Get a list of all available plugin classes.
262 * Returns a newly allocated GList which should be freed with plugin_class_list_free(list). */
263 GList * plugin_get_available_classes(void)
264 {
265 /* Initialize static plugins on first call. */
266 if (pcl == NULL)
267 init_plugin_class_list();
268
269 /* Loop over all classes to formulate the result.
270 * Increase the reference count; it will be decreased in plugin_class_list_free. */
271 GList * classes = NULL;
272 GList * l;
273 for (l = pcl; l != NULL; l = l->next)
274 {
275 PluginClass * pc = (PluginClass *) l->data;
276 classes = g_list_prepend(classes, pc);
277 pc->count += 1;
278 }
279
280 #ifndef DISABLE_PLUGINS_LOADING
281 GDir * dir = g_dir_open(PACKAGE_LIB_DIR "/lxpanel/plugins", 0, NULL);
282 if (dir != NULL)
283 {
284 const char * file;
285 while ((file = g_dir_read_name(dir)) != NULL)
286 {
287 if (g_str_has_suffix(file, ".so"))
288 {
289 char * type = g_strndup(file, strlen(file) - 3);
290 if (plugin_find_class(type) == NULL)
291 {
292 /* If it has not been loaded, do it. If successful, add it to the result. */
293 char * path = g_build_filename(PACKAGE_LIB_DIR "/lxpanel/plugins", file, NULL );
294 PluginClass * pc = plugin_load_dynamic(type, path);
295 if (pc != NULL)
296 {
297 pc->count += 1;
298 classes = g_list_prepend(classes, pc);
299 }
300 g_free(path);
301 }
302 g_free(type);
303 }
304 }
305 g_dir_close(dir);
306 }
307 #endif
308 return classes;
309 }
310
311 /* Free the list allocated by plugin_get_available_classes. */
312 void plugin_class_list_free(GList * list)
313 {
314 g_list_foreach(list, (GFunc) plugin_class_unref, NULL);
315 g_list_free(list);
316 }
317
318 /* Recursively set the background of all widgets on a panel background configuration change. */
319 void plugin_widget_set_background(GtkWidget * w, Panel * p)
320 {
321 if (w != NULL)
322 {
323 if ( ! GTK_WIDGET_NO_WINDOW(w))
324 {
325 if ((p->background) || (p->transparent))
326 {
327 if (GTK_WIDGET_REALIZED(w))
328 {
329 panel_determine_background_pixmap(p, w, w->window);
330 gdk_window_invalidate_rect(w->window, NULL, TRUE);
331 }
332 }
333 else
334 {
335 /* Set background according to the current GTK style. */
336 gtk_widget_set_app_paintable(w, FALSE);
337 if (GTK_WIDGET_REALIZED(w))
338 {
339 gdk_window_set_back_pixmap(w->window, NULL, TRUE);
340 gtk_style_set_background(w->style, w->window, GTK_STATE_NORMAL);
341 }
342 }
343 }
344
345 /* Special handling to get tray icons redrawn. */
346 if (GTK_IS_SOCKET(w))
347 {
348 gtk_widget_hide(w);
349 gdk_window_process_all_updates();
350 gtk_widget_show(w);
351 gdk_window_process_all_updates();
352 }
353
354 /* Recursively process all children of a container. */
355 if (GTK_IS_CONTAINER(w))
356 gtk_container_foreach(GTK_CONTAINER(w), (GtkCallback) plugin_widget_set_background, p);
357 }
358 }
359
360 /* Handler for "button_press_event" signal with Plugin as parameter.
361 * External so can be used from a plugin. */
362 gboolean plugin_button_press_event(GtkWidget *widget, GdkEventButton *event, Plugin *plugin)
363 {
364 if (event->button == 3) /* right button */
365 {
366 GtkMenu* popup = (GtkMenu*) lxpanel_get_panel_menu(plugin->panel, plugin, FALSE);
367 gtk_menu_popup(popup, NULL, NULL, NULL, NULL, event->button, event->time);
368 return TRUE;
369 }
370 return FALSE;
371 }
372
373 /* Helper for position-calculation callback for popup menus. */
374 void plugin_popup_set_position_helper(Plugin * p, GtkWidget * near, GtkWidget * popup, GtkRequisition * popup_req, gint * px, gint * py)
375 {
376 /* Get the origin of the requested-near widget in screen coordinates. */
377 gint x, y;
378 gdk_window_get_origin(GDK_WINDOW(near->window), &x, &y);
379 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 */
380 if (y != near->allocation.y) y += near->allocation.y;
381
382 /* Dispatch on edge to lay out the popup menu with respect to the button.
383 * Also set "push-in" to avoid any case where it might flow off screen. */
384 switch (p->panel->edge)
385 {
386 case EDGE_TOP: y += near->allocation.height; break;
387 case EDGE_BOTTOM: y -= popup_req->height; break;
388 case EDGE_LEFT: x += near->allocation.width; break;
389 case EDGE_RIGHT: x -= popup_req->width; break;
390 }
391 *px = x;
392 *py = y;
393 }
394
395 /* Adjust the position of a popup window to ensure that it is not hidden by the panel.
396 * It is observed that some window managers do not honor the strut that is set on the panel. */
397 void plugin_adjust_popup_position(GtkWidget * popup, Plugin * plugin)
398 {
399 /* Initialize. */
400 Panel * p = plugin->panel;
401 GtkWidget * parent = plugin->pwid;
402
403 /* Get the coordinates of the plugin top level widget. */
404 int x = p->cx + parent->allocation.x;
405 int y = p->cy + parent->allocation.y;
406
407 /* Adjust these coordinates according to the panel edge. */
408 switch (p->edge)
409 {
410 case EDGE_TOP:
411 y += parent->allocation.height;
412 break;
413 case EDGE_BOTTOM:
414 y -= popup->allocation.height;
415 break;
416 case EDGE_LEFT:
417 x += parent->allocation.width;
418 break;
419 case EDGE_RIGHT:
420 x -= popup->allocation.width;
421 break;
422 }
423
424 /* Clip the coordinates to ensure that the popup remains on screen. */
425 int screen_width = gdk_screen_width();
426 int screen_height = gdk_screen_height();
427 if ((x + popup->allocation.width) > screen_width) x = screen_width - popup->allocation.width;
428 if ((y + popup->allocation.height) > screen_height) y = screen_height - popup->allocation.height;
429
430 /* Move the popup to position. */
431 gdk_window_move(popup->window, x, y);
432 }