nexmon – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* |
2 | * Copyright © 2011 Canonical Ltd. |
||
3 | * |
||
4 | * This library is free software; you can redistribute it and/or modify |
||
5 | * it under the terms of the GNU Lesser General Public License as |
||
6 | * published by the Free Software Foundation; either version 2 of the |
||
7 | * licence, or (at your option) any later version. |
||
8 | * |
||
9 | * This library is distributed in the hope that it will be useful, but |
||
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
12 | * Lesser General Public License for more details. |
||
13 | * |
||
14 | * You should have received a copy of the GNU Lesser General Public |
||
15 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
||
16 | * |
||
17 | * Author: Ryan Lortie <desrt@desrt.ca> |
||
18 | */ |
||
19 | |||
20 | #include "config.h" |
||
21 | |||
22 | #include "gdbusmenumodel.h" |
||
23 | |||
24 | #include "gmenumodel.h" |
||
25 | |||
26 | /* Prelude {{{1 */ |
||
27 | |||
28 | /** |
||
29 | * SECTION:gdbusmenumodel |
||
30 | * @title: GDBusMenuModel |
||
31 | * @short_description: A D-Bus GMenuModel implementation |
||
32 | * @include: gio/gio.h |
||
33 | * @see_also: [GMenuModel Exporter][gio-GMenuModel-exporter] |
||
34 | * |
||
35 | * #GDBusMenuModel is an implementation of #GMenuModel that can be used |
||
36 | * as a proxy for a menu model that is exported over D-Bus with |
||
37 | * g_dbus_connection_export_menu_model(). |
||
38 | */ |
||
39 | |||
40 | /** |
||
41 | * GDBusMenuModel: |
||
42 | * |
||
43 | * #GDBusMenuModel is an opaque data structure and can only be accessed |
||
44 | * using the following functions. |
||
45 | */ |
||
46 | |||
47 | /* |
||
48 | * There are 3 main (quasi-)classes involved here: |
||
49 | * |
||
50 | * - GDBusMenuPath |
||
51 | * - GDBusMenuGroup |
||
52 | * - GDBusMenuModel |
||
53 | * |
||
54 | * Each of these classes exists as a parameterised singleton keyed to a |
||
55 | * particular thing: |
||
56 | * |
||
57 | * - GDBusMenuPath represents a D-Bus object path on a particular |
||
58 | * unique bus name on a particular GDBusConnection and in a |
||
59 | * particular GMainContext. |
||
60 | * |
||
61 | * - GDBusMenuGroup represents a particular group on a particular |
||
62 | * GDBusMenuPath. |
||
63 | * |
||
64 | * - GDBusMenuModel represents a particular menu within a particular |
||
65 | * GDBusMenuGroup. |
||
66 | * |
||
67 | * There are also two (and a half) utility structs: |
||
68 | * |
||
69 | * - PathIdentifier and ConstPathIdentifier |
||
70 | * - GDBusMenuModelItem |
||
71 | * |
||
72 | * PathIdentifier is the 4-tuple of (GMainContext, GDBusConnection, |
||
73 | * unique name, object path) that uniquely identifies a particular |
||
74 | * GDBusMenuPath. It holds ownership on each of these things, so we |
||
75 | * have a ConstPathIdentifier variant that does not. |
||
76 | * |
||
77 | * We have a 3-level hierarchy of hashtables: |
||
78 | * |
||
79 | * - a global hashtable (g_dbus_menu_paths) maps from PathIdentifier |
||
80 | * to GDBusMenuPath |
||
81 | * |
||
82 | * - each GDBusMenuPath has a hashtable mapping from guint (group |
||
83 | * number) to GDBusMenuGroup |
||
84 | * |
||
85 | * - each GDBusMenuGroup has a hashtable mapping from guint (menu |
||
86 | * number) to GDBusMenuModel. |
||
87 | * |
||
88 | * In this way, each quintuplet of (connection, bus name, object path, |
||
89 | * group id, menu id) maps to a single GDBusMenuModel instance that can be |
||
90 | * located via 3 hashtable lookups. |
||
91 | * |
||
92 | * All of the 3 classes are refcounted (GDBusMenuPath and |
||
93 | * GDBusMenuGroup manually, and GDBusMenuModel by virtue of being a |
||
94 | * GObject). The hashtables do not hold references -- rather, when the |
||
95 | * last reference is dropped, the object is removed from the hashtable. |
||
96 | * |
||
97 | * The hard references go in the other direction: GDBusMenuModel is created |
||
98 | * as the user requests it and only exists as long as the user holds a |
||
99 | * reference on it. GDBusMenuModel holds a reference on the GDBusMenuGroup |
||
100 | * from which it came. GDBusMenuGroup holds a reference on |
||
101 | * GDBusMenuPath. |
||
102 | * |
||
103 | * In addition to refcounts, each object has an 'active' variable (ints |
||
104 | * for GDBusMenuPath and GDBusMenuGroup, boolean for GDBusMenuModel). |
||
105 | * |
||
106 | * - GDBusMenuModel is inactive when created and becomes active only when |
||
107 | * first queried for information. This prevents extra work from |
||
108 | * happening just by someone acquiring a GDBusMenuModel (and not |
||
109 | * actually trying to display it yet). |
||
110 | * |
||
111 | * - The active count on GDBusMenuGroup is equal to the number of |
||
112 | * GDBusMenuModel instances in that group that are active. When the |
||
113 | * active count transitions from 0 to 1, the group calls the 'Start' |
||
114 | * method on the service to begin monitoring that group. When it |
||
115 | * drops from 1 to 0, the group calls the 'End' method to stop |
||
116 | * monitoring. |
||
117 | * |
||
118 | * - The active count on GDBusMenuPath is equal to the number of |
||
119 | * GDBusMenuGroup instances on that path with a non-zero active |
||
120 | * count. When the active count transitions from 0 to 1, the path |
||
121 | * sets up a signal subscription to monitor any changes. The signal |
||
122 | * subscription is taken down when the active count transitions from |
||
123 | * 1 to 0. |
||
124 | * |
||
125 | * When active, GDBusMenuPath gets incoming signals when changes occur. |
||
126 | * If the change signal mentions a group for which we currently have an |
||
127 | * active GDBusMenuGroup, the change signal is passed along to that |
||
128 | * group. If the group is inactive, the change signal is ignored. |
||
129 | * |
||
130 | * Most of the "work" occurs in GDBusMenuGroup. In addition to the |
||
131 | * hashtable of GDBusMenuModel instances, it keeps a hashtable of the actual |
||
132 | * menu contents, each encoded as GSequence of GDBusMenuModelItem. It |
||
133 | * initially populates this table with the results of the "Start" method |
||
134 | * call and then updates it according to incoming change signals. If |
||
135 | * the change signal mentions a menu for which we current have an active |
||
136 | * GDBusMenuModel, the change signal is passed along to that model. If the |
||
137 | * model is inactive, the change signal is ignored. |
||
138 | * |
||
139 | * GDBusMenuModelItem is just a pair of hashtables, one for the attributes |
||
140 | * and one for the links of the item. Both map strings to GVariant |
||
141 | * instances. In the case of links, the GVariant has type '(uu)' and is |
||
142 | * turned into a GDBusMenuModel at the point that the user pulls it through |
||
143 | * the API. |
||
144 | * |
||
145 | * Following the "empty is the same as non-existent" rule, the hashtable |
||
146 | * of GSequence of GDBusMenuModelItem holds NULL for empty menus. |
||
147 | * |
||
148 | * GDBusMenuModel contains very little functionality of its own. It holds a |
||
149 | * (weak) reference to the GSequence of GDBusMenuModelItem contained in the |
||
150 | * GDBusMenuGroup. It uses this GSequence to implement the GMenuModel |
||
151 | * interface. It also emits the "items-changed" signal if it is active |
||
152 | * and it was told that the contents of the GSequence changed. |
||
153 | */ |
||
154 | |||
155 | typedef struct _GDBusMenuGroup GDBusMenuGroup; |
||
156 | typedef struct _GDBusMenuPath GDBusMenuPath; |
||
157 | |||
158 | static void g_dbus_menu_group_changed (GDBusMenuGroup *group, |
||
159 | guint menu_id, |
||
160 | gint position, |
||
161 | gint removed, |
||
162 | GVariant *added); |
||
163 | static void g_dbus_menu_model_changed (GDBusMenuModel *proxy, |
||
164 | GSequence *items, |
||
165 | gint position, |
||
166 | gint removed, |
||
167 | gint added); |
||
168 | static GDBusMenuGroup * g_dbus_menu_group_get_from_path (GDBusMenuPath *path, |
||
169 | guint group_id); |
||
170 | static GDBusMenuModel * g_dbus_menu_model_get_from_group (GDBusMenuGroup *group, |
||
171 | guint menu_id); |
||
172 | |||
173 | /* PathIdentifier {{{1 */ |
||
174 | typedef struct |
||
175 | { |
||
176 | GMainContext *context; |
||
177 | GDBusConnection *connection; |
||
178 | gchar *bus_name; |
||
179 | gchar *object_path; |
||
180 | } PathIdentifier; |
||
181 | |||
182 | typedef const struct |
||
183 | { |
||
184 | GMainContext *context; |
||
185 | GDBusConnection *connection; |
||
186 | const gchar *bus_name; |
||
187 | const gchar *object_path; |
||
188 | } ConstPathIdentifier; |
||
189 | |||
190 | static guint |
||
191 | path_identifier_hash (gconstpointer data) |
||
192 | { |
||
193 | ConstPathIdentifier *id = data; |
||
194 | |||
195 | return g_str_hash (id->object_path); |
||
196 | } |
||
197 | |||
198 | static gboolean |
||
199 | path_identifier_equal (gconstpointer a, |
||
200 | gconstpointer b) |
||
201 | { |
||
202 | ConstPathIdentifier *id_a = a; |
||
203 | ConstPathIdentifier *id_b = b; |
||
204 | |||
205 | return id_a->connection == id_b->connection && |
||
206 | g_str_equal (id_a->bus_name, id_b->bus_name) && |
||
207 | g_str_equal (id_a->object_path, id_b->object_path); |
||
208 | } |
||
209 | |||
210 | static void |
||
211 | path_identifier_free (PathIdentifier *id) |
||
212 | { |
||
213 | g_main_context_unref (id->context); |
||
214 | g_object_unref (id->connection); |
||
215 | g_free (id->bus_name); |
||
216 | g_free (id->object_path); |
||
217 | |||
218 | g_slice_free (PathIdentifier, id); |
||
219 | } |
||
220 | |||
221 | static PathIdentifier * |
||
222 | path_identifier_new (ConstPathIdentifier *cid) |
||
223 | { |
||
224 | PathIdentifier *id; |
||
225 | |||
226 | id = g_slice_new (PathIdentifier); |
||
227 | id->context = g_main_context_ref (cid->context); |
||
228 | id->connection = g_object_ref (cid->connection); |
||
229 | id->bus_name = g_strdup (cid->bus_name); |
||
230 | id->object_path = g_strdup (cid->object_path); |
||
231 | |||
232 | return id; |
||
233 | } |
||
234 | |||
235 | /* GDBusMenuPath {{{1 */ |
||
236 | |||
237 | struct _GDBusMenuPath |
||
238 | { |
||
239 | PathIdentifier *id; |
||
240 | gint ref_count; |
||
241 | |||
242 | GHashTable *groups; |
||
243 | gint active; |
||
244 | guint watch_id; |
||
245 | }; |
||
246 | |||
247 | static GHashTable *g_dbus_menu_paths; |
||
248 | |||
249 | static GDBusMenuPath * |
||
250 | g_dbus_menu_path_ref (GDBusMenuPath *path) |
||
251 | { |
||
252 | path->ref_count++; |
||
253 | |||
254 | return path; |
||
255 | } |
||
256 | |||
257 | static void |
||
258 | g_dbus_menu_path_unref (GDBusMenuPath *path) |
||
259 | { |
||
260 | if (--path->ref_count == 0) |
||
261 | { |
||
262 | g_hash_table_remove (g_dbus_menu_paths, path->id); |
||
263 | g_hash_table_unref (path->groups); |
||
264 | path_identifier_free (path->id); |
||
265 | |||
266 | g_slice_free (GDBusMenuPath, path); |
||
267 | } |
||
268 | } |
||
269 | |||
270 | static void |
||
271 | g_dbus_menu_path_signal (GDBusConnection *connection, |
||
272 | const gchar *sender_name, |
||
273 | const gchar *object_path, |
||
274 | const gchar *interface_name, |
||
275 | const gchar *signal_name, |
||
276 | GVariant *parameters, |
||
277 | gpointer user_data) |
||
278 | { |
||
279 | GDBusMenuPath *path = user_data; |
||
280 | GVariantIter *iter; |
||
281 | guint group_id; |
||
282 | guint menu_id; |
||
283 | guint position; |
||
284 | guint removes; |
||
285 | GVariant *adds; |
||
286 | |||
287 | if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a(uuuuaa{sv}))"))) |
||
288 | return; |
||
289 | |||
290 | g_variant_get (parameters, "(a(uuuuaa{sv}))", &iter); |
||
291 | while (g_variant_iter_loop (iter, "(uuuu@aa{sv})", &group_id, &menu_id, &position, &removes, &adds)) |
||
292 | { |
||
293 | GDBusMenuGroup *group; |
||
294 | |||
295 | group = g_hash_table_lookup (path->groups, GINT_TO_POINTER (group_id)); |
||
296 | |||
297 | if (group != NULL) |
||
298 | g_dbus_menu_group_changed (group, menu_id, position, removes, adds); |
||
299 | } |
||
300 | g_variant_iter_free (iter); |
||
301 | } |
||
302 | |||
303 | static void |
||
304 | g_dbus_menu_path_activate (GDBusMenuPath *path) |
||
305 | { |
||
306 | if (path->active++ == 0) |
||
307 | path->watch_id = g_dbus_connection_signal_subscribe (path->id->connection, path->id->bus_name, |
||
308 | "org.gtk.Menus", "Changed", path->id->object_path, |
||
309 | NULL, G_DBUS_SIGNAL_FLAGS_NONE, |
||
310 | g_dbus_menu_path_signal, path, NULL); |
||
311 | } |
||
312 | |||
313 | static void |
||
314 | g_dbus_menu_path_deactivate (GDBusMenuPath *path) |
||
315 | { |
||
316 | if (--path->active == 0) |
||
317 | g_dbus_connection_signal_unsubscribe (path->id->connection, path->watch_id); |
||
318 | } |
||
319 | |||
320 | static GDBusMenuPath * |
||
321 | g_dbus_menu_path_get (GMainContext *context, |
||
322 | GDBusConnection *connection, |
||
323 | const gchar *bus_name, |
||
324 | const gchar *object_path) |
||
325 | { |
||
326 | ConstPathIdentifier cid = { context, connection, bus_name, object_path }; |
||
327 | GDBusMenuPath *path; |
||
328 | |||
329 | if (g_dbus_menu_paths == NULL) |
||
330 | g_dbus_menu_paths = g_hash_table_new (path_identifier_hash, path_identifier_equal); |
||
331 | |||
332 | path = g_hash_table_lookup (g_dbus_menu_paths, &cid); |
||
333 | |||
334 | if (path == NULL) |
||
335 | { |
||
336 | path = g_slice_new (GDBusMenuPath); |
||
337 | path->id = path_identifier_new (&cid); |
||
338 | path->groups = g_hash_table_new (NULL, NULL); |
||
339 | path->ref_count = 0; |
||
340 | path->active = 0; |
||
341 | |||
342 | g_hash_table_insert (g_dbus_menu_paths, path->id, path); |
||
343 | } |
||
344 | |||
345 | return g_dbus_menu_path_ref (path); |
||
346 | } |
||
347 | |||
348 | /* GDBusMenuGroup, GDBusMenuModelItem {{{1 */ |
||
349 | typedef enum |
||
350 | { |
||
351 | GROUP_OFFLINE, |
||
352 | GROUP_PENDING, |
||
353 | GROUP_ONLINE |
||
354 | } GroupStatus; |
||
355 | |||
356 | struct _GDBusMenuGroup |
||
357 | { |
||
358 | GDBusMenuPath *path; |
||
359 | guint id; |
||
360 | |||
361 | GHashTable *proxies; /* uint -> unowned GDBusMenuModel */ |
||
362 | GHashTable *menus; /* uint -> owned GSequence */ |
||
363 | gint ref_count; |
||
364 | GroupStatus state; |
||
365 | gint active; |
||
366 | }; |
||
367 | |||
368 | typedef struct |
||
369 | { |
||
370 | GHashTable *attributes; |
||
371 | GHashTable *links; |
||
372 | } GDBusMenuModelItem; |
||
373 | |||
374 | static GDBusMenuGroup * |
||
375 | g_dbus_menu_group_ref (GDBusMenuGroup *group) |
||
376 | { |
||
377 | group->ref_count++; |
||
378 | |||
379 | return group; |
||
380 | } |
||
381 | |||
382 | static void |
||
383 | g_dbus_menu_group_unref (GDBusMenuGroup *group) |
||
384 | { |
||
385 | if (--group->ref_count == 0) |
||
386 | { |
||
387 | g_assert (group->state == GROUP_OFFLINE); |
||
388 | g_assert (group->active == 0); |
||
389 | |||
390 | g_hash_table_remove (group->path->groups, GINT_TO_POINTER (group->id)); |
||
391 | g_hash_table_unref (group->proxies); |
||
392 | g_hash_table_unref (group->menus); |
||
393 | |||
394 | g_dbus_menu_path_unref (group->path); |
||
395 | |||
396 | g_slice_free (GDBusMenuGroup, group); |
||
397 | } |
||
398 | } |
||
399 | |||
400 | static void |
||
401 | g_dbus_menu_model_item_free (gpointer data) |
||
402 | { |
||
403 | GDBusMenuModelItem *item = data; |
||
404 | |||
405 | g_hash_table_unref (item->attributes); |
||
406 | g_hash_table_unref (item->links); |
||
407 | |||
408 | g_slice_free (GDBusMenuModelItem, item); |
||
409 | } |
||
410 | |||
411 | static GDBusMenuModelItem * |
||
412 | g_dbus_menu_group_create_item (GVariant *description) |
||
413 | { |
||
414 | GDBusMenuModelItem *item; |
||
415 | GVariantIter iter; |
||
416 | const gchar *key; |
||
417 | GVariant *value; |
||
418 | |||
419 | item = g_slice_new (GDBusMenuModelItem); |
||
420 | item->attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); |
||
421 | item->links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); |
||
422 | |||
423 | g_variant_iter_init (&iter, description); |
||
424 | while (g_variant_iter_loop (&iter, "{&sv}", &key, &value)) |
||
425 | if (key[0] == ':') |
||
426 | /* key + 1 to skip the ':' */ |
||
427 | g_hash_table_insert (item->links, g_strdup (key + 1), g_variant_ref (value)); |
||
428 | else |
||
429 | g_hash_table_insert (item->attributes, g_strdup (key), g_variant_ref (value)); |
||
430 | |||
431 | return item; |
||
432 | } |
||
433 | |||
434 | /* |
||
435 | * GDBusMenuGroup can be in three states: |
||
436 | * |
||
437 | * OFFLINE: not subscribed to this group |
||
438 | * PENDING: we made the call to subscribe to this group, but the result |
||
439 | * has not come back yet |
||
440 | * ONLINE: we are fully subscribed |
||
441 | * |
||
442 | * We can get into some nasty situations where we make a call due to an |
||
443 | * activation request but receive a deactivation request before the call |
||
444 | * returns. If another activation request occurs then we could risk |
||
445 | * sending a Start request even though one is already in progress. For |
||
446 | * this reason, we have to carefully consider what to do in each of the |
||
447 | * three states for each of the following situations: |
||
448 | * |
||
449 | * - activation requested |
||
450 | * - deactivation requested |
||
451 | * - Start call finishes |
||
452 | * |
||
453 | * To simplify things a bit, we do not have a callback for the Stop |
||
454 | * call. We just send it and assume that it takes effect immediately. |
||
455 | * |
||
456 | * Activation requested: |
||
457 | * OFFLINE: make the Start call and transition to PENDING |
||
458 | * PENDING: do nothing -- call is already in progress. |
||
459 | * ONLINE: this should not be possible |
||
460 | * |
||
461 | * Deactivation requested: |
||
462 | * OFFLINE: this should not be possible |
||
463 | * PENDING: do nothing -- handle it when the Start call finishes |
||
464 | * ONLINE: send the Stop call and move to OFFLINE immediately |
||
465 | * |
||
466 | * Start call finishes: |
||
467 | * OFFLINE: this should not be possible |
||
468 | * PENDING: |
||
469 | * If we should be active (ie: active count > 0): move to ONLINE |
||
470 | * If not: send Stop call and move to OFFLINE immediately |
||
471 | * ONLINE: this should not be possible |
||
472 | * |
||
473 | * We have to take care with regards to signal subscriptions (ie: |
||
474 | * activation of the GDBusMenuPath). The signal subscription is always |
||
475 | * established when transitioning from OFFLINE to PENDING and taken down |
||
476 | * when transitioning to OFFLINE (from either PENDING or ONLINE). |
||
477 | * |
||
478 | * Since there are two places where we transition to OFFLINE, we split |
||
479 | * that code out into a separate function. |
||
480 | */ |
||
481 | static void |
||
482 | g_dbus_menu_group_go_offline (GDBusMenuGroup *group) |
||
483 | { |
||
484 | g_dbus_menu_path_deactivate (group->path); |
||
485 | g_dbus_connection_call (group->path->id->connection, |
||
486 | group->path->id->bus_name, |
||
487 | group->path->id->object_path, |
||
488 | "org.gtk.Menus", "End", |
||
489 | g_variant_new_parsed ("([ %u ],)", group->id), |
||
490 | NULL, G_DBUS_CALL_FLAGS_NONE, -1, |
||
491 | NULL, NULL, NULL); |
||
492 | group->state = GROUP_OFFLINE; |
||
493 | } |
||
494 | |||
495 | |||
496 | static void |
||
497 | g_dbus_menu_group_start_ready (GObject *source_object, |
||
498 | GAsyncResult *result, |
||
499 | gpointer user_data) |
||
500 | { |
||
501 | GDBusConnection *connection = G_DBUS_CONNECTION (source_object); |
||
502 | GDBusMenuGroup *group = user_data; |
||
503 | GVariant *reply; |
||
504 | |||
505 | g_assert (group->state == GROUP_PENDING); |
||
506 | |||
507 | reply = g_dbus_connection_call_finish (connection, result, NULL); |
||
508 | |||
509 | if (group->active) |
||
510 | { |
||
511 | group->state = GROUP_ONLINE; |
||
512 | |||
513 | /* If we receive no reply, just act like we got an empty reply. */ |
||
514 | if (reply) |
||
515 | { |
||
516 | GVariantIter *iter; |
||
517 | GVariant *items; |
||
518 | guint group_id; |
||
519 | guint menu_id; |
||
520 | |||
521 | g_variant_get (reply, "(a(uuaa{sv}))", &iter); |
||
522 | while (g_variant_iter_loop (iter, "(uu@aa{sv})", &group_id, &menu_id, &items)) |
||
523 | if (group_id == group->id) |
||
524 | g_dbus_menu_group_changed (group, menu_id, 0, 0, items); |
||
525 | g_variant_iter_free (iter); |
||
526 | } |
||
527 | } |
||
528 | else |
||
529 | g_dbus_menu_group_go_offline (group); |
||
530 | |||
531 | if (reply) |
||
532 | g_variant_unref (reply); |
||
533 | |||
534 | g_dbus_menu_group_unref (group); |
||
535 | } |
||
536 | |||
537 | static void |
||
538 | g_dbus_menu_group_activate (GDBusMenuGroup *group) |
||
539 | { |
||
540 | if (group->active++ == 0) |
||
541 | { |
||
542 | g_assert (group->state != GROUP_ONLINE); |
||
543 | |||
544 | if (group->state == GROUP_OFFLINE) |
||
545 | { |
||
546 | g_dbus_menu_path_activate (group->path); |
||
547 | |||
548 | g_dbus_connection_call (group->path->id->connection, |
||
549 | group->path->id->bus_name, |
||
550 | group->path->id->object_path, |
||
551 | "org.gtk.Menus", "Start", |
||
552 | g_variant_new_parsed ("([ %u ],)", group->id), |
||
553 | G_VARIANT_TYPE ("(a(uuaa{sv}))"), |
||
554 | G_DBUS_CALL_FLAGS_NONE, -1, NULL, |
||
555 | g_dbus_menu_group_start_ready, |
||
556 | g_dbus_menu_group_ref (group)); |
||
557 | group->state = GROUP_PENDING; |
||
558 | } |
||
559 | } |
||
560 | } |
||
561 | |||
562 | static void |
||
563 | g_dbus_menu_group_deactivate (GDBusMenuGroup *group) |
||
564 | { |
||
565 | if (--group->active == 0) |
||
566 | { |
||
567 | g_assert (group->state != GROUP_OFFLINE); |
||
568 | |||
569 | if (group->state == GROUP_ONLINE) |
||
570 | { |
||
571 | /* We are here because nobody is watching, so just free |
||
572 | * everything and don't bother with the notifications. |
||
573 | */ |
||
574 | g_hash_table_remove_all (group->menus); |
||
575 | |||
576 | g_dbus_menu_group_go_offline (group); |
||
577 | } |
||
578 | } |
||
579 | } |
||
580 | |||
581 | static void |
||
582 | g_dbus_menu_group_changed (GDBusMenuGroup *group, |
||
583 | guint menu_id, |
||
584 | gint position, |
||
585 | gint removed, |
||
586 | GVariant *added) |
||
587 | { |
||
588 | GSequenceIter *point; |
||
589 | GVariantIter iter; |
||
590 | GDBusMenuModel *proxy; |
||
591 | GSequence *items; |
||
592 | GVariant *item; |
||
593 | gint n_added; |
||
594 | |||
595 | /* We could have signals coming to us when we're not active (due to |
||
596 | * some other process having subscribed to this group) or when we're |
||
597 | * pending. In both of those cases, we want to ignore the signal |
||
598 | * since we'll get our own information when we call "Start" for |
||
599 | * ourselves. |
||
600 | */ |
||
601 | if (group->state != GROUP_ONLINE) |
||
602 | return; |
||
603 | |||
604 | items = g_hash_table_lookup (group->menus, GINT_TO_POINTER (menu_id)); |
||
605 | |||
606 | if (items == NULL) |
||
607 | { |
||
608 | items = g_sequence_new (g_dbus_menu_model_item_free); |
||
609 | g_hash_table_insert (group->menus, GINT_TO_POINTER (menu_id), items); |
||
610 | } |
||
611 | |||
612 | point = g_sequence_get_iter_at_pos (items, position + removed); |
||
613 | |||
614 | g_return_if_fail (point != NULL); |
||
615 | |||
616 | if (removed) |
||
617 | { |
||
618 | GSequenceIter *start; |
||
619 | |||
620 | start = g_sequence_get_iter_at_pos (items, position); |
||
621 | g_sequence_remove_range (start, point); |
||
622 | } |
||
623 | |||
624 | n_added = g_variant_iter_init (&iter, added); |
||
625 | while (g_variant_iter_loop (&iter, "@a{sv}", &item)) |
||
626 | g_sequence_insert_before (point, g_dbus_menu_group_create_item (item)); |
||
627 | |||
628 | if (g_sequence_is_empty (items)) |
||
629 | { |
||
630 | g_hash_table_remove (group->menus, GINT_TO_POINTER (menu_id)); |
||
631 | items = NULL; |
||
632 | } |
||
633 | |||
634 | if ((proxy = g_hash_table_lookup (group->proxies, GINT_TO_POINTER (menu_id)))) |
||
635 | g_dbus_menu_model_changed (proxy, items, position, removed, n_added); |
||
636 | } |
||
637 | |||
638 | static GDBusMenuGroup * |
||
639 | g_dbus_menu_group_get_from_path (GDBusMenuPath *path, |
||
640 | guint group_id) |
||
641 | { |
||
642 | GDBusMenuGroup *group; |
||
643 | |||
644 | group = g_hash_table_lookup (path->groups, GINT_TO_POINTER (group_id)); |
||
645 | |||
646 | if (group == NULL) |
||
647 | { |
||
648 | group = g_slice_new (GDBusMenuGroup); |
||
649 | group->path = g_dbus_menu_path_ref (path); |
||
650 | group->id = group_id; |
||
651 | group->proxies = g_hash_table_new (NULL, NULL); |
||
652 | group->menus = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_sequence_free); |
||
653 | group->state = GROUP_OFFLINE; |
||
654 | group->active = 0; |
||
655 | group->ref_count = 0; |
||
656 | |||
657 | g_hash_table_insert (path->groups, GINT_TO_POINTER (group->id), group); |
||
658 | } |
||
659 | |||
660 | return g_dbus_menu_group_ref (group); |
||
661 | } |
||
662 | |||
663 | static GDBusMenuGroup * |
||
664 | g_dbus_menu_group_get (GMainContext *context, |
||
665 | GDBusConnection *connection, |
||
666 | const gchar *bus_name, |
||
667 | const gchar *object_path, |
||
668 | guint group_id) |
||
669 | { |
||
670 | GDBusMenuGroup *group; |
||
671 | GDBusMenuPath *path; |
||
672 | |||
673 | path = g_dbus_menu_path_get (context, connection, bus_name, object_path); |
||
674 | group = g_dbus_menu_group_get_from_path (path, group_id); |
||
675 | g_dbus_menu_path_unref (path); |
||
676 | |||
677 | return group; |
||
678 | } |
||
679 | |||
680 | /* GDBusMenuModel {{{1 */ |
||
681 | |||
682 | typedef GMenuModelClass GDBusMenuModelClass; |
||
683 | struct _GDBusMenuModel |
||
684 | { |
||
685 | GMenuModel parent; |
||
686 | |||
687 | GDBusMenuGroup *group; |
||
688 | guint id; |
||
689 | |||
690 | GSequence *items; /* unowned */ |
||
691 | gboolean active; |
||
692 | }; |
||
693 | |||
694 | G_DEFINE_TYPE (GDBusMenuModel, g_dbus_menu_model, G_TYPE_MENU_MODEL) |
||
695 | |||
696 | static gboolean |
||
697 | g_dbus_menu_model_is_mutable (GMenuModel *model) |
||
698 | { |
||
699 | return TRUE; |
||
700 | } |
||
701 | |||
702 | static gint |
||
703 | g_dbus_menu_model_get_n_items (GMenuModel *model) |
||
704 | { |
||
705 | GDBusMenuModel *proxy = G_DBUS_MENU_MODEL (model); |
||
706 | |||
707 | if (!proxy->active) |
||
708 | { |
||
709 | g_dbus_menu_group_activate (proxy->group); |
||
710 | proxy->active = TRUE; |
||
711 | } |
||
712 | |||
713 | return proxy->items ? g_sequence_get_length (proxy->items) : 0; |
||
714 | } |
||
715 | |||
716 | static void |
||
717 | g_dbus_menu_model_get_item_attributes (GMenuModel *model, |
||
718 | gint item_index, |
||
719 | GHashTable **table) |
||
720 | { |
||
721 | GDBusMenuModel *proxy = G_DBUS_MENU_MODEL (model); |
||
722 | GDBusMenuModelItem *item; |
||
723 | GSequenceIter *iter; |
||
724 | |||
725 | g_return_if_fail (proxy->active); |
||
726 | g_return_if_fail (proxy->items); |
||
727 | |||
728 | iter = g_sequence_get_iter_at_pos (proxy->items, item_index); |
||
729 | g_return_if_fail (iter); |
||
730 | |||
731 | item = g_sequence_get (iter); |
||
732 | g_return_if_fail (item); |
||
733 | |||
734 | *table = g_hash_table_ref (item->attributes); |
||
735 | } |
||
736 | |||
737 | static void |
||
738 | g_dbus_menu_model_get_item_links (GMenuModel *model, |
||
739 | gint item_index, |
||
740 | GHashTable **table) |
||
741 | { |
||
742 | GDBusMenuModel *proxy = G_DBUS_MENU_MODEL (model); |
||
743 | GDBusMenuModelItem *item; |
||
744 | GSequenceIter *iter; |
||
745 | |||
746 | g_return_if_fail (proxy->active); |
||
747 | g_return_if_fail (proxy->items); |
||
748 | |||
749 | iter = g_sequence_get_iter_at_pos (proxy->items, item_index); |
||
750 | g_return_if_fail (iter); |
||
751 | |||
752 | item = g_sequence_get (iter); |
||
753 | g_return_if_fail (item); |
||
754 | |||
755 | *table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); |
||
756 | |||
757 | { |
||
758 | GHashTableIter tmp; |
||
759 | gpointer key; |
||
760 | gpointer value; |
||
761 | |||
762 | g_hash_table_iter_init (&tmp, item->links); |
||
763 | while (g_hash_table_iter_next (&tmp, &key, &value)) |
||
764 | { |
||
765 | if (g_variant_is_of_type (value, G_VARIANT_TYPE ("(uu)"))) |
||
766 | { |
||
767 | guint group_id, menu_id; |
||
768 | GDBusMenuGroup *group; |
||
769 | GDBusMenuModel *link; |
||
770 | |||
771 | g_variant_get (value, "(uu)", &group_id, &menu_id); |
||
772 | |||
773 | /* save the hash lookup in a relatively common case */ |
||
774 | if (proxy->group->id != group_id) |
||
775 | group = g_dbus_menu_group_get_from_path (proxy->group->path, group_id); |
||
776 | else |
||
777 | group = g_dbus_menu_group_ref (proxy->group); |
||
778 | |||
779 | link = g_dbus_menu_model_get_from_group (group, menu_id); |
||
780 | |||
781 | g_hash_table_insert (*table, g_strdup (key), link); |
||
782 | |||
783 | g_dbus_menu_group_unref (group); |
||
784 | } |
||
785 | } |
||
786 | } |
||
787 | } |
||
788 | |||
789 | static void |
||
790 | g_dbus_menu_model_finalize (GObject *object) |
||
791 | { |
||
792 | GDBusMenuModel *proxy = G_DBUS_MENU_MODEL (object); |
||
793 | |||
794 | if (proxy->active) |
||
795 | g_dbus_menu_group_deactivate (proxy->group); |
||
796 | |||
797 | g_hash_table_remove (proxy->group->proxies, GINT_TO_POINTER (proxy->id)); |
||
798 | g_dbus_menu_group_unref (proxy->group); |
||
799 | |||
800 | G_OBJECT_CLASS (g_dbus_menu_model_parent_class) |
||
801 | ->finalize (object); |
||
802 | } |
||
803 | |||
804 | static void |
||
805 | g_dbus_menu_model_init (GDBusMenuModel *proxy) |
||
806 | { |
||
807 | } |
||
808 | |||
809 | static void |
||
810 | g_dbus_menu_model_class_init (GDBusMenuModelClass *class) |
||
811 | { |
||
812 | GObjectClass *object_class = G_OBJECT_CLASS (class); |
||
813 | |||
814 | class->is_mutable = g_dbus_menu_model_is_mutable; |
||
815 | class->get_n_items = g_dbus_menu_model_get_n_items; |
||
816 | class->get_item_attributes = g_dbus_menu_model_get_item_attributes; |
||
817 | class->get_item_links = g_dbus_menu_model_get_item_links; |
||
818 | |||
819 | object_class->finalize = g_dbus_menu_model_finalize; |
||
820 | } |
||
821 | |||
822 | static void |
||
823 | g_dbus_menu_model_changed (GDBusMenuModel *proxy, |
||
824 | GSequence *items, |
||
825 | gint position, |
||
826 | gint removed, |
||
827 | gint added) |
||
828 | { |
||
829 | proxy->items = items; |
||
830 | |||
831 | if (proxy->active && (removed || added)) |
||
832 | g_menu_model_items_changed (G_MENU_MODEL (proxy), position, removed, added); |
||
833 | } |
||
834 | |||
835 | static GDBusMenuModel * |
||
836 | g_dbus_menu_model_get_from_group (GDBusMenuGroup *group, |
||
837 | guint menu_id) |
||
838 | { |
||
839 | GDBusMenuModel *proxy; |
||
840 | |||
841 | proxy = g_hash_table_lookup (group->proxies, GINT_TO_POINTER (menu_id)); |
||
842 | if (proxy) |
||
843 | g_object_ref (proxy); |
||
844 | |||
845 | if (proxy == NULL) |
||
846 | { |
||
847 | proxy = g_object_new (G_TYPE_DBUS_MENU_MODEL, NULL); |
||
848 | proxy->items = g_hash_table_lookup (group->menus, GINT_TO_POINTER (menu_id)); |
||
849 | g_hash_table_insert (group->proxies, GINT_TO_POINTER (menu_id), proxy); |
||
850 | proxy->group = g_dbus_menu_group_ref (group); |
||
851 | proxy->id = menu_id; |
||
852 | } |
||
853 | |||
854 | return proxy; |
||
855 | } |
||
856 | |||
857 | /** |
||
858 | * g_dbus_menu_model_get: |
||
859 | * @connection: a #GDBusConnection |
||
860 | * @bus_name: the bus name which exports the menu model |
||
861 | * @object_path: the object path at which the menu model is exported |
||
862 | * |
||
863 | * Obtains a #GDBusMenuModel for the menu model which is exported |
||
864 | * at the given @bus_name and @object_path. |
||
865 | * |
||
866 | * The thread default main context is taken at the time of this call. |
||
867 | * All signals on the menu model (and any linked models) are reported |
||
868 | * with respect to this context. All calls on the returned menu model |
||
869 | * (and linked models) must also originate from this same context, with |
||
870 | * the thread default main context unchanged. |
||
871 | * |
||
872 | * Returns: (transfer full): a #GDBusMenuModel object. Free with |
||
873 | * g_object_unref(). |
||
874 | * |
||
875 | * Since: 2.32 |
||
876 | */ |
||
877 | GDBusMenuModel * |
||
878 | g_dbus_menu_model_get (GDBusConnection *connection, |
||
879 | const gchar *bus_name, |
||
880 | const gchar *object_path) |
||
881 | { |
||
882 | GDBusMenuGroup *group; |
||
883 | GDBusMenuModel *proxy; |
||
884 | GMainContext *context; |
||
885 | |||
886 | context = g_main_context_get_thread_default (); |
||
887 | if (context == NULL) |
||
888 | context = g_main_context_default (); |
||
889 | |||
890 | group = g_dbus_menu_group_get (context, connection, bus_name, object_path, 0); |
||
891 | proxy = g_dbus_menu_model_get_from_group (group, 0); |
||
892 | g_dbus_menu_group_unref (group); |
||
893 | |||
894 | return proxy; |
||
895 | } |
||
896 | |||
897 | #if 0 |
||
898 | static void |
||
899 | dump_proxy (gpointer key, gpointer value, gpointer data) |
||
900 | { |
||
901 | GDBusMenuModel *proxy = value; |
||
902 | |||
903 | g_print (" menu %d refcount %d active %d\n", |
||
904 | proxy->id, G_OBJECT (proxy)->ref_count, proxy->active); |
||
905 | } |
||
906 | |||
907 | static void |
||
908 | dump_group (gpointer key, gpointer value, gpointer data) |
||
909 | { |
||
910 | GDBusMenuGroup *group = value; |
||
911 | |||
912 | g_print (" group %d refcount %d state %d active %d\n", |
||
913 | group->id, group->ref_count, group->state, group->active); |
||
914 | |||
915 | g_hash_table_foreach (group->proxies, dump_proxy, NULL); |
||
916 | } |
||
917 | |||
918 | static void |
||
919 | dump_path (gpointer key, gpointer value, gpointer data) |
||
920 | { |
||
921 | PathIdentifier *pid = key; |
||
922 | GDBusMenuPath *path = value; |
||
923 | |||
924 | g_print ("%s active %d\n", pid->object_path, path->active); |
||
925 | g_hash_table_foreach (path->groups, dump_group, NULL); |
||
926 | } |
||
927 | |||
928 | void |
||
929 | g_dbus_menu_model_dump (void) |
||
930 | { |
||
931 | g_hash_table_foreach (g_dbus_menu_paths, dump_path, NULL); |
||
932 | } |
||
933 | |||
934 | #endif |
||
935 | |||
936 | /* Epilogue {{{1 */ |
||
937 | /* vim:set foldmethod=marker: */ |