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