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 <stdbool.h>
20 
21 #include "dbusmenu-interface.h"
22 #include "definitions.h"
23 #include "item.h"
24 #include "utils.h"
25 
26 #define ITEM_MAGIC 0xDEADBEEF
27 #define item_set_magic(item) (item)->magic = GUINT_TO_POINTER(ITEM_MAGIC)
28 #define item_check_magic(item) (GPOINTER_TO_UINT((item)->magic) == ITEM_MAGIC)
29 
30 #define submenu_str(en) ((en) ? G_MENU_LINK_SUBMENU : DBUS_MENU_DISABLED_SUBMENU)
31 
32 G_GNUC_INTERNAL void dbus_menu_item_free(gpointer data);
33 G_GNUC_INTERNAL DBusMenuItem *dbus_menu_item_copy(DBusMenuItem *src);
34 G_DEFINE_BOXED_TYPE(DBusMenuItem, dbus_menu_item, dbus_menu_item_copy, dbus_menu_item_free)
35 #if 0
36 #include "item-pixbuf.c"
37 #endif
38 
39 static void act_props_try_update(DBusMenuItem *item);
40 
dbus_menu_item_new_first_section(u_int32_t id,GActionGroup * action_group)41 G_GNUC_INTERNAL DBusMenuItem *dbus_menu_item_new_first_section(u_int32_t id,
42                                                                GActionGroup *action_group)
43 {
44 	DBusMenuItem *item = g_slice_new0(DBusMenuItem);
45 	item->id           = id;
46 	item->action_type  = DBUS_MENU_ACTION_SECTION;
47 	item->enabled      = false;
48 	item->toggled      = false;
49 	item->attrs =
50 	    g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
51 	item->links = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
52 	item->ref_action_group = action_group;
53 	item_set_magic(item);
54 	return item;
55 }
56 
dbus_menu_item_new(u_int32_t id,DBusMenuModel * parent_model,GVariant * props)57 G_GNUC_INTERNAL DBusMenuItem *dbus_menu_item_new(u_int32_t id, DBusMenuModel *parent_model,
58                                                  GVariant *props)
59 {
60 	DBusMenuItem *item = g_slice_new0(DBusMenuItem);
61 	DBusMenuXml *xml;
62 	GVariantIter iter;
63 	const char *prop;
64 	GVariant *value;
65 	item_set_magic(item);
66 	item->enabled = true;
67 	item->toggled = false;
68 	item->id      = id;
69 	item->attrs =
70 	    g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
71 	item->links = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
72 	g_object_get(parent_model, "action-group", &item->ref_action_group, "xml", &xml, NULL);
73 	g_variant_iter_init(&iter, props);
74 	// Iterate by immutable properties, it is construct_only
75 	bool action_creator_found = false;
76 	while (g_variant_iter_loop(&iter, "{&sv}", &prop, &value))
77 	{
78 		if (g_strcmp0(prop, DBUS_MENU_PROP_CHILDREN_DISPLAY) == 0)
79 		{
80 			if (value == NULL)
81 			{
82 				g_hash_table_remove(item->attrs, G_MENU_ATTRIBUTE_SUBMENU_ACTION);
83 				continue;
84 			}
85 			else if (g_strcmp0(g_variant_get_string(value, NULL),
86 			                   DBUS_MENU_CHILDREN_DISPLAY_SUBMENU) == 0)
87 			{
88 				item->action_type = DBUS_MENU_ACTION_SUBMENU;
89 				g_autofree char *name =
90 				    dbus_menu_action_get_name(id, item->action_type, true);
91 				g_hash_table_insert(item->attrs,
92 				                    g_strdup(G_MENU_ATTRIBUTE_SUBMENU_ACTION),
93 				                    g_variant_new_string(name));
94 				action_creator_found = true;
95 			}
96 		}
97 		else if (g_strcmp0(prop, DBUS_MENU_PROP_TOGGLE_TYPE) == 0)
98 		{
99 			g_autofree char *name =
100 			    dbus_menu_action_get_name(id, item->action_type, true);
101 			if (g_strcmp0(g_variant_get_string(value, NULL),
102 			              DBUS_MENU_TOGGLE_TYPE_CHECK) == 0)
103 			{
104 				item->action_type = DBUS_MENU_ACTION_CHECKMARK;
105 				g_hash_table_insert(item->attrs,
106 				                    g_strdup(G_MENU_ATTRIBUTE_ACTION),
107 				                    g_variant_new_string(name));
108 				action_creator_found = true;
109 			}
110 			else if (g_strcmp0(g_variant_get_string(value, NULL),
111 			                   DBUS_MENU_TOGGLE_TYPE_RADIO) == 0)
112 			{
113 				item->action_type = DBUS_MENU_ACTION_RADIO;
114 				g_hash_table_insert(item->attrs,
115 				                    g_strdup(G_MENU_ATTRIBUTE_ACTION),
116 				                    g_variant_new_string(name));
117 				GVariant *vstr =
118 				    g_variant_new_string(DBUS_MENU_ACTION_RADIO_SELECTED);
119 				g_hash_table_insert(item->attrs,
120 				                    g_strdup(G_MENU_ATTRIBUTE_TARGET),
121 				                    g_variant_ref_sink(vstr));
122 				action_creator_found = true;
123 			}
124 		}
125 		else if (g_strcmp0(prop, DBUS_MENU_PROP_TYPE) == 0)
126 		{
127 			const char *type = g_variant_get_string(value, NULL);
128 			if (!g_strcmp0(type, DBUS_MENU_TYPE_SEPARATOR))
129 			{
130 				item->action_type    = DBUS_MENU_ACTION_SECTION;
131 				action_creator_found = true;
132 			}
133 			else if (!g_strcmp0(type, DBUS_MENU_TYPE_NORMAL))
134 			{
135 				item->action_type = DBUS_MENU_ACTION_NORMAL;
136 				g_autofree char *name =
137 				    dbus_menu_action_get_name(id, item->action_type, true);
138 				g_hash_table_insert(item->attrs,
139 				                    g_strdup(G_MENU_ATTRIBUTE_ACTION),
140 				                    g_variant_new_string(name));
141 				action_creator_found = true;
142 			}
143 		}
144 		else if (g_strcmp0(prop, "x-kde-title") == 0)
145 		{
146 			item->action_type = DBUS_MENU_ACTION_SECTION;
147 			g_hash_table_insert(item->attrs, g_strdup(G_MENU_ATTRIBUTE_LABEL), value);
148 			action_creator_found = true;
149 		}
150 		else if (!action_creator_found)
151 		{
152 			item->action_type = DBUS_MENU_ACTION_NORMAL;
153 			g_autofree char *name =
154 			    dbus_menu_action_get_name(id, item->action_type, true);
155 			g_hash_table_insert(item->attrs,
156 			                    g_strdup(G_MENU_ATTRIBUTE_ACTION),
157 			                    g_variant_new_string(name));
158 			action_creator_found = true;
159 		}
160 	}
161 	if (item->action_type != DBUS_MENU_ACTION_SECTION)
162 		g_hash_table_insert(item->attrs,
163 		                    g_strdup(G_MENU_ATTRIBUTE_LABEL),
164 		                    g_variant_new_string(""));
165 	dbus_menu_item_update_props(item, props);
166 	return item;
167 }
168 
dbus_menu_item_free(gpointer data)169 G_GNUC_INTERNAL void dbus_menu_item_free(gpointer data)
170 {
171 	DBusMenuItem *item = (DBusMenuItem *)data;
172 	if (item == NULL)
173 		return;
174 	item->magic = NULL;
175 	g_clear_pointer(&item->attrs, g_hash_table_destroy);
176 	g_clear_pointer(&item->links, g_hash_table_destroy);
177 	g_clear_object(&item->ref_action);
178 	g_slice_free(DBusMenuItem, data);
179 }
180 
dbus_menu_item_copy(DBusMenuItem * src)181 G_GNUC_INTERNAL DBusMenuItem *dbus_menu_item_copy(DBusMenuItem *src)
182 {
183 	DBusMenuItem *dst     = g_slice_new0(DBusMenuItem);
184 	dst->id               = src->id;
185 	dst->action_type      = src->action_type;
186 	dst->enabled          = src->enabled;
187 	dst->toggled          = src->toggled;
188 	dst->ref_action       = G_ACTION(g_object_ref(src->ref_action));
189 	dst->ref_action_group = src->ref_action_group;
190 	dst->attrs            = g_hash_table_ref(src->attrs);
191 	dst->links            = g_hash_table_ref(src->links);
192 	return dst;
193 }
194 
attr_update_checked(DBusMenuItem * item,const char * key,GVariant * value)195 static bool attr_update_checked(DBusMenuItem *item, const char *key, GVariant *value)
196 {
197 	GVariant *old  = (GVariant *)g_hash_table_lookup(item->attrs, key);
198 	bool are_equal = false;
199 	if (old != NULL)
200 		are_equal = g_variant_equal(old, value);
201 	if (!are_equal)
202 	{
203 		g_hash_table_insert(item->attrs, g_strdup(key), g_variant_ref_sink(value));
204 		return true;
205 	}
206 	return false;
207 }
208 
dbus_menu_item_is_firefox_stub(DBusMenuItem * item)209 G_GNUC_INTERNAL bool dbus_menu_item_is_firefox_stub(DBusMenuItem *item)
210 {
211 	const char *hidden_when =
212 	    (const char *)g_hash_table_lookup(item->attrs, G_MENU_ATTRIBUTE_HIDDEN_WHEN);
213 	const char *action =
214 	    (const char *)g_hash_table_lookup(item->attrs, G_MENU_ATTRIBUTE_ACTION);
215 	const char *label = (const char *)g_hash_table_lookup(item->attrs, G_MENU_ATTRIBUTE_LABEL);
216 	if (!g_strcmp0(hidden_when, G_MENU_HIDDEN_WHEN_ACTION_MISSING) &&
217 	    !g_strcmp0(action, DBUS_MENU_DISABLED_ACTION) && !g_strcmp0(label, "Label Empty"))
218 		return true;
219 	return false;
220 }
221 
dbus_menu_item_preload(DBusMenuItem * item)222 G_GNUC_INTERNAL void dbus_menu_item_preload(DBusMenuItem *item)
223 {
224 	if (!item_check_magic(item))
225 		return;
226 	if (item->action_type != DBUS_MENU_ACTION_SUBMENU)
227 		return;
228 	int id;
229 	DBusMenuXml *xml = NULL;
230 	bool need_update;
231 	DBusMenuModel *submenu =
232 	    DBUS_MENU_MODEL(g_hash_table_lookup(item->links, submenu_str(item->enabled)));
233 	if (!submenu || !DBUS_MENU_IS_MODEL(submenu))
234 		return;
235 	g_object_get(submenu, "parent-id", &id, "xml", &xml, NULL);
236 	if (!xml || !DBUS_MENU_IS_XML(xml))
237 		return;
238 	dbus_menu_xml_call_event_sync(xml,
239 	                              id,
240 	                              "opened",
241 	                              g_variant_new("v", g_variant_new_int32(0)),
242 	                              CURRENT_TIME,
243 	                              NULL,
244 	                              NULL);
245 	dbus_menu_xml_call_about_to_show_sync(xml, id, (gboolean *)&need_update, NULL, NULL);
246 	need_update = need_update || dbus_menu_model_is_layout_update_required(submenu);
247 	if (need_update)
248 		dbus_menu_model_update_layout(submenu);
249 }
250 
dbus_menu_item_copy_attrs(DBusMenuItem * src,DBusMenuItem * dst)251 G_GNUC_INTERNAL bool dbus_menu_item_copy_attrs(DBusMenuItem *src, DBusMenuItem *dst)
252 {
253 	GHashTableIter iter;
254 	g_hash_table_iter_init(&iter, src->attrs);
255 	bool is_updated = false;
256 	char *key;
257 	GVariant *value;
258 	while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&value))
259 	{
260 		is_updated = attr_update_checked(dst, key, value) || is_updated;
261 	}
262 	return is_updated;
263 }
264 
dbus_menu_item_update_enabled(DBusMenuItem * item,bool enabled)265 G_GNUC_INTERNAL bool dbus_menu_item_update_enabled(DBusMenuItem *item, bool enabled)
266 {
267 	bool updated = false;
268 	if (item->action_type == DBUS_MENU_ACTION_SUBMENU && !item->toggled)
269 	{
270 		DBusMenuModel *submenu =
271 		    DBUS_MENU_MODEL(g_hash_table_lookup(item->links, submenu_str(item->enabled)));
272 		if (item->enabled != enabled)
273 		{
274 			if (submenu != NULL)
275 			{
276 				g_object_ref(submenu);
277 				g_hash_table_remove(item->links, submenu_str(item->enabled));
278 				g_hash_table_insert(item->links, submenu_str(enabled), submenu);
279 			}
280 			if (enabled)
281 			{
282 				g_hash_table_remove(item->attrs, G_MENU_ATTRIBUTE_ACTION);
283 			}
284 			else
285 			{
286 				g_hash_table_insert(item->attrs,
287 				                    g_strdup(G_MENU_ATTRIBUTE_ACTION),
288 				                    g_variant_new_string(
289 				                        DBUS_MENU_DISABLED_ACTION));
290 			}
291 			updated = true;
292 		}
293 	}
294 	item->enabled = enabled;
295 	act_props_try_update(item);
296 	return updated;
297 }
298 
act_props_try_update(DBusMenuItem * item)299 static void act_props_try_update(DBusMenuItem *item)
300 {
301 	if (!G_IS_ACTION(item->ref_action))
302 		return;
303 	g_simple_action_set_enabled(G_SIMPLE_ACTION(item->ref_action), item->enabled);
304 	if (item->action_type == DBUS_MENU_ACTION_RADIO)
305 	{
306 		dbus_menu_action_lock(item->ref_action);
307 		g_action_change_state((item->ref_action),
308 		                      g_variant_new_string(
309 		                          item->toggled ? DBUS_MENU_ACTION_RADIO_SELECTED
310 		                                        : DBUS_MENU_ACTION_RADIO_UNSELECTED));
311 		dbus_menu_action_unlock(item->ref_action);
312 	}
313 	else if (item->action_type == DBUS_MENU_ACTION_CHECKMARK)
314 	{
315 		dbus_menu_action_lock(item->ref_action);
316 		g_action_change_state((item->ref_action), g_variant_new_boolean(item->toggled));
317 		dbus_menu_action_unlock(item->ref_action);
318 	}
319 }
320 
dbus_menu_item_update_shortcut(DBusMenuItem * item,GVariant * value)321 static bool dbus_menu_item_update_shortcut(DBusMenuItem *item, GVariant *value)
322 {
323 	GString *new_accel_string = g_string_new(NULL);
324 	if (g_variant_n_children(value) != 1)
325 		g_debug("Unable to parse shortcut correctly, too many keys. Taking first.");
326 
327 	GVariantIter iter;
328 	GVariant *child = g_variant_get_child_value(value, 0);
329 	g_variant_iter_init(&iter, child);
330 	char *string;
331 
332 	while (g_variant_iter_loop(&iter, "s", &string))
333 	{
334 		if (g_strcmp0(string, DBUS_MENU_SHORTCUT_CONTROL) == 0)
335 			g_string_append(new_accel_string, DBUS_MENUMODEL_SHORTCUT_CONTROL);
336 		else if (g_strcmp0(string, DBUS_MENU_SHORTCUT_ALT) == 0)
337 			g_string_append(new_accel_string, DBUS_MENUMODEL_SHORTCUT_ALT);
338 		else if (g_strcmp0(string, DBUS_MENU_SHORTCUT_SHIFT) == 0)
339 			g_string_append(new_accel_string, DBUS_MENUMODEL_SHORTCUT_SHIFT);
340 		else if (g_strcmp0(string, DBUS_MENU_SHORTCUT_SUPER) == 0)
341 			g_string_append(new_accel_string, DBUS_MENUMODEL_SHORTCUT_SUPER);
342 		else
343 			g_string_append(new_accel_string, string);
344 	}
345 	g_variant_unref(child);
346 	g_autofree char *str = g_string_free(new_accel_string, false);
347 	GVariant *new_accel  = g_variant_new_string(str);
348 	bool updated         = attr_update_checked(item, G_MENU_ATTRIBUTE_ACCEL, new_accel);
349 	if (!updated)
350 		g_variant_unref(new_accel);
351 	return updated;
352 }
353 
dbus_menu_item_update_props(DBusMenuItem * item,GVariant * props)354 G_GNUC_INTERNAL bool dbus_menu_item_update_props(DBusMenuItem *item, GVariant *props)
355 {
356 	GVariantIter iter;
357 	const char *prop;
358 	GVariant *value;
359 	bool properties_is_updated = false;
360 
361 	g_variant_iter_init(&iter, props);
362 	while (g_variant_iter_loop(&iter, "{&sv}", &prop, &value))
363 	{
364 		if (g_strcmp0(prop, "accessible-desc") == 0)
365 		{
366 			// TODO: Can we supported this property?
367 			// properties_is_updated = true;
368 		}
369 		else if (g_strcmp0(prop, "enabled") == 0)
370 		{
371 			bool enabled = g_variant_get_boolean(value);
372 			properties_is_updated =
373 			    dbus_menu_item_update_enabled(item, enabled) || properties_is_updated;
374 		}
375 #if 0
376 		else if (g_strcmp0(prop, "icon-data") == 0)
377 		{
378 			// icon-name has more priority
379             if (!g_hash_table_lookup(item->attrs, G_MENU_ATTRIBUTE_ICON))
380 			{
381 				g_autoptr(GIcon) icon = g_icon_new_pixbuf_from_variant(value);
382 				GVariant *value       = g_icon_serialize(icon);
383 				properties_is_updated =
384 				    properties_is_updated ||
385                     attr_update_checked(item,
386 				                                       G_MENU_ATTRIBUTE_ICON,
387 				                                       value);
388 				properties_is_updated =
389 				    properties_is_updated ||
390                     attr_update_checked(item,
391 				                                       G_MENU_ATTRIBUTE_VERB_ICON,
392 				                                       value);
393 			}
394 		}
395 		else if (g_strcmp0(prop, "icon-name") == 0)
396 		{
397 			g_autoptr(GIcon) icon =
398 			    g_themed_icon_new(g_variant_get_string(value, NULL));
399 			GVariant *value             = g_icon_serialize(icon);
400 			g_autoptr(GVariant) boolvar = g_variant_new_boolean(true);
401 			properties_is_updated =
402 			    properties_is_updated ||
403                 attr_update_checked(item, G_MENU_ATTRIBUTE_ICON, value);
404 			properties_is_updated =
405 			    properties_is_updated ||
406                 attr_update_checked(item,
407 			                                       G_MENU_ATTRIBUTE_VERB_ICON,
408 			                                       value);
409 			properties_is_updated =
410 			    properties_is_updated ||
411                 attr_update_checked(item, HAS_ICON_NAME, boolvar);
412 		}
413 #endif
414 		else if (g_strcmp0(prop, "label") == 0)
415 		{
416 			properties_is_updated =
417 			    attr_update_checked(item, G_MENU_ATTRIBUTE_LABEL, value) ||
418 			    properties_is_updated;
419 		}
420 		else if (g_strcmp0(prop, "shortcut") == 0)
421 		{
422 			properties_is_updated =
423 			    dbus_menu_item_update_shortcut(item, value) || properties_is_updated;
424 		}
425 		else if (g_strcmp0(prop, "toggle-state") == 0)
426 		{
427 			item->toggled = g_variant_get_int32(value) > 0;
428 			act_props_try_update(item);
429 		}
430 		else if (g_strcmp0(prop, "visible") == 0)
431 		{
432 			bool vis = g_variant_get_boolean(value);
433 			if (item->action_type == DBUS_MENU_ACTION_SECTION)
434 			{
435 				item->toggled = !vis;
436 			}
437 			else if (vis)
438 			{
439 				g_autofree char *name =
440 				    dbus_menu_action_get_name(item->id, item->action_type, true);
441 				bool found =
442 				    g_hash_table_remove(item->attrs, G_MENU_ATTRIBUTE_HIDDEN_WHEN);
443 				if (found)
444 				{
445 					g_hash_table_insert(item->attrs,
446 					                    g_strdup(G_MENU_ATTRIBUTE_ACTION),
447 					                    g_variant_new_string(name));
448 					properties_is_updated = true;
449 				}
450 			}
451 			else
452 			{
453 				bool found = g_hash_table_contains(item->attrs,
454 				                                   G_MENU_ATTRIBUTE_HIDDEN_WHEN);
455 				if (!found)
456 				{
457 					g_hash_table_insert(item->attrs,
458 					                    g_strdup(G_MENU_ATTRIBUTE_HIDDEN_WHEN),
459 					                    g_variant_new_string(
460 					                        G_MENU_HIDDEN_WHEN_ACTION_MISSING));
461 					g_hash_table_insert(item->attrs,
462 					                    g_strdup(G_MENU_ATTRIBUTE_ACTION),
463 					                    g_variant_new_string(
464 					                        DBUS_MENU_DISABLED_ACTION));
465 					properties_is_updated = true;
466 				}
467 			}
468 		}
469 		else
470 		{
471 			g_debug("updating unsupported property - '%s'", prop);
472 		}
473 	}
474 	return properties_is_updated;
475 }
476 
dbus_menu_item_remove_props(DBusMenuItem * item,GVariant * props)477 G_GNUC_INTERNAL bool dbus_menu_item_remove_props(DBusMenuItem *item, GVariant *props)
478 {
479 	GVariantIter iter;
480 	const char *prop;
481 	bool properties_is_updated = false;
482 
483 	g_variant_iter_init(&iter, props);
484 	while (g_variant_iter_next(&iter, "&s", &prop))
485 	{
486 		if (g_strcmp0(prop, "accessible-desc") == 0)
487 		{
488 			// TODO: Can we support this property?
489 			// properties_is_updated = true;
490 		}
491 		else if (g_strcmp0(prop, "enabled") == 0)
492 		{
493 			bool enabled = true;
494 			dbus_menu_item_update_enabled(item, enabled);
495 		}
496 		else if (g_strcmp0(prop, "icon-name") == 0)
497 		{
498 			if (g_hash_table_lookup(item->attrs, HAS_ICON_NAME))
499 			{
500 				g_hash_table_remove(item->attrs, G_MENU_ATTRIBUTE_ICON);
501 				g_hash_table_remove(item->attrs, G_MENU_ATTRIBUTE_VERB_ICON);
502 				g_hash_table_remove(item->attrs, HAS_ICON_NAME);
503 				properties_is_updated = true;
504 			}
505 		}
506 		else if (g_strcmp0(prop, "icon-data") == 0)
507 		{
508 			if (!g_hash_table_lookup(item->attrs, HAS_ICON_NAME))
509 			{
510 				g_hash_table_remove(item->attrs, G_MENU_ATTRIBUTE_ICON);
511 				g_hash_table_remove(item->attrs, G_MENU_ATTRIBUTE_VERB_ICON);
512 				properties_is_updated = true;
513 			}
514 		}
515 		else if (g_strcmp0(prop, "label") == 0)
516 		{
517 			g_hash_table_remove(item->attrs, G_MENU_ATTRIBUTE_LABEL);
518 			properties_is_updated = true;
519 		}
520 		else if (g_strcmp0(prop, "shortcut") == 0)
521 		{
522 			g_hash_table_remove(item->attrs, G_MENU_ATTRIBUTE_ACCEL);
523 			properties_is_updated = true;
524 		}
525 		else if (g_strcmp0(prop, "visible") == 0)
526 		{
527 			g_autofree char *name =
528 			    dbus_menu_action_get_name(item->id, item->action_type, false);
529 			g_hash_table_remove(item->attrs, G_MENU_ATTRIBUTE_HIDDEN_WHEN);
530 			g_hash_table_insert(item->attrs,
531 			                    g_strdup(G_MENU_ATTRIBUTE_ACTION),
532 			                    g_variant_new_string(name));
533 			properties_is_updated = true;
534 		}
535 		else
536 		{
537 			g_debug("removing unsupported property - '%s'", prop);
538 		}
539 	}
540 	return properties_is_updated;
541 }
542 
dbus_menu_item_compare_func(const DBusMenuItem * a,const DBusMenuItem * b,gpointer user_data)543 G_GNUC_INTERNAL int dbus_menu_item_compare_func(const DBusMenuItem *a, const DBusMenuItem *b,
544                                                 gpointer user_data)
545 {
546 	return b->id - a->id;
547 }
548 
dbus_menu_item_id_compare_func(const DBusMenuItem * a,gconstpointer b,gpointer user_data)549 G_GNUC_INTERNAL int dbus_menu_item_id_compare_func(const DBusMenuItem *a, gconstpointer b,
550                                                    gpointer user_data)
551 {
552 	return GPOINTER_TO_UINT(b) - a->id;
553 }
554 
dbus_menu_item_compare_immutable(DBusMenuItem * a,DBusMenuItem * b)555 G_GNUC_INTERNAL bool dbus_menu_item_compare_immutable(DBusMenuItem *a, DBusMenuItem *b)
556 {
557 	if (a->id != b->id)
558 		return false;
559 	if (a->ref_action_group != b->ref_action_group)
560 		return false;
561 	if (a->action_type != b->action_type)
562 		return false;
563 	return true;
564 }
565 
dbus_menu_item_is_submenu(DBusMenuItem * item)566 static bool dbus_menu_item_is_submenu(DBusMenuItem *item)
567 {
568 	if (!item)
569 		return false;
570 	if (item->action_type != DBUS_MENU_ACTION_SUBMENU)
571 		return false;
572 	return true;
573 }
574 
dbus_menu_item_copy_submenu(DBusMenuItem * src,DBusMenuItem * dst,DBusMenuModel * parent)575 G_GNUC_INTERNAL void dbus_menu_item_copy_submenu(DBusMenuItem *src, DBusMenuItem *dst,
576                                                  DBusMenuModel *parent)
577 {
578 	DBusMenuXml *xml;
579 	DBusMenuModel *submenu = NULL;
580 	g_object_get(parent, "xml", &xml, NULL);
581 	if (!dbus_menu_item_is_submenu(src))
582 	{
583 		if (dst->action_type == DBUS_MENU_ACTION_SUBMENU)
584 		{
585 			if (dst->toggled)
586 				dst->enabled = true;
587 			submenu = dbus_menu_model_new(dst->id, parent, xml, dst->ref_action_group);
588 			g_hash_table_insert(dst->links, submenu_str(dst->enabled), submenu);
589 		}
590 		return;
591 	}
592 	if (dst->action_type == DBUS_MENU_ACTION_SUBMENU &&
593 	    src->action_type == DBUS_MENU_ACTION_SUBMENU)
594 	{
595 		if (src->toggled || dst->toggled)
596 			dst->enabled = dst->toggled = true;
597 		submenu =
598 		    DBUS_MENU_MODEL(g_hash_table_lookup(src->links, submenu_str(src->enabled)));
599 		g_hash_table_insert(dst->links, submenu_str(dst->enabled), g_object_ref(submenu));
600 		g_object_set(submenu, "parent-id", dst->id, NULL);
601 	}
602 }
603 
dbus_menu_item_generate_action(DBusMenuItem * item,DBusMenuModel * parent)604 G_GNUC_INTERNAL void dbus_menu_item_generate_action(DBusMenuItem *item, DBusMenuModel *parent)
605 {
606 	if (item->action_type == DBUS_MENU_ACTION_SECTION)
607 		return;
608 	DBusMenuXml *xml;
609 	DBusMenuModel *submenu = g_hash_table_lookup(item->links, submenu_str(item->enabled));
610 	g_object_get(parent, "xml", &xml, NULL);
611 	item->ref_action = dbus_menu_action_reference(item->id,
612 	                                              xml,
613 	                                              submenu,
614 	                                              G_ACTION_MAP(item->ref_action_group),
615 	                                              item->action_type);
616 	act_props_try_update(item);
617 }
618