Branch data Line data Source code
1 : : /*
2 : : * Copyright © 2011 Canonical Ltd.
3 : : *
4 : : * SPDX-License-Identifier: LGPL-2.1-or-later
5 : : *
6 : : * This library is free software; you can redistribute it and/or
7 : : * modify it under the terms of the GNU Lesser General Public
8 : : * License as published by the Free Software Foundation; either
9 : : * version 2.1 of the License, or (at your option) any later version.
10 : : *
11 : : * This library is distributed in the hope that it will be useful, but
12 : : * WITHOUT ANY WARRANTY; without even the implied warranty of
13 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : : * Lesser General Public License for more details.
15 : : *
16 : : * You should have received a copy of the GNU Lesser General Public
17 : : * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 : : *
19 : : * Author: Ryan Lortie <desrt@desrt.ca>
20 : : */
21 : :
22 : : #include "config.h"
23 : :
24 : : #include "gmenuexporter.h"
25 : :
26 : : #include "gdbusmethodinvocation.h"
27 : : #include "gdbusintrospection.h"
28 : : #include "gdbusnamewatching.h"
29 : : #include "gdbuserror.h"
30 : :
31 : : /* {{{1 D-Bus Interface description */
32 : :
33 : : /* For documentation of this interface, see
34 : : * https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI
35 : : */
36 : :
37 : : static GDBusInterfaceInfo *
38 : 100006 : org_gtk_Menus_get_interface (void)
39 : : {
40 : : static GDBusInterfaceInfo *interface_info;
41 : : static gsize interface_info_initialized = 0;
42 : :
43 : 100006 : if (g_once_init_enter (&interface_info_initialized))
44 : : {
45 : 1 : GError *error = NULL;
46 : : GDBusNodeInfo *info;
47 : :
48 : 1 : info = g_dbus_node_info_new_for_xml ("<node>"
49 : : " <interface name='org.gtk.Menus'>"
50 : : " <method name='Start'>"
51 : : " <arg type='au' name='groups' direction='in'/>"
52 : : " <arg type='a(uuaa{sv})' name='content' direction='out'/>"
53 : : " </method>"
54 : : " <method name='End'>"
55 : : " <arg type='au' name='groups' direction='in'/>"
56 : : " </method>"
57 : : " <signal name='Changed'>"
58 : : " arg type='a(uuuuaa{sv})' name='changes'/>"
59 : : " </signal>"
60 : : " </interface>"
61 : : "</node>", &error);
62 : 1 : if (info == NULL)
63 : 0 : g_error ("%s", error->message);
64 : 1 : interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus");
65 : 1 : g_assert (interface_info != NULL);
66 : 1 : g_dbus_interface_info_ref (interface_info);
67 : 1 : g_dbus_node_info_unref (info);
68 : :
69 : 1 : g_once_init_leave (&interface_info_initialized, 1);
70 : : }
71 : :
72 : 100006 : return interface_info;
73 : : }
74 : :
75 : : /* {{{1 Forward declarations */
76 : : typedef struct _GMenuExporterMenu GMenuExporterMenu;
77 : : typedef struct _GMenuExporterLink GMenuExporterLink;
78 : : typedef struct _GMenuExporterGroup GMenuExporterGroup;
79 : : typedef struct _GMenuExporterRemote GMenuExporterRemote;
80 : : typedef struct _GMenuExporterWatch GMenuExporterWatch;
81 : : typedef struct _GMenuExporter GMenuExporter;
82 : :
83 : : static gboolean g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group);
84 : : static guint g_menu_exporter_group_get_id (GMenuExporterGroup *group);
85 : : static GMenuExporter * g_menu_exporter_group_get_exporter (GMenuExporterGroup *group);
86 : : static GMenuExporterMenu * g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
87 : : GMenuModel *model);
88 : : static void g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
89 : : guint id);
90 : :
91 : : static GMenuExporterGroup * g_menu_exporter_create_group (GMenuExporter *exporter);
92 : : static GMenuExporterGroup * g_menu_exporter_lookup_group (GMenuExporter *exporter,
93 : : guint group_id);
94 : : static void g_menu_exporter_report (GMenuExporter *exporter,
95 : : GVariant *report);
96 : : static void g_menu_exporter_remove_group (GMenuExporter *exporter,
97 : : guint id);
98 : :
99 : : /* {{{1 GMenuExporterLink, GMenuExporterMenu */
100 : :
101 : : struct _GMenuExporterMenu
102 : : {
103 : : GMenuExporterGroup *group;
104 : : guint id;
105 : :
106 : : GMenuModel *model;
107 : : gulong handler_id;
108 : : GSequence *item_links;
109 : : };
110 : :
111 : : struct _GMenuExporterLink
112 : : {
113 : : gchar *name;
114 : : GMenuExporterMenu *menu;
115 : : GMenuExporterLink *next;
116 : : };
117 : :
118 : : static void
119 : 100311 : g_menu_exporter_menu_free (GMenuExporterMenu *menu)
120 : : {
121 : 100311 : g_menu_exporter_group_remove_menu (menu->group, menu->id);
122 : :
123 : 100311 : if (menu->handler_id != 0)
124 : 310 : g_signal_handler_disconnect (menu->model, menu->handler_id);
125 : :
126 : 100311 : if (menu->item_links != NULL)
127 : 310 : g_sequence_free (menu->item_links);
128 : :
129 : 100311 : g_object_unref (menu->model);
130 : :
131 : 100311 : g_slice_free (GMenuExporterMenu, menu);
132 : 100311 : }
133 : :
134 : : static void
135 : 1162 : g_menu_exporter_link_free (gpointer data)
136 : : {
137 : 1162 : GMenuExporterLink *link = data;
138 : :
139 : 1468 : while (link != NULL)
140 : : {
141 : 306 : GMenuExporterLink *tmp = link;
142 : 306 : link = tmp->next;
143 : :
144 : 306 : g_menu_exporter_menu_free (tmp->menu);
145 : 306 : g_free (tmp->name);
146 : :
147 : 306 : g_slice_free (GMenuExporterLink, tmp);
148 : : }
149 : 1162 : }
150 : :
151 : : static GMenuExporterLink *
152 : 1162 : g_menu_exporter_menu_create_links (GMenuExporterMenu *menu,
153 : : gint position)
154 : : {
155 : 1162 : GMenuExporterLink *list = NULL;
156 : : GMenuLinkIter *iter;
157 : : const char *name;
158 : : GMenuModel *model;
159 : :
160 : 1162 : iter = g_menu_model_iterate_item_links (menu->model, position);
161 : :
162 : 1468 : while (g_menu_link_iter_get_next (iter, &name, &model))
163 : : {
164 : : GMenuExporterGroup *group;
165 : : GMenuExporterLink *tmp;
166 : :
167 : : /* keep sections in the same group, but create new groups
168 : : * otherwise
169 : : */
170 : 306 : if (!g_str_equal (name, "section"))
171 : 150 : group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group));
172 : : else
173 : 156 : group = menu->group;
174 : :
175 : 306 : tmp = g_slice_new (GMenuExporterLink);
176 : 306 : tmp->name = g_strconcat (":", name, NULL);
177 : 306 : tmp->menu = g_menu_exporter_group_add_menu (group, model);
178 : 306 : tmp->next = list;
179 : 306 : list = tmp;
180 : :
181 : 306 : g_object_unref (model);
182 : : }
183 : :
184 : 1162 : g_object_unref (iter);
185 : :
186 : 1162 : return list;
187 : : }
188 : :
189 : : static GVariant *
190 : 1162 : g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu,
191 : : gint position)
192 : : {
193 : : GMenuAttributeIter *attr_iter;
194 : : GVariantBuilder builder;
195 : : GSequenceIter *iter;
196 : : GMenuExporterLink *link;
197 : : const char *name;
198 : : GVariant *value;
199 : :
200 : 1162 : g_variant_builder_init_static (&builder, G_VARIANT_TYPE_VARDICT);
201 : :
202 : 1162 : attr_iter = g_menu_model_iterate_item_attributes (menu->model, position);
203 : 2246 : while (g_menu_attribute_iter_get_next (attr_iter, &name, &value))
204 : : {
205 : 1084 : g_variant_builder_add (&builder, "{sv}", name, value);
206 : 1084 : g_variant_unref (value);
207 : : }
208 : 1162 : g_object_unref (attr_iter);
209 : :
210 : 1162 : iter = g_sequence_get_iter_at_pos (menu->item_links, position);
211 : 1468 : for (link = g_sequence_get (iter); link; link = link->next)
212 : 306 : g_variant_builder_add (&builder, "{sv}", link->name,
213 : 306 : g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id));
214 : :
215 : 1162 : return g_variant_builder_end (&builder);
216 : : }
217 : :
218 : : static GVariant *
219 : 194 : g_menu_exporter_menu_list (GMenuExporterMenu *menu)
220 : : {
221 : : GVariantBuilder builder;
222 : : gint i, n;
223 : :
224 : 194 : g_variant_builder_init_static (&builder, G_VARIANT_TYPE ("aa{sv}"));
225 : :
226 : 194 : n = g_sequence_get_length (menu->item_links);
227 : 766 : for (i = 0; i < n; i++)
228 : 572 : g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
229 : :
230 : 194 : return g_variant_builder_end (&builder);
231 : : }
232 : :
233 : : static void
234 : 522 : g_menu_exporter_menu_items_changed (GMenuModel *model,
235 : : gint position,
236 : : gint removed,
237 : : gint added,
238 : : gpointer user_data)
239 : : {
240 : 522 : GMenuExporterMenu *menu = user_data;
241 : : GSequenceIter *point;
242 : : gint i;
243 : : #ifndef G_DISABLE_ASSERT
244 : : gint n_items;
245 : : #endif
246 : :
247 : 522 : g_assert (menu->model == model);
248 : 522 : g_assert (menu->item_links != NULL);
249 : :
250 : : #ifndef G_DISABLE_ASSERT
251 : 522 : n_items = g_sequence_get_length (menu->item_links);
252 : : #endif
253 : 522 : g_assert (position >= 0 && position < G_MENU_EXPORTER_MAX_SECTION_SIZE);
254 : 522 : g_assert (removed >= 0 && removed < G_MENU_EXPORTER_MAX_SECTION_SIZE);
255 : 522 : g_assert (added < G_MENU_EXPORTER_MAX_SECTION_SIZE);
256 : 522 : g_assert (position + removed <= n_items);
257 : 522 : g_assert (n_items - removed + added < G_MENU_EXPORTER_MAX_SECTION_SIZE);
258 : :
259 : 522 : point = g_sequence_get_iter_at_pos (menu->item_links, position + removed);
260 : 522 : g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point);
261 : :
262 : 1684 : for (i = position; i < position + added; i++)
263 : 1162 : g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i));
264 : :
265 : 522 : if (g_menu_exporter_group_is_subscribed (menu->group))
266 : : {
267 : : GVariantBuilder builder;
268 : :
269 : 324 : g_variant_builder_init_static (&builder, G_VARIANT_TYPE ("(uuuuaa{sv})"));
270 : 324 : g_variant_builder_add (&builder, "u", g_menu_exporter_group_get_id (menu->group));
271 : 324 : g_variant_builder_add (&builder, "u", menu->id);
272 : 324 : g_variant_builder_add (&builder, "u", position);
273 : 324 : g_variant_builder_add (&builder, "u", removed);
274 : :
275 : 324 : g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}"));
276 : 914 : for (i = position; i < position + added; i++)
277 : 590 : g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i));
278 : 324 : g_variant_builder_close (&builder);
279 : :
280 : 324 : g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder));
281 : : }
282 : 522 : }
283 : :
284 : : static void
285 : 310 : g_menu_exporter_menu_prepare (GMenuExporterMenu *menu)
286 : : {
287 : : gint n_items;
288 : :
289 : 310 : g_assert (menu->item_links == NULL);
290 : :
291 : 310 : if (g_menu_model_is_mutable (menu->model))
292 : 310 : menu->handler_id = g_signal_connect (menu->model, "items-changed",
293 : : G_CALLBACK (g_menu_exporter_menu_items_changed), menu);
294 : :
295 : 310 : menu->item_links = g_sequence_new (g_menu_exporter_link_free);
296 : :
297 : 310 : n_items = g_menu_model_get_n_items (menu->model);
298 : 310 : if (n_items)
299 : 310 : g_menu_exporter_menu_items_changed (menu->model, 0, 0, n_items, menu);
300 : 310 : }
301 : :
302 : : static GMenuExporterMenu *
303 : 100311 : g_menu_exporter_menu_new (GMenuExporterGroup *group,
304 : : guint id,
305 : : GMenuModel *model)
306 : : {
307 : : GMenuExporterMenu *menu;
308 : :
309 : 100311 : menu = g_slice_new0 (GMenuExporterMenu);
310 : 100311 : menu->group = group;
311 : 100311 : menu->id = id;
312 : 100311 : menu->model = g_object_ref (model);
313 : :
314 : 100311 : return menu;
315 : : }
316 : :
317 : : /* {{{1 GMenuExporterGroup */
318 : :
319 : : struct _GMenuExporterGroup
320 : : {
321 : : GMenuExporter *exporter;
322 : : guint id;
323 : :
324 : : GHashTable *menus;
325 : : guint next_menu_id;
326 : : gboolean prepared;
327 : :
328 : : gint subscribed;
329 : : };
330 : :
331 : : static void
332 : 100465 : g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group)
333 : : {
334 : 100465 : if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0)
335 : : {
336 : 100155 : g_menu_exporter_remove_group (group->exporter, group->id);
337 : :
338 : 100155 : g_hash_table_unref (group->menus);
339 : :
340 : 100155 : g_slice_free (GMenuExporterGroup, group);
341 : : }
342 : 100465 : }
343 : :
344 : : static void
345 : 154 : g_menu_exporter_group_subscribe (GMenuExporterGroup *group,
346 : : GVariantBuilder *builder)
347 : : {
348 : : GHashTableIter iter;
349 : : gpointer key, val;
350 : :
351 : 154 : if (!group->prepared)
352 : : {
353 : : GMenuExporterMenu *menu;
354 : :
355 : : /* set this first, so that any menus created during the
356 : : * preparation of the first menu also end up in the prepared
357 : : * state.
358 : : * */
359 : 154 : group->prepared = TRUE;
360 : :
361 : 154 : menu = g_hash_table_lookup (group->menus, 0);
362 : :
363 : : /* If the group was created by a subscription and does not yet
364 : : * exist, it won't have a root menu...
365 : : *
366 : : * That menu will be prepared if it is ever added (due to
367 : : * group->prepared == TRUE).
368 : : */
369 : 154 : if (menu)
370 : 154 : g_menu_exporter_menu_prepare (menu);
371 : : }
372 : :
373 : 154 : group->subscribed++;
374 : :
375 : 154 : g_hash_table_iter_init (&iter, group->menus);
376 : 502 : while (g_hash_table_iter_next (&iter, &key, &val))
377 : : {
378 : 194 : guint id = GPOINTER_TO_INT (key);
379 : 194 : GMenuExporterMenu *menu = val;
380 : :
381 : 194 : if (!g_sequence_is_empty (menu->item_links))
382 : : {
383 : 194 : g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})"));
384 : 194 : g_variant_builder_add (builder, "u", group->id);
385 : 194 : g_variant_builder_add (builder, "u", id);
386 : 194 : g_variant_builder_add_value (builder, g_menu_exporter_menu_list (menu));
387 : 194 : g_variant_builder_close (builder);
388 : : }
389 : : }
390 : 154 : }
391 : :
392 : : static void
393 : 154 : g_menu_exporter_group_unsubscribe (GMenuExporterGroup *group,
394 : : gint count)
395 : : {
396 : 154 : g_assert (group->subscribed >= count);
397 : :
398 : 154 : group->subscribed -= count;
399 : :
400 : 154 : g_menu_exporter_group_check_if_useless (group);
401 : 154 : }
402 : :
403 : : static GMenuExporter *
404 : 474 : g_menu_exporter_group_get_exporter (GMenuExporterGroup *group)
405 : : {
406 : 474 : return group->exporter;
407 : : }
408 : :
409 : : static gboolean
410 : 522 : g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group)
411 : : {
412 : 522 : return group->subscribed > 0;
413 : : }
414 : :
415 : : static guint
416 : 630 : g_menu_exporter_group_get_id (GMenuExporterGroup *group)
417 : : {
418 : 630 : return group->id;
419 : : }
420 : :
421 : : static void
422 : 100311 : g_menu_exporter_group_remove_menu (GMenuExporterGroup *group,
423 : : guint id)
424 : : {
425 : 100311 : g_hash_table_remove (group->menus, GINT_TO_POINTER (id));
426 : :
427 : 100311 : g_menu_exporter_group_check_if_useless (group);
428 : 100311 : }
429 : :
430 : : static GMenuExporterMenu *
431 : 100311 : g_menu_exporter_group_add_menu (GMenuExporterGroup *group,
432 : : GMenuModel *model)
433 : : {
434 : : GMenuExporterMenu *menu;
435 : : guint id;
436 : :
437 : 100311 : id = group->next_menu_id++;
438 : 100311 : menu = g_menu_exporter_menu_new (group, id, model);
439 : 100311 : g_hash_table_insert (group->menus, GINT_TO_POINTER (id), menu);
440 : :
441 : 100311 : if (group->prepared)
442 : 156 : g_menu_exporter_menu_prepare (menu);
443 : :
444 : 100311 : return menu;
445 : : }
446 : :
447 : : static GMenuExporterGroup *
448 : 100155 : g_menu_exporter_group_new (GMenuExporter *exporter,
449 : : guint id)
450 : : {
451 : : GMenuExporterGroup *group;
452 : :
453 : 100155 : group = g_slice_new0 (GMenuExporterGroup);
454 : 100155 : group->menus = g_hash_table_new (NULL, NULL);
455 : 100155 : group->exporter = exporter;
456 : 100155 : group->id = id;
457 : :
458 : 100155 : return group;
459 : : }
460 : :
461 : : /* {{{1 GMenuExporterRemote */
462 : :
463 : : struct _GMenuExporterRemote
464 : : {
465 : : GMenuExporter *exporter;
466 : : GHashTable *watches;
467 : : guint watch_id;
468 : : };
469 : :
470 : : static void
471 : 154 : g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote,
472 : : guint group_id,
473 : : GVariantBuilder *builder)
474 : : {
475 : : GMenuExporterGroup *group;
476 : : guint count;
477 : :
478 : 154 : count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
479 : 154 : g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count + 1));
480 : :
481 : : /* Group will be created (as empty/unsubscribed if it does not exist) */
482 : 154 : group = g_menu_exporter_lookup_group (remote->exporter, group_id);
483 : 154 : g_menu_exporter_group_subscribe (group, builder);
484 : 154 : }
485 : :
486 : : static void
487 : 148 : g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote,
488 : : guint group_id)
489 : : {
490 : : GMenuExporterGroup *group;
491 : : guint count;
492 : :
493 : 148 : count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id));
494 : :
495 : 148 : if (count == 0)
496 : 0 : return;
497 : :
498 : 148 : if (count != 1)
499 : 0 : g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count - 1));
500 : : else
501 : 148 : g_hash_table_remove (remote->watches, GINT_TO_POINTER (group_id));
502 : :
503 : 148 : group = g_menu_exporter_lookup_group (remote->exporter, group_id);
504 : 148 : g_menu_exporter_group_unsubscribe (group, 1);
505 : : }
506 : :
507 : : static gboolean
508 : 148 : g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote)
509 : : {
510 : 148 : return g_hash_table_size (remote->watches) != 0;
511 : : }
512 : :
513 : : static void
514 : 4 : g_menu_exporter_remote_free (gpointer data)
515 : : {
516 : 4 : GMenuExporterRemote *remote = data;
517 : : GHashTableIter iter;
518 : : gpointer key, val;
519 : :
520 : 4 : g_hash_table_iter_init (&iter, remote->watches);
521 : 10 : while (g_hash_table_iter_next (&iter, &key, &val))
522 : : {
523 : : GMenuExporterGroup *group;
524 : :
525 : 6 : group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key));
526 : 6 : g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val));
527 : : }
528 : :
529 : 4 : if (remote->watch_id > 0)
530 : 2 : g_bus_unwatch_name (remote->watch_id);
531 : :
532 : 4 : g_hash_table_unref (remote->watches);
533 : :
534 : 4 : g_slice_free (GMenuExporterRemote, remote);
535 : 4 : }
536 : :
537 : : static GMenuExporterRemote *
538 : 4 : g_menu_exporter_remote_new (GMenuExporter *exporter,
539 : : guint watch_id)
540 : : {
541 : : GMenuExporterRemote *remote;
542 : :
543 : 4 : remote = g_slice_new0 (GMenuExporterRemote);
544 : 4 : remote->exporter = exporter;
545 : 4 : remote->watches = g_hash_table_new (NULL, NULL);
546 : 4 : remote->watch_id = watch_id;
547 : :
548 : 4 : return remote;
549 : : }
550 : :
551 : : /* {{{1 GMenuExporter */
552 : :
553 : : struct _GMenuExporter
554 : : {
555 : : GDBusConnection *connection;
556 : : gchar *object_path;
557 : : guint registration_id;
558 : : GHashTable *groups;
559 : : guint next_group_id;
560 : :
561 : : GMenuExporterMenu *root;
562 : : GMenuExporterRemote *peer_remote;
563 : : GHashTable *remotes;
564 : : };
565 : :
566 : : static void
567 : 0 : g_menu_exporter_name_vanished (GDBusConnection *connection,
568 : : const gchar *name,
569 : : gpointer user_data)
570 : : {
571 : 0 : GMenuExporter *exporter = user_data;
572 : :
573 : : /* connection == NULL when we get called because the connection closed */
574 : 0 : g_assert (exporter->connection == connection || connection == NULL);
575 : :
576 : 0 : g_hash_table_remove (exporter->remotes, name);
577 : 0 : }
578 : :
579 : : static GVariant *
580 : 154 : g_menu_exporter_subscribe (GMenuExporter *exporter,
581 : : const gchar *sender,
582 : : GVariant *group_ids)
583 : : {
584 : : GMenuExporterRemote *remote;
585 : : GVariantBuilder builder;
586 : : GVariantIter iter;
587 : : guint32 id;
588 : :
589 : 154 : if (sender != NULL)
590 : 77 : remote = g_hash_table_lookup (exporter->remotes, sender);
591 : : else
592 : 77 : remote = exporter->peer_remote;
593 : :
594 : 154 : if (remote == NULL)
595 : : {
596 : 4 : if (sender != NULL)
597 : : {
598 : : guint watch_id;
599 : :
600 : 2 : watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
601 : : NULL, g_menu_exporter_name_vanished, exporter, NULL);
602 : 2 : remote = g_menu_exporter_remote_new (exporter, watch_id);
603 : 2 : g_hash_table_insert (exporter->remotes, g_strdup (sender), remote);
604 : : }
605 : : else
606 : 2 : remote = exporter->peer_remote =
607 : 2 : g_menu_exporter_remote_new (exporter, 0);
608 : : }
609 : :
610 : 154 : g_variant_builder_init_static (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))"));
611 : :
612 : 154 : g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})"));
613 : :
614 : 154 : g_variant_iter_init (&iter, group_ids);
615 : 308 : while (g_variant_iter_next (&iter, "u", &id))
616 : 154 : g_menu_exporter_remote_subscribe (remote, id, &builder);
617 : :
618 : 154 : g_variant_builder_close (&builder);
619 : :
620 : 154 : return g_variant_builder_end (&builder);
621 : : }
622 : :
623 : : static void
624 : 149 : g_menu_exporter_unsubscribe (GMenuExporter *exporter,
625 : : const gchar *sender,
626 : : GVariant *group_ids)
627 : : {
628 : : GMenuExporterRemote *remote;
629 : : GVariantIter iter;
630 : : guint32 id;
631 : :
632 : 149 : if (sender != NULL)
633 : 75 : remote = g_hash_table_lookup (exporter->remotes, sender);
634 : : else
635 : 74 : remote = exporter->peer_remote;
636 : :
637 : 149 : if (remote == NULL)
638 : 1 : return;
639 : :
640 : 148 : g_variant_iter_init (&iter, group_ids);
641 : 296 : while (g_variant_iter_next (&iter, "u", &id))
642 : 148 : g_menu_exporter_remote_unsubscribe (remote, id);
643 : :
644 : 148 : if (!g_menu_exporter_remote_has_subscriptions (remote))
645 : : {
646 : 2 : if (sender != NULL)
647 : 1 : g_hash_table_remove (exporter->remotes, sender);
648 : : else
649 : 1 : g_clear_pointer (&exporter->peer_remote, g_menu_exporter_remote_free);
650 : : }
651 : : }
652 : :
653 : : static void
654 : 324 : g_menu_exporter_report (GMenuExporter *exporter,
655 : : GVariant *report)
656 : : {
657 : : GVariantBuilder builder;
658 : :
659 : 324 : g_variant_builder_init_static (&builder, G_VARIANT_TYPE_TUPLE);
660 : 324 : g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY);
661 : 324 : g_variant_builder_add_value (&builder, report);
662 : 324 : g_variant_builder_close (&builder);
663 : :
664 : 324 : g_dbus_connection_emit_signal (exporter->connection,
665 : : NULL,
666 : 324 : exporter->object_path,
667 : : "org.gtk.Menus", "Changed",
668 : : g_variant_builder_end (&builder),
669 : : NULL);
670 : 324 : }
671 : :
672 : : static void
673 : 100155 : g_menu_exporter_remove_group (GMenuExporter *exporter,
674 : : guint id)
675 : : {
676 : 100155 : g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id));
677 : 100155 : }
678 : :
679 : : static GMenuExporterGroup *
680 : 308 : g_menu_exporter_lookup_group (GMenuExporter *exporter,
681 : : guint group_id)
682 : : {
683 : : GMenuExporterGroup *group;
684 : :
685 : 308 : group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id));
686 : :
687 : 308 : if (group == NULL)
688 : : {
689 : 0 : group = g_menu_exporter_group_new (exporter, group_id);
690 : 0 : g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group);
691 : : }
692 : :
693 : 308 : return group;
694 : : }
695 : :
696 : : static GMenuExporterGroup *
697 : 100155 : g_menu_exporter_create_group (GMenuExporter *exporter)
698 : : {
699 : : GMenuExporterGroup *group;
700 : : guint id;
701 : :
702 : 100155 : id = exporter->next_group_id++;
703 : 100155 : group = g_menu_exporter_group_new (exporter, id);
704 : 100155 : g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group);
705 : :
706 : 100155 : return group;
707 : : }
708 : :
709 : : static void
710 : 100006 : g_menu_exporter_free (GMenuExporter *exporter)
711 : : {
712 : 100006 : g_clear_pointer (&exporter->root, g_menu_exporter_menu_free);
713 : 100006 : g_clear_pointer (&exporter->peer_remote, g_menu_exporter_remote_free);
714 : 100006 : g_hash_table_unref (exporter->remotes);
715 : 100006 : g_hash_table_unref (exporter->groups);
716 : 100006 : g_object_unref (exporter->connection);
717 : 100006 : g_free (exporter->object_path);
718 : :
719 : 100006 : g_slice_free (GMenuExporter, exporter);
720 : 100006 : }
721 : :
722 : : static void
723 : 303 : g_menu_exporter_method_call (GDBusConnection *connection,
724 : : const gchar *sender,
725 : : const gchar *object_path,
726 : : const gchar *interface_name,
727 : : const gchar *method_name,
728 : : GVariant *parameters,
729 : : GDBusMethodInvocation *invocation,
730 : : gpointer user_data)
731 : : {
732 : 303 : GMenuExporter *exporter = user_data;
733 : : GVariant *group_ids;
734 : :
735 : 303 : group_ids = g_variant_get_child_value (parameters, 0);
736 : :
737 : 303 : if (g_str_equal (method_name, "Start"))
738 : 154 : g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids));
739 : :
740 : 149 : else if (g_str_equal (method_name, "End"))
741 : : {
742 : 149 : g_menu_exporter_unsubscribe (exporter, sender, group_ids);
743 : 149 : g_dbus_method_invocation_return_value (invocation, NULL);
744 : : }
745 : :
746 : : else
747 : : g_assert_not_reached ();
748 : :
749 : 303 : g_variant_unref (group_ids);
750 : 303 : }
751 : :
752 : : /* {{{1 Public API */
753 : :
754 : : /**
755 : : * g_dbus_connection_export_menu_model:
756 : : * @connection: a #GDBusConnection
757 : : * @object_path: a D-Bus object path
758 : : * @menu: a #GMenuModel
759 : : * @error: return location for an error, or %NULL
760 : : *
761 : : * Exports @menu on @connection at @object_path.
762 : : *
763 : : * The implemented D-Bus API should be considered private.
764 : : * It is subject to change in the future.
765 : : *
766 : : * An object path can only have one menu model exported on it. If this
767 : : * constraint is violated, the export will fail and 0 will be
768 : : * returned (with @error set accordingly).
769 : : *
770 : : * Exporting menus with sections containing more than
771 : : * %G_MENU_EXPORTER_MAX_SECTION_SIZE items is not supported and results in
772 : : * undefined behavior.
773 : : *
774 : : * You can unexport the menu model using
775 : : * g_dbus_connection_unexport_menu_model() with the return value of
776 : : * this function.
777 : : *
778 : : * Returns: the ID of the export (never zero), or 0 in case of failure
779 : : *
780 : : * Since: 2.32
781 : : */
782 : : guint
783 : 100006 : g_dbus_connection_export_menu_model (GDBusConnection *connection,
784 : : const gchar *object_path,
785 : : GMenuModel *menu,
786 : : GError **error)
787 : : {
788 : 100006 : const GDBusInterfaceVTable vtable = {
789 : : g_menu_exporter_method_call, NULL, NULL, { 0 }
790 : : };
791 : : GMenuExporter *exporter;
792 : : guint id;
793 : :
794 : 100006 : exporter = g_slice_new0 (GMenuExporter);
795 : 100006 : exporter->connection = g_object_ref (connection);
796 : 100006 : exporter->object_path = g_strdup (object_path);
797 : 100006 : exporter->groups = g_hash_table_new (NULL, NULL);
798 : 100006 : exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free);
799 : :
800 : 100006 : id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (),
801 : : &vtable, exporter, (GDestroyNotify) g_menu_exporter_free, error);
802 : :
803 : 100006 : if (id != 0)
804 : 100005 : exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), menu);
805 : :
806 : 100006 : return id;
807 : : }
808 : :
809 : : /**
810 : : * g_dbus_connection_unexport_menu_model:
811 : : * @connection: a #GDBusConnection
812 : : * @export_id: the ID from g_dbus_connection_export_menu_model()
813 : : *
814 : : * Reverses the effect of a previous call to
815 : : * g_dbus_connection_export_menu_model().
816 : : *
817 : : * It is an error to call this function with an ID that wasn't returned
818 : : * from g_dbus_connection_export_menu_model() or to call it with the
819 : : * same ID more than once.
820 : : *
821 : : * Since: 2.32
822 : : */
823 : : void
824 : 100005 : g_dbus_connection_unexport_menu_model (GDBusConnection *connection,
825 : : guint export_id)
826 : : {
827 : 100005 : g_dbus_connection_unregister_object (connection, export_id);
828 : 100005 : }
829 : :
830 : : /* {{{1 Epilogue */
831 : : /* vim:set foldmethod=marker: */
|