/** * @file plugin_manager.cc * @copyright (C) 2001-2006 Marcus Bjurman\n * @copyright (C) 2007-2012 Piotr Eljasiak\n * @copyright (C) 2013-2023 Uwe Scholz\n * * @copyright 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. * * @copyright 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. * * @copyright 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 St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "gnome-cmd-includes.h" #include "gnome-cmd-data.h" #include "plugin_manager.h" #include "utils.h" #include "imageloader.h" #include "gnome-cmd-main-win.h" #include "gnome-cmd-about-plugin.h" #include "dirlist.h" using namespace std; // The names of these functions shall never change #define MODULE_INIT_FUNC "create_plugin" #define MODULE_INFO_FUNC "get_plugin_info" static GList *plugins = nullptr; static GdkPixbuf *exec_pixbuf = nullptr; static GdkPixbuf *blank_pixbuf = nullptr; gchar* get_plugin_config_location(); static void load_plugin (PluginData *data) { GModule *module = g_module_open (data->fpath, G_MODULE_BIND_LAZY); PluginInfoFunc info_func; PluginConstructorFunc init_func; GnomeCmdPlugin *plugin; if (!module) { g_printerr ("ERROR: Failed to load the plugin '%s': %s\n", data->fname, g_module_error ()); return; } // Try to get a reference to the "get_plugin_info" function if (!g_module_symbol (module, MODULE_INFO_FUNC, (gpointer *) &info_func)) { g_printerr ("ERROR: The plugin-file '%s' has no function named '%s'.\n", data->fname, MODULE_INFO_FUNC); g_module_close (module); return; } // Try to get the plugin info data->info = info_func (); if (!data->info) { g_printerr ("ERROR: The plugin-file '%s' did not return valid plugin info:\n", data->fname); g_printerr (" The function '%s' returned NULL\n", MODULE_INFO_FUNC); g_module_close (module); return; } // Check that the plugin is compatible if (data->info->plugin_system_version != GNOME_CMD_PLUGIN_SYSTEM_CURRENT_VERSION) { g_printerr ("ERROR: The plugin '%s' is not compatible with this version of %s:\n", data->info->name, PACKAGE); g_printerr (" Plugin system supported by the plugin is %d, it should be %d\n", data->info->plugin_system_version, GNOME_CMD_PLUGIN_SYSTEM_CURRENT_VERSION); return; } // Try to get a reference to the "create_plugin" function if (!g_module_symbol (module, MODULE_INIT_FUNC, (gpointer *) &init_func)) { g_printerr ("ERROR: The plugin '%s' has no '%s' function\n", data->info->name, MODULE_INIT_FUNC); g_module_close (module); return; } // Try to initialize the plugin plugin = init_func (); if (!plugin) { g_printerr ("ERROR: The plugin '%s' could not be initialized:\n", data->info->name); g_printerr (" The '%s' function returned NULL\n", MODULE_INIT_FUNC); g_module_close (module); return; } // All is OK, everyone is happy data->plugin = plugin; data->module = module; data->loaded = TRUE; } static void activate_plugin (PluginData *data) { if (data->active) return; if (!data->loaded) load_plugin (data); if (!data->loaded) return; data->active = TRUE; GnomeCmdState *state = main_win->get_state(); data->menu = gnome_cmd_plugin_create_main_menu (data->plugin, state); if (data->menu) main_win->add_plugin_menu(data); } static void inactivate_plugin (PluginData *data) { if (!data->active) return; data->active = FALSE; if (data->menu) gtk_object_destroy (GTK_OBJECT (data->menu)); } static void scan_plugins_in_dir (const gchar *dpath) { auto gFileInfoList = sync_dir_list(dpath); if (g_list_length(gFileInfoList) == 0) return; for (auto gFileInfoListItem = gFileInfoList; gFileInfoListItem; gFileInfoListItem = gFileInfoListItem->next) { auto gFileInfo = (GFileInfo*) gFileInfoListItem->data; if (g_file_info_get_file_type(gFileInfo) != G_FILE_TYPE_REGULAR) continue; if (!g_str_has_suffix (g_file_info_get_name(gFileInfo), "." G_MODULE_SUFFIX)) continue; // the direntry has the correct extension and is a regular file, let's accept it PluginData *data = g_new0 (PluginData, 1); data->fname = g_strdup (g_file_info_get_name(gFileInfo)); data->fpath = g_build_filename (dpath, g_file_info_get_name(gFileInfo), nullptr); data->loaded = FALSE; data->active = FALSE; data->menu = nullptr; data->autoload = FALSE; activate_plugin (data); if (!data->loaded) { g_free (data->fname); g_free (data->fpath); g_free (data); } else plugins = g_list_append (plugins, data); } g_list_free_full(gFileInfoList, g_object_unref); } void plugin_manager_init () { if (plugins) { g_warning ("plugin_manager already initiated"); return; } // find user plugins gchar *user_dir = get_plugin_config_location(); if (!is_dir_existing(user_dir)) { create_dir (user_dir); } scan_plugins_in_dir (user_dir); g_free (user_dir); // find system plugins scan_plugins_in_dir (PLUGIN_DIR); // activate plugins for (GList *l=gnome_cmd_data_get_auto_load_plugins (); l; l=l->next) { char *name = (gchar *) l->data; for (GList *l2 = plugins; l2; l2 = l2->next) { auto data = static_cast (l2->data); if (strcmp (name, data->fname) == 0) data->autoload = TRUE; } } // inactivate plugins that shouldn't be autoloaded for (GList *l=plugins; l; l=l->next) { auto data = static_cast (l->data); if (!data->autoload) inactivate_plugin (data); } } void plugin_manager_shutdown () { GList *out = nullptr; for (GList *l=plugins; l; l=l->next) { auto data = static_cast (l->data); if (data->active) out = g_list_append (out, data->fname); } gnome_cmd_data_set_auto_load_plugins (out); } GList *plugin_manager_get_all () { return plugins; } static PluginData *get_selected_plugin (GtkTreeView *view) { GtkTreeModel *model; GtkTreeIter iter; PluginData *data; g_return_val_if_fail (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (view), &model, &iter), nullptr); gtk_tree_model_get (model, &iter, 4, &data, -1); return data; } static void on_plugin_selection_changed (GtkTreeSelection *selection, GtkWidget *dialog) { GtkWidget *toggle_button = lookup_widget (dialog, "toggle_button"); GtkWidget *conf_button = lookup_widget (dialog, "conf_button"); GtkWidget *about_button = lookup_widget (dialog, "about_button"); GtkTreeModel *model; GtkTreeIter iter; PluginData *data; if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gtk_tree_model_get (model, &iter, 4, &data, -1); gtk_widget_set_sensitive (about_button, TRUE); gtk_button_set_label (GTK_BUTTON (toggle_button), data->active ? _("Disable") : _("Enable")); gtk_widget_set_sensitive (toggle_button, TRUE); gtk_widget_set_sensitive (conf_button, data->active); } else { gtk_widget_set_sensitive (toggle_button, FALSE); gtk_widget_set_sensitive (about_button, FALSE); gtk_widget_set_sensitive (conf_button, FALSE); } } static void update_plugin_list (GtkTreeView *view, GtkWidget *dialog) { GtkTreeModel *model = gtk_tree_view_get_model (view); GtkTreeIter iter; gboolean only_update = gtk_tree_model_get_iter_first (model, &iter); GtkListStore *store = GTK_LIST_STORE (model); for (GList *i=plugins; i; i=i->next) { auto data = static_cast (i->data); if (only_update) { gtk_list_store_set (store, &iter, 0, data->active ? exec_pixbuf : blank_pixbuf, 1, (gchar *) data->info->name, 4, data, -1); gtk_tree_model_iter_next (model, &iter); } else { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, data->active ? exec_pixbuf : blank_pixbuf, 1, (gchar *) data->info->name, 2, (gchar *) data->info->version, 3, data->fname, 4, data, -1); } } on_plugin_selection_changed (gtk_tree_view_get_selection (view), dialog); } inline void do_toggle (GtkWidget *dialog) { GtkTreeView *view = GTK_TREE_VIEW (lookup_widget (dialog, "avail_view")); PluginData *data = get_selected_plugin (view); if (!data) return; if (data->active) inactivate_plugin (data); else activate_plugin (data); update_plugin_list (view, dialog); } static void on_plugin_activated (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, GtkWidget *dialog) { do_toggle (dialog); } static void on_toggle (GtkButton *button, GtkWidget *dialog) { do_toggle (dialog); } static void on_configure (GtkButton *button, GtkWidget *dialog) { GtkTreeView *view = GTK_TREE_VIEW (lookup_widget (dialog, "avail_view")); PluginData *data = get_selected_plugin (view); g_return_if_fail (data != nullptr); g_return_if_fail (data->active); gnome_cmd_plugin_configure (data->plugin); } static void on_about (GtkButton *button, GtkWidget *dialog) { GtkTreeView *view = GTK_TREE_VIEW (lookup_widget (dialog, "avail_view")); PluginData *data = get_selected_plugin (view); g_return_if_fail (data != nullptr); GtkWidget *about = gnome_cmd_about_plugin_new (data->info); gtk_window_set_transient_for (GTK_WINDOW (about), *main_win); g_object_ref (about); gtk_widget_show (about); } static void on_close (GtkButton *button, GtkWidget *dialog) { gtk_widget_destroy (dialog); } void plugin_manager_show () { GtkWidget *dialog, *hbox, *bbox, *button; GtkListStore *avail_store; GtkWidget *avail_view; dialog = gnome_cmd_dialog_new (_("Available plugins")); g_object_ref (dialog); hbox = create_vbox (dialog, FALSE, 6); avail_store = gtk_list_store_new (5, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER); avail_view = create_treeview (dialog, "avail_view", GTK_TREE_MODEL (avail_store), 20, GTK_SIGNAL_FUNC (on_plugin_selection_changed), nullptr); create_treeview_column (avail_view, 0, 20, ""); create_treeview_column (avail_view, 1, 200, _("Name")); create_treeview_column (avail_view, 2, 50, _("Version")); create_treeview_column (avail_view, 3, 50, _("File")); bbox = create_hbuttonbox (dialog); gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_START); button = create_button (GTK_WIDGET (dialog), _("_Enable"), GTK_SIGNAL_FUNC (on_toggle)); g_object_set_data (G_OBJECT (dialog), "toggle_button", button); gtk_box_pack_start (GTK_BOX (bbox), button, TRUE, FALSE, 0); button = create_button (GTK_WIDGET (dialog), _("_Configure"), GTK_SIGNAL_FUNC (on_configure)); g_object_set_data (G_OBJECT (dialog), "conf_button", button); gtk_widget_set_sensitive (button, FALSE); gtk_box_pack_start (GTK_BOX (bbox), button, TRUE, FALSE, 0); button = create_button (GTK_WIDGET (dialog), _("_About"), GTK_SIGNAL_FUNC (on_about)); g_object_set_data (G_OBJECT (dialog), "about_button", button); gtk_widget_set_sensitive (button, FALSE); gtk_box_pack_start (GTK_BOX (bbox), button, TRUE, FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), avail_view, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (hbox), bbox, FALSE, TRUE, 0); gnome_cmd_dialog_add_expanding_category (GNOME_CMD_DIALOG (dialog), hbox); avail_view = lookup_widget (avail_view, "avail_view"); g_signal_connect (GTK_TREE_VIEW (avail_view), "row-activated", G_CALLBACK (on_plugin_activated), dialog); if (!exec_pixbuf) exec_pixbuf = IMAGE_get_pixbuf (PIXMAP_EXEC_WHEEL); if (!blank_pixbuf) blank_pixbuf = IMAGE_get_pixbuf (PIXMAP_FLIST_ARROW_BLANK); update_plugin_list (GTK_TREE_VIEW (avail_view), dialog); gnome_cmd_dialog_add_button (GNOME_CMD_DIALOG (dialog), GTK_STOCK_CLOSE, GTK_SIGNAL_FUNC(on_close), dialog); gtk_window_set_transient_for (GTK_WINDOW (dialog), *main_win); gtk_widget_set_size_request (GTK_WIDGET (dialog), 500, 300); gtk_window_set_resizable ((GtkWindow *) GTK_WIDGET (dialog), TRUE); gtk_widget_show_all (GTK_WIDGET (dialog)); } gchar* get_plugin_config_location() { gchar* returnString {nullptr}; string userPluginConfigDir = get_package_config_dir(); userPluginConfigDir += (char*) "/plugins"; if (!is_dir_existing(userPluginConfigDir.c_str())) { create_dir (userPluginConfigDir.c_str()); } returnString = g_strdup(userPluginConfigDir.c_str()); return returnString; }