2
* Copyright © 2012 Canonical Ltd.
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.
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.
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/>.
16
* Author: Ryan Lortie <desrt@desrt.ca>
19
#define G_LOG_DOMAIN "hudappmenuregistrar"
21
#include "appmenu-registrar.h"
26
* SECTION:hudappmenuregistrar
27
* @title: HudAppMenuRegistrar
28
* @short_description: client for the com.canonical.AppMenu.Registrar
31
* The #HudAppMenuRegistrar is a singleton object that monitors the
32
* com.canonical.AppMenu.Registrar D-Bus service.
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.
39
* After that point, all queries for information from the registrar are
40
* satisfied from the local cache, without blocking.
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.
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.
54
* HudAppMenuRegistrar:
56
* This is an opaque structure type.
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"
65
HudAppMenuRegistrarObserverFunc callback;
67
} HudAppMenuRegistrarObserver;
75
} HudAppMenuRegistrarWindow;
77
struct _HudAppMenuRegistrar
79
GObject parent_instance;
82
GCancellable *cancellable;
88
typedef GObjectClass HudAppMenuRegistrarClass;
90
G_DEFINE_TYPE (HudAppMenuRegistrar, hud_app_menu_registrar, G_TYPE_OBJECT)
93
hud_app_menu_registrar_window_free (gpointer user_data)
95
HudAppMenuRegistrarWindow *window = user_data;
97
g_assert (window->bus_name == NULL);
98
g_assert (window->object_path == NULL);
99
g_assert (window->observers == NULL);
101
g_debug ("free window instance for %u", window->xid);
103
g_slice_free (HudAppMenuRegistrarWindow, window);
106
static HudAppMenuRegistrarWindow *
107
hud_app_menu_registrar_get_window (HudAppMenuRegistrar *registrar,
110
HudAppMenuRegistrarWindow *window;
112
window = g_hash_table_lookup (registrar->windows, GINT_TO_POINTER (xid));
116
window = g_slice_new0 (HudAppMenuRegistrarWindow);
119
g_debug ("create window instance for %u", xid);
120
g_hash_table_insert (registrar->windows, GINT_TO_POINTER (xid), window);
127
hud_app_menu_registrar_possibly_free_window (HudAppMenuRegistrar *registrar,
128
HudAppMenuRegistrarWindow *window)
130
if (window->bus_name == NULL && window->observers == NULL)
131
g_hash_table_remove (registrar->windows, GINT_TO_POINTER (window->xid));
135
hud_app_menu_registrar_notify_window_observers (HudAppMenuRegistrar *registrar,
136
HudAppMenuRegistrarWindow *window)
140
registrar->notifying = TRUE;
142
for (node = window->observers; node; node = node->next)
144
HudAppMenuRegistrarObserver *observer = node->data;
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);
150
registrar->notifying = FALSE;
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,
162
HudAppMenuRegistrar *registrar = user_data;
164
g_debug ("got signal");
166
if (!registrar->ready)
168
g_debug ("not ready, so ignoring signal");
172
if (g_str_equal (signal_name, "WindowRegistered"))
174
HudAppMenuRegistrarWindow *window;
177
if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uso)")))
180
g_variant_get_child (parameters, 0, "u", &xid);
181
window = hud_app_menu_registrar_get_window (registrar, xid);
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);
188
g_debug ("xid %u is now at (%s, %s)", xid, window->bus_name, window->object_path);
190
hud_app_menu_registrar_notify_window_observers (registrar, window);
193
else if (g_str_equal (signal_name, "WindowUnregistered"))
195
HudAppMenuRegistrarWindow *window;
198
if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(u)")))
201
g_variant_get (parameters, "(u)", &xid);
203
g_debug ("xid %u disappeared", xid);
205
window = hud_app_menu_registrar_get_window (registrar, xid);
207
g_free (window->bus_name);
208
window->bus_name = NULL;
209
g_free (window->object_path);
210
window->object_path = NULL;
212
hud_app_menu_registrar_notify_window_observers (registrar, window);
214
hud_app_menu_registrar_possibly_free_window (registrar, window);
219
hud_app_menu_registrar_ready (GObject *source,
220
GAsyncResult *result,
223
HudAppMenuRegistrar *registrar = user_data;
224
GError *error = NULL;
227
g_debug ("GetMenus returned");
229
g_clear_object (®istrar->cancellable);
233
g_debug("Callback invoked with null connection");
237
reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
242
const gchar *bus_name;
243
const gchar *object_path;
246
g_assert (!registrar->ready);
247
registrar->ready = TRUE;
249
g_debug ("going ready");
251
g_variant_get (reply, "(a(uso))", &iter);
253
while (g_variant_iter_next (iter, "(u&s&o)", &xid, &bus_name, &object_path))
255
HudAppMenuRegistrarWindow *window;
257
window = hud_app_menu_registrar_get_window (registrar, xid);
259
/* we were not ready until now, so we expect this to be unset */
260
g_assert (window->bus_name == NULL);
262
window->bus_name = g_strdup (bus_name);
263
window->object_path = g_strdup (object_path);
265
hud_app_menu_registrar_notify_window_observers (registrar, window);
268
g_variant_iter_free (iter);
269
g_variant_unref (reply);
273
g_warning ("GetMenus returned an error: %s", error->message);
274
g_error_free (error);
277
g_object_unref (registrar);
279
g_debug ("done handling GetMenus reply");
283
hud_app_menu_registrar_name_appeared (GDBusConnection *connection,
285
const gchar *name_owner,
288
HudAppMenuRegistrar *registrar = user_data;
290
g_debug ("name appeared (owner is %s)", name_owner);
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,
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));
309
hud_app_menu_registrar_name_vanished (GDBusConnection *connection,
313
HudAppMenuRegistrar *registrar = user_data;
315
g_debug ("name vanished");
317
if(connection == NULL)
322
if (registrar->subscription > 0)
324
g_dbus_connection_signal_unsubscribe (connection, registrar->subscription);
325
registrar->subscription = 0;
328
if (registrar->cancellable)
330
g_cancellable_cancel (registrar->cancellable);
331
g_clear_object (®istrar->cancellable);
334
if (registrar->ready)
339
registrar->ready = FALSE;
341
g_hash_table_iter_init (&iter, registrar->windows);
342
while (g_hash_table_iter_next (&iter, NULL, &value))
344
HudAppMenuRegistrarWindow *window = value;
346
g_free (window->bus_name);
347
window->bus_name = NULL;
348
g_free (window->object_path);
349
window->object_path = NULL;
351
hud_app_menu_registrar_notify_window_observers (registrar, window);
353
/* Cannot go the normal route here because we are iterating... */
354
if (window->observers == NULL)
355
g_hash_table_iter_remove (&iter);
361
hud_app_menu_registrar_finalize (GObject *object)
364
G_OBJECT_CLASS(hud_app_menu_registrar_parent_class)->finalize(object);
369
hud_app_menu_registrar_init (HudAppMenuRegistrar *registrar)
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);
380
hud_app_menu_registrar_class_init (HudAppMenuRegistrarClass *class)
382
class->finalize = hud_app_menu_registrar_finalize;
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
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.
396
* You should pass the values of @bus_name and @object_path to
397
* dbusmenu_client_new() to get started.
399
* If no menu is available then @bus_name and @object_path will both be
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
410
* Begins observing @xid.
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).
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
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.
426
* Call hud_app_menu_registrar_remove_observer() to when you are no
427
* longer interested in @xid.
430
hud_app_menu_registrar_add_observer (HudAppMenuRegistrar *registrar,
432
HudAppMenuRegistrarObserverFunc callback,
435
HudAppMenuRegistrarObserver *observer;
436
HudAppMenuRegistrarWindow *window;
438
g_return_if_fail (xid != 0);
439
g_return_if_fail (callback != NULL);
440
g_return_if_fail (!registrar->notifying);
442
g_debug ("observer added for xid %u (%p)", xid, user_data);
444
observer = g_slice_new (HudAppMenuRegistrarObserver);
445
observer->callback = callback;
446
observer->user_data = user_data;
448
window = hud_app_menu_registrar_get_window (registrar, xid);
449
window->observers = g_slist_prepend (window->observers, observer);
451
/* send the first update */
452
(* callback) (registrar, xid, window->bus_name, window->object_path, user_data);
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
462
* Reverses the effect of a previous call to
463
* hud_app_menu_registrar_add_observer().
465
* @callback and @user_data must be exactly equal to the values passed
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().
473
hud_app_menu_registrar_remove_observer (HudAppMenuRegistrar *registrar,
475
HudAppMenuRegistrarObserverFunc callback,
478
HudAppMenuRegistrarWindow *window;
481
g_return_if_fail (xid != 0);
482
g_return_if_fail (callback != NULL);
483
g_return_if_fail (!registrar->notifying);
485
g_debug ("observer removed for xid %u (%p)", xid, user_data);
487
window = hud_app_menu_registrar_get_window (registrar, xid);
488
for (node = &window->observers; *node; node = &(*node)->next)
490
HudAppMenuRegistrarObserver *observer = (*node)->data;
492
if (observer->callback == callback && observer->user_data == user_data)
494
g_slice_free (HudAppMenuRegistrarObserver, observer);
495
*node = g_slist_delete_link (*node, *node);
500
hud_app_menu_registrar_possibly_free_window (registrar, window);
504
* hud_app_menu_registrar_get:
506
* Gets the singleton instance of #HudAppMenuRegistrar.
508
* Returns: (transfer none): the instance
510
HudAppMenuRegistrar *
511
hud_app_menu_registrar_get (void)
513
static HudAppMenuRegistrar *singleton;
516
singleton = g_object_new (HUD_TYPE_APP_MENU_REGISTRAR, NULL);