1 /*
2 * Copyright (C) 2016 Alberts Muktupāvels
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <config.h>
19
20 #include "sn-dbus-menu-item.h"
21
22 static GdkPixbuf *
pxibuf_new(GVariant * variant)23 pxibuf_new (GVariant *variant)
24 {
25 gsize length;
26 const guchar *data;
27 GInputStream *stream;
28 GdkPixbuf *pixbuf;
29 GError *error;
30
31 data = g_variant_get_fixed_array (variant, &length, sizeof (guchar));
32
33 if (length == 0)
34 return NULL;
35
36 stream = g_memory_input_stream_new_from_data (data, length, NULL);
37
38 if (stream == NULL)
39 return NULL;
40
41 error = NULL;
42 pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, &error);
43 g_object_unref (stream);
44
45 if (error != NULL)
46 {
47 g_warning ("Unable to build GdkPixbuf from icon data: %s", error->message);
48 g_error_free (error);
49 }
50
51 return pixbuf;
52 }
53
54 static SnShortcut *
sn_shortcut_new(guint key,GdkModifierType mask)55 sn_shortcut_new (guint key,
56 GdkModifierType mask)
57 {
58 SnShortcut *shortcut;
59
60 shortcut = g_new0 (SnShortcut, 1);
61
62 shortcut->key = key;
63 shortcut->mask = mask;
64
65 return shortcut;
66 }
67
68 static SnShortcut **
sn_shortcuts_new(GVariant * variant)69 sn_shortcuts_new (GVariant *variant)
70 {
71 GPtrArray *array;
72 GVariantIter shortcuts;
73 GVariantIter *shortcut;
74
75 if (variant == NULL || g_variant_iter_init (&shortcuts, variant) == 0)
76 return NULL;
77
78 array = g_ptr_array_new ();
79 while (g_variant_iter_next (&shortcuts, "as", &shortcut))
80 {
81 guint key;
82 GdkModifierType mask;
83 const gchar *string;
84
85 key = 0;
86 mask = 0;
87
88 while (g_variant_iter_next (shortcut, "&s", &string))
89 {
90 if (g_strcmp0 (string, "Control") == 0)
91 mask |= GDK_CONTROL_MASK;
92 else if (g_strcmp0 (string, "Alt") == 0)
93 mask |= GDK_MOD1_MASK;
94 else if (g_strcmp0 (string, "Shift") == 0)
95 mask |= GDK_SHIFT_MASK;
96 else if (g_strcmp0 (string, "Super") == 0)
97 mask |= GDK_SUPER_MASK;
98 else
99 gtk_accelerator_parse (string, &key, NULL);
100 }
101
102 g_ptr_array_add (array,sn_shortcut_new (key, mask));
103 g_variant_iter_free (shortcut);
104 }
105
106 g_ptr_array_add (array, NULL);
107 return (SnShortcut **) g_ptr_array_free (array, FALSE);
108 }
109
110 static void
sn_shortcuts_free(SnShortcut ** shortcuts)111 sn_shortcuts_free (SnShortcut **shortcuts)
112 {
113 guint i;
114
115 if (shortcuts == NULL)
116 return;
117
118 for (i = 0; shortcuts[i] != NULL; i++)
119 g_free (shortcuts[i]);
120
121 g_free (shortcuts);
122 }
123
124 SnDBusMenuItem *
sn_dbus_menu_item_new(GVariant * props)125 sn_dbus_menu_item_new (GVariant *props)
126 {
127 SnDBusMenuItem *item;
128 GVariantIter iter;
129 const gchar *prop;
130 GVariant *value;
131
132 item = g_new0 (SnDBusMenuItem, 1);
133
134 item->enabled = TRUE;
135 item->toggle_state = -1;
136 item->visible = TRUE;
137
138 g_variant_iter_init (&iter, props);
139 while (g_variant_iter_next (&iter, "{&sv}", &prop, &value))
140 {
141 if (g_strcmp0 (prop, "accessible-desc") == 0)
142 item->accessible_desc = g_variant_dup_string (value, NULL);
143 else if (g_strcmp0 (prop, "children-display") == 0)
144 item->children_display = g_variant_dup_string (value, NULL);
145 else if (g_strcmp0 (prop, "disposition") == 0)
146 item->disposition = g_variant_dup_string (value, NULL);
147 else if (g_strcmp0 (prop, "enabled") == 0)
148 item->enabled = g_variant_get_boolean (value);
149 else if (g_strcmp0 (prop, "icon-name") == 0)
150 item->icon_name = g_variant_dup_string (value, NULL);
151 else if (g_strcmp0 (prop, "icon-data") == 0)
152 item->icon_data = pxibuf_new (value);
153 else if (g_strcmp0 (prop, "label") == 0)
154 item->label = g_variant_dup_string (value, NULL);
155 else if (g_strcmp0 (prop, "shortcut") == 0)
156 item->shortcuts = sn_shortcuts_new (value);
157 else if (g_strcmp0 (prop, "toggle-type") == 0)
158 item->toggle_type = g_variant_dup_string (value, NULL);
159 else if (g_strcmp0 (prop, "toggle-state") == 0)
160 item->toggle_state = g_variant_get_int32 (value);
161 else if (g_strcmp0 (prop, "type") == 0)
162 item->type = g_variant_dup_string (value, NULL);
163 else if (g_strcmp0 (prop, "visible") == 0)
164 item->visible = g_variant_get_boolean (value);
165 else
166 g_debug ("unknown property '%s'", prop);
167
168 g_variant_unref (value);
169 }
170
171 if (g_strcmp0 (item->type, "separator") == 0)
172 {
173 item->item = gtk_separator_menu_item_new ();
174 }
175 else
176 {
177 if (g_strcmp0 (item->toggle_type, "checkmark") == 0)
178 {
179 item->item = gtk_check_menu_item_new ();
180 }
181 else if (g_strcmp0 (item->toggle_type, "radio") == 0)
182 {
183 item->item = gtk_check_menu_item_new ();
184 gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM(item->item),TRUE);
185 AtkObject *atk_obj;
186 atk_obj = gtk_widget_get_accessible (item->item);
187 atk_object_set_role (atk_obj,ATK_ROLE_RADIO_MENU_ITEM);
188 }
189 else
190 {
191 GtkWidget *image = NULL;
192
193 if (item->icon_name)
194 {
195 image = gtk_image_new_from_icon_name (item->icon_name,
196 GTK_ICON_SIZE_MENU);
197 }
198 else if (item->icon_data)
199 {
200 cairo_surface_t *surface;
201 surface = gdk_cairo_surface_create_from_pixbuf (item->icon_data, 0, NULL);
202 image = gtk_image_new_from_surface (surface);
203 cairo_surface_destroy (surface);
204 }
205
206 item->item = gtk_image_menu_item_new ();
207 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item->item),
208 image);
209 }
210
211 if (g_strcmp0 (item->children_display, "submenu") == 0)
212 {
213 GtkWidget *submenu;
214
215 submenu = gtk_menu_new ();
216 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item->item), submenu);
217
218 item->submenu = GTK_MENU (submenu);
219 g_object_ref_sink (item->submenu);
220 }
221
222 gtk_menu_item_set_use_underline (GTK_MENU_ITEM (item->item), TRUE);
223 gtk_menu_item_set_label (GTK_MENU_ITEM (item->item), item->label);
224
225 if (item->shortcuts)
226 {
227 guint i;
228
229 for (i = 0; item->shortcuts[i] != NULL; i++)
230 {
231 }
232 }
233
234 if (item->toggle_state != -1 && GTK_IS_CHECK_MENU_ITEM (item->item))
235 {
236 GtkCheckMenuItem *check;
237
238 check = GTK_CHECK_MENU_ITEM (item->item);
239
240 if (item->toggle_state == 1)
241 gtk_check_menu_item_set_active (check, TRUE);
242 else if (item->toggle_state == 0)
243 gtk_check_menu_item_set_active (check, FALSE);
244 }
245 }
246
247 gtk_widget_set_sensitive (item->item, item->enabled);
248 gtk_widget_set_visible (item->item, item->visible);
249
250 g_object_ref_sink (item->item);
251 return item;
252 }
253
254 void
sn_dubs_menu_item_free(gpointer data)255 sn_dubs_menu_item_free (gpointer data)
256 {
257 SnDBusMenuItem *item;
258
259 item = (SnDBusMenuItem *) data;
260 if (item == NULL)
261 return;
262
263 g_clear_pointer (&item->accessible_desc, g_free);
264 g_clear_pointer (&item->children_display, g_free);
265 g_clear_pointer (&item->disposition, g_free);
266 g_clear_pointer (&item->icon_name, g_free);
267 g_clear_object (&item->icon_data);
268 g_clear_pointer (&item->label, g_free);
269 g_clear_pointer (&item->shortcuts, sn_shortcuts_free);
270 g_clear_pointer (&item->toggle_type, g_free);
271 g_clear_pointer (&item->type, g_free);
272
273 gtk_widget_destroy (item->item);
274 g_clear_object (&item->item);
275 g_clear_object (&item->submenu);
276
277 g_free (item);
278 }
279
280 void
sn_dbus_menu_item_update_props(SnDBusMenuItem * item,GVariant * props)281 sn_dbus_menu_item_update_props (SnDBusMenuItem *item,
282 GVariant *props)
283 {
284 GVariantIter iter;
285 const gchar *prop;
286 GVariant *value;
287
288 g_variant_iter_init (&iter, props);
289 while (g_variant_iter_next (&iter, "{&sv}", &prop, &value))
290 {
291 if (g_strcmp0 (prop, "accessible-desc") == 0)
292 {
293 g_free (item->accessible_desc);
294 item->accessible_desc = g_variant_dup_string (value, NULL);
295 }
296 else if (g_strcmp0 (prop, "children-display") == 0)
297 {
298 g_free (item->children_display);
299 item->children_display = g_variant_dup_string (value, NULL);
300 }
301 else if (g_strcmp0 (prop, "disposition") == 0)
302 {
303 g_free (item->disposition);
304 item->disposition = g_variant_dup_string (value, NULL);
305 }
306 else if (g_strcmp0 (prop, "enabled") == 0)
307 {
308 item->enabled = g_variant_get_boolean (value);
309 gtk_widget_set_sensitive (item->item, item->enabled);
310 }
311 else if (g_strcmp0 (prop, "icon-name") == 0)
312 {
313 GtkWidget *image;
314
315 g_free (item->icon_name);
316 item->icon_name = g_variant_dup_string (value, NULL);
317
318 if (item->icon_name)
319 {
320 image = gtk_image_new_from_icon_name (item->icon_name,
321 GTK_ICON_SIZE_MENU);
322 }
323 else
324 {
325 image = NULL;
326 }
327
328 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item->item),
329 image);
330 }
331 else if (g_strcmp0 (prop, "icon-data") == 0)
332 {
333 GtkWidget *image;
334
335 g_clear_object (&item->icon_data);
336 item->icon_data = pxibuf_new (value);
337
338 if (item->icon_data)
339 {
340 cairo_surface_t *surface;
341 surface = gdk_cairo_surface_create_from_pixbuf (item->icon_data, 0, NULL);
342 image = gtk_image_new_from_surface (surface);
343 cairo_surface_destroy (surface);
344 }
345 else
346 {
347 image = NULL;
348 }
349
350 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item->item),
351 image);
352 }
353 else if (g_strcmp0 (prop, "label") == 0)
354 {
355 g_free (item->label);
356 item->label = g_variant_dup_string (value, NULL);
357
358 if (!GTK_IS_SEPARATOR_MENU_ITEM (item->item))
359 gtk_menu_item_set_label (GTK_MENU_ITEM (item->item), item->label);
360 }
361 else if (g_strcmp0 (prop, "shortcut") == 0)
362 {
363 sn_shortcuts_free (item->shortcuts);
364 item->shortcuts = sn_shortcuts_new (value);
365 }
366 else if (g_strcmp0 (prop, "toggle-type") == 0)
367 {
368 g_free (item->toggle_type);
369 item->toggle_type = g_variant_dup_string (value, NULL);
370 }
371 else if (g_strcmp0 (prop, "toggle-state") == 0)
372 {
373 item->toggle_state = g_variant_get_int32 (value);
374
375 if (item->toggle_state != -1 && GTK_IS_CHECK_MENU_ITEM (item->item))
376 {
377 GtkCheckMenuItem *check;
378
379 check = GTK_CHECK_MENU_ITEM (item->item);
380
381 g_signal_handler_block (item->item, item->activate_id);
382
383 if (item->toggle_state == 1)
384 gtk_check_menu_item_set_active (check, TRUE);
385 else if (item->toggle_state == 0)
386 gtk_check_menu_item_set_active (check, FALSE);
387
388 g_signal_handler_unblock (item->item, item->activate_id);
389 }
390 }
391 else if (g_strcmp0 (prop, "type") == 0)
392 {
393 g_free (item->type);
394 item->type = g_variant_dup_string (value, NULL);
395 }
396 else if (g_strcmp0 (prop, "visible") == 0)
397 {
398 item->visible = g_variant_get_boolean (value);
399 gtk_widget_set_visible (item->item, item->visible);
400 }
401 else
402 {
403 g_debug ("updating unknown property - '%s'", prop);
404 }
405
406 g_variant_unref (value);
407 }
408 }
409
410 void
sn_dbus_menu_item_remove_props(SnDBusMenuItem * item,GVariant * props)411 sn_dbus_menu_item_remove_props (SnDBusMenuItem *item,
412 GVariant *props)
413 {
414 GVariantIter iter;
415 const gchar *prop;
416
417 g_variant_iter_init (&iter, props);
418 while (g_variant_iter_next (&iter, "&s", &prop))
419 {
420 if (g_strcmp0 (prop, "accessible-desc") == 0)
421 {
422 g_clear_pointer (&item->accessible_desc, g_free);
423 }
424 else if (g_strcmp0 (prop, "children-display") == 0)
425 {
426 g_clear_pointer (&item->children_display, g_free);
427 }
428 else if (g_strcmp0 (prop, "disposition") == 0)
429 {
430 g_clear_pointer (&item->disposition, g_free);
431 }
432 else if (g_strcmp0 (prop, "enabled") == 0)
433 {
434 item->enabled = TRUE;
435 gtk_widget_set_sensitive (item->item, item->enabled);
436 }
437 else if (g_strcmp0 (prop, "icon-name") == 0)
438 {
439 g_clear_pointer (&item->icon_name, g_free);
440 if (GTK_IS_IMAGE_MENU_ITEM (item->item))
441 {
442 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item->item),
443 NULL);
444 }
445 }
446 else if (g_strcmp0 (prop, "icon-data") == 0)
447 {
448 g_clear_object (&item->icon_data);
449 if (GTK_IS_IMAGE_MENU_ITEM (item->item))
450 {
451 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item->item),
452 NULL);
453 }
454 }
455 else if (g_strcmp0 (prop, "label") == 0)
456 {
457 g_clear_pointer (&item->label, g_free);
458 if (!GTK_IS_SEPARATOR_MENU_ITEM (item->item))
459 gtk_menu_item_set_label (GTK_MENU_ITEM (item->item), item->label);
460 }
461 else if (g_strcmp0 (prop, "shortcut") == 0)
462 {
463 g_clear_pointer (&item->shortcuts, sn_shortcuts_free);
464 }
465 else if (g_strcmp0 (prop, "toggle-type") == 0)
466 {
467 g_clear_pointer (&item->toggle_type, g_free);
468 }
469 else if (g_strcmp0 (prop, "toggle-state") == 0)
470 {
471 item->toggle_state = -1;
472 }
473 else if (g_strcmp0 (prop, "type") == 0)
474 {
475 g_clear_pointer (&item->type, g_free);
476 }
477 else if (g_strcmp0 (prop, "visible") == 0)
478 {
479 item->visible = TRUE;
480 gtk_widget_set_visible (item->item, item->visible);
481 }
482 else
483 {
484 g_debug ("removing unknown property - '%s'", prop);
485 }
486 }
487 }
488