← Back to team overview

ayatana-commits team mailing list archive

[Merge] lp:~bratsche/appmenu-gtk/almost-rewrite into lp:appmenu-gtk

 

Cody Russell has proposed merging lp:~bratsche/appmenu-gtk/almost-rewrite into lp:appmenu-gtk.

Requested reviews:
  Canonical Desktop Experience Team (canonical-dx-team)


So in the process of trying to store root and server per-window, I ended up rewriting almost the entire appmenu-gtk.
-- 
https://code.launchpad.net/~bratsche/appmenu-gtk/almost-rewrite/+merge/27638
Your team ayatana-commits is subscribed to branch lp:appmenu-gtk.
=== modified file 'src/bridge.c'
--- src/bridge.c	2010-06-10 21:00:06 +0000
+++ src/bridge.c	2010-06-15 15:04:25 +0000
@@ -21,6 +21,7 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include <dbus/dbus.h>
 #include <dbus/dbus-glib.h>
@@ -46,34 +47,155 @@
                                             GtkWidget         *child,
                                             guint              position);
 static gboolean app_menu_bridge_show_local (UbuntuMenuProxy   *proxy);
+static void     dbus_owner_change          (DBusGProxy        *proxy,
+                                            const gchar       *name,
+                                            const gchar       *prev,
+                                            const gchar       *new,
+                                            AppMenuBridge     *bridge);
+static void     toplevel_realized          (GtkWidget         *widget,
+                                            gpointer           user_data);
+static void     toplevel_notify_cb         (GtkWidget         *widget,
+                                            GParamSpec        *pspec,
+                                            UbuntuMenuProxy   *proxy);
+
+typedef struct _AppWindowContext AppWindowContext;
+
+struct _AppWindowContext
+{
+  GtkWidget        *window;
+  DbusmenuServer   *server;
+  DbusmenuMenuitem *root;
+  gchar            *path;
+  gboolean          registered;
+};
 
 struct _AppMenuBridgePrivate
 {
-  GHashTable *items;      /* <GtkWidget *, DbusmenuMenuitem *> */
+  GHashTable *windows;    /* XID,          AppWindowContext *> */
 
-  DbusmenuServer   *server;
+  DBusGConnection  *session_bus;
+  DBusGProxy       *dbusproxy;
+  DBusGProxy       *appmenuproxy;
+  gboolean          online;
 };
 
-static DBusGProxy *dbusproxy = NULL;
-static gboolean    registered = FALSE;
+typedef struct _RecurseContext
+{
+  AppMenuBridge    *bridge;
+  AppWindowContext *context;
+
+  gint count;
+  DbusmenuMenuitem *stack[30];
+} RecurseContext;
 
 G_DEFINE_DYNAMIC_TYPE(AppMenuBridge, app_menu_bridge, UBUNTU_TYPE_MENU_PROXY)
 
 static void
+app_menu_bridge_setup_proxy (AppMenuBridge *bridge)
+{
+  bridge->priv->appmenuproxy = dbus_g_proxy_new_for_name_owner (bridge->priv->session_bus,
+                                                                APP_MENU_DBUS_NAME,
+                                                                APP_MENU_DBUS_OBJECT,
+                                                                APP_MENU_INTERFACE,
+                                                                NULL);
+
+  bridge->priv->online = TRUE;
+}
+
+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);
+  bridge->priv->windows = g_hash_table_new (g_int64_hash, g_int64_equal);
+
+  bridge->priv->session_bus = dbus_g_bus_get(DBUS_BUS_SESSION, NULL);
+
+  bridge->priv->dbusproxy = dbus_g_proxy_new_for_name_owner (bridge->priv->session_bus,
+                                                             DBUS_SERVICE_DBUS,
+                                                             DBUS_PATH_DBUS,
+                                                             DBUS_INTERFACE_DBUS,
+                                                             NULL);
+
+  if (bridge->priv->dbusproxy)
+    {
+      dbus_g_proxy_add_signal (bridge->priv->dbusproxy,
+                               "NameOwnerChanged",
+                               G_TYPE_STRING,
+                               G_TYPE_STRING,
+                               G_TYPE_STRING,
+                               G_TYPE_INVALID);
+
+      dbus_g_proxy_connect_signal (bridge->priv->dbusproxy,
+                                   "NameOwnerChanged",
+                                   G_CALLBACK (dbus_owner_change),
+                                   bridge,
+                                   NULL);
+    }
+
+  app_menu_bridge_setup_proxy (bridge);
+}
+
+static void
+app_menu_bridge_dispose (GObject *object)
+{
+  AppMenuBridge *bridge = APP_MENU_BRIDGE (object);
+
+  if (bridge->priv->appmenuproxy)
+    {
+      g_object_unref (bridge->priv->appmenuproxy);
+    }
+
+  if (bridge->priv->dbusproxy)
+    {
+      g_object_unref (bridge->priv->dbusproxy);
+    }
+
+  if (bridge->priv->session_bus)
+    {
+      g_object_unref (bridge->priv->session_bus);
+    }
+}
+
+static void
+destroy_window_context (gpointer key,
+                        gpointer value,
+                        gpointer user_data)
+{
+  AppWindowContext *context = (AppWindowContext *)value;
+
+  if (context->root)
+    {
+      g_object_unref (context->root);
+      context->root = NULL;
+    }
+
+  if (context->server)
+    {
+      g_object_unref (context->server);
+      context->server = NULL;
+    }
+
+  if (context->path)
+    {
+      g_free (context->path);
+    }
 }
 
 static void
 app_menu_bridge_finalize (GObject *object)
 {
-  g_hash_table_destroy (APP_MENU_BRIDGE (object)->priv->items);
+  AppMenuBridge *bridge = APP_MENU_BRIDGE (object);
+
+  if (bridge->priv->windows)
+    {
+      g_hash_table_foreach (bridge->priv->windows,
+                            destroy_window_context,
+                            NULL);
+      g_hash_table_destroy (bridge->priv->windows);
+    }
+
+  G_OBJECT_CLASS (app_menu_bridge_parent_class)->finalize (object);
 }
 
 static void
@@ -90,6 +212,7 @@
   proxy_class->insert = app_menu_bridge_insert;
   proxy_class->show_local = app_menu_bridge_show_local;
 
+  object_class->dispose  = app_menu_bridge_dispose;
   object_class->finalize = app_menu_bridge_finalize;
 
   g_type_class_add_private (class, sizeof (AppMenuBridgePrivate));
@@ -100,6 +223,96 @@
 {
 }
 
+static void
+register_application_window (gpointer key,
+                             gpointer value,
+                             gpointer user_data)
+{
+  AppWindowContext *context     = (AppWindowContext *)value;
+  GtkWidget        *widget      = context->window;
+  AppMenuBridge    *bridge     = APP_MENU_BRIDGE (user_data);
+
+  if (!context->registered && context->server != NULL && context->root != NULL && GTK_IS_WINDOW (widget))
+    {
+      dbusmenu_server_set_root (context->server, context->root);
+      org_ayatana_WindowMenu_Registrar_register_window (bridge->priv->appmenuproxy,
+                                                        GDK_WINDOW_XID (gtk_widget_get_window (widget)),
+                                                        context->path,
+                                                        NULL);
+
+      context->registered = TRUE;
+    }
+}
+
+static void
+register_application_windows (AppMenuBridge *bridge)
+{
+  g_hash_table_foreach (bridge->priv->windows,
+                        register_application_window,
+                        bridge);
+}
+
+static void
+unregister_application_window (gpointer key,
+                               gpointer value,
+                               gpointer user_data)
+{
+  AppWindowContext *context = (AppWindowContext *)value;
+
+  context->registered = FALSE;
+}
+
+static void
+unregister_application_windows (AppMenuBridge *bridge)
+{
+  g_hash_table_foreach (bridge->priv->windows,
+                        unregister_application_window,
+                        bridge);
+}
+
+static void
+app_menu_bridge_proxy_vanished (AppMenuBridge *bridge)
+{
+  if (bridge->priv->online)
+    g_print ("VANISH\n");
+
+  bridge->priv->online = FALSE;
+
+  unregister_application_windows (bridge);
+}
+
+static void
+app_menu_bridge_proxy_appeared (AppMenuBridge *bridge)
+{
+  g_print ("APPEAR\n");
+
+  bridge->priv->online = TRUE;
+
+  app_menu_bridge_setup_proxy (bridge);
+
+  register_application_windows (bridge);
+}
+
+static void
+dbus_owner_change (DBusGProxy    *proxy,
+                   const gchar   *name,
+                   const gchar   *prev,
+                   const gchar   *new,
+                   AppMenuBridge *bridge)
+{
+  if (strlen (new) > 0 && strlen (prev) == 0)
+    {
+      if (g_strcmp0 (name, APP_MENU_DBUS_NAME) == 0)
+        {
+          app_menu_bridge_proxy_appeared (bridge);
+        }
+    }
+  else if (strlen (new) == 0 && strlen (prev) > 0)
+    {
+      app_menu_bridge_proxy_vanished (bridge);
+    }
+}
+
 static GtkWidget *
 find_menu_label (GtkWidget *widget)
 {
@@ -184,15 +397,187 @@
 }
 
 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 DbusmenuMenuitem *
+construct_dbusmenu_for_widget (GtkWidget *widget)
+{
+  DbusmenuMenuitem *mi = dbusmenu_menuitem_new ();
+
+  if (GTK_IS_MENU_ITEM (widget))
+    {
+      if (GTK_IS_SEPARATOR_MENU_ITEM (widget))
+        {
+          dbusmenu_menuitem_property_set (mi,
+                                          "type",
+                                          "separator");
+        }
+      else
+        {
+          if (GTK_IS_CHECK_MENU_ITEM (widget))
+            {
+              dbusmenu_menuitem_property_set (mi,
+                                              DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
+                                              GTK_IS_RADIO_MENU_ITEM (widget) ? DBUSMENU_MENUITEM_TOGGLE_RADIO : DBUSMENU_MENUITEM_TOGGLE_CHECK);
+
+              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);
+
+              g_signal_connect (widget,
+                                "toggle",
+                                G_CALLBACK (checkbox_toggled),
+                                mi);
+            }
+
+          dbusmenu_menuitem_property_set (mi,
+                                          "label",
+                                          get_menu_label_text (widget));
+
+          dbusmenu_menuitem_property_set_bool (mi,
+                                               DBUSMENU_MENUITEM_PROP_VISIBLE,
+                                               gtk_widget_get_visible (widget));
+
+          dbusmenu_menuitem_property_set_bool (mi,
+                                               DBUSMENU_MENUITEM_PROP_ENABLED,
+                                               gtk_widget_get_sensitive (widget));
+
+          g_signal_connect (G_OBJECT (widget),
+                            "notify",
+                            G_CALLBACK (widget_notify_cb),
+                            mi);
+
+          g_signal_connect (G_OBJECT (mi),
+                            "item_activated",
+                            G_CALLBACK (item_activated),
+                            widget);
+        }
+    }
+
+  return mi;
+}
+
+static void
+rebuild_item (GtkWidget      *widget,
+              RecurseContext *recurse)
+{
+  if (GTK_IS_CONTAINER (widget))
+    {
+      gboolean increment = GTK_IS_MENU_BAR (widget) || GTK_IS_MENU_ITEM (widget);
+
+      if (increment)
+        recurse->count++;
+
+      if (recurse->count > -1 && increment)
+        {
+          recurse->stack[recurse->count] = construct_dbusmenu_for_widget (widget);
+
+          if (recurse->count > 0)
+            {
+              if (recurse->count == 1)
+                dbusmenu_menuitem_child_append (recurse->stack[recurse->count - 1],
+                                                recurse->stack[recurse->count]);
+              else
+                dbusmenu_menuitem_child_prepend (recurse->stack[recurse->count - 1],
+                                                 recurse->stack[recurse->count]);
+            }
+        }
+
+      gtk_container_foreach (GTK_CONTAINER (widget),
+                             (GtkCallback)rebuild_item,
+                             recurse);
+
+      if (GTK_IS_MENU_ITEM (widget))
+        {
+          GtkWidget *menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
+
+          if (menu != NULL)
+            {
+              rebuild_item (menu, recurse);
+            }
+        }
+
+      if (increment)
+        recurse->count--;
+    }
+}
+
+static void
+rebuild_window_items (AppMenuBridge *bridge,
+                      GtkWidget     *toplevel)
+{
+  XID xid;
+  AppWindowContext *context;
+  RecurseContext recurse;
+
+  if (!GTK_IS_WINDOW (toplevel))
+    {
+      g_signal_connect (G_OBJECT (toplevel),
+                        "notify",
+                        G_CALLBACK (toplevel_notify_cb),
+                        bridge);
+
+      return;
+    }
+
+  if (!gtk_widget_get_realized (toplevel))
+    {
+      g_signal_connect (toplevel, "realize",
+                        G_CALLBACK (toplevel_realized),
+                        bridge);
+
+      return;
+    }
+
+  xid = GDK_WINDOW_XID (gtk_widget_get_window (toplevel));
+  context = g_hash_table_lookup (bridge->priv->windows, &xid);
+
+  if (!context)
+    {
+      context = g_new0 (AppWindowContext, 1);
+      g_hash_table_insert (bridge->priv->windows, &xid, context);
+    }
+
+  context->window = toplevel;
+
+  if (!context->path)
+    context->path = g_strdup_printf ("/org/ayatana/menu/%X", (guint)xid);
+
+  if (!context->server)
+    context->server = dbusmenu_server_new (context->path);
+
+  recurse.bridge  = bridge;
+  recurse.context = context;
+  recurse.count   = -1;
+
+  gtk_container_foreach (GTK_CONTAINER (toplevel),
+                         (GtkCallback)rebuild_item,
+                         &recurse);
+
+  context->root = recurse.stack[0];
+
+  dbusmenu_server_set_root (context->server, context->root);
+}
+
+
+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;
+  AppMenuBridge *bridge = APP_MENU_BRIDGE (user_data);
+
+  if (GTK_IS_WINDOW (widget))
+    {
+      rebuild_window_items (bridge, widget);
+      register_application_windows (bridge);
+
+      return;
+    }
 }
 
 static void
@@ -202,58 +587,26 @@
 {
   if (pspec->name == g_intern_static_string ("parent"))
     {
+      GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
       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);
-              }
-            }
-        }
+
+      rebuild_window_items (bridge, toplevel);
     }
 }
 
 static void
-checkbox_toggled (GtkWidget *widget, DbusmenuMenuitem *mi)
+attach_notify_cb (GtkWidget     *widget,
+                  GParamSpec    *pspec,
+                  AppMenuBridge *bridge)
 {
-  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);
+  if (pspec->name == g_intern_static_string ("attach-widget"))
+    {
+      GtkWidget *attach = NULL;
+
+      g_object_get (widget, "attach-widget", &attach, NULL);
+
+      rebuild_window_items (bridge, attach);
+    }
 }
 
 static void
@@ -264,8 +617,7 @@
 {
   AppMenuBridge        *bridge;
   AppMenuBridgePrivate *priv;
-  DbusmenuMenuitem     *item;
-  DbusmenuMenuitem     *parent_item = NULL;
+  GtkWidget            *toplevel = NULL;
   gboolean              append = FALSE;
 
   if (GTK_IS_TEAROFF_MENU_ITEM (child))
@@ -274,24 +626,16 @@
   bridge = APP_MENU_BRIDGE (proxy);
   priv = bridge->priv;
 
-  if (g_hash_table_lookup (bridge->priv->items, child) != NULL)
-    return;
+  /*
+  g_print ("INSERT (parent %s %p, child %s %p\n",
+           G_OBJECT_TYPE_NAME (parent), parent,
+           G_OBJECT_TYPE_NAME (child), child);
+  */
+
+  toplevel = gtk_widget_get_toplevel (parent);
 
   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),
@@ -306,80 +650,20 @@
       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?
-            }
+        {
+          g_signal_connect (G_OBJECT (parent),
+                            "notify",
+                            G_CALLBACK (attach_notify_cb),
+                            bridge);
+          return;
         }
     }
 
-  if (GTK_IS_MENU_ITEM (child))
+  if (GTK_IS_WINDOW (toplevel))
     {
-      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);
-            }
-        }
+      g_signal_connect (toplevel, "realize",
+                        G_CALLBACK (toplevel_realized),
+                        bridge);
     }
 }
 
@@ -398,20 +682,6 @@
 
   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;


Follow ups