~charlesk/appmenu-gtk/lp-788640

« back to all changes in this revision

Viewing changes to src/bridge.c

  • Committer: Ken VanDine
  • Date: 2010-06-11 16:22:18 UTC
  • Revision ID: ken.vandine@canonical.com-20100611162218-xjcop79gp5kym1u2
Include Makefile.am, configure.ac and autogen.sh in EXTRA_DIST so they don't show up in the packaging diff

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
#include <stdio.h>
23
23
#include <stdlib.h>
24
 
#include <string.h>
 
24
 
 
25
#include <dbus/dbus.h>
 
26
#include <dbus/dbus-glib.h>
 
27
#include <dbus/dbus-glib-lowlevel.h>
 
28
#include <dbus/dbus-glib-bindings.h>
25
29
 
26
30
#include <gtk/gtk.h>
27
31
#include <gdk/gdkx.h>
28
 
#include <gio/gio.h>
29
 
 
30
 
#include <libdbusmenu-gtk/menuitem.h>
31
 
#include <libdbusmenu-gtk/parser.h>
32
32
 
33
33
#include <libdbusmenu-glib/menuitem.h>
34
34
#include <libdbusmenu-glib/server.h>
35
35
 
36
36
#include "bridge.h"
37
 
#include "gen-application-menu-registrar.xml.h"
 
37
#include "application-menu-registrar-client.h"
38
38
 
39
 
#define APP_MENU_DBUS_NAME   "com.canonical.AppMenu.Registrar"
40
 
#define APP_MENU_DBUS_OBJECT "/com/canonical/AppMenu/Registrar"
41
 
#define APP_MENU_INTERFACE   "com.canonical.AppMenu.Registrar"
 
39
#define APP_MENU_DBUS_NAME   "org.ayatana.WindowMenu.Registrar"
 
40
#define APP_MENU_DBUS_OBJECT "/org/ayatana/WindowMenu/Registrar"
 
41
#define APP_MENU_INTERFACE   "org.ayatana.WindowMenu.Registrar"
42
42
#define APP_MENU_PATH        "/this/is/a/long/object/path"
43
43
 
44
 
typedef struct _AppWindowContext AppWindowContext;
45
 
 
46
44
static void app_menu_bridge_insert         (UbuntuMenuProxy   *proxy,
47
45
                                            GtkWidget         *shell,
48
46
                                            GtkWidget         *child,
49
47
                                            guint              position);
50
48
static gboolean app_menu_bridge_show_local (UbuntuMenuProxy   *proxy);
51
 
static void     toplevel_mapped            (GtkWidget         *widget,
52
 
                                            gpointer           user_data);
53
 
static void     mnemonic_shown_cb          (GtkWidget         *widget,
54
 
                                            GParamSpec        *pspec,
55
 
                                            AppWindowContext  *context);
56
 
static void     toplevel_notify_parent_cb  (GtkWidget         *widget,
57
 
                                            GParamSpec        *pspec,
58
 
                                            UbuntuMenuProxy   *proxy);
59
 
static void     menubar_notify_parent_cb   (GtkWidget         *widget,
60
 
                                            GParamSpec        *pspec,
61
 
                                            UbuntuMenuProxy   *proxy);
62
 
static void     rebuild_window_items       (AppMenuBridge     *bridge,
63
 
                                            GtkWidget         *toplevel);
64
 
static void     rebuild                    (AppMenuBridge     *bridge,
65
 
                                            GtkWidget         *toplevel);
66
 
static void app_menu_bridge_proxy_vanished (AppMenuBridge *bridge);
67
 
static void app_menu_bridge_proxy_appeared (AppMenuBridge *bridge);
68
 
static void appmenuproxy_created_cb        (GObject *          object,
69
 
                                            GAsyncResult *     res,
70
 
                                            gpointer           user_data);
71
49
 
72
 
struct _AppWindowContext
 
50
struct _AppMenuBridgePrivate
73
51
{
74
 
  GtkWidget        *window;
 
52
  GHashTable *items;      /* <GtkWidget *, DbusmenuMenuitem *> */
 
53
 
75
54
  DbusmenuServer   *server;
76
 
  gchar            *path;
77
 
  gboolean          registered;
78
 
  AppMenuBridge    *bridge;
79
 
  GCancellable     *cancel;
80
 
};
81
 
 
82
 
struct _AppMenuBridgePrivate
83
 
{
84
 
  GList *windows;
85
 
 
86
 
  GDBusProxy       *appmenuproxy;
87
 
  gboolean          online;
88
 
};
89
 
 
90
 
typedef struct _RecurseContext
91
 
{
92
 
  AppMenuBridge    *bridge;
93
 
  AppWindowContext *context;
94
 
 
95
 
  gint count;
96
 
  gboolean previous;
97
 
  DbusmenuMenuitem *stack[30];
98
 
} RecurseContext;
 
55
};
 
56
 
 
57
static DBusGProxy *dbusproxy = NULL;
 
58
static gboolean    registered = FALSE;
99
59
 
100
60
G_DEFINE_DYNAMIC_TYPE(AppMenuBridge, app_menu_bridge, UBUNTU_TYPE_MENU_PROXY)
101
61
 
102
 
static GHashTable *rebuild_ids = NULL;
103
 
static GDBusNodeInfo *      registrar_node_info = NULL;
104
 
static GDBusInterfaceInfo * registrar_interface_info = NULL;
105
 
 
106
 
static void
107
 
activate_menu (AppMenuBridge *bridge,
108
 
               GtkWidget     *widget,
109
 
               gpointer       user_data)
110
 
{
111
 
  DbusmenuMenuitem *mi = dbusmenu_gtk_parse_get_cached_item (widget);
112
 
 
113
 
  if (mi != NULL)
114
 
    {
115
 
      dbusmenu_menuitem_show_to_user (mi, 0);
116
 
    }
117
 
 
118
 
  return;
119
 
}
120
 
 
121
62
static void
122
63
app_menu_bridge_init (AppMenuBridge *bridge)
123
64
{
124
65
  bridge->priv = G_TYPE_INSTANCE_GET_PRIVATE (bridge, APP_MENU_TYPE_BRIDGE, AppMenuBridgePrivate);
125
66
 
126
 
  bridge->priv->windows = NULL;
127
 
 
128
 
  bridge->priv->appmenuproxy = NULL;
129
 
  g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
130
 
                           G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
131
 
                           registrar_interface_info,
132
 
                           APP_MENU_DBUS_NAME,
133
 
                           APP_MENU_DBUS_OBJECT,
134
 
                           APP_MENU_INTERFACE,
135
 
                           NULL, /* TODO: cancelable */
136
 
                           appmenuproxy_created_cb,
137
 
                           bridge);
138
 
 
139
 
  bridge->priv->online = FALSE;
140
 
 
141
 
  g_signal_connect (bridge,
142
 
                    "activate-menu",
143
 
                    G_CALLBACK (activate_menu),
144
 
                    NULL);
145
 
 
146
 
  return;
147
 
}
148
 
 
149
 
static void
150
 
context_dispose (AppWindowContext *context)
151
 
{
152
 
  if (context->cancel != NULL)
153
 
    {
154
 
      g_cancellable_cancel (context->cancel);
155
 
      g_object_unref (context->cancel);
156
 
      context->cancel = NULL;
157
 
    }
158
 
 
159
 
  if (context->server != NULL)
160
 
    {
161
 
      g_object_unref (context->server);
162
 
      context->server = NULL;
163
 
    }
164
 
 
165
 
  if (context->window != NULL)
166
 
    {
167
 
      g_signal_handlers_disconnect_by_func(context->window, G_CALLBACK(mnemonic_shown_cb), context);
168
 
      g_object_remove_weak_pointer(G_OBJECT(context->window), (gpointer *)&(context->window));
169
 
      context->window = NULL;
170
 
    }
171
 
}
172
 
 
173
 
static void
174
 
context_free (AppWindowContext *context)
175
 
{
176
 
  context_dispose (context);
177
 
 
178
 
  if (context->path != NULL)
179
 
    {
180
 
      g_free (context->path);
181
 
      context->path = NULL;
182
 
    }
183
 
 
184
 
  g_free (context);
185
 
}
186
 
 
187
 
static void
188
 
app_menu_bridge_dispose (GObject *object)
189
 
{
190
 
  AppMenuBridge *bridge = APP_MENU_BRIDGE (object);
191
 
 
192
 
  g_list_foreach (bridge->priv->windows, (GFunc)context_dispose, NULL);
193
 
 
194
 
  if (bridge->priv->appmenuproxy)
195
 
    {
196
 
      g_object_unref (bridge->priv->appmenuproxy);
197
 
      bridge->priv->appmenuproxy = NULL;
198
 
    }
 
67
  bridge->priv->items = g_hash_table_new (g_direct_hash, g_direct_equal);
 
68
  bridge->priv->items = g_hash_table_new (g_direct_hash, g_direct_equal);
 
69
 
 
70
  bridge->priv->server = dbusmenu_server_new (APP_MENU_PATH);
199
71
}
200
72
 
201
73
static void
202
74
app_menu_bridge_finalize (GObject *object)
203
75
{
204
 
  AppMenuBridge *bridge = APP_MENU_BRIDGE (object);
205
 
 
206
 
  g_list_foreach (bridge->priv->windows, (GFunc)context_free, NULL);
207
 
  g_list_free (bridge->priv->windows);
208
 
  bridge->priv->windows = NULL;
209
 
 
210
 
  G_OBJECT_CLASS (app_menu_bridge_parent_class)->finalize (object);
211
 
}
212
 
 
213
 
static void
214
 
toplevel_unmapped (GtkWidget *widget,
215
 
                   gpointer   user_data)
216
 
{
217
 
  AppWindowContext *context = (AppWindowContext *)user_data;
218
 
 
219
 
  g_signal_handlers_disconnect_by_func(widget,
220
 
                                       G_CALLBACK(toplevel_unmapped),
221
 
                                       user_data);
222
 
 
223
 
  if (context)
224
 
    {
225
 
      context->bridge->priv->windows = g_list_remove (context->bridge->priv->windows, context);
226
 
      context_free (context);
227
 
    }
 
76
  g_hash_table_destroy (APP_MENU_BRIDGE (object)->priv->items);
228
77
}
229
78
 
230
79
static void
241
90
  proxy_class->insert = app_menu_bridge_insert;
242
91
  proxy_class->show_local = app_menu_bridge_show_local;
243
92
 
244
 
  object_class->dispose  = app_menu_bridge_dispose;
245
93
  object_class->finalize = app_menu_bridge_finalize;
246
94
 
247
95
  g_type_class_add_private (class, sizeof (AppMenuBridgePrivate));
248
 
 
249
 
        if (registrar_node_info == NULL) {
250
 
                GError * error = NULL;
251
 
 
252
 
                registrar_node_info = g_dbus_node_info_new_for_xml(_application_menu_registrar, &error);
253
 
                if (error != NULL) {
254
 
                        g_error("Unable to parse app menu registrar Interface description: %s", error->message);
255
 
                        g_error_free(error);
256
 
                }
257
 
        }
258
 
 
259
 
        if (registrar_interface_info == NULL) {
260
 
                registrar_interface_info = g_dbus_node_info_lookup_interface(registrar_node_info, APP_MENU_INTERFACE);
261
 
 
262
 
                if (registrar_interface_info == NULL) {
263
 
                        g_error("Unable to find interface '" APP_MENU_INTERFACE "'");
264
 
                }
265
 
        }
266
 
 
267
 
        return;
268
96
}
269
97
 
270
98
static void
272
100
{
273
101
}
274
102
 
275
 
static void
276
 
app_menu_bridge_set_show_local (AppMenuBridge *bridge,
277
 
                                gboolean       local)
278
 
{
279
 
  const gchar *env = g_getenv ("APPMENU_DISPLAY_BOTH");
280
 
 
281
 
  if (g_strcmp0 (env, "1") == 0)
282
 
    local = TRUE;
283
 
 
284
 
  g_object_set (bridge,
285
 
                "show-local", local,
286
 
                NULL);
287
 
}
288
 
 
289
 
static void
290
 
register_application_window_cb (GObject          *object,
291
 
                                GAsyncResult     *res,
292
 
                                void             *user_data)
293
 
{
294
 
  GError * error = NULL;
295
 
  AppWindowContext *context = (AppWindowContext *)user_data;
296
 
 
297
 
  GVariant * variants = g_dbus_proxy_call_finish(G_DBUS_PROXY(object), res, &error);
298
 
 
299
 
  if (variants != NULL) {
300
 
    /* Only unref variants if we get some.  Doing this hear instead of
301
 
       with the error so that it's clear we don't use it. */
302
 
    g_variant_unref(variants);
303
 
  }
304
 
 
305
 
  if (error != NULL &&
306
 
      error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)
307
 
    {
308
 
      /* If we were cancelled, we've been disposed (and possibly finalized) and
309
 
         shouldn't trust anything in context.  This is because cancel callbacks
310
 
         are done in the idle loop for GIO functions. */
311
 
      g_error_free(error);
312
 
      return;
313
 
    }
314
 
 
315
 
  if (context->cancel != NULL)
316
 
    {
317
 
      g_object_unref (context->cancel);
318
 
      context->cancel = NULL;
319
 
    }
320
 
 
321
 
  if (error != NULL)
322
 
    {
323
 
      g_warning("Unable to register window with path '%s': %s", context->path, error->message);
324
 
      g_error_free(error);
325
 
 
326
 
      context->registered = FALSE;
327
 
 
328
 
      if (context->bridge != NULL)
329
 
          app_menu_bridge_set_show_local (context->bridge, TRUE);
330
 
 
331
 
      return;
332
 
    }
333
 
 
334
 
  context->registered = TRUE;
335
 
 
336
 
  if (context->bridge != NULL)
337
 
    app_menu_bridge_set_show_local (context->bridge, FALSE);
338
 
}
339
 
 
340
 
static void
341
 
register_application_windows (AppMenuBridge *bridge)
342
 
{
343
 
  GList *tmp = NULL;
344
 
 
345
 
  for (tmp = bridge->priv->windows; tmp != NULL; tmp = tmp->next)
346
 
    {
347
 
      AppWindowContext *context = tmp->data;
348
 
      GtkWidget *widget = context->window;
349
 
 
350
 
      if (bridge->priv->appmenuproxy == NULL || bridge->priv->online == FALSE)
351
 
        {
352
 
          if (context->bridge != NULL)
353
 
            {
354
 
              app_menu_bridge_set_show_local (context->bridge, TRUE);
355
 
            }
356
 
          continue;
357
 
        }
358
 
 
359
 
      if (!context->registered && context->server != NULL &&
360
 
          context->cancel == NULL && GTK_IS_WINDOW (widget) &&
361
 
          bridge->priv->appmenuproxy != NULL)
362
 
        {
363
 
          context->cancel = g_cancellable_new ();
364
 
          g_dbus_proxy_call(bridge->priv->appmenuproxy,
365
 
                            "RegisterWindow",
366
 
                            g_variant_new("(uo)",
367
 
                                          GDK_WINDOW_XID(gtk_widget_get_window(widget)),
368
 
                                          context->path),
369
 
                            G_DBUS_CALL_FLAGS_NONE,
370
 
                            -1, /* timeout */
371
 
                            context->cancel,
372
 
                            register_application_window_cb,
373
 
                            context);
374
 
        }
375
 
    }
376
 
}
377
 
 
378
 
static void
379
 
unregister_application_windows (AppMenuBridge *bridge)
380
 
{
381
 
  GList *tmp = NULL;
382
 
 
383
 
  for (tmp = bridge->priv->windows; tmp != NULL; tmp = tmp->next)
384
 
    {
385
 
      AppWindowContext *context = tmp->data;
386
 
 
387
 
      context->registered = FALSE;
388
 
    }
389
 
 
390
 
  app_menu_bridge_set_show_local (bridge, TRUE);
391
 
}
392
 
 
393
 
static void
394
 
app_menu_bridge_proxy_vanished (AppMenuBridge *bridge)
395
 
{
396
 
  bridge->priv->online = FALSE;
397
 
 
398
 
  unregister_application_windows (bridge);
399
 
}
400
 
 
401
 
static void
402
 
app_menu_bridge_proxy_appeared (AppMenuBridge *bridge)
403
 
{
404
 
  bridge->priv->online = TRUE;
405
 
 
406
 
  register_application_windows (bridge);
407
 
}
408
 
 
409
 
/* Gets called anytime the name owner changes, this is typically
410
 
   when the indicator crashes or gets removed. */
411
 
static void
412
 
appmenuproxy_owner_changed (GObject * object, GParamSpec * pspec, gpointer user_data)
413
 
{
414
 
        AppMenuBridge * bridge = APP_MENU_BRIDGE(user_data);
415
 
        g_return_if_fail(bridge != NULL);
416
 
 
417
 
        gchar * name = g_dbus_proxy_get_name_owner(bridge->priv->appmenuproxy);
418
 
        if (name != NULL) {
419
 
                g_free(name);
420
 
                app_menu_bridge_proxy_appeared(bridge);
421
 
        } else {
422
 
                app_menu_bridge_proxy_vanished(bridge);
423
 
        }
424
 
 
425
 
        return;
426
 
}
427
 
 
428
 
/* Callback for the asyncronous creation of the proxy object.
429
 
   If it is created successfully we act like it just appeared, otherwise
430
 
   we error as if it vanished */
431
 
static void
432
 
appmenuproxy_created_cb (GObject * object, GAsyncResult * res, gpointer user_data)
433
 
{
434
 
        GError * error = NULL;
435
 
        GDBusProxy * proxy = NULL;
436
 
 
437
 
        proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
438
 
        if (error != NULL) {
439
 
                g_warning("Unable to create Ubuntu Menu Proxy: %s", error->message);
440
 
                g_error_free(error);
441
 
                /* No return, we want to still call vanished */
442
 
        }
443
 
 
444
 
        AppMenuBridge * bridge = APP_MENU_BRIDGE(user_data);
445
 
        g_return_if_fail(bridge != NULL);
446
 
 
447
 
        bridge->priv->appmenuproxy = proxy;
448
 
 
449
 
        gchar * name = NULL;
450
 
        if (proxy != NULL) {
451
 
                name = g_dbus_proxy_get_name_owner(proxy);
452
 
 
453
 
                g_signal_connect(G_OBJECT(proxy),
454
 
                                 "notify::g-name-owner",
455
 
                                 G_CALLBACK(appmenuproxy_owner_changed),
456
 
                                 bridge);
457
 
        }
458
 
 
459
 
        /* Note: name will be NULL if proxy was NULL */
460
 
        if (name != NULL) {
461
 
                g_free(name);
462
 
                app_menu_bridge_proxy_appeared(bridge);
463
 
        } else {
464
 
                app_menu_bridge_proxy_vanished(bridge);
465
 
        }
466
 
 
467
 
        return;
468
 
}
469
 
 
470
 
typedef struct _RebuildData {
471
 
  AppMenuBridge *bridge;
472
 
  GtkWidget     *widget;
473
 
} RebuildData;
474
 
 
475
 
static gboolean
476
 
do_rebuild (RebuildData *data)
477
 
{
478
 
  if (data->widget != NULL && gtk_widget_is_toplevel (data->widget))
479
 
    {
480
 
      rebuild_window_items (data->bridge,
481
 
                            data->widget);
482
 
    }
483
 
 
484
 
  if (data->widget != NULL)
485
 
    {
486
 
      g_object_remove_weak_pointer (G_OBJECT (data->widget), (gpointer*)&data->widget);
487
 
      g_hash_table_remove (rebuild_ids, data->widget);
488
 
    }
489
 
 
490
 
  g_free (data);
491
 
 
492
 
  return FALSE;
493
 
}
494
 
 
495
 
static void
496
 
rebuild (AppMenuBridge *bridge,
497
 
         GtkWidget     *toplevel)
498
 
{
499
 
  guint id = 0;
500
 
 
501
 
  if (rebuild_ids != NULL)
502
 
    {
503
 
      id = GPOINTER_TO_UINT (g_hash_table_lookup (rebuild_ids, toplevel));
504
 
 
505
 
      if (id > 0)
506
 
        {
507
 
          g_source_remove (id);
508
 
          g_hash_table_remove (rebuild_ids, toplevel);
509
 
          id = 0;
510
 
        }
511
 
    }
512
 
 
513
 
  RebuildData *data = g_new0 (RebuildData, 1);
514
 
  data->bridge = bridge;
515
 
  data->widget = toplevel;
516
 
 
517
 
  id = g_timeout_add (100,
518
 
                      (GSourceFunc)do_rebuild,
519
 
                      data);
520
 
 
521
 
  g_object_add_weak_pointer (G_OBJECT (data->widget), (gpointer*)&data->widget);
522
 
 
523
 
  if (rebuild_ids == NULL)
524
 
    {
525
 
      rebuild_ids = g_hash_table_new (g_direct_hash, g_direct_equal);
526
 
    }
527
 
 
528
 
  g_hash_table_insert (rebuild_ids, toplevel, GUINT_TO_POINTER (id));
529
 
}
530
 
 
531
 
static DbusmenuMenuitem * find_menu_bar (GtkWidget * widget);
532
 
 
533
 
static void
534
 
find_menu_bar_helper (GtkWidget * widget, gpointer data)
535
 
{
536
 
        DbusmenuMenuitem ** mi = (DbusmenuMenuitem **)data;
537
 
 
538
 
        /* We've already found a menu, let's get through the
539
 
           foreach as quickly as possible */
540
 
        if (*mi != NULL) {
541
 
                return;
542
 
        }
543
 
 
544
 
        *mi = find_menu_bar(widget);
545
 
        return;
546
 
}
547
 
 
548
 
static DbusmenuMenuitem *
549
 
find_menu_bar (GtkWidget * widget)
550
 
{
551
 
        if (GTK_IS_MENU_BAR(widget) || GTK_IS_MENU_ITEM(widget)) {
552
 
                return dbusmenu_gtk_parse_menu_structure(widget);
553
 
        }
554
 
 
555
 
        if (GTK_IS_CONTAINER(widget)) {
556
 
                DbusmenuMenuitem * mi = NULL;
557
 
 
558
 
                gtk_container_foreach(GTK_CONTAINER(widget), find_menu_bar_helper, &mi);
559
 
 
560
 
                return mi;
561
 
        }
562
 
 
563
 
        return NULL;
564
 
}
565
 
 
566
 
/* Respond to changing of the mnemonics shown property to say
567
 
   to the appmenu wether we need to have the menus shown on the
568
 
   panel.  If there is no auto-mnemonics in this theme we're just
569
 
   not doing this as we can't tell what the user wanted. */
570
 
static void
571
 
mnemonic_shown_cb (GtkWidget       *widget,
 
103
static GtkWidget *
 
104
find_menu_label (GtkWidget *widget)
 
105
{
 
106
  GtkWidget *label = NULL;
 
107
 
 
108
  if (GTK_IS_LABEL (widget))
 
109
    return widget;
 
110
 
 
111
  if (GTK_IS_CONTAINER (widget))
 
112
    {
 
113
      GList *children;
 
114
      GList *l;
 
115
 
 
116
      children = gtk_container_get_children (GTK_CONTAINER (widget));
 
117
 
 
118
      for (l = children; l; l = l->next)
 
119
        {
 
120
          label = find_menu_label (l->data);
 
121
 
 
122
          if (label)
 
123
            break;
 
124
        }
 
125
 
 
126
      g_list_free (children);
 
127
    }
 
128
 
 
129
  return label;
 
130
}
 
131
 
 
132
static const gchar *
 
133
get_menu_label_text (GtkWidget *menuitem)
 
134
{
 
135
  GtkWidget *label = find_menu_label (menuitem);
 
136
 
 
137
  if (label)
 
138
    return gtk_label_get_text (GTK_LABEL (label));
 
139
 
 
140
  return NULL;
 
141
}
 
142
 
 
143
static void
 
144
item_activated (DbusmenuMenuitem *item, guint timestamp, gpointer user_data)
 
145
{
 
146
  GtkWidget *child;
 
147
 
 
148
  if (user_data != NULL)
 
149
    {
 
150
      child = (GtkWidget *)user_data;
 
151
 
 
152
      if (GTK_IS_MENU_ITEM (child))
 
153
        {
 
154
          gtk_menu_item_activate (GTK_MENU_ITEM (child));
 
155
        }
 
156
    }
 
157
}
 
158
 
 
159
static void
 
160
widget_notify_cb (GtkWidget  *widget,
 
161
                  GParamSpec *pspec,
 
162
                  gpointer    data)
 
163
{
 
164
  DbusmenuMenuitem *child = (DbusmenuMenuitem *)data;
 
165
 
 
166
  if (pspec->name == g_intern_static_string ("sensitive"))
 
167
    {
 
168
      dbusmenu_menuitem_property_set_bool (child,
 
169
                                           DBUSMENU_MENUITEM_PROP_ENABLED,
 
170
                                           gtk_widget_get_sensitive (widget));
 
171
    }
 
172
  else if (pspec->name == g_intern_static_string ("label"))
 
173
    {
 
174
      dbusmenu_menuitem_property_set (child,
 
175
                                      DBUSMENU_MENUITEM_PROP_LABEL,
 
176
                                      gtk_menu_item_get_label (GTK_MENU_ITEM (widget)));
 
177
    }
 
178
  else if (pspec->name == g_intern_static_string ("visible"))
 
179
    {
 
180
      dbusmenu_menuitem_property_set_bool (child,
 
181
                                           DBUSMENU_MENUITEM_PROP_VISIBLE,
 
182
                                           gtk_widget_get_visible (widget));
 
183
    }
 
184
}
 
185
 
 
186
static void
 
187
toplevel_realized (GtkWidget *widget,
 
188
                   gpointer   user_data)
 
189
{
 
190
  /* Register the toplevel window now that it's been realized. */
 
191
  org_ayatana_WindowMenu_Registrar_register_window (dbusproxy,
 
192
                                                    GDK_WINDOW_XID (gtk_widget_get_window (widget)),
 
193
                                                    APP_MENU_PATH,
 
194
                                                    NULL);
 
195
  registered = TRUE;
 
196
}
 
197
 
 
198
static void
 
199
toplevel_notify_cb (GtkWidget       *widget,
572
200
                    GParamSpec      *pspec,
573
 
                    AppWindowContext *context)
574
 
{
575
 
        DbusmenuStatus dstatus = DBUSMENU_STATUS_NORMAL;
576
 
        if (context->window != NULL) {
577
 
                gboolean mshown = gtk_window_get_mnemonics_visible(GTK_WINDOW(context->window));
578
 
                gboolean autom;
579
 
 
580
 
                g_object_get(gtk_widget_get_settings(context->window), "gtk-auto-mnemonics", &autom, NULL);
581
 
 
582
 
                if (autom && mshown) {
583
 
                        dstatus = DBUSMENU_STATUS_NOTICE;
584
 
                }
585
 
        }
586
 
 
587
 
        if (context->server != NULL) {
588
 
                /* g_debug("Setting dbusmenu server status to: %d", dstatus); */
589
 
                dbusmenu_server_set_status(context->server, dstatus);
590
 
        }
591
 
 
592
 
        return;
593
 
}
594
 
 
595
 
static void
596
 
rebuild_window_items (AppMenuBridge *bridge,
597
 
                      GtkWidget     *toplevel)
598
 
{
599
 
  XID xid;
600
 
  AppWindowContext *context = NULL;
601
 
 
602
 
  /* Disconnect any "map" signal and reconnect, which guarantees that there is
603
 
     at least one and at most one listener */
604
 
  g_signal_handlers_disconnect_by_func(toplevel,
605
 
                                       G_CALLBACK(toplevel_mapped),
606
 
                                       bridge);
607
 
  g_signal_connect (toplevel, "map",
608
 
                    G_CALLBACK (toplevel_mapped),
609
 
                    bridge);
610
 
 
611
 
  if (!GTK_IS_WINDOW (toplevel))
612
 
    {
613
 
      g_signal_connect (G_OBJECT (toplevel),
614
 
                        "notify::parent",
615
 
                        G_CALLBACK (toplevel_notify_parent_cb),
616
 
                        bridge);
617
 
 
618
 
      return;
619
 
    }
620
 
  else if (g_object_class_find_property (G_OBJECT_GET_CLASS (toplevel),
621
 
                                         "ubuntu-no-proxy") != NULL)
622
 
 
623
 
    {
624
 
      gboolean no_proxy;
625
 
 
626
 
      g_object_get (G_OBJECT (toplevel),
627
 
                    "ubuntu-no-proxy", &no_proxy,
628
 
                    NULL);
629
 
 
630
 
      if (no_proxy)
631
 
        {
632
 
          return;
633
 
        }
634
 
    }
635
 
 
636
 
  if (!gtk_widget_get_mapped (toplevel))
637
 
    {
638
 
      return;
639
 
    }
640
 
 
641
 
  xid = GDK_WINDOW_XID (gtk_widget_get_window (toplevel));
642
 
 
643
 
  GList *tmp;
644
 
  gboolean found = FALSE;
645
 
 
646
 
  for (tmp = bridge->priv->windows; tmp != NULL; tmp = tmp->next)
647
 
    {
648
 
      context = tmp->data;
649
 
 
650
 
      if (context && GTK_IS_WIDGET (context->window))
651
 
        {
652
 
          XID xid2 = GDK_WINDOW_XID (gtk_widget_get_window (context->window));
653
 
 
654
 
          if (xid == xid2)
655
 
            {
656
 
              found = TRUE;
657
 
              break;
658
 
            }
659
 
        }
660
 
    }
661
 
 
662
 
  if (!found)
663
 
    {
664
 
      context = g_new0 (AppWindowContext, 1);
665
 
      context->bridge = bridge;
666
 
      context->cancel = NULL;
667
 
      bridge->priv->windows = g_list_prepend (bridge->priv->windows, context);
668
 
    }
669
 
 
670
 
  if (context->window)
671
 
    {
672
 
      if (context->window != toplevel)
673
 
        {
674
 
          g_signal_handlers_disconnect_by_func (context->window,
675
 
                                                G_CALLBACK (toplevel_unmapped),
676
 
                                                context);
677
 
        }
678
 
    }
679
 
 
680
 
  if (context->window != toplevel)
681
 
    {
682
 
      if (context->window != NULL) {
683
 
        g_object_remove_weak_pointer(G_OBJECT(context->window), (gpointer *)&(context->window));
684
 
        g_signal_handlers_disconnect_by_func(context->window, G_CALLBACK(mnemonic_shown_cb), context);
685
 
      }
686
 
 
687
 
      context->window = toplevel;
688
 
 
689
 
      g_object_add_weak_pointer(G_OBJECT(context->window), (gpointer *)&(context->window));
690
 
      g_signal_connect (toplevel,
691
 
                        "unmap",
692
 
                        G_CALLBACK (toplevel_unmapped),
693
 
                        context);
694
 
    }
695
 
 
696
 
  if (!context->path)
697
 
    context->path = g_strdup_printf ("/com/canonical/menu/%X", (guint)xid);
698
 
 
699
 
  if (!context->server) {
700
 
    context->server = dbusmenu_server_new (context->path);
701
 
 
702
 
    GtkTextDirection dir = gtk_widget_get_default_direction();
703
 
    if (dir != GTK_TEXT_DIR_NONE) {
704
 
      dbusmenu_server_set_text_direction(context->server, dir == GTK_TEXT_DIR_LTR ? DBUSMENU_TEXT_DIRECTION_LTR : DBUSMENU_TEXT_DIRECTION_RTL);
705
 
    }
706
 
  }
707
 
 
708
 
  if (context->window != NULL) {
709
 
    g_signal_connect (G_OBJECT (toplevel),
710
 
                      "notify::mnemonics-visible",
711
 
                      G_CALLBACK (mnemonic_shown_cb),
712
 
                      context);
713
 
  }
714
 
 
715
 
  DbusmenuMenuitem * mi = find_menu_bar(toplevel);
716
 
  dbusmenu_server_set_root(context->server, mi);
717
 
  if (mi != NULL) {
718
 
    g_object_unref(G_OBJECT(mi));
719
 
  }
720
 
 
721
 
  register_application_windows (bridge);
722
 
}
723
 
 
724
 
static void
725
 
toplevel_mapped (GtkWidget *widget,
726
 
                 gpointer   user_data)
727
 
{
728
 
  AppMenuBridge *bridge = APP_MENU_BRIDGE (user_data);
729
 
 
730
 
  if (GTK_IS_WINDOW (widget))
731
 
    {
732
 
      rebuild (bridge, widget);
733
 
      //register_application_windows (bridge);
734
 
 
735
 
      return;
736
 
    }
737
 
}
738
 
 
739
 
static void
740
 
toplevel_notify_parent_cb (GtkWidget       *widget,
741
 
                           GParamSpec      *pspec,
742
 
                           UbuntuMenuProxy *proxy)
743
 
{
744
 
  GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
745
 
  AppMenuBridge *bridge = APP_MENU_BRIDGE (proxy);
746
 
 
747
 
  if (gtk_widget_get_parent (widget) == NULL)
748
 
    return;
749
 
 
750
 
  if (GTK_IS_WINDOW (toplevel))
751
 
    {
752
 
      g_signal_handlers_disconnect_by_func (widget,
753
 
                                            G_CALLBACK (toplevel_notify_parent_cb),
754
 
                                            proxy);
755
 
      rebuild (bridge, toplevel);
756
 
    }
757
 
  else
758
 
    {
759
 
      g_signal_connect (G_OBJECT (toplevel),
760
 
                        "notify::parent",
761
 
                        G_CALLBACK (toplevel_notify_parent_cb),
762
 
                        proxy);
763
 
    }
764
 
}
765
 
 
766
 
static void
767
 
menubar_notify_parent_cb (GtkWidget       *widget,
768
 
                          GParamSpec      *pspec,
769
 
                          UbuntuMenuProxy *proxy)
770
 
{
771
 
  if (gtk_widget_get_parent (widget) == NULL)
772
 
    return; /* TODO: Should clear all entries (find old context and set root to NULL) */
773
 
 
774
 
  /* Rebuild now or when toplevel window appears */
775
 
  toplevel_notify_parent_cb (widget, NULL, proxy);
776
 
}
777
 
 
778
 
static void
779
 
attach_notify_cb (GtkWidget     *widget,
780
 
                  GParamSpec    *pspec,
781
 
                  AppMenuBridge *bridge)
782
 
{
783
 
  if (pspec->name == g_intern_static_string ("attach-widget"))
784
 
    {
785
 
      GtkWidget *attach = NULL;
786
 
 
787
 
      g_object_get (widget, "attach-widget", &attach, NULL);
788
 
 
789
 
      rebuild (bridge, attach);
790
 
    }
 
201
                    UbuntuMenuProxy *proxy)
 
202
{
 
203
  if (pspec->name == g_intern_static_string ("parent"))
 
204
    {
 
205
      AppMenuBridge *bridge = APP_MENU_BRIDGE (proxy);
 
206
      DbusmenuMenuitem *root = g_hash_table_lookup (bridge->priv->items, widget);
 
207
 
 
208
      if (root)
 
209
        {
 
210
          dbusmenu_server_set_root (bridge->priv->server, root);
 
211
        }
 
212
 
 
213
      if (!registered)
 
214
        {
 
215
          GtkWidget *parent = gtk_widget_get_toplevel (widget);
 
216
 
 
217
          if (!GTK_IS_WINDOW (parent))
 
218
            {
 
219
              /* The current toplevel widget is not our final toplevel widget, as it's
 
220
               * not a GtkWindow.  Let's defer registration until we have a real toplevel.
 
221
               */
 
222
              g_signal_connect (G_OBJECT (parent),
 
223
                                "notify",
 
224
                                G_CALLBACK (toplevel_notify_cb),
 
225
                                proxy);
 
226
 
 
227
              return;
 
228
            }
 
229
          else
 
230
            {
 
231
              /* This is the real toplevel window widget.  If it's already
 
232
               * realized then go ahead and register it, otherwise wait until
 
233
               * it's been realized.
 
234
               */
 
235
              if (gtk_widget_get_realized (widget)) {
 
236
                org_ayatana_WindowMenu_Registrar_register_window (dbusproxy,
 
237
                                                                  GDK_WINDOW_XID (gtk_widget_get_window (widget)),
 
238
                                                                  APP_MENU_PATH,
 
239
                                                                  NULL);
 
240
                registered = TRUE;
 
241
              } else {
 
242
                g_signal_connect (parent, "realize",
 
243
                                  G_CALLBACK (toplevel_realized),
 
244
                                  NULL);
 
245
              }
 
246
            }
 
247
        }
 
248
    }
 
249
}
 
250
 
 
251
static void
 
252
checkbox_toggled (GtkWidget *widget, DbusmenuMenuitem *mi)
 
253
{
 
254
  dbusmenu_menuitem_property_set_int (mi,
 
255
                                      DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
 
256
                                      gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
791
257
}
792
258
 
793
259
static void
797
263
                        guint            position)
798
264
{
799
265
  AppMenuBridge        *bridge;
800
 
  GtkWidget            *toplevel = NULL;
 
266
  AppMenuBridgePrivate *priv;
 
267
  DbusmenuMenuitem     *item;
 
268
  DbusmenuMenuitem     *parent_item = NULL;
 
269
  gboolean              append = FALSE;
801
270
 
802
271
  if (GTK_IS_TEAROFF_MENU_ITEM (child))
803
272
    return;
804
273
 
805
274
  bridge = APP_MENU_BRIDGE (proxy);
 
275
  priv = bridge->priv;
806
276
 
807
 
  toplevel = gtk_widget_get_toplevel (parent);
 
277
  if (g_hash_table_lookup (bridge->priv->items, child) != NULL)
 
278
    return;
808
279
 
809
280
  if (GTK_IS_MENU_BAR (parent))
810
281
    {
811
 
      /* Watch for toplevel window to appear */
812
 
      if (!GTK_IS_WINDOW (toplevel) &&
813
 
          g_signal_handler_find (toplevel, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
814
 
                                 toplevel_notify_parent_cb, NULL) == 0)
815
 
        {
816
 
          g_signal_connect (toplevel,
817
 
                            "notify::parent",
818
 
                            G_CALLBACK (toplevel_notify_parent_cb),
819
 
                            proxy);
820
 
        }
821
 
 
822
 
      /* Watch for parent changes for the menubar itself */
823
 
      if (g_signal_handler_find (parent, G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
824
 
                                 menubar_notify_parent_cb, NULL) == 0)
825
 
        {
826
 
          g_signal_connect (parent,
827
 
                            "notify::parent",
828
 
                            G_CALLBACK (menubar_notify_parent_cb),
829
 
                            proxy);
830
 
        }
 
282
      if (g_hash_table_lookup (bridge->priv->items, parent) == NULL)
 
283
        {
 
284
          DbusmenuMenuitem *root = dbusmenu_menuitem_new ();
 
285
          g_hash_table_insert (bridge->priv->items, parent, root);
 
286
          parent_item = root;
 
287
        }
 
288
      else
 
289
        {
 
290
          parent_item = g_hash_table_lookup (bridge->priv->items, parent);
 
291
        }
 
292
 
 
293
      GtkWidget *toplevel = gtk_widget_get_toplevel (parent);
 
294
 
 
295
      g_signal_connect (G_OBJECT (toplevel),
 
296
                        "notify",
 
297
                        G_CALLBACK (toplevel_notify_cb),
 
298
                        proxy);
 
299
 
 
300
      append = TRUE;
831
301
    }
832
302
  else if (GTK_IS_MENU (parent))
833
303
    {
834
 
      GtkWidget *attach = NULL;
 
304
      GtkWidget *attach;
835
305
 
836
306
      g_object_get (parent, "attach-widget", &attach, NULL);
837
307
 
838
308
      if (attach == NULL)
839
 
        {
840
 
          g_signal_connect (G_OBJECT (parent),
 
309
        return;
 
310
 
 
311
      if (g_hash_table_lookup (bridge->priv->items, parent) != NULL)
 
312
        {
 
313
          parent_item = g_hash_table_lookup (bridge->priv->items, parent);
 
314
        }
 
315
      else
 
316
        {
 
317
          if (g_hash_table_lookup (bridge->priv->items, attach) != NULL)
 
318
            {
 
319
              parent_item = g_hash_table_lookup (bridge->priv->items, attach);
 
320
            }
 
321
          else
 
322
            {
 
323
              // XXX insert the attach item?
 
324
            }
 
325
        }
 
326
    }
 
327
 
 
328
  if (GTK_IS_MENU_ITEM (child))
 
329
    {
 
330
      item = dbusmenu_menuitem_new ();
 
331
      g_hash_table_insert (bridge->priv->items, child, item);
 
332
 
 
333
      if (GTK_IS_SEPARATOR_MENU_ITEM (child))
 
334
        {
 
335
          dbusmenu_menuitem_property_set (item,
 
336
                                          "type",
 
337
                                          "separator");
 
338
        }
 
339
      else
 
340
        {
 
341
          if (GTK_IS_CHECK_MENU_ITEM (child))
 
342
            {
 
343
              dbusmenu_menuitem_property_set (item,
 
344
                                              DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
 
345
                                              GTK_IS_RADIO_MENU_ITEM (child) ? DBUSMENU_MENUITEM_TOGGLE_RADIO : DBUSMENU_MENUITEM_TOGGLE_CHECK);
 
346
 
 
347
              dbusmenu_menuitem_property_set_int (item,
 
348
                                                  DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
 
349
                                                  gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (child)) ? DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED : DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
 
350
 
 
351
              g_signal_connect (child,
 
352
                                "toggled",
 
353
                                G_CALLBACK (checkbox_toggled),
 
354
                                item);
 
355
            }
 
356
 
 
357
          dbusmenu_menuitem_property_set (item,
 
358
                                          "label",
 
359
                                          get_menu_label_text (child));
 
360
 
 
361
          dbusmenu_menuitem_property_set_bool (item,
 
362
                                               DBUSMENU_MENUITEM_PROP_ENABLED,
 
363
                                               gtk_widget_get_sensitive (child));
 
364
 
 
365
          g_signal_connect (G_OBJECT (child),
841
366
                            "notify",
842
 
                            G_CALLBACK (attach_notify_cb),
843
 
                            bridge);
844
 
          return;
845
 
        }
846
 
      else
847
 
        {
848
 
          DbusmenuMenuitem *mi = dbusmenu_gtk_parse_get_cached_item (attach);
849
 
 
850
 
          if (mi != NULL)
 
367
                            G_CALLBACK (widget_notify_cb),
 
368
                            item);
 
369
 
 
370
          g_signal_connect (G_OBJECT (item),
 
371
                            "item_activated",
 
372
                            G_CALLBACK (item_activated),
 
373
                            child);
 
374
 
 
375
          if (parent_item)
851
376
            {
852
 
              DbusmenuMenuitem *child_dmi = dbusmenu_gtk_parse_menu_structure (child);
853
 
 
854
 
              g_object_set_data (G_OBJECT (child_dmi), "dbusmenu-parent", mi);
855
 
              dbusmenu_menuitem_child_add_position (mi,
856
 
                                                    child_dmi,
857
 
                                                    position);
858
 
              g_object_unref (child_dmi);
859
 
              return;
 
377
              if (append)
 
378
                dbusmenu_menuitem_child_append (parent_item, item);
 
379
              else
 
380
                dbusmenu_menuitem_child_prepend (parent_item, item);
860
381
            }
861
 
 
862
 
          rebuild (bridge, toplevel);
863
 
        }
864
 
    }
865
 
 
866
 
  if (GTK_IS_WINDOW (toplevel))
867
 
    {
868
 
      if (gtk_widget_get_mapped (toplevel))
869
 
        {
870
 
          rebuild (bridge, toplevel);
871
 
        }
872
 
      else
873
 
        {
874
 
          g_signal_connect (toplevel, "map",
875
 
                            G_CALLBACK (toplevel_mapped),
876
 
                            bridge);
877
382
        }
878
383
    }
879
384
}
881
386
static gboolean
882
387
app_menu_bridge_show_local (UbuntuMenuProxy *proxy)
883
388
{
884
 
  gboolean local;
885
 
 
886
 
  g_object_get (proxy,
887
 
                "show-local", &local,
888
 
                NULL);
889
 
 
890
 
  return local;
891
 
}
892
 
 
893
 
/* Crude blacklist to avoid patching innocent apps */
894
 
/* Use xprop | grep CLASS to find the name to use */
895
 
static gboolean
896
 
app_menu_brige_shouldnt_load (void)
897
 
{
898
 
  const char *prg = g_get_prgname ();
899
 
 
900
 
  if ((g_strrstr (prg, "indicator-applet") != NULL)
901
 
      || (g_strcmp0 (prg, "indicator-loader") == 0)
902
 
      || (g_strcmp0 (prg, "mutter") == 0)
903
 
      || (g_strcmp0 (prg, "midori") == 0)
904
 
      || (g_strcmp0 (prg, "firefox-bin") == 0)
905
 
      || (g_strcmp0 (prg, "thunderbird-bin") == 0)
906
 
      || (g_strcmp0 (prg, "Eclipse") == 0)
907
 
      || (g_strcmp0 (prg, "emacs") == 0)
908
 
      || (g_strcmp0 (prg, "emacs23") == 0)
909
 
      || (g_strcmp0 (prg, "emacs23-lucid") == 0)
910
 
      || (g_strcmp0 (prg, "gnome-panel") == 0)
911
 
      || (g_strcmp0 (prg, "Lotus Notes") == 0))
912
 
    {
913
 
      return TRUE;
914
 
    }
915
 
 
916
 
  return FALSE;
917
 
}
918
 
 
 
389
  const gchar *env = g_getenv ("APPMENU_DISPLAY_BOTH");
 
390
 
 
391
  return (g_strcmp0 (env, "1") == 0);
 
392
}
919
393
 
920
394
G_MODULE_EXPORT void
921
395
menu_proxy_module_load (UbuntuMenuProxyModule *module)
922
396
{
923
397
  static gboolean registered = FALSE;
924
398
 
925
 
  /* Prevent well-known applications to re-export
926
 
         their own dbusmenus */
927
 
  if (app_menu_brige_shouldnt_load ())
928
 
          return;
929
 
 
930
399
  if (!registered)
931
400
    {
 
401
      DBusGConnection *connection;
 
402
 
 
403
      connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
 
404
 
 
405
      g_return_if_fail (connection != NULL);
 
406
 
 
407
      dbusproxy = dbus_g_proxy_new_for_name_owner (connection,
 
408
                                                   APP_MENU_DBUS_NAME,
 
409
                                                   APP_MENU_DBUS_OBJECT,
 
410
                                                   APP_MENU_INTERFACE,
 
411
                                                   NULL);
 
412
 
 
413
      g_return_if_fail (dbusproxy != NULL);
 
414
 
932
415
      app_menu_bridge_register_type (G_TYPE_MODULE (module));
933
416
 
934
417
      registered = TRUE;
938
421
G_MODULE_EXPORT void
939
422
menu_proxy_module_unload (UbuntuMenuProxyModule *module)
940
423
{
941
 
  if (rebuild_ids)
942
 
    {
943
 
      g_hash_table_destroy (rebuild_ids);
944
 
      rebuild_ids = NULL;
945
 
    }
946
424
}