1 /*
2  * statusnotifier - Copyright (C) 2014 Olivier Brunel
3  *
4  * statusnotifier.c
5  * Copyright (C) 2014 Olivier Brunel <jjk@jjacky.com>
6  *
7  * This file is part of statusnotifier.
8  *
9  * statusnotifier is free software: you can redistribute it and/or modify it
10  * under the terms of the GNU General Public License as published by the Free
11  * Software Foundation, either version 3 of the License, or (at your option) any
12  * later version.
13  *
14  * statusnotifier is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.
17  * See the GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * statusnotifier. If not, see http://www.gnu.org/licenses/
21  */
22 
23 //#include "config.h"
24 
25 #include <unistd.h>
26 #include <gdk/gdk.h>
27 #include "statusnotifier.h"
28 #include "enums.h"
29 #include "interfaces.h"
30 #include "closures.h"
31 
32 /**
33  * SECTION:statusnotifier
34  * @Short_description: A StatusNotifierItem as per KDE's specifications
35  *
36  * Starting with Plasma Next, KDE doesn't support the XEmbed systray in favor of
37  * their own Status Notifier Specification.
38  *
39  * A #StatusNotifier is a #GObject that can be used to represent a
40  * StatusNotifierItem, handling all the DBus implementation and leaving you
41  * simply dealing with regular properties and signals.
42  *
43  * You can simply create a new #StatusNotifier using one of the helper function,
44  * e.g. status_notifier_new_from_icon_name(), or simply creating an object as
45  * usual - you then just need to make sure to specify #StatusNotifier:id :
46  * <programlisting>
47  * sn = (StatusNotifier *) g_object_new (TYPE_STATUS_NOTIFIER,
48  *      "id",                       "app-id",
49  *      "status",                   STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION,
50  *      "main-icon-name",           "app-icon",
51  *      "attention-icon-pixbuf",    pixbuf,
52  *      "tooltip-title",            "My tooltip",
53  *      "tooltip-body",             "This is an item about &lt;b&gt;app&lt;/b&gt;",
54  *      NULL);
55  * </programlisting>
56  *
57  * You can also set properties (other than id) after creation. Once ready, call
58  * status_notifier_register() to register the item on the session bus and to the
59  * StatusNotifierWatcher.
60  *
61  * If an error occurs, signal #StatusNotifier::registration-failed will be
62  * emitted. On success, #StatusNotifier:state will be
63  * %STATUS_NOTIFIER_STATE_REGISTERED. See status_notifier_register() for more.
64  *
65  * Once registered, you can change properties as needed, and the proper DBus
66  * signal will be emitted to let visualizations (hosts) know, and connect to the
67  * signals (such as #StatusNotifier::context-menu) which will be emitted when
68  * the corresponding DBus method was called.
69  *
70  * For reference, the KDE specifications can be found at
71  * http://www.notmart.org/misc/statusnotifieritem/index.html
72  */
73 
74 enum
75 {
76     PROP_0,
77 
78     PROP_ID,
79     PROP_TITLE,
80     PROP_CATEGORY,
81     PROP_STATUS,
82     PROP_MAIN_ICON_NAME,
83     PROP_MAIN_ICON_PIXBUF,
84     PROP_OVERLAY_ICON_NAME,
85     PROP_OVERLAY_ICON_PIXBUF,
86     PROP_ATTENTION_ICON_NAME,
87     PROP_ATTENTION_ICON_PIXBUF,
88     PROP_ATTENTION_MOVIE_NAME,
89     PROP_TOOLTIP_ICON_NAME,
90     PROP_TOOLTIP_ICON_PIXBUF,
91     PROP_TOOLTIP_TITLE,
92     PROP_TOOLTIP_BODY,
93     PROP_WINDOW_ID,
94 
95     PROP_STATE,
96 
97     NB_PROPS
98 };
99 
100 static guint prop_name_from_icon[_NB_STATUS_NOTIFIER_ICONS] = {
101     PROP_MAIN_ICON_NAME,
102     PROP_ATTENTION_ICON_NAME,
103     PROP_OVERLAY_ICON_NAME,
104     PROP_TOOLTIP_ICON_NAME
105 };
106 static guint prop_pixbuf_from_icon[_NB_STATUS_NOTIFIER_ICONS] = {
107     PROP_MAIN_ICON_PIXBUF,
108     PROP_ATTENTION_ICON_PIXBUF,
109     PROP_OVERLAY_ICON_PIXBUF,
110     PROP_TOOLTIP_ICON_PIXBUF
111 };
112 
113 enum
114 {
115     SIGNAL_REGISTRATION_FAILED,
116     SIGNAL_CONTEXT_MENU,
117     SIGNAL_ACTIVATE,
118     SIGNAL_SECONDARY_ACTIVATE,
119     SIGNAL_SCROLL,
120     NB_SIGNALS
121 };
122 
123 struct _StatusNotifierPrivate
124 {
125     gchar *id;
126     StatusNotifierCategory category;
127     gchar *title;
128     StatusNotifierStatus status;
129     struct {
130         gboolean has_pixbuf;
131         union {
132             gchar *icon_name;
133             GdkPixbuf *pixbuf;
134         };
135     } icon[_NB_STATUS_NOTIFIER_ICONS];
136     gchar *attention_movie_name;
137     gchar *tooltip_title;
138     gchar *tooltip_body;
139     guint32 window_id;
140 
141     guint tooltip_freeze;
142 
143     StatusNotifierState state;
144     guint dbus_watch_id;
145     guint dbus_sid;
146     guint dbus_owner_id;
147     guint dbus_reg_id;
148     GDBusProxy *dbus_proxy;
149     GDBusConnection *dbus_conn;
150     GError *dbus_err;
151 };
152 
153 static guint uniq_id = 0;
154 
155 static GParamSpec *status_notifier_props[NB_PROPS] = { NULL, };
156 static guint status_notifier_signals[NB_SIGNALS] = { 0, };
157 
158 #define notify(sn,prop) \
159     g_object_notify_by_pspec ((GObject *) sn, status_notifier_props[prop])
160 
161 static void     status_notifier_set_property    (GObject            *object,
162                                                  guint               prop_id,
163                                                  const GValue       *value,
164                                                  GParamSpec         *pspec);
165 static void     status_notifier_get_property    (GObject            *object,
166                                                  guint               prop_id,
167                                                  GValue             *value,
168                                                  GParamSpec         *pspec);
169 static void     status_notifier_finalize        (GObject            *object);
170 
G_DEFINE_TYPE(StatusNotifier,status_notifier,G_TYPE_OBJECT)171 G_DEFINE_TYPE (StatusNotifier, status_notifier, G_TYPE_OBJECT)
172 
173 static void
174 status_notifier_class_init (StatusNotifierClass *klass)
175 {
176     GObjectClass *o_class;
177 
178     o_class = G_OBJECT_CLASS (klass);
179     o_class->set_property   = status_notifier_set_property;
180     o_class->get_property   = status_notifier_get_property;
181     o_class->finalize       = status_notifier_finalize;
182 
183     /**
184      * StatusNotifier:id:
185      *
186      * It's a name that should be unique for this application and consistent
187      * between sessions, such as the application name itself.
188      */
189     status_notifier_props[PROP_ID] =
190         g_param_spec_string ("id", "id", "Unique application identifier",
191                 NULL,
192                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
193 
194     /**
195      * StatusNotifier:title:
196      *
197      * It's a name that describes the application, it can be more descriptive
198      * than #StatusNotifier:id.
199      */
200     status_notifier_props[PROP_TITLE] =
201         g_param_spec_string ("title", "title", "Descriptive name for the item",
202                 NULL,
203                 G_PARAM_READWRITE);
204 
205     /**
206      * StatusNotifier:category:
207      *
208      * Describes the category of this item.
209      */
210     status_notifier_props[PROP_CATEGORY] =
211         g_param_spec_enum ("category", "category", "Category of the item",
212                 TYPE_STATUS_NOTIFIER_CATEGORY,
213                 STATUS_NOTIFIER_CATEGORY_APPLICATION_STATUS,
214                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
215 
216     /**
217      * StatusNotifier:status:
218      *
219      * Describes the status of this item or of the associated application.
220      */
221     status_notifier_props[PROP_STATUS] =
222         g_param_spec_enum ("status", "status", "Status of the item",
223                 TYPE_STATUS_NOTIFIER_STATUS,
224                 STATUS_NOTIFIER_STATUS_PASSIVE,
225                 G_PARAM_READWRITE);
226 
227     /**
228      * StatusNotifier:main-icon-name:
229      *
230      * The item can carry an icon that can be used by the visualization to
231      * identify the item.
232      *
233      * An icon can either be identified by its Freedesktop-compliant icon name,
234      * set by this property, or by the icon data itself, set by the property
235      * #StatusNotifier:main-icon-pixbuf.
236      *
237      * It is currently not possible to set both, as setting one will unset the
238      * other.
239      */
240     status_notifier_props[PROP_MAIN_ICON_NAME] =
241         g_param_spec_string ("main-icon-name", "main-icon-name",
242                 "Icon name for the main icon",
243                 NULL,
244                 G_PARAM_READWRITE);
245 
246     /**
247      * StatusNotifier:main-icon-pixbuf:
248      *
249      * The item can carry an icon that can be used by the visualization to
250      * identify the item.
251      *
252      * An icon can either be identified by its Freedesktop-compliant icon name,
253      * set by property #StatusNotifier:main-icon-name, or by the icon data
254      * itself, set by this property.
255      *
256      * It is currently not possible to set both, as setting one will unset the
257      * other.
258      */
259     status_notifier_props[PROP_MAIN_ICON_PIXBUF] =
260         g_param_spec_object ("main-icon-pixbuf", "main-icon-pixbuf",
261                 "Pixbuf for the main icon",
262                 GDK_TYPE_PIXBUF,
263                 G_PARAM_READWRITE);
264 
265     /**
266      * StatusNotifier:overlay-icon-name:
267      *
268      * This can be used by the visualization to indicate extra state
269      * information, for instance as an overlay for the main icon.
270      *
271      * An icon can either be identified by its Freedesktop-compliant icon name,
272      * set by this property, or by the icon data itself, set by property
273      * #StatusNotifier:overlay-icon-pixbuf.
274      *
275      * It is currently not possible to set both, as setting one will unset the
276      * other.
277      */
278     status_notifier_props[PROP_OVERLAY_ICON_NAME] =
279         g_param_spec_string ("overlay-icon-name", "overlay-icon-name",
280                 "Icon name for the overlay icon",
281                 NULL,
282                 G_PARAM_READWRITE);
283 
284     /**
285      * StatusNotifier:overlay-icon-pixbuf:
286      *
287      * This can be used by the visualization to indicate extra state
288      * information, for instance as an overlay for the main icon.
289      *
290      * An icon can either be identified by its Freedesktop-compliant icon name,
291      * set by property #StatusNotifier:overlay-icon-name, or by the icon data
292      * itself, set by this property.
293      *
294      * It is currently not possible to set both, as setting one will unset the
295      * other.
296      */
297     status_notifier_props[PROP_OVERLAY_ICON_PIXBUF] =
298         g_param_spec_object ("overlay-icon-pixbuf", "overlay-icon-pixbuf",
299                 "Pixbuf for the overlay icon",
300                 GDK_TYPE_PIXBUF,
301                 G_PARAM_READWRITE);
302 
303     /**
304      * StatusNotifier:attention-icon-name:
305      *
306      * This can be used by the visualization to indicate that the item is in
307      * %STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status.
308      *
309      * An icon can either be identified by its Freedesktop-compliant icon name,
310      * set by this property, or by the icon data itself, set by property
311      * #StatusNotifier:attention-icon-pixbuf.
312      *
313      * It is currently not possible to set both, as setting one will unset the
314      * other.
315      */
316     status_notifier_props[PROP_ATTENTION_ICON_NAME] =
317         g_param_spec_string ("attention-icon-name", "attention-icon-name",
318                 "Icon name for the attention icon",
319                 NULL,
320                 G_PARAM_READWRITE);
321 
322     /**
323      * StatusNotifier:attention-icon-pixbuf:
324      *
325      * This can be used by the visualization to indicate that the item is in
326      * %STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status.
327      *
328      * An icon can either be identified by its Freedesktop-compliant icon name,
329      * set by property #StatusNotifier:attention-icon-name, or by the icon data
330      * itself, set by this property.
331      *
332      * It is currently not possible to set both, as setting one will unset the
333      * other.
334      */
335     status_notifier_props[PROP_ATTENTION_ICON_PIXBUF] =
336         g_param_spec_object ("attention-icon-pixbuf", "attention-icon-pixbuf",
337                 "Pixbuf for the attention icon",
338                 GDK_TYPE_PIXBUF,
339                 G_PARAM_READWRITE);
340 
341     /**
342      * StatusNotifier:attention-movie-name:
343      *
344      * In addition to the icon, the item can also specify an animation
345      * associated to the #STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status.
346      *
347      * This should be either a Freedesktop-compliant icon name or a full path.
348      * The visualization can chose between the movie or icon (or using neither
349      * of those) at its discretion.
350      */
351     status_notifier_props[PROP_ATTENTION_MOVIE_NAME] =
352         g_param_spec_string ("attention-movie-name", "attention-movie-name",
353                 "Animation name/full path when the item is in needs-attention status",
354                 NULL,
355                 G_PARAM_READWRITE);
356 
357     /**
358      * StatusNotifier:tooltip-icon-name:
359      *
360      * A tooltip can be defined on the item; It can be used by the visualization
361      * to show as a tooltip (or by any other mean it considers appropriate).
362      *
363      * The tooltip is composed of a title, a body, and an icon. Note that
364      * changing any of these will trigger a DBus signal NewToolTip (for hosts to
365      * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for
366      * changing more than one and only emitting one DBus signal at the end.
367      *
368      * The icon can either be identified by its Freedesktop-compliant icon name,
369      * set by this property, or by the icon data itself, set by property
370      * #StatusNotifier:tooltip-icon-pixbuf.
371      *
372      * It is currently not possible to set both, as setting one will unset the
373      * other.
374      */
375     status_notifier_props[PROP_TOOLTIP_ICON_NAME] =
376         g_param_spec_string ("tooltip-icon-name", "tooltip-icon-name",
377                 "Icon name for the tooltip icon",
378                 NULL,
379                 G_PARAM_READWRITE);
380 
381     /**
382      * StatusNotifier:tooltip-icon-pixbuf:
383      *
384      * A tooltip can be defined on the item; It can be used by the visualization
385      * to show as a tooltip (or by any other mean it considers appropriate).
386      *
387      * The tooltip is composed of a title, a body, and an icon. Note that
388      * changing any of these will trigger a DBus signal NewToolTip (for hosts to
389      * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for
390      * changing more than one and only emitting one DBus signal at the end.
391      *
392      * The icon can either be identified by its Freedesktop-compliant icon name,
393      * set by property #StatusNotifier:tooltip-icon-name, or by the icon data
394      * itself, set by this property.
395      *
396      * It is currently not possible to set both, as setting one will unset the
397      * other.
398      */
399     status_notifier_props[PROP_TOOLTIP_ICON_PIXBUF] =
400         g_param_spec_object ("tooltip-icon-pixbuf", "tooltip-icon-pixbuf",
401                 "Pixbuf for the tooltip icon",
402                 GDK_TYPE_PIXBUF,
403                 G_PARAM_READWRITE);
404 
405     /**
406      * StatusNotifier:tooltip-title:
407      *
408      * A tooltip can be defined on the item; It can be used by the visualization
409      * to show as a tooltip (or by any other mean it considers appropriate).
410      *
411      * The tooltip is composed of a title, a body, and an icon. Note that
412      * changing any of these will trigger a DBus signal NewToolTip (for hosts to
413      * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for
414      * changing more than one and only emitting one DBus signal at the end.
415      */
416     status_notifier_props[PROP_TOOLTIP_TITLE] =
417         g_param_spec_string ("tooltip-title", "tooltip-title",
418                 "Title of the tooltip",
419                 NULL,
420                 G_PARAM_READWRITE);
421 
422     /**
423      * StatusNotifier:tooltip-body:
424      *
425      * A tooltip can be defined on the item; It can be used by the visualization
426      * to show as a tooltip (or by any other mean it considers appropriate).
427      *
428      * The tooltip is composed of a title, a body, and an icon. Note that
429      * changing any of these will trigger a DBus signal NewToolTip (for hosts to
430      * refresh DBus property ToolTip), see status_notifier_freeze_tooltip() for
431      * changing more than one and only emitting one DBus signal at the end.
432      *
433      * This body can contain some markup, which consists of a small subset of
434      * XHTML. See http://www.notmart.org/misc/statusnotifieritem/markup.html for
435      * more.
436      */
437     status_notifier_props[PROP_TOOLTIP_BODY] =
438         g_param_spec_string ("tooltip-body", "tooltip-body",
439                 "Body of the tooltip",
440                 NULL,
441                 G_PARAM_READWRITE);
442 
443     /**
444      * StatusNotifier:window-id:
445      *
446      * It's the windowing-system dependent identifier for a window, the
447      * application can chose one of its windows to be available trough this
448      * property or just set 0 if it's not interested.
449      */
450     status_notifier_props[PROP_WINDOW_ID] =
451         g_param_spec_uint ("window-id", "window-id", "Window ID",
452                 0, G_MAXUINT32,
453                 0,
454                 G_PARAM_READWRITE);
455 
456     /**
457      * StatusNotifier:state:
458      *
459      * The state of the item, regarding its DBus registration on the
460      * StatusNotifierWatcher. After you've created the item, you need to call
461      * status_notifier_register() to have it registered via DBus on the watcher.
462      *
463      * See status_notifier_register() for more.
464      */
465     status_notifier_props[PROP_STATE] =
466         g_param_spec_enum ("state", "state",
467                 "DBus registration state of the item",
468                 TYPE_STATUS_NOTIFIER_STATE,
469                 STATUS_NOTIFIER_STATE_NOT_REGISTERED,
470                 G_PARAM_READABLE);
471 
472     g_object_class_install_properties (o_class, NB_PROPS, status_notifier_props);
473 
474     /**
475      * StatusNotifier::registration-failed:
476      * @sn: The #StatusNotifier
477      * @error: A #GError with the reason of failure
478      *
479      * This signal is emited after a call to status_notifier_register() when
480      * registering the item eventually failed (e.g. if there wasn't (yet) any
481      * StatusNotifierHost registered.)
482      *
483      * When this happens, you should fallback to using the systray. You should
484      * also check #StatusNotifier:state as it might still be
485      * %STATUS_NOTIFIER_STATE_REGISTERING if the registration remains eventually
486      * possible (e.g. waiting for a StatusNotifierHost to register)
487      *
488      * See status_notifier_register() for more.
489      */
490     status_notifier_signals[SIGNAL_REGISTRATION_FAILED] = g_signal_new (
491             "registration-failed",
492             TYPE_STATUS_NOTIFIER,
493             G_SIGNAL_RUN_LAST,
494             G_STRUCT_OFFSET (StatusNotifierClass, registration_failed),
495             NULL,
496             NULL,
497             g_cclosure_marshal_VOID__BOXED,
498             G_TYPE_NONE,
499             1,
500             G_TYPE_ERROR);
501 
502     /**
503      * StatusNotifier::context-menu:
504      * @sn: The #StatusNotifier
505      * @x: screen coordinates X
506      * @y: screen coordinates Y
507      *
508      * Emitted when the ContextMenu method was called on the item. Item should
509      * then show a context menu, this is typically a consequence of user input,
510      * such as mouse right click over the graphical representation of the item.
511      *
512      * @x and @y are to be considered an hint to the item about where to show
513      * the context menu.
514      */
515     status_notifier_signals[SIGNAL_CONTEXT_MENU] = g_signal_new (
516             "context-menu",
517             TYPE_STATUS_NOTIFIER,
518             G_SIGNAL_RUN_LAST,
519             G_STRUCT_OFFSET (StatusNotifierClass, context_menu),
520             g_signal_accumulator_true_handled,
521             NULL,
522             g_cclosure_user_marshal_BOOLEAN__INT_INT,
523             G_TYPE_BOOLEAN,
524             2,
525             G_TYPE_INT,
526             G_TYPE_INT);
527 
528     /**
529      * StatusNotifier::activate:
530      * @sn: The #StatusNotifier
531      * @x: screen coordinates X
532      * @y: screen coordinates Y
533      *
534      * Emitted when the Activate method was called on the item. Activation of
535      * the item was requested, this is typically a consequence of user input,
536      * such as mouse left click over the graphical representation of the item.
537      *
538      * @x and @y are to be considered an hint to the item about where to show
539      * the context menu.
540      */
541     status_notifier_signals[SIGNAL_ACTIVATE] = g_signal_new (
542             "activate",
543             TYPE_STATUS_NOTIFIER,
544             G_SIGNAL_RUN_LAST,
545             G_STRUCT_OFFSET (StatusNotifierClass, activate),
546             g_signal_accumulator_true_handled,
547             NULL,
548             g_cclosure_user_marshal_BOOLEAN__INT_INT,
549             G_TYPE_BOOLEAN,
550             2,
551             G_TYPE_INT,
552             G_TYPE_INT);
553 
554     /**
555      * StatusNotifier::secondary-activate:
556      * @sn: The #StatusNotifier
557      * @x: screen coordinates X
558      * @y: screen coordinates Y
559      *
560      * Emitted when the SecondaryActivate method was called on the item.
561      * Secondary and less important form of activation (compared to
562      * #StatusNotifier::activate) of the item was requested. This is typically a
563      * consequence of user input, such as mouse middle click over the graphical
564      * representation of the item.
565      *
566      * @x and @y are to be considered an hint to the item about where to show
567      * the context menu.
568      */
569     status_notifier_signals[SIGNAL_SECONDARY_ACTIVATE] = g_signal_new (
570             "secondary-activate",
571             TYPE_STATUS_NOTIFIER,
572             G_SIGNAL_RUN_LAST,
573             G_STRUCT_OFFSET (StatusNotifierClass, secondary_activate),
574             g_signal_accumulator_true_handled,
575             NULL,
576             g_cclosure_user_marshal_BOOLEAN__INT_INT,
577             G_TYPE_BOOLEAN,
578             2,
579             G_TYPE_INT,
580             G_TYPE_INT);
581 
582     /**
583      * StatusNotifier::scroll:
584      * @sn: The #StatusNotifier
585      * @delta: the amount of scroll
586      * @orientation: orientation of the scroll request
587      *
588      * Emitted when the Scroll method was called on the item. The user asked for
589      * a scroll action. This is caused from input such as mouse wheel over the
590      * graphical representation of the item.
591      */
592     status_notifier_signals[SIGNAL_SCROLL] = g_signal_new (
593             "scroll",
594             TYPE_STATUS_NOTIFIER,
595             G_SIGNAL_RUN_LAST,
596             G_STRUCT_OFFSET (StatusNotifierClass, scroll),
597             g_signal_accumulator_true_handled,
598             NULL,
599             g_cclosure_user_marshal_BOOLEAN__INT_INT,
600             G_TYPE_BOOLEAN,
601             2,
602             G_TYPE_INT,
603             TYPE_STATUS_NOTIFIER_SCROLL_ORIENTATION);
604 
605     g_type_class_add_private (klass, sizeof (StatusNotifierPrivate));
606 }
607 
608 static void
status_notifier_init(StatusNotifier * sn)609 status_notifier_init (StatusNotifier *sn)
610 {
611     sn->priv = G_TYPE_INSTANCE_GET_PRIVATE (sn,
612             TYPE_STATUS_NOTIFIER, StatusNotifierPrivate);
613 }
614 
615 static void
status_notifier_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)616 status_notifier_set_property (GObject            *object,
617                               guint               prop_id,
618                               const GValue       *value,
619                               GParamSpec         *pspec)
620 {
621     StatusNotifier *sn = (StatusNotifier *) object;
622     StatusNotifierPrivate *priv = sn->priv;
623 
624     switch (prop_id)
625     {
626         case PROP_ID:   /* G_PARAM_CONSTRUCT_ONLY */
627             priv->id = g_value_dup_string (value);
628             break;
629         case PROP_TITLE:
630             status_notifier_set_title (sn, g_value_get_string (value));
631             break;
632         case PROP_CATEGORY: /* G_PARAM_CONSTRUCT_ONLY */
633             priv->category = g_value_get_enum (value);
634             break;
635         case PROP_STATUS:
636             status_notifier_set_status (sn, g_value_get_enum (value));
637             break;
638         case PROP_MAIN_ICON_NAME:
639             status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_ICON,
640                     g_value_get_string (value));
641             break;
642         case PROP_MAIN_ICON_PIXBUF:
643             status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_ICON,
644                     g_value_get_object (value));
645             break;
646         case PROP_OVERLAY_ICON_NAME:
647             status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_OVERLAY_ICON,
648                     g_value_get_string (value));
649             break;
650         case PROP_OVERLAY_ICON_PIXBUF:
651             status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_OVERLAY_ICON,
652                     g_value_get_object (value));
653             break;
654         case PROP_ATTENTION_ICON_NAME:
655             status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_ATTENTION_ICON,
656                     g_value_get_string (value));
657             break;
658         case PROP_ATTENTION_ICON_PIXBUF:
659             status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_ATTENTION_ICON,
660                     g_value_get_object (value));
661             break;
662         case PROP_ATTENTION_MOVIE_NAME:
663             status_notifier_set_attention_movie_name (sn, g_value_get_string (value));
664             break;
665         case PROP_TOOLTIP_ICON_NAME:
666             status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_TOOLTIP_ICON,
667                     g_value_get_string (value));
668             break;
669         case PROP_TOOLTIP_ICON_PIXBUF:
670             status_notifier_set_from_pixbuf (sn, STATUS_NOTIFIER_TOOLTIP_ICON,
671                     g_value_get_object (value));
672             break;
673         case PROP_TOOLTIP_TITLE:
674             status_notifier_set_tooltip_title (sn, g_value_get_string (value));
675             break;
676         case PROP_TOOLTIP_BODY:
677             status_notifier_set_tooltip_body (sn, g_value_get_string (value));
678             break;
679         case PROP_WINDOW_ID:
680             status_notifier_set_window_id (sn, g_value_get_uint (value));
681         default:
682             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
683             break;
684     }
685 }
686 
687 static void
status_notifier_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)688 status_notifier_get_property (GObject            *object,
689                               guint               prop_id,
690                               GValue             *value,
691                               GParamSpec         *pspec)
692 {
693     StatusNotifier *sn = (StatusNotifier *) object;
694     StatusNotifierPrivate *priv = sn->priv;
695 
696     switch (prop_id)
697     {
698         case PROP_ID:
699             g_value_set_string (value, priv->id);
700             break;
701         case PROP_TITLE:
702             g_value_set_string (value, priv->title);
703             break;
704         case PROP_CATEGORY:
705             g_value_set_enum (value, priv->category);
706             break;
707         case PROP_STATUS:
708             g_value_set_enum (value, priv->status);
709             break;
710         case PROP_MAIN_ICON_NAME:
711             g_value_take_string (value, status_notifier_get_icon_name (sn,
712                         STATUS_NOTIFIER_ICON));
713             break;
714         case PROP_MAIN_ICON_PIXBUF:
715             g_value_take_object (value, status_notifier_get_pixbuf (sn,
716                         STATUS_NOTIFIER_ICON));
717             break;
718         case PROP_OVERLAY_ICON_NAME:
719             g_value_take_string (value, status_notifier_get_icon_name (sn,
720                         STATUS_NOTIFIER_OVERLAY_ICON));
721             break;
722         case PROP_OVERLAY_ICON_PIXBUF:
723             g_value_take_object (value, status_notifier_get_pixbuf (sn,
724                         STATUS_NOTIFIER_OVERLAY_ICON));
725             break;
726         case PROP_ATTENTION_ICON_NAME:
727             g_value_take_string (value, status_notifier_get_icon_name (sn,
728                         STATUS_NOTIFIER_ATTENTION_ICON));
729             break;
730         case PROP_ATTENTION_ICON_PIXBUF:
731             g_value_take_object (value, status_notifier_get_pixbuf (sn,
732                         STATUS_NOTIFIER_ATTENTION_ICON));
733             break;
734         case PROP_ATTENTION_MOVIE_NAME:
735             g_value_set_string (value, priv->attention_movie_name);
736             break;
737         case PROP_TOOLTIP_ICON_NAME:
738             g_value_take_string (value, status_notifier_get_icon_name (sn,
739                         STATUS_NOTIFIER_TOOLTIP_ICON));
740             break;
741         case PROP_TOOLTIP_ICON_PIXBUF:
742             g_value_take_object (value, status_notifier_get_pixbuf (sn,
743                         STATUS_NOTIFIER_TOOLTIP_ICON));
744             break;
745         case PROP_TOOLTIP_TITLE:
746             g_value_set_string (value, priv->tooltip_title);
747             break;
748         case PROP_TOOLTIP_BODY:
749             g_value_set_string (value, priv->tooltip_body);
750             break;
751         case PROP_WINDOW_ID:
752             g_value_set_uint (value, priv->window_id);
753         case PROP_STATE:
754             g_value_set_enum (value, priv->state);
755             break;
756         default:
757             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
758             break;
759     }
760 }
761 
762 static void
free_icon(StatusNotifier * sn,StatusNotifierIcon icon)763 free_icon (StatusNotifier *sn, StatusNotifierIcon icon)
764 {
765     StatusNotifierPrivate *priv = sn->priv;
766 
767     if (priv->icon[icon].has_pixbuf)
768         g_object_unref (priv->icon[icon].pixbuf);
769     else
770         g_free (priv->icon[icon].icon_name);
771     priv->icon[icon].has_pixbuf = FALSE;
772     priv->icon[icon].icon_name = NULL;
773 }
774 
775 static void
dbus_free(StatusNotifier * sn)776 dbus_free (StatusNotifier *sn)
777 {
778     StatusNotifierPrivate *priv = sn->priv;
779 
780     if (priv->dbus_watch_id > 0)
781     {
782         g_bus_unwatch_name (priv->dbus_watch_id);
783         priv->dbus_watch_id = 0;
784     }
785     if (priv->dbus_sid > 0)
786     {
787         g_signal_handler_disconnect (priv->dbus_proxy, priv->dbus_sid);
788         priv->dbus_sid = 0;
789     }
790     if (G_LIKELY (priv->dbus_owner_id > 0))
791     {
792         g_bus_unown_name (priv->dbus_owner_id);
793         priv->dbus_owner_id = 0;
794     }
795     if (priv->dbus_proxy)
796     {
797         g_object_unref (priv->dbus_proxy);
798         priv->dbus_proxy = NULL;
799     }
800     if (priv->dbus_reg_id > 0)
801     {
802         g_dbus_connection_unregister_object (priv->dbus_conn, priv->dbus_reg_id);
803         priv->dbus_reg_id = 0;
804     }
805     if (priv->dbus_conn)
806     {
807         g_object_unref (priv->dbus_conn);
808         priv->dbus_conn = NULL;
809     }
810 }
811 
812 static void
status_notifier_finalize(GObject * object)813 status_notifier_finalize (GObject *object)
814 {
815     StatusNotifier *sn = (StatusNotifier *) object;
816     StatusNotifierPrivate *priv = sn->priv;
817     guint i;
818 
819     g_free (priv->id);
820     g_free (priv->title);
821     for (i = 0; i < _NB_STATUS_NOTIFIER_ICONS; ++i)
822         free_icon (sn, i);
823     g_free (priv->attention_movie_name);
824     g_free (priv->tooltip_title);
825     g_free (priv->tooltip_body);
826 
827     dbus_free (sn);
828 
829     G_OBJECT_CLASS (status_notifier_parent_class)->finalize (object);
830 }
831 
832 static void
dbus_notify(StatusNotifier * sn,guint prop)833 dbus_notify (StatusNotifier *sn, guint prop)
834 {
835     StatusNotifierPrivate *priv = sn->priv;
836     const gchar *signal;
837 
838     if (priv->state !=  STATUS_NOTIFIER_STATE_REGISTERED)
839         return;
840 
841     switch (prop)
842     {
843         case PROP_STATUS:
844             {
845                 const gchar *s_status[] = {
846                     "Passive",
847                     "Active",
848                     "NeedsAttention"
849                 };
850                 signal = "NewStatus";
851                 g_dbus_connection_emit_signal (priv->dbus_conn,
852                         NULL,
853                         ITEM_OBJECT,
854                         ITEM_INTERFACE,
855                         signal,
856                         g_variant_new ("(s)", s_status[priv->status]),
857                         NULL);
858                 return;
859             }
860         case PROP_TITLE:
861             signal = "NewTitle";
862             break;
863         case PROP_MAIN_ICON_NAME:
864         case PROP_MAIN_ICON_PIXBUF:
865             signal = "NewIcon";
866             break;
867         case PROP_ATTENTION_ICON_NAME:
868         case PROP_ATTENTION_ICON_PIXBUF:
869             signal = "NewAttentionIcon";
870             break;
871         case PROP_OVERLAY_ICON_NAME:
872         case PROP_OVERLAY_ICON_PIXBUF:
873             signal = "NewOverlayIcon";
874             break;
875         case PROP_TOOLTIP_TITLE:
876         case PROP_TOOLTIP_BODY:
877         case PROP_TOOLTIP_ICON_NAME:
878         case PROP_TOOLTIP_ICON_PIXBUF:
879             signal = "NewToolTip";
880             break;
881         default:
882             g_return_if_reached ();
883     }
884 
885     g_dbus_connection_emit_signal (priv->dbus_conn,
886             NULL,
887             ITEM_OBJECT,
888             ITEM_INTERFACE,
889             signal,
890             NULL,
891             NULL);
892 }
893 
894 /**
895  * status_notifier_new_from_pixbuf:
896  * @id: The application id
897  * @category: The category for the item
898  * @pixbuf: The icon to use as main icon
899  *
900  * Creates a new item
901  *
902  * Returns: (transfer full): A new #StatusNotifier
903  */
904 StatusNotifier *
status_notifier_new_from_pixbuf(const gchar * id,StatusNotifierCategory category,GdkPixbuf * pixbuf)905 status_notifier_new_from_pixbuf (const gchar             *id,
906                                  StatusNotifierCategory   category,
907                                  GdkPixbuf               *pixbuf)
908 {
909     return (StatusNotifier *) g_object_new (TYPE_STATUS_NOTIFIER,
910             "id",               id,
911             "category",         category,
912             "main-icon-pixbuf", pixbuf,
913             NULL);
914 }
915 
916 /**
917  * status_notifier_new_from_icon_name:
918  * @id: The application id
919  * @category: The category for the item
920  * @icon_name: The name of the icon to use as main icon
921  *
922  * Creates a new item
923  *
924  * Returns: (transfer full): A new #StatusNotifier
925  */
926 StatusNotifier *
status_notifier_new_from_icon_name(const gchar * id,StatusNotifierCategory category,const gchar * icon_name)927 status_notifier_new_from_icon_name (const gchar             *id,
928                                     StatusNotifierCategory   category,
929                                     const gchar             *icon_name)
930 {
931     return (StatusNotifier *) g_object_new (TYPE_STATUS_NOTIFIER,
932             "id",               id,
933             "category",         category,
934             "main-icon-name",   icon_name,
935             NULL);
936 }
937 
938 /**
939  * status_notifier_get_id:
940  * @sn: A #StatusNotifier
941  *
942  * Returns the application id of @sn
943  *
944  * Returns: The application id of @sn. The string is owned by @sn, you should
945  * not free it
946  */
947 const gchar *
status_notifier_get_id(StatusNotifier * sn)948 status_notifier_get_id (StatusNotifier          *sn)
949 {
950     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL);
951     return sn->priv->id;
952 }
953 
954 /**
955  * status_notifier_get_category:
956  * @sn: A #StatusNotifier
957  *
958  * Returns the category of @sn
959  *
960  * Returns: The category of @sn
961  */
962 StatusNotifierCategory
status_notifier_get_category(StatusNotifier * sn)963 status_notifier_get_category (StatusNotifier          *sn)
964 {
965     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), -1);
966     return sn->priv->category;
967 }
968 
969 /**
970  * status_notifier_set_from_pixbuf:
971  * @sn: A #StatusNotifier
972  * @icon: Which icon to set
973  * @pixbuf: A #GdkPixbuf to use for @icon
974  *
975  * Sets the icon @icon to @pixbuf.
976  *
977  * An icon can either be identified by its Freedesktop-compliant icon name,
978  * or by the icon data itself (via #GdkPixbuf).
979  *
980  * It is currently not possible to set both, as setting one will unset the
981  * other.
982  */
983 void
status_notifier_set_from_pixbuf(StatusNotifier * sn,StatusNotifierIcon icon,GdkPixbuf * pixbuf)984 status_notifier_set_from_pixbuf (StatusNotifier          *sn,
985                                  StatusNotifierIcon       icon,
986                                  GdkPixbuf               *pixbuf)
987 {
988     StatusNotifierPrivate *priv;
989 
990     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
991     priv = sn->priv;
992 
993     free_icon (sn, icon);
994     priv->icon[icon].has_pixbuf = TRUE;
995     priv->icon[icon].pixbuf = g_object_ref (pixbuf);
996 
997     notify (sn, prop_name_from_icon[icon]);
998     if (icon != STATUS_NOTIFIER_TOOLTIP_ICON || priv->tooltip_freeze == 0)
999         dbus_notify (sn, prop_name_from_icon[icon]);
1000 }
1001 
1002 /**
1003  * status_notifier_set_from_icon_name:
1004  * @sn: A #StatusNotifier
1005  * @icon: Which icon to set
1006  * @icon_name: Name of an icon to use for @icon
1007  *
1008  * Sets the icon @icon to be @icon_name.
1009  *
1010  * An icon can either be identified by its Freedesktop-compliant icon name,
1011  * or by the icon data itself (via #GdkPixbuf).
1012  *
1013  * It is currently not possible to set both, as setting one will unset the
1014  * other.
1015  */
1016 void
status_notifier_set_from_icon_name(StatusNotifier * sn,StatusNotifierIcon icon,const gchar * icon_name)1017 status_notifier_set_from_icon_name (StatusNotifier          *sn,
1018                                     StatusNotifierIcon       icon,
1019                                     const gchar             *icon_name)
1020 {
1021     StatusNotifierPrivate *priv;
1022 
1023     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1024     priv = sn->priv;
1025 
1026     free_icon (sn, icon);
1027     priv->icon[icon].icon_name = g_strdup (icon_name);
1028 
1029     notify (sn, prop_pixbuf_from_icon[icon]);
1030     if (icon != STATUS_NOTIFIER_TOOLTIP_ICON || priv->tooltip_freeze == 0)
1031         dbus_notify (sn, prop_name_from_icon[icon]);
1032 }
1033 
1034 /**
1035  * status_notifier_has_pixbuf:
1036  * @sn: A #StatusNotifier
1037  * @icon: Which icon
1038  *
1039  * Returns whether icon @icon currently has a #GdkPixbuf set or not. If so, the
1040  * icon data will be sent via DBus, else the icon name (if any) will be used.
1041  *
1042  * Returns: %TRUE is a #GdkPixbuf is set for @icon, else %FALSE
1043  */
1044 gboolean
status_notifier_has_pixbuf(StatusNotifier * sn,StatusNotifierIcon icon)1045 status_notifier_has_pixbuf (StatusNotifier          *sn,
1046                             StatusNotifierIcon       icon)
1047 {
1048     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), FALSE);
1049     return sn->priv->icon[icon].has_pixbuf;
1050 }
1051 
1052 /**
1053  * status_notifier_get_pixbuf:
1054  * @sn: A #StatusNotifier
1055  * @icon: The icon to get
1056  *
1057  * Returns the #GdkPixbuf set for @icon, if there's one. Not that it will return
1058  * %NULL if an icon name is set.
1059  *
1060  * Returns: (transfer full): The #GdkPixbuf set for @icon, or %NULL
1061  */
1062 GdkPixbuf *
status_notifier_get_pixbuf(StatusNotifier * sn,StatusNotifierIcon icon)1063 status_notifier_get_pixbuf (StatusNotifier          *sn,
1064                             StatusNotifierIcon       icon)
1065 {
1066     StatusNotifierPrivate *priv;
1067 
1068     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL);
1069     priv = sn->priv;
1070 
1071     if (!priv->icon[icon].has_pixbuf)
1072         return NULL;
1073 
1074     return g_object_ref (priv->icon[icon].pixbuf);
1075 }
1076 
1077 /**
1078  * status_notifier_get_icon_name:
1079  * @sn: A #StatusNotifier
1080  * @icon: The icon to get
1081  *
1082  * Returns the icon name set for @icon, if there's one. Not that it will return
1083  * %NULL if a #GdkPixbuf is set.
1084  *
1085  * Returns: (transfer full): A newly allocated string of the icon name set for
1086  * @icon, free using g_free()
1087  */
1088 gchar *
status_notifier_get_icon_name(StatusNotifier * sn,StatusNotifierIcon icon)1089 status_notifier_get_icon_name (StatusNotifier          *sn,
1090                                StatusNotifierIcon       icon)
1091 {
1092     StatusNotifierPrivate *priv;
1093 
1094     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL);
1095     priv = sn->priv;
1096 
1097     if (priv->icon[icon].has_pixbuf)
1098         return NULL;
1099 
1100     return g_strdup (priv->icon[icon].icon_name);
1101 }
1102 
1103 /**
1104  * status_notifier_set_attention_movie_name:
1105  * @sn: A #StatusNotifier
1106  * @movie_name: The name of the movie
1107  *
1108  * In addition to the icon, the item can also specify an animation associated to
1109  * the #STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status.
1110  *
1111  * This should be either a Freedesktop-compliant icon name or a full path.  The
1112  * visualization can chose between the movie or icon (or using neither of those)
1113  * at its discretion.
1114  */
1115 void
status_notifier_set_attention_movie_name(StatusNotifier * sn,const gchar * movie_name)1116 status_notifier_set_attention_movie_name (StatusNotifier          *sn,
1117                                           const gchar             *movie_name)
1118 {
1119     StatusNotifierPrivate *priv;
1120 
1121     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1122     priv = sn->priv;
1123 
1124     g_free (priv->attention_movie_name);
1125     priv->attention_movie_name = g_strdup (movie_name);
1126 
1127     notify (sn, PROP_ATTENTION_MOVIE_NAME);
1128 }
1129 
1130 /**
1131  * status_notifier_get_attention_movie_name:
1132  * @sn: A #StatusNotifier
1133  *
1134  * Returns the movie name set for animation associated with the
1135  * #STATUS_NOTIFIER_STATUS_NEEDS_ATTENTION status
1136  *
1137  * Returns: A newly allocated string with the movie name, free using g_free()
1138  * when done
1139  */
1140 gchar *
status_notifier_get_attention_movie_name(StatusNotifier * sn)1141 status_notifier_get_attention_movie_name (StatusNotifier          *sn)
1142 {
1143     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL);
1144     return g_strdup (sn->priv->attention_movie_name);
1145 }
1146 
1147 /**
1148  * status_notifier_set_title:
1149  * @sn: A #StatusNotifier
1150  * @title: The title
1151  *
1152  * Sets the title of the item (might be used by visualization e.g. in menu of
1153  * hidden items when #STATUS_NOTIFIER_STATUS_PASSIVE)
1154  */
1155 void
status_notifier_set_title(StatusNotifier * sn,const gchar * title)1156 status_notifier_set_title (StatusNotifier          *sn,
1157                            const gchar             *title)
1158 {
1159     StatusNotifierPrivate *priv;
1160 
1161     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1162     priv = sn->priv;
1163 
1164     g_free (priv->title);
1165     priv->title = g_strdup (title);
1166 
1167     notify (sn, PROP_TITLE);
1168     dbus_notify (sn, PROP_TITLE);
1169 }
1170 
1171 /**
1172  * status_notifier_get_title:
1173  * @sn: A #StatusNotifier
1174  *
1175  * Returns the title of the item
1176  *
1177  * Returns: A newly allocated string, free with g_free() when done
1178  */
1179 gchar *
status_notifier_get_title(StatusNotifier * sn)1180 status_notifier_get_title (StatusNotifier          *sn)
1181 {
1182     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL);
1183     return g_strdup (sn->priv->title);
1184 }
1185 
1186 /**
1187  * status_notifier_set_status:
1188  * @sn: A #StatusNotifier
1189  * @status: The new status
1190  *
1191  * Sets the item status to @status, describing the status of this item or of the
1192  * associated application.
1193  */
1194 void
status_notifier_set_status(StatusNotifier * sn,StatusNotifierStatus status)1195 status_notifier_set_status (StatusNotifier          *sn,
1196                             StatusNotifierStatus     status)
1197 {
1198     StatusNotifierPrivate *priv;
1199 
1200     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1201     priv = sn->priv;
1202 
1203     priv->status = status;
1204 
1205     notify (sn, PROP_STATUS);
1206     dbus_notify (sn, PROP_STATUS);
1207 }
1208 
1209 /**
1210  * status_notifier_get_status:
1211  * @sn: A #StatusNotifier
1212  *
1213  * Returns the status of @sn
1214  *
1215  * Returns: Current status of @sn
1216  */
1217 StatusNotifierStatus
status_notifier_get_status(StatusNotifier * sn)1218 status_notifier_get_status (StatusNotifier          *sn)
1219 {
1220     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), -1);
1221     return sn->priv->status;
1222 }
1223 
1224 /**
1225  * status_notifier_set_window_id:
1226  * @sn: A #StatusNotifier
1227  * @window_id: The window ID
1228  *
1229  * Sets the window ID for @sn
1230  *
1231  * It's the windowing-system dependent identifier for a window, the application
1232  * can chose one of its windows to be available trough this property or just set
1233  * 0 if it's not interested.
1234  */
1235 void
status_notifier_set_window_id(StatusNotifier * sn,guint32 window_id)1236 status_notifier_set_window_id (StatusNotifier          *sn,
1237                                guint32                  window_id)
1238 {
1239     StatusNotifierPrivate *priv;
1240 
1241     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1242     priv = sn->priv;
1243 
1244     priv->window_id = window_id;
1245 
1246     notify (sn, PROP_WINDOW_ID);
1247 }
1248 
1249 /**
1250  * status_notifier_get_window_id:
1251  * @sn: A #StatusNotifier
1252  *
1253  * Returns the windowing-system dependent idnetifier for a window associated
1254  * with @sn
1255  *
1256  * Returns: The window ID associated with @sn
1257  */
1258 guint32
status_notifier_get_window_id(StatusNotifier * sn)1259 status_notifier_get_window_id (StatusNotifier          *sn)
1260 {
1261     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), 0);
1262     return sn->priv->window_id;
1263 }
1264 
1265 /**
1266  * status_notifier_freeze_tooltip:
1267  * @sn:A #StatusNotifier
1268  *
1269  * Increases the freeze count for tooltip on @sn. If the freeze count is
1270  * non-zero, the emission of a DBus signal for StatusNotifierHost to refresh the
1271  * ToolTip property will be blocked until the freeze count drops back to zero
1272  * (via status_notifier_thaw_tooltip())
1273  *
1274  * This is to allow to set the different properties forming the tooltip (title,
1275  * body and icon) without triggering a refresh afetr each change (as there is a
1276  * single property ToolTip on the DBus item, with all data).
1277  *
1278  * Every call to status_notifier_freeze_tooltip() should later be followed by a
1279  * call to status_notifier_thaw_tooltip()
1280  */
1281 void
status_notifier_freeze_tooltip(StatusNotifier * sn)1282 status_notifier_freeze_tooltip (StatusNotifier          *sn)
1283 {
1284     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1285     ++sn->priv->tooltip_freeze;
1286 }
1287 
1288 /**
1289  * status_notifier_thaw_tooltip:
1290  * @sn: A #StatusNotifier
1291  *
1292  * Reverts the effect of a previous call to status_notifier_freeze_tooltip(). If
1293  * the freeze count drops back to zero, a signal NewToolTip will be emitted on
1294  * the DBus object for @sn, for StatusNotifierHost to refresh its ToolTip
1295  * property.
1296  *
1297  * It is an error to call this function when the freeze count is zero.
1298  */
1299 void
status_notifier_thaw_tooltip(StatusNotifier * sn)1300 status_notifier_thaw_tooltip (StatusNotifier          *sn)
1301 {
1302     StatusNotifierPrivate *priv;
1303 
1304     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1305     priv = sn->priv;
1306     g_return_if_fail (priv->tooltip_freeze > 0);
1307 
1308     if (--priv->tooltip_freeze == 0)
1309         dbus_notify (sn, PROP_TOOLTIP_TITLE);
1310 }
1311 
1312 /**
1313  * status_notifier_set_tooltip:
1314  * @sn: A #StatusNotifier
1315  * @icon_name: The icon name to be used for #STATUS_NOTIFIER_TOOLTIP_ICON
1316  * @title: The title of the tooltip
1317  * @body: The body of the tooltip
1318  *
1319  * This is an helper function that allows to set icon name, title and body of
1320  * the tooltip and then emit one DBus signal NewToolTip.
1321  *
1322  * It is equivalent to the following code, and similar code can be used e.g. to
1323  * set the icon from a #GdkPixbuf instead:
1324  * <programlisting>
1325  * status_notifier_freeze_tooltip (sn);
1326  * status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_TOOLTIP_ICON, icon_name);
1327  * status_notifier_set_tooltip_title (sn, title);
1328  * status_notifier_set_tooltip_body (sn, body);
1329  * status_notifier_thaw_tooltip (sn);
1330  * </programlisting>
1331  */
1332 void
status_notifier_set_tooltip(StatusNotifier * sn,const gchar * icon_name,const gchar * title,const gchar * body)1333 status_notifier_set_tooltip (StatusNotifier          *sn,
1334                              const gchar             *icon_name,
1335                              const gchar             *title,
1336                              const gchar             *body)
1337 {
1338     StatusNotifierPrivate *priv;
1339 
1340     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1341     priv = sn->priv;
1342 
1343     ++priv->tooltip_freeze;
1344     status_notifier_set_from_icon_name (sn, STATUS_NOTIFIER_TOOLTIP_ICON, icon_name);
1345     status_notifier_set_tooltip_title (sn, title);
1346     status_notifier_set_tooltip_body (sn, body);
1347     status_notifier_thaw_tooltip (sn);
1348 }
1349 
1350 /**
1351  * status_notifier_set_tooltip_title:
1352  * @sn: A #StatusNotifier
1353  * @title: The tooltip title
1354  *
1355  * Sets the title of the tooltip
1356  *
1357  * The tooltip is composed of a title, a body, and an icon. Note that changing
1358  * any of these will trigger a DBus signal NewToolTip (for hosts to refresh DBus
1359  * property ToolTip), see status_notifier_freeze_tooltip() for changing more
1360  * than one and only emitting one DBus signal at the end.
1361  */
1362 void
status_notifier_set_tooltip_title(StatusNotifier * sn,const gchar * title)1363 status_notifier_set_tooltip_title (StatusNotifier          *sn,
1364                                    const gchar             *title)
1365 {
1366     StatusNotifierPrivate *priv;
1367 
1368     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1369     priv = sn->priv;
1370 
1371     g_free (priv->tooltip_title);
1372     priv->tooltip_title = g_strdup (title);
1373 
1374     notify (sn, PROP_TOOLTIP_TITLE);
1375     if (priv->tooltip_freeze == 0)
1376         dbus_notify (sn, PROP_TOOLTIP_TITLE);
1377 }
1378 
1379 /**
1380  * status_notifier_get_tooltip_title:
1381  * @sn: A #StatusNotifier
1382  *
1383  * Returns the tooltip title
1384  *
1385  * Returns: A newly allocated string of the tooltip title, use g_free() when
1386  * done
1387  */
1388 gchar *
status_notifier_get_tooltip_title(StatusNotifier * sn)1389 status_notifier_get_tooltip_title (StatusNotifier          *sn)
1390 {
1391     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL);
1392     return g_strdup (sn->priv->tooltip_title);
1393 }
1394 
1395 /**
1396  * status_notifier_set_tooltip_body:
1397  * @sn: A #StatusNotifier
1398  * @body: The tooltip body
1399  *
1400  * Sets the body of the tooltip
1401  *
1402  * This body can contain some markup, which consists of a small subset of XHTML.
1403  * See http://www.notmart.org/misc/statusnotifieritem/markup.html for more.
1404  *
1405  * The tooltip is composed of a title, a body, and an icon. Note that changing
1406  * any of these will trigger a DBus signal NewToolTip (for hosts to refresh DBus
1407  * property ToolTip), see status_notifier_freeze_tooltip() for changing more
1408  * than one and only emitting one DBus signal at the end.
1409  */
1410 void
status_notifier_set_tooltip_body(StatusNotifier * sn,const gchar * body)1411 status_notifier_set_tooltip_body (StatusNotifier          *sn,
1412                                   const gchar             *body)
1413 {
1414     StatusNotifierPrivate *priv;
1415 
1416     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1417     priv = sn->priv;
1418 
1419     g_free (priv->tooltip_body);
1420     priv->tooltip_body = g_strdup (body);
1421 
1422     notify (sn, PROP_TOOLTIP_BODY);
1423     if (priv->tooltip_freeze == 0)
1424         dbus_notify (sn, PROP_TOOLTIP_BODY);
1425 }
1426 
1427 /**
1428  * status_notifier_get_tooltip_body:
1429  * @sn: A #StatusNotifier
1430  *
1431  * Returns the tooltip body
1432  *
1433  * Returns: A newly allocated string of the tooltip body, use g_free() when done
1434  */
1435 gchar *
status_notifier_get_tooltip_body(StatusNotifier * sn)1436 status_notifier_get_tooltip_body (StatusNotifier          *sn)
1437 {
1438     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), NULL);
1439     return g_strdup (sn->priv->tooltip_body);
1440 }
1441 
1442 static void
method_call(GDBusConnection * conn,const gchar * sender,const gchar * object,const gchar * interface,const gchar * method,GVariant * params,GDBusMethodInvocation * invocation,gpointer data)1443 method_call (GDBusConnection        *conn,
1444              const gchar            *sender,
1445              const gchar            *object,
1446              const gchar            *interface,
1447              const gchar            *method,
1448              GVariant               *params,
1449              GDBusMethodInvocation  *invocation,
1450              gpointer                data)
1451 {
1452     (void)conn;
1453     (void)sender;
1454     (void)object;
1455     (void)interface;
1456     StatusNotifier *sn = (StatusNotifier *) data;
1457     guint signal;
1458     gint x, y;
1459     gboolean ret;
1460 
1461     if (!g_strcmp0 (method, "ContextMenu"))
1462         signal = SIGNAL_CONTEXT_MENU;
1463     else if (!g_strcmp0 (method, "Activate"))
1464         signal = SIGNAL_ACTIVATE;
1465     else if (!g_strcmp0 (method, "SecondaryActivate"))
1466         signal = SIGNAL_SECONDARY_ACTIVATE;
1467     else if (!g_strcmp0 (method, "Scroll"))
1468     {
1469         gint delta, orientation;
1470         gchar *s_orientation;
1471 
1472         g_variant_get (params, "(is)", &delta, &s_orientation);
1473         if (!g_strcmp0 (s_orientation, "vertical"))
1474             orientation = STATUS_NOTIFIER_SCROLL_ORIENTATION_VERTICAL;
1475         else
1476             orientation = STATUS_NOTIFIER_SCROLL_ORIENTATION_HORIZONTAL;
1477         g_free (s_orientation);
1478 
1479         g_signal_emit (sn, status_notifier_signals[SIGNAL_SCROLL], 0,
1480                 delta, orientation, &ret);
1481         g_dbus_method_invocation_return_value (invocation, NULL);
1482         return;
1483     }
1484     else
1485         /* should never happen */
1486         g_return_if_reached ();
1487 
1488     g_variant_get (params, "(ii)", &x, &y);
1489     g_signal_emit (sn, status_notifier_signals[signal], 0, x, y, &ret);
1490     g_dbus_method_invocation_return_value (invocation, NULL);
1491 }
1492 
1493 static GVariant *
get_icon_pixmap(StatusNotifier * sn,StatusNotifierIcon icon)1494 get_icon_pixmap (StatusNotifier *sn, StatusNotifierIcon icon)
1495 {
1496     StatusNotifierPrivate *priv = sn->priv;
1497     GVariantBuilder *builder;
1498     cairo_surface_t *surface;
1499     cairo_t *cr;
1500     gint width, height, stride;
1501     guint *data;
1502 
1503     if (G_UNLIKELY (!priv->icon[icon].has_pixbuf))
1504         return NULL;
1505 
1506     width = gdk_pixbuf_get_width (priv->icon[icon].pixbuf);
1507     height = gdk_pixbuf_get_height (priv->icon[icon].pixbuf);
1508 
1509     surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
1510     cr = cairo_create (surface);
1511     gdk_cairo_set_source_pixbuf (cr, priv->icon[icon].pixbuf, 0, 0);
1512     cairo_paint (cr);
1513     cairo_destroy (cr);
1514 
1515     stride = cairo_image_surface_get_stride (surface);
1516     cairo_surface_flush (surface);
1517     data = (guint *) cairo_image_surface_get_data (surface);
1518 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
1519     guint i, max;
1520 
1521     max = (stride * height) / sizeof (guint);
1522     for (i = 0; i < max; ++i)
1523         data[i] = GUINT_TO_BE (data[i]);
1524 #endif
1525 
1526     builder = g_variant_builder_new (G_VARIANT_TYPE ("a(iiay)"));
1527     g_variant_builder_open (builder, G_VARIANT_TYPE ("(iiay)"));
1528     g_variant_builder_add (builder, "i", width);
1529     g_variant_builder_add (builder, "i", height);
1530     g_variant_builder_add_value (builder,
1531             g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
1532                 data,
1533                 stride * height,
1534                 TRUE,
1535                 (GDestroyNotify) cairo_surface_destroy,
1536                 surface));
1537     g_variant_builder_close (builder);
1538     GVariant *pixmap = g_variant_new ("a(iiay)", builder);
1539 
1540     g_variant_builder_unref(builder);   // Allow the builder to be deallocated
1541 
1542     return pixmap;
1543 }
1544 
1545 static GVariant *
get_prop(GDBusConnection * conn,const gchar * sender,const gchar * object,const gchar * interface,const gchar * property,GError ** error,gpointer data)1546 get_prop (GDBusConnection        *conn,
1547           const gchar            *sender,
1548           const gchar            *object,
1549           const gchar            *interface,
1550           const gchar            *property,
1551           GError                **error,
1552           gpointer                data)
1553 {
1554     (void)conn;
1555     (void)sender;
1556     (void)object;
1557     (void)interface;
1558     (void)error;
1559     StatusNotifier *sn = (StatusNotifier *) data;
1560     StatusNotifierPrivate *priv = sn->priv;
1561 
1562     if (!g_strcmp0 (property, "Id"))
1563         return g_variant_new ("s", priv->id);
1564     else if (!g_strcmp0 (property, "Category"))
1565     {
1566         const gchar *s_category[] = {
1567             "ApplicationStatus",
1568             "Communications",
1569             "SystemServices",
1570             "Hardware"
1571         };
1572         return g_variant_new ("s", s_category[priv->category]);
1573     }
1574     else if (!g_strcmp0 (property, "Title"))
1575         return g_variant_new ("s", (priv->title) ? priv->title : "");
1576     else if (!g_strcmp0 (property, "Status"))
1577     {
1578         const gchar *s_status[] = {
1579             "Passive",
1580             "Active",
1581             "NeedsAttention"
1582         };
1583         return g_variant_new ("s", s_status[priv->status]);
1584     }
1585     else if (!g_strcmp0 (property, "WindowId"))
1586         return g_variant_new ("i", priv->window_id);
1587     else if (!g_strcmp0 (property, "IconName"))
1588         return g_variant_new ("s", (!priv->icon[STATUS_NOTIFIER_ICON].has_pixbuf)
1589                 ? ((priv->icon[STATUS_NOTIFIER_ICON].icon_name)
1590                     ? priv->icon[STATUS_NOTIFIER_ICON].icon_name : "") : "");
1591     else if (!g_strcmp0 (property, "IconPixmap"))
1592         return get_icon_pixmap (sn, STATUS_NOTIFIER_ICON);
1593     else if (!g_strcmp0 (property, "OverlayIconName"))
1594         return g_variant_new ("s", (!priv->icon[STATUS_NOTIFIER_OVERLAY_ICON].has_pixbuf)
1595                 ? ((priv->icon[STATUS_NOTIFIER_OVERLAY_ICON].icon_name)
1596                     ? priv->icon[STATUS_NOTIFIER_OVERLAY_ICON].icon_name : "") : "");
1597     else if (!g_strcmp0 (property, "OverlayIconPixmap"))
1598         return get_icon_pixmap (sn, STATUS_NOTIFIER_OVERLAY_ICON);
1599     else if (!g_strcmp0 (property, "AttentionIconName"))
1600         return g_variant_new ("s", (!priv->icon[STATUS_NOTIFIER_ATTENTION_ICON].has_pixbuf)
1601                 ? ((priv->icon[STATUS_NOTIFIER_ATTENTION_ICON].icon_name)
1602                     ? priv->icon[STATUS_NOTIFIER_ATTENTION_ICON].icon_name : "") : "");
1603     else if (!g_strcmp0 (property, "AttentionIconPixmap"))
1604         return get_icon_pixmap (sn, STATUS_NOTIFIER_ATTENTION_ICON);
1605     else if (!g_strcmp0 (property, "AttentionMovieName"))
1606         return g_variant_new ("s", (priv->attention_movie_name)
1607                 ? priv->attention_movie_name : "");
1608     else if (!g_strcmp0 (property, "ToolTip"))
1609     {
1610         GVariant *variant;
1611         GVariant *pixmap;
1612 
1613         if (!priv->icon[STATUS_NOTIFIER_TOOLTIP_ICON].has_pixbuf)
1614         {
1615             variant = g_variant_new ("(sa(iiay)ss)",
1616                     (priv->icon[STATUS_NOTIFIER_TOOLTIP_ICON].icon_name)
1617                     ? priv->icon[STATUS_NOTIFIER_TOOLTIP_ICON].icon_name : "",
1618                     NULL,
1619                     (priv->tooltip_title) ? priv->tooltip_title : "",
1620                     (priv->tooltip_body) ? priv->tooltip_body : "");
1621             return variant;
1622         }
1623 
1624         pixmap = get_icon_pixmap (sn, STATUS_NOTIFIER_TOOLTIP_ICON);
1625         variant = g_variant_new ("(sa(iiay)ss)",
1626                 "",
1627                 pixmap,
1628                 (priv->tooltip_title) ? priv->tooltip_title : "",
1629                 (priv->tooltip_body) ? priv->tooltip_body : "");
1630         g_variant_unref (pixmap);
1631 
1632         return variant;
1633     }
1634 
1635     g_return_val_if_reached (NULL);
1636 }
1637 
1638 static void
dbus_failed(StatusNotifier * sn,GError * error,gboolean fatal)1639 dbus_failed (StatusNotifier *sn, GError *error, gboolean fatal)
1640 {
1641     StatusNotifierPrivate *priv = sn->priv;
1642 
1643     dbus_free (sn);
1644     if (fatal)
1645     {
1646         priv->state = STATUS_NOTIFIER_STATE_FAILED;
1647         notify (sn, PROP_STATE);
1648     }
1649     g_signal_emit (sn, status_notifier_signals[SIGNAL_REGISTRATION_FAILED], 0,
1650             error);
1651     g_error_free (error);
1652 }
1653 
1654 static void
bus_acquired(GDBusConnection * conn,const gchar * name,gpointer data)1655 bus_acquired (GDBusConnection *conn, const gchar *name, gpointer data)
1656 {
1657     (void)name;
1658     GError *err = NULL;
1659     StatusNotifier *sn = (StatusNotifier *) data;
1660     StatusNotifierPrivate *priv = sn->priv;
1661     GDBusInterfaceVTable interface_vtable = {
1662         .method_call = method_call,
1663         .get_property = get_prop,
1664         .set_property = NULL
1665     };
1666     GDBusNodeInfo *info;
1667 
1668     info = g_dbus_node_info_new_for_xml (item_xml, NULL);
1669     priv->dbus_reg_id = g_dbus_connection_register_object (conn,
1670             ITEM_OBJECT,
1671             info->interfaces[0],
1672             &interface_vtable,
1673             sn, NULL,
1674             &err);
1675     g_dbus_node_info_unref (info);
1676     if (priv->dbus_reg_id == 0)
1677     {
1678         dbus_failed (sn, err, TRUE);
1679         return;
1680     }
1681 
1682     priv->dbus_conn = g_object_ref (conn);
1683 }
1684 
1685 static void
register_item_cb(GObject * sce,GAsyncResult * result,gpointer data)1686 register_item_cb (GObject *sce, GAsyncResult *result, gpointer data)
1687 {
1688     GError *err = NULL;
1689     StatusNotifier *sn = (StatusNotifier *) data;
1690     StatusNotifierPrivate *priv = sn->priv;
1691     GVariant *variant;
1692 
1693     variant = g_dbus_proxy_call_finish ((GDBusProxy *) sce, result, &err);
1694     if (!variant)
1695     {
1696         dbus_failed (sn, err, TRUE);
1697         return;
1698     }
1699     g_variant_unref (variant);
1700 
1701     priv->state = STATUS_NOTIFIER_STATE_REGISTERED;
1702     notify (sn, PROP_STATE);
1703 }
1704 
1705 static void
name_acquired(GDBusConnection * conn,const gchar * name,gpointer data)1706 name_acquired (GDBusConnection *conn, const gchar *name, gpointer data)
1707 {
1708     (void)conn;
1709     StatusNotifier *sn = (StatusNotifier *) data;
1710     StatusNotifierPrivate *priv = sn->priv;
1711 
1712     g_dbus_proxy_call (priv->dbus_proxy,
1713             "RegisterStatusNotifierItem",
1714             g_variant_new ("(s)", name),
1715             G_DBUS_CALL_FLAGS_NONE,
1716             -1,
1717             NULL,
1718             register_item_cb,
1719             sn);
1720     g_object_unref (priv->dbus_proxy);
1721     priv->dbus_proxy = NULL;
1722 }
1723 
1724 static void
name_lost(GDBusConnection * conn,const gchar * name,gpointer data)1725 name_lost (GDBusConnection *conn, const gchar *name, gpointer data)
1726 {
1727     (void)name;
1728     GError *err = NULL;
1729     StatusNotifier *sn = (StatusNotifier *) data;
1730 
1731     if (!conn)
1732         g_set_error (&err, STATUS_NOTIFIER_ERROR,
1733                 STATUS_NOTIFIER_ERROR_NO_CONNECTION,
1734                 "Failed to establish DBus connection");
1735     else
1736         g_set_error (&err, STATUS_NOTIFIER_ERROR,
1737                 STATUS_NOTIFIER_ERROR_NO_NAME,
1738                 "Failed to acquire name for item");
1739     dbus_failed (sn, err, TRUE);
1740 }
1741 
1742 static void
dbus_reg_item(StatusNotifier * sn)1743 dbus_reg_item (StatusNotifier *sn)
1744 {
1745     StatusNotifierPrivate *priv = sn->priv;
1746     gchar buf[64], *b = buf;
1747 
1748     if (G_UNLIKELY (g_snprintf (buf, 64, "org.kde.StatusNotifierItem-%u-%u",
1749                     getpid (), ++uniq_id) >= 64))
1750         b = g_strdup_printf ("org.kde.StatusNotifierItem-%u-%u",
1751             getpid (), uniq_id);
1752     priv->dbus_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
1753             b,
1754             G_BUS_NAME_OWNER_FLAGS_NONE,
1755             bus_acquired,
1756             name_acquired,
1757             name_lost,
1758             sn, NULL);
1759     if (G_UNLIKELY (b != buf))
1760         g_free (b);
1761 }
1762 
1763 static void
watcher_signal(GDBusProxy * proxy,const gchar * sender,const gchar * signal,GVariant * params,StatusNotifier * sn)1764 watcher_signal (GDBusProxy      *proxy,
1765                 const gchar     *sender,
1766                 const gchar     *signal,
1767                 GVariant        *params,
1768                 StatusNotifier  *sn)
1769 {
1770     (void)proxy;
1771     (void)sender;
1772     (void)params;
1773     StatusNotifierPrivate *priv = sn->priv;
1774 
1775     if (!g_strcmp0 (signal, "StatusNotifierHostRegistered"))
1776     {
1777         g_signal_handler_disconnect (priv->dbus_proxy, priv->dbus_sid);
1778         priv->dbus_sid = 0;
1779 
1780         dbus_reg_item (sn);
1781     }
1782 }
1783 
1784 static void
proxy_cb(GObject * sce,GAsyncResult * result,gpointer data)1785 proxy_cb (GObject *sce, GAsyncResult *result, gpointer data)
1786 {
1787     (void)sce;
1788     GError *err = NULL;
1789     StatusNotifier *sn = (StatusNotifier *) data;
1790     StatusNotifierPrivate *priv = sn->priv;
1791     GVariant *variant;
1792 
1793     priv->dbus_proxy = g_dbus_proxy_new_for_bus_finish (result, &err);
1794     if (!priv->dbus_proxy)
1795     {
1796         dbus_failed (sn, err, TRUE);
1797         return;
1798     }
1799 
1800     variant = g_dbus_proxy_get_cached_property (priv->dbus_proxy,
1801             "IsStatusNotifierHostRegistered");
1802     if (!variant || !g_variant_get_boolean (variant))
1803     {
1804         GDBusProxy *proxy;
1805 
1806         g_set_error (&err, STATUS_NOTIFIER_ERROR,
1807                 STATUS_NOTIFIER_ERROR_NO_HOST,
1808                 "No Host registered on the Watcher");
1809         if (variant)
1810             g_variant_unref (variant);
1811 
1812         /* keep the proxy, we'll wait for the signal when a host registers */
1813         proxy = priv->dbus_proxy;
1814         /* (so dbus_free() from dbus_failed() doesn't unref) */
1815         priv->dbus_proxy = NULL;
1816         dbus_failed (sn, err, FALSE);
1817         priv->dbus_proxy = proxy;
1818 
1819         priv->dbus_sid = g_signal_connect (priv->dbus_proxy, "g-signal",
1820                 (GCallback) watcher_signal, sn);
1821         return;
1822     }
1823     g_variant_unref (variant);
1824 
1825     dbus_reg_item (sn);
1826 }
1827 
1828 static void
watcher_appeared(GDBusConnection * conn,const gchar * name,const gchar * owner,gpointer data)1829 watcher_appeared (GDBusConnection   *conn,
1830                   const gchar       *name,
1831                   const gchar       *owner,
1832                   gpointer           data)
1833 {
1834     (void)conn;
1835     (void)name;
1836     (void)owner;
1837     StatusNotifier *sn = data;
1838     StatusNotifierPrivate *priv = sn->priv;
1839     GDBusNodeInfo *info;
1840 
1841     g_bus_unwatch_name (priv->dbus_watch_id);
1842     priv->dbus_watch_id = 0;
1843 
1844     info = g_dbus_node_info_new_for_xml (watcher_xml, NULL);
1845     g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
1846             G_DBUS_PROXY_FLAGS_NONE,
1847             info->interfaces[0],
1848             WATCHER_NAME,
1849             WATCHER_OBJECT,
1850             WATCHER_INTERFACE,
1851             NULL,
1852             proxy_cb,
1853             sn);
1854     g_dbus_node_info_unref (info);
1855 }
1856 
1857 static void
watcher_vanished(GDBusConnection * conn,const gchar * name,gpointer data)1858 watcher_vanished (GDBusConnection   *conn,
1859                   const gchar       *name,
1860                   gpointer           data)
1861 {
1862     (void)conn;
1863     (void)name;
1864     GError *err = NULL;
1865     StatusNotifier *sn = data;
1866     StatusNotifierPrivate *priv = sn->priv;
1867     guint id;
1868 
1869     /* keep the watch active, so if a watcher shows up we'll resume the
1870      * registering automatically */
1871     id = priv->dbus_watch_id;
1872     /* (so dbus_free() from dbus_failed() doesn't unwatch) */
1873     priv->dbus_watch_id = 0;
1874 
1875     g_set_error (&err, STATUS_NOTIFIER_ERROR,
1876             STATUS_NOTIFIER_ERROR_NO_WATCHER,
1877             "No Watcher found");
1878     dbus_failed (sn, err, FALSE);
1879 
1880     priv->dbus_watch_id = id;
1881 }
1882 
1883 /**
1884  * status_notifier_register:
1885  * @sn: A #StatusNotifier
1886  *
1887  * Registers @sn to the StatusNotifierWatcher over DBus.
1888  *
1889  * Once you have created your #StatusNotifier you need to register it, so any
1890  * host/visualization can use it and update their GUI as needed.
1891  *
1892  * This function will connect to the StatusNotifierWatcher and make sure at
1893  * least one StatusNotifierHost is registered. Then, it will register a new
1894  * StatusNotifierItem on the session bus and register it with the watcher.
1895  *
1896  * When done, property #StatusNotifier:state will change to
1897  * %STATUS_NOTIFIER_STATE_REGISTERED. If something fails, signal
1898  * #StatusNotifier::registration-failed will be emitted, at which point you
1899  * should fallback to using the systray.
1900  *
1901  * However there are two possible types of failures: fatal and non-fatal ones.
1902  * Fatal error means that #StatusNotifier:state will be
1903  * %STATUS_NOTIFIER_STATE_FAILED and you can unref @sn.
1904  *
1905  * Non-fatal error means it will still be %STATUS_NOTIFIER_STATE_REGISTERING as
1906  * the registration process could still eventually succeed. For example, if
1907  * there was no host registered on the watcher, as soon as signal
1908  * StatusNotifierHostRegistered is emitted on the watcher, the registration
1909  * process for @sn will complete and #StatusNotifier:state set to
1910  * %STATUS_NOTIFIER_STATE_REGISTERED, at which point you should stop using the
1911  * systray.
1912  *
1913  * This also means it is possible to have multiple signals
1914  * #StatusNotifier::registration-failed emitted on the same #StatusNotifier.
1915  *
1916  * Note that you can call status_notifier_register() after a fatal error
1917  * occured, to try again. You can also unref @sn while it is
1918  * %STATUS_NOTIFIER_STATE_REGISTERING safely.
1919  */
1920 void
status_notifier_register(StatusNotifier * sn)1921 status_notifier_register (StatusNotifier          *sn)
1922 
1923 {
1924     StatusNotifierPrivate *priv;
1925 
1926     g_return_if_fail (IS_STATUS_NOTIFIER (sn));
1927     priv = sn->priv;
1928 
1929     if (priv->state == STATUS_NOTIFIER_STATE_REGISTERING
1930             || priv->state == STATUS_NOTIFIER_STATE_REGISTERED)
1931         return;
1932     priv->state = STATUS_NOTIFIER_STATE_REGISTERING;
1933 
1934     priv->dbus_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
1935             WATCHER_NAME,
1936             G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
1937             watcher_appeared,
1938             watcher_vanished,
1939             sn, NULL);
1940 }
1941 
1942 /**
1943  * status_notifier_get_state:
1944  * @sn: A #StatusNotifier
1945  *
1946  * Returns the DBus registration state of @sn. See status_notifier_register()
1947  * for more.
1948  *
1949  * Returns: The DBus registration state of @sn
1950  */
1951 StatusNotifierState
status_notifier_get_state(StatusNotifier * sn)1952 status_notifier_get_state (StatusNotifier          *sn)
1953 {
1954     g_return_val_if_fail (IS_STATUS_NOTIFIER (sn), FALSE);
1955     return sn->priv->state;
1956 }
1957