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