~ubuntu-branches/ubuntu/trusty/hud/trusty-updates

« back to all changes in this revision

Viewing changes to src/appmenu-registrar.c

  • Committer: Package Import Robot
  • Author(s): Ubuntu daily release
  • Date: 2014-01-20 19:43:59 UTC
  • mfrom: (1.1.26)
  • Revision ID: package-import@ubuntu.com-20140120194359-jxxxqtd4ql9elvpf
Tags: 13.10.1+14.04.20140120-0ubuntu1
* New rebuild forced
* Automatic snapshot from revision 362

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Copyright © 2012 Canonical Ltd.
3
 
 *
4
 
 * This program is free software: you can redistribute it and/or modify it
5
 
 * under the terms of the GNU General Public License version 3, as
6
 
 * published by the Free Software Foundation.
7
 
 *
8
 
 * This program is distributed in the hope that it will be useful, but
9
 
 * WITHOUT ANY WARRANTY; without even the implied warranties of
10
 
 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11
 
 * PURPOSE.  See the GNU General Public License for more details.
12
 
 *
13
 
 * You should have received a copy of the GNU General Public License along
14
 
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
15
 
 *
16
 
 * Author: Ryan Lortie <desrt@desrt.ca>
17
 
 */
18
 
 
19
 
#define G_LOG_DOMAIN "hudappmenuregistrar"
20
 
 
21
 
#include "appmenu-registrar.h"
22
 
 
23
 
#include <gio/gio.h>
24
 
 
25
 
/**
26
 
 * SECTION:hudappmenuregistrar
27
 
 * @title: HudAppMenuRegistrar
28
 
 * @short_description: client for the com.canonical.AppMenu.Registrar
29
 
 *   D-Bus service
30
 
 *
31
 
 * The #HudAppMenuRegistrar is a singleton object that monitors the
32
 
 * com.canonical.AppMenu.Registrar D-Bus service.
33
 
 *
34
 
 * On instantiation, a D-Bus name watch is setup for the registrar.
35
 
 * When the registrar is found to exist, a local copy is made of the
36
 
 * windows and menus that the registrar knows about.  Change
37
 
 * notifications are also monitored to keep the local cache in sync.
38
 
 *
39
 
 * After that point, all queries for information from the registrar are
40
 
 * satisfied from the local cache, without blocking.
41
 
 *
42
 
 * Information is acquired from #HudAppMenuRegistrar by using
43
 
 * hud_app_menu_registrar_add_observer().  This immediately calls a
44
 
 * callback with the initial information and makes future calls to the
45
 
 * same callback if the information is found to have changed.
46
 
 *
47
 
 * If the registrar is offline or the information is not yet available
48
 
 * at the time of the original query, the window will initially be
49
 
 * reported as having no menu but a change notification will arrive when
50
 
 * the proper information becomes available.
51
 
 **/
52
 
 
53
 
/**
54
 
 * HudAppMenuRegistrar:
55
 
 *
56
 
 * This is an opaque structure type.
57
 
 **/
58
 
 
59
 
#define APPMENU_REGISTRAR_BUS_NAME    "com.canonical.AppMenu.Registrar"
60
 
#define APPMENU_REGISTRAR_OBJECT_PATH "/com/canonical/AppMenu/Registrar"
61
 
#define APPMENU_REGISTRAR_IFACE       "com.canonical.AppMenu.Registrar"
62
 
 
63
 
typedef struct
64
 
{
65
 
  HudAppMenuRegistrarObserverFunc callback;
66
 
  gpointer                        user_data;
67
 
} HudAppMenuRegistrarObserver;
68
 
 
69
 
typedef struct
70
 
{
71
 
  guint xid;
72
 
  gchar *bus_name;
73
 
  gchar *object_path;
74
 
  GSList *observers;
75
 
} HudAppMenuRegistrarWindow;
76
 
 
77
 
struct _HudAppMenuRegistrar
78
 
{
79
 
  GObject parent_instance;
80
 
 
81
 
  guint subscription;
82
 
  GCancellable *cancellable;
83
 
  GHashTable *windows;
84
 
  gboolean notifying;
85
 
  gboolean ready;
86
 
};
87
 
 
88
 
typedef GObjectClass HudAppMenuRegistrarClass;
89
 
 
90
 
G_DEFINE_TYPE (HudAppMenuRegistrar, hud_app_menu_registrar, G_TYPE_OBJECT)
91
 
 
92
 
static void
93
 
hud_app_menu_registrar_window_free (gpointer user_data)
94
 
{
95
 
  HudAppMenuRegistrarWindow *window = user_data;
96
 
 
97
 
  g_assert (window->bus_name == NULL);
98
 
  g_assert (window->object_path == NULL);
99
 
  g_assert (window->observers == NULL);
100
 
 
101
 
  g_debug ("free window instance for %u", window->xid);
102
 
 
103
 
  g_slice_free (HudAppMenuRegistrarWindow, window);
104
 
}
105
 
 
106
 
static HudAppMenuRegistrarWindow *
107
 
hud_app_menu_registrar_get_window (HudAppMenuRegistrar *registrar,
108
 
                                   guint                xid)
109
 
{
110
 
  HudAppMenuRegistrarWindow *window;
111
 
 
112
 
  window = g_hash_table_lookup (registrar->windows, GINT_TO_POINTER (xid));
113
 
 
114
 
  if (!window)
115
 
    {
116
 
      window = g_slice_new0 (HudAppMenuRegistrarWindow);
117
 
      window->xid = xid;
118
 
 
119
 
      g_debug ("create window instance for %u", xid);
120
 
      g_hash_table_insert (registrar->windows, GINT_TO_POINTER (xid), window);
121
 
    }
122
 
 
123
 
  return window;
124
 
}
125
 
 
126
 
static void
127
 
hud_app_menu_registrar_possibly_free_window (HudAppMenuRegistrar       *registrar,
128
 
                                             HudAppMenuRegistrarWindow *window)
129
 
{
130
 
  if (window->bus_name == NULL && window->observers == NULL)
131
 
    g_hash_table_remove (registrar->windows, GINT_TO_POINTER (window->xid));
132
 
}
133
 
 
134
 
static void
135
 
hud_app_menu_registrar_notify_window_observers (HudAppMenuRegistrar       *registrar,
136
 
                                                HudAppMenuRegistrarWindow *window)
137
 
{
138
 
  GSList *node;
139
 
 
140
 
  registrar->notifying = TRUE;
141
 
 
142
 
  for (node = window->observers; node; node = node->next)
143
 
    {
144
 
      HudAppMenuRegistrarObserver *observer = node->data;
145
 
 
146
 
      g_debug ("notifying %p about %u", observer->user_data, window->xid);
147
 
      (* observer->callback) (registrar, window->xid, window->bus_name, window->object_path, observer->user_data);
148
 
    }
149
 
 
150
 
  registrar->notifying = FALSE;
151
 
}
152
 
 
153
 
static void
154
 
hud_app_menu_registrar_dbus_signal (GDBusConnection *connection,
155
 
                                    const gchar     *sender_name,
156
 
                                    const gchar     *object_path,
157
 
                                    const gchar     *interface_name,
158
 
                                    const gchar     *signal_name,
159
 
                                    GVariant        *parameters,
160
 
                                    gpointer         user_data)
161
 
{
162
 
  HudAppMenuRegistrar *registrar = user_data;
163
 
 
164
 
  g_debug ("got signal");
165
 
 
166
 
  if (!registrar->ready)
167
 
    {
168
 
      g_debug ("not ready, so ignoring signal");
169
 
      return;
170
 
    }
171
 
 
172
 
  if (g_str_equal (signal_name, "WindowRegistered"))
173
 
    {
174
 
      HudAppMenuRegistrarWindow *window;
175
 
      guint xid;
176
 
 
177
 
      if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uso)")))
178
 
          return;
179
 
 
180
 
      g_variant_get_child (parameters, 0, "u", &xid);
181
 
      window = hud_app_menu_registrar_get_window (registrar, xid);
182
 
 
183
 
      g_free (window->bus_name);
184
 
      g_variant_get_child (parameters, 1, "s", &window->bus_name);
185
 
      g_free (window->object_path);
186
 
      g_variant_get_child (parameters, 2, "o", &window->object_path);
187
 
 
188
 
      g_debug ("xid %u is now at (%s, %s)", xid, window->bus_name, window->object_path);
189
 
 
190
 
      hud_app_menu_registrar_notify_window_observers (registrar, window);
191
 
    }
192
 
 
193
 
  else if (g_str_equal (signal_name, "WindowUnregistered"))
194
 
    {
195
 
      HudAppMenuRegistrarWindow *window;
196
 
      guint xid;
197
 
 
198
 
      if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(u)")))
199
 
        return;
200
 
 
201
 
      g_variant_get (parameters, "(u)", &xid);
202
 
 
203
 
      g_debug ("xid %u disappeared", xid);
204
 
 
205
 
      window = hud_app_menu_registrar_get_window (registrar, xid);
206
 
 
207
 
      g_free (window->bus_name);
208
 
      window->bus_name = NULL;
209
 
      g_free (window->object_path);
210
 
      window->object_path = NULL;
211
 
 
212
 
      hud_app_menu_registrar_notify_window_observers (registrar, window);
213
 
 
214
 
      hud_app_menu_registrar_possibly_free_window (registrar, window);
215
 
    }
216
 
}
217
 
 
218
 
static void
219
 
hud_app_menu_registrar_ready (GObject      *source,
220
 
                              GAsyncResult *result,
221
 
                              gpointer      user_data)
222
 
{
223
 
  HudAppMenuRegistrar *registrar = user_data;
224
 
  GError *error = NULL;
225
 
  GVariant *reply;
226
 
 
227
 
  g_debug ("GetMenus returned");
228
 
 
229
 
  g_clear_object (&registrar->cancellable);
230
 
 
231
 
  if (source == NULL)
232
 
  {
233
 
    g_debug("Callback invoked with null connection");
234
 
    return;
235
 
  }
236
 
 
237
 
  reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
238
 
 
239
 
  if (reply)
240
 
    {
241
 
      GVariantIter *iter;
242
 
      const gchar *bus_name;
243
 
      const gchar *object_path;
244
 
      guint xid;
245
 
 
246
 
      g_assert (!registrar->ready);
247
 
      registrar->ready = TRUE;
248
 
 
249
 
      g_debug ("going ready");
250
 
 
251
 
      g_variant_get (reply, "(a(uso))", &iter);
252
 
 
253
 
      while (g_variant_iter_next (iter, "(u&s&o)", &xid, &bus_name, &object_path))
254
 
        {
255
 
          HudAppMenuRegistrarWindow *window;
256
 
 
257
 
          window = hud_app_menu_registrar_get_window (registrar, xid);
258
 
 
259
 
          /* we were not ready until now, so we expect this to be unset */
260
 
          g_assert (window->bus_name == NULL);
261
 
 
262
 
          window->bus_name = g_strdup (bus_name);
263
 
          window->object_path = g_strdup (object_path);
264
 
 
265
 
          hud_app_menu_registrar_notify_window_observers (registrar, window);
266
 
        }
267
 
 
268
 
      g_variant_iter_free (iter);
269
 
      g_variant_unref (reply);
270
 
    }
271
 
  else
272
 
    {
273
 
      g_warning ("GetMenus returned an error: %s", error->message);
274
 
      g_error_free (error);
275
 
    }
276
 
 
277
 
  g_object_unref (registrar);
278
 
 
279
 
  g_debug ("done handling GetMenus reply");
280
 
}
281
 
 
282
 
static void
283
 
hud_app_menu_registrar_name_appeared (GDBusConnection *connection,
284
 
                                      const gchar     *name,
285
 
                                      const gchar     *name_owner,
286
 
                                      gpointer         user_data)
287
 
{
288
 
  HudAppMenuRegistrar *registrar = user_data;
289
 
 
290
 
  g_debug ("name appeared (owner is %s)", name_owner);
291
 
 
292
 
  g_assert (registrar->subscription == 0);
293
 
  registrar->subscription = g_dbus_connection_signal_subscribe (connection, name_owner,
294
 
                                                                APPMENU_REGISTRAR_IFACE, NULL,
295
 
                                                                APPMENU_REGISTRAR_OBJECT_PATH, NULL,
296
 
                                                                G_DBUS_SIGNAL_FLAGS_NONE,
297
 
                                                                hud_app_menu_registrar_dbus_signal,
298
 
                                                                registrar, NULL);
299
 
 
300
 
  g_assert (registrar->cancellable == NULL);
301
 
  registrar->cancellable = g_cancellable_new ();
302
 
  g_dbus_connection_call (connection, name_owner, APPMENU_REGISTRAR_OBJECT_PATH,
303
 
                          APPMENU_REGISTRAR_IFACE, "GetMenus", NULL, G_VARIANT_TYPE ("(a(uso))"),
304
 
                          G_DBUS_CALL_FLAGS_NONE, -1, registrar->cancellable,
305
 
                          hud_app_menu_registrar_ready, g_object_ref (registrar));
306
 
}
307
 
 
308
 
static void
309
 
hud_app_menu_registrar_name_vanished (GDBusConnection *connection,
310
 
                                      const gchar     *name,
311
 
                                      gpointer         user_data)
312
 
{
313
 
  HudAppMenuRegistrar *registrar = user_data;
314
 
 
315
 
  g_debug ("name vanished");
316
 
 
317
 
  if(connection == NULL)
318
 
    {
319
 
      return;
320
 
    }
321
 
 
322
 
  if (registrar->subscription > 0)
323
 
    {
324
 
      g_dbus_connection_signal_unsubscribe (connection, registrar->subscription);
325
 
      registrar->subscription = 0;
326
 
    }
327
 
 
328
 
  if (registrar->cancellable)
329
 
    {
330
 
      g_cancellable_cancel (registrar->cancellable);
331
 
      g_clear_object (&registrar->cancellable);
332
 
    }
333
 
 
334
 
  if (registrar->ready)
335
 
    {
336
 
      GHashTableIter iter;
337
 
      gpointer value;
338
 
 
339
 
      registrar->ready = FALSE;
340
 
 
341
 
      g_hash_table_iter_init (&iter, registrar->windows);
342
 
      while (g_hash_table_iter_next (&iter, NULL, &value))
343
 
        {
344
 
          HudAppMenuRegistrarWindow *window = value;
345
 
 
346
 
          g_free (window->bus_name);
347
 
          window->bus_name = NULL;
348
 
          g_free (window->object_path);
349
 
          window->object_path = NULL;
350
 
 
351
 
          hud_app_menu_registrar_notify_window_observers (registrar, window);
352
 
 
353
 
          /* Cannot go the normal route here because we are iterating... */
354
 
          if (window->observers == NULL)
355
 
            g_hash_table_iter_remove (&iter);
356
 
        }
357
 
    }
358
 
}
359
 
 
360
 
static void
361
 
hud_app_menu_registrar_finalize (GObject *object)
362
 
{
363
 
 
364
 
  G_OBJECT_CLASS(hud_app_menu_registrar_parent_class)->finalize(object);
365
 
  return;
366
 
}
367
 
 
368
 
static void
369
 
hud_app_menu_registrar_init (HudAppMenuRegistrar *registrar)
370
 
{
371
 
  g_debug ("online");
372
 
 
373
 
  registrar->windows = g_hash_table_new_full (NULL, NULL, NULL, hud_app_menu_registrar_window_free);
374
 
  g_bus_watch_name (G_BUS_TYPE_SESSION, APPMENU_REGISTRAR_BUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE,
375
 
                    hud_app_menu_registrar_name_appeared, hud_app_menu_registrar_name_vanished,
376
 
                    g_object_ref (registrar), g_object_unref);
377
 
}
378
 
 
379
 
static void
380
 
hud_app_menu_registrar_class_init (HudAppMenuRegistrarClass *class)
381
 
{
382
 
  class->finalize = hud_app_menu_registrar_finalize;
383
 
}
384
 
 
385
 
/**
386
 
 * HudAppMenuRegistrarObserverFunc:
387
 
 * @registrar: the #HudAppMenuRegistrar
388
 
 * @xid: the xid that we are notifying about
389
 
 * @bus_name: the bus name for the dbusmenu, or %NULL
390
 
 * @object_path: the object path for the dbusmenu, or %NULL
391
 
 * @user_data: the data pointer
392
 
 *
393
 
 * Notifies about the initial values for or changes to the bus name and
394
 
 * object path at which to find the dbusmenu for @xid.
395
 
 *
396
 
 * You should pass the values of @bus_name and @object_path to
397
 
 * dbusmenu_client_new() to get started.
398
 
 *
399
 
 * If no menu is available then @bus_name and @object_path will both be
400
 
 * given as %NULL.
401
 
 **/
402
 
 
403
 
/**
404
 
 * hud_app_menu_registrar_add_observer:
405
 
 * @registrar: the #HudAppMenuRegistrar
406
 
 * @xid: the xid to begin observing
407
 
 * @callback: a #HudAppMenuRegistrarObserverFunc
408
 
 * @user_data: user data for @callback
409
 
 *
410
 
 * Begins observing @xid.
411
 
 *
412
 
 * @callback will be called exactly once before the function returns
413
 
 * with a set of initial values (the bus name and object path at which
414
 
 * to find the menu for the window).
415
 
 *
416
 
 * If the location of the menu for @xid changes (including being created
417
 
 * or destroyed) then @callback will be called each time an update is
418
 
 * required.
419
 
 *
420
 
 * It is possible that the values are not initially known because they
421
 
 * have not yet been retreived from the registrar or because the
422
 
 * registrar is not running.  In this case, %NULL values will be
423
 
 * provided initially and @callback will be invoked again when the
424
 
 * real values are known.
425
 
 *
426
 
 * Call hud_app_menu_registrar_remove_observer() to when you are no
427
 
 * longer interested in @xid.
428
 
 **/
429
 
void
430
 
hud_app_menu_registrar_add_observer (HudAppMenuRegistrar             *registrar,
431
 
                                     guint                            xid,
432
 
                                     HudAppMenuRegistrarObserverFunc  callback,
433
 
                                     gpointer                         user_data)
434
 
{
435
 
  HudAppMenuRegistrarObserver *observer;
436
 
  HudAppMenuRegistrarWindow *window;
437
 
 
438
 
  g_return_if_fail (xid != 0);
439
 
  g_return_if_fail (callback != NULL);
440
 
  g_return_if_fail (!registrar->notifying);
441
 
 
442
 
  g_debug ("observer added for xid %u (%p)", xid, user_data);
443
 
 
444
 
  observer = g_slice_new (HudAppMenuRegistrarObserver);
445
 
  observer->callback = callback;
446
 
  observer->user_data = user_data;
447
 
 
448
 
  window = hud_app_menu_registrar_get_window (registrar, xid);
449
 
  window->observers = g_slist_prepend (window->observers, observer);
450
 
 
451
 
  /* send the first update */
452
 
  (* callback) (registrar, xid, window->bus_name, window->object_path, user_data);
453
 
}
454
 
 
455
 
/**
456
 
 * hud_app_menu_registrar_remove_observer:
457
 
 * @registrar: the #HudAppMenuRegistrar
458
 
 * @xid: the xid to begin observing
459
 
 * @callback: a #HudAppMenuRegistrarObserverFunc
460
 
 * @user_data: user data for @callback
461
 
 *
462
 
 * Reverses the effect of a previous call to
463
 
 * hud_app_menu_registrar_add_observer().
464
 
 *
465
 
 * @callback and @user_data must be exactly equal to the values passed
466
 
 * to that function.
467
 
 *
468
 
 * One call does not remove all instances of @callback and @user_data.
469
 
 * You need to call this function the same number of times that you
470
 
 * called hud_app_menu_registrar_add_observer().
471
 
 **/
472
 
void
473
 
hud_app_menu_registrar_remove_observer (HudAppMenuRegistrar             *registrar,
474
 
                                        guint                            xid,
475
 
                                        HudAppMenuRegistrarObserverFunc  callback,
476
 
                                        gpointer                         user_data)
477
 
{
478
 
  HudAppMenuRegistrarWindow *window;
479
 
  GSList **node;
480
 
 
481
 
  g_return_if_fail (xid != 0);
482
 
  g_return_if_fail (callback != NULL);
483
 
  g_return_if_fail (!registrar->notifying);
484
 
 
485
 
  g_debug ("observer removed for xid %u (%p)", xid, user_data);
486
 
 
487
 
  window = hud_app_menu_registrar_get_window (registrar, xid);
488
 
  for (node = &window->observers; *node; node = &(*node)->next)
489
 
    {
490
 
      HudAppMenuRegistrarObserver *observer = (*node)->data;
491
 
 
492
 
      if (observer->callback == callback && observer->user_data == user_data)
493
 
        {
494
 
          g_slice_free (HudAppMenuRegistrarObserver, observer);
495
 
          *node = g_slist_delete_link (*node, *node);
496
 
          break;
497
 
        }
498
 
    }
499
 
 
500
 
  hud_app_menu_registrar_possibly_free_window (registrar, window);
501
 
}
502
 
 
503
 
/**
504
 
 * hud_app_menu_registrar_get:
505
 
 *
506
 
 * Gets the singleton instance of #HudAppMenuRegistrar.
507
 
 *
508
 
 * Returns: (transfer none): the instance
509
 
 **/
510
 
HudAppMenuRegistrar *
511
 
hud_app_menu_registrar_get (void)
512
 
{
513
 
  static HudAppMenuRegistrar *singleton;
514
 
 
515
 
  if (!singleton)
516
 
    singleton = g_object_new (HUD_TYPE_APP_MENU_REGISTRAR, NULL);
517
 
 
518
 
  return singleton;
519
 
}