* Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA typedef struct _PurplePluginInfoPrivate PurplePluginInfoPrivate; /************************************************************************** * Plugin info private data **************************************************************************/ struct _PurplePluginInfoPrivate { char *ui_requirement; /* ID of UI that is required to load the plugin */ char *error; /* Why a plugin is not loadable */ PurplePluginInfoFlags flags; /* Flags for the plugin */ /* Callback that returns a list of actions the plugin can perform */ PurplePluginActionsCb actions_cb; /* Callback that returns extra information about a plugin */ PurplePluginExtraCb extra_cb; /* Callback that returns a preferences frame for a plugin */ PurplePluginPrefFrameCb pref_frame_cb; /* Callback that returns a preferences request handle for a plugin */ PurplePluginPrefRequestCb pref_request_cb; /* TRUE if a plugin has been unloaded at least once. Auto-load * plugins that have been unloaded once will not be auto-loaded again. */ G_DEFINE_TYPE_WITH_PRIVATE(PurplePluginInfo, purple_plugin_info, GPLUGIN_TYPE_PLUGIN_INFO); /************************************************************************** **************************************************************************/ static GList *loaded_plugins = NULL; static GList *plugins_to_disable = NULL; /************************************************************************** **************************************************************************/ plugin_loading_cb(GObject *manager, PurplePlugin *plugin, GError **error, PurplePluginInfoPrivate *priv; g_return_val_if_fail(PURPLE_IS_PLUGIN(plugin), FALSE); info = purple_plugin_get_info(plugin); return TRUE; /* a GPlugin internal plugin */ priv = purple_plugin_info_get_instance_private(info); purple_debug_error("plugins", "Failed to load plugin %s: %s", gplugin_plugin_get_filename(plugin), g_set_error(error, PURPLE_PLUGINS_DOMAIN, 0, "Plugin is not loadable: %s", priv->error); plugin_loaded_cb(GObject *manager, PurplePlugin *plugin) g_return_if_fail(PURPLE_IS_PLUGIN(plugin)); info = purple_plugin_get_info(plugin); return; /* a GPlugin internal plugin */ loaded_plugins = g_list_prepend(loaded_plugins, plugin); purple_debug_info("plugins", "Loaded plugin %s\n", gplugin_plugin_get_filename(plugin)); purple_signal_emit(purple_plugins_get_handle(), "plugin-load", plugin); plugin_unloading_cb(GObject *manager, PurplePlugin *plugin, GError **error, g_return_val_if_fail(PURPLE_IS_PLUGIN(plugin), FALSE); info = purple_plugin_get_info(plugin); purple_debug_info("plugins", "Unloading plugin %s\n", gplugin_plugin_get_filename(plugin)); plugin_unloaded_cb(GObject *manager, PurplePlugin *plugin) PurplePluginInfoPrivate *priv; g_return_if_fail(PURPLE_IS_PLUGIN(plugin)); info = purple_plugin_get_info(plugin); return; /* a GPlugin internal plugin */ priv = purple_plugin_info_get_instance_private(info); /* cancel any pending dialogs the plugin has */ purple_request_close_with_handle(plugin); purple_notify_close_with_handle(plugin); purple_signals_disconnect_by_handle(plugin); purple_signals_unregister_by_instance(plugin); loaded_plugins = g_list_remove(loaded_plugins, plugin); plugins_to_disable = g_list_remove(plugins_to_disable, plugin); purple_signal_emit(purple_plugins_get_handle(), "plugin-unload", plugin); purple_prefs_disconnect_by_handle(plugin); purple_plugin_load(PurplePlugin *plugin, GError **error) g_return_val_if_fail(plugin != NULL, FALSE); if (purple_plugin_is_loaded(plugin)) if (!gplugin_manager_load_plugin(plugin, &err)) { purple_debug_error("plugins", "Failed to load plugin %s: %s", gplugin_plugin_get_filename(plugin), err ? err->message : "Unknown reason"); *error = g_error_copy(err); purple_plugin_unload(PurplePlugin *plugin, GError **error) g_return_val_if_fail(plugin != NULL, FALSE); if (!purple_plugin_is_loaded(plugin)) if (!gplugin_manager_unload_plugin(plugin, &err)) { purple_debug_error("plugins", "Failed to unload plugin %s: %s", gplugin_plugin_get_filename(plugin), err ? err->message : "Unknown reason"); *error = g_error_copy(err); purple_plugin_is_loaded(PurplePlugin *plugin) g_return_val_if_fail(plugin != NULL, FALSE); return (gplugin_plugin_get_state(plugin) == GPLUGIN_PLUGIN_STATE_LOADED); purple_plugin_get_info(PurplePlugin *plugin) g_return_val_if_fail(plugin != NULL, NULL); info = gplugin_plugin_get_info(plugin); /* GPlugin refs the plugin info object before returning it. This workaround * is to avoid managing the reference counts everywhere in our codebase * where we use the plugin info. The plugin info instance is guaranteed to * exist as long as the plugin exists. */ if (PURPLE_IS_PLUGIN_INFO(info)) return PURPLE_PLUGIN_INFO(info); purple_plugin_disable(PurplePlugin *plugin) g_return_if_fail(plugin != NULL); if (!g_list_find(plugins_to_disable, plugin)) plugins_to_disable = g_list_prepend(plugins_to_disable, plugin); purple_plugin_is_internal(PurplePlugin *plugin) g_return_val_if_fail(plugin != NULL, FALSE); info = purple_plugin_get_info(plugin); return (purple_plugin_info_get_flags(info) & PURPLE_PLUGIN_INFO_FLAGS_INTERNAL); purple_plugin_get_dependent_plugins(PurplePlugin *plugin) #warning TODO: Implement this when GPlugin can return dependent plugins. /************************************************************************** * GObject code for PurplePluginInfo **************************************************************************/ /* GObject initialization function */ purple_plugin_info_init(PurplePluginInfo *info) /* Set method for GObject properties */ purple_plugin_info_set_property(GObject *obj, guint param_id, const GValue *value, PurplePluginInfo *info = PURPLE_PLUGIN_INFO(obj); PurplePluginInfoPrivate *priv = purple_plugin_info_get_instance_private(info); case PROP_UI_REQUIREMENT: priv->ui_requirement = g_value_dup_string(value); priv->actions_cb = g_value_get_pointer(value); priv->extra_cb = g_value_get_pointer(value); priv->pref_frame_cb = g_value_get_pointer(value); case PROP_PREF_REQUEST_CB: priv->pref_request_cb = g_value_get_pointer(value); priv->flags = g_value_get_flags(value); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); /* Get method for GObject properties */ purple_plugin_info_get_property(GObject *obj, guint param_id, GValue *value, PurplePluginInfo *info = PURPLE_PLUGIN_INFO(obj); g_value_set_pointer(value, purple_plugin_info_get_actions_cb(info)); g_value_set_pointer(value, purple_plugin_info_get_extra_cb(info)); g_value_set_pointer(value, purple_plugin_info_get_pref_frame_cb(info)); case PROP_PREF_REQUEST_CB: g_value_set_pointer(value, purple_plugin_info_get_pref_request_cb(info)); g_value_set_flags(value, purple_plugin_info_get_flags(info)); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); /* Called when done constructing */ purple_plugin_info_constructed(GObject *object) PurplePluginInfo *info = PURPLE_PLUGIN_INFO(object); GPluginPluginInfo *ginfo = GPLUGIN_PLUGIN_INFO(info); PurplePluginInfoPrivate *priv = purple_plugin_info_get_instance_private(info); const char *id = gplugin_plugin_info_get_id(ginfo); G_OBJECT_CLASS(purple_plugin_info_parent_class)->constructed(object); if (id == NULL || *id == '\0') priv->error = g_strdup(_("This plugin has not defined an ID.")); if (priv->ui_requirement && !purple_strequal(priv->ui_requirement, purple_core_get_ui())) priv->error = g_strdup_printf(_("You are using %s, but this plugin requires %s."), purple_core_get_ui(), priv->ui_requirement); purple_debug_error("plugins", "%s is not loadable: The UI requirement is not met. (%s)\n", version = gplugin_plugin_info_get_abi_version(ginfo); if (PURPLE_PLUGIN_ABI_MAJOR_VERSION(version) != PURPLE_MAJOR_VERSION || PURPLE_PLUGIN_ABI_MINOR_VERSION(version) > PURPLE_MINOR_VERSION) priv->error = g_strdup_printf(_("Your libpurple version is %d.%d.x (need %d.%d.x)"), PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_ABI_MAJOR_VERSION(version), PURPLE_PLUGIN_ABI_MINOR_VERSION(version)); purple_debug_error("plugins", "%s is not loadable: libpurple version is %d.%d.x (need %d.%d.x)\n", id, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_ABI_MAJOR_VERSION(version), PURPLE_PLUGIN_ABI_MINOR_VERSION(version)); /* GObject finalize function */ purple_plugin_info_finalize(GObject *object) PurplePluginInfoPrivate *priv = purple_plugin_info_get_instance_private( PURPLE_PLUGIN_INFO(object)); g_free(priv->ui_requirement); G_OBJECT_CLASS(purple_plugin_info_parent_class)->finalize(object); /* Class initializer function */ static void purple_plugin_info_class_init(PurplePluginInfoClass *klass) GObjectClass *obj_class = G_OBJECT_CLASS(klass); obj_class->constructed = purple_plugin_info_constructed; obj_class->finalize = purple_plugin_info_finalize; obj_class->get_property = purple_plugin_info_get_property; obj_class->set_property = purple_plugin_info_set_property; g_object_class_install_property(obj_class, PROP_UI_REQUIREMENT, g_param_spec_string("ui-requirement", "ID of UI that is required by this plugin", NULL, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(obj_class, PROP_ACTIONS_CB, g_param_spec_pointer("actions-cb", "Callback that returns list of plugin's actions", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(obj_class, PROP_EXTRA_CB, g_param_spec_pointer("extra-cb", "Callback that returns extra info about the plugin", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(obj_class, PROP_PREF_FRAME_CB, g_param_spec_pointer("pref-frame-cb", "Preferences frame callback", "The callback that returns the preferences frame", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(obj_class, PROP_PREF_REQUEST_CB, g_param_spec_pointer("pref-request-cb", "Preferences request callback", "Callback that returns preferences request handle", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(obj_class, PROP_FLAGS, g_param_spec_flags("flags", "The flags for the plugin", PURPLE_TYPE_PLUGIN_INFO_FLAGS, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /************************************************************************** **************************************************************************/ purple_plugin_info_new(const char *first_property, ...) /* at least ID is required */ va_start(var_args, first_property); info = g_object_new_valist(PURPLE_TYPE_PLUGIN_INFO, first_property, return PURPLE_PLUGIN_INFO(info); purple_plugin_info_get_actions_cb(PurplePluginInfo *info) PurplePluginInfoPrivate *priv = NULL; g_return_val_if_fail(PURPLE_IS_PLUGIN_INFO(info), NULL); priv = purple_plugin_info_get_instance_private(info); purple_plugin_info_get_extra_cb(PurplePluginInfo *info) PurplePluginInfoPrivate *priv = NULL; g_return_val_if_fail(PURPLE_IS_PLUGIN_INFO(info), NULL); priv = purple_plugin_info_get_instance_private(info); purple_plugin_info_get_pref_frame_cb(PurplePluginInfo *info) PurplePluginInfoPrivate *priv = NULL; g_return_val_if_fail(PURPLE_IS_PLUGIN_INFO(info), NULL); priv = purple_plugin_info_get_instance_private(info); return priv->pref_frame_cb; PurplePluginPrefRequestCb purple_plugin_info_get_pref_request_cb(PurplePluginInfo *info) PurplePluginInfoPrivate *priv = NULL; g_return_val_if_fail(PURPLE_IS_PLUGIN_INFO(info), NULL); priv = purple_plugin_info_get_instance_private(info); return priv->pref_request_cb; purple_plugin_info_get_flags(PurplePluginInfo *info) PurplePluginInfoPrivate *priv = NULL; g_return_val_if_fail(PURPLE_IS_PLUGIN_INFO(info), 0); priv = purple_plugin_info_get_instance_private(info); purple_plugin_info_get_error(PurplePluginInfo *info) PurplePluginInfoPrivate *priv = NULL; g_return_val_if_fail(PURPLE_IS_PLUGIN_INFO(info), NULL); priv = purple_plugin_info_get_instance_private(info); purple_plugin_info_set_ui_data(PurplePluginInfo *info, gpointer ui_data) g_return_if_fail(PURPLE_IS_PLUGIN_INFO(info)); purple_plugin_info_get_ui_data(PurplePluginInfo *info) g_return_val_if_fail(PURPLE_IS_PLUGIN_INFO(info), NULL); /************************************************************************** **************************************************************************/ purple_plugin_action_new(const char* label, PurplePluginActionCb callback) PurplePluginAction *action; g_return_val_if_fail(label != NULL && callback != NULL, NULL); action = g_new0(PurplePluginAction, 1); action->label = g_strdup(label); action->callback = callback; purple_plugin_action_free(PurplePluginAction *action) g_return_if_fail(action != NULL); static PurplePluginAction * purple_plugin_action_copy(PurplePluginAction *action) g_return_val_if_fail(action != NULL, NULL); return purple_plugin_action_new(action->label, action->callback); G_DEFINE_BOXED_TYPE(PurplePluginAction, purple_plugin_action, purple_plugin_action_copy, purple_plugin_action_free) /************************************************************************** **************************************************************************/ purple_plugins_find_all(void) GList *ret = NULL, *ids, *l; ids = gplugin_manager_list_plugins(); for (l = ids; l; l = l->next) { plugins = gplugin_manager_find_plugins(l->data); for (ll = plugins; ll; ll = ll->next) { PurplePlugin *plugin = PURPLE_PLUGIN(ll->data); if (purple_plugin_get_info(plugin)) ret = g_list_append(ret, plugin); gplugin_manager_free_plugin_list(plugins); purple_plugins_get_loaded(void) purple_plugins_add_search_path(const gchar *path) gplugin_manager_append_path(path); purple_plugins_refresh(void) gplugin_manager_refresh(); plugins = purple_plugins_find_all(); for (l = plugins; l != NULL; l = l->next) { PurplePlugin *plugin = PURPLE_PLUGIN(l->data); PurplePluginInfoPrivate *priv; if (purple_plugin_is_loaded(plugin)) info = purple_plugin_get_info(plugin); priv = purple_plugin_info_get_instance_private(info); if (!priv->unloaded && purple_plugin_info_get_flags(info) & PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD) { purple_debug_info("plugins", "Auto-loading plugin %s\n", gplugin_plugin_get_filename(plugin)); purple_plugin_load(plugin, NULL); purple_plugins_find_plugin(const gchar *id) g_return_val_if_fail(id != NULL && *id != '\0', NULL); plugin = gplugin_manager_find_plugin(id); /* GPlugin refs the plugin object before returning it. This workaround is * to avoid managing the reference counts everywhere in our codebase where * we use plugin instances. A plugin object will exist till the plugins * subsystem is uninitialized. */ purple_plugins_find_by_filename(const char *filename) g_return_val_if_fail(filename != NULL && *filename != '\0', NULL); plugins = purple_plugins_find_all(); for (l = plugins; l != NULL; l = l->next) { PurplePlugin *plugin = PURPLE_PLUGIN(l->data); if (purple_strequal(gplugin_plugin_get_filename(plugin), purple_plugins_save_loaded(const char *key) g_return_if_fail(key != NULL && *key != '\0'); for (pl = purple_plugins_get_loaded(); pl != NULL; pl = pl->next) { PurplePlugin *plugin = PURPLE_PLUGIN(pl->data); PurplePluginInfo *info = purple_plugin_get_info(plugin); if (purple_plugin_info_get_flags(info) & PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD) if (!g_list_find(plugins_to_disable, plugin)) (gchar *)gplugin_plugin_get_filename(plugin)); purple_prefs_set_path_list(key, files); purple_plugins_load_saved(const char *key) g_return_if_fail(key != NULL && *key != '\0'); files = purple_prefs_get_path_list(key); for (l = files; l; l = l->next) plugin = purple_plugins_find_by_filename(file); purple_debug_info("plugins", "Loading saved plugin %s\n", file); purple_plugin_load(plugin, NULL); purple_debug_error("plugins", "Unable to find saved plugin %s\n", file); /************************************************************************** **************************************************************************/ purple_plugins_get_handle(void) purple_plugins_init(void) void *handle = purple_plugins_get_handle(); const gchar *search_path; purple_signal_register(handle, "plugin-load", purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, PURPLE_TYPE_PLUGIN); purple_signal_register(handle, "plugin-unload", purple_marshal_VOID__POINTER, G_TYPE_NONE, 1, PURPLE_TYPE_PLUGIN); search_path = g_getenv("PURPLE_PLUGIN_PATH"); paths = g_strsplit(search_path, G_SEARCHPATH_SEPARATOR_S, 0); for (i = 0; paths[i]; ++i) { purple_plugins_add_search_path(paths[i]); gplugin_manager_add_default_paths(); if(!g_getenv("PURPLE_PLUGINS_SKIP")) { purple_plugins_add_search_path(PURPLE_LIBDIR); purple_debug_info("plugins", "PURPLE_PLUGINS_SKIP environment variable set, skipping normal plugin paths"); g_signal_connect(gplugin_manager_get_instance(), "loading-plugin", G_CALLBACK(plugin_loading_cb), NULL); g_signal_connect(gplugin_manager_get_instance(), "loaded-plugin", G_CALLBACK(plugin_loaded_cb), NULL); g_signal_connect(gplugin_manager_get_instance(), "unloading-plugin", G_CALLBACK(plugin_unloading_cb), NULL); g_signal_connect(gplugin_manager_get_instance(), "unloaded-plugin", G_CALLBACK(plugin_unloaded_cb), NULL); purple_plugins_refresh(); purple_plugins_uninit(void) void *handle = purple_plugins_get_handle(); purple_debug_info("plugins", "Unloading all plugins\n"); while (loaded_plugins != NULL) purple_plugin_unload(loaded_plugins->data, NULL); purple_signals_disconnect_by_handle(handle); purple_signals_unregister_by_instance(handle);