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