Improvde #include order consistency
[glib.git] / gio / gdbusmenumodel.c
blobc437dcf28fa45bdcb99693e5d20aa4abc954541f
1 /*
2 * Copyright © 2011 Canonical Ltd.
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.
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.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
19 * Author: Ryan Lortie <desrt@desrt.ca>
22 #include "config.h"
24 #include "gdbusmenumodel.h"
26 #include "gmenumodel.h"
28 /* Prelude {{{1 */
30 /**
31 * SECTION:gdbusmenumodel
32 * @title: GDBusMenuModel
33 * @short_description: A D-Bus GMenuModel implementation
34 * @see_also: <link linkend="gio-GMenuModel-exporter">GMenuModel Exporter</link>
36 * #GDBusMenuModel is an implementation of #GMenuModel that can be used
37 * as a proxy for a menu model that is exported over D-Bus with
38 * g_dbus_connection_export_menu_model().
42 * There are 3 main (quasi-)classes involved here:
44 * - GDBusMenuPath
45 * - GDBusMenuGroup
46 * - GDBusMenuModel
48 * Each of these classes exists as a parameterised singleton keyed to a
49 * particular thing:
51 * - GDBusMenuPath represents a D-Bus object path on a particular
52 * unique bus name on a particular GDBusConnection and in a
53 * particular GMainContext.
55 * - GDBusMenuGroup represents a particular group on a particular
56 * GDBusMenuPath.
58 * - GDBusMenuModel represents a particular menu within a particular
59 * GDBusMenuGroup.
61 * There are also two (and a half) utility structs:
63 * - PathIdentifier and ConstPathIdentifier
64 * - GDBusMenuModelItem
66 * PathIdentifier is the 4-tuple of (GMainContext, GDBusConnection,
67 * unique name, object path) that uniquely identifies a particular
68 * GDBusMenuPath. It holds ownership on each of these things, so we
69 * have a ConstPathIdentifier variant that does not.
71 * We have a 3-level hierarchy of hashtables:
73 * - a global hashtable (g_dbus_menu_paths) maps from PathIdentifier
74 * to GDBusMenuPath
76 * - each GDBusMenuPath has a hashtable mapping from guint (group
77 * number) to GDBusMenuGroup
79 * - each GDBusMenuGroup has a hashtable mapping from guint (menu
80 * number) to GDBusMenuModel.
82 * In this way, each quintuplet of (connection, bus name, object path,
83 * group id, menu id) maps to a single GDBusMenuModel instance that can be
84 * located via 3 hashtable lookups.
86 * All of the 3 classes are refcounted (GDBusMenuPath and
87 * GDBusMenuGroup manually, and GDBusMenuModel by virtue of being a
88 * GObject). The hashtables do not hold references -- rather, when the
89 * last reference is dropped, the object is removed from the hashtable.
91 * The hard references go in the other direction: GDBusMenuModel is created
92 * as the user requests it and only exists as long as the user holds a
93 * reference on it. GDBusMenuModel holds a reference on the GDBusMenuGroup
94 * from which it came. GDBusMenuGroup holds a reference on
95 * GDBusMenuPath.
97 * In addition to refcounts, each object has an 'active' variable (ints
98 * for GDBusMenuPath and GDBusMenuGroup, boolean for GDBusMenuModel).
100 * - GDBusMenuModel is inactive when created and becomes active only when
101 * first queried for information. This prevents extra work from
102 * happening just by someone acquiring a GDBusMenuModel (and not
103 * actually trying to display it yet).
105 * - The active count on GDBusMenuGroup is equal to the number of
106 * GDBusMenuModel instances in that group that are active. When the
107 * active count transitions from 0 to 1, the group calls the 'Start'
108 * method on the service to begin monitoring that group. When it
109 * drops from 1 to 0, the group calls the 'End' method to stop
110 * monitoring.
112 * - The active count on GDBusMenuPath is equal to the number of
113 * GDBusMenuGroup instances on that path with a non-zero active
114 * count. When the active count transitions from 0 to 1, the path
115 * sets up a signal subscription to monitor any changes. The signal
116 * subscription is taken down when the active count transitions from
117 * 1 to 0.
119 * When active, GDBusMenuPath gets incoming signals when changes occur.
120 * If the change signal mentions a group for which we currently have an
121 * active GDBusMenuGroup, the change signal is passed along to that
122 * group. If the group is inactive, the change signal is ignored.
124 * Most of the "work" occurs in GDBusMenuGroup. In addition to the
125 * hashtable of GDBusMenuModel instances, it keeps a hashtable of the actual
126 * menu contents, each encoded as GSequence of GDBusMenuModelItem. It
127 * initially populates this table with the results of the "Start" method
128 * call and then updates it according to incoming change signals. If
129 * the change signal mentions a menu for which we current have an active
130 * GDBusMenuModel, the change signal is passed along to that model. If the
131 * model is inactive, the change signal is ignored.
133 * GDBusMenuModelItem is just a pair of hashtables, one for the attributes
134 * and one for the links of the item. Both map strings to GVariant
135 * instances. In the case of links, the GVariant has type '(uu)' and is
136 * turned into a GDBusMenuModel at the point that the user pulls it through
137 * the API.
139 * Following the "empty is the same as non-existent" rule, the hashtable
140 * of GSequence of GDBusMenuModelItem holds NULL for empty menus.
142 * GDBusMenuModel contains very little functionality of its own. It holds a
143 * (weak) reference to the GSequence of GDBusMenuModelItem contained in the
144 * GDBusMenuGroup. It uses this GSequence to implement the GMenuModel
145 * interface. It also emits the "items-changed" signal if it is active
146 * and it was told that the contents of the GSequence changed.
149 typedef struct _GDBusMenuGroup GDBusMenuGroup;
150 typedef struct _GDBusMenuPath GDBusMenuPath;
152 static void g_dbus_menu_group_changed (GDBusMenuGroup *group,
153 guint menu_id,
154 gint position,
155 gint removed,
156 GVariant *added);
157 static void g_dbus_menu_model_changed (GDBusMenuModel *proxy,
158 GSequence *items,
159 gint position,
160 gint removed,
161 gint added);
162 static GDBusMenuGroup * g_dbus_menu_group_get_from_path (GDBusMenuPath *path,
163 guint group_id);
164 static GDBusMenuModel * g_dbus_menu_model_get_from_group (GDBusMenuGroup *group,
165 guint menu_id);
167 /* PathIdentifier {{{1 */
168 typedef struct
170 GMainContext *context;
171 GDBusConnection *connection;
172 gchar *bus_name;
173 gchar *object_path;
174 } PathIdentifier;
176 typedef const struct
178 GMainContext *context;
179 GDBusConnection *connection;
180 const gchar *bus_name;
181 const gchar *object_path;
182 } ConstPathIdentifier;
184 static guint
185 path_identifier_hash (gconstpointer data)
187 ConstPathIdentifier *id = data;
189 return g_str_hash (id->object_path);
192 static gboolean
193 path_identifier_equal (gconstpointer a,
194 gconstpointer b)
196 ConstPathIdentifier *id_a = a;
197 ConstPathIdentifier *id_b = b;
199 return id_a->connection == id_b->connection &&
200 g_str_equal (id_a->bus_name, id_b->bus_name) &&
201 g_str_equal (id_a->object_path, id_b->object_path);
204 static void
205 path_identifier_free (PathIdentifier *id)
207 g_main_context_unref (id->context);
208 g_object_unref (id->connection);
209 g_free (id->bus_name);
210 g_free (id->object_path);
212 g_slice_free (PathIdentifier, id);
215 static PathIdentifier *
216 path_identifier_new (ConstPathIdentifier *cid)
218 PathIdentifier *id;
220 id = g_slice_new (PathIdentifier);
221 id->context = g_main_context_ref (cid->context);
222 id->connection = g_object_ref (cid->connection);
223 id->bus_name = g_strdup (cid->bus_name);
224 id->object_path = g_strdup (cid->object_path);
226 return id;
229 /* GDBusMenuPath {{{1 */
231 struct _GDBusMenuPath
233 PathIdentifier *id;
234 gint ref_count;
236 GHashTable *groups;
237 gint active;
238 guint watch_id;
241 static GHashTable *g_dbus_menu_paths;
243 static GDBusMenuPath *
244 g_dbus_menu_path_ref (GDBusMenuPath *path)
246 path->ref_count++;
248 return path;
251 static void
252 g_dbus_menu_path_unref (GDBusMenuPath *path)
254 if (--path->ref_count == 0)
256 g_hash_table_remove (g_dbus_menu_paths, path->id);
257 g_hash_table_unref (path->groups);
258 path_identifier_free (path->id);
260 g_slice_free (GDBusMenuPath, path);
264 static void
265 g_dbus_menu_path_signal (GDBusConnection *connection,
266 const gchar *sender_name,
267 const gchar *object_path,
268 const gchar *interface_name,
269 const gchar *signal_name,
270 GVariant *parameters,
271 gpointer user_data)
273 GDBusMenuPath *path = user_data;
274 GVariantIter *iter;
275 guint group_id;
276 guint menu_id;
277 guint position;
278 guint removes;
279 GVariant *adds;
281 if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a(uuuuaa{sv}))")))
282 return;
284 g_variant_get (parameters, "(a(uuuuaa{sv}))", &iter);
285 while (g_variant_iter_loop (iter, "(uuuu@aa{sv})", &group_id, &menu_id, &position, &removes, &adds))
287 GDBusMenuGroup *group;
289 group = g_hash_table_lookup (path->groups, GINT_TO_POINTER (group_id));
291 if (group != NULL)
292 g_dbus_menu_group_changed (group, menu_id, position, removes, adds);
294 g_variant_iter_free (iter);
297 static void
298 g_dbus_menu_path_activate (GDBusMenuPath *path)
300 if (path->active++ == 0)
301 path->watch_id = g_dbus_connection_signal_subscribe (path->id->connection, path->id->bus_name,
302 "org.gtk.Menus", "Changed", path->id->object_path,
303 NULL, G_DBUS_SIGNAL_FLAGS_NONE,
304 g_dbus_menu_path_signal, path, NULL);
307 static void
308 g_dbus_menu_path_deactivate (GDBusMenuPath *path)
310 if (--path->active == 0)
311 g_dbus_connection_signal_unsubscribe (path->id->connection, path->watch_id);
314 static GDBusMenuPath *
315 g_dbus_menu_path_get (GMainContext *context,
316 GDBusConnection *connection,
317 const gchar *bus_name,
318 const gchar *object_path)
320 ConstPathIdentifier cid = { context, connection, bus_name, object_path };
321 GDBusMenuPath *path;
323 if (g_dbus_menu_paths == NULL)
324 g_dbus_menu_paths = g_hash_table_new (path_identifier_hash, path_identifier_equal);
326 path = g_hash_table_lookup (g_dbus_menu_paths, &cid);
328 if (path == NULL)
330 path = g_slice_new (GDBusMenuPath);
331 path->id = path_identifier_new (&cid);
332 path->groups = g_hash_table_new (NULL, NULL);
333 path->ref_count = 0;
334 path->active = 0;
336 g_hash_table_insert (g_dbus_menu_paths, path->id, path);
339 return g_dbus_menu_path_ref (path);
342 /* GDBusMenuGroup, GDBusMenuModelItem {{{1 */
343 typedef enum
345 GROUP_OFFLINE,
346 GROUP_PENDING,
347 GROUP_ONLINE
348 } GroupStatus;
350 struct _GDBusMenuGroup
352 GDBusMenuPath *path;
353 guint id;
355 GHashTable *proxies; /* uint -> unowned GDBusMenuModel */
356 GHashTable *menus; /* uint -> owned GSequence */
357 gint ref_count;
358 GroupStatus state;
359 gint active;
362 typedef struct
364 GHashTable *attributes;
365 GHashTable *links;
366 } GDBusMenuModelItem;
368 static GDBusMenuGroup *
369 g_dbus_menu_group_ref (GDBusMenuGroup *group)
371 group->ref_count++;
373 return group;
376 static void
377 g_dbus_menu_group_unref (GDBusMenuGroup *group)
379 if (--group->ref_count == 0)
381 g_assert (group->state == GROUP_OFFLINE);
382 g_assert (group->active == 0);
384 g_hash_table_remove (group->path->groups, GINT_TO_POINTER (group->id));
385 g_hash_table_unref (group->proxies);
386 g_hash_table_unref (group->menus);
388 g_dbus_menu_path_unref (group->path);
390 g_slice_free (GDBusMenuGroup, group);
394 static void
395 g_dbus_menu_model_item_free (gpointer data)
397 GDBusMenuModelItem *item = data;
399 g_hash_table_unref (item->attributes);
400 g_hash_table_unref (item->links);
402 g_slice_free (GDBusMenuModelItem, item);
405 static GDBusMenuModelItem *
406 g_dbus_menu_group_create_item (GVariant *description)
408 GDBusMenuModelItem *item;
409 GVariantIter iter;
410 const gchar *key;
411 GVariant *value;
413 item = g_slice_new (GDBusMenuModelItem);
414 item->attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
415 item->links = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
417 g_variant_iter_init (&iter, description);
418 while (g_variant_iter_loop (&iter, "{&sv}", &key, &value))
419 if (key[0] == ':')
420 /* key + 1 to skip the ':' */
421 g_hash_table_insert (item->links, g_strdup (key + 1), g_variant_ref (value));
422 else
423 g_hash_table_insert (item->attributes, g_strdup (key), g_variant_ref (value));
425 return item;
429 * GDBusMenuGroup can be in three states:
431 * OFFLINE: not subscribed to this group
432 * PENDING: we made the call to subscribe to this group, but the result
433 * has not come back yet
434 * ONLINE: we are fully subscribed
436 * We can get into some nasty situations where we make a call due to an
437 * activation request but receive a deactivation request before the call
438 * returns. If another activation request occurs then we could risk
439 * sending a Start request even though one is already in progress. For
440 * this reason, we have to carefully consider what to do in each of the
441 * three states for each of the following situations:
443 * - activation requested
444 * - deactivation requested
445 * - Start call finishes
447 * To simplify things a bit, we do not have a callback for the Stop
448 * call. We just send it and assume that it takes effect immediately.
450 * Activation requested:
451 * OFFLINE: make the Start call and transition to PENDING
452 * PENDING: do nothing -- call is already in progress.
453 * ONLINE: this should not be possible
455 * Deactivation requested:
456 * OFFLINE: this should not be possible
457 * PENDING: do nothing -- handle it when the Start call finishes
458 * ONLINE: send the Stop call and move to OFFLINE immediately
460 * Start call finishes:
461 * OFFLINE: this should not be possible
462 * PENDING:
463 * If we should be active (ie: active count > 0): move to ONLINE
464 * If not: send Stop call and move to OFFLINE immediately
465 * ONLINE: this should not be possible
467 * We have to take care with regards to signal subscriptions (ie:
468 * activation of the GDBusMenuPath). The signal subscription is always
469 * established when transitioning from OFFLINE to PENDING and taken down
470 * when transitioning to OFFLINE (from either PENDING or ONLINE).
472 * Since there are two places where we transition to OFFLINE, we split
473 * that code out into a separate function.
475 static void
476 g_dbus_menu_group_go_offline (GDBusMenuGroup *group)
478 g_dbus_menu_path_deactivate (group->path);
479 g_dbus_connection_call (group->path->id->connection,
480 group->path->id->bus_name,
481 group->path->id->object_path,
482 "org.gtk.Menus", "End",
483 g_variant_new_parsed ("([ %u ],)", group->id),
484 NULL, G_DBUS_CALL_FLAGS_NONE, -1,
485 NULL, NULL, NULL);
486 group->state = GROUP_OFFLINE;
490 static void
491 g_dbus_menu_group_start_ready (GObject *source_object,
492 GAsyncResult *result,
493 gpointer user_data)
495 GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
496 GDBusMenuGroup *group = user_data;
497 GVariant *reply;
499 g_assert (group->state == GROUP_PENDING);
501 reply = g_dbus_connection_call_finish (connection, result, NULL);
503 if (group->active)
505 group->state = GROUP_ONLINE;
507 /* If we receive no reply, just act like we got an empty reply. */
508 if (reply)
510 GVariantIter *iter;
511 GVariant *items;
512 guint group_id;
513 guint menu_id;
515 g_variant_get (reply, "(a(uuaa{sv}))", &iter);
516 while (g_variant_iter_loop (iter, "(uu@aa{sv})", &group_id, &menu_id, &items))
517 if (group_id == group->id)
518 g_dbus_menu_group_changed (group, menu_id, 0, 0, items);
519 g_variant_iter_free (iter);
522 else
523 g_dbus_menu_group_go_offline (group);
525 if (reply)
526 g_variant_unref (reply);
528 g_dbus_menu_group_unref (group);
531 static void
532 g_dbus_menu_group_activate (GDBusMenuGroup *group)
534 if (group->active++ == 0)
536 g_assert (group->state != GROUP_ONLINE);
538 if (group->state == GROUP_OFFLINE)
540 g_dbus_menu_path_activate (group->path);
542 g_dbus_connection_call (group->path->id->connection,
543 group->path->id->bus_name,
544 group->path->id->object_path,
545 "org.gtk.Menus", "Start",
546 g_variant_new_parsed ("([ %u ],)", group->id),
547 G_VARIANT_TYPE ("(a(uuaa{sv}))"),
548 G_DBUS_CALL_FLAGS_NONE, -1, NULL,
549 g_dbus_menu_group_start_ready,
550 g_dbus_menu_group_ref (group));
551 group->state = GROUP_PENDING;
556 static void
557 g_dbus_menu_group_deactivate (GDBusMenuGroup *group)
559 if (--group->active == 0)
561 g_assert (group->state != GROUP_OFFLINE);
563 if (group->state == GROUP_ONLINE)
565 /* We are here because nobody is watching, so just free
566 * everything and don't bother with the notifications.
568 g_hash_table_remove_all (group->menus);
570 g_dbus_menu_group_go_offline (group);
575 static void
576 g_dbus_menu_group_changed (GDBusMenuGroup *group,
577 guint menu_id,
578 gint position,
579 gint removed,
580 GVariant *added)
582 GSequenceIter *point;
583 GVariantIter iter;
584 GDBusMenuModel *proxy;
585 GSequence *items;
586 GVariant *item;
587 gint n_added;
589 /* We could have signals coming to us when we're not active (due to
590 * some other process having subscribed to this group) or when we're
591 * pending. In both of those cases, we want to ignore the signal
592 * since we'll get our own information when we call "Start" for
593 * ourselves.
595 if (group->state != GROUP_ONLINE)
596 return;
598 items = g_hash_table_lookup (group->menus, GINT_TO_POINTER (menu_id));
600 if (items == NULL)
602 items = g_sequence_new (g_dbus_menu_model_item_free);
603 g_hash_table_insert (group->menus, GINT_TO_POINTER (menu_id), items);
606 point = g_sequence_get_iter_at_pos (items, position + removed);
608 g_return_if_fail (point != NULL);
610 if (removed)
612 GSequenceIter *start;
614 start = g_sequence_get_iter_at_pos (items, position);
615 g_sequence_remove_range (start, point);
618 n_added = g_variant_iter_init (&iter, added);
619 while (g_variant_iter_loop (&iter, "@a{sv}", &item))
620 g_sequence_insert_before (point, g_dbus_menu_group_create_item (item));
622 if (g_sequence_get_length (items) == 0)
624 g_hash_table_remove (group->menus, GINT_TO_POINTER (menu_id));
625 items = NULL;
628 if ((proxy = g_hash_table_lookup (group->proxies, GINT_TO_POINTER (menu_id))))
629 g_dbus_menu_model_changed (proxy, items, position, removed, n_added);
632 static GDBusMenuGroup *
633 g_dbus_menu_group_get_from_path (GDBusMenuPath *path,
634 guint group_id)
636 GDBusMenuGroup *group;
638 group = g_hash_table_lookup (path->groups, GINT_TO_POINTER (group_id));
640 if (group == NULL)
642 group = g_slice_new (GDBusMenuGroup);
643 group->path = g_dbus_menu_path_ref (path);
644 group->id = group_id;
645 group->proxies = g_hash_table_new (NULL, NULL);
646 group->menus = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_sequence_free);
647 group->state = GROUP_OFFLINE;
648 group->active = 0;
649 group->ref_count = 0;
651 g_hash_table_insert (path->groups, GINT_TO_POINTER (group->id), group);
654 return g_dbus_menu_group_ref (group);
657 static GDBusMenuGroup *
658 g_dbus_menu_group_get (GMainContext *context,
659 GDBusConnection *connection,
660 const gchar *bus_name,
661 const gchar *object_path,
662 guint group_id)
664 GDBusMenuGroup *group;
665 GDBusMenuPath *path;
667 path = g_dbus_menu_path_get (context, connection, bus_name, object_path);
668 group = g_dbus_menu_group_get_from_path (path, group_id);
669 g_dbus_menu_path_unref (path);
671 return group;
674 /* GDBusMenuModel {{{1 */
676 typedef GMenuModelClass GDBusMenuModelClass;
677 struct _GDBusMenuModel
679 GMenuModel parent;
681 GDBusMenuGroup *group;
682 guint id;
684 GSequence *items; /* unowned */
685 gboolean active;
688 G_DEFINE_TYPE (GDBusMenuModel, g_dbus_menu_model, G_TYPE_MENU_MODEL)
690 static gboolean
691 g_dbus_menu_model_is_mutable (GMenuModel *model)
693 return TRUE;
696 static gint
697 g_dbus_menu_model_get_n_items (GMenuModel *model)
699 GDBusMenuModel *proxy = G_DBUS_MENU_MODEL (model);
701 if (!proxy->active)
703 g_dbus_menu_group_activate (proxy->group);
704 proxy->active = TRUE;
707 return proxy->items ? g_sequence_get_length (proxy->items) : 0;
710 static void
711 g_dbus_menu_model_get_item_attributes (GMenuModel *model,
712 gint item_index,
713 GHashTable **table)
715 GDBusMenuModel *proxy = G_DBUS_MENU_MODEL (model);
716 GDBusMenuModelItem *item;
717 GSequenceIter *iter;
719 g_return_if_fail (proxy->active);
720 g_return_if_fail (proxy->items);
722 iter = g_sequence_get_iter_at_pos (proxy->items, item_index);
723 g_return_if_fail (iter);
725 item = g_sequence_get (iter);
726 g_return_if_fail (item);
728 *table = g_hash_table_ref (item->attributes);
731 static void
732 g_dbus_menu_model_get_item_links (GMenuModel *model,
733 gint item_index,
734 GHashTable **table)
736 GDBusMenuModel *proxy = G_DBUS_MENU_MODEL (model);
737 GDBusMenuModelItem *item;
738 GSequenceIter *iter;
740 g_return_if_fail (proxy->active);
741 g_return_if_fail (proxy->items);
743 iter = g_sequence_get_iter_at_pos (proxy->items, item_index);
744 g_return_if_fail (iter);
746 item = g_sequence_get (iter);
747 g_return_if_fail (item);
749 *table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
752 GHashTableIter tmp;
753 gpointer key;
754 gpointer value;
756 g_hash_table_iter_init (&tmp, item->links);
757 while (g_hash_table_iter_next (&tmp, &key, &value))
759 if (g_variant_is_of_type (value, G_VARIANT_TYPE ("(uu)")))
761 guint group_id, menu_id;
762 GDBusMenuGroup *group;
763 GDBusMenuModel *link;
765 g_variant_get (value, "(uu)", &group_id, &menu_id);
767 /* save the hash lookup in a relatively common case */
768 if (proxy->group->id != group_id)
769 group = g_dbus_menu_group_get_from_path (proxy->group->path, group_id);
770 else
771 group = g_dbus_menu_group_ref (proxy->group);
773 link = g_dbus_menu_model_get_from_group (group, menu_id);
775 g_hash_table_insert (*table, g_strdup (key), link);
777 g_dbus_menu_group_unref (group);
783 static void
784 g_dbus_menu_model_finalize (GObject *object)
786 GDBusMenuModel *proxy = G_DBUS_MENU_MODEL (object);
788 if (proxy->active)
789 g_dbus_menu_group_deactivate (proxy->group);
791 g_hash_table_remove (proxy->group->proxies, GINT_TO_POINTER (proxy->id));
792 g_dbus_menu_group_unref (proxy->group);
794 G_OBJECT_CLASS (g_dbus_menu_model_parent_class)
795 ->finalize (object);
798 static void
799 g_dbus_menu_model_init (GDBusMenuModel *proxy)
803 static void
804 g_dbus_menu_model_class_init (GDBusMenuModelClass *class)
806 GObjectClass *object_class = G_OBJECT_CLASS (class);
808 class->is_mutable = g_dbus_menu_model_is_mutable;
809 class->get_n_items = g_dbus_menu_model_get_n_items;
810 class->get_item_attributes = g_dbus_menu_model_get_item_attributes;
811 class->get_item_links = g_dbus_menu_model_get_item_links;
813 object_class->finalize = g_dbus_menu_model_finalize;
816 static void
817 g_dbus_menu_model_changed (GDBusMenuModel *proxy,
818 GSequence *items,
819 gint position,
820 gint removed,
821 gint added)
823 proxy->items = items;
825 if (proxy->active && (removed || added))
826 g_menu_model_items_changed (G_MENU_MODEL (proxy), position, removed, added);
829 static GDBusMenuModel *
830 g_dbus_menu_model_get_from_group (GDBusMenuGroup *group,
831 guint menu_id)
833 GDBusMenuModel *proxy;
835 proxy = g_hash_table_lookup (group->proxies, GINT_TO_POINTER (menu_id));
836 if (proxy)
837 g_object_ref (proxy);
839 if (proxy == NULL)
841 proxy = g_object_new (G_TYPE_DBUS_MENU_MODEL, NULL);
842 proxy->items = g_hash_table_lookup (group->menus, GINT_TO_POINTER (menu_id));
843 g_hash_table_insert (group->proxies, GINT_TO_POINTER (menu_id), proxy);
844 proxy->group = g_dbus_menu_group_ref (group);
845 proxy->id = menu_id;
848 return proxy;
852 * g_dbus_menu_model_get:
853 * @connection: a #GDBusConnection
854 * @bus_name: the bus name which exports the menu model
855 * @object_path: the object path at which the menu model is exported
857 * Obtains a #GDBusMenuModel for the menu model which is exported
858 * at the given @bus_name and @object_path.
860 * The thread default main context is taken at the time of this call.
861 * All signals on the menu model (and any linked models) are reported
862 * with respect to this context. All calls on the returned menu model
863 * (and linked models) must also originate from this same context, with
864 * the thread default main context unchanged.
866 * Returns: (transfer full): a #GDBusMenuModel object. Free with
867 * g_object_unref().
869 * Since: 2.32
871 GDBusMenuModel *
872 g_dbus_menu_model_get (GDBusConnection *connection,
873 const gchar *bus_name,
874 const gchar *object_path)
876 GDBusMenuGroup *group;
877 GDBusMenuModel *proxy;
878 GMainContext *context;
880 context = g_main_context_get_thread_default ();
881 if (context == NULL)
882 context = g_main_context_default ();
884 group = g_dbus_menu_group_get (context, connection, bus_name, object_path, 0);
885 proxy = g_dbus_menu_model_get_from_group (group, 0);
886 g_dbus_menu_group_unref (group);
888 return proxy;
891 #if 0
892 static void
893 dump_proxy (gpointer key, gpointer value, gpointer data)
895 GDBusMenuModel *proxy = value;
897 g_print (" menu %d refcount %d active %d\n",
898 proxy->id, G_OBJECT (proxy)->ref_count, proxy->active);
901 static void
902 dump_group (gpointer key, gpointer value, gpointer data)
904 GDBusMenuGroup *group = value;
906 g_print (" group %d refcount %d state %d active %d\n",
907 group->id, group->ref_count, group->state, group->active);
909 g_hash_table_foreach (group->proxies, dump_proxy, NULL);
912 static void
913 dump_path (gpointer key, gpointer value, gpointer data)
915 PathIdentifier *pid = key;
916 GDBusMenuPath *path = value;
918 g_print ("%s active %d\n", pid->object_path, path->active);
919 g_hash_table_foreach (path->groups, dump_group, NULL);
922 void
923 g_dbus_menu_model_dump (void)
925 g_hash_table_foreach (g_dbus_menu_paths, dump_path, NULL);
928 #endif
930 /* Epilogue {{{1 */
931 /* vim:set foldmethod=marker: */