/* * 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 . */ #include #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; if (!g_variant_is_of_type (layout, G_VARIANT_TYPE ("(ia{sv}av)"))) { g_warning ("Type of return value for 'layout' property in " "'GetLayout' call should be '(ia{sv}av)' but got '%s'", g_variant_get_type_string (layout)); return; } 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)); /* Reposition menu to accomodate any size changes */ /* Menu size never changes with GTK 3.20 or earlier */ gtk_menu_reposition(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; GtkWidget *toplevel; GdkScreen *screen; GdkVisual *visual; GtkStyleContext *context; G_OBJECT_CLASS (sn_dbus_menu_parent_class)->constructed (object); menu = SN_DBUS_MENU (object); /*Set up theme and transparency support*/ toplevel = gtk_widget_get_toplevel(GTK_WIDGET(menu)); /* Fix any failures of compiz/other wm's to communicate with gtk for transparency */ screen = gtk_widget_get_screen(GTK_WIDGET(toplevel)); visual = gdk_screen_get_rgba_visual(screen); gtk_widget_set_visual(GTK_WIDGET(toplevel), visual); /* Set menu and it's toplevel window to follow panel theme */ context = gtk_widget_get_style_context (GTK_WIDGET(toplevel)); gtk_style_context_add_class(context,"gnome-panel-menu-bar"); gtk_style_context_add_class(context,"mate-panel-menu-bar"); 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_clear_pointer (&menu->bus_name, g_free); g_clear_pointer (&menu->object_path, g_free); 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); }