src/plugin.c

branch
gaim
changeset 20470
77693555855f
parent 13071
b98e72d4089a
parent 20469
b2836a24d81e
child 20471
1966704b3e42
equal deleted inserted replaced
13071:b98e72d4089a 20470:77693555855f
1 /*
2 * gaim
3 *
4 * Gaim is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 */
22 #include "internal.h"
23
24 #include "accountopt.h"
25 #include "debug.h"
26 #include "notify.h"
27 #include "prefs.h"
28 #include "prpl.h"
29 #include "request.h"
30 #include "signals.h"
31 #include "util.h"
32 #include "version.h"
33
34 typedef struct
35 {
36 GHashTable *commands;
37 size_t command_count;
38
39 } GaimPluginIpcInfo;
40
41 typedef struct
42 {
43 GaimCallback func;
44 GaimSignalMarshalFunc marshal;
45
46 int num_params;
47 GaimValue **params;
48 GaimValue *ret_value;
49
50 } GaimPluginIpcCommand;
51
52 static GList *search_paths = NULL;
53 static GList *plugins = NULL;
54 static GList *loaded_plugins = NULL;
55 static GList *protocol_plugins = NULL;
56 #ifdef GAIM_PLUGINS
57 static GList *load_queue = NULL;
58 static GList *plugin_loaders = NULL;
59 #endif
60
61 /*
62 * TODO: I think the intention was to allow multiple load and unload
63 * callback functions. Perhaps using a GList instead of a
64 * pointer to a single function.
65 */
66 static void (*probe_cb)(void *) = NULL;
67 static void *probe_cb_data = NULL;
68 static void (*load_cb)(GaimPlugin *, void *) = NULL;
69 static void *load_cb_data = NULL;
70 static void (*unload_cb)(GaimPlugin *, void *) = NULL;
71 static void *unload_cb_data = NULL;
72
73 #ifdef GAIM_PLUGINS
74
75 static gboolean
76 has_file_extension(const char *filename, const char *ext)
77 {
78 int len, extlen;
79
80 if (filename == NULL || *filename == '\0' || ext == NULL)
81 return 0;
82
83 extlen = strlen(ext);
84 len = strlen(filename) - extlen;
85
86 if (len < 0)
87 return 0;
88
89 return (strncmp(filename + len, ext, extlen) == 0);
90 }
91
92 static gboolean
93 is_native(const char *filename)
94 {
95 const char *last_period;
96
97 last_period = strrchr(filename, '.');
98 if (last_period == NULL)
99 return FALSE;
100
101 return !(strcmp(last_period, ".dll") &
102 strcmp(last_period, ".sl") &
103 strcmp(last_period, ".so"));
104 }
105
106 static char *
107 gaim_plugin_get_basename(const char *filename)
108 {
109 const char *basename;
110 const char *last_period;
111
112 basename = strrchr(filename, G_DIR_SEPARATOR);
113 if (basename != NULL)
114 basename++;
115 else
116 basename = filename;
117
118 if (is_native(basename) &&
119 ((last_period = strrchr(basename, '.')) != NULL))
120 return g_strndup(basename, (last_period - basename));
121
122 return g_strdup(basename);
123 }
124
125 static gboolean
126 loader_supports_file(GaimPlugin *loader, const char *filename)
127 {
128 GList *exts;
129
130 for (exts = GAIM_PLUGIN_LOADER_INFO(loader)->exts; exts != NULL; exts = exts->next) {
131 if (has_file_extension(filename, (char *)exts->data)) {
132 return TRUE;
133 }
134 }
135
136 return FALSE;
137 }
138
139 static GaimPlugin *
140 find_loader_for_plugin(const GaimPlugin *plugin)
141 {
142 GaimPlugin *loader;
143 GList *l;
144
145 if (plugin->path == NULL)
146 return NULL;
147
148 for (l = gaim_plugins_get_loaded(); l != NULL; l = l->next) {
149 loader = l->data;
150
151 if (loader->info->type == GAIM_PLUGIN_LOADER &&
152 loader_supports_file(loader, plugin->path)) {
153
154 return loader;
155 }
156
157 loader = NULL;
158 }
159
160 return NULL;
161 }
162
163 #endif /* GAIM_PLUGINS */
164
165 /**
166 * Negative if a before b, 0 if equal, positive if a after b.
167 */
168 static gint
169 compare_prpl(GaimPlugin *a, GaimPlugin *b)
170 {
171 if(GAIM_IS_PROTOCOL_PLUGIN(a)) {
172 if(GAIM_IS_PROTOCOL_PLUGIN(b))
173 return strcmp(a->info->name, b->info->name);
174 else
175 return -1;
176 } else {
177 if(GAIM_IS_PROTOCOL_PLUGIN(b))
178 return 1;
179 else
180 return 0;
181 }
182 }
183
184 GaimPlugin *
185 gaim_plugin_new(gboolean native, const char *path)
186 {
187 GaimPlugin *plugin;
188
189 plugin = g_new0(GaimPlugin, 1);
190
191 plugin->native_plugin = native;
192 plugin->path = (path == NULL ? NULL : g_strdup(path));
193
194 return plugin;
195 }
196
197 GaimPlugin *
198 gaim_plugin_probe(const char *filename)
199 {
200 #ifdef GAIM_PLUGINS
201 GaimPlugin *plugin = NULL;
202 GaimPlugin *loader;
203 gpointer unpunned;
204 gchar *basename = NULL;
205 gboolean (*gaim_init_plugin)(GaimPlugin *);
206
207 gaim_debug_misc("plugins", "probing %s\n", filename);
208 g_return_val_if_fail(filename != NULL, NULL);
209
210 if (!g_file_test(filename, G_FILE_TEST_EXISTS))
211 return NULL;
212
213 /* If this plugin has already been probed then exit */
214 basename = gaim_plugin_get_basename(filename);
215 plugin = gaim_plugins_find_with_basename(basename);
216 g_free(basename);
217 if (plugin != NULL)
218 {
219 if (!strcmp(filename, plugin->path))
220 return plugin;
221 else if (!gaim_plugin_is_unloadable(plugin))
222 {
223 gaim_debug_info("plugins", "Not loading %s. "
224 "Another plugin with the same name (%s) has already been loaded.\n",
225 filename, plugin->path);
226 return plugin;
227 }
228 else
229 {
230 /* The old plugin was a different file and it was unloadable.
231 * There's no guarantee that this new file with the same name
232 * will be loadable, but unless it fails in one of the silent
233 * ways and the first one didn't, it's not any worse. The user
234 * will still see a greyed-out plugin, which is what we want. */
235 gaim_plugin_destroy(plugin);
236 }
237 }
238
239 plugin = gaim_plugin_new(has_file_extension(filename, G_MODULE_SUFFIX), filename);
240
241 if (plugin->native_plugin) {
242 const char *error;
243 #ifdef _WIN32
244 /* Suppress error popups for failing to load plugins */
245 UINT old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
246 #endif
247
248 /*
249 * We pass G_MODULE_BIND_LOCAL here to prevent symbols from
250 * plugins being added to the global name space.
251 *
252 * G_MODULE_BIND_LOCAL was added in glib 2.3.3.
253 * TODO: I guess there's nothing we can do about that?
254 */
255 #if GLIB_CHECK_VERSION(2,3,3)
256 plugin->handle = g_module_open(filename, G_MODULE_BIND_LOCAL);
257 #else
258 plugin->handle = g_module_open(filename, 0);
259 #endif
260
261 if (plugin->handle == NULL)
262 {
263 const char *error = g_module_error();
264 if (error != NULL && gaim_str_has_prefix(error, filename))
265 {
266 error = error + strlen(filename);
267
268 /* These are just so we don't crash. If we
269 * got this far, they should always be true. */
270 if (*error == ':')
271 error++;
272 if (*error == ' ')
273 error++;
274 }
275
276 if (error == NULL || !*error)
277 {
278 plugin->error = g_strdup(_("Unknown error"));
279 gaim_debug_error("plugins", "%s is unloadable: Unknown error\n",
280 plugin->path);
281 }
282 else
283 {
284 plugin->error = g_strdup(error);
285 gaim_debug_error("plugins", "%s is unloadable: %s\n",
286 plugin->path, plugin->error);
287 }
288 #if GLIB_CHECK_VERSION(2,3,3)
289 plugin->handle = g_module_open(filename, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
290 #else
291 plugin->handle = g_module_open(filename, G_MODULE_BIND_LAZY);
292 #endif
293
294 if (plugin->handle == NULL)
295 {
296 #ifdef _WIN32
297 /* Restore the original error mode */
298 SetErrorMode(old_error_mode);
299 #endif
300 gaim_plugin_destroy(plugin);
301 return NULL;
302 }
303 else
304 {
305 /* We were able to load the plugin with lazy symbol binding.
306 * This means we're missing some symbol. Mark it as
307 * unloadable and keep going so we get the info to display
308 * to the user so they know to rebuild this plugin. */
309 plugin->unloadable = TRUE;
310 }
311 }
312
313 if (!g_module_symbol(plugin->handle, "gaim_init_plugin",
314 &unpunned))
315 {
316 gaim_debug_error("plugins", "%s is not usable because the "
317 "'gaim_init_plugin' symbol could not be "
318 "found. Does the plugin call the "
319 "GAIM_INIT_PLUGIN() macro?\n", plugin->path);
320
321 g_module_close(plugin->handle);
322 error = g_module_error();
323 if (error != NULL)
324 gaim_debug_error("plugins", "Error closing module %s: %s\n",
325 plugin->path, error);
326 plugin->handle = NULL;
327
328 #ifdef _WIN32
329 /* Restore the original error mode */
330 SetErrorMode(old_error_mode);
331 #endif
332 gaim_plugin_destroy(plugin);
333 return NULL;
334 }
335 gaim_init_plugin = unpunned;
336
337 #ifdef _WIN32
338 /* Restore the original error mode */
339 SetErrorMode(old_error_mode);
340 #endif
341 }
342 else {
343 loader = find_loader_for_plugin(plugin);
344
345 if (loader == NULL) {
346 gaim_plugin_destroy(plugin);
347 return NULL;
348 }
349
350 gaim_init_plugin = GAIM_PLUGIN_LOADER_INFO(loader)->probe;
351 }
352
353 if (!gaim_init_plugin(plugin) || plugin->info == NULL)
354 {
355 gaim_plugin_destroy(plugin);
356 return NULL;
357 }
358
359 /* Really old plugins. */
360 if (plugin->info->magic != GAIM_PLUGIN_MAGIC)
361 {
362 if (plugin->info->magic >= 2 && plugin->info->magic <= 4)
363 {
364 struct _GaimPluginInfo2
365 {
366 unsigned int api_version;
367 GaimPluginType type;
368 char *ui_requirement;
369 unsigned long flags;
370 GList *dependencies;
371 GaimPluginPriority priority;
372
373 char *id;
374 char *name;
375 char *version;
376 char *summary;
377 char *description;
378 char *author;
379 char *homepage;
380
381 gboolean (*load)(GaimPlugin *plugin);
382 gboolean (*unload)(GaimPlugin *plugin);
383 void (*destroy)(GaimPlugin *plugin);
384
385 void *ui_info;
386 void *extra_info;
387 GaimPluginUiInfo *prefs_info;
388 GList *(*actions)(GaimPlugin *plugin, gpointer context);
389 } *info2 = (struct _GaimPluginInfo2 *)plugin->info;
390
391 /* This leaks... but only for ancient plugins, so deal with it. */
392 plugin->info = g_new0(GaimPluginInfo, 1);
393
394 /* We don't really need all these to display the plugin info, but
395 * I'm copying them all for good measure. */
396 plugin->info->magic = info2->api_version;
397 plugin->info->type = info2->type;
398 plugin->info->ui_requirement = info2->ui_requirement;
399 plugin->info->flags = info2->flags;
400 plugin->info->dependencies = info2->dependencies;
401 plugin->info->id = info2->id;
402 plugin->info->name = info2->name;
403 plugin->info->version = info2->version;
404 plugin->info->summary = info2->summary;
405 plugin->info->description = info2->description;
406 plugin->info->author = info2->author;
407 plugin->info->homepage = info2->homepage;
408 plugin->info->load = info2->load;
409 plugin->info->unload = info2->unload;
410 plugin->info->destroy = info2->destroy;
411 plugin->info->ui_info = info2->ui_info;
412 plugin->info->extra_info = info2->extra_info;
413
414 if (info2->api_version >= 3)
415 plugin->info->prefs_info = info2->prefs_info;
416
417 if (info2->api_version >= 4)
418 plugin->info->actions = info2->actions;
419
420
421 plugin->error = g_strdup_printf(_("Plugin magic mismatch %d (need %d)"),
422 plugin->info->magic, GAIM_PLUGIN_MAGIC);
423 gaim_debug_error("plugins", "%s is unloadable: Plugin magic mismatch %d (need %d)\n",
424 plugin->path, plugin->info->magic, GAIM_PLUGIN_MAGIC);
425 plugin->unloadable = TRUE;
426 return plugin;
427 }
428
429 gaim_debug_error("plugins", "%s is unloadable: Plugin magic mismatch %d (need %d)\n",
430 plugin->path, plugin->info->magic, GAIM_PLUGIN_MAGIC);
431 gaim_plugin_destroy(plugin);
432 return NULL;
433 }
434
435 if (plugin->info->major_version != GAIM_MAJOR_VERSION ||
436 plugin->info->minor_version > GAIM_MINOR_VERSION)
437 {
438 plugin->error = g_strdup_printf(_("ABI version mismatch %d.%d.x (need %d.%d.x)"),
439 plugin->info->major_version, plugin->info->minor_version,
440 GAIM_MAJOR_VERSION, GAIM_MINOR_VERSION);
441 gaim_debug_error("plugins", "%s is unloadable: ABI version mismatch %d.%d.x (need %d.%d.x)\n",
442 plugin->path, plugin->info->major_version, plugin->info->minor_version,
443 GAIM_MAJOR_VERSION, GAIM_MINOR_VERSION);
444 plugin->unloadable = TRUE;
445 return plugin;
446 }
447
448 /* If plugin is a PRPL, make sure it implements the required functions */
449 if ((plugin->info->type == GAIM_PLUGIN_PROTOCOL) && (
450 (GAIM_PLUGIN_PROTOCOL_INFO(plugin)->list_icon == NULL) ||
451 (GAIM_PLUGIN_PROTOCOL_INFO(plugin)->login == NULL) ||
452 (GAIM_PLUGIN_PROTOCOL_INFO(plugin)->close == NULL)))
453 {
454 plugin->error = g_strdup(_("Plugin does not implement all required functions"));
455 gaim_debug_error("plugins", "%s is unloadable: Plugin does not implement all required functions\n",
456 plugin->path);
457 plugin->unloadable = TRUE;
458 return plugin;
459 }
460
461 return plugin;
462 #else
463 return NULL;
464 #endif /* !GAIM_PLUGINS */
465 }
466
467 static gint
468 compare_plugins(gconstpointer a, gconstpointer b)
469 {
470 const GaimPlugin *plugina = a;
471 const GaimPlugin *pluginb = b;
472
473 return strcmp(plugina->info->name, pluginb->info->name);
474 }
475
476 gboolean
477 gaim_plugin_load(GaimPlugin *plugin)
478 {
479 #ifdef GAIM_PLUGINS
480 GList *dep_list = NULL;
481 GList *l;
482
483 g_return_val_if_fail(plugin != NULL, FALSE);
484
485 if (gaim_plugin_is_loaded(plugin))
486 return TRUE;
487
488 if (gaim_plugin_is_unloadable(plugin))
489 return FALSE;
490
491 g_return_val_if_fail(plugin->error == NULL, FALSE);
492
493 /*
494 * Go through the list of the plugin's dependencies.
495 *
496 * First pass: Make sure all the plugins needed are probed.
497 */
498 for (l = plugin->info->dependencies; l != NULL; l = l->next)
499 {
500 const char *dep_name = (const char *)l->data;
501 GaimPlugin *dep_plugin;
502
503 dep_plugin = gaim_plugins_find_with_id(dep_name);
504
505 if (dep_plugin == NULL)
506 {
507 char *tmp;
508
509 tmp = g_strdup_printf(_("The required plugin %s was not found. "
510 "Please install this plugin and try again."),
511 dep_name);
512
513 gaim_notify_error(NULL, NULL,
514 _("Gaim encountered errors loading the plugin."), tmp);
515 g_free(tmp);
516
517 if (dep_list != NULL)
518 g_list_free(dep_list);
519
520 return FALSE;
521 }
522
523 dep_list = g_list_append(dep_list, dep_plugin);
524 }
525
526 /* Second pass: load all the required plugins. */
527 for (l = dep_list; l != NULL; l = l->next)
528 {
529 GaimPlugin *dep_plugin = (GaimPlugin *)l->data;
530
531 if (!gaim_plugin_is_loaded(dep_plugin))
532 {
533 if (!gaim_plugin_load(dep_plugin))
534 {
535 char *tmp;
536
537 tmp = g_strdup_printf(_("The required plugin %s was unable to load."),
538 plugin->info->name);
539
540 gaim_notify_error(NULL, NULL,
541 _("Gaim was unable to load your plugin."), tmp);
542 g_free(tmp);
543
544 if (dep_list != NULL)
545 g_list_free(dep_list);
546
547 return FALSE;
548 }
549 }
550 }
551
552 /* Third pass: note that other plugins are dependencies of this plugin.
553 * This is done separately in case we had to bail out earlier. */
554 for (l = dep_list; l != NULL; l = l->next)
555 {
556 GaimPlugin *dep_plugin = (GaimPlugin *)l->data;
557 dep_plugin->dependent_plugins = g_list_prepend(dep_plugin->dependent_plugins, plugin->info->id);
558 }
559
560 if (dep_list != NULL)
561 g_list_free(dep_list);
562
563 if (plugin->native_plugin)
564 {
565 if (plugin->info != NULL && plugin->info->load != NULL)
566 {
567 if (!plugin->info->load(plugin))
568 return FALSE;
569 }
570 }
571 else {
572 GaimPlugin *loader;
573 GaimPluginLoaderInfo *loader_info;
574
575 loader = find_loader_for_plugin(plugin);
576
577 if (loader == NULL)
578 return FALSE;
579
580 loader_info = GAIM_PLUGIN_LOADER_INFO(loader);
581
582 if (loader_info->load != NULL)
583 {
584 if (!loader_info->load(plugin))
585 return FALSE;
586 }
587 }
588
589 loaded_plugins = g_list_insert_sorted(loaded_plugins, plugin, compare_plugins);
590
591 plugin->loaded = TRUE;
592
593 /* TODO */
594 if (load_cb != NULL)
595 load_cb(plugin, load_cb_data);
596
597 gaim_signal_emit(gaim_plugins_get_handle(), "plugin-load", plugin);
598
599 return TRUE;
600
601 #else
602 return TRUE;
603 #endif /* !GAIM_PLUGINS */
604 }
605
606 gboolean
607 gaim_plugin_unload(GaimPlugin *plugin)
608 {
609 #ifdef GAIM_PLUGINS
610 GList *l;
611
612 g_return_val_if_fail(plugin != NULL, FALSE);
613
614 loaded_plugins = g_list_remove(loaded_plugins, plugin);
615 if ((plugin->info != NULL) && GAIM_IS_PROTOCOL_PLUGIN(plugin))
616 protocol_plugins = g_list_remove(protocol_plugins, plugin);
617
618 g_return_val_if_fail(gaim_plugin_is_loaded(plugin), FALSE);
619
620 gaim_debug_info("plugins", "Unloading plugin %s\n", plugin->info->name);
621
622 /* cancel any pending dialogs the plugin has */
623 gaim_request_close_with_handle(plugin);
624 gaim_notify_close_with_handle(plugin);
625
626 plugin->loaded = FALSE;
627
628 /* Unload all plugins that depend on this plugin. */
629 while ((l = plugin->dependent_plugins) != NULL)
630 {
631 const char * dep_name = (const char *)l->data;
632 GaimPlugin *dep_plugin;
633
634 dep_plugin = gaim_plugins_find_with_id(dep_name);
635
636 if (dep_plugin != NULL && gaim_plugin_is_loaded(dep_plugin))
637 {
638 if (!gaim_plugin_unload(dep_plugin))
639 {
640 char *translated_name = g_strdup(_(dep_plugin->info->name));
641 char *tmp;
642
643 tmp = g_strdup_printf(_("The dependent plugin %s failed to unload."),
644 translated_name);
645 g_free(translated_name);
646
647 gaim_notify_error(NULL, NULL,
648 _("Gaim encountered errors unloading the plugin."), tmp);
649 g_free(tmp);
650 }
651 }
652 }
653
654 /* Remove this plugin from each dependency's dependent_plugins list. */
655 for (l = plugin->info->dependencies; l != NULL; l = l->next)
656 {
657 const char *dep_name = (const char *)l->data;
658 GaimPlugin *dependency;
659
660 dependency = gaim_plugins_find_with_id(dep_name);
661
662 dependency->dependent_plugins = g_list_remove(dependency->dependent_plugins, plugin->info->id);
663 }
664
665 if (plugin->native_plugin) {
666 if (plugin->info->unload != NULL)
667 plugin->info->unload(plugin);
668
669 if (plugin->info->type == GAIM_PLUGIN_PROTOCOL) {
670 GaimPluginProtocolInfo *prpl_info;
671 GList *l;
672
673 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin);
674
675 for (l = prpl_info->user_splits; l != NULL; l = l->next)
676 gaim_account_user_split_destroy(l->data);
677
678 for (l = prpl_info->protocol_options; l != NULL; l = l->next)
679 gaim_account_option_destroy(l->data);
680
681 if (prpl_info->user_splits != NULL) {
682 g_list_free(prpl_info->user_splits);
683 prpl_info->user_splits = NULL;
684 }
685
686 if (prpl_info->protocol_options != NULL) {
687 g_list_free(prpl_info->protocol_options);
688 prpl_info->protocol_options = NULL;
689 }
690 }
691 }
692 else {
693 GaimPlugin *loader;
694 GaimPluginLoaderInfo *loader_info;
695
696 loader = find_loader_for_plugin(plugin);
697
698 if (loader == NULL)
699 return FALSE;
700
701 loader_info = GAIM_PLUGIN_LOADER_INFO(loader);
702
703 if (loader_info->unload != NULL)
704 loader_info->unload(plugin);
705 }
706
707 gaim_signals_disconnect_by_handle(plugin);
708 gaim_plugin_ipc_unregister_all(plugin);
709
710 /* TODO */
711 if (unload_cb != NULL)
712 unload_cb(plugin, unload_cb_data);
713
714 gaim_signal_emit(gaim_plugins_get_handle(), "plugin-unload", plugin);
715
716 gaim_prefs_disconnect_by_handle(plugin);
717
718 return TRUE;
719 #else
720 return TRUE;
721 #endif /* GAIM_PLUGINS */
722 }
723
724 gboolean
725 gaim_plugin_reload(GaimPlugin *plugin)
726 {
727 #ifdef GAIM_PLUGINS
728 g_return_val_if_fail(plugin != NULL, FALSE);
729 g_return_val_if_fail(gaim_plugin_is_loaded(plugin), FALSE);
730
731 if (!gaim_plugin_unload(plugin))
732 return FALSE;
733
734 if (!gaim_plugin_load(plugin))
735 return FALSE;
736
737 return TRUE;
738 #else
739 return TRUE;
740 #endif /* !GAIM_PLUGINS */
741 }
742
743 void
744 gaim_plugin_destroy(GaimPlugin *plugin)
745 {
746 #ifdef GAIM_PLUGINS
747 g_return_if_fail(plugin != NULL);
748
749 if (gaim_plugin_is_loaded(plugin))
750 gaim_plugin_unload(plugin);
751
752 plugins = g_list_remove(plugins, plugin);
753
754 if (load_queue != NULL)
755 load_queue = g_list_remove(load_queue, plugin);
756
757 /* true, this may leak a little memory if there is a major version
758 * mismatch, but it's a lot better than trying to free something
759 * we shouldn't, and crashing while trying to load an old plugin */
760 if(plugin->info == NULL || plugin->info->magic != GAIM_PLUGIN_MAGIC ||
761 plugin->info->major_version != GAIM_MAJOR_VERSION) {
762 if(plugin->handle)
763 g_module_close(plugin->handle);
764 g_free(plugin);
765 return;
766 }
767
768 if (plugin->info != NULL && plugin->info->dependencies != NULL)
769 g_list_free(plugin->info->dependencies);
770
771 if (plugin->native_plugin)
772 {
773 if (plugin->info != NULL && plugin->info->type == GAIM_PLUGIN_LOADER)
774 {
775 GaimPluginLoaderInfo *loader_info;
776 GList *exts, *l, *next_l;
777 GaimPlugin *p2;
778
779 loader_info = GAIM_PLUGIN_LOADER_INFO(plugin);
780
781 if (loader_info != NULL && loader_info->exts != NULL)
782 {
783 for (exts = GAIM_PLUGIN_LOADER_INFO(plugin)->exts;
784 exts != NULL;
785 exts = exts->next) {
786
787 for (l = gaim_plugins_get_all(); l != NULL; l = next_l)
788 {
789 next_l = l->next;
790
791 p2 = l->data;
792
793 if (p2->path != NULL &&
794 has_file_extension(p2->path, exts->data))
795 {
796 gaim_plugin_destroy(p2);
797 }
798 }
799 }
800
801 g_list_free(loader_info->exts);
802 }
803
804 plugin_loaders = g_list_remove(plugin_loaders, plugin);
805 }
806
807 if (plugin->info != NULL && plugin->info->destroy != NULL)
808 plugin->info->destroy(plugin);
809
810 if (plugin->handle != NULL)
811 g_module_close(plugin->handle);
812 }
813 else
814 {
815 GaimPlugin *loader;
816 GaimPluginLoaderInfo *loader_info;
817
818 loader = find_loader_for_plugin(plugin);
819
820 if (loader != NULL)
821 {
822 loader_info = GAIM_PLUGIN_LOADER_INFO(loader);
823
824 if (loader_info->destroy != NULL)
825 loader_info->destroy(plugin);
826 }
827 }
828
829 if (plugin->path != NULL) g_free(plugin->path);
830 if (plugin->error != NULL) g_free(plugin->error);
831
832 g_free(plugin);
833 #endif /* !GAIM_PLUGINS */
834 }
835
836 gboolean
837 gaim_plugin_is_loaded(const GaimPlugin *plugin)
838 {
839 g_return_val_if_fail(plugin != NULL, FALSE);
840
841 return plugin->loaded;
842 }
843
844 gboolean
845 gaim_plugin_is_unloadable(const GaimPlugin *plugin)
846 {
847 g_return_val_if_fail(plugin != NULL, FALSE);
848
849 return plugin->unloadable;
850 }
851
852 const gchar *
853 gaim_plugin_get_id(const GaimPlugin *plugin) {
854 g_return_val_if_fail(plugin, NULL);
855 g_return_val_if_fail(plugin->info, NULL);
856
857 return plugin->info->id;
858 }
859
860 const gchar *
861 gaim_plugin_get_name(const GaimPlugin *plugin) {
862 g_return_val_if_fail(plugin, NULL);
863 g_return_val_if_fail(plugin->info, NULL);
864
865 return plugin->info->name;
866 }
867
868 const gchar *
869 gaim_plugin_get_version(const GaimPlugin *plugin) {
870 g_return_val_if_fail(plugin, NULL);
871 g_return_val_if_fail(plugin->info, NULL);
872
873 return plugin->info->version;
874 }
875
876 const gchar *
877 gaim_plugin_get_summary(const GaimPlugin *plugin) {
878 g_return_val_if_fail(plugin, NULL);
879 g_return_val_if_fail(plugin->info, NULL);
880
881 return plugin->info->summary;
882 }
883
884 const gchar *
885 gaim_plugin_get_description(const GaimPlugin *plugin) {
886 g_return_val_if_fail(plugin, NULL);
887 g_return_val_if_fail(plugin->info, NULL);
888
889 return plugin->info->description;
890 }
891
892 const gchar *
893 gaim_plugin_get_author(const GaimPlugin *plugin) {
894 g_return_val_if_fail(plugin, NULL);
895 g_return_val_if_fail(plugin->info, NULL);
896
897 return plugin->info->author;
898 }
899
900 const gchar *
901 gaim_plugin_get_homepage(const GaimPlugin *plugin) {
902 g_return_val_if_fail(plugin, NULL);
903 g_return_val_if_fail(plugin->info, NULL);
904
905 return plugin->info->homepage;
906 }
907
908 /**************************************************************************
909 * Plugin IPC
910 **************************************************************************/
911 static void
912 destroy_ipc_info(void *data)
913 {
914 GaimPluginIpcCommand *ipc_command = (GaimPluginIpcCommand *)data;
915 int i;
916
917 if (ipc_command->params != NULL)
918 {
919 for (i = 0; i < ipc_command->num_params; i++)
920 gaim_value_destroy(ipc_command->params[i]);
921
922 g_free(ipc_command->params);
923 }
924
925 if (ipc_command->ret_value != NULL)
926 gaim_value_destroy(ipc_command->ret_value);
927
928 g_free(ipc_command);
929 }
930
931 gboolean
932 gaim_plugin_ipc_register(GaimPlugin *plugin, const char *command,
933 GaimCallback func, GaimSignalMarshalFunc marshal,
934 GaimValue *ret_value, int num_params, ...)
935 {
936 GaimPluginIpcInfo *ipc_info;
937 GaimPluginIpcCommand *ipc_command;
938
939 g_return_val_if_fail(plugin != NULL, FALSE);
940 g_return_val_if_fail(command != NULL, FALSE);
941 g_return_val_if_fail(func != NULL, FALSE);
942 g_return_val_if_fail(marshal != NULL, FALSE);
943
944 if (plugin->ipc_data == NULL)
945 {
946 ipc_info = plugin->ipc_data = g_new0(GaimPluginIpcInfo, 1);
947 ipc_info->commands = g_hash_table_new_full(g_str_hash, g_str_equal,
948 g_free, destroy_ipc_info);
949 }
950 else
951 ipc_info = (GaimPluginIpcInfo *)plugin->ipc_data;
952
953 ipc_command = g_new0(GaimPluginIpcCommand, 1);
954 ipc_command->func = func;
955 ipc_command->marshal = marshal;
956 ipc_command->num_params = num_params;
957 ipc_command->ret_value = ret_value;
958
959 if (num_params > 0)
960 {
961 va_list args;
962 int i;
963
964 ipc_command->params = g_new0(GaimValue *, num_params);
965
966 va_start(args, num_params);
967
968 for (i = 0; i < num_params; i++)
969 ipc_command->params[i] = va_arg(args, GaimValue *);
970
971 va_end(args);
972 }
973
974 g_hash_table_replace(ipc_info->commands, g_strdup(command), ipc_command);
975
976 ipc_info->command_count++;
977
978 return TRUE;
979 }
980
981 void
982 gaim_plugin_ipc_unregister(GaimPlugin *plugin, const char *command)
983 {
984 GaimPluginIpcInfo *ipc_info;
985
986 g_return_if_fail(plugin != NULL);
987 g_return_if_fail(command != NULL);
988
989 ipc_info = (GaimPluginIpcInfo *)plugin->ipc_data;
990
991 if (ipc_info == NULL ||
992 g_hash_table_lookup(ipc_info->commands, command) == NULL)
993 {
994 gaim_debug_error("plugins",
995 "IPC command '%s' was not registered for plugin %s\n",
996 command, plugin->info->name);
997 return;
998 }
999
1000 g_hash_table_remove(ipc_info->commands, command);
1001
1002 ipc_info->command_count--;
1003
1004 if (ipc_info->command_count == 0)
1005 {
1006 g_hash_table_destroy(ipc_info->commands);
1007 g_free(ipc_info);
1008
1009 plugin->ipc_data = NULL;
1010 }
1011 }
1012
1013 void
1014 gaim_plugin_ipc_unregister_all(GaimPlugin *plugin)
1015 {
1016 GaimPluginIpcInfo *ipc_info;
1017
1018 g_return_if_fail(plugin != NULL);
1019
1020 if (plugin->ipc_data == NULL)
1021 return; /* Silently ignore it. */
1022
1023 ipc_info = (GaimPluginIpcInfo *)plugin->ipc_data;
1024
1025 g_hash_table_destroy(ipc_info->commands);
1026 g_free(ipc_info);
1027
1028 plugin->ipc_data = NULL;
1029 }
1030
1031 gboolean
1032 gaim_plugin_ipc_get_params(GaimPlugin *plugin, const char *command,
1033 GaimValue **ret_value, int *num_params,
1034 GaimValue ***params)
1035 {
1036 GaimPluginIpcInfo *ipc_info;
1037 GaimPluginIpcCommand *ipc_command;
1038
1039 g_return_val_if_fail(plugin != NULL, FALSE);
1040 g_return_val_if_fail(command != NULL, FALSE);
1041
1042 ipc_info = (GaimPluginIpcInfo *)plugin->ipc_data;
1043
1044 if (ipc_info == NULL ||
1045 (ipc_command = g_hash_table_lookup(ipc_info->commands,
1046 command)) == NULL)
1047 {
1048 gaim_debug_error("plugins",
1049 "IPC command '%s' was not registered for plugin %s\n",
1050 command, plugin->info->name);
1051
1052 return FALSE;
1053 }
1054
1055 if (num_params != NULL)
1056 *num_params = ipc_command->num_params;
1057
1058 if (params != NULL)
1059 *params = ipc_command->params;
1060
1061 if (ret_value != NULL)
1062 *ret_value = ipc_command->ret_value;
1063
1064 return TRUE;
1065 }
1066
1067 void *
1068 gaim_plugin_ipc_call(GaimPlugin *plugin, const char *command,
1069 gboolean *ok, ...)
1070 {
1071 GaimPluginIpcInfo *ipc_info;
1072 GaimPluginIpcCommand *ipc_command;
1073 va_list args;
1074 void *ret_value;
1075
1076 if (ok != NULL)
1077 *ok = FALSE;
1078
1079 g_return_val_if_fail(plugin != NULL, NULL);
1080 g_return_val_if_fail(command != NULL, NULL);
1081
1082 ipc_info = (GaimPluginIpcInfo *)plugin->ipc_data;
1083
1084 if (ipc_info == NULL ||
1085 (ipc_command = g_hash_table_lookup(ipc_info->commands,
1086 command)) == NULL)
1087 {
1088 gaim_debug_error("plugins",
1089 "IPC command '%s' was not registered for plugin %s\n",
1090 command, plugin->info->name);
1091
1092 return NULL;
1093 }
1094
1095 va_start(args, ok);
1096 ipc_command->marshal(ipc_command->func, args, NULL, &ret_value);
1097 va_end(args);
1098
1099 if (ok != NULL)
1100 *ok = TRUE;
1101
1102 return ret_value;
1103 }
1104
1105 /**************************************************************************
1106 * Plugins subsystem
1107 **************************************************************************/
1108 void *
1109 gaim_plugins_get_handle(void) {
1110 static int handle;
1111
1112 return &handle;
1113 }
1114
1115 void
1116 gaim_plugins_init(void) {
1117 void *handle = gaim_plugins_get_handle();
1118
1119 gaim_signal_register(handle, "plugin-load",
1120 gaim_marshal_VOID__POINTER,
1121 NULL, 1,
1122 gaim_value_new(GAIM_TYPE_SUBTYPE,
1123 GAIM_SUBTYPE_PLUGIN));
1124 gaim_signal_register(handle, "plugin-unload",
1125 gaim_marshal_VOID__POINTER,
1126 NULL, 1,
1127 gaim_value_new(GAIM_TYPE_SUBTYPE,
1128 GAIM_SUBTYPE_PLUGIN));
1129 }
1130
1131 void
1132 gaim_plugins_uninit(void) {
1133 gaim_signals_disconnect_by_handle(gaim_plugins_get_handle());
1134 }
1135
1136 /**************************************************************************
1137 * Plugins API
1138 **************************************************************************/
1139 void
1140 gaim_plugins_add_search_path(const char *path)
1141 {
1142 g_return_if_fail(path != NULL);
1143
1144 if (g_list_find_custom(search_paths, path, (GCompareFunc)strcmp))
1145 return;
1146
1147 search_paths = g_list_append(search_paths, strdup(path));
1148 }
1149
1150 void
1151 gaim_plugins_unload_all(void)
1152 {
1153 #ifdef GAIM_PLUGINS
1154
1155 while (loaded_plugins != NULL)
1156 gaim_plugin_unload(loaded_plugins->data);
1157
1158 #endif /* GAIM_PLUGINS */
1159 }
1160
1161 void
1162 gaim_plugins_destroy_all(void)
1163 {
1164 #ifdef GAIM_PLUGINS
1165
1166 while (plugins != NULL)
1167 gaim_plugin_destroy(plugins->data);
1168
1169 #endif /* GAIM_PLUGINS */
1170 }
1171
1172 void
1173 gaim_plugins_load_saved(const char *key)
1174 {
1175 #ifdef GAIM_PLUGINS
1176 GList *f, *files;
1177
1178 g_return_if_fail(key != NULL);
1179
1180 files = gaim_prefs_get_string_list(key);
1181
1182 for (f = files; f; f = f->next)
1183 {
1184 char *filename;
1185 char *basename;
1186 GaimPlugin *plugin;
1187
1188 if (f->data == NULL)
1189 continue;
1190
1191 filename = f->data;
1192 /*
1193 * We don't know if the filename uses Windows or Unix path
1194 * separators (because people might be sharing a prefs.xml
1195 * file across systems), so we find the last occurrence
1196 * of either.
1197 */
1198 basename = strrchr(filename, '/');
1199 if ((basename == NULL) || (basename < strrchr(filename, '\\')))
1200 basename = strrchr(filename, '\\');
1201 if (basename != NULL)
1202 basename++;
1203
1204 if ((plugin = gaim_plugins_find_with_filename(filename)) != NULL)
1205 {
1206 gaim_debug_info("plugins", "Loading saved plugin %s\n",
1207 plugin->path);
1208 gaim_plugin_load(plugin);
1209 }
1210 else if ((plugin = gaim_plugins_find_with_basename(basename)) != NULL)
1211 {
1212 gaim_debug_info("plugins", "Loading saved plugin %s\n",
1213 plugin->path);
1214 gaim_plugin_load(plugin);
1215 }
1216 else
1217 {
1218 gaim_debug_error("plugins", "Unable to find saved plugin %s\n",
1219 filename);
1220 }
1221
1222 g_free(f->data);
1223 }
1224
1225 g_list_free(files);
1226 #endif /* GAIM_PLUGINS */
1227 }
1228
1229
1230 void
1231 gaim_plugins_probe(const char *ext)
1232 {
1233 #ifdef GAIM_PLUGINS
1234 GDir *dir;
1235 const gchar *file;
1236 gchar *path;
1237 GaimPlugin *plugin;
1238 GList *cur;
1239 const char *search_path;
1240
1241 if (!g_module_supported())
1242 return;
1243
1244 /* Probe plugins */
1245 for (cur = search_paths; cur != NULL; cur = cur->next)
1246 {
1247 search_path = cur->data;
1248
1249 dir = g_dir_open(search_path, 0, NULL);
1250
1251 if (dir != NULL)
1252 {
1253 while ((file = g_dir_read_name(dir)) != NULL)
1254 {
1255 path = g_build_filename(search_path, file, NULL);
1256
1257 if (ext == NULL || has_file_extension(file, ext))
1258 plugin = gaim_plugin_probe(path);
1259
1260 g_free(path);
1261 }
1262
1263 g_dir_close(dir);
1264 }
1265 }
1266
1267 /* See if we have any plugins waiting to load */
1268 while (load_queue != NULL)
1269 {
1270 plugin = (GaimPlugin *)load_queue->data;
1271
1272 load_queue = g_list_remove(load_queue, plugin);
1273
1274 if (plugin == NULL || plugin->info == NULL)
1275 continue;
1276
1277 if (plugin->info->type == GAIM_PLUGIN_LOADER)
1278 {
1279 /* We'll just load this right now. */
1280 if (!gaim_plugin_load(plugin))
1281 {
1282 gaim_plugin_destroy(plugin);
1283
1284 continue;
1285 }
1286
1287 plugin_loaders = g_list_append(plugin_loaders, plugin);
1288
1289 for (cur = GAIM_PLUGIN_LOADER_INFO(plugin)->exts;
1290 cur != NULL;
1291 cur = cur->next)
1292 {
1293 gaim_plugins_probe(cur->data);
1294 }
1295 }
1296 else if (plugin->info->type == GAIM_PLUGIN_PROTOCOL)
1297 {
1298 /* We'll just load this right now. */
1299 if (!gaim_plugin_load(plugin))
1300 {
1301 gaim_plugin_destroy(plugin);
1302
1303 continue;
1304 }
1305
1306 /* Make sure we don't load two PRPLs with the same name? */
1307 if (gaim_find_prpl(plugin->info->id))
1308 {
1309 /* Nothing to see here--move along, move along */
1310 gaim_plugin_destroy(plugin);
1311
1312 continue;
1313 }
1314
1315 protocol_plugins = g_list_insert_sorted(protocol_plugins, plugin,
1316 (GCompareFunc)compare_prpl);
1317 }
1318 }
1319
1320 if (probe_cb != NULL)
1321 probe_cb(probe_cb_data);
1322 #endif /* GAIM_PLUGINS */
1323 }
1324
1325 gboolean
1326 gaim_plugin_register(GaimPlugin *plugin)
1327 {
1328 g_return_val_if_fail(plugin != NULL, FALSE);
1329
1330 /* If this plugin has been registered already then exit */
1331 if (g_list_find(plugins, plugin))
1332 return TRUE;
1333
1334 /* Ensure the plugin has the requisite information */
1335 if (plugin->info->type == GAIM_PLUGIN_LOADER)
1336 {
1337 GaimPluginLoaderInfo *loader_info;
1338
1339 loader_info = GAIM_PLUGIN_LOADER_INFO(plugin);
1340
1341 if (loader_info == NULL)
1342 {
1343 gaim_debug_error("plugins", "%s is unloadable\n",
1344 plugin->path);
1345 return FALSE;
1346 }
1347 }
1348 else if (plugin->info->type == GAIM_PLUGIN_PROTOCOL)
1349 {
1350 GaimPluginProtocolInfo *prpl_info;
1351
1352 prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(plugin);
1353
1354 if (prpl_info == NULL)
1355 {
1356 gaim_debug_error("plugins", "%s is unloadable\n",
1357 plugin->path);
1358 return FALSE;
1359 }
1360 }
1361
1362 #ifdef GAIM_PLUGINS
1363 /* This plugin should be probed and maybe loaded--add it to the queue */
1364 load_queue = g_list_append(load_queue, plugin);
1365 #else
1366 if (plugin->info != NULL)
1367 {
1368 if (plugin->info->type == GAIM_PLUGIN_PROTOCOL)
1369 protocol_plugins = g_list_insert_sorted(protocol_plugins, plugin,
1370 (GCompareFunc)compare_prpl);
1371 if (plugin->info->load != NULL)
1372 if (!plugin->info->load(plugin))
1373 return FALSE;
1374 }
1375 #endif
1376
1377 plugins = g_list_append(plugins, plugin);
1378
1379 return TRUE;
1380 }
1381
1382 gboolean
1383 gaim_plugins_enabled(void)
1384 {
1385 #ifdef GAIM_PLUGINS
1386 return TRUE;
1387 #else
1388 return FALSE;
1389 #endif
1390 }
1391
1392 void
1393 gaim_plugins_register_probe_notify_cb(void (*func)(void *), void *data)
1394 {
1395 /* TODO */
1396 probe_cb = func;
1397 probe_cb_data = data;
1398 }
1399
1400 void
1401 gaim_plugins_unregister_probe_notify_cb(void (*func)(void *))
1402 {
1403 /* TODO */
1404 probe_cb = NULL;
1405 probe_cb_data = NULL;
1406 }
1407
1408 void
1409 gaim_plugins_register_load_notify_cb(void (*func)(GaimPlugin *, void *),
1410 void *data)
1411 {
1412 /* TODO */
1413 load_cb = func;
1414 load_cb_data = data;
1415 }
1416
1417 void
1418 gaim_plugins_unregister_load_notify_cb(void (*func)(GaimPlugin *, void *))
1419 {
1420 /* TODO */
1421 load_cb = NULL;
1422 load_cb_data = NULL;
1423 }
1424
1425 void
1426 gaim_plugins_register_unload_notify_cb(void (*func)(GaimPlugin *, void *),
1427 void *data)
1428 {
1429 /* TODO */
1430 unload_cb = func;
1431 unload_cb_data = data;
1432 }
1433
1434 void
1435 gaim_plugins_unregister_unload_notify_cb(void (*func)(GaimPlugin *, void *))
1436 {
1437 /* TODO */
1438 unload_cb = NULL;
1439 unload_cb_data = NULL;
1440 }
1441
1442 GaimPlugin *
1443 gaim_plugins_find_with_name(const char *name)
1444 {
1445 GaimPlugin *plugin;
1446 GList *l;
1447
1448 for (l = plugins; l != NULL; l = l->next) {
1449 plugin = l->data;
1450
1451 if (!strcmp(plugin->info->name, name))
1452 return plugin;
1453 }
1454
1455 return NULL;
1456 }
1457
1458 GaimPlugin *
1459 gaim_plugins_find_with_filename(const char *filename)
1460 {
1461 GaimPlugin *plugin;
1462 GList *l;
1463
1464 for (l = plugins; l != NULL; l = l->next) {
1465 plugin = l->data;
1466
1467 if (plugin->path != NULL && !strcmp(plugin->path, filename))
1468 return plugin;
1469 }
1470
1471 return NULL;
1472 }
1473
1474 GaimPlugin *
1475 gaim_plugins_find_with_basename(const char *basename)
1476 {
1477 #ifdef GAIM_PLUGINS
1478 GaimPlugin *plugin;
1479 GList *l;
1480 char *basename_no_ext;
1481 char *tmp;
1482
1483 g_return_val_if_fail(basename != NULL, NULL);
1484
1485 basename_no_ext = gaim_plugin_get_basename(basename);
1486
1487 for (l = plugins; l != NULL; l = l->next)
1488 {
1489 plugin = (GaimPlugin *)l->data;
1490
1491 if (plugin->path != NULL) {
1492 tmp = gaim_plugin_get_basename(plugin->path);
1493 if (!strcmp(tmp, basename_no_ext))
1494 {
1495 g_free(tmp);
1496 g_free(basename_no_ext);
1497 return plugin;
1498 }
1499 g_free(tmp);
1500 }
1501 }
1502
1503 g_free(basename_no_ext);
1504 #endif /* GAIM_PLUGINS */
1505
1506 return NULL;
1507 }
1508
1509 GaimPlugin *
1510 gaim_plugins_find_with_id(const char *id)
1511 {
1512 GaimPlugin *plugin;
1513 GList *l;
1514
1515 g_return_val_if_fail(id != NULL, NULL);
1516
1517 for (l = plugins; l != NULL; l = l->next)
1518 {
1519 plugin = l->data;
1520
1521 if (plugin->info->id != NULL && !strcmp(plugin->info->id, id))
1522 return plugin;
1523 }
1524
1525 return NULL;
1526 }
1527
1528 GList *
1529 gaim_plugins_get_loaded(void)
1530 {
1531 return loaded_plugins;
1532 }
1533
1534 GList *
1535 gaim_plugins_get_protocols(void)
1536 {
1537 return protocol_plugins;
1538 }
1539
1540 GList *
1541 gaim_plugins_get_all(void)
1542 {
1543 return plugins;
1544 }
1545
1546
1547 GaimPluginAction *
1548 gaim_plugin_action_new(char* label, void (*callback)(GaimPluginAction *))
1549 {
1550 GaimPluginAction *act = g_new0(GaimPluginAction, 1);
1551
1552 act->label = label;
1553 act->callback = callback;
1554
1555 return act;
1556 }

mercurial