1 /*
2 * Copyright (C) 2018, Matthias Clasen
3 * Copyright (C) 2019, Patrick Griffis
4 *
5 * This file is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation, version 3.0 of the
8 * License.
9 *
10 * This file is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: LGPL-3.0-only
19 */
20
21 #include "config.h"
22
23 #include "background.h"
24 #include "portal-private.h"
25
26 typedef struct {
27 XdpPortal *portal;
28 XdpParent *parent;
29 char *parent_handle;
30 guint signal_id;
31 GTask *task;
32 char *request_path;
33 guint cancelled_id;
34
35 gboolean autostart;
36 gboolean dbus_activatable;
37 GPtrArray *commandline;
38 char *reason;
39 } BackgroundCall;
40
41 static void
background_call_free(BackgroundCall * call)42 background_call_free (BackgroundCall *call)
43 {
44 if (call->parent)
45 {
46 call->parent->parent_unexport (call->parent);
47 xdp_parent_free (call->parent);
48 }
49 g_free (call->parent_handle);
50
51 if (call->signal_id)
52 g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
53
54 if (call->cancelled_id)
55 g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
56
57 g_free (call->request_path);
58
59 g_object_unref (call->portal);
60 g_object_unref (call->task);
61
62 g_clear_pointer (&call->commandline, g_ptr_array_unref);
63 g_free (call->reason);
64
65 g_free (call);
66 }
67
68 static void
response_received(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)69 response_received (GDBusConnection *bus,
70 const char *sender_name,
71 const char *object_path,
72 const char *interface_name,
73 const char *signal_name,
74 GVariant *parameters,
75 gpointer data)
76 {
77 BackgroundCall *call = data;
78 guint32 response;
79 g_autoptr(GVariant) ret = NULL;
80
81 if (call->cancelled_id)
82 {
83 g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
84 call->cancelled_id = 0;
85 }
86
87 g_variant_get (parameters, "(u@a{sv})", &response, &ret);
88
89 if (response == 0)
90 {
91 gboolean permission_granted = FALSE;
92 g_variant_lookup (ret, call->autostart ? "autostart" : "background", "b", &permission_granted);
93 g_task_return_boolean (call->task, permission_granted);
94 }
95 else if (response == 1)
96 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Background request canceled");
97 else
98 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Background request failed");
99
100 background_call_free (call);
101 }
102
103 static void request_background (BackgroundCall *call);
104
105 static void
parent_exported(XdpParent * parent,const char * handle,gpointer data)106 parent_exported (XdpParent *parent,
107 const char *handle,
108 gpointer data)
109 {
110 BackgroundCall *call = data;
111 call->parent_handle = g_strdup (handle);
112 request_background (call);
113 }
114
115 static void
cancelled_cb(GCancellable * cancellable,gpointer data)116 cancelled_cb (GCancellable *cancellable,
117 gpointer data)
118 {
119 BackgroundCall *call = data;
120
121 g_dbus_connection_call (call->portal->bus,
122 PORTAL_BUS_NAME,
123 call->request_path,
124 REQUEST_INTERFACE,
125 "Close",
126 NULL,
127 NULL,
128 G_DBUS_CALL_FLAGS_NONE,
129 -1,
130 NULL, NULL, NULL);
131
132 background_call_free (call);
133 }
134
135 static void
call_returned(GObject * object,GAsyncResult * result,gpointer data)136 call_returned (GObject *object,
137 GAsyncResult *result,
138 gpointer data)
139 {
140 BackgroundCall *call = data;
141 GError *error = NULL;
142 g_autoptr(GVariant) ret = NULL;
143
144 ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
145 if (error)
146 {
147 g_task_return_error (call->task, error);
148 background_call_free (call);
149 }
150 }
151
152 static void
request_background(BackgroundCall * call)153 request_background (BackgroundCall *call)
154 {
155 GVariantBuilder options;
156 g_autofree char *token = NULL;
157 GCancellable *cancellable;
158
159 if (call->parent_handle == NULL)
160 {
161 call->parent->parent_export (call->parent, parent_exported, call);
162 return;
163 }
164
165 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
166 call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
167 call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
168 PORTAL_BUS_NAME,
169 REQUEST_INTERFACE,
170 "Response",
171 call->request_path,
172 NULL,
173 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
174 response_received,
175 call,
176 NULL);
177
178 cancellable = g_task_get_cancellable (call->task);
179 if (cancellable)
180 call->cancelled_id = g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), call);
181
182 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
183 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
184
185 g_variant_builder_add (&options, "{sv}", "autostart", g_variant_new_boolean (call->autostart));
186 g_variant_builder_add (&options, "{sv}", "dbus-activatable", g_variant_new_boolean (call->dbus_activatable));
187 if (call->reason)
188 g_variant_builder_add (&options, "{sv}", "reason", g_variant_new_string (call->reason));
189 if (call->commandline)
190 g_variant_builder_add (&options, "{sv}", "commandline", g_variant_new_strv ((const char* const*)call->commandline->pdata, call->commandline->len));
191
192 g_debug ("calling background");
193 g_dbus_connection_call (call->portal->bus,
194 PORTAL_BUS_NAME,
195 PORTAL_OBJECT_PATH,
196 "org.freedesktop.portal.Background",
197 "RequestBackground",
198 g_variant_new ("(sa{sv})", call->parent_handle, &options),
199 NULL,
200 G_DBUS_CALL_FLAGS_NONE,
201 -1,
202 cancellable,
203 call_returned,
204 call);
205 }
206
207 /**
208 * xdp_portal_request_background:
209 * @portal: a [class@Portal]
210 * @parent: (nullable): parent window information
211 * @commandline: (element-type utf8) (transfer container): command line to autostart
212 * @reason: (nullable): reason to present to user for request
213 * @flags: options for this call
214 * @cancellable: (nullable): optional [class@Gio.Cancellable]
215 * @callback: (scope async): a callback to call when the request is done
216 * @user_data: (closure): data to pass to @callback
217 *
218 * Requests background permissions.
219 *
220 * When the request is done, @callback will be called. You can then
221 * call [method@Portal.request_background_finish] to get the results.
222 */
223 void
xdp_portal_request_background(XdpPortal * portal,XdpParent * parent,char * reason,GPtrArray * commandline,XdpBackgroundFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)224 xdp_portal_request_background (XdpPortal *portal,
225 XdpParent *parent,
226 char *reason,
227 GPtrArray *commandline,
228 XdpBackgroundFlags flags,
229 GCancellable *cancellable,
230 GAsyncReadyCallback callback,
231 gpointer user_data)
232 {
233 BackgroundCall *call;
234
235 g_return_if_fail (XDP_IS_PORTAL (portal));
236 g_return_if_fail ((flags & ~(XDP_BACKGROUND_FLAG_AUTOSTART |
237 XDP_BACKGROUND_FLAG_ACTIVATABLE)) == 0);
238
239 call = g_new0 (BackgroundCall, 1);
240 call->portal = g_object_ref (portal);
241 if (parent)
242 call->parent = xdp_parent_copy (parent);
243 else
244 call->parent_handle = g_strdup ("");
245
246 call->autostart = (flags & XDP_BACKGROUND_FLAG_AUTOSTART) != 0;
247 call->dbus_activatable = (flags & XDP_BACKGROUND_FLAG_ACTIVATABLE) != 0;
248 call->reason = g_strdup (reason);
249 if (commandline)
250 call->commandline = g_ptr_array_ref (commandline);
251
252 call->task = g_task_new (portal, cancellable, callback, user_data);
253
254 request_background (call);
255 }
256
257 /**
258 * xdp_portal_request_background_finish:
259 * @portal: a [class@Portal]
260 * @result: a [iface@Gio.AsyncResult]
261 * @error: return location for an error
262 *
263 * Finishes the request, and returns `TRUE` if successful.
264 *
265 * Returns: `TRUE` if successful.
266 */
267 gboolean
xdp_portal_request_background_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)268 xdp_portal_request_background_finish (XdpPortal *portal,
269 GAsyncResult *result,
270 GError **error)
271 {
272 g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
273 g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
274
275 return g_task_propagate_boolean (G_TASK (result), error);
276 }
277