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