1 /*
2 * vala-panel-appmenu
3 * Copyright (C) 2018 Konstantin Pugin <ria.freelander@gmail.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <inttypes.h>
20
21 #include "debug.h"
22 #include "definitions.h"
23 #include "item.h"
24 #include "model.h"
25 #include "section.h"
26
27 struct _DBusMenuModel
28 {
29 GMenuModel parent_instance;
30
31 uint parent_id;
32 uint current_revision;
33 GCancellable *cancellable;
34 DBusMenuXml *xml;
35 GActionGroup *received_action_group;
36 GSequence *items;
37 bool layout_update_required;
38 bool layout_update_in_progress;
39 };
40
41 static const char *property_names[] = { "accessible-desc",
42 "children-display",
43 "disposition",
44 "enabled",
45 "icon-data",
46 "icon-name",
47 "label",
48 "shortcut",
49 "toggle-type",
50 "toggle-state",
51 "type",
52 "visible",
53 NULL };
54 enum
55 {
56 PROP_NULL = 0,
57 PROP_XML = 1,
58 PROP_ACTION_GROUP = 2,
59 PROP_PARENT_ID = 3,
60 NUM_PROPS
61 };
62
63 static GParamSpec *properties[NUM_PROPS] = { NULL };
64
65 static DBusMenuItem *dbus_menu_model_find(DBusMenuModel *menu, uint item_id);
66 static DBusMenuItem *dbus_menu_model_find_section(DBusMenuModel *menu, uint section_num);
67 static GSequenceIter *dbus_menu_model_find_place(DBusMenuModel *menu, uint section_num, int place);
68
G_DEFINE_TYPE(DBusMenuModel,dbus_menu_model,G_TYPE_MENU_MODEL)69 G_DEFINE_TYPE(DBusMenuModel, dbus_menu_model, G_TYPE_MENU_MODEL)
70
71 static gint dbus_menu_model_get_n_items(GMenuModel *model)
72 {
73 DBusMenuModel *menu = (DBusMenuModel *)(model);
74 GSequenceIter *last_iter = g_sequence_iter_prev(g_sequence_get_end_iter(menu->items));
75 DBusMenuItem *last = g_sequence_get(last_iter);
76 return last->section_num + 1;
77 }
78
dbus_menu_model_get_item_attributes(GMenuModel * model,gint position,GHashTable ** table)79 static void dbus_menu_model_get_item_attributes(GMenuModel *model, gint position,
80 GHashTable **table)
81 {
82 DBusMenuModel *menu = DBUS_MENU_MODEL(model);
83 for (GSequenceIter *iter = g_sequence_get_begin_iter(menu->items);
84 !g_sequence_iter_is_end(iter);
85 iter = g_sequence_iter_next(iter))
86 {
87 DBusMenuItem *item = (DBusMenuItem *)g_sequence_get(iter);
88 if (item->section_num == position && item->place == -1)
89 {
90 *table = g_hash_table_ref(item->attrs);
91 return;
92 }
93 }
94 }
95
dbus_menu_model_get_item_links(GMenuModel * model,gint position,GHashTable ** table)96 static void dbus_menu_model_get_item_links(GMenuModel *model, gint position, GHashTable **table)
97 {
98 DBusMenuModel *menu = DBUS_MENU_MODEL(model);
99 for (GSequenceIter *iter = g_sequence_get_begin_iter(menu->items);
100 !g_sequence_iter_is_end(iter);
101 iter = g_sequence_iter_next(iter))
102 {
103 DBusMenuItem *item = (DBusMenuItem *)g_sequence_get(iter);
104 if (item->section_num == position && item->place == -1)
105 {
106 *table = g_hash_table_ref(item->links);
107 return;
108 }
109 }
110 }
111
dbus_menu_model_is_mutable(GMenuModel * model)112 static int dbus_menu_model_is_mutable(GMenuModel *model)
113 {
114 return true;
115 }
116
117 struct layout_data
118 {
119 GMenuModel *model;
120 int section_num;
121 uint pos;
122 uint old_num;
123 uint new_num;
124 };
125
dbus_menu_model_items(DBusMenuModel * model)126 GSequence *dbus_menu_model_items(DBusMenuModel *model)
127 {
128 return model->items;
129 }
130
queue_compare_func(const struct layout_data * a,const struct layout_data * b)131 int queue_compare_func(const struct layout_data *a, const struct layout_data *b)
132 {
133 if (a->model != b->model)
134 return DBUS_MENU_IS_MODEL(a->model) ? -1 : 1;
135 else if (a->old_num != b->old_num)
136 return b->old_num - a->old_num;
137 else if (a->new_num != b->new_num)
138 return b->new_num - a->new_num;
139 else if (a->pos != b->pos)
140 return b->pos - a->pos;
141 return 0;
142 }
143
dbus_menu_model_sort_func(gconstpointer a,gconstpointer b,G_GNUC_UNUSED void * user_data)144 static int dbus_menu_model_sort_func(gconstpointer a, gconstpointer b,
145 G_GNUC_UNUSED void *user_data)
146 {
147 DBusMenuItem *aitem = (DBusMenuItem *)a;
148 DBusMenuItem *bitem = (DBusMenuItem *)b;
149
150 if (bitem->section_num != aitem->section_num)
151 return aitem->section_num - bitem->section_num;
152
153 return aitem->place - bitem->place;
154 }
155
add_signal_to_queue(DBusMenuModel * model,GQueue * queue,int sect_num,int pos,int removed,int added)156 static void add_signal_to_queue(DBusMenuModel *model, GQueue *queue, int sect_num, int pos,
157 int removed, int added)
158 {
159 struct layout_data *data = g_new0(struct layout_data, 1);
160 if (sect_num >= 0)
161 {
162 DBusMenuItem *item = dbus_menu_model_find_section(model, sect_num);
163 data->model = G_MENU_MODEL(g_hash_table_lookup(item->links, G_MENU_LINK_SECTION));
164 }
165 else
166 {
167 data->model = G_MENU_MODEL(model);
168 }
169 data->section_num = sect_num;
170 data->pos = pos;
171 data->old_num = removed;
172 data->new_num = added;
173 gpointer l = g_queue_find_custom(queue, data, (GCompareFunc)queue_compare_func);
174 if (!l)
175 g_queue_push_head(queue, data);
176 }
177
queue_emit_all(GQueue * queue)178 static bool queue_emit_all(GQueue *queue)
179 {
180 struct layout_data *index = NULL;
181 while ((index = (struct layout_data *)g_queue_pop_head(queue)))
182 {
183 g_menu_model_items_changed(index->model,
184 index->pos,
185 index->old_num,
186 index->new_num);
187 g_free(index);
188 }
189 return G_SOURCE_REMOVE;
190 }
191
preload_idle(DBusMenuItem * item)192 static bool preload_idle(DBusMenuItem *item)
193 {
194 dbus_menu_item_preload(item);
195 return G_SOURCE_REMOVE;
196 }
197
menu_item_copy_and_load(DBusMenuModel * menu,DBusMenuItem * old,DBusMenuItem * new_item)198 static void menu_item_copy_and_load(DBusMenuModel *menu, DBusMenuItem *old, DBusMenuItem *new_item)
199 {
200 dbus_menu_item_copy_submenu(old, new_item, menu);
201 dbus_menu_item_generate_action(new_item, menu);
202 // It is a preload hack. If this is a toplevel menu, we need to fetch menu under toplevel to
203 // avoid menu jumping bug. Now we need to use it for all menus - no menus are preloaded,
204 // AFAIK
205 dbus_menu_item_update_enabled(new_item, true);
206 new_item->toggled = true;
207 g_timeout_add_full(100, 300, (GSourceFunc)preload_idle, new_item, NULL);
208 }
209
210 // We deal only with layouts with depth 1 (not all)
layout_parse(DBusMenuModel * menu,GVariant * layout)211 static void layout_parse(DBusMenuModel *menu, GVariant *layout)
212 {
213 guint id;
214 GVariant *props;
215 GVariant *items;
216 if (!g_variant_is_of_type(layout, G_VARIANT_TYPE("(ia{sv}av)")))
217 {
218 g_warning(
219 "Type of return value for 'layout' property in "
220 "'GetLayout' call should be '(ia{sv}av)' but got '%s'",
221 g_variant_get_type_string(layout));
222
223 return;
224 }
225 if (menu->layout_update_in_progress)
226 return;
227 menu->layout_update_required = false;
228 menu->layout_update_in_progress = true;
229 g_variant_get(layout, "(i@a{sv}@av)", &id, &props, &items);
230 g_variant_unref(props);
231 g_autoptr(GQueue) signal_queue = g_queue_new();
232 GVariantIter iter;
233 GVariant *child;
234 // Start parsing. We need to track section number, and also GSequenceIter to
235 // current section. Also we track change position, number of added and removed
236 // items for current section
237 uint section_num = 0;
238 uint place = 0;
239 uint old_sections = g_menu_model_get_n_items(G_MENU_MODEL(menu));
240 uint added = 0;
241 int change_pos = -1;
242 GSequenceIter *current_iter = g_sequence_get_begin_iter(menu->items);
243 g_variant_iter_init(&iter, items);
244 while ((child = g_variant_iter_next_value(&iter)))
245 {
246 GVariant *value = g_variant_get_variant(child);
247 guint cid;
248 GVariant *cprops;
249 GVariant *citems;
250 g_variant_get(value, "(i@a{sv}@av)", &cid, &cprops, &citems);
251 g_variant_unref(citems);
252
253 DBusMenuItem *old = NULL;
254 DBusMenuItem *new_item = dbus_menu_item_new(cid, menu, cprops);
255 // We receive a section (separator or x-kde-title)
256 if (new_item->action_type == DBUS_MENU_ACTION_SECTION)
257 {
258 bool is_valid_section = !new_item->toggled && place > 0;
259 // Section is valid, so, parse it
260 if (is_valid_section)
261 {
262 // Do some common tasks: increment section_num and iter
263 ++section_num;
264 place--;
265 new_item->section_num = section_num;
266 new_item->place = -1;
267 GSequenceIter *old_iter =
268 g_sequence_lookup(menu->items,
269 new_item,
270 dbus_menu_model_sort_func,
271 NULL);
272 if (!old_iter)
273 {
274 g_hash_table_insert(
275 new_item->links,
276 G_MENU_LINK_SECTION,
277 dbus_menu_section_model_new(menu, section_num));
278 old_iter =
279 g_sequence_insert_sorted(menu->items,
280 new_item,
281 dbus_menu_model_sort_func,
282 NULL);
283 }
284 else
285 dbus_menu_item_free(new_item);
286 old =
287 (DBusMenuItem *)g_sequence_get(g_sequence_iter_prev(old_iter));
288 int delta = old->place - place;
289 GSequenceIter *place_iter = g_sequence_iter_move(old_iter, -delta);
290 // Cleanup all items in prev section (if there is more items than
291 // current)
292 if (delta > 0)
293 g_sequence_remove_range(place_iter, old_iter);
294 // If we already have this section in old layout, and items to this
295 // section was added and/or removed, we add a signal to signal_queue
296 // about this change. Else do nothing, section signal will do it for
297 // us
298 if ((delta > 0 || added > 0) && section_num <= old_sections)
299 {
300 add_signal_to_queue(menu,
301 signal_queue,
302 section_num - 1,
303 change_pos < 0 ? place + 1 : change_pos,
304 MAX(0, delta),
305 MAX(0, added));
306 }
307 // Update current_section and reset current_iter and added to new
308 // section
309 current_iter = g_sequence_iter_next(old_iter);
310 added = 0;
311 change_pos = -1;
312 place = 0;
313 }
314 // If section was invalid, just free received item.
315 else
316 dbus_menu_item_free(new_item);
317 }
318 else if (!dbus_menu_item_is_firefox_stub(new_item))
319 {
320 new_item->section_num = section_num;
321 new_item->place = place;
322 GSequenceIter *old_iter = g_sequence_lookup(menu->items,
323 new_item,
324 dbus_menu_model_sort_func,
325 NULL);
326 // There is no old item on this place
327 if (!old_iter)
328 {
329 if (!added)
330 change_pos = change_pos < 0 ? place : change_pos;
331 menu_item_copy_and_load(menu, NULL, new_item);
332 current_iter = g_sequence_insert_sorted(menu->items,
333 new_item,
334 dbus_menu_model_sort_func,
335 NULL);
336 added++;
337 }
338 // If there is an old item exists, we need to check this properties
339 else
340 {
341 old = (DBusMenuItem *)g_sequence_get(old_iter);
342 // We should compare properties of old and new item
343 bool diff = !dbus_menu_item_compare_immutable(old, new_item);
344 bool updated = dbus_menu_item_update_props(old, cprops);
345 if (diff)
346 {
347 // Immutable properties was different, replace menu item
348 menu_item_copy_and_load(menu, old, new_item);
349 g_sequence_remove(old_iter);
350 current_iter =
351 g_sequence_insert_sorted(menu->items,
352 new_item,
353 dbus_menu_model_sort_func,
354 NULL);
355 }
356 else
357 {
358 // Just free unneeded item
359 dbus_menu_item_free(new_item);
360 }
361 // If item was updated - add a signal to queue about it, but only if
362 // section was in old layout
363 if ((diff || updated) && section_num < old_sections)
364 {
365 add_signal_to_queue(menu,
366 signal_queue,
367 section_num,
368 place,
369 1,
370 1);
371 }
372 }
373 current_iter = g_sequence_iter_next(current_iter);
374 place++;
375 }
376 else
377 // Just free unnedede item
378 dbus_menu_item_free(new_item);
379 g_variant_unref(cprops);
380 g_variant_unref(value);
381 g_variant_unref(child);
382 }
383 section_num++;
384 int secdiff = old_sections - section_num;
385 if (secdiff > 0)
386 {
387 GSequenceIter *section_iter = dbus_menu_model_find_place(menu, section_num, -1);
388 g_sequence_remove_range(section_iter, g_sequence_get_end_iter(menu->items));
389 }
390 // We need to manage last section's changes. And check its validity
391 if (secdiff >= 0)
392 {
393 place--;
394 DBusMenuItem *old = (DBusMenuItem *)g_sequence_get(
395 g_sequence_iter_prev(g_sequence_get_end_iter(menu->items)));
396 int delta = old->place - place;
397 GSequenceIter *last_iter = g_sequence_get_end_iter(menu->items);
398 GSequenceIter *place_iter = g_sequence_iter_move(last_iter, -delta);
399
400 // Cleanup all items in prev section (if there is more items than
401 // current)
402 if (delta > 0)
403 g_sequence_remove_range(place_iter, last_iter);
404 // If section number is not changed, emit a signal about last section.
405 // Because if we emit it and section will be a part of sections signal, this can
406 // duplicate menu items
407 if ((delta > 0 || added > 0))
408 {
409 add_signal_to_queue(menu,
410 signal_queue,
411 section_num - 1,
412 change_pos < 0 ? place + 1 : change_pos,
413 MAX(0, delta),
414 MAX(0, added));
415 }
416 }
417 // If sections was changed, add change signal to queue
418 if (secdiff != 0)
419 {
420 add_signal_to_queue(menu,
421 signal_queue,
422 -1,
423 MIN(old_sections, section_num),
424 (secdiff) > 0 ? ABS(secdiff) : 0,
425 (secdiff) < 0 ? ABS(secdiff) : 0);
426 }
427 g_variant_unref(items);
428 // Emit all signals from queus by LIFO order
429 queue_emit_all(signal_queue);
430 }
431
get_layout_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)432 static void get_layout_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
433 {
434 g_autoptr(GVariant) layout = NULL;
435 guint revision;
436 if (!DBUS_MENU_IS_MODEL(user_data))
437 return;
438 DBusMenuModel *menu = DBUS_MENU_MODEL(user_data);
439 g_autoptr(GError) error = NULL;
440 dbus_menu_xml_call_get_layout_finish((DBusMenuXml *)(source_object),
441 &revision,
442 &layout,
443 res,
444 &error);
445 if (error != NULL)
446 {
447 if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
448 g_warning("%s", error->message);
449 return;
450 }
451 layout_parse(menu, layout);
452 menu->layout_update_in_progress = false;
453 if (menu->layout_update_required)
454 dbus_menu_model_update_layout(menu);
455 }
456
dbus_menu_update_item_properties_from_layout_sync(DBusMenuModel * menu,DBusMenuItem * item,int sect_n,int pos)457 static void dbus_menu_update_item_properties_from_layout_sync(DBusMenuModel *menu,
458 DBusMenuItem *item, int sect_n,
459 int pos)
460 {
461 g_return_if_fail(DBUS_MENU_IS_MODEL(menu));
462 g_autoptr(GVariant) props = NULL;
463 g_autoptr(GVariant) items = NULL;
464 g_autoptr(GVariant) layout = NULL;
465 g_autoptr(GError) error = NULL;
466 g_autoptr(GQueue) signal_queue = g_queue_new();
467 guint id, revision;
468 dbus_menu_xml_call_get_layout_sync(menu->xml,
469 item->id,
470 0,
471 property_names,
472 &revision,
473 &layout,
474 menu->cancellable,
475 &error);
476 if (error != NULL)
477 {
478 if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
479 g_warning("%s", error->message);
480 return;
481 }
482 g_variant_get(layout, "(i@a{sv}@av)", &id, &props, &items);
483 bool is_item_updated = dbus_menu_item_update_props(item, props);
484 if (is_item_updated)
485 add_signal_to_queue(menu, signal_queue, sect_n, pos, 1, 1);
486 queue_emit_all(signal_queue);
487 }
488
dbus_menu_model_update_layout_sync(DBusMenuModel * menu)489 G_GNUC_INTERNAL void dbus_menu_model_update_layout_sync(DBusMenuModel *menu)
490 {
491 g_return_if_fail(DBUS_MENU_IS_MODEL(menu));
492 g_autoptr(GVariant) layout = NULL;
493 g_autoptr(GError) error = NULL;
494 guint revision;
495 if (menu->layout_update_in_progress)
496 menu->layout_update_required = true;
497 else
498 dbus_menu_xml_call_get_layout_sync(menu->xml,
499 menu->parent_id,
500 1,
501 property_names,
502 &revision,
503 &layout,
504 menu->cancellable,
505 &error);
506 if (error != NULL)
507 {
508 if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
509 g_warning("%s", error->message);
510 return;
511 }
512 layout_parse(menu, layout);
513 menu->layout_update_in_progress = false;
514 if (menu->layout_update_required)
515 dbus_menu_model_update_layout_sync(menu);
516 }
517
dbus_menu_model_update_layout(DBusMenuModel * menu)518 G_GNUC_INTERNAL void dbus_menu_model_update_layout(DBusMenuModel *menu)
519 {
520 g_return_if_fail(DBUS_MENU_IS_MODEL(menu));
521 if (menu->layout_update_in_progress)
522 menu->layout_update_required = true;
523 else
524 dbus_menu_xml_call_get_layout(menu->xml,
525 menu->parent_id,
526 1,
527 property_names,
528 menu->cancellable,
529 get_layout_cb,
530 menu);
531 }
532
layout_updated_cb(DBusMenuXml * proxy,guint revision,gint parent,DBusMenuModel * menu)533 static void layout_updated_cb(DBusMenuXml *proxy, guint revision, gint parent, DBusMenuModel *menu)
534 {
535 if (!DBUS_MENU_IS_XML(proxy))
536 return;
537 if (((uint)parent == menu->parent_id) && menu->current_revision < revision)
538 {
539 g_debug("Remote attempt to update %u with rev %u\n", parent, revision);
540 dbus_menu_model_update_layout(menu);
541 menu->current_revision = revision;
542 return;
543 }
544 DBusMenuItem *item = dbus_menu_model_find(menu, (uint)parent);
545 if (item != NULL)
546 dbus_menu_update_item_properties_from_layout_sync(menu,
547 item,
548 item->section_num,
549 item->place);
550 }
551
item_activation_requested_cb(DBusMenuXml * proxy,gint id,guint timestamp,DBusMenuModel * menu)552 static void item_activation_requested_cb(DBusMenuXml *proxy, gint id, guint timestamp,
553 DBusMenuModel *menu)
554 {
555 if (!DBUS_MENU_IS_XML(proxy))
556 return;
557 g_autofree char *ordinary_name = g_strdup_printf(ACTION_PREFIX "%u", id);
558 g_action_group_activate_action(menu->received_action_group, ordinary_name, NULL);
559 g_debug("activation requested: id - %d, timestamp - %d", id, timestamp);
560 }
561
items_properties_loop(DBusMenuModel * menu,GVariant * up_props,GQueue * signal_queue,bool is_removal)562 static void items_properties_loop(DBusMenuModel *menu, GVariant *up_props, GQueue *signal_queue,
563 bool is_removal)
564 {
565 GVariantIter iter;
566 guint id;
567 GVariant *props;
568 g_variant_iter_init(&iter, up_props);
569 while (g_variant_iter_loop(&iter, !is_removal ? "(i@a{sv})" : "(i@as)", &id, &props))
570 {
571 DBusMenuItem *item = (DBusMenuItem *)dbus_menu_model_find(menu, id);
572 bool is_item_updated = false;
573 if (item != NULL)
574 {
575 // It is the best what we can do to update a section
576 if (item->action_type == DBUS_MENU_ACTION_SECTION)
577 {
578 // dbus_menu_model_update_layout(menu);
579 }
580 else
581 {
582 is_item_updated = !is_removal
583 ? dbus_menu_item_update_props(item, props)
584 : dbus_menu_item_remove_props(item, props);
585 if (is_item_updated)
586 add_signal_to_queue(menu,
587 signal_queue,
588 item->section_num,
589 item->place,
590 1,
591 1);
592 }
593 }
594 }
595 }
596
items_properties_updated_cb(DBusMenuXml * proxy,GVariant * updated_props,GVariant * removed_props,DBusMenuModel * menu)597 static void items_properties_updated_cb(DBusMenuXml *proxy, GVariant *updated_props,
598 GVariant *removed_props, DBusMenuModel *menu)
599 {
600 if (!DBUS_MENU_IS_XML(proxy))
601 return;
602 if (menu->layout_update_in_progress == true)
603 return;
604 g_autoptr(GQueue) signal_queue = g_queue_new();
605 items_properties_loop(menu, updated_props, signal_queue, false);
606 items_properties_loop(menu, removed_props, signal_queue, true);
607 queue_emit_all(signal_queue);
608 }
609
on_xml_property_changed(DBusMenuModel * model)610 static void on_xml_property_changed(DBusMenuModel *model)
611 {
612 if (!DBUS_MENU_IS_XML(model->xml))
613 return;
614 g_signal_connect(model->xml,
615 "items-properties-updated",
616 G_CALLBACK(items_properties_updated_cb),
617 model);
618 g_signal_connect(model->xml, "layout-updated", G_CALLBACK(layout_updated_cb), model);
619 g_signal_connect(model->xml,
620 "item-activation-requested",
621 G_CALLBACK(item_activation_requested_cb),
622 model);
623 if (model->parent_id == 0)
624 dbus_menu_model_update_layout(model);
625 }
626
dbus_menu_model_new(uint parent_id,DBusMenuModel * parent,DBusMenuXml * xml,GActionGroup * action_group)627 G_GNUC_INTERNAL DBusMenuModel *dbus_menu_model_new(uint parent_id, DBusMenuModel *parent,
628 DBusMenuXml *xml, GActionGroup *action_group)
629 {
630 DBusMenuModel *ret = (DBusMenuModel *)g_object_new(dbus_menu_model_get_type(),
631 "parent-id",
632 parent_id,
633 "xml",
634 xml,
635 "action-group",
636 action_group,
637 NULL);
638 if (parent != NULL)
639 g_object_bind_property(parent, "xml", ret, "xml", G_BINDING_SYNC_CREATE);
640 return ret;
641 }
642
dbus_menu_model_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)643 static void dbus_menu_model_set_property(GObject *object, guint property_id, const GValue *value,
644 GParamSpec *pspec)
645 {
646 DBusMenuModel *menu = (DBusMenuModel *)(object);
647 void *old_xml = menu->xml;
648
649 switch (property_id)
650 {
651 case PROP_XML:
652 menu->xml = DBUS_MENU_XML(g_value_get_object(value));
653 if (menu->xml != NULL && old_xml != menu->xml)
654 {
655 if (old_xml != NULL)
656 g_signal_handlers_disconnect_by_data(old_xml, menu);
657 on_xml_property_changed(menu);
658 }
659 break;
660 case PROP_ACTION_GROUP:
661 menu->received_action_group = G_ACTION_GROUP(g_value_get_object(value));
662 break;
663 case PROP_PARENT_ID:
664 menu->layout_update_required = true;
665 menu->parent_id = g_value_get_uint(value);
666 break;
667 default:
668 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
669 break;
670 }
671 }
672
dbus_menu_model_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)673 static void dbus_menu_model_get_property(GObject *object, guint property_id, GValue *value,
674 GParamSpec *pspec)
675 {
676 DBusMenuModel *menu;
677
678 menu = (DBusMenuModel *)(object);
679
680 switch (property_id)
681 {
682 case PROP_XML:
683 g_value_set_object(value, menu->xml);
684 break;
685 case PROP_PARENT_ID:
686 g_value_set_uint(value, menu->parent_id);
687 break;
688 case PROP_ACTION_GROUP:
689 g_value_set_object(value, menu->received_action_group);
690 break;
691 default:
692 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
693 break;
694 }
695 }
696
dbus_menu_model_is_layout_update_required(DBusMenuModel * model)697 G_GNUC_INTERNAL bool dbus_menu_model_is_layout_update_required(DBusMenuModel *model)
698 {
699 return model->layout_update_required;
700 }
701
dbus_menu_model_set_layout_update_required(DBusMenuModel * model,bool required)702 G_GNUC_INTERNAL void dbus_menu_model_set_layout_update_required(DBusMenuModel *model, bool required)
703 {
704 model->layout_update_required = required;
705 }
706
dbus_menu_model_find(DBusMenuModel * menu,uint item_id)707 static DBusMenuItem *dbus_menu_model_find(DBusMenuModel *menu, uint item_id)
708 {
709 for (GSequenceIter *iter = g_sequence_get_begin_iter(menu->items);
710 !g_sequence_iter_is_end(iter);
711 iter = g_sequence_iter_next(iter))
712 {
713 DBusMenuItem *item = (DBusMenuItem *)g_sequence_get(iter);
714 if (item->id == item_id)
715 return item;
716 }
717 return NULL;
718 }
719
dbus_menu_model_find_place(DBusMenuModel * menu,uint section_num,int place)720 static GSequenceIter *dbus_menu_model_find_place(DBusMenuModel *menu, uint section_num, int place)
721 {
722 for (GSequenceIter *iter = g_sequence_get_begin_iter(menu->items);
723 !g_sequence_iter_is_end(iter);
724 iter = g_sequence_iter_next(iter))
725 {
726 DBusMenuItem *item = (DBusMenuItem *)g_sequence_get(iter);
727 if (item->section_num == section_num && item->place == place)
728 return iter;
729 }
730 return NULL;
731 }
732
dbus_menu_model_find_section(DBusMenuModel * menu,uint section_num)733 static DBusMenuItem *dbus_menu_model_find_section(DBusMenuModel *menu, uint section_num)
734 {
735 return (DBusMenuItem *)g_sequence_get(dbus_menu_model_find_place(menu, section_num, -1));
736 }
737
dbus_menu_model_init(DBusMenuModel * menu)738 static void dbus_menu_model_init(DBusMenuModel *menu)
739 {
740 menu->cancellable = g_cancellable_new();
741 menu->parent_id = UINT_MAX;
742 menu->items = g_sequence_new(dbus_menu_item_free);
743 menu->layout_update_required = true;
744 menu->layout_update_in_progress = false;
745 menu->current_revision = 0;
746 }
747
dbus_menu_model_constructed(GObject * object)748 static void dbus_menu_model_constructed(GObject *object)
749 {
750 G_OBJECT_CLASS(dbus_menu_model_parent_class)->constructed(object);
751 DBusMenuModel *menu = DBUS_MENU_MODEL(object);
752
753 DBusMenuItem *first_section =
754 dbus_menu_item_new_first_section(menu->parent_id, menu->received_action_group);
755 first_section->section_num = 0;
756 first_section->place = -1;
757 g_hash_table_insert(first_section->links,
758 G_MENU_LINK_SECTION,
759 dbus_menu_section_model_new(menu, 0));
760 g_sequence_insert_sorted(menu->items, first_section, dbus_menu_model_sort_func, NULL);
761 }
762
dbus_menu_model_finalize(GObject * object)763 static void dbus_menu_model_finalize(GObject *object)
764 {
765 DBusMenuModel *menu = (DBusMenuModel *)(object);
766 if (menu->xml)
767 g_signal_handlers_disconnect_by_data(menu->xml, menu);
768 g_cancellable_cancel(menu->cancellable);
769 g_clear_object(&menu->cancellable);
770 g_clear_pointer(&menu->items, g_sequence_free);
771
772 G_OBJECT_CLASS(dbus_menu_model_parent_class)->finalize(object);
773 }
774
install_properties(GObjectClass * object_class)775 static void install_properties(GObjectClass *object_class)
776 {
777 properties[PROP_XML] =
778 g_param_spec_object("xml",
779 "xml",
780 "xml",
781 dbus_menu_xml_get_type(),
782 (GParamFlags)(G_PARAM_CONSTRUCT | G_PARAM_READABLE |
783 G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
784
785 properties[PROP_ACTION_GROUP] =
786 g_param_spec_object("action-group",
787 "action-group",
788 "action-group",
789 g_action_group_get_type(),
790 (GParamFlags)(G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
791 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
792 properties[PROP_PARENT_ID] =
793 g_param_spec_uint("parent-id",
794 "parent-id",
795 "parent-id",
796 0,
797 UINT_MAX,
798 0,
799 (GParamFlags)(G_PARAM_CONSTRUCT | G_PARAM_WRITABLE |
800 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
801
802 g_object_class_install_properties(object_class, NUM_PROPS, properties);
803 }
804
dbus_menu_model_class_init(DBusMenuModelClass * klass)805 static void dbus_menu_model_class_init(DBusMenuModelClass *klass)
806 {
807 GMenuModelClass *model_class = G_MENU_MODEL_CLASS(klass);
808 GObjectClass *object_class = G_OBJECT_CLASS(klass);
809
810 object_class->finalize = dbus_menu_model_finalize;
811 object_class->set_property = dbus_menu_model_set_property;
812 object_class->get_property = dbus_menu_model_get_property;
813 object_class->constructed = dbus_menu_model_constructed;
814
815 model_class->is_mutable = dbus_menu_model_is_mutable;
816 model_class->get_n_items = dbus_menu_model_get_n_items;
817 model_class->get_item_attributes = dbus_menu_model_get_item_attributes;
818 model_class->get_item_links = dbus_menu_model_get_item_links;
819 install_properties(object_class);
820 }
821