1 /*
2  * Copyright (C) 2016 Alberts Muktupāvels
3  * Copyright (C) 2017 Colomban Wendling <cwendling@hypra.fr>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 2 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 General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <config.h>
20 
21 #include <math.h>
22 
23 #include "sn-item.h"
24 #include "sn-item-v0.h"
25 #include "sn-item-v0-gen.h"
26 
27 #define SN_ITEM_INTERFACE "org.kde.StatusNotifierItem"
28 
29 typedef struct
30 {
31   cairo_surface_t *surface;
32   gint             width;
33   gint             height;
34 } SnIconPixmap;
35 
36 typedef struct
37 {
38   gchar         *icon_name;
39   SnIconPixmap **icon_pixmap;
40   gchar         *title;
41   gchar         *text;
42 } SnTooltip;
43 
44 struct _SnItemV0
45 {
46   SnItem         parent;
47 
48   GtkWidget     *image;
49   gint           icon_size;
50   gint           effective_icon_size;
51 
52   GCancellable  *cancellable;
53   SnItemV0Gen   *proxy;
54 
55   gchar         *id;
56   gchar         *category;
57   gchar         *status;
58 
59   gchar         *title;
60   gint32         window_id;
61   gchar         *icon_name;
62   gchar         *label;
63   SnIconPixmap **icon_pixmap;
64   gchar         *overlay_icon_name;
65   SnIconPixmap **overlay_icon_pixmap;
66   gchar         *attention_icon_name;
67   SnIconPixmap **attention_icon_pixmap;
68   gchar         *attention_movie_name;
69   SnTooltip     *tooltip;
70   gchar         *icon_theme_path;
71   gchar         *menu;
72   gboolean       item_is_menu;
73 
74   guint          update_id;
75 };
76 
77 enum
78 {
79   PROP_0,
80 
81   PROP_ICON_SIZE,
82   PROP_ICON_PADDING,
83 
84   LAST_PROP
85 };
86 
87 static GParamSpec *obj_properties[LAST_PROP] = { NULL };
88 
G_DEFINE_TYPE(SnItemV0,sn_item_v0,SN_TYPE_ITEM)89 G_DEFINE_TYPE (SnItemV0, sn_item_v0, SN_TYPE_ITEM)
90 
91 static cairo_surface_t *
92 scale_surface (SnIconPixmap   *pixmap,
93                GtkOrientation  orientation,
94                gint            size)
95 {
96   gdouble ratio;
97   gdouble new_width;
98   gdouble new_height;
99   gdouble scale_x;
100   gdouble scale_y;
101   gint width;
102   gint height;
103   cairo_content_t content;
104   cairo_surface_t *scaled;
105   cairo_t *cr;
106 
107   g_return_val_if_fail (pixmap != NULL, NULL);
108 
109   ratio = pixmap->width / (gdouble) pixmap->height;
110   if (orientation == GTK_ORIENTATION_HORIZONTAL)
111     {
112       new_height = (gdouble) size;
113       new_width = new_height * ratio;
114     }
115   else
116     {
117       new_width = (gdouble) size;
118       new_height = new_width * ratio;
119     }
120 
121   scale_x = new_width / pixmap->width;
122   scale_y = new_height / pixmap->height;
123 
124   width = ceil (new_width);
125   height = ceil (new_height);
126 
127   content = CAIRO_CONTENT_COLOR_ALPHA;
128   scaled = cairo_surface_create_similar (pixmap->surface, content, width, height);
129   cr = cairo_create (scaled);
130 
131   cairo_scale (cr, scale_x, scale_y);
132   cairo_set_source_surface (cr, pixmap->surface, 0, 0);
133   cairo_paint (cr);
134 
135   cairo_destroy (cr);
136   return scaled;
137 }
138 
139 static gint
compare_size(gconstpointer a,gconstpointer b,gpointer user_data)140 compare_size (gconstpointer a,
141               gconstpointer b,
142               gpointer      user_data)
143 {
144   SnIconPixmap *p1;
145   SnIconPixmap *p2;
146   GtkOrientation orientation;
147 
148   p1 = (SnIconPixmap *) a;
149   p2 = (SnIconPixmap *) b;
150   orientation = GPOINTER_TO_UINT (user_data);
151 
152   if (orientation == GTK_ORIENTATION_HORIZONTAL)
153     return p1->height - p2->height;
154   else
155     return p1->width - p2->width;
156 }
157 
158 static cairo_surface_t *
get_surface(SnItemV0 * v0,GtkOrientation orientation,gint size)159 get_surface (SnItemV0       *v0,
160              GtkOrientation  orientation,
161              gint            size)
162 {
163   gint i;
164   GList *pixmaps = NULL;
165   SnIconPixmap *pixmap = NULL;
166   GList *l;
167 
168   g_assert (v0->icon_pixmap != NULL && v0->icon_pixmap[0] != NULL);
169 
170   for (i = 0; v0->icon_pixmap[i] != NULL; i++)
171     pixmaps = g_list_prepend (pixmaps, v0->icon_pixmap[i]);
172 
173   pixmaps = g_list_sort_with_data (pixmaps, compare_size,
174                                    GUINT_TO_POINTER (orientation));
175 
176   pixmap = (SnIconPixmap *) pixmaps->data;
177   for (l = pixmaps; l != NULL; l = l->next)
178     {
179       SnIconPixmap *p = (SnIconPixmap *) l->data;
180 
181       if (p->height > size && p->width > size)
182         {
183           break;
184         }
185       pixmap = p;
186     }
187 
188   g_list_free (pixmaps);
189 
190   if (pixmap == NULL || pixmap->surface == NULL)
191     return NULL;
192   else if (pixmap->height > size || pixmap->width > size)
193     return scale_surface (pixmap, orientation, size);
194   else
195     return cairo_surface_reference (pixmap->surface);
196 }
197 
198 static cairo_surface_t *
get_icon_by_name(const gchar * icon_name,gint requested_size,gint scale)199 get_icon_by_name (const gchar *icon_name,
200                   gint         requested_size,
201                   gint         scale)
202 {
203   GtkIconTheme *icon_theme;
204   gint *sizes;
205   gint i;
206   gint chosen_size = 0;
207 
208   g_return_val_if_fail (icon_name != NULL && icon_name[0] != '\0', NULL);
209   g_return_val_if_fail (requested_size > 0, NULL);
210 
211   icon_theme = gtk_icon_theme_get_default ();
212   gtk_icon_theme_rescan_if_needed (icon_theme);
213 
214   sizes = gtk_icon_theme_get_icon_sizes (icon_theme, icon_name);
215   for (i = 0; sizes[i] != 0; i++)
216     {
217       if (sizes[i] == requested_size ||
218           sizes[i] == -1) /* scalable */
219         {
220           /* perfect match, stop here */
221           chosen_size = requested_size;
222           break;
223         }
224       else if (sizes[i] < requested_size && sizes[i] > chosen_size)
225         chosen_size = sizes[i];
226     }
227   g_free (sizes);
228 
229   if (chosen_size == 0)
230     chosen_size = requested_size;
231 
232   return gtk_icon_theme_load_surface (icon_theme, icon_name,
233                                       chosen_size, scale,
234                                       NULL, GTK_ICON_LOOKUP_FORCE_SIZE, NULL);
235 }
236 
237 static void
update(SnItemV0 * v0)238 update (SnItemV0 *v0)
239 {
240   AtkObject *accessible;
241   GtkImage *image;
242   SnTooltip *tip;
243   gint icon_size;
244   gboolean visible;
245   g_return_if_fail (SN_IS_ITEM_V0 (v0));
246 
247   image = GTK_IMAGE (v0->image);
248 
249   if (v0->icon_size > 0)
250     icon_size = v0->icon_size;
251   else
252     icon_size = MAX (1, v0->effective_icon_size);
253 
254   if (v0->icon_name != NULL && v0->icon_name[0] != '\0')
255     {
256       cairo_surface_t *surface;
257       gint scale;
258 
259       scale = gtk_widget_get_scale_factor (GTK_WIDGET (image));
260       surface = get_icon_by_name (v0->icon_name, icon_size, scale);
261 
262       if (!surface)
263         {
264           GdkPixbuf *pixbuf;
265           /*try to find icons specified by path and filename*/
266           pixbuf = gdk_pixbuf_new_from_file (v0->icon_name, NULL);
267           if (pixbuf && icon_size > 1)
268             {
269               /*An icon specified by path and filename may be the wrong size for the tray */
270               pixbuf = gdk_pixbuf_scale_simple (pixbuf, icon_size-2, icon_size-2, GDK_INTERP_BILINEAR);
271               surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, NULL);
272             }
273           if (pixbuf)
274             g_object_unref (pixbuf);
275         }
276       if (!surface)
277         {
278           /*deal with missing icon or failure to load icon*/
279           surface = get_icon_by_name ("image-missing", icon_size, scale);
280         }
281       gtk_image_set_from_surface (image, surface);
282       cairo_surface_destroy (surface);
283     }
284   else if (v0->icon_pixmap != NULL && v0->icon_pixmap[0] != NULL)
285     {
286       cairo_surface_t *surface;
287 
288       surface = get_surface (v0,
289                              gtk_orientable_get_orientation (GTK_ORIENTABLE (v0)),
290                              icon_size);
291       if (surface != NULL)
292         {
293           gtk_image_set_from_surface (image, surface);
294           cairo_surface_destroy (surface);
295         }
296     }
297   else
298     {
299       gtk_image_set_from_icon_name (image, "image-missing", GTK_ICON_SIZE_MENU);
300       gtk_image_set_pixel_size (image, icon_size);
301     }
302 
303   tip = v0->tooltip;
304 
305   if (tip != NULL)
306     {
307       gchar *markup;
308 
309       markup = NULL;
310 
311       if ((tip->title != NULL && *tip->title != '\0') &&
312           (tip->text != NULL && *tip->text != '\0'))
313         {
314           markup = g_strdup_printf ("%s\n%s", tip->title, tip->text);
315         }
316       else if (tip->title != NULL && *tip->title != '\0')
317         {
318           markup = g_strdup (tip->title);
319         }
320       else if (tip->text != NULL && *tip->text != '\0')
321         {
322           markup = g_strdup (tip->text);
323         }
324 
325       gtk_widget_set_tooltip_markup (GTK_WIDGET (v0), markup);
326       g_free (markup);
327     }
328   else
329     {
330       gtk_widget_set_tooltip_markup (GTK_WIDGET (v0), NULL);
331     }
332 
333   gtk_button_set_label (GTK_BUTTON (v0), v0->label);
334   accessible = gtk_widget_get_accessible (GTK_WIDGET (v0));
335 
336   if (v0->title != NULL && *v0->title != '\0')
337     atk_object_set_name (accessible, v0->title);
338   else
339     atk_object_set_name (accessible, v0->id);
340 
341   /* TODO: hide "Passive" items with a setting? */
342   /*Special case mate-polkit*/
343   if (g_strcmp0 (v0->status, "password-dialog") != 0){
344     visible = g_strcmp0 (v0->status, "Passive") != 0;
345     gtk_widget_set_visible (GTK_WIDGET (v0), visible);
346     }
347   else
348   gtk_widget_set_visible (GTK_WIDGET (v0), TRUE);
349 }
350 
351 static gboolean
update_cb(gpointer user_data)352 update_cb (gpointer user_data)
353 {
354   SnItemV0 *v0;
355 
356   v0 = SN_ITEM_V0 (user_data);
357 
358   v0->update_id = 0;
359   update (v0);
360 
361   return G_SOURCE_REMOVE;
362 }
363 
364 static void
queue_update(SnItemV0 * v0)365 queue_update (SnItemV0 *v0)
366 {
367   if (v0->update_id != 0)
368     return;
369 
370   v0->update_id = g_timeout_add (10, update_cb, v0);
371   g_source_set_name_by_id (v0->update_id, "[status-notifier] update_cb");
372 }
373 
374 static cairo_surface_t *
surface_from_variant(GVariant * variant,gint width,gint height)375 surface_from_variant (GVariant *variant,
376                       gint      width,
377                       gint      height)
378 {
379   cairo_format_t format;
380   gint stride;
381   guint32 *data;
382   gint x;
383   gint y;
384   guchar *p;
385 
386   format = CAIRO_FORMAT_ARGB32;
387   stride = cairo_format_stride_for_width (format, width);
388   data = (guint32 *) g_variant_get_data (variant);
389 
390 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
391   {
392     gint i;
393 
394     for (i = 0; i < width * height; i++)
395       data[i] = GUINT32_FROM_BE (data[i]);
396   }
397 #endif
398 
399   p = (guchar *) data;
400   /* premultiply alpha for Cairo */
401   for (y = 0; y < height; y++)
402     {
403       for (x = 0; x < width; x++)
404         {
405           guchar alpha = p[x * 4 + 3];
406 
407           p[x * 4 + 0] = (p[x * 4 + 0] * alpha) / 255;
408           p[x * 4 + 1] = (p[x * 4 + 1] * alpha) / 255;
409           p[x * 4 + 2] = (p[x * 4 + 2] * alpha) / 255;
410         }
411 
412       p += stride;
413     }
414 
415   return cairo_image_surface_create_for_data ((guchar *) data, format,
416                                               width, height, stride);
417 }
418 
419 static cairo_surface_t *
icon_surface_new(GVariant * variant,gint width,gint height)420 icon_surface_new (GVariant *variant,
421                   gint      width,
422                   gint      height)
423 {
424   cairo_surface_t *surface;
425   cairo_surface_t *tmp;
426   cairo_t *cr;
427 
428   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
429   if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
430     return NULL;
431 
432   tmp = surface_from_variant (variant, width, height);
433   if (cairo_surface_status (tmp) != CAIRO_STATUS_SUCCESS)
434     {
435       cairo_surface_destroy (surface);
436       return NULL;
437     }
438 
439   cr = cairo_create (surface);
440   if (cairo_status (cr) != CAIRO_STATUS_SUCCESS)
441     {
442       cairo_surface_destroy (surface);
443       cairo_surface_destroy (tmp);
444       return NULL;
445     }
446 
447   cairo_set_source_surface (cr, tmp, 0, 0);
448   cairo_paint (cr);
449 
450   cairo_surface_destroy (tmp);
451   cairo_destroy (cr);
452 
453   return surface;
454 }
455 
456 static SnIconPixmap **
icon_pixmap_new(GVariant * variant)457 icon_pixmap_new (GVariant *variant)
458 {
459   GPtrArray *array;
460   GVariantIter iter;
461   gint width;
462   gint height;
463   GVariant *value;
464 
465   if (variant == NULL || g_variant_iter_init (&iter, variant) == 0)
466     return NULL;
467 
468   array = g_ptr_array_new ();
469   while (g_variant_iter_next (&iter, "(ii@ay)", &width, &height, &value))
470     {
471       cairo_surface_t *surface;
472 
473       if (width == 0 || height == 0)
474         {
475           g_variant_unref (value);
476           continue;
477         }
478 
479       surface = icon_surface_new (value, width, height);
480       g_variant_unref (value);
481 
482       if (surface != NULL)
483         {
484           SnIconPixmap *pixmap;
485 
486           pixmap = g_new0 (SnIconPixmap, 1);
487 
488           pixmap->surface = surface;
489           pixmap->width = width;
490           pixmap->height = height;
491 
492           g_ptr_array_add (array, pixmap);
493         }
494     }
495 
496   g_ptr_array_add (array, NULL);
497   return (SnIconPixmap **) g_ptr_array_free (array, FALSE);
498 }
499 
500 static void
icon_pixmap_free(SnIconPixmap ** data)501 icon_pixmap_free (SnIconPixmap **data)
502 {
503   gint i;
504 
505   if (data == NULL)
506     return;
507 
508   for (i = 0; data[i] != NULL; i++)
509     {
510       cairo_surface_destroy (data[i]->surface);
511       g_free (data[i]);
512     }
513 
514   g_free (data);
515 }
516 
517 static SnTooltip *
sn_tooltip_new(GVariant * variant)518 sn_tooltip_new (GVariant *variant)
519 {
520   const gchar *icon_name;
521   GVariant *icon_pixmap;
522   const gchar *title;
523   const gchar *text;
524   SnTooltip *tooltip;
525 
526   if (variant == NULL)
527     return NULL;
528 
529   if (!g_variant_is_of_type (variant, G_VARIANT_TYPE ("(sa(iiay)ss)")))
530     {
531       g_warning ("Type for 'ToolTip' property should be '(sa(iiay)ss)' "
532                  "but got '%s'", g_variant_get_type_string (variant));
533 
534       return NULL;
535     }
536 
537   g_variant_get (variant, "(&s@a(iiay)&s&s)",
538                  &icon_name, &icon_pixmap,
539                  &title, &text);
540 
541   tooltip = g_new0 (SnTooltip, 1);
542 
543   tooltip->icon_name = g_strdup (icon_name);
544   tooltip->icon_pixmap = icon_pixmap_new (icon_pixmap);
545   tooltip->title = g_strdup (title);
546   tooltip->text = g_strdup (text);
547 
548   g_variant_unref (icon_pixmap);
549   return tooltip;
550 }
551 
552 static void
sn_tooltip_free(SnTooltip * tooltip)553 sn_tooltip_free (SnTooltip *tooltip)
554 {
555   if (tooltip == NULL)
556     return;
557 
558   g_free (tooltip->icon_name);
559   icon_pixmap_free (tooltip->icon_pixmap);
560   g_free (tooltip->title);
561   g_free (tooltip->text);
562 
563   g_free (tooltip);
564 }
565 
566 static GVariant *
get_property(GObject * source_object,GAsyncResult * res,gpointer user_data,gboolean * cancelled)567 get_property (GObject      *source_object,
568               GAsyncResult *res,
569               gpointer      user_data,
570               gboolean     *cancelled)
571 {
572   GVariant *variant;
573   GError *error;
574   GVariant *property;
575 
576   error = NULL;
577   variant = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
578                                            res, &error);
579 
580   *cancelled = FALSE;
581   if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
582     {
583       *cancelled = TRUE;
584       g_error_free (error);
585       return NULL;
586     }
587 
588   if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS))
589     {
590       g_error_free (error);
591       return NULL;
592     }
593 
594   if (error)
595     {
596       g_warning ("%s", error->message);
597       g_error_free (error);
598       return NULL;
599     }
600 
601   g_variant_get (variant, "(v)", &property);
602   g_variant_unref (variant);
603 
604   return property;
605 }
606 
607 static void
update_property(SnItemV0 * v0,const gchar * property,GAsyncReadyCallback callback)608 update_property (SnItemV0            *v0,
609                  const gchar         *property,
610                  GAsyncReadyCallback  callback)
611 {
612   GDBusProxy *proxy;
613   SnItem *item;
614 
615   proxy = G_DBUS_PROXY (v0->proxy);
616   item = SN_ITEM (v0);
617 
618   g_dbus_connection_call (g_dbus_proxy_get_connection (proxy),
619                           sn_item_get_bus_name (item),
620                           sn_item_get_object_path (item),
621                           "org.freedesktop.DBus.Properties", "Get",
622                           g_variant_new ("(ss)", SN_ITEM_INTERFACE, property),
623                           G_VARIANT_TYPE ("(v)"),
624                           G_DBUS_CALL_FLAGS_NONE, -1,
625                           v0->cancellable, callback, v0);
626 }
627 
628 static void
update_title(GObject * source_object,GAsyncResult * res,gpointer user_data)629 update_title (GObject      *source_object,
630               GAsyncResult *res,
631               gpointer      user_data)
632 {
633   SnItemV0 *v0;
634   GVariant *variant;
635   gboolean cancelled;
636 
637   variant = get_property (source_object, res, user_data, &cancelled);
638   if (cancelled)
639     return;
640 
641   v0 = SN_ITEM_V0 (user_data);
642 
643   g_clear_pointer (&v0->title, g_free);
644   v0->title = g_variant_dup_string (variant, NULL);
645   g_clear_pointer (&variant, g_variant_unref);
646 
647   queue_update (v0);
648 }
649 
650 static void
new_title_cb(SnItemV0 * v0)651 new_title_cb (SnItemV0 *v0)
652 {
653   update_property (v0, "Title", update_title);
654 }
655 
656 static void
update_icon_name(GObject * source_object,GAsyncResult * res,gpointer user_data)657 update_icon_name (GObject      *source_object,
658                   GAsyncResult *res,
659                   gpointer      user_data)
660 {
661   SnItemV0 *v0;
662   GVariant *variant;
663   gboolean cancelled;
664 
665   variant = get_property (source_object, res, user_data, &cancelled);
666   if (cancelled)
667     return;
668 
669   v0 = SN_ITEM_V0 (user_data);
670 
671   g_clear_pointer (&v0->icon_name, g_free);
672   v0->icon_name = g_variant_dup_string (variant, NULL);
673   g_clear_pointer (&variant, g_variant_unref);
674 
675   queue_update (v0);
676 }
677 
678 static void
update_icon_pixmap(GObject * source_object,GAsyncResult * res,gpointer user_data)679 update_icon_pixmap (GObject      *source_object,
680                     GAsyncResult *res,
681                     gpointer      user_data)
682 {
683   SnItemV0 *v0;
684   GVariant *variant;
685   gboolean cancelled;
686 
687   variant = get_property (source_object, res, user_data, &cancelled);
688   if (cancelled)
689     return;
690 
691   v0 = SN_ITEM_V0 (user_data);
692 
693   g_clear_pointer (&v0->icon_pixmap, icon_pixmap_free);
694   v0->icon_pixmap = icon_pixmap_new (variant);
695   g_clear_pointer (&variant, g_variant_unref);
696 
697   queue_update (v0);
698 }
699 
700 static void
new_icon_cb(SnItemV0 * v0)701 new_icon_cb (SnItemV0 *v0)
702 {
703   update_property (v0, "IconName", update_icon_name);
704   update_property (v0, "IconPixmap", update_icon_pixmap);
705 }
706 
707 static void
update_overlay_icon_name(GObject * source_object,GAsyncResult * res,gpointer user_data)708 update_overlay_icon_name (GObject      *source_object,
709                           GAsyncResult *res,
710                           gpointer      user_data)
711 {
712   SnItemV0 *v0;
713   GVariant *variant;
714   gboolean cancelled;
715 
716   variant = get_property (source_object, res, user_data, &cancelled);
717   if (cancelled)
718     return;
719 
720   v0 = SN_ITEM_V0 (user_data);
721 
722   g_clear_pointer (&v0->overlay_icon_name, g_free);
723   v0->overlay_icon_name = g_variant_dup_string (variant, NULL);
724   g_clear_pointer (&variant, g_variant_unref);
725 
726   queue_update (v0);
727 }
728 
729 static void
update_overlay_icon_pixmap(GObject * source_object,GAsyncResult * res,gpointer user_data)730 update_overlay_icon_pixmap (GObject      *source_object,
731                             GAsyncResult *res,
732                             gpointer      user_data)
733 {
734   SnItemV0 *v0;
735   GVariant *variant;
736   gboolean cancelled;
737 
738   variant = get_property (source_object, res, user_data, &cancelled);
739   if (cancelled)
740     return;
741 
742   v0 = SN_ITEM_V0 (user_data);
743 
744   g_clear_pointer (&v0->overlay_icon_pixmap, icon_pixmap_free);
745   v0->overlay_icon_pixmap = icon_pixmap_new (variant);
746   g_clear_pointer (&variant, g_variant_unref);
747 
748   queue_update (v0);
749 }
750 
751 static void
new_overlay_icon_cb(SnItemV0 * v0)752 new_overlay_icon_cb (SnItemV0 *v0)
753 {
754   update_property (v0, "OverlayIconName", update_overlay_icon_name);
755   update_property (v0, "OverlayIconPixmap", update_overlay_icon_pixmap);
756 }
757 
758 static void
update_attention_icon_name(GObject * source_object,GAsyncResult * res,gpointer user_data)759 update_attention_icon_name (GObject      *source_object,
760                             GAsyncResult *res,
761                             gpointer      user_data)
762 {
763   SnItemV0 *v0;
764   GVariant *variant;
765   gboolean cancelled;
766 
767   variant = get_property (source_object, res, user_data, &cancelled);
768   if (cancelled)
769     return;
770 
771   v0 = SN_ITEM_V0 (user_data);
772 
773   g_clear_pointer (&v0->attention_icon_name, g_free);
774   v0->attention_icon_name = g_variant_dup_string (variant, NULL);
775   g_clear_pointer (&variant, g_variant_unref);
776 
777   queue_update (v0);
778 }
779 
780 static void
update_attention_icon_pixmap(GObject * source_object,GAsyncResult * res,gpointer user_data)781 update_attention_icon_pixmap (GObject      *source_object,
782                               GAsyncResult *res,
783                               gpointer      user_data)
784 {
785   SnItemV0 *v0;
786   GVariant *variant;
787   gboolean cancelled;
788 
789   variant = get_property (source_object, res, user_data, &cancelled);
790   if (cancelled)
791     return;
792 
793   v0 = SN_ITEM_V0 (user_data);
794 
795   g_clear_pointer (&v0->attention_icon_pixmap, icon_pixmap_free);
796   v0->attention_icon_pixmap = icon_pixmap_new (variant);
797   g_clear_pointer (&variant, g_variant_unref);
798 
799   queue_update (v0);
800 }
801 
802 static void
new_attention_icon_cb(SnItemV0 * v0)803 new_attention_icon_cb (SnItemV0 *v0)
804 {
805   update_property (v0, "AttentionIconName", update_attention_icon_name);
806   update_property (v0, "AttentionIconPixmap", update_attention_icon_pixmap);
807 }
808 
809 static void
update_tooltip(GObject * source_object,GAsyncResult * res,gpointer user_data)810 update_tooltip (GObject      *source_object,
811                 GAsyncResult *res,
812                 gpointer      user_data)
813 {
814   SnItemV0 *v0;
815   GVariant *variant;
816   gboolean cancelled;
817 
818   variant = get_property (source_object, res, user_data, &cancelled);
819   if (cancelled)
820     return;
821 
822   v0 = SN_ITEM_V0 (user_data);
823 
824   g_clear_pointer (&v0->tooltip, sn_tooltip_free);
825   v0->tooltip = sn_tooltip_new (variant);
826   g_clear_pointer (&variant, g_variant_unref);
827 
828   queue_update (v0);
829 }
830 
831 static void
new_tooltip_cb(SnItemV0 * v0)832 new_tooltip_cb (SnItemV0 *v0)
833 {
834   update_property (v0, "ToolTip", update_tooltip);
835 }
836 
837 static void
new_status_cb(SnItemV0 * v0,GVariant * parameters)838 new_status_cb (SnItemV0 *v0,
839                GVariant *parameters)
840 {
841   GVariant *variant;
842 
843   variant = g_variant_get_child_value (parameters, 0);
844 
845   g_free (v0->status);
846   v0->status = g_variant_dup_string (variant, NULL);
847   g_variant_unref (variant);
848 
849   queue_update (v0);
850 }
851 
852 static void
new_label_cb(SnItemV0 * v0,GVariant * parameters)853 new_label_cb (SnItemV0 *v0,
854               GVariant *parameters)
855 {
856   GVariant *variant;
857 
858   variant = g_variant_get_child_value (parameters, 0);
859 
860   g_free (v0->label);
861   v0->label = g_variant_dup_string (variant, NULL);
862   g_variant_unref (variant);
863 
864   queue_update (v0);
865 }
866 
867 static void
new_icon_theme_path_cb(SnItemV0 * v0,GVariant * parameters)868 new_icon_theme_path_cb (SnItemV0 *v0,
869                         GVariant *parameters)
870 {
871   GVariant *variant;
872 
873   variant = g_variant_get_child_value (parameters, 0);
874 
875   g_free (v0->icon_theme_path);
876   v0->icon_theme_path = g_variant_dup_string (variant, NULL);
877   g_variant_unref (variant);
878 
879   if (v0->icon_theme_path != NULL)
880     {
881       GtkIconTheme *icon_theme;
882 
883       icon_theme = gtk_icon_theme_get_default ();
884 
885       gtk_icon_theme_append_search_path (icon_theme, v0->icon_theme_path);
886     }
887 
888   queue_update (v0);
889 }
890 
891 static void
g_properties_changed_cb(GDBusProxy * proxy,GVariant * changed_properties,GStrv invalidated_properties,SnItemV0 * v0)892 g_properties_changed_cb (GDBusProxy *proxy,
893                          GVariant   *changed_properties,
894                          GStrv       invalidated_properties,
895                          SnItemV0   *v0)
896 {
897   gchar *debug;
898 
899   debug = g_variant_print (changed_properties, FALSE);
900   g_debug ("g_properties_changed_cb: %s", debug);
901   g_free (debug);
902 }
903 
904 static void
g_signal_cb(GDBusProxy * proxy,gchar * sender_name,gchar * signal_name,GVariant * parameters,SnItemV0 * v0)905 g_signal_cb (GDBusProxy *proxy,
906              gchar      *sender_name,
907              gchar      *signal_name,
908              GVariant   *parameters,
909              SnItemV0   *v0)
910 {
911   if (g_strcmp0 (signal_name, "NewTitle") == 0)
912     new_title_cb (v0);
913   else if (g_strcmp0 (signal_name, "NewIcon") == 0)
914     new_icon_cb (v0);
915   else if (g_strcmp0 (signal_name, "NewOverlayIcon") == 0)
916     new_overlay_icon_cb (v0);
917   else if (g_strcmp0 (signal_name, "NewAttentionIcon") == 0)
918     new_attention_icon_cb (v0);
919   else if (g_strcmp0 (signal_name, "NewToolTip") == 0)
920     new_tooltip_cb (v0);
921   else if (g_strcmp0 (signal_name, "NewStatus") == 0)
922     new_status_cb (v0, parameters);
923   else if (g_strcmp0 (signal_name, "NewIconThemePath") == 0)
924     new_icon_theme_path_cb (v0, parameters);
925   else if (g_strcmp0 (signal_name, "XAyatanaNewLabel") == 0)
926     new_label_cb (v0, parameters);
927   else
928     g_debug ("signal '%s' not handled!", signal_name);
929 }
930 
931 static void
get_all_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)932 get_all_cb (GObject      *source_object,
933             GAsyncResult *res,
934             gpointer      user_data)
935 {
936   SnItemV0 *v0;
937   GVariant *properties;
938   GError *error;
939   GVariantIter *iter;
940   gchar *key;
941   GVariant *value;
942 
943   error = NULL;
944   properties = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
945                                               res, &error);
946 
947   if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
948     {
949       g_error_free (error);
950       return;
951     }
952 
953   v0 = SN_ITEM_V0 (user_data);
954 
955   if (error)
956     {
957       g_warning ("%s", error->message);
958       g_error_free (error);
959       return;
960     }
961 
962   g_variant_get (properties, "(a{sv})", &iter);
963   while (g_variant_iter_next (iter, "{sv}", &key, &value))
964     {
965       if (g_strcmp0 (key, "Category") == 0)
966         v0->category = g_variant_dup_string (value, NULL);
967       else if (g_strcmp0 (key, "Id") == 0)
968         v0->id = g_variant_dup_string (value, NULL);
969       else if (g_strcmp0 (key, "Title") == 0)
970         v0->title = g_variant_dup_string (value, NULL);
971       else if (g_strcmp0 (key, "Status") == 0)
972         v0->status = g_variant_dup_string (value, NULL);
973       else if (g_strcmp0 (key, "WindowId") == 0)
974         v0->window_id = g_variant_get_int32 (value);
975       else if (g_strcmp0 (key, "IconName") == 0)
976         v0->icon_name = g_variant_dup_string (value, NULL);
977       else if (g_strcmp0 (key, "IconPixmap") == 0)
978         v0->icon_pixmap = icon_pixmap_new (value);
979       else if (g_strcmp0 (key, "OverlayIconName") == 0)
980         v0->overlay_icon_name = g_variant_dup_string (value, NULL);
981       else if (g_strcmp0 (key, "OverlayIconPixmap") == 0)
982         v0->overlay_icon_pixmap = icon_pixmap_new (value);
983       else if (g_strcmp0 (key, "AttentionIconName") == 0)
984         v0->attention_icon_name = g_variant_dup_string (value, NULL);
985       else if (g_strcmp0 (key, "AttentionIconPixmap") == 0)
986         v0->attention_icon_pixmap = icon_pixmap_new (value);
987       else if (g_strcmp0 (key, "AttentionMovieName") == 0)
988         v0->attention_movie_name = g_variant_dup_string (value, NULL);
989       else if (g_strcmp0 (key, "ToolTip") == 0)
990         v0->tooltip = sn_tooltip_new (value);
991       else if (g_strcmp0 (key, "IconThemePath") == 0)
992         v0->icon_theme_path = g_variant_dup_string (value, NULL);
993       else if (g_strcmp0 (key, "Menu") == 0)
994         v0->menu = g_variant_dup_string (value, NULL);
995       else if (g_strcmp0 (key, "ItemIsMenu") == 0)
996         v0->item_is_menu = g_variant_get_boolean (value);
997       else if (g_strcmp0 (key, "XAyatanaLabel") == 0)
998         v0->label = g_variant_dup_string (value, NULL);
999       else
1000         g_debug ("property '%s' not handled!", key);
1001 
1002       g_variant_unref (value);
1003       g_free (key);
1004     }
1005 
1006   g_variant_iter_free (iter);
1007   g_variant_unref (properties);
1008 
1009   if (v0->id == NULL || v0->category == NULL || v0->status == NULL)
1010     {
1011       SnItem *item;
1012       const gchar *bus_name;
1013       const gchar *object_path;
1014 
1015       item = SN_ITEM (v0);
1016       bus_name = sn_item_get_bus_name (item);
1017       object_path = sn_item_get_object_path (item);
1018 
1019       g_warning ("Invalid Status Notifier Item (%s, %s)",
1020                  bus_name, object_path);
1021 
1022       return;
1023     }
1024 
1025   if (v0->icon_theme_path != NULL)
1026     {
1027       GtkIconTheme *icon_theme;
1028 
1029       icon_theme = gtk_icon_theme_get_default ();
1030 
1031       gtk_icon_theme_append_search_path (icon_theme, v0->icon_theme_path);
1032     }
1033 
1034   g_signal_connect (v0->proxy, "g-properties-changed",
1035                     G_CALLBACK (g_properties_changed_cb), v0);
1036 
1037   g_signal_connect (v0->proxy, "g-signal",
1038                     G_CALLBACK (g_signal_cb), v0);
1039 
1040   update (v0);
1041   sn_item_emit_ready (SN_ITEM (v0));
1042 }
1043 
1044 static void
proxy_ready_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)1045 proxy_ready_cb (GObject      *source_object,
1046                 GAsyncResult *res,
1047                 gpointer      user_data)
1048 {
1049   SnItemV0 *v0;
1050   SnItemV0Gen *proxy;
1051   GError *error;
1052 
1053   error = NULL;
1054   proxy = sn_item_v0_gen_proxy_new_for_bus_finish (res, &error);
1055 
1056   if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1057     {
1058       g_error_free (error);
1059       return;
1060     }
1061 
1062   v0 = SN_ITEM_V0 (user_data);
1063   v0->proxy = proxy;
1064 
1065   if (error)
1066     {
1067       g_warning ("%s", error->message);
1068       g_error_free (error);
1069       return;
1070     }
1071 
1072   g_dbus_connection_call (g_dbus_proxy_get_connection (G_DBUS_PROXY (proxy)),
1073                           sn_item_get_bus_name (SN_ITEM (v0)),
1074                           sn_item_get_object_path (SN_ITEM (v0)),
1075                           "org.freedesktop.DBus.Properties", "GetAll",
1076                           g_variant_new ("(s)", SN_ITEM_INTERFACE),
1077                           G_VARIANT_TYPE ("(a{sv})"),
1078                           G_DBUS_CALL_FLAGS_NONE, -1,
1079                           v0->cancellable, get_all_cb, v0);
1080 }
1081 
1082 static void
sn_item_v0_constructed(GObject * object)1083 sn_item_v0_constructed (GObject *object)
1084 {
1085   SnItemV0 *v0;
1086   SnItem *item;
1087 
1088   v0 = SN_ITEM_V0 (object);
1089   item = SN_ITEM (v0);
1090 
1091   G_OBJECT_CLASS (sn_item_v0_parent_class)->constructed (object);
1092 
1093   v0->cancellable = g_cancellable_new ();
1094   sn_item_v0_gen_proxy_new_for_bus (G_BUS_TYPE_SESSION,
1095                                     G_DBUS_PROXY_FLAGS_NONE,
1096                                     sn_item_get_bus_name (item),
1097                                     sn_item_get_object_path (item),
1098                                     v0->cancellable,
1099                                     proxy_ready_cb, v0);
1100 }
1101 
1102 static void
sn_item_v0_dispose(GObject * object)1103 sn_item_v0_dispose (GObject *object)
1104 {
1105   SnItemV0 *v0;
1106 
1107   v0 = SN_ITEM_V0 (object);
1108 
1109   g_cancellable_cancel (v0->cancellable);
1110   g_clear_object (&v0->cancellable);
1111   g_clear_object (&v0->proxy);
1112 
1113   if (v0->update_id != 0)
1114     {
1115       g_source_remove (v0->update_id);
1116       v0->update_id = 0;
1117     }
1118 
1119   G_OBJECT_CLASS (sn_item_v0_parent_class)->dispose (object);
1120 }
1121 
1122 static void
sn_item_v0_finalize(GObject * object)1123 sn_item_v0_finalize (GObject *object)
1124 {
1125   SnItemV0 *v0;
1126 
1127   v0 = SN_ITEM_V0 (object);
1128 
1129   g_clear_pointer (&v0->id, g_free);
1130   g_clear_pointer (&v0->category, g_free);
1131   g_clear_pointer (&v0->status, g_free);
1132 
1133   g_clear_pointer (&v0->title, g_free);
1134   g_clear_pointer (&v0->icon_name, g_free);
1135   g_clear_pointer (&v0->label, g_free);
1136   g_clear_pointer (&v0->icon_pixmap, icon_pixmap_free);
1137   g_clear_pointer (&v0->overlay_icon_name, g_free);
1138   g_clear_pointer (&v0->overlay_icon_pixmap, icon_pixmap_free);
1139   g_clear_pointer (&v0->attention_icon_name, g_free);
1140   g_clear_pointer (&v0->attention_icon_pixmap, icon_pixmap_free);
1141   g_clear_pointer (&v0->attention_movie_name, g_free);
1142   g_clear_pointer (&v0->tooltip, sn_tooltip_free);
1143   g_clear_pointer (&v0->icon_theme_path, g_free);
1144   g_clear_pointer (&v0->menu, g_free);
1145 
1146   G_OBJECT_CLASS (sn_item_v0_parent_class)->finalize (object);
1147 }
1148 
1149 static const gchar *
sn_item_v0_get_id(SnItem * item)1150 sn_item_v0_get_id (SnItem *item)
1151 {
1152   SnItemV0 *v0;
1153 
1154   v0 = SN_ITEM_V0 (item);
1155 
1156   return v0->id;
1157 }
1158 
1159 static const gchar *
sn_item_v0_get_category(SnItem * item)1160 sn_item_v0_get_category (SnItem *item)
1161 {
1162   SnItemV0 *v0;
1163 
1164   v0 = SN_ITEM_V0 (item);
1165 
1166   return v0->category;
1167 }
1168 
1169 static const gchar *
sn_item_v0_get_menu(SnItem * item)1170 sn_item_v0_get_menu (SnItem *item)
1171 {
1172   SnItemV0 *v0;
1173 
1174   v0 = SN_ITEM_V0 (item);
1175 
1176   return v0->menu;
1177 }
1178 
1179 static void
context_menu_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)1180 context_menu_cb (GObject      *source_object,
1181                  GAsyncResult *res,
1182                  gpointer      user_data)
1183 {
1184   SnItemV0 *v0;
1185 
1186   v0 = SN_ITEM_V0 (user_data);
1187 
1188   sn_item_v0_gen_call_context_menu_finish (v0->proxy, res, NULL);
1189 }
1190 
1191 static void
sn_item_v0_context_menu(SnItem * item,gint x,gint y)1192 sn_item_v0_context_menu (SnItem *item,
1193                          gint    x,
1194                          gint    y)
1195 {
1196   SnItemV0 *v0;
1197 
1198   v0 = SN_ITEM_V0 (item);
1199 
1200   sn_item_v0_gen_call_context_menu (v0->proxy, x, y, NULL,
1201                                     context_menu_cb, v0);
1202 }
1203 
1204 static void
activate_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)1205 activate_cb (GObject      *source_object,
1206              GAsyncResult *res,
1207              gpointer      user_data)
1208 {
1209   SnItemV0 *v0;
1210 
1211   v0 = SN_ITEM_V0 (user_data);
1212 
1213   sn_item_v0_gen_call_activate_finish (v0->proxy, res, NULL);
1214 }
1215 
1216 static void
sn_item_v0_activate(SnItem * item,gint x,gint y)1217 sn_item_v0_activate (SnItem *item,
1218                      gint    x,
1219                      gint    y)
1220 {
1221   SnItemV0 *v0;
1222 
1223   v0 = SN_ITEM_V0 (item);
1224 
1225   sn_item_v0_gen_call_activate (v0->proxy, x, y, NULL,
1226                                 activate_cb, v0);
1227 }
1228 
1229 static void
secondary_activate_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)1230 secondary_activate_cb (GObject      *source_object,
1231                        GAsyncResult *res,
1232                        gpointer      user_data)
1233 {
1234   SnItemV0 *v0;
1235 
1236   v0 = SN_ITEM_V0 (user_data);
1237 
1238   sn_item_v0_gen_call_secondary_activate_finish (v0->proxy, res, NULL);
1239 }
1240 
1241 static void
sn_item_v0_secondary_activate(SnItem * item,gint x,gint y)1242 sn_item_v0_secondary_activate (SnItem *item,
1243                                gint    x,
1244                                gint    y)
1245 {
1246   SnItemV0 *v0;
1247 
1248   v0 = SN_ITEM_V0 (item);
1249 
1250   sn_item_v0_gen_call_secondary_activate (v0->proxy, x, y, NULL,
1251                                           secondary_activate_cb, v0);
1252 }
1253 
1254 static void
scroll_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)1255 scroll_cb (GObject      *source_object,
1256            GAsyncResult *res,
1257            gpointer      user_data)
1258 {
1259   SnItemV0 *v0;
1260 
1261   v0 = SN_ITEM_V0 (user_data);
1262 
1263   sn_item_v0_gen_call_scroll_finish (v0->proxy, res, NULL);
1264 }
1265 
1266 static void
sn_item_v0_scroll(SnItem * item,gint delta,SnItemOrientation orientation)1267 sn_item_v0_scroll (SnItem            *item,
1268                    gint               delta,
1269                    SnItemOrientation  orientation)
1270 {
1271   SnItemV0 *v0;
1272   const gchar *tmp;
1273 
1274   v0 = SN_ITEM_V0 (item);
1275 
1276   switch (orientation)
1277     {
1278       case SN_ITEM_ORIENTATION_VERTICAL:
1279         tmp = "Vertical";
1280         break;
1281 
1282       case SN_ITEM_ORIENTATION_HORIZONTAL:
1283       default:
1284         tmp = "Horizontal";
1285         break;
1286     }
1287 
1288   sn_item_v0_gen_call_scroll (v0->proxy, delta, tmp, NULL, scroll_cb, v0);
1289 }
1290 
1291 static void
sn_item_v0_size_allocate(GtkWidget * widget,GtkAllocation * allocation)1292 sn_item_v0_size_allocate (GtkWidget      *widget,
1293                           GtkAllocation  *allocation)
1294 {
1295   SnItemV0 *v0 = SN_ITEM_V0 (widget);
1296 
1297   GTK_WIDGET_CLASS (sn_item_v0_parent_class)->size_allocate (widget, allocation);
1298 
1299   /* FIXME: this leads to grow-only size, unless there's underallocation.
1300    *        not a problem in the panel, but one in the test app. */
1301   if (v0->icon_size <= 0)
1302     {
1303       gint prev_effective_icon_size = v0->effective_icon_size;
1304 
1305       if (gtk_orientable_get_orientation (GTK_ORIENTABLE (v0)) == GTK_ORIENTATION_HORIZONTAL)
1306         v0->effective_icon_size = allocation->height;
1307       else
1308         v0->effective_icon_size = allocation->width;
1309 
1310       if (v0->effective_icon_size != prev_effective_icon_size)
1311         queue_update (SN_ITEM_V0 (widget));
1312     }
1313 }
1314 
1315 static void
sn_item_v0_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)1316 sn_item_v0_get_property (GObject    *object,
1317                          guint       property_id,
1318                          GValue     *value,
1319                          GParamSpec *pspec)
1320 {
1321   SnItemV0 *v0;
1322 
1323   v0 = SN_ITEM_V0 (object);
1324 
1325   switch (property_id)
1326     {
1327       case PROP_ICON_SIZE:
1328         g_value_set_uint (value, v0->icon_size);
1329         break;
1330 
1331       case PROP_ICON_PADDING:
1332         g_value_set_int (value, sn_item_v0_get_icon_padding (v0));
1333         break;
1334 
1335       default:
1336         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1337         break;
1338     }
1339 }
1340 
1341 static void
sn_item_v0_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)1342 sn_item_v0_set_property (GObject      *object,
1343                          guint         property_id,
1344                          const GValue *value,
1345                          GParamSpec   *pspec)
1346 {
1347   SnItemV0 *v0;
1348 
1349   v0 = SN_ITEM_V0 (object);
1350 
1351   switch (property_id)
1352     {
1353       case PROP_ICON_SIZE:
1354         sn_item_v0_set_icon_size (v0, g_value_get_int (value));
1355         break;
1356 
1357       case PROP_ICON_PADDING:
1358         sn_item_v0_set_icon_padding (v0, g_value_get_int (value));
1359         break;
1360 
1361       default:
1362         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1363         break;
1364     }
1365 }
1366 
1367 static void
install_properties(GObjectClass * object_class)1368 install_properties (GObjectClass *object_class)
1369 {
1370   obj_properties[PROP_ICON_SIZE] =
1371     g_param_spec_int ("icon-size", "Icon size", "Icon size", 0, G_MAXINT, 16,
1372                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1373 
1374   obj_properties[PROP_ICON_PADDING] =
1375     g_param_spec_int ("icon-padding", "Icon padding", "Icon padding", 0,
1376                       G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1377 
1378   g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
1379 }
1380 
1381 static void
sn_item_v0_class_init(SnItemV0Class * v0_class)1382 sn_item_v0_class_init (SnItemV0Class *v0_class)
1383 {
1384   GObjectClass *object_class;
1385   GtkWidgetClass *widget_class;
1386   SnItemClass *item_class;
1387 
1388   object_class = G_OBJECT_CLASS (v0_class);
1389   widget_class = GTK_WIDGET_CLASS (v0_class);
1390   item_class = SN_ITEM_CLASS (v0_class);
1391 
1392   object_class->constructed = sn_item_v0_constructed;
1393   object_class->dispose = sn_item_v0_dispose;
1394   object_class->finalize = sn_item_v0_finalize;
1395   object_class->get_property = sn_item_v0_get_property;
1396   object_class->set_property = sn_item_v0_set_property;
1397 
1398   item_class->get_id = sn_item_v0_get_id;
1399   item_class->get_category = sn_item_v0_get_category;
1400   item_class->get_menu = sn_item_v0_get_menu;
1401 
1402   item_class->context_menu = sn_item_v0_context_menu;
1403   item_class->activate = sn_item_v0_activate;
1404   item_class->secondary_activate = sn_item_v0_secondary_activate;
1405   item_class->scroll = sn_item_v0_scroll;
1406 
1407   widget_class->size_allocate = sn_item_v0_size_allocate;
1408 
1409   gtk_widget_class_set_css_name (widget_class, "sn-item");
1410 
1411   install_properties (object_class);
1412 }
1413 
1414 static void
sn_item_v0_init(SnItemV0 * v0)1415 sn_item_v0_init (SnItemV0 *v0)
1416 {
1417   v0->icon_size = 16;
1418   v0->effective_icon_size = 0;
1419   v0->image = gtk_image_new ();
1420   gtk_button_set_image (GTK_BUTTON (v0), v0->image);
1421   gtk_widget_show (v0->image);
1422 }
1423 
1424 SnItem *
sn_item_v0_new(const gchar * bus_name,const gchar * object_path)1425 sn_item_v0_new (const gchar *bus_name,
1426                 const gchar *object_path)
1427 {
1428   return g_object_new (SN_TYPE_ITEM_V0,
1429                        "bus-name", bus_name,
1430                        "object-path", object_path,
1431                        NULL);
1432 }
1433 
1434 gint
sn_item_v0_get_icon_padding(SnItemV0 * v0)1435 sn_item_v0_get_icon_padding (SnItemV0 *v0)
1436 {
1437   GtkOrientation orientation;
1438   gint a, b;
1439 
1440   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (v0));
1441 
1442   if (orientation == GTK_ORIENTATION_HORIZONTAL)
1443     {
1444       a = gtk_widget_get_margin_start (v0->image);
1445       b = gtk_widget_get_margin_end (v0->image);
1446     }
1447   else
1448     {
1449       a = gtk_widget_get_margin_top (v0->image);
1450       b = gtk_widget_get_margin_bottom (v0->image);
1451     }
1452 
1453   return (a + b) / 2;
1454 }
1455 
1456 void
sn_item_v0_set_icon_padding(SnItemV0 * v0,gint padding)1457 sn_item_v0_set_icon_padding (SnItemV0 *v0,
1458                              gint padding)
1459 {
1460   GtkOrientation orientation;
1461   gint padding_x = 0;
1462   gint padding_y = 0;
1463 
1464   orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (v0));
1465 
1466   if (orientation == GTK_ORIENTATION_HORIZONTAL)
1467     padding_x = padding;
1468   else
1469     padding_y = padding;
1470 
1471   gtk_widget_set_margin_start (v0->image, padding_x);
1472   gtk_widget_set_margin_end (v0->image, padding_x);
1473   gtk_widget_set_margin_top (v0->image, padding_y);
1474   gtk_widget_set_margin_bottom (v0->image, padding_y);
1475 }
1476 
1477 gint
sn_item_v0_get_icon_size(SnItemV0 * v0)1478 sn_item_v0_get_icon_size (SnItemV0 *v0)
1479 {
1480   return v0->icon_size;
1481 }
1482 
1483 void
sn_item_v0_set_icon_size(SnItemV0 * v0,gint size)1484 sn_item_v0_set_icon_size (SnItemV0 *v0,
1485                           gint size)
1486 {
1487   if (v0->icon_size != size)
1488     {
1489       v0->icon_size = size;
1490       g_object_notify_by_pspec (G_OBJECT (v0), obj_properties[PROP_ICON_SIZE]);
1491 
1492       if (v0->id != NULL)
1493         queue_update (v0);
1494     }
1495 }
1496