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