comparison libgaim/plugin.c @ 14192:60b1bc8dbf37

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