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