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 "hudappmenuregistrar.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, 0, "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);
231
reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
236
const gchar *bus_name;
237
const gchar *object_path;
240
g_assert (!registrar->ready);
241
registrar->ready = TRUE;
243
g_debug ("going ready");
245
g_variant_get (reply, "(a(uso))", &iter);
247
while (g_variant_iter_next (iter, "(u&s&o)", &xid, &bus_name, &object_path))
249
HudAppMenuRegistrarWindow *window;
251
window = hud_app_menu_registrar_get_window (registrar, xid);
253
/* we were not ready until now, so we expect this to be unset */
254
g_assert (window->bus_name == NULL);
256
window->bus_name = g_strdup (bus_name);
257
window->object_path = g_strdup (object_path);
259
hud_app_menu_registrar_notify_window_observers (registrar, window);
262
g_variant_iter_free (iter);
263
g_variant_unref (reply);
267
g_warning ("GetMenus returned an error: %s", error->message);
268
g_error_free (error);
271
g_object_unref (registrar);
273
g_debug ("done handling GetMenus reply");
277
hud_app_menu_registrar_name_appeared (GDBusConnection *connection,
279
const gchar *name_owner,
282
HudAppMenuRegistrar *registrar = user_data;
284
g_debug ("name appeared (owner is %s)", name_owner);
286
g_assert (registrar->subscription == 0);
287
registrar->subscription = g_dbus_connection_signal_subscribe (connection, name_owner,
288
APPMENU_REGISTRAR_IFACE, NULL,
289
APPMENU_REGISTRAR_OBJECT_PATH, NULL,
290
G_DBUS_SIGNAL_FLAGS_NONE,
291
hud_app_menu_registrar_dbus_signal,
294
g_assert (registrar->cancellable == NULL);
295
registrar->cancellable = g_cancellable_new ();
296
g_dbus_connection_call (connection, name_owner, APPMENU_REGISTRAR_OBJECT_PATH,
297
APPMENU_REGISTRAR_IFACE, "GetMenus", NULL, G_VARIANT_TYPE ("(a(uso))"),
298
G_DBUS_CALL_FLAGS_NONE, -1, registrar->cancellable,
299
hud_app_menu_registrar_ready, g_object_ref (registrar));
303
hud_app_menu_registrar_name_vanished (GDBusConnection *connection,
307
HudAppMenuRegistrar *registrar = user_data;
309
g_debug ("name vanished");
311
if (registrar->subscription > 0)
313
g_dbus_connection_signal_unsubscribe (connection, registrar->subscription);
314
registrar->subscription = 0;
317
if (registrar->cancellable)
319
g_cancellable_cancel (registrar->cancellable);
320
g_clear_object (®istrar->cancellable);
323
if (registrar->ready)
328
registrar->ready = FALSE;
330
g_hash_table_iter_init (&iter, registrar->windows);
331
while (g_hash_table_iter_next (&iter, NULL, &value))
333
HudAppMenuRegistrarWindow *window = value;
335
g_free (window->bus_name);
336
window->bus_name = NULL;
337
g_free (window->object_path);
338
window->object_path = NULL;
340
hud_app_menu_registrar_notify_window_observers (registrar, window);
342
/* Cannot go the normal route here because we are iterating... */
343
if (window->observers == NULL)
344
g_hash_table_iter_remove (&iter);
350
hud_app_menu_registrar_finalize (GObject *object)
352
/* This is an immortal singleton. If we're here, we have trouble. */
353
g_assert_not_reached ();
357
hud_app_menu_registrar_init (HudAppMenuRegistrar *registrar)
361
registrar->windows = g_hash_table_new_full (NULL, NULL, NULL, hud_app_menu_registrar_window_free);
362
g_bus_watch_name (G_BUS_TYPE_SESSION, APPMENU_REGISTRAR_BUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE,
363
hud_app_menu_registrar_name_appeared, hud_app_menu_registrar_name_vanished,
364
g_object_ref (registrar), g_object_unref);
368
hud_app_menu_registrar_class_init (HudAppMenuRegistrarClass *class)
370
class->finalize = hud_app_menu_registrar_finalize;
374
* HudAppMenuRegistrarObserverFunc:
375
* @registrar: the #HudAppMenuRegistrar
376
* @xid: the xid that we are notifying about
377
* @bus_name: the bus name for the dbusmenu, or %NULL
378
* @object_path: the object path for the dbusmenu, or %NULL
379
* @user_data: the data pointer
381
* Notifies about the initial values for or changes to the bus name and
382
* object path at which to find the dbusmenu for @xid.
384
* You should pass the values of @bus_name and @object_path to
385
* dbusmenu_client_new() to get started.
387
* If no menu is available then @bus_name and @object_path will both be
392
* hud_app_menu_registrar_add_observer:
393
* @registrar: the #HudAppMenuRegistrar
394
* @xid: the xid to begin observing
395
* @callback: a #HudAppMenuRegistrarObserverFunc
396
* @user_data: user data for @callback
398
* Begins observing @xid.
400
* @callback will be called exactly once before the function returns
401
* with a set of initial values (the bus name and object path at which
402
* to find the menu for the window).
404
* If the location of the menu for @xid changes (including being created
405
* or destroyed) then @callback will be called each time an update is
408
* It is possible that the values are not initially known because they
409
* have not yet been retreived from the registrar or because the
410
* registrar is not running. In this case, %NULL values will be
411
* provided initially and @callback will be invoked again when the
412
* real values are known.
414
* Call hud_app_menu_registrar_remove_observer() to when you are no
415
* longer interested in @xid.
418
hud_app_menu_registrar_add_observer (HudAppMenuRegistrar *registrar,
420
HudAppMenuRegistrarObserverFunc callback,
423
HudAppMenuRegistrarObserver *observer;
424
HudAppMenuRegistrarWindow *window;
426
g_return_if_fail (xid != 0);
427
g_return_if_fail (callback != NULL);
428
g_return_if_fail (!registrar->notifying);
430
g_debug ("observer added for xid %u (%p)", xid, user_data);
432
observer = g_slice_new (HudAppMenuRegistrarObserver);
433
observer->callback = callback;
434
observer->user_data = user_data;
436
window = hud_app_menu_registrar_get_window (registrar, xid);
437
window->observers = g_slist_prepend (window->observers, observer);
439
/* send the first update */
440
(* callback) (registrar, xid, window->bus_name, window->object_path, user_data);
444
* hud_app_menu_registrar_remove_observer:
445
* @registrar: the #HudAppMenuRegistrar
446
* @xid: the xid to begin observing
447
* @callback: a #HudAppMenuRegistrarObserverFunc
448
* @user_data: user data for @callback
450
* Reverses the effect of a previous call to
451
* hud_app_menu_registrar_add_observer().
453
* @callback and @user_data must be exactly equal to the values passed
456
* One call does not remove all instances of @callback and @user_data.
457
* You need to call this function the same number of times that you
458
* called hud_app_menu_registrar_add_observer().
461
hud_app_menu_registrar_remove_observer (HudAppMenuRegistrar *registrar,
463
HudAppMenuRegistrarObserverFunc callback,
466
HudAppMenuRegistrarWindow *window;
469
g_return_if_fail (xid != 0);
470
g_return_if_fail (callback != NULL);
471
g_return_if_fail (!registrar->notifying);
473
g_debug ("observer removed for xid %u (%p)", xid, user_data);
475
window = hud_app_menu_registrar_get_window (registrar, xid);
476
for (node = &window->observers; *node; node = &(*node)->next)
478
HudAppMenuRegistrarObserver *observer = (*node)->data;
480
if (observer->callback == callback && observer->user_data == user_data)
482
g_slice_free (HudAppMenuRegistrarObserver, observer);
483
*node = g_slist_delete_link (*node, *node);
488
hud_app_menu_registrar_possibly_free_window (registrar, window);
492
* hud_app_menu_registrar_get:
494
* Gets the singleton instance of #HudAppMenuRegistrar.
496
* Returns: (transfer none): the instance
498
HudAppMenuRegistrar *
499
hud_app_menu_registrar_get (void)
501
static HudAppMenuRegistrar *singleton;
504
singleton = g_object_new (HUD_TYPE_APP_MENU_REGISTRAR, NULL);