1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2006 Christian Hammond
4  * Copyright (C) 2006 John Palmieri
5  * Copyright (C) 2010 Red Hat, Inc.
6  * Copyright © 2010 Christian Persch
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA  02111-1307, USA.
22  */
23 
24 #include "config.h"
25 
26 #include <gio/gio.h>
27 
28 #include "notify.h"
29 #include "internal.h"
30 
31 
32 /**
33  * SECTION:notification
34  * @Short_description: A passive pop-up notification.
35  * @Title: NotifyNotification
36  *
37  * #NotifyNotification represents a passive pop-up notification. It can
38  * contain summary text, body text, and an icon, as well as hints specifying
39  * how the notification should be presented. The notification is rendered
40  * by a notification daemon, and may present the notification in any number
41  * of ways. As such, there is a clear separation of content and presentation,
42  * and this API enforces that.
43  */
44 
45 
46 #if !defined(G_PARAM_STATIC_NAME) && !defined(G_PARAM_STATIC_NICK) && \
47     !defined(G_PARAM_STATIC_BLURB)
48 # define G_PARAM_STATIC_NAME 0
49 # define G_PARAM_STATIC_NICK 0
50 # define G_PARAM_STATIC_BLURB 0
51 #endif
52 
53 static void     notify_notification_class_init (NotifyNotificationClass *klass);
54 static void     notify_notification_init       (NotifyNotification *sp);
55 static void     notify_notification_finalize   (GObject            *object);
56 
57 typedef struct
58 {
59         NotifyActionCallback cb;
60         GFreeFunc            free_func;
61         gpointer             user_data;
62 
63 } CallbackPair;
64 
65 struct _NotifyNotificationPrivate
66 {
67         guint32         id;
68         char           *app_name;
69         char           *summary;
70         char           *body;
71 
72         /* NULL to use icon data. Anything else to have server lookup icon */
73         char           *icon_name;
74 
75         /*
76          * -1   = use server default
77          *  0   = never timeout
78          *  > 0 = Number of milliseconds before we timeout
79          */
80         gint            timeout;
81 
82         GSList         *actions;
83         GHashTable     *action_map;
84         GHashTable     *hints;
85 
86         gboolean        has_nondefault_actions;
87         gboolean        updates_pending;
88 
89         gulong          proxy_signal_handler;
90 
91         gint            closed_reason;
92 };
93 
94 enum
95 {
96         SIGNAL_CLOSED,
97         LAST_SIGNAL
98 };
99 
100 enum
101 {
102         PROP_0,
103         PROP_ID,
104         PROP_APP_NAME,
105         PROP_SUMMARY,
106         PROP_BODY,
107         PROP_ICON_NAME,
108         PROP_CLOSED_REASON
109 };
110 
111 static void     notify_notification_set_property (GObject      *object,
112                                                   guint         prop_id,
113                                                   const GValue *value,
114                                                   GParamSpec   *pspec);
115 static void     notify_notification_get_property (GObject      *object,
116                                                   guint         prop_id,
117                                                   GValue       *value,
118                                                   GParamSpec   *pspec);
119 static guint    signals[LAST_SIGNAL] = { 0 };
120 
121 static GObjectClass *parent_class = NULL;
122 
G_DEFINE_TYPE(NotifyNotification,notify_notification,G_TYPE_OBJECT)123 G_DEFINE_TYPE (NotifyNotification, notify_notification, G_TYPE_OBJECT)
124 
125 static GObject *
126 notify_notification_constructor (GType                  type,
127                                  guint                  n_construct_properties,
128                                  GObjectConstructParam *construct_params)
129 {
130         GObject *object;
131 
132         object = parent_class->constructor (type,
133                                             n_construct_properties,
134                                             construct_params);
135 
136         _notify_cache_add_notification (NOTIFY_NOTIFICATION (object));
137 
138         return object;
139 }
140 
141 static void
notify_notification_class_init(NotifyNotificationClass * klass)142 notify_notification_class_init (NotifyNotificationClass *klass)
143 {
144         GObjectClass *object_class = G_OBJECT_CLASS (klass);
145 
146         parent_class = g_type_class_peek_parent (klass);
147 
148         object_class->constructor = notify_notification_constructor;
149         object_class->get_property = notify_notification_get_property;
150         object_class->set_property = notify_notification_set_property;
151         object_class->finalize = notify_notification_finalize;
152 
153         /**
154 	 * NotifyNotification::closed:
155 	 * @notification: The object which received the signal.
156 	 *
157 	 * Emitted when the notification is closed.
158 	 */
159         signals[SIGNAL_CLOSED] =
160                 g_signal_new ("closed",
161                               G_TYPE_FROM_CLASS (object_class),
162                               G_SIGNAL_RUN_FIRST,
163                               G_STRUCT_OFFSET (NotifyNotificationClass, closed),
164                               NULL,
165                               NULL,
166                               g_cclosure_marshal_VOID__VOID,
167                               G_TYPE_NONE,
168                               0);
169 
170         g_object_class_install_property (object_class,
171                                          PROP_ID,
172                                          g_param_spec_int ("id", "ID",
173                                                            "The notification ID",
174                                                            0,
175                                                            G_MAXINT32,
176                                                            0,
177                                                            G_PARAM_READWRITE
178                                                            | G_PARAM_CONSTRUCT
179                                                            | G_PARAM_STATIC_NAME
180                                                            | G_PARAM_STATIC_NICK
181                                                            | G_PARAM_STATIC_BLURB));
182 
183         g_object_class_install_property (object_class,
184                                          PROP_APP_NAME,
185                                          g_param_spec_string ("app-name",
186                                                               "Application name",
187                                                               "The application name to use for this notification",
188                                                               NULL,
189                                                               G_PARAM_READWRITE
190                                                               | G_PARAM_STATIC_NAME
191                                                               | G_PARAM_STATIC_NICK
192                                                               | G_PARAM_STATIC_BLURB));
193 
194         g_object_class_install_property (object_class,
195                                          PROP_SUMMARY,
196                                          g_param_spec_string ("summary",
197                                                               "Summary",
198                                                               "The summary text",
199                                                               NULL,
200                                                               G_PARAM_READWRITE
201                                                               | G_PARAM_CONSTRUCT
202                                                               | G_PARAM_STATIC_NAME
203                                                               | G_PARAM_STATIC_NICK
204                                                               | G_PARAM_STATIC_BLURB));
205 
206         g_object_class_install_property (object_class,
207                                          PROP_BODY,
208                                          g_param_spec_string ("body",
209                                                               "Message Body",
210                                                               "The message body text",
211                                                               NULL,
212                                                               G_PARAM_READWRITE
213                                                               | G_PARAM_CONSTRUCT
214                                                               | G_PARAM_STATIC_NAME
215                                                               | G_PARAM_STATIC_NICK
216                                                               | G_PARAM_STATIC_BLURB));
217 
218         g_object_class_install_property (object_class,
219                                          PROP_ICON_NAME,
220                                          g_param_spec_string ("icon-name",
221                                                               "Icon Name",
222                                                               "The icon filename or icon theme-compliant name",
223                                                               NULL,
224                                                               G_PARAM_READWRITE
225                                                               | G_PARAM_CONSTRUCT
226                                                               | G_PARAM_STATIC_NAME
227                                                               | G_PARAM_STATIC_NICK
228                                                               | G_PARAM_STATIC_BLURB));
229 
230         g_object_class_install_property (object_class,
231                                          PROP_CLOSED_REASON,
232                                          g_param_spec_int ("closed-reason",
233                                                            "Closed Reason",
234                                                            "The reason code for why the notification was closed",
235                                                            -1,
236                                                            G_MAXINT32,
237                                                            -1,
238                                                            G_PARAM_READABLE
239                                                            | G_PARAM_STATIC_NAME
240                                                            | G_PARAM_STATIC_NICK
241                                                            | G_PARAM_STATIC_BLURB));
242 }
243 
244 static void
245 notify_notification_update_internal (NotifyNotification *notification,
246                                      const char         *app_name,
247                                      const char         *summary,
248                                      const char         *body,
249                                      const char         *icon);
250 
251 static void
notify_notification_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)252 notify_notification_set_property (GObject      *object,
253                                   guint         prop_id,
254                                   const GValue *value,
255                                   GParamSpec   *pspec)
256 {
257         NotifyNotification        *notification = NOTIFY_NOTIFICATION (object);
258         NotifyNotificationPrivate *priv = notification->priv;
259 
260         switch (prop_id) {
261         case PROP_ID:
262                 priv->id = g_value_get_int (value);
263                 break;
264 
265         case PROP_APP_NAME:
266                 notify_notification_update_internal (notification,
267                                                      g_value_get_string (value),
268                                                      priv->summary,
269                                                      priv->body,
270                                                      priv->icon_name);
271                 break;
272 
273         case PROP_SUMMARY:
274                 notify_notification_update_internal (notification,
275                                                      priv->app_name,
276                                                      g_value_get_string (value),
277                                                      priv->body,
278                                                      priv->icon_name);
279                 break;
280 
281         case PROP_BODY:
282                 notify_notification_update_internal (notification,
283                                                      priv->app_name,
284                                                      priv->summary,
285                                                      g_value_get_string (value),
286                                                      priv->icon_name);
287                 break;
288 
289         case PROP_ICON_NAME:
290                 notify_notification_update_internal (notification,
291                                                      priv->app_name,
292                                                      priv->summary,
293                                                      priv->body,
294                                                      g_value_get_string (value));
295                 break;
296 
297         default:
298                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
299                 break;
300         }
301 }
302 
303 static void
notify_notification_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)304 notify_notification_get_property (GObject    *object,
305                                   guint       prop_id,
306                                   GValue     *value,
307                                   GParamSpec *pspec)
308 {
309         NotifyNotification        *notification = NOTIFY_NOTIFICATION (object);
310         NotifyNotificationPrivate *priv = notification->priv;
311 
312         switch (prop_id) {
313         case PROP_ID:
314                 g_value_set_int (value, priv->id);
315                 break;
316 
317         case PROP_SUMMARY:
318                 g_value_set_string (value, priv->summary);
319                 break;
320 
321         case PROP_APP_NAME:
322                 g_value_set_string (value, priv->app_name);
323                 break;
324 
325         case PROP_BODY:
326                 g_value_set_string (value, priv->body);
327                 break;
328 
329         case PROP_ICON_NAME:
330                 g_value_set_string (value, priv->icon_name);
331                 break;
332 
333         case PROP_CLOSED_REASON:
334                 g_value_set_int (value, priv->closed_reason);
335                 break;
336 
337         default:
338                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
339                 break;
340         }
341 }
342 
343 static void
destroy_pair(CallbackPair * pair)344 destroy_pair (CallbackPair *pair)
345 {
346         if (pair->user_data != NULL && pair->free_func != NULL) {
347                 pair->free_func (pair->user_data);
348         }
349 
350         g_free (pair);
351 }
352 
353 static void
notify_notification_init(NotifyNotification * obj)354 notify_notification_init (NotifyNotification *obj)
355 {
356         obj->priv = g_new0 (NotifyNotificationPrivate, 1);
357         obj->priv->timeout = NOTIFY_EXPIRES_DEFAULT;
358         obj->priv->closed_reason = -1;
359         obj->priv->hints = g_hash_table_new_full (g_str_hash,
360                                                   g_str_equal,
361                                                   g_free,
362                                                   (GDestroyNotify) g_variant_unref);
363 
364         obj->priv->action_map = g_hash_table_new_full (g_str_hash,
365                                                        g_str_equal,
366                                                        g_free,
367                                                        (GDestroyNotify) destroy_pair);
368 }
369 
370 static void
notify_notification_finalize(GObject * object)371 notify_notification_finalize (GObject *object)
372 {
373         NotifyNotification        *obj = NOTIFY_NOTIFICATION (object);
374         NotifyNotificationPrivate *priv = obj->priv;
375         GDBusProxy                *proxy;
376 
377         _notify_cache_remove_notification (obj);
378 
379         g_free (priv->app_name);
380         g_free (priv->summary);
381         g_free (priv->body);
382         g_free (priv->icon_name);
383 
384         if (priv->actions != NULL) {
385                 g_slist_foreach (priv->actions, (GFunc) g_free, NULL);
386                 g_slist_free (priv->actions);
387         }
388 
389         if (priv->action_map != NULL)
390                 g_hash_table_destroy (priv->action_map);
391 
392         if (priv->hints != NULL)
393                 g_hash_table_destroy (priv->hints);
394 
395         proxy = _notify_get_proxy (NULL);
396         if (proxy != NULL && priv->proxy_signal_handler != 0) {
397                 g_signal_handler_disconnect (proxy, priv->proxy_signal_handler);
398         }
399 
400         g_free (obj->priv);
401 
402         G_OBJECT_CLASS (parent_class)->finalize (object);
403 }
404 
405 /**
406  * notify_notification_new:
407  * @summary: The required summary text.
408  * @body: (allow-none): The optional body text.
409  * @icon: (allow-none): The optional icon theme icon name or filename.
410  *
411  * Creates a new #NotifyNotification. The summary text is required, but
412  * all other parameters are optional.
413  *
414  * Returns: The new #NotifyNotification.
415  */
416 NotifyNotification *
notify_notification_new(const char * summary,const char * body,const char * icon)417 notify_notification_new (const char *summary,
418                          const char *body,
419                          const char *icon)
420 {
421         return g_object_new (NOTIFY_TYPE_NOTIFICATION,
422                              "summary", summary,
423                              "body", body,
424                              "icon-name", icon,
425                              NULL);
426 }
427 
428 static gchar *
try_prepend_path(const char * base_path,const char * path)429 try_prepend_path (const char *base_path,
430                   const char *path)
431 {
432         gchar *path_filename;
433         gchar *path_ret;
434 
435         if (!path || *path == '\0')
436                 return NULL;
437 
438         path_ret = NULL;
439         path_filename = g_filename_from_uri (base_path, NULL, NULL);
440 
441         if (path_filename == NULL) {
442                 if (base_path && base_path[0] == G_DIR_SEPARATOR) {
443                         path_filename = g_strdup (base_path);
444                 } else {
445                         path_filename = realpath (base_path, NULL);
446                 }
447         }
448 
449         g_debug ("Trying to look at file '%s' in the '%s' prefix.",
450                  base_path,
451                  path);
452 
453         path_ret = g_build_filename (path, path_filename, NULL);
454 
455         if (!g_file_test (path_ret, G_FILE_TEST_EXISTS)) {
456                 g_free (path_ret);
457                 path_ret = NULL;
458         }
459 
460         g_free (path_filename);
461 
462         return path_ret;
463 }
464 
465 static gchar *
try_prepend_desktop(const gchar * desktop)466 try_prepend_desktop (const gchar *desktop)
467 {
468         gchar *ret;
469 
470         /*
471          * if it's an absolute path, try prepending $SNAP, otherwise try
472          * $SNAP_NAME_; snap .desktop files are in the format
473          * ${SNAP_NAME}_desktop_file_name
474          */
475         ret = try_prepend_path (desktop, g_getenv ("SNAP"));
476 
477         if (ret == NULL) {
478                 const gchar *snap_name = g_getenv ("SNAP_NAME");
479 
480                 if (snap_name != NULL && snap_name[0] != '\0') {
481                         ret = g_strdup_printf ("%s_%s", snap_name, desktop);
482                 }
483         }
484 
485         return ret;
486 }
487 
488 static gchar *
try_prepend_snap(const gchar * value)489 try_prepend_snap (const gchar *value)
490 {
491         /* hardcoded paths to icons might be relocated under $SNAP */
492         return try_prepend_path (value, g_getenv ("SNAP"));
493 }
494 
495 
496 static void
notify_notification_update_internal(NotifyNotification * notification,const char * app_name,const char * summary,const char * body,const char * icon)497 notify_notification_update_internal (NotifyNotification *notification,
498                                      const char         *app_name,
499                                      const char         *summary,
500                                      const char         *body,
501                                      const char         *icon)
502 {
503         if (notification->priv->app_name != app_name) {
504                 g_free (notification->priv->app_name);
505                 notification->priv->app_name = g_strdup (app_name);
506                 g_object_notify (G_OBJECT (notification), "app-name");
507         }
508 
509         if (notification->priv->summary != summary) {
510                 g_free (notification->priv->summary);
511                 notification->priv->summary = g_strdup (summary);
512                 g_object_notify (G_OBJECT (notification), "summary");
513         }
514 
515         if (notification->priv->body != body) {
516                 g_free (notification->priv->body);
517                 notification->priv->body = (body != NULL
518                                             && *body != '\0' ? g_strdup (body) : NULL);
519                 g_object_notify (G_OBJECT (notification), "body");
520         }
521 
522         if (notification->priv->icon_name != icon) {
523                 gchar *snapped_icon;
524                 g_free (notification->priv->icon_name);
525                 notification->priv->icon_name = (icon != NULL
526                                                  && *icon != '\0' ? g_strdup (icon) : NULL);
527                 snapped_icon = try_prepend_desktop (notification->priv->icon_name);
528                 if (snapped_icon != NULL) {
529                         g_debug ("Icon updated in snap environment: '%s' -> '%s'\n",
530                                  notification->priv->icon_name, snapped_icon);
531                         g_free (notification->priv->icon_name);
532                         notification->priv->icon_name = snapped_icon;
533                 }
534                 g_object_notify (G_OBJECT (notification), "icon-name");
535         }
536 
537         notification->priv->updates_pending = TRUE;
538 }
539 
540 /**
541  * notify_notification_update:
542  * @notification: The notification to update.
543  * @summary: The new required summary text.
544  * @body: (allow-none): The optional body text.
545  * @icon: (allow-none): The optional icon theme icon name or filename.
546  *
547  * Updates the notification text and icon. This won't send the update out
548  * and display it on the screen. For that, you will need to call
549  * notify_notification_show().
550  *
551  * Returns: %TRUE, unless an invalid parameter was passed.
552  */
553 gboolean
notify_notification_update(NotifyNotification * notification,const char * summary,const char * body,const char * icon)554 notify_notification_update (NotifyNotification *notification,
555                             const char         *summary,
556                             const char         *body,
557                             const char         *icon)
558 {
559         g_return_val_if_fail (notification != NULL, FALSE);
560         g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE);
561         g_return_val_if_fail (summary != NULL && *summary != '\0', FALSE);
562 
563         notify_notification_update_internal (notification,
564                                              notification->priv->app_name,
565                                              summary, body, icon);
566 
567         return TRUE;
568 }
569 
570 static void
proxy_g_signal_cb(GDBusProxy * proxy,const char * sender_name,const char * signal_name,GVariant * parameters,NotifyNotification * notification)571 proxy_g_signal_cb (GDBusProxy *proxy,
572                    const char *sender_name,
573                    const char *signal_name,
574                    GVariant   *parameters,
575                    NotifyNotification *notification)
576 {
577         g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
578 
579         if (g_strcmp0 (signal_name, "NotificationClosed") == 0 &&
580             g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) {
581                 guint32 id, reason;
582 
583                 g_variant_get (parameters, "(uu)", &id, &reason);
584                 if (id != notification->priv->id)
585                         return;
586 
587                 g_object_ref (G_OBJECT (notification));
588                 notification->priv->closed_reason = reason;
589                 g_signal_emit (notification, signals[SIGNAL_CLOSED], 0);
590                 notification->priv->id = 0;
591                 g_object_unref (G_OBJECT (notification));
592         } else if (g_strcmp0 (signal_name, "ActionInvoked") == 0 &&
593                    g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) {
594                 guint32 id;
595                 const char *action;
596                 CallbackPair *pair;
597 
598                 g_variant_get (parameters, "(u&s)", &id, &action);
599 
600                 if (id != notification->priv->id)
601                         return;
602 
603                 pair = (CallbackPair *) g_hash_table_lookup (notification->priv->action_map,
604                                                             action);
605 
606                 if (pair == NULL) {
607                         if (g_ascii_strcasecmp (action, "default")) {
608                                 g_warning ("Received unknown action %s", action);
609                         }
610                 } else {
611                         pair->cb (notification, (char *) action, pair->user_data);
612                 }
613         }
614 }
615 
616 /**
617  * notify_notification_show:
618  * @notification: The notification.
619  * @error: The returned error information.
620  *
621  * Tells the notification server to display the notification on the screen.
622  *
623  * Returns: %TRUE if successful. On error, this will return %FALSE and set
624  *          @error.
625  */
626 gboolean
notify_notification_show(NotifyNotification * notification,GError ** error)627 notify_notification_show (NotifyNotification *notification,
628                           GError            **error)
629 {
630         NotifyNotificationPrivate *priv;
631         GDBusProxy                *proxy;
632         GVariantBuilder            actions_builder, hints_builder;
633         GSList                    *l;
634         GHashTableIter             iter;
635         gpointer                   key, data;
636         GVariant                  *result;
637 
638         g_return_val_if_fail (notification != NULL, FALSE);
639         g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE);
640         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
641 
642         if (!notify_is_initted ()) {
643                 g_warning ("you must call notify_init() before showing");
644                 g_assert_not_reached ();
645         }
646 
647         priv = notification->priv;
648         proxy = _notify_get_proxy (error);
649         if (proxy == NULL) {
650                 return FALSE;
651         }
652 
653         if (priv->proxy_signal_handler == 0) {
654                 priv->proxy_signal_handler = g_signal_connect (proxy,
655                                                                "g-signal",
656                                                                G_CALLBACK (proxy_g_signal_cb),
657                                                                notification);
658         }
659 
660         g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("as"));
661         for (l = priv->actions; l != NULL; l = l->next) {
662                 g_variant_builder_add (&actions_builder, "s", l->data);
663         }
664 
665         g_variant_builder_init (&hints_builder, G_VARIANT_TYPE ("a{sv}"));
666         g_hash_table_iter_init (&iter, priv->hints);
667         while (g_hash_table_iter_next (&iter, &key, &data)) {
668                 g_variant_builder_add (&hints_builder, "{sv}", key, data);
669         }
670 
671         /* TODO: make this nonblocking */
672         result = g_dbus_proxy_call_sync (proxy,
673                                          "Notify",
674                                          g_variant_new ("(susssasa{sv}i)",
675                                                         priv->app_name ? priv->app_name : notify_get_app_name (),
676                                                         priv->id,
677                                                         priv->icon_name ? priv->icon_name : "",
678                                                         priv->summary ? priv->summary : "",
679                                                         priv->body ? priv->body : "",
680                                                         &actions_builder,
681                                                         &hints_builder,
682                                                         priv->timeout),
683                                          G_DBUS_CALL_FLAGS_NONE,
684                                          -1 /* FIXME ? */,
685                                          NULL,
686                                          error);
687         if (result == NULL) {
688                 return FALSE;
689         }
690         if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(u)"))) {
691                 g_variant_unref (result);
692                 g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
693                              "Unexpected reply type");
694                 return FALSE;
695         }
696 
697         g_variant_get (result, "(u)", &priv->id);
698         g_variant_unref (result);
699 
700         return TRUE;
701 }
702 
703 /**
704  * notify_notification_set_timeout:
705  * @notification: The notification.
706  * @timeout: The timeout in milliseconds.
707  *
708  * Sets the timeout of the notification. To set the default time, pass
709  * %NOTIFY_EXPIRES_DEFAULT as @timeout. To set the notification to never
710  * expire, pass %NOTIFY_EXPIRES_NEVER.
711  *
712  * Note that the timeout may be ignored by the server.
713  */
714 void
notify_notification_set_timeout(NotifyNotification * notification,gint timeout)715 notify_notification_set_timeout (NotifyNotification *notification,
716                                  gint                timeout)
717 {
718         g_return_if_fail (notification != NULL);
719         g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
720 
721         notification->priv->timeout = timeout;
722 }
723 
724 gint
_notify_notification_get_timeout(const NotifyNotification * notification)725 _notify_notification_get_timeout (const NotifyNotification *notification)
726 {
727         g_return_val_if_fail (notification != NULL, -1);
728         g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), -1);
729 
730         return notification->priv->timeout;
731 }
732 
733 /**
734  * notify_notification_set_category:
735  * @notification: The notification.
736  * @category: The category.
737  *
738  * Sets the category of this notification. This can be used by the
739  * notification server to filter or display the data in a certain way.
740  */
741 void
notify_notification_set_category(NotifyNotification * notification,const char * category)742 notify_notification_set_category (NotifyNotification *notification,
743                                   const char         *category)
744 {
745         g_return_if_fail (notification != NULL);
746         g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
747 
748         if (category != NULL && category[0] != '\0') {
749                 notify_notification_set_hint_string (notification,
750                                                      "category",
751                                                      category);
752         }
753 }
754 
755 /**
756  * notify_notification_set_urgency:
757  * @notification: The notification.
758  * @urgency: The urgency level.
759  *
760  * Sets the urgency level of this notification.
761  *
762  * See: #NotifyUrgency
763  */
764 void
notify_notification_set_urgency(NotifyNotification * notification,NotifyUrgency urgency)765 notify_notification_set_urgency (NotifyNotification *notification,
766                                  NotifyUrgency       urgency)
767 {
768         g_return_if_fail (notification != NULL);
769         g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
770 
771         notify_notification_set_hint_byte (notification,
772                                            "urgency",
773                                            (guchar) urgency);
774 }
775 
776 /**
777  * notify_notification_set_icon_from_pixbuf:
778  * @notification: The notification.
779  * @icon: The icon.
780  *
781  * Sets the icon in the notification from a #GdkPixbuf.
782  * Deprecated: use notify_notification_set_image_from_pixbuf() instead.
783  *
784  */
785 void
notify_notification_set_icon_from_pixbuf(NotifyNotification * notification,GdkPixbuf * icon)786 notify_notification_set_icon_from_pixbuf (NotifyNotification *notification,
787                                           GdkPixbuf          *icon)
788 {
789         notify_notification_set_image_from_pixbuf (notification, icon);
790 }
791 
792 /**
793  * notify_notification_set_image_from_pixbuf:
794  * @notification: The notification.
795  * @pixbuf: The image.
796  *
797  * Sets the image in the notification from a #GdkPixbuf.
798  *
799  */
800 void
notify_notification_set_image_from_pixbuf(NotifyNotification * notification,GdkPixbuf * pixbuf)801 notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
802                                            GdkPixbuf          *pixbuf)
803 {
804         gint            width;
805         gint            height;
806         gint            rowstride;
807         gint            bits_per_sample;
808         gint            n_channels;
809         guchar         *image;
810         gboolean        has_alpha;
811         gsize           image_len;
812         GVariant       *value;
813         const char     *hint_name;
814 
815         g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf));
816 
817         if (_notify_check_spec_version(1, 2)) {
818                 hint_name = "image-data";
819         } else if (_notify_check_spec_version(1, 1)) {
820                 hint_name = "image_data";
821         } else {
822                 hint_name = "icon_data";
823         }
824 
825         if (pixbuf == NULL) {
826                 notify_notification_set_hint (notification, hint_name, NULL);
827                 return;
828         }
829 
830         g_object_get (pixbuf,
831                       "width", &width,
832                       "height", &height,
833                       "rowstride", &rowstride,
834                       "n-channels", &n_channels,
835                       "bits-per-sample", &bits_per_sample,
836                       "pixels", &image,
837                       "has-alpha", &has_alpha,
838                       NULL);
839         image_len = (height - 1) * rowstride + width *
840                 ((n_channels * bits_per_sample + 7) / 8);
841 
842         value = g_variant_new ("(iiibii@ay)",
843                                width,
844                                height,
845                                rowstride,
846                                has_alpha,
847                                bits_per_sample,
848                                n_channels,
849                                g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
850                                                         image,
851                                                         image_len,
852                                                         TRUE,
853                                                         (GDestroyNotify) g_object_unref,
854                                                         g_object_ref (pixbuf)));
855         notify_notification_set_hint (notification, hint_name, value);
856 }
857 
858 static GVariant *
get_parsed_variant(GVariant * variant,gchar * (* str_parser)(const gchar *))859 get_parsed_variant (GVariant *variant,
860                     gchar    *(*str_parser)(const gchar *))
861 {
862         gchar *parsed = str_parser (g_variant_get_string (variant, NULL));
863 
864         if (parsed != NULL) {
865                 g_variant_unref (variant);
866                 variant = g_variant_new_take_string (parsed);
867         }
868 
869         return variant;
870 }
871 
872 static GVariant *
maybe_parse_snap_hint_value(const gchar * key,GVariant * value)873 maybe_parse_snap_hint_value (const gchar *key,
874                              GVariant    *value)
875 {
876         if (g_strcmp0 (key, "desktop-entry") == 0) {
877                 value = get_parsed_variant (value, try_prepend_desktop);
878         } else if (g_strcmp0 (key, "image-path") == 0 ||
879                    g_strcmp0 (key, "image_path") == 0 ||
880                    g_strcmp0 (key, "sound-file") == 0) {
881                 value = get_parsed_variant (value, try_prepend_snap);
882         }
883 
884         return value;
885 }
886 
887 /**
888  * notify_notification_set_hint:
889  * @notification: a #NotifyNotification
890  * @key: the hint key
891  * @value: (allow-none): the hint value, or %NULL to unset the hint
892  *
893  * Sets a hint for @key with value @value. If @value is %NULL,
894  * a previously set hint for @key is unset.
895  *
896  * If @value is floating, it is consumed.
897  *
898  * Since: 0.6
899  */
900 void
notify_notification_set_hint(NotifyNotification * notification,const char * key,GVariant * value)901 notify_notification_set_hint (NotifyNotification *notification,
902                               const char         *key,
903                               GVariant           *value)
904 {
905         g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
906         g_return_if_fail (key != NULL && *key != '\0');
907 
908         if (value != NULL) {
909                 value = maybe_parse_snap_hint_value (key, value);
910                 g_hash_table_insert (notification->priv->hints,
911                                     g_strdup (key),
912                                     g_variant_ref_sink (value));
913         } else {
914                 g_hash_table_remove (notification->priv->hints, key);
915         }
916 }
917 
918 /**
919  * notify_notification_set_app_name:
920  * @notification: a #NotifyNotification
921  * @app_name: the localised application name
922  *
923  * Sets the application name for the notification. If this function is
924  * not called or if @app_name is %NULL, the application name will be
925  * set from the value used in notify_init() or overridden with
926  * notify_set_app_name().
927  *
928  * Since: 0.7.3
929  */
930 void
notify_notification_set_app_name(NotifyNotification * notification,const char * app_name)931 notify_notification_set_app_name (NotifyNotification *notification,
932                                   const char         *app_name)
933 {
934         g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
935 
936         g_free (notification->priv->app_name);
937         notification->priv->app_name = g_strdup (app_name);
938 
939         g_object_notify (G_OBJECT (notification), "app-name");
940 }
941 
942 /**
943  * notify_notification_set_hint_int32:
944  * @notification: The notification.
945  * @key: The hint.
946  * @value: The hint's value.
947  *
948  * Sets a hint with a 32-bit integer value.
949  *
950  * Deprecated: 0.6. Use notify_notification_set_hint() instead
951  */
952 void
notify_notification_set_hint_int32(NotifyNotification * notification,const char * key,gint value)953 notify_notification_set_hint_int32 (NotifyNotification *notification,
954                                     const char         *key,
955                                     gint                value)
956 {
957         notify_notification_set_hint (notification, key,
958                                       g_variant_new_int32 (value));
959 }
960 
961 
962 /**
963  * notify_notification_set_hint_uint32:
964  * @notification: The notification.
965  * @key: The hint.
966  * @value: The hint's value.
967  *
968  * Sets a hint with an unsigned 32-bit integer value.
969  *
970  * Deprecated: 0.6. Use notify_notification_set_hint() instead
971  */
972 void
notify_notification_set_hint_uint32(NotifyNotification * notification,const char * key,guint value)973 notify_notification_set_hint_uint32 (NotifyNotification *notification,
974                                      const char         *key,
975                                      guint               value)
976 {
977         notify_notification_set_hint (notification, key,
978                                       g_variant_new_uint32 (value));
979 }
980 
981 /**
982  * notify_notification_set_hint_double:
983  * @notification: The notification.
984  * @key: The hint.
985  * @value: The hint's value.
986  *
987  * Sets a hint with a double value.
988  *
989  * Deprecated: 0.6. Use notify_notification_set_hint() instead
990  */
991 void
notify_notification_set_hint_double(NotifyNotification * notification,const char * key,gdouble value)992 notify_notification_set_hint_double (NotifyNotification *notification,
993                                      const char         *key,
994                                      gdouble             value)
995 {
996         notify_notification_set_hint (notification, key,
997                                       g_variant_new_double (value));
998 }
999 
1000 /**
1001  * notify_notification_set_hint_byte:
1002  * @notification: The notification.
1003  * @key: The hint.
1004  * @value: The hint's value.
1005  *
1006  * Sets a hint with a byte value.
1007  *
1008  * Deprecated: 0.6. Use notify_notification_set_hint() instead
1009  */
1010 void
notify_notification_set_hint_byte(NotifyNotification * notification,const char * key,guchar value)1011 notify_notification_set_hint_byte (NotifyNotification *notification,
1012                                    const char         *key,
1013                                    guchar              value)
1014 {
1015         notify_notification_set_hint (notification, key,
1016                                       g_variant_new_byte (value));
1017 }
1018 
1019 /**
1020  * notify_notification_set_hint_byte_array:
1021  * @notification: The notification.
1022  * @key: The hint.
1023  * @value: (array length=len): The hint's value.
1024  * @len: The length of the byte array.
1025  *
1026  * Sets a hint with a byte array value. The length of @value must be passed
1027  * as @len.
1028  *
1029  * Deprecated: 0.6. Use notify_notification_set_hint() instead
1030  */
1031 void
notify_notification_set_hint_byte_array(NotifyNotification * notification,const char * key,const guchar * value,gsize len)1032 notify_notification_set_hint_byte_array (NotifyNotification *notification,
1033                                          const char         *key,
1034                                          const guchar       *value,
1035                                          gsize               len)
1036 {
1037         gpointer value_dup;
1038 
1039         g_return_if_fail (value != NULL || len == 0);
1040 
1041         value_dup = g_memdup (value, len);
1042         notify_notification_set_hint (notification, key,
1043                                       g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
1044                                                                value_dup,
1045                                                                len,
1046                                                                TRUE,
1047                                                                g_free,
1048                                                                value_dup));
1049 }
1050 
1051 /**
1052  * notify_notification_set_hint_string:
1053  * @notification: The notification.
1054  * @key: The hint.
1055  * @value: The hint's value.
1056  *
1057  * Sets a hint with a string value.
1058  *
1059  * Deprecated: 0.6. Use notify_notification_set_hint() instead
1060  */
1061 void
notify_notification_set_hint_string(NotifyNotification * notification,const char * key,const char * value)1062 notify_notification_set_hint_string (NotifyNotification *notification,
1063                                      const char         *key,
1064                                      const char         *value)
1065 {
1066         if (value != NULL && value[0] != '\0') {
1067                 notify_notification_set_hint (notification,
1068                                               key,
1069                                               g_variant_new_string (value));
1070         }
1071 }
1072 
1073 static gboolean
_remove_all(void)1074 _remove_all (void)
1075 {
1076         return TRUE;
1077 }
1078 
1079 /**
1080  * notify_notification_clear_hints:
1081  * @notification: The notification.
1082  *
1083  * Clears all hints from the notification.
1084  */
1085 void
notify_notification_clear_hints(NotifyNotification * notification)1086 notify_notification_clear_hints (NotifyNotification *notification)
1087 {
1088         g_return_if_fail (notification != NULL);
1089         g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
1090 
1091         g_hash_table_foreach_remove (notification->priv->hints,
1092                                      (GHRFunc) _remove_all,
1093                                      NULL);
1094 }
1095 
1096 /**
1097  * notify_notification_clear_actions:
1098  * @notification: The notification.
1099  *
1100  * Clears all actions from the notification.
1101  */
1102 void
notify_notification_clear_actions(NotifyNotification * notification)1103 notify_notification_clear_actions (NotifyNotification *notification)
1104 {
1105         g_return_if_fail (notification != NULL);
1106         g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
1107 
1108         g_hash_table_foreach_remove (notification->priv->action_map,
1109                                      (GHRFunc) _remove_all,
1110                                      NULL);
1111 
1112         if (notification->priv->actions != NULL) {
1113                 g_slist_foreach (notification->priv->actions,
1114                                  (GFunc) g_free,
1115                                  NULL);
1116                 g_slist_free (notification->priv->actions);
1117         }
1118 
1119         notification->priv->actions = NULL;
1120         notification->priv->has_nondefault_actions = FALSE;
1121 }
1122 
1123 /**
1124  * notify_notification_add_action:
1125  * @notification: The notification.
1126  * @action: The action ID.
1127  * @label: The human-readable action label.
1128  * @callback: The action's callback function.
1129  * @user_data: Optional custom data to pass to @callback.
1130  * @free_func: (type GLib.DestroyNotify): An optional function to free @user_data when the notification
1131  *             is destroyed.
1132  *
1133  * Adds an action to a notification. When the action is invoked, the
1134  * specified callback function will be called, along with the value passed
1135  * to @user_data.
1136  */
1137 void
notify_notification_add_action(NotifyNotification * notification,const char * action,const char * label,NotifyActionCallback callback,gpointer user_data,GFreeFunc free_func)1138 notify_notification_add_action (NotifyNotification  *notification,
1139                                 const char          *action,
1140                                 const char          *label,
1141                                 NotifyActionCallback callback,
1142                                 gpointer             user_data,
1143                                 GFreeFunc            free_func)
1144 {
1145         NotifyNotificationPrivate *priv;
1146         CallbackPair              *pair;
1147 
1148         g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
1149         g_return_if_fail (action != NULL && *action != '\0');
1150         g_return_if_fail (label != NULL && *label != '\0');
1151         g_return_if_fail (callback != NULL);
1152 
1153         priv = notification->priv;
1154 
1155         priv->actions = g_slist_append (priv->actions, g_strdup (action));
1156         priv->actions = g_slist_append (priv->actions, g_strdup (label));
1157 
1158         pair = g_new0 (CallbackPair, 1);
1159         pair->cb = callback;
1160         pair->user_data = user_data;
1161         pair->free_func = free_func;
1162         g_hash_table_insert (priv->action_map, g_strdup (action), pair);
1163 
1164         if (!notification->priv->has_nondefault_actions &&
1165             g_ascii_strcasecmp (action, "default") != 0) {
1166                 notification->priv->has_nondefault_actions = TRUE;
1167         }
1168 }
1169 
1170 gboolean
_notify_notification_has_nondefault_actions(const NotifyNotification * n)1171 _notify_notification_has_nondefault_actions (const NotifyNotification *n)
1172 {
1173         g_return_val_if_fail (n != NULL, FALSE);
1174         g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (n), FALSE);
1175 
1176         return n->priv->has_nondefault_actions;
1177 }
1178 
1179 /**
1180  * notify_notification_close:
1181  * @notification: The notification.
1182  * @error: The returned error information.
1183  *
1184  * Synchronously tells the notification server to hide the notification on the screen.
1185  *
1186  * Returns: %TRUE on success, or %FALSE on error with @error filled in
1187  */
1188 gboolean
notify_notification_close(NotifyNotification * notification,GError ** error)1189 notify_notification_close (NotifyNotification *notification,
1190                            GError            **error)
1191 {
1192         NotifyNotificationPrivate *priv;
1193         GDBusProxy  *proxy;
1194         GVariant   *result;
1195 
1196         g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE);
1197         g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1198 
1199         priv = notification->priv;
1200 
1201         proxy = _notify_get_proxy (error);
1202         if (proxy == NULL) {
1203                 return FALSE;
1204         }
1205 
1206         /* FIXME: make this nonblocking! */
1207         result = g_dbus_proxy_call_sync (proxy,
1208                                          "CloseNotification",
1209                                          g_variant_new ("(u)", priv->id),
1210                                          G_DBUS_CALL_FLAGS_NONE,
1211                                          -1 /* FIXME! */,
1212                                          NULL,
1213                                          error);
1214         if (result == NULL) {
1215                 return FALSE;
1216         }
1217 
1218         g_variant_unref (result);
1219 
1220         return TRUE;
1221 }
1222 
1223 /**
1224  * notify_notification_get_closed_reason:
1225  * @notification: The notification.
1226  *
1227  * Returns the closed reason code for the notification. This is valid only
1228  * after the "closed" signal is emitted.
1229  *
1230  * Returns: The closed reason code.
1231  */
1232 gint
notify_notification_get_closed_reason(const NotifyNotification * notification)1233 notify_notification_get_closed_reason (const NotifyNotification *notification)
1234 {
1235         g_return_val_if_fail (notification != NULL, -1);
1236         g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), -1);
1237 
1238         return notification->priv->closed_reason;
1239 }
1240