summaryrefslogtreecommitdiff
path: root/applets/notification_area/status-notifier/sn-dbus-menu.c
diff options
context:
space:
mode:
Diffstat (limited to 'applets/notification_area/status-notifier/sn-dbus-menu.c')
-rw-r--r--applets/notification_area/status-notifier/sn-dbus-menu.c472
1 files changed, 472 insertions, 0 deletions
diff --git a/applets/notification_area/status-notifier/sn-dbus-menu.c b/applets/notification_area/status-notifier/sn-dbus-menu.c
new file mode 100644
index 00000000..9cbab345
--- /dev/null
+++ b/applets/notification_area/status-notifier/sn-dbus-menu.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2016 Alberts Muktupāvels
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "sn-dbus-menu.h"
+#include "sn-dbus-menu-gen.h"
+#include "sn-dbus-menu-item.h"
+
+struct _SnDBusMenu
+{
+ GtkMenu parent;
+
+ GHashTable *items;
+
+ GCancellable *cancellable;
+
+ gchar *bus_name;
+ gchar *object_path;
+ guint name_id;
+
+ SnDBusMenuGen *proxy;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_BUS_NAME,
+ PROP_OBJECT_PATH,
+
+ LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
+static const gchar *property_names[] =
+{
+ "accessible-desc",
+ "children-display",
+ "disposition",
+ "enabled",
+ "icon-data",
+ "icon-name",
+ "label",
+ "shortcut",
+ "toggle-type",
+ "toggle-state",
+ "type",
+ "visible",
+ NULL
+};
+
+G_DEFINE_TYPE (SnDBusMenu, sn_dbus_menu, GTK_TYPE_MENU)
+
+static void
+activate_cb (GtkWidget *widget,
+ SnDBusMenu *menu)
+{
+ guint id;
+
+ if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)) != NULL)
+ return;
+
+ id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "item-id"));
+ sn_dbus_menu_gen_call_event_sync (menu->proxy, id, "clicked",
+ g_variant_new ("v", g_variant_new_int32 (0)),
+ gtk_get_current_event_time (), NULL, NULL);
+}
+
+static GtkMenu *
+layout_update_item (SnDBusMenu *menu,
+ GtkMenu *gtk_menu,
+ guint id,
+ GVariant *props)
+{
+ SnDBusMenuItem *item;
+
+ if (id == 0)
+ return gtk_menu;
+
+ item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id));
+
+ if (item == NULL)
+ {
+ item = sn_dbus_menu_item_new (props);
+
+ g_object_set_data (G_OBJECT (item->item), "item-id", GUINT_TO_POINTER (id));
+ gtk_menu_shell_append (GTK_MENU_SHELL (gtk_menu), item->item);
+
+ item->activate_id = g_signal_connect (item->item, "activate",
+ G_CALLBACK (activate_cb), menu);
+
+ g_hash_table_replace (menu->items, GUINT_TO_POINTER (id), item);
+ }
+ else
+ {
+ sn_dbus_menu_item_update_props (item, props);
+ }
+
+ return item->submenu;
+}
+
+static void
+layout_parse (SnDBusMenu *menu,
+ GVariant *layout,
+ GtkMenu *gtk_menu)
+{
+ guint id;
+ GVariant *props;
+ GVariant *items;
+ GtkMenu *submenu;
+ GVariantIter iter;
+ GVariant *child;
+
+ g_variant_get (layout, "(i@a{sv}@av)", &id, &props, &items);
+
+ submenu = layout_update_item (menu, gtk_menu, id, props);
+ g_variant_unref (props);
+
+ g_variant_iter_init (&iter, items);
+ while ((child = g_variant_iter_next_value (&iter)))
+ {
+ GVariant *value;
+
+ value = g_variant_get_variant (child);
+
+ layout_parse (menu, value, submenu);
+ g_variant_unref (value);
+
+ g_variant_unref (child);
+ }
+
+ g_variant_unref (items);
+}
+
+static void
+get_layout_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GVariant *layout;
+ guint revision;
+ GError *error;
+ SnDBusMenu *menu;
+
+ error = NULL;
+ sn_dbus_menu_gen_call_get_layout_finish (SN_DBUS_MENU_GEN (source_object),
+ &revision, &layout, res, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ menu = SN_DBUS_MENU (user_data);
+
+ if (error != NULL)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_hash_table_remove_all (menu->items);
+ layout_parse (menu, layout, GTK_MENU (menu));
+ g_variant_unref (layout);
+}
+
+static void
+update_layout (SnDBusMenu *menu,
+ gint parent)
+{
+ gint depth;
+
+ parent = 0;
+ depth = -1;
+
+ sn_dbus_menu_gen_call_get_layout (menu->proxy, parent, depth,
+ property_names, menu->cancellable,
+ get_layout_cb, menu);
+}
+
+static void
+items_properties_updated_cb (SnDBusMenuGen *proxy,
+ GVariant *updated_props,
+ GVariant *removed_props,
+ SnDBusMenu *menu)
+{
+ GVariantIter iter;
+ guint id;
+ GVariant *props;
+ SnDBusMenuItem *item;
+
+ g_variant_iter_init (&iter, updated_props);
+ while (g_variant_iter_next (&iter, "(i@a{sv})", &id, &props))
+ {
+ item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id));
+
+ if (item != NULL)
+ sn_dbus_menu_item_update_props (item, props);
+
+ g_variant_unref (props);
+ }
+
+ g_variant_iter_init (&iter, removed_props);
+ while (g_variant_iter_next (&iter, "(i@as)", &id, &props))
+ {
+ item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id));
+
+ if (item != NULL)
+ sn_dbus_menu_item_remove_props (item, props);
+
+ g_variant_unref (props);
+ }
+}
+
+static void
+layout_updated_cb (SnDBusMenuGen *proxy,
+ guint revision,
+ gint parent,
+ SnDBusMenu *menu)
+{
+ update_layout (menu, parent);
+}
+
+static void
+item_activation_requested_cb (SnDBusMenuGen *proxy,
+ gint id,
+ guint timestamp,
+ SnDBusMenu *menu)
+{
+ g_debug ("activation requested: id - %d, timestamp - %d", id, timestamp);
+}
+
+static void
+map_cb (GtkWidget *widget,
+ SnDBusMenu *menu)
+{
+ gboolean need_update;
+
+ sn_dbus_menu_gen_call_event_sync (menu->proxy, 0, "opened",
+ g_variant_new ("v", g_variant_new_int32 (0)),
+ gtk_get_current_event_time (),
+ NULL, NULL);
+
+ sn_dbus_menu_gen_call_about_to_show_sync (menu->proxy, 0, &need_update,
+ NULL, NULL);
+
+ if (need_update)
+ {
+ update_layout (menu, 0);
+ }
+}
+
+static void
+unmap_cb (GtkWidget *widget,
+ SnDBusMenu *menu)
+{
+ sn_dbus_menu_gen_call_event_sync (menu->proxy, 0, "closed",
+ g_variant_new ("v", g_variant_new_int32 (0)),
+ gtk_get_current_event_time (),
+ NULL, NULL);
+}
+
+static void
+proxy_ready_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SnDBusMenuGen *proxy;
+ GError *error;
+ SnDBusMenu *menu;
+
+ error = NULL;
+ proxy = sn_dbus_menu_gen_proxy_new_finish (res, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ menu = SN_DBUS_MENU (user_data);
+ menu->proxy = proxy;
+
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_signal_connect (proxy, "items-properties-updated",
+ G_CALLBACK (items_properties_updated_cb), menu);
+
+ g_signal_connect (proxy, "layout-updated",
+ G_CALLBACK (layout_updated_cb), menu);
+
+ g_signal_connect (proxy, "item-activation-requested",
+ G_CALLBACK (item_activation_requested_cb), menu);
+
+ g_signal_connect (menu, "map", G_CALLBACK (map_cb), menu);
+ g_signal_connect (menu, "unmap", G_CALLBACK (unmap_cb), menu);
+
+ update_layout (menu, 0);
+}
+
+static void
+name_appeared_cb (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ SnDBusMenu *menu;
+
+ menu = SN_DBUS_MENU (user_data);
+
+ sn_dbus_menu_gen_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE,
+ menu->bus_name, menu->object_path,
+ menu->cancellable, proxy_ready_cb, menu);
+}
+
+static void
+name_vanished_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ SnDBusMenu *menu;
+
+ menu = SN_DBUS_MENU (user_data);
+
+ g_clear_object (&menu->proxy);
+}
+
+static void
+sn_dbus_menu_constructed (GObject *object)
+{
+ SnDBusMenu *menu;
+
+ G_OBJECT_CLASS (sn_dbus_menu_parent_class)->constructed (object);
+ menu = SN_DBUS_MENU (object);
+
+ menu->name_id = g_bus_watch_name (G_BUS_TYPE_SESSION, menu->bus_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ name_appeared_cb, name_vanished_cb,
+ menu, NULL);
+}
+
+static void
+sn_dbus_menu_dispose (GObject *object)
+{
+ SnDBusMenu *menu;
+
+ menu = SN_DBUS_MENU (object);
+
+ if (menu->name_id > 0)
+ {
+ g_bus_unwatch_name (menu->name_id);
+ menu->name_id = 0;
+ }
+
+ g_clear_pointer (&menu->items, g_hash_table_destroy);
+
+ g_cancellable_cancel (menu->cancellable);
+ g_clear_object (&menu->cancellable);
+ g_clear_object (&menu->proxy);
+
+ G_OBJECT_CLASS (sn_dbus_menu_parent_class)->dispose (object);
+}
+
+static void
+sn_dbus_menu_finalize (GObject *object)
+{
+ SnDBusMenu *menu;
+
+ menu = SN_DBUS_MENU (object);
+
+ g_free (menu->bus_name);
+ g_free (menu->object_path);
+
+ G_OBJECT_CLASS (sn_dbus_menu_parent_class)->finalize (object);
+}
+
+static void
+sn_dbus_menu_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SnDBusMenu *menu;
+
+ menu = SN_DBUS_MENU (object);
+
+ switch (property_id)
+ {
+ case PROP_BUS_NAME:
+ menu->bus_name = g_value_dup_string (value);
+ break;
+
+ case PROP_OBJECT_PATH:
+ menu->object_path = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+install_properties (GObjectClass *object_class)
+{
+ properties[PROP_BUS_NAME] =
+ g_param_spec_string ("bus-name", "bus-name", "bus-name", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_OBJECT_PATH] =
+ g_param_spec_string ("object-path", "object-path", "object-path", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+sn_dbus_menu_class_init (SnDBusMenuClass *menu_class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (menu_class);
+
+ object_class->constructed = sn_dbus_menu_constructed;
+ object_class->dispose = sn_dbus_menu_dispose;
+ object_class->finalize = sn_dbus_menu_finalize;
+ object_class->set_property = sn_dbus_menu_set_property;
+
+ install_properties (object_class);
+}
+
+static void
+sn_dbus_menu_init (SnDBusMenu *menu)
+{
+ menu->items = g_hash_table_new_full (NULL, NULL, NULL, sn_dubs_menu_item_free);
+ menu->cancellable = g_cancellable_new ();
+}
+
+GtkMenu *
+sn_dbus_menu_new (const gchar *bus_name,
+ const gchar *object_path)
+{
+ return g_object_new (SN_TYPE_DBUS_MENU,
+ "bus-name", bus_name,
+ "object-path", object_path,
+ NULL);
+}