1 /*
2  * Copyright (C) 2018, Matthias Clasen
3  *
4  * This file is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as
6  * published by the Free Software Foundation, version 3.0 of the
7  * License.
8  *
9  * This file is distributed in the hope that it will be useful, but
10  * 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 Public
15  * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * SPDX-License-Identifier: LGPL-3.0-only
18  */
19 
20 #include "config.h"
21 
22 #include "notification.h"
23 #include "portal-private.h"
24 
25 static void
action_invoked(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)26 action_invoked (GDBusConnection *bus,
27                 const char *sender_name,
28                 const char *object_path,
29                 const char *interface_name,
30                 const char *signal_name,
31                 GVariant *parameters,
32                 gpointer data)
33 {
34   XdpPortal *portal = data;
35   g_autoptr(GVariant) info = NULL;
36   const char *id;
37   const char *action;
38   g_autoptr(GVariant) parameter = NULL;
39 
40   g_variant_get (parameters, "(&s&s@av)", &id, &action, &parameter);
41 
42   g_signal_emit_by_name (portal, "notification-action-invoked",
43                          id, action, parameter);
44 }
45 
46 static void
ensure_action_invoked_connection(XdpPortal * portal)47 ensure_action_invoked_connection (XdpPortal *portal)
48 {
49   if (portal->action_invoked_signal == 0)
50     portal->action_invoked_signal =
51        g_dbus_connection_signal_subscribe (portal->bus,
52                                            PORTAL_BUS_NAME,
53                                            "org.freedesktop.portal.Notification",
54                                            "ActionInvoked",
55                                            PORTAL_OBJECT_PATH,
56                                            NULL,
57                                            G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
58                                            action_invoked,
59                                            portal,
60                                            NULL);
61 }
62 
63 typedef struct {
64   XdpPortal *portal;
65   GAsyncReadyCallback callback;
66   gpointer data;
67 } CallDoneData;
68 
69 static void
call_done(GObject * source,GAsyncResult * result,gpointer data)70 call_done (GObject *source,
71            GAsyncResult *result,
72            gpointer data)
73 {
74   CallDoneData *call_done_data = data;
75 
76   call_done_data->callback (G_OBJECT (call_done_data->portal), result, call_done_data->data);
77 
78   g_object_unref (call_done_data->portal);
79   g_free (call_done_data);
80 }
81 
82 /**
83  * xdp_portal_add_notification:
84  * @portal: a [class@Portal]
85  * @id: unique ID for the notification
86  * @notification: a [struct@GLib.Variant] dictionary with the content of the notification
87  * @flags: options for this call
88  * @cancellable: (nullable): optional [class@Gio.Cancellable]
89  * @callback: (scope async): a callback to call when the request is done
90  * @data: (closure): data to pass to @callback
91  *
92  * Sends a desktop notification.
93  *
94  * The following keys may be present in @notification:
95  *
96  * - title `s`: a user-visible string to display as title
97  * - body `s`: a user-visible string to display as body
98  * - icon `v`: a serialized icon (in the format produced by [method@Gio.Icon.serialize])
99  * - priority `s`: "low", "normal", "high" or "urgent"
100  * - default-action `s`: name of an action that
101  *     will be activated when the user clicks on the notification
102  * - default-action-target `v`: target parameter to send along when
103  *     activating the default action.
104  * - buttons `aa{sv}`: array of serialized buttons
105  *
106  * Each serialized button is a dictionary with the following supported keys:
107  *
108  * - label `s`: user-visible lable for the button. Mandatory
109  * - action `s`: name of an action that will be activated when
110  *     the user clicks on the button. Mandatory
111  * - target `v`: target parameter to send along when activating
112  *     the button
113  *
114  * Actions with a prefix of "app." are assumed to be exported by the
115  * application and will be activated via the org.freedesktop.Application
116  * interface, others are activated by emitting the
117  * [signal@Portal::notification-action-invoked] signal.
118  *
119  * It is the callers responsibility to ensure that the ID is unique
120  * among all notifications.
121  *
122  * To withdraw a notification, use [method@Portal.remove_notification].
123  */
124 void
xdp_portal_add_notification(XdpPortal * portal,const char * id,GVariant * notification,XdpNotificationFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)125 xdp_portal_add_notification (XdpPortal *portal,
126                              const char *id,
127                              GVariant *notification,
128                              XdpNotificationFlags flags,
129                              GCancellable *cancellable,
130                              GAsyncReadyCallback callback,
131                              gpointer data)
132 {
133   GAsyncReadyCallback call_done_cb = NULL;
134   CallDoneData *call_done_data = NULL;
135 
136   g_return_if_fail (XDP_IS_PORTAL (portal));
137   g_return_if_fail (flags == XDP_NOTIFICATION_FLAG_NONE);
138 
139   ensure_action_invoked_connection (portal);
140 
141   if (callback)
142     {
143       call_done_cb = call_done;
144       call_done_data = g_new (CallDoneData, 1);
145       call_done_data->portal = g_object_ref (portal);
146       call_done_data->callback = callback,
147       call_done_data->data = data;
148     }
149 
150   g_dbus_connection_call (portal->bus,
151                           PORTAL_BUS_NAME,
152                           PORTAL_OBJECT_PATH,
153                           "org.freedesktop.portal.Notification",
154                           "AddNotification",
155                           g_variant_new ("(s@a{sv})", id, notification),
156                           NULL,
157                           G_DBUS_CALL_FLAGS_NONE,
158                           -1,
159                           cancellable,
160                           call_done_cb,
161                           call_done_data);
162 }
163 
164 /**
165  * xdp_portal_add_notification_finish:
166  * @portal: a [class@Portal]
167  * @result: a [iface@Gio.AsyncResult]
168  * @error: return location for an error
169  *
170  * Finishes the notification request, and returns the result
171  * as a boolean.
172  *
173  * Returns: `TRUE` if the notification was added
174  */
175 gboolean
xdp_portal_add_notification_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)176 xdp_portal_add_notification_finish (XdpPortal     *portal,
177                                     GAsyncResult  *result,
178                                     GError       **error)
179 {
180   g_autoptr(GVariant) res = NULL;
181 
182   res = g_dbus_connection_call_finish (portal->bus, result, error);
183 
184   return !!res;
185 }
186 
187 /**
188  * xdp_portal_remove_notification:
189  * @portal: a [class@Portal]
190  * @id: the ID of an notification
191  *
192  * Withdraws a desktop notification.
193  */
194 void
xdp_portal_remove_notification(XdpPortal * portal,const char * id)195 xdp_portal_remove_notification (XdpPortal  *portal,
196                                 const char *id)
197 {
198   g_return_if_fail (XDP_IS_PORTAL (portal));
199 
200   g_dbus_connection_call (portal->bus,
201                           PORTAL_BUS_NAME,
202                           PORTAL_OBJECT_PATH,
203                           "org.freedesktop.portal.Notification",
204                           "RemoveNotification",
205                           g_variant_new ("(s)", id),
206                           NULL,
207                           G_DBUS_CALL_FLAGS_NONE,
208                           -1,
209                           NULL,
210                           NULL,
211                           NULL);
212 }
213