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