/* * Copyright 2010 Canonical, Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 3, as published by the Free Software Foundation. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, see . * * Authors: * Cody Russell */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "bridge.h" #include "application-menu-registrar-client.h" #define APP_MENU_DBUS_NAME "org.ayatana.WindowMenu.Registrar" #define APP_MENU_DBUS_OBJECT "/org/ayatana/WindowMenu/Registrar" #define APP_MENU_INTERFACE "org.ayatana.WindowMenu.Registrar" #define APP_MENU_PATH "/this/is/a/long/object/path" static void app_menu_bridge_insert (UbuntuMenuProxy *proxy, GtkWidget *shell, GtkWidget *child, guint position); static gboolean app_menu_bridge_show_local (UbuntuMenuProxy *proxy); struct _AppMenuBridgePrivate { GHashTable *items; /* */ DbusmenuServer *server; }; static DBusGProxy *dbusproxy = NULL; static gboolean registered = FALSE; G_DEFINE_DYNAMIC_TYPE(AppMenuBridge, app_menu_bridge, UBUNTU_TYPE_MENU_PROXY) static void app_menu_bridge_init (AppMenuBridge *bridge) { bridge->priv = G_TYPE_INSTANCE_GET_PRIVATE (bridge, APP_MENU_TYPE_BRIDGE, AppMenuBridgePrivate); bridge->priv->items = g_hash_table_new (g_direct_hash, g_direct_equal); bridge->priv->items = g_hash_table_new (g_direct_hash, g_direct_equal); bridge->priv->server = dbusmenu_server_new (APP_MENU_PATH); } static void app_menu_bridge_finalize (GObject *object) { g_hash_table_destroy (APP_MENU_BRIDGE (object)->priv->items); } static void app_menu_bridge_class_init (AppMenuBridgeClass *class) { UbuntuMenuProxyClass *proxy_class; GObjectClass *object_class; app_menu_bridge_parent_class = g_type_class_peek_parent (class); proxy_class = UBUNTU_MENU_PROXY_CLASS (class); object_class = G_OBJECT_CLASS (class); proxy_class->insert = app_menu_bridge_insert; proxy_class->show_local = app_menu_bridge_show_local; object_class->finalize = app_menu_bridge_finalize; g_type_class_add_private (class, sizeof (AppMenuBridgePrivate)); } static void app_menu_bridge_class_finalize (AppMenuBridgeClass *class) { } static GtkWidget * find_menu_label (GtkWidget *widget) { GtkWidget *label = NULL; if (GTK_IS_LABEL (widget)) return widget; if (GTK_IS_CONTAINER (widget)) { GList *children; GList *l; children = gtk_container_get_children (GTK_CONTAINER (widget)); for (l = children; l; l = l->next) { label = find_menu_label (l->data); if (label) break; } g_list_free (children); } return label; } static const gchar * get_menu_label_text (GtkWidget *menuitem) { GtkWidget *label = find_menu_label (menuitem); if (label) return gtk_label_get_text (GTK_LABEL (label)); return NULL; } static void item_activated (DbusmenuMenuitem *item, guint timestamp, gpointer user_data) { GtkWidget *child; if (user_data != NULL) { child = (GtkWidget *)user_data; if (GTK_IS_MENU_ITEM (child)) { gtk_menu_item_activate (GTK_MENU_ITEM (child)); } } } static void widget_notify_cb (GtkWidget *widget, GParamSpec *pspec, gpointer data) { DbusmenuMenuitem *child = (DbusmenuMenuitem *)data; if (pspec->name == g_intern_static_string ("sensitive")) { dbusmenu_menuitem_property_set_bool (child, DBUSMENU_MENUITEM_PROP_ENABLED, gtk_widget_get_sensitive (widget)); } else if (pspec->name == g_intern_static_string ("label")) { dbusmenu_menuitem_property_set (child, DBUSMENU_MENUITEM_PROP_LABEL, gtk_menu_item_get_label (GTK_MENU_ITEM (widget))); } else if (pspec->name == g_intern_static_string ("visible")) { dbusmenu_menuitem_property_set_bool (child, DBUSMENU_MENUITEM_PROP_VISIBLE, gtk_widget_get_visible (widget)); } } static void toplevel_realized (GtkWidget *widget, gpointer user_data) { /* Register the toplevel window now that it's been realized. */ org_ayatana_WindowMenu_Registrar_register_window (dbusproxy, GDK_WINDOW_XID (gtk_widget_get_window (widget)), APP_MENU_PATH, NULL); registered = TRUE; } static void toplevel_notify_cb (GtkWidget *widget, GParamSpec *pspec, UbuntuMenuProxy *proxy) { if (pspec->name == g_intern_static_string ("parent")) { AppMenuBridge *bridge = APP_MENU_BRIDGE (proxy); DbusmenuMenuitem *root = g_hash_table_lookup (bridge->priv->items, widget); if (root) { dbusmenu_server_set_root (bridge->priv->server, root); } if (!registered) { GtkWidget *parent = gtk_widget_get_toplevel (widget); if (!GTK_IS_WINDOW (parent)) { /* The current toplevel widget is not our final toplevel widget, as it's * not a GtkWindow. Let's defer registration until we have a real toplevel. */ g_signal_connect (G_OBJECT (parent), "notify", G_CALLBACK (toplevel_notify_cb), proxy); return; } else { /* This is the real toplevel window widget. If it's already * realized then go ahead and register it, otherwise wait until * it's been realized. */ if (gtk_widget_get_realized (widget)) { org_ayatana_WindowMenu_Registrar_register_window (dbusproxy, GDK_WINDOW_XID (gtk_widget_get_window (widget)), APP_MENU_PATH, NULL); registered = TRUE; } else { g_signal_connect (parent, "realize", G_CALLBACK (toplevel_realized), NULL); } } } } } static void checkbox_toggled (GtkWidget *widget, DbusmenuMenuitem *mi) { dbusmenu_menuitem_property_set_int (mi, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED); } static void app_menu_bridge_insert (UbuntuMenuProxy *proxy, GtkWidget *parent, GtkWidget *child, guint position) { AppMenuBridge *bridge; AppMenuBridgePrivate *priv; DbusmenuMenuitem *item; DbusmenuMenuitem *parent_item = NULL; gboolean append = FALSE; if (GTK_IS_TEAROFF_MENU_ITEM (child)) return; bridge = APP_MENU_BRIDGE (proxy); priv = bridge->priv; if (g_hash_table_lookup (bridge->priv->items, child) != NULL) return; if (GTK_IS_MENU_BAR (parent)) { if (g_hash_table_lookup (bridge->priv->items, parent) == NULL) { DbusmenuMenuitem *root = dbusmenu_menuitem_new (); g_hash_table_insert (bridge->priv->items, parent, root); parent_item = root; } else { parent_item = g_hash_table_lookup (bridge->priv->items, parent); } GtkWidget *toplevel = gtk_widget_get_toplevel (parent); g_signal_connect (G_OBJECT (toplevel), "notify", G_CALLBACK (toplevel_notify_cb), proxy); append = TRUE; } else if (GTK_IS_MENU (parent)) { GtkWidget *attach; g_object_get (parent, "attach-widget", &attach, NULL); if (attach == NULL) return; if (g_hash_table_lookup (bridge->priv->items, parent) != NULL) { parent_item = g_hash_table_lookup (bridge->priv->items, parent); } else { if (g_hash_table_lookup (bridge->priv->items, attach) != NULL) { parent_item = g_hash_table_lookup (bridge->priv->items, attach); } else { // XXX insert the attach item? } } } if (GTK_IS_MENU_ITEM (child)) { item = dbusmenu_menuitem_new (); g_hash_table_insert (bridge->priv->items, child, item); if (GTK_IS_SEPARATOR_MENU_ITEM (child)) { dbusmenu_menuitem_property_set (item, "type", "separator"); } else { if (GTK_IS_CHECK_MENU_ITEM (child)) { dbusmenu_menuitem_property_set (item, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, GTK_IS_RADIO_MENU_ITEM (child) ? DBUSMENU_MENUITEM_TOGGLE_RADIO : DBUSMENU_MENUITEM_TOGGLE_CHECK); dbusmenu_menuitem_property_set_int (item, DBUSMENU_MENUITEM_PROP_TOGGLE_STATE, gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (child)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED); g_signal_connect (child, "toggled", G_CALLBACK (checkbox_toggled), item); } dbusmenu_menuitem_property_set (item, "label", get_menu_label_text (child)); dbusmenu_menuitem_property_set_bool (item, DBUSMENU_MENUITEM_PROP_ENABLED, gtk_widget_get_sensitive (child)); g_signal_connect (G_OBJECT (child), "notify", G_CALLBACK (widget_notify_cb), item); g_signal_connect (G_OBJECT (item), "item_activated", G_CALLBACK (item_activated), child); if (parent_item) { if (append) dbusmenu_menuitem_child_append (parent_item, item); else dbusmenu_menuitem_child_prepend (parent_item, item); } } } } static gboolean app_menu_bridge_show_local (UbuntuMenuProxy *proxy) { const gchar *env = g_getenv ("APPMENU_DISPLAY_BOTH"); return (g_strcmp0 (env, "1") == 0); } G_MODULE_EXPORT void menu_proxy_module_load (UbuntuMenuProxyModule *module) { static gboolean registered = FALSE; if (!registered) { DBusGConnection *connection; connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); g_return_if_fail (connection != NULL); dbusproxy = dbus_g_proxy_new_for_name_owner (connection, APP_MENU_DBUS_NAME, APP_MENU_DBUS_OBJECT, APP_MENU_INTERFACE, NULL); g_return_if_fail (dbusproxy != NULL); app_menu_bridge_register_type (G_TYPE_MODULE (module)); registered = TRUE; } } G_MODULE_EXPORT void menu_proxy_module_unload (UbuntuMenuProxyModule *module) { }