Add the new PidginPluginsMenu which manages itself when plugins and loaded/unloaded.
--- a/pidgin/glade/pidgin3.xml.in Tue Mar 31 05:38:10 2020 -0500
+++ b/pidgin/glade/pidgin3.xml.in Tue Mar 31 23:30:54 2020 -0500
@@ -4,12 +4,14 @@
<glade-widget-class name="PidginAccountChooser" generic-name="account_chooser" title="AccountChooser"/>
<glade-widget-class name="PidginInviteDialog" generic-name="invite_dialog" title="InviteDialog"/>
<glade-widget-class name="PidginMenuTray" generic-name="menu_tray" title="MenuTray"/>
+ <glade-widget-class name="PidginPluginsMenu" generic-name="plugins_menu" title="PluginsMenu"/> <glade-widget-class name="PidginScrollBook" generic-name="scroll_book" title="ScrollBook"/>
<glade-widget-group name="pidgin" title="Pidgin">
<glade-widget-class-ref name="PidginAccountChooser"/>
<glade-widget-class-ref name="PidginInviteDialog"/>
<glade-widget-class-ref name="PidginMenuTray"/>
+ <glade-widget-class-ref name="PidginPluginsMenu"/> <glade-widget-class-ref name="PidginScrollBook"/>
--- a/pidgin/meson.build Tue Mar 31 05:38:10 2020 -0500
+++ b/pidgin/meson.build Tue Mar 31 23:30:54 2020 -0500
@@ -46,6 +46,7 @@
'pidginprotocolchooser.c',
@@ -102,6 +103,7 @@
'pidginprotocolchooser.h',
--- a/pidgin/pidginactiongroup.c Tue Mar 31 05:38:10 2020 -0500
+++ b/pidgin/pidginactiongroup.c Tue Mar 31 23:30:54 2020 -0500
@@ -36,7 +36,6 @@
#include "pidgin/gtkxfer.h"
#include "pidgin/pidginabout.h"
#include "pidgin/pidginlog.h"
-#include "pidgin/pidginpluginsdialog.h"
struct _PidginActionGroup {
GSimpleActionGroup parent;
@@ -430,20 +429,6 @@
-pidgin_action_group_plugins(GSimpleAction *simple, GVariant *parameter,
- GtkWidget *dialog = pidgin_plugins_dialog_new();
- gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window));
- gtk_widget_show_all(dialog);
pidgin_action_group_preferences(GSimpleAction *simple, GVariant *parameter,
@@ -587,9 +572,6 @@
.name = PIDGIN_ACTION_ONLINE_HELP,
.activate = pidgin_action_group_online_help,
- .name = PIDGIN_ACTION_PLUGINS,
- .activate = pidgin_action_group_plugins,
.name = PIDGIN_ACTION_PREFERENCES,
.activate = pidgin_action_group_preferences,
--- a/pidgin/pidginactiongroup.h Tue Mar 31 05:38:10 2020 -0500
+++ b/pidgin/pidginactiongroup.h Tue Mar 31 23:30:54 2020 -0500
@@ -125,13 +125,6 @@
#define PIDGIN_ACTION_ONLINE_HELP ("online-help")
- * PIDGIN_ACTION_PLUGINS:
- * A constant that represents the plugins action.
-#define PIDGIN_ACTION_PLUGINS ("plugins")
* PIDGIN_ACTION_PREFERENCES:
* A constant that represents the preferences action.
--- a/pidgin/pidginbuddylistmenu.c Tue Mar 31 05:38:10 2020 -0500
+++ b/pidgin/pidginbuddylistmenu.c Tue Mar 31 23:30:54 2020 -0500
@@ -22,10 +22,13 @@
#include "pidginbuddylistmenu.h"
+#include <pidgin/pidginpluginsmenu.h> struct _PidginBuddyListMenu {
/******************************************************************************
@@ -36,6 +39,9 @@
pidgin_buddy_list_menu_init(PidginBuddyListMenu *menu) {
gtk_widget_init_template(GTK_WIDGET(menu));
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu->plugins), + pidgin_plugins_menu_new()); @@ -49,6 +55,8 @@
gtk_widget_class_bind_template_child(widget_class, PidginBuddyListMenu,
+ gtk_widget_class_bind_template_child(widget_class, PidginBuddyListMenu, /******************************************************************************
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidginpluginsmenu.c Tue Mar 31 23:30:54 2020 -0500
@@ -0,0 +1,303 @@
+ * Pidgin 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 +#include "pidginpluginsmenu.h" +#include "pidgin/pidginpluginsdialog.h" +struct _PidginPluginsMenu { + GSimpleActionGroup *action_group; + GHashTable *plugin_items; +#define PIDGIN_PLUGINS_MENU_ACTION_PREFIX "plugins-menu" +/****************************************************************************** + *****************************************************************************/ +pidgin_plugins_menu_action_activated(GSimpleAction *simple, GVariant *parameter, + PurplePluginAction *action = (PurplePluginAction *)data; + if(action != NULL && action->callback != NULL) { + action->callback(action); +pidgin_plugins_menu_add_plugin_actions(PidginPluginsMenu *menu, + GPluginPluginInfo *info = NULL; + PurplePluginActionsCb actions_cb = NULL; + GList *actions = NULL, *l = NULL; + GtkWidget *submenu = NULL, *item = NULL; + info = gplugin_plugin_get_info(GPLUGIN_PLUGIN(plugin)); + actions_cb = purple_plugin_info_get_actions_cb(PURPLE_PLUGIN_INFO(info)); + if(actions_cb == NULL) { + g_object_unref(G_OBJECT(info)); + actions = actions_cb(plugin); + g_object_unref(G_OBJECT(info)); + submenu = gtk_menu_new(); + for(l = actions, i = 0; l != NULL; l = l->next, i++) { + PurplePluginAction *action = (PurplePluginAction *)l->data; + GSimpleAction *gaction = NULL; + GtkWidget *action_item = NULL; + gchar *action_base_name = NULL; + gchar *action_full_name = NULL; + if(action->label == NULL) { + action_base_name = g_strdup_printf("%s-%d", + gplugin_plugin_info_get_id(info), + action_full_name = g_strdup_printf("%s.%s", + PIDGIN_PLUGINS_MENU_ACTION_PREFIX, + /* create the menu item with the full action name */ + action_item = gtk_menu_item_new_with_label(action->label); + gtk_actionable_set_action_name(GTK_ACTIONABLE(action_item), + gtk_widget_show(action_item); + g_free(action_full_name); + /* add our action item to the menu */ + gtk_menu_shell_append(GTK_MENU_SHELL(submenu), action_item); + /* now Creation the gaction with the base name */ + gaction = g_simple_action_new(action_base_name, NULL); + g_free(action_base_name); + /* now connect to the activate signal of the action using + * g_signal_connect_data with a destory notify to free the plugin action + * when the signal handler is removed. + g_signal_connect_data(G_OBJECT(gaction), "activate", + G_CALLBACK(pidgin_plugins_menu_action_activated), + (GClosureNotify)purple_plugin_action_free, + /* finally add the action to the action group and remove our ref */ + g_action_map_add_action(G_ACTION_MAP(menu->action_group), + g_object_unref(G_OBJECT(gaction)); + item = gtk_menu_item_new_with_label(gplugin_plugin_info_get_name(info)); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + g_hash_table_insert(menu->plugin_items, + g_object_ref(G_OBJECT(plugin)), + g_object_unref(G_OBJECT(info)); + /* make sure that our separator is visible */ + gtk_widget_show(menu->separator); +pidgin_plugins_menu_remove_plugin_actions(PidginPluginsMenu *menu, + GPluginPluginInfo *info = NULL; + PurplePluginActionsCb actions_cb = NULL; + GList *actions = NULL, *l = NULL; + /* try remove the menu item from plugin from the hash table. If we didn't + * remove anything, we have nothing to do so bail. + if(!g_hash_table_remove(menu->plugin_items, plugin)) { + info = gplugin_plugin_get_info(GPLUGIN_PLUGIN(plugin)); + actions_cb = purple_plugin_info_get_actions_cb(PURPLE_PLUGIN_INFO(info)); + if(actions_cb == NULL) { + g_object_unref(G_OBJECT(info)); + actions = actions_cb(plugin); + g_object_unref(G_OBJECT(info)); + /* now walk through the actions and remove them from the action group. */ + for(l = actions, i = 0; l != NULL; l = l->next, i++) { + name = g_strdup_printf("%s-%d", gplugin_plugin_info_get_id(info), i); + g_action_map_remove_action(G_ACTION_MAP(menu->action_group), name); + g_object_unref(G_OBJECT(info)); + /* finally, if this was the last item in the list, hide the separator. */ + if(g_hash_table_size(menu->plugin_items) == 0) { + gtk_widget_hide(menu->separator); +/****************************************************************************** + * Purple Signal Callbacks + *****************************************************************************/ +pidgin_plugins_menu_plugin_load_cb(PurplePlugin *plugin, gpointer data) { + pidgin_plugins_menu_add_plugin_actions(PIDGIN_PLUGINS_MENU(data), plugin); +pidgin_plugins_menu_plugin_unload_cb(PurplePlugin *plugin, gpointer data) { + pidgin_plugins_menu_remove_plugin_actions(PIDGIN_PLUGINS_MENU(data), +/****************************************************************************** + *****************************************************************************/ +pidgin_plugins_menu_show_manager(GSimpleAction *action, GVariant *parameter, + GtkWidget *dialog = pidgin_plugins_dialog_new(); + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window)); + gtk_widget_show_all(dialog); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +G_DEFINE_TYPE(PidginPluginsMenu, pidgin_plugins_menu, GTK_TYPE_MENU) +pidgin_plugins_menu_init(PidginPluginsMenu *menu) { + GActionEntry actions[] = { + .activate = pidgin_plugins_menu_show_manager, + /* initialize our template */ + gtk_widget_init_template(GTK_WIDGET(menu)); + /* create our internal action group and assign it to ourself */ + menu->action_group = g_simple_action_group_new(); + g_action_map_add_action_entries(G_ACTION_MAP(menu->action_group), actions, + G_N_ELEMENTS(actions), NULL); + gtk_widget_insert_action_group(GTK_WIDGET(menu), + PIDGIN_PLUGINS_MENU_ACTION_PREFIX, + G_ACTION_GROUP(menu->action_group)); + /* create our storage for the items */ + menu->plugin_items = g_hash_table_new_full(g_direct_hash, g_direct_equal, + (GDestroyNotify)gtk_widget_destroy); + /* finally connect to the purple signals to stay up to date */ + handle = purple_plugins_get_handle(); + purple_signal_connect(handle, "plugin-load", menu, + PURPLE_CALLBACK(pidgin_plugins_menu_plugin_load_cb), + purple_signal_connect(handle, "plugin-unload", menu, + PURPLE_CALLBACK(pidgin_plugins_menu_plugin_unload_cb), +pidgin_plugins_menu_finalize(GObject *obj) { + purple_signals_disconnect_by_handle(obj); + G_OBJECT_CLASS(pidgin_plugins_menu_parent_class)->finalize(obj); +pidgin_plugins_menu_class_init(PidginPluginsMenuClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + obj_class->finalize = pidgin_plugins_menu_finalize; + gtk_widget_class_set_template_from_resource( + "/im/pidgin/Pidgin/Plugins/menu.ui" + gtk_widget_class_bind_template_child(widget_class, PidginPluginsMenu, +/****************************************************************************** + *****************************************************************************/ +pidgin_plugins_menu_new(void) { + return GTK_WIDGET(g_object_new(PIDGIN_TYPE_PLUGINS_MENU, NULL)); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidginpluginsmenu.h Tue Mar 31 23:30:54 2020 -0500
@@ -0,0 +1,44 @@
+ * Pidgin 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 +#ifndef PIDGIN_PLUGINS_MENU_H +#define PIDGIN_PLUGINS_MENU_H +#define PIDGIN_TYPE_PLUGINS_MENU (pidgin_plugins_menu_get_type()) +G_DECLARE_FINAL_TYPE(PidginPluginsMenu, pidgin_plugins_menu, PIDGIN, + * pidgin_action_group_new: + * Creates a new #PidginPluginsMenu instance that keeps itself up to date. + * Returns: (transfer full): The new #PidginPluginsMenu instance. +GtkWidget *pidgin_plugins_menu_new(void); +#endif /* PIDGIN_PLUGINS_MENU_H */ --- a/pidgin/resources/BuddyList/menu.ui Tue Mar 31 05:38:10 2020 -0500
+++ b/pidgin/resources/BuddyList/menu.ui Tue Mar 31 23:30:54 2020 -0500
@@ -236,16 +236,6 @@
- <object class="GtkMenuItem" id="plugins">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="action_name">blist.plugins</property>
- <property name="label" translatable="yes">Plu_gins</property>
- <property name="use_underline">True</property>
- <accelerator key="u" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<object class="GtkMenuItem" id="preferences">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -328,6 +318,14 @@
+ <object class="GtkMenuItem" id="plugins"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">_Plugins</property> + <property name="use_underline">True</property> <object class="GtkMenuItem">
<property name="visible">True</property>
<property name="can_focus">False</property>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/resources/Plugins/menu.ui Tue Mar 31 23:30:54 2020 -0500
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.2 +Pidgin - Internet Messenger +Copyright (C) Pidgin Developers <devel@pidgin.im> +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 02110-1301, USA. + <requires lib="gtk+" version="3.22"/> + <!-- interface-license-type gplv2 --> + <!-- interface-name Pidgin --> + <!-- interface-description Internet Messenger --> + <!-- interface-copyright Pidgin Developers <devel@pidgin.im> --> + <template class="PidginPluginsMenu" parent="GtkMenu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <object class="GtkMenuItem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="action_name">plugins-menu.manager</property> + <property name="label" translatable="yes">_Manager</property> + <property name="use_underline">True</property> + <accelerator key="u" signal="activate" modifiers="GDK_CONTROL_MASK"/> + <object class="GtkSeparatorMenuItem" id="separator"> + <property name="can_focus">False</property> --- a/pidgin/resources/pidgin.gresource.xml Tue Mar 31 05:38:10 2020 -0500
+++ b/pidgin/resources/pidgin.gresource.xml Tue Mar 31 23:30:54 2020 -0500
@@ -12,6 +12,7 @@
<file compressed="true">Debug/plugininfo.ui</file>
<file compressed="true">Log/log-viewer.ui</file>
<file compressed="true">Plugins/dialog.ui</file>
+ <file compressed="true">Plugins/menu.ui</file> <file compressed="true">Prefs/prefs.ui</file>
<file compressed="true">Prefs/vv.ui</file>
<file compressed="true">Privacy/dialog.ui</file>