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