1 /*
2 * Copyright © 2013 Lars Uebernickel
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General
15 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Lars Uebernickel <lars@uebernic.de>
18 */
19
20 #include "config.h"
21
22 #include "gnotification-private.h"
23 #include "gdbusutils.h"
24 #include "gicon.h"
25 #include "gaction.h"
26 #include "gioenumtypes.h"
27
28 /**
29 * SECTION:gnotification
30 * @short_description: User Notifications (pop up messages)
31 * @include: gio/gio.h
32 *
33 * #GNotification is a mechanism for creating a notification to be shown
34 * to the user -- typically as a pop-up notification presented by the
35 * desktop environment shell.
36 *
37 * The key difference between #GNotification and other similar APIs is
38 * that, if supported by the desktop environment, notifications sent
39 * with #GNotification will persist after the application has exited,
40 * and even across system reboots.
41 *
42 * Since the user may click on a notification while the application is
43 * not running, applications using #GNotification should be able to be
44 * started as a D-Bus service, using #GApplication.
45 *
46 * User interaction with a notification (either the default action, or
47 * buttons) must be associated with actions on the application (ie:
48 * "app." actions). It is not possible to route user interaction
49 * through the notification itself, because the object will not exist if
50 * the application is autostarted as a result of a notification being
51 * clicked.
52 *
53 * A notification can be sent with g_application_send_notification().
54 *
55 * Since: 2.40
56 **/
57
58 /**
59 * GNotification:
60 *
61 * This structure type is private and should only be accessed using the
62 * public APIs.
63 *
64 * Since: 2.40
65 **/
66
67 typedef GObjectClass GNotificationClass;
68
69 struct _GNotification
70 {
71 GObject parent;
72
73 gchar *title;
74 gchar *body;
75 GIcon *icon;
76 GNotificationPriority priority;
77 GPtrArray *buttons;
78 gchar *default_action;
79 GVariant *default_action_target;
80 };
81
82 typedef struct
83 {
84 gchar *label;
85 gchar *action_name;
86 GVariant *target;
87 } Button;
88
G_DEFINE_TYPE(GNotification,g_notification,G_TYPE_OBJECT)89 G_DEFINE_TYPE (GNotification, g_notification, G_TYPE_OBJECT)
90
91 static void
92 button_free (gpointer data)
93 {
94 Button *button = data;
95
96 g_free (button->label);
97 g_free (button->action_name);
98 if (button->target)
99 g_variant_unref (button->target);
100
101 g_slice_free (Button, button);
102 }
103
104 static void
g_notification_dispose(GObject * object)105 g_notification_dispose (GObject *object)
106 {
107 GNotification *notification = G_NOTIFICATION (object);
108
109 g_clear_object (¬ification->icon);
110
111 G_OBJECT_CLASS (g_notification_parent_class)->dispose (object);
112 }
113
114 static void
g_notification_finalize(GObject * object)115 g_notification_finalize (GObject *object)
116 {
117 GNotification *notification = G_NOTIFICATION (object);
118
119 g_free (notification->title);
120 g_free (notification->body);
121 g_free (notification->default_action);
122 if (notification->default_action_target)
123 g_variant_unref (notification->default_action_target);
124 g_ptr_array_free (notification->buttons, TRUE);
125
126 G_OBJECT_CLASS (g_notification_parent_class)->finalize (object);
127 }
128
129 static void
g_notification_class_init(GNotificationClass * klass)130 g_notification_class_init (GNotificationClass *klass)
131 {
132 GObjectClass *object_class = G_OBJECT_CLASS (klass);
133
134 object_class->dispose = g_notification_dispose;
135 object_class->finalize = g_notification_finalize;
136 }
137
138 static void
g_notification_init(GNotification * notification)139 g_notification_init (GNotification *notification)
140 {
141 notification->buttons = g_ptr_array_new_full (2, button_free);
142 }
143
144 /**
145 * g_notification_new:
146 * @title: the title of the notification
147 *
148 * Creates a new #GNotification with @title as its title.
149 *
150 * After populating @notification with more details, it can be sent to
151 * the desktop shell with g_application_send_notification(). Changing
152 * any properties after this call will not have any effect until
153 * resending @notification.
154 *
155 * Returns: a new #GNotification instance
156 *
157 * Since: 2.40
158 */
159 GNotification *
g_notification_new(const gchar * title)160 g_notification_new (const gchar *title)
161 {
162 GNotification *notification;
163
164 g_return_val_if_fail (title != NULL, NULL);
165
166 notification = g_object_new (G_TYPE_NOTIFICATION, NULL);
167 notification->title = g_strdup (title);
168
169 return notification;
170 }
171
172 /*< private >
173 * g_notification_get_title:
174 * @notification: a #GNotification
175 *
176 * Gets the title of @notification.
177 *
178 * Returns: the title of @notification
179 *
180 * Since: 2.40
181 */
182 const gchar *
g_notification_get_title(GNotification * notification)183 g_notification_get_title (GNotification *notification)
184 {
185 g_return_val_if_fail (G_IS_NOTIFICATION (notification), NULL);
186
187 return notification->title;
188 }
189
190 /**
191 * g_notification_set_title:
192 * @notification: a #GNotification
193 * @title: the new title for @notification
194 *
195 * Sets the title of @notification to @title.
196 *
197 * Since: 2.40
198 */
199 void
g_notification_set_title(GNotification * notification,const gchar * title)200 g_notification_set_title (GNotification *notification,
201 const gchar *title)
202 {
203 g_return_if_fail (G_IS_NOTIFICATION (notification));
204 g_return_if_fail (title != NULL);
205
206 g_free (notification->title);
207
208 notification->title = g_strdup (title);
209 }
210
211 /*< private >
212 * g_notification_get_body:
213 * @notification: a #GNotification
214 *
215 * Gets the current body of @notification.
216 *
217 * Returns: (nullable): the body of @notification
218 *
219 * Since: 2.40
220 */
221 const gchar *
g_notification_get_body(GNotification * notification)222 g_notification_get_body (GNotification *notification)
223 {
224 g_return_val_if_fail (G_IS_NOTIFICATION (notification), NULL);
225
226 return notification->body;
227 }
228
229 /**
230 * g_notification_set_body:
231 * @notification: a #GNotification
232 * @body: (nullable): the new body for @notification, or %NULL
233 *
234 * Sets the body of @notification to @body.
235 *
236 * Since: 2.40
237 */
238 void
g_notification_set_body(GNotification * notification,const gchar * body)239 g_notification_set_body (GNotification *notification,
240 const gchar *body)
241 {
242 g_return_if_fail (G_IS_NOTIFICATION (notification));
243 g_return_if_fail (body != NULL);
244
245 g_free (notification->body);
246
247 notification->body = g_strdup (body);
248 }
249
250 /*< private >
251 * g_notification_get_icon:
252 * @notification: a #GNotification
253 *
254 * Gets the icon currently set on @notification.
255 *
256 * Returns: (transfer none): the icon associated with @notification
257 *
258 * Since: 2.40
259 */
260 GIcon *
g_notification_get_icon(GNotification * notification)261 g_notification_get_icon (GNotification *notification)
262 {
263 g_return_val_if_fail (G_IS_NOTIFICATION (notification), NULL);
264
265 return notification->icon;
266 }
267
268 /**
269 * g_notification_set_icon:
270 * @notification: a #GNotification
271 * @icon: the icon to be shown in @notification, as a #GIcon
272 *
273 * Sets the icon of @notification to @icon.
274 *
275 * Since: 2.40
276 */
277 void
g_notification_set_icon(GNotification * notification,GIcon * icon)278 g_notification_set_icon (GNotification *notification,
279 GIcon *icon)
280 {
281 g_return_if_fail (G_IS_NOTIFICATION (notification));
282
283 if (notification->icon)
284 g_object_unref (notification->icon);
285
286 notification->icon = g_object_ref (icon);
287 }
288
289 /*< private >
290 * g_notification_get_priority:
291 * @notification: a #GNotification
292 *
293 * Returns the priority of @notification
294 *
295 * Since: 2.42
296 */
297 GNotificationPriority
g_notification_get_priority(GNotification * notification)298 g_notification_get_priority (GNotification *notification)
299 {
300 g_return_val_if_fail (G_IS_NOTIFICATION (notification), G_NOTIFICATION_PRIORITY_NORMAL);
301
302 return notification->priority;
303 }
304
305 /**
306 * g_notification_set_urgent:
307 * @notification: a #GNotification
308 * @urgent: %TRUE if @notification is urgent
309 *
310 * Deprecated in favor of g_notification_set_priority().
311 *
312 * Since: 2.40
313 * Deprecated: 2.42: Since 2.42, this has been deprecated in favour of
314 * g_notification_set_priority().
315 */
316 void
g_notification_set_urgent(GNotification * notification,gboolean urgent)317 g_notification_set_urgent (GNotification *notification,
318 gboolean urgent)
319 {
320 g_return_if_fail (G_IS_NOTIFICATION (notification));
321
322 notification->priority = urgent ?
323 G_NOTIFICATION_PRIORITY_URGENT :
324 G_NOTIFICATION_PRIORITY_NORMAL;
325 }
326
327 /**
328 * g_notification_set_priority:
329 * @notification: a #GNotification
330 * @priority: a #GNotificationPriority
331 *
332 * Sets the priority of @notification to @priority. See
333 * #GNotificationPriority for possible values.
334 */
335 void
g_notification_set_priority(GNotification * notification,GNotificationPriority priority)336 g_notification_set_priority (GNotification *notification,
337 GNotificationPriority priority)
338 {
339 g_return_if_fail (G_IS_NOTIFICATION (notification));
340
341 notification->priority = priority;
342 }
343
344 /**
345 * g_notification_add_button:
346 * @notification: a #GNotification
347 * @label: label of the button
348 * @detailed_action: a detailed action name
349 *
350 * Adds a button to @notification that activates the action in
351 * @detailed_action when clicked. That action must be an
352 * application-wide action (starting with "app."). If @detailed_action
353 * contains a target, the action will be activated with that target as
354 * its parameter.
355 *
356 * See g_action_parse_detailed_name() for a description of the format
357 * for @detailed_action.
358 *
359 * Since: 2.40
360 */
361 void
g_notification_add_button(GNotification * notification,const gchar * label,const gchar * detailed_action)362 g_notification_add_button (GNotification *notification,
363 const gchar *label,
364 const gchar *detailed_action)
365 {
366 gchar *action;
367 GVariant *target;
368 GError *error = NULL;
369
370 g_return_if_fail (detailed_action != NULL);
371
372 if (!g_action_parse_detailed_name (detailed_action, &action, &target, &error))
373 {
374 g_warning ("%s: %s", G_STRFUNC, error->message);
375 g_error_free (error);
376 return;
377 }
378
379 g_notification_add_button_with_target_value (notification, label, action, target);
380
381 g_free (action);
382 if (target)
383 g_variant_unref (target);
384 }
385
386 /**
387 * g_notification_add_button_with_target: (skip)
388 * @notification: a #GNotification
389 * @label: label of the button
390 * @action: an action name
391 * @target_format: (nullable): a #GVariant format string, or %NULL
392 * @...: positional parameters, as determined by @target_format
393 *
394 * Adds a button to @notification that activates @action when clicked.
395 * @action must be an application-wide action (it must start with "app.").
396 *
397 * If @target_format is given, it is used to collect remaining
398 * positional parameters into a #GVariant instance, similar to
399 * g_variant_new(). @action will be activated with that #GVariant as its
400 * parameter.
401 *
402 * Since: 2.40
403 */
404 void
g_notification_add_button_with_target(GNotification * notification,const gchar * label,const gchar * action,const gchar * target_format,...)405 g_notification_add_button_with_target (GNotification *notification,
406 const gchar *label,
407 const gchar *action,
408 const gchar *target_format,
409 ...)
410 {
411 va_list args;
412 GVariant *target = NULL;
413
414 if (target_format)
415 {
416 va_start (args, target_format);
417 target = g_variant_new_va (target_format, NULL, &args);
418 va_end (args);
419 }
420
421 g_notification_add_button_with_target_value (notification, label, action, target);
422 }
423
424 /**
425 * g_notification_add_button_with_target_value: (rename-to g_notification_add_button_with_target)
426 * @notification: a #GNotification
427 * @label: label of the button
428 * @action: an action name
429 * @target: (nullable): a #GVariant to use as @action's parameter, or %NULL
430 *
431 * Adds a button to @notification that activates @action when clicked.
432 * @action must be an application-wide action (it must start with "app.").
433 *
434 * If @target is non-%NULL, @action will be activated with @target as
435 * its parameter.
436 *
437 * Since: 2.40
438 */
439 void
g_notification_add_button_with_target_value(GNotification * notification,const gchar * label,const gchar * action,GVariant * target)440 g_notification_add_button_with_target_value (GNotification *notification,
441 const gchar *label,
442 const gchar *action,
443 GVariant *target)
444 {
445 Button *button;
446
447 g_return_if_fail (G_IS_NOTIFICATION (notification));
448 g_return_if_fail (label != NULL);
449 g_return_if_fail (action != NULL && g_action_name_is_valid (action));
450
451 if (!g_str_has_prefix (action, "app."))
452 {
453 g_warning ("%s: action '%s' does not start with 'app.'."
454 "This is unlikely to work properly.", G_STRFUNC, action);
455 }
456
457 button = g_slice_new0 (Button);
458 button->label = g_strdup (label);
459 button->action_name = g_strdup (action);
460
461 if (target)
462 button->target = g_variant_ref_sink (target);
463
464 g_ptr_array_add (notification->buttons, button);
465 }
466
467 /*< private >
468 * g_notification_get_n_buttons:
469 * @notification: a #GNotification
470 *
471 * Returns: the amount of buttons added to @notification.
472 */
473 guint
g_notification_get_n_buttons(GNotification * notification)474 g_notification_get_n_buttons (GNotification *notification)
475 {
476 return notification->buttons->len;
477 }
478
479 /*< private >
480 * g_notification_get_button:
481 * @notification: a #GNotification
482 * @index: index of the button
483 * @label: (): return location for the button's label
484 * @action: (): return location for the button's associated action
485 * @target: (): return location for the target @action should be
486 * activated with
487 *
488 * Returns a description of a button that was added to @notification
489 * with g_notification_add_button().
490 *
491 * @index must be smaller than the value returned by
492 * g_notification_get_n_buttons().
493 */
494 void
g_notification_get_button(GNotification * notification,gint index,gchar ** label,gchar ** action,GVariant ** target)495 g_notification_get_button (GNotification *notification,
496 gint index,
497 gchar **label,
498 gchar **action,
499 GVariant **target)
500 {
501 Button *button;
502
503 button = g_ptr_array_index (notification->buttons, index);
504
505 if (label)
506 *label = g_strdup (button->label);
507
508 if (action)
509 *action = g_strdup (button->action_name);
510
511 if (target)
512 *target = button->target ? g_variant_ref (button->target) : NULL;
513 }
514
515 /*< private >
516 * g_notification_get_button_with_action:
517 * @notification: a #GNotification
518 * @action: an action name
519 *
520 * Returns the index of the button in @notification that is associated
521 * with @action, or -1 if no such button exists.
522 */
523 gint
g_notification_get_button_with_action(GNotification * notification,const gchar * action)524 g_notification_get_button_with_action (GNotification *notification,
525 const gchar *action)
526 {
527 guint i;
528
529 for (i = 0; i < notification->buttons->len; i++)
530 {
531 Button *button;
532
533 button = g_ptr_array_index (notification->buttons, i);
534 if (g_str_equal (action, button->action_name))
535 return i;
536 }
537
538 return -1;
539 }
540
541
542 /*< private >
543 * g_notification_get_default_action:
544 * @notification: a #GNotification
545 * @action: (nullable): return location for the default action
546 * @target: (nullable): return location for the target of the default action
547 *
548 * Gets the action and target for the default action of @notification.
549 *
550 * Returns: %TRUE if @notification has a default action
551 */
552 gboolean
g_notification_get_default_action(GNotification * notification,gchar ** action,GVariant ** target)553 g_notification_get_default_action (GNotification *notification,
554 gchar **action,
555 GVariant **target)
556 {
557 if (notification->default_action == NULL)
558 return FALSE;
559
560 if (action)
561 *action = g_strdup (notification->default_action);
562
563 if (target)
564 {
565 if (notification->default_action_target)
566 *target = g_variant_ref (notification->default_action_target);
567 else
568 *target = NULL;
569 }
570
571 return TRUE;
572 }
573
574 /**
575 * g_notification_set_default_action:
576 * @notification: a #GNotification
577 * @detailed_action: a detailed action name
578 *
579 * Sets the default action of @notification to @detailed_action. This
580 * action is activated when the notification is clicked on.
581 *
582 * The action in @detailed_action must be an application-wide action (it
583 * must start with "app."). If @detailed_action contains a target, the
584 * given action will be activated with that target as its parameter.
585 * See g_action_parse_detailed_name() for a description of the format
586 * for @detailed_action.
587 *
588 * When no default action is set, the application that the notification
589 * was sent on is activated.
590 *
591 * Since: 2.40
592 */
593 void
g_notification_set_default_action(GNotification * notification,const gchar * detailed_action)594 g_notification_set_default_action (GNotification *notification,
595 const gchar *detailed_action)
596 {
597 gchar *action;
598 GVariant *target;
599 GError *error = NULL;
600
601 if (!g_action_parse_detailed_name (detailed_action, &action, &target, &error))
602 {
603 g_warning ("%s: %s", G_STRFUNC, error->message);
604 g_error_free (error);
605 return;
606 }
607
608 g_notification_set_default_action_and_target_value (notification, action, target);
609
610 g_free (action);
611 if (target)
612 g_variant_unref (target);
613 }
614
615 /**
616 * g_notification_set_default_action_and_target: (skip)
617 * @notification: a #GNotification
618 * @action: an action name
619 * @target_format: (nullable): a #GVariant format string, or %NULL
620 * @...: positional parameters, as determined by @target_format
621 *
622 * Sets the default action of @notification to @action. This action is
623 * activated when the notification is clicked on. It must be an
624 * application-wide action (it must start with "app.").
625 *
626 * If @target_format is given, it is used to collect remaining
627 * positional parameters into a #GVariant instance, similar to
628 * g_variant_new(). @action will be activated with that #GVariant as its
629 * parameter.
630 *
631 * When no default action is set, the application that the notification
632 * was sent on is activated.
633 *
634 * Since: 2.40
635 */
636 void
g_notification_set_default_action_and_target(GNotification * notification,const gchar * action,const gchar * target_format,...)637 g_notification_set_default_action_and_target (GNotification *notification,
638 const gchar *action,
639 const gchar *target_format,
640 ...)
641 {
642 va_list args;
643 GVariant *target = NULL;
644
645 if (target_format)
646 {
647 va_start (args, target_format);
648 target = g_variant_new_va (target_format, NULL, &args);
649 va_end (args);
650 }
651
652 g_notification_set_default_action_and_target_value (notification, action, target);
653 }
654
655 /**
656 * g_notification_set_default_action_and_target_value: (rename-to g_notification_set_default_action_and_target)
657 * @notification: a #GNotification
658 * @action: an action name
659 * @target: (nullable): a #GVariant to use as @action's parameter, or %NULL
660 *
661 * Sets the default action of @notification to @action. This action is
662 * activated when the notification is clicked on. It must be an
663 * application-wide action (start with "app.").
664 *
665 * If @target is non-%NULL, @action will be activated with @target as
666 * its parameter.
667 *
668 * When no default action is set, the application that the notification
669 * was sent on is activated.
670 *
671 * Since: 2.40
672 */
673 void
g_notification_set_default_action_and_target_value(GNotification * notification,const gchar * action,GVariant * target)674 g_notification_set_default_action_and_target_value (GNotification *notification,
675 const gchar *action,
676 GVariant *target)
677 {
678 g_return_if_fail (G_IS_NOTIFICATION (notification));
679 g_return_if_fail (action != NULL && g_action_name_is_valid (action));
680
681 if (!g_str_has_prefix (action, "app."))
682 {
683 g_warning ("%s: action '%s' does not start with 'app.'."
684 "This is unlikely to work properly.", G_STRFUNC, action);
685 }
686
687 g_free (notification->default_action);
688 g_clear_pointer (¬ification->default_action_target, g_variant_unref);
689
690 notification->default_action = g_strdup (action);
691
692 if (target)
693 notification->default_action_target = g_variant_ref_sink (target);
694 }
695
696 static GVariant *
g_notification_serialize_button(Button * button)697 g_notification_serialize_button (Button *button)
698 {
699 GVariantBuilder builder;
700
701 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
702
703 g_variant_builder_add (&builder, "{sv}", "label", g_variant_new_string (button->label));
704 g_variant_builder_add (&builder, "{sv}", "action", g_variant_new_string (button->action_name));
705
706 if (button->target)
707 g_variant_builder_add (&builder, "{sv}", "target", button->target);
708
709 return g_variant_builder_end (&builder);
710 }
711
712 static GVariant *
g_notification_get_priority_nick(GNotification * notification)713 g_notification_get_priority_nick (GNotification *notification)
714 {
715 GEnumClass *enum_class;
716 GEnumValue *value;
717 GVariant *nick;
718
719 enum_class = g_type_class_ref (G_TYPE_NOTIFICATION_PRIORITY);
720 value = g_enum_get_value (enum_class, g_notification_get_priority (notification));
721 g_assert (value != NULL);
722 nick = g_variant_new_string (value->value_nick);
723 g_type_class_unref (enum_class);
724
725 return nick;
726 }
727
728 /*< private >
729 * g_notification_serialize:
730 *
731 * Serializes @notification into a floating variant of type a{sv}.
732 *
733 * Returns: the serialized @notification as a floating variant.
734 */
735 GVariant *
g_notification_serialize(GNotification * notification)736 g_notification_serialize (GNotification *notification)
737 {
738 GVariantBuilder builder;
739
740 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
741
742 if (notification->title)
743 g_variant_builder_add (&builder, "{sv}", "title", g_variant_new_string (notification->title));
744
745 if (notification->body)
746 g_variant_builder_add (&builder, "{sv}", "body", g_variant_new_string (notification->body));
747
748 if (notification->icon)
749 {
750 GVariant *serialized_icon;
751
752 if ((serialized_icon = g_icon_serialize (notification->icon)))
753 {
754 g_variant_builder_add (&builder, "{sv}", "icon", serialized_icon);
755 g_variant_unref (serialized_icon);
756 }
757 }
758
759 g_variant_builder_add (&builder, "{sv}", "priority", g_notification_get_priority_nick (notification));
760
761 if (notification->default_action)
762 {
763 g_variant_builder_add (&builder, "{sv}", "default-action",
764 g_variant_new_string (notification->default_action));
765
766 if (notification->default_action_target)
767 g_variant_builder_add (&builder, "{sv}", "default-action-target",
768 notification->default_action_target);
769 }
770
771 if (notification->buttons->len > 0)
772 {
773 GVariantBuilder actions_builder;
774 guint i;
775
776 g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("aa{sv}"));
777
778 for (i = 0; i < notification->buttons->len; i++)
779 {
780 Button *button = g_ptr_array_index (notification->buttons, i);
781 g_variant_builder_add (&actions_builder, "@a{sv}", g_notification_serialize_button (button));
782 }
783
784 g_variant_builder_add (&builder, "{sv}", "buttons", g_variant_builder_end (&actions_builder));
785 }
786
787 return g_variant_builder_end (&builder);
788 }
789