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 "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 | org_gtk_Menus_get_interface (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 ("<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); |
||
74 | if (info == NULL) |
||
75 | g_error ("%s\n", error->message); |
||
76 | interface_info = g_dbus_node_info_lookup_interface (info, "org.gtk.Menus"); |
||
77 | g_assert (interface_info != NULL); |
||
78 | g_dbus_interface_info_ref (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 GMenuExporterMenu; |
||
87 | typedef struct _GMenuExporterLink GMenuExporterLink; |
||
88 | typedef struct _GMenuExporterGroup GMenuExporterGroup; |
||
89 | typedef struct _GMenuExporterRemote GMenuExporterRemote; |
||
90 | typedef struct _GMenuExporterWatch GMenuExporterWatch; |
||
91 | typedef struct _GMenuExporter 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 _GMenuExporterMenu |
||
112 | { |
||
113 | GMenuExporterGroup *group; |
||
114 | guint id; |
||
115 | |||
116 | GMenuModel *model; |
||
117 | gulong handler_id; |
||
118 | GSequence *item_links; |
||
119 | }; |
||
120 | |||
121 | struct _GMenuExporterLink |
||
122 | { |
||
123 | gchar *name; |
||
124 | GMenuExporterMenu *menu; |
||
125 | GMenuExporterLink *next; |
||
126 | }; |
||
127 | |||
128 | static void |
||
129 | g_menu_exporter_menu_free (GMenuExporterMenu *menu) |
||
130 | { |
||
131 | g_menu_exporter_group_remove_menu (menu->group, menu->id); |
||
132 | |||
133 | if (menu->handler_id != 0) |
||
134 | g_signal_handler_disconnect (menu->model, menu->handler_id); |
||
135 | |||
136 | if (menu->item_links != NULL) |
||
137 | g_sequence_free (menu->item_links); |
||
138 | |||
139 | g_object_unref (menu->model); |
||
140 | |||
141 | g_slice_free (GMenuExporterMenu, menu); |
||
142 | } |
||
143 | |||
144 | static void |
||
145 | g_menu_exporter_link_free (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 (tmp->menu); |
||
155 | g_free (tmp->name); |
||
156 | |||
157 | g_slice_free (GMenuExporterLink, tmp); |
||
158 | } |
||
159 | } |
||
160 | |||
161 | static GMenuExporterLink * |
||
162 | g_menu_exporter_menu_create_links (GMenuExporterMenu *menu, |
||
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 (menu->model, position); |
||
171 | |||
172 | while (g_menu_link_iter_get_next (iter, &name, &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 (name, "section")) |
||
181 | group = g_menu_exporter_create_group (g_menu_exporter_group_get_exporter (menu->group)); |
||
182 | else |
||
183 | group = menu->group; |
||
184 | |||
185 | tmp = g_slice_new (GMenuExporterLink); |
||
186 | tmp->name = g_strconcat (":", 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 (model); |
||
192 | } |
||
193 | |||
194 | g_object_unref (iter); |
||
195 | |||
196 | return list; |
||
197 | } |
||
198 | |||
199 | static GVariant * |
||
200 | g_menu_exporter_menu_describe_item (GMenuExporterMenu *menu, |
||
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, G_VARIANT_TYPE_VARDICT); |
||
211 | |||
212 | attr_iter = g_menu_model_iterate_item_attributes (menu->model, position); |
||
213 | while (g_menu_attribute_iter_get_next (attr_iter, &name, &value)) |
||
214 | { |
||
215 | g_variant_builder_add (&builder, "{sv}", name, value); |
||
216 | g_variant_unref (value); |
||
217 | } |
||
218 | g_object_unref (attr_iter); |
||
219 | |||
220 | iter = g_sequence_get_iter_at_pos (menu->item_links, position); |
||
221 | for (link = g_sequence_get (iter); link; link = link->next) |
||
222 | g_variant_builder_add (&builder, "{sv}", link->name, |
||
223 | g_variant_new ("(uu)", g_menu_exporter_group_get_id (link->menu->group), link->menu->id)); |
||
224 | |||
225 | return g_variant_builder_end (&builder); |
||
226 | } |
||
227 | |||
228 | static GVariant * |
||
229 | g_menu_exporter_menu_list (GMenuExporterMenu *menu) |
||
230 | { |
||
231 | GVariantBuilder builder; |
||
232 | gint i, n; |
||
233 | |||
234 | g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}")); |
||
235 | |||
236 | n = g_sequence_get_length (menu->item_links); |
||
237 | for (i = 0; i < n; i++) |
||
238 | g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i)); |
||
239 | |||
240 | return g_variant_builder_end (&builder); |
||
241 | } |
||
242 | |||
243 | static void |
||
244 | g_menu_exporter_menu_items_changed (GMenuModel *model, |
||
245 | gint position, |
||
246 | gint removed, |
||
247 | gint added, |
||
248 | gpointer user_data) |
||
249 | { |
||
250 | GMenuExporterMenu *menu = 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 (menu->item_links, position + removed); |
||
259 | g_sequence_remove_range (g_sequence_get_iter_at_pos (menu->item_links, position), point); |
||
260 | |||
261 | for (i = position; i < position + added; i++) |
||
262 | g_sequence_insert_before (point, g_menu_exporter_menu_create_links (menu, i)); |
||
263 | |||
264 | if (g_menu_exporter_group_is_subscribed (menu->group)) |
||
265 | { |
||
266 | GVariantBuilder builder; |
||
267 | |||
268 | g_variant_builder_init (&builder, G_VARIANT_TYPE ("(uuuuaa{sv})")); |
||
269 | g_variant_builder_add (&builder, "u", g_menu_exporter_group_get_id (menu->group)); |
||
270 | g_variant_builder_add (&builder, "u", menu->id); |
||
271 | g_variant_builder_add (&builder, "u", position); |
||
272 | g_variant_builder_add (&builder, "u", removed); |
||
273 | |||
274 | g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}")); |
||
275 | for (i = position; i < position + added; i++) |
||
276 | g_variant_builder_add_value (&builder, g_menu_exporter_menu_describe_item (menu, i)); |
||
277 | g_variant_builder_close (&builder); |
||
278 | |||
279 | g_menu_exporter_report (g_menu_exporter_group_get_exporter (menu->group), g_variant_builder_end (&builder)); |
||
280 | } |
||
281 | } |
||
282 | |||
283 | static void |
||
284 | g_menu_exporter_menu_prepare (GMenuExporterMenu *menu) |
||
285 | { |
||
286 | gint n_items; |
||
287 | |||
288 | g_assert (menu->item_links == NULL); |
||
289 | |||
290 | if (g_menu_model_is_mutable (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 (g_menu_exporter_link_free); |
||
295 | |||
296 | n_items = g_menu_model_get_n_items (menu->model); |
||
297 | if (n_items) |
||
298 | g_menu_exporter_menu_items_changed (menu->model, 0, 0, n_items, menu); |
||
299 | } |
||
300 | |||
301 | static GMenuExporterMenu * |
||
302 | g_menu_exporter_menu_new (GMenuExporterGroup *group, |
||
303 | guint id, |
||
304 | GMenuModel *model) |
||
305 | { |
||
306 | GMenuExporterMenu *menu; |
||
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 _GMenuExporterGroup |
||
319 | { |
||
320 | GMenuExporter *exporter; |
||
321 | guint id; |
||
322 | |||
323 | GHashTable *menus; |
||
324 | guint next_menu_id; |
||
325 | gboolean prepared; |
||
326 | |||
327 | gint subscribed; |
||
328 | }; |
||
329 | |||
330 | static void |
||
331 | g_menu_exporter_group_check_if_useless (GMenuExporterGroup *group) |
||
332 | { |
||
333 | if (g_hash_table_size (group->menus) == 0 && group->subscribed == 0) |
||
334 | { |
||
335 | g_menu_exporter_remove_group (group->exporter, group->id); |
||
336 | |||
337 | g_hash_table_unref (group->menus); |
||
338 | |||
339 | g_slice_free (GMenuExporterGroup, group); |
||
340 | } |
||
341 | } |
||
342 | |||
343 | static void |
||
344 | g_menu_exporter_group_subscribe (GMenuExporterGroup *group, |
||
345 | GVariantBuilder *builder) |
||
346 | { |
||
347 | GHashTableIter iter; |
||
348 | gpointer key, val; |
||
349 | |||
350 | if (!group->prepared) |
||
351 | { |
||
352 | GMenuExporterMenu *menu; |
||
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 (group->menus, 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, group->menus); |
||
375 | while (g_hash_table_iter_next (&iter, &key, &val)) |
||
376 | { |
||
377 | guint id = GPOINTER_TO_INT (key); |
||
378 | GMenuExporterMenu *menu = val; |
||
379 | |||
380 | if (!g_sequence_is_empty (menu->item_links)) |
||
381 | { |
||
382 | g_variant_builder_open (builder, G_VARIANT_TYPE ("(uuaa{sv})")); |
||
383 | g_variant_builder_add (builder, "u", group->id); |
||
384 | g_variant_builder_add (builder, "u", id); |
||
385 | g_variant_builder_add_value (builder, g_menu_exporter_menu_list (menu)); |
||
386 | g_variant_builder_close (builder); |
||
387 | } |
||
388 | } |
||
389 | } |
||
390 | |||
391 | static void |
||
392 | g_menu_exporter_group_unsubscribe (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 | g_menu_exporter_group_get_exporter (GMenuExporterGroup *group) |
||
404 | { |
||
405 | return group->exporter; |
||
406 | } |
||
407 | |||
408 | static gboolean |
||
409 | g_menu_exporter_group_is_subscribed (GMenuExporterGroup *group) |
||
410 | { |
||
411 | return group->subscribed > 0; |
||
412 | } |
||
413 | |||
414 | static guint |
||
415 | g_menu_exporter_group_get_id (GMenuExporterGroup *group) |
||
416 | { |
||
417 | return group->id; |
||
418 | } |
||
419 | |||
420 | static void |
||
421 | g_menu_exporter_group_remove_menu (GMenuExporterGroup *group, |
||
422 | guint id) |
||
423 | { |
||
424 | g_hash_table_remove (group->menus, GINT_TO_POINTER (id)); |
||
425 | |||
426 | g_menu_exporter_group_check_if_useless (group); |
||
427 | } |
||
428 | |||
429 | static GMenuExporterMenu * |
||
430 | g_menu_exporter_group_add_menu (GMenuExporterGroup *group, |
||
431 | GMenuModel *model) |
||
432 | { |
||
433 | GMenuExporterMenu *menu; |
||
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 (group->menus, GINT_TO_POINTER (id), menu); |
||
439 | |||
440 | if (group->prepared) |
||
441 | g_menu_exporter_menu_prepare (menu); |
||
442 | |||
443 | return menu; |
||
444 | } |
||
445 | |||
446 | static GMenuExporterGroup * |
||
447 | g_menu_exporter_group_new (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 _GMenuExporterRemote |
||
463 | { |
||
464 | GMenuExporter *exporter; |
||
465 | GHashTable *watches; |
||
466 | guint watch_id; |
||
467 | }; |
||
468 | |||
469 | static void |
||
470 | g_menu_exporter_remote_subscribe (GMenuExporterRemote *remote, |
||
471 | guint group_id, |
||
472 | GVariantBuilder *builder) |
||
473 | { |
||
474 | GMenuExporterGroup *group; |
||
475 | guint count; |
||
476 | |||
477 | count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id)); |
||
478 | g_hash_table_insert (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 (remote->exporter, group_id); |
||
482 | g_menu_exporter_group_subscribe (group, builder); |
||
483 | } |
||
484 | |||
485 | static void |
||
486 | g_menu_exporter_remote_unsubscribe (GMenuExporterRemote *remote, |
||
487 | guint group_id) |
||
488 | { |
||
489 | GMenuExporterGroup *group; |
||
490 | guint count; |
||
491 | |||
492 | count = (gsize) g_hash_table_lookup (remote->watches, GINT_TO_POINTER (group_id)); |
||
493 | |||
494 | if (count == 0) |
||
495 | return; |
||
496 | |||
497 | if (count != 1) |
||
498 | g_hash_table_insert (remote->watches, GINT_TO_POINTER (group_id), GINT_TO_POINTER (count - 1)); |
||
499 | else |
||
500 | g_hash_table_remove (remote->watches, GINT_TO_POINTER (group_id)); |
||
501 | |||
502 | group = g_menu_exporter_lookup_group (remote->exporter, group_id); |
||
503 | g_menu_exporter_group_unsubscribe (group, 1); |
||
504 | } |
||
505 | |||
506 | static gboolean |
||
507 | g_menu_exporter_remote_has_subscriptions (GMenuExporterRemote *remote) |
||
508 | { |
||
509 | return g_hash_table_size (remote->watches) != 0; |
||
510 | } |
||
511 | |||
512 | static void |
||
513 | g_menu_exporter_remote_free (gpointer data) |
||
514 | { |
||
515 | GMenuExporterRemote *remote = data; |
||
516 | GHashTableIter iter; |
||
517 | gpointer key, val; |
||
518 | |||
519 | g_hash_table_iter_init (&iter, remote->watches); |
||
520 | while (g_hash_table_iter_next (&iter, &key, &val)) |
||
521 | { |
||
522 | GMenuExporterGroup *group; |
||
523 | |||
524 | group = g_menu_exporter_lookup_group (remote->exporter, GPOINTER_TO_INT (key)); |
||
525 | g_menu_exporter_group_unsubscribe (group, GPOINTER_TO_INT (val)); |
||
526 | } |
||
527 | |||
528 | g_bus_unwatch_name (remote->watch_id); |
||
529 | g_hash_table_unref (remote->watches); |
||
530 | |||
531 | g_slice_free (GMenuExporterRemote, remote); |
||
532 | } |
||
533 | |||
534 | static GMenuExporterRemote * |
||
535 | g_menu_exporter_remote_new (GMenuExporter *exporter, |
||
536 | guint watch_id) |
||
537 | { |
||
538 | GMenuExporterRemote *remote; |
||
539 | |||
540 | remote = g_slice_new0 (GMenuExporterRemote); |
||
541 | remote->exporter = exporter; |
||
542 | remote->watches = g_hash_table_new (NULL, NULL); |
||
543 | remote->watch_id = watch_id; |
||
544 | |||
545 | return remote; |
||
546 | } |
||
547 | |||
548 | /* {{{1 GMenuExporter */ |
||
549 | |||
550 | struct _GMenuExporter |
||
551 | { |
||
552 | GDBusConnection *connection; |
||
553 | gchar *object_path; |
||
554 | guint registration_id; |
||
555 | GHashTable *groups; |
||
556 | guint next_group_id; |
||
557 | |||
558 | GMenuExporterMenu *root; |
||
559 | GHashTable *remotes; |
||
560 | }; |
||
561 | |||
562 | static void |
||
563 | g_menu_exporter_name_vanished (GDBusConnection *connection, |
||
564 | const gchar *name, |
||
565 | gpointer user_data) |
||
566 | { |
||
567 | GMenuExporter *exporter = user_data; |
||
568 | |||
569 | /* connection == NULL when we get called because the connection closed */ |
||
570 | g_assert (exporter->connection == connection || connection == NULL); |
||
571 | |||
572 | g_hash_table_remove (exporter->remotes, name); |
||
573 | } |
||
574 | |||
575 | static GVariant * |
||
576 | g_menu_exporter_subscribe (GMenuExporter *exporter, |
||
577 | const gchar *sender, |
||
578 | GVariant *group_ids) |
||
579 | { |
||
580 | GMenuExporterRemote *remote; |
||
581 | GVariantBuilder builder; |
||
582 | GVariantIter iter; |
||
583 | guint32 id; |
||
584 | |||
585 | remote = g_hash_table_lookup (exporter->remotes, sender); |
||
586 | |||
587 | if (remote == NULL) |
||
588 | { |
||
589 | guint watch_id; |
||
590 | |||
591 | watch_id = g_bus_watch_name_on_connection (exporter->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE, |
||
592 | NULL, g_menu_exporter_name_vanished, exporter, NULL); |
||
593 | remote = g_menu_exporter_remote_new (exporter, watch_id); |
||
594 | g_hash_table_insert (exporter->remotes, g_strdup (sender), remote); |
||
595 | } |
||
596 | |||
597 | g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a(uuaa{sv}))")); |
||
598 | |||
599 | g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(uuaa{sv})")); |
||
600 | |||
601 | g_variant_iter_init (&iter, group_ids); |
||
602 | while (g_variant_iter_next (&iter, "u", &id)) |
||
603 | g_menu_exporter_remote_subscribe (remote, id, &builder); |
||
604 | |||
605 | g_variant_builder_close (&builder); |
||
606 | |||
607 | return g_variant_builder_end (&builder); |
||
608 | } |
||
609 | |||
610 | static void |
||
611 | g_menu_exporter_unsubscribe (GMenuExporter *exporter, |
||
612 | const gchar *sender, |
||
613 | GVariant *group_ids) |
||
614 | { |
||
615 | GMenuExporterRemote *remote; |
||
616 | GVariantIter iter; |
||
617 | guint32 id; |
||
618 | |||
619 | remote = g_hash_table_lookup (exporter->remotes, sender); |
||
620 | |||
621 | if (remote == NULL) |
||
622 | return; |
||
623 | |||
624 | g_variant_iter_init (&iter, group_ids); |
||
625 | while (g_variant_iter_next (&iter, "u", &id)) |
||
626 | g_menu_exporter_remote_unsubscribe (remote, id); |
||
627 | |||
628 | if (!g_menu_exporter_remote_has_subscriptions (remote)) |
||
629 | g_hash_table_remove (exporter->remotes, sender); |
||
630 | } |
||
631 | |||
632 | static void |
||
633 | g_menu_exporter_report (GMenuExporter *exporter, |
||
634 | GVariant *report) |
||
635 | { |
||
636 | GVariantBuilder builder; |
||
637 | |||
638 | g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); |
||
639 | g_variant_builder_open (&builder, G_VARIANT_TYPE_ARRAY); |
||
640 | g_variant_builder_add_value (&builder, report); |
||
641 | g_variant_builder_close (&builder); |
||
642 | |||
643 | g_dbus_connection_emit_signal (exporter->connection, |
||
644 | NULL, |
||
645 | exporter->object_path, |
||
646 | "org.gtk.Menus", "Changed", |
||
647 | g_variant_builder_end (&builder), |
||
648 | NULL); |
||
649 | } |
||
650 | |||
651 | static void |
||
652 | g_menu_exporter_remove_group (GMenuExporter *exporter, |
||
653 | guint id) |
||
654 | { |
||
655 | g_hash_table_remove (exporter->groups, GINT_TO_POINTER (id)); |
||
656 | } |
||
657 | |||
658 | static GMenuExporterGroup * |
||
659 | g_menu_exporter_lookup_group (GMenuExporter *exporter, |
||
660 | guint group_id) |
||
661 | { |
||
662 | GMenuExporterGroup *group; |
||
663 | |||
664 | group = g_hash_table_lookup (exporter->groups, GINT_TO_POINTER (group_id)); |
||
665 | |||
666 | if (group == NULL) |
||
667 | { |
||
668 | group = g_menu_exporter_group_new (exporter, group_id); |
||
669 | g_hash_table_insert (exporter->groups, GINT_TO_POINTER (group_id), group); |
||
670 | } |
||
671 | |||
672 | return group; |
||
673 | } |
||
674 | |||
675 | static GMenuExporterGroup * |
||
676 | g_menu_exporter_create_group (GMenuExporter *exporter) |
||
677 | { |
||
678 | GMenuExporterGroup *group; |
||
679 | guint id; |
||
680 | |||
681 | id = exporter->next_group_id++; |
||
682 | group = g_menu_exporter_group_new (exporter, id); |
||
683 | g_hash_table_insert (exporter->groups, GINT_TO_POINTER (id), group); |
||
684 | |||
685 | return group; |
||
686 | } |
||
687 | |||
688 | static void |
||
689 | g_menu_exporter_free (gpointer user_data) |
||
690 | { |
||
691 | GMenuExporter *exporter = user_data; |
||
692 | |||
693 | g_menu_exporter_menu_free (exporter->root); |
||
694 | g_hash_table_unref (exporter->remotes); |
||
695 | g_hash_table_unref (exporter->groups); |
||
696 | g_object_unref (exporter->connection); |
||
697 | g_free (exporter->object_path); |
||
698 | |||
699 | g_slice_free (GMenuExporter, exporter); |
||
700 | } |
||
701 | |||
702 | static void |
||
703 | g_menu_exporter_method_call (GDBusConnection *connection, |
||
704 | const gchar *sender, |
||
705 | const gchar *object_path, |
||
706 | const gchar *interface_name, |
||
707 | const gchar *method_name, |
||
708 | GVariant *parameters, |
||
709 | GDBusMethodInvocation *invocation, |
||
710 | gpointer user_data) |
||
711 | { |
||
712 | GMenuExporter *exporter = user_data; |
||
713 | GVariant *group_ids; |
||
714 | |||
715 | group_ids = g_variant_get_child_value (parameters, 0); |
||
716 | |||
717 | if (g_str_equal (method_name, "Start")) |
||
718 | g_dbus_method_invocation_return_value (invocation, g_menu_exporter_subscribe (exporter, sender, group_ids)); |
||
719 | |||
720 | else if (g_str_equal (method_name, "End")) |
||
721 | { |
||
722 | g_menu_exporter_unsubscribe (exporter, sender, group_ids); |
||
723 | g_dbus_method_invocation_return_value (invocation, NULL); |
||
724 | } |
||
725 | |||
726 | else |
||
727 | g_assert_not_reached (); |
||
728 | |||
729 | g_variant_unref (group_ids); |
||
730 | } |
||
731 | |||
732 | /* {{{1 Public API */ |
||
733 | |||
734 | /** |
||
735 | * g_dbus_connection_export_menu_model: |
||
736 | * @connection: a #GDBusConnection |
||
737 | * @object_path: a D-Bus object path |
||
738 | * @menu: a #GMenuModel |
||
739 | * @error: return location for an error, or %NULL |
||
740 | * |
||
741 | * Exports @menu on @connection at @object_path. |
||
742 | * |
||
743 | * The implemented D-Bus API should be considered private. |
||
744 | * It is subject to change in the future. |
||
745 | * |
||
746 | * An object path can only have one menu model exported on it. If this |
||
747 | * constraint is violated, the export will fail and 0 will be |
||
748 | * returned (with @error set accordingly). |
||
749 | * |
||
750 | * You can unexport the menu model using |
||
751 | * g_dbus_connection_unexport_menu_model() with the return value of |
||
752 | * this function. |
||
753 | * |
||
754 | * Returns: the ID of the export (never zero), or 0 in case of failure |
||
755 | * |
||
756 | * Since: 2.32 |
||
757 | */ |
||
758 | guint |
||
759 | g_dbus_connection_export_menu_model (GDBusConnection *connection, |
||
760 | const gchar *object_path, |
||
761 | GMenuModel *menu, |
||
762 | GError **error) |
||
763 | { |
||
764 | const GDBusInterfaceVTable vtable = { |
||
765 | g_menu_exporter_method_call, |
||
766 | }; |
||
767 | GMenuExporter *exporter; |
||
768 | guint id; |
||
769 | |||
770 | exporter = g_slice_new0 (GMenuExporter); |
||
771 | |||
772 | id = g_dbus_connection_register_object (connection, object_path, org_gtk_Menus_get_interface (), |
||
773 | &vtable, exporter, g_menu_exporter_free, error); |
||
774 | |||
775 | if (id == 0) |
||
776 | { |
||
777 | g_slice_free (GMenuExporter, exporter); |
||
778 | return 0; |
||
779 | } |
||
780 | |||
781 | exporter->connection = g_object_ref (connection); |
||
782 | exporter->object_path = g_strdup (object_path); |
||
783 | exporter->groups = g_hash_table_new (NULL, NULL); |
||
784 | exporter->remotes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_menu_exporter_remote_free); |
||
785 | exporter->root = g_menu_exporter_group_add_menu (g_menu_exporter_create_group (exporter), menu); |
||
786 | |||
787 | return id; |
||
788 | } |
||
789 | |||
790 | /** |
||
791 | * g_dbus_connection_unexport_menu_model: |
||
792 | * @connection: a #GDBusConnection |
||
793 | * @export_id: the ID from g_dbus_connection_export_menu_model() |
||
794 | * |
||
795 | * Reverses the effect of a previous call to |
||
796 | * g_dbus_connection_export_menu_model(). |
||
797 | * |
||
798 | * It is an error to call this function with an ID that wasn't returned |
||
799 | * from g_dbus_connection_export_menu_model() or to call it with the |
||
800 | * same ID more than once. |
||
801 | * |
||
802 | * Since: 2.32 |
||
803 | */ |
||
804 | void |
||
805 | g_dbus_connection_unexport_menu_model (GDBusConnection *connection, |
||
806 | guint export_id) |
||
807 | { |
||
808 | g_dbus_connection_unregister_object (connection, export_id); |
||
809 | } |
||
810 | |||
811 | /* {{{1 Epilogue */ |
||
812 | /* vim:set foldmethod=marker: */ |