1/* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright 2017 Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but 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 library; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "config.h"
20
21#include <sys/stat.h>
22#include <fcntl.h>
23#include <errno.h>
24#include <string.h>
25
26#include "gopenuriportal.h"
27#include "xdp-dbus.h"
28#include "gstdio.h"
29
30#ifdef G_OS_UNIX
31#include "gunixfdlist.h"
32#endif
33
34#ifndef O_CLOEXEC
35#define O_CLOEXEC 0
36#else
37#define HAVE_O_CLOEXEC 1
38#endif
39
40
41static GXdpOpenURI *openuri;
42
43static gboolean
44init_openuri_portal (void)
45{
46 static gsize openuri_inited = 0;
47
48 if (g_once_init_enter (&openuri_inited))
49 {
50 GError *error = NULL;
51 GDBusConnection *connection = g_bus_get_sync (bus_type: G_BUS_TYPE_SESSION, NULL, error: &error);
52
53 if (connection != NULL)
54 {
55 openuri = gxdp_open_uri_proxy_new_sync (connection, 0,
56 "org.freedesktop.portal.Desktop",
57 "/org/freedesktop/portal/desktop",
58 NULL, &error);
59 if (openuri == NULL)
60 {
61 g_warning ("Cannot create document portal proxy: %s", error->message);
62 g_error_free (error);
63 }
64
65 g_object_unref (object: connection);
66 }
67 else
68 {
69 g_warning ("Cannot connect to session bus when initializing document portal: %s",
70 error->message);
71 g_error_free (error);
72 }
73
74 g_once_init_leave (&openuri_inited, 1);
75 }
76
77 return openuri != NULL;
78}
79
80gboolean
81g_openuri_portal_open_uri (const char *uri,
82 const char *parent_window,
83 GError **error)
84{
85 GFile *file = NULL;
86 GVariantBuilder opt_builder;
87 gboolean res;
88
89 if (!init_openuri_portal ())
90 {
91 g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_NOT_INITIALIZED,
92 format: "OpenURI portal is not available");
93 return FALSE;
94 }
95
96 g_variant_builder_init (builder: &opt_builder, G_VARIANT_TYPE_VARDICT);
97
98 file = g_file_new_for_uri (uri);
99 if (g_file_is_native (file))
100 {
101 char *path = NULL;
102 GUnixFDList *fd_list = NULL;
103 int fd, fd_id, errsv;
104
105 path = g_file_get_path (file);
106
107 fd = g_open (file: path, O_RDONLY | O_CLOEXEC);
108 errsv = errno;
109 if (fd == -1)
110 {
111 g_set_error (err: error, G_IO_ERROR, code: g_io_error_from_errno (err_no: errsv),
112 format: "Failed to open '%s'", path);
113 return FALSE;
114 }
115
116#ifndef HAVE_O_CLOEXEC
117 fcntl (fd, F_SETFD, FD_CLOEXEC);
118#endif
119 fd_list = g_unix_fd_list_new_from_array (fds: &fd, n_fds: 1);
120 fd = -1;
121 fd_id = 0;
122
123 res = gxdp_open_uri_call_open_file_sync (openuri,
124 parent_window ? parent_window : "",
125 g_variant_new (format_string: "h", fd_id),
126 g_variant_builder_end (builder: &opt_builder),
127 fd_list,
128 NULL,
129 NULL,
130 NULL,
131 error);
132 g_free (mem: path);
133 g_object_unref (object: fd_list);
134 }
135 else
136 {
137 res = gxdp_open_uri_call_open_uri_sync (openuri,
138 parent_window ? parent_window : "",
139 uri,
140 g_variant_builder_end (builder: &opt_builder),
141 NULL,
142 NULL,
143 error);
144 }
145
146 g_object_unref (object: file);
147
148 return res;
149}
150
151enum {
152 XDG_DESKTOP_PORTAL_SUCCESS = 0,
153 XDG_DESKTOP_PORTAL_CANCELLED = 1,
154 XDG_DESKTOP_PORTAL_FAILED = 2
155};
156
157static void
158response_received (GDBusConnection *connection,
159 const char *sender_name,
160 const char *object_path,
161 const char *interface_name,
162 const char *signal_name,
163 GVariant *parameters,
164 gpointer user_data)
165{
166 GTask *task = user_data;
167 guint32 response;
168 guint signal_id;
169
170 signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
171 g_dbus_connection_signal_unsubscribe (connection, subscription_id: signal_id);
172
173 g_variant_get (value: parameters, format_string: "(u@a{sv})", &response, NULL);
174
175 switch (response)
176 {
177 case XDG_DESKTOP_PORTAL_SUCCESS:
178 g_task_return_boolean (task, TRUE);
179 break;
180 case XDG_DESKTOP_PORTAL_CANCELLED:
181 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_CANCELLED, format: "Launch cancelled");
182 break;
183 case XDG_DESKTOP_PORTAL_FAILED:
184 default:
185 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_FAILED, format: "Launch failed");
186 break;
187 }
188
189 g_object_unref (object: task);
190}
191
192static void
193open_call_done (GObject *source,
194 GAsyncResult *result,
195 gpointer user_data)
196{
197 GXdpOpenURI *openuri = GXDP_OPEN_URI (source);
198 GDBusConnection *connection;
199 GTask *task = user_data;
200 GError *error = NULL;
201 gboolean open_file;
202 gboolean res;
203 char *path = NULL;
204 const char *handle;
205 guint signal_id;
206
207 connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri));
208 open_file = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "open-file"));
209
210 if (open_file)
211 res = gxdp_open_uri_call_open_file_finish (openuri, &path, NULL, result, &error);
212 else
213 res = gxdp_open_uri_call_open_uri_finish (openuri, &path, result, &error);
214
215 if (!res)
216 {
217 g_task_return_error (task, error);
218 g_object_unref (object: task);
219 g_free (mem: path);
220 return;
221 }
222
223 handle = (const char *)g_object_get_data (G_OBJECT (task), key: "handle");
224 if (g_strcmp0 (str1: handle, str2: path) != 0)
225 {
226 signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
227 g_dbus_connection_signal_unsubscribe (connection, subscription_id: signal_id);
228
229 signal_id = g_dbus_connection_signal_subscribe (connection,
230 sender: "org.freedesktop.portal.Desktop",
231 interface_name: "org.freedesktop.portal.Request",
232 member: "Response",
233 object_path: path,
234 NULL,
235 flags: G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
236 callback: response_received,
237 user_data: task,
238 NULL);
239 g_object_set_data (G_OBJECT (task), key: "signal-id", GINT_TO_POINTER (signal_id));
240 }
241}
242
243void
244g_openuri_portal_open_uri_async (const char *uri,
245 const char *parent_window,
246 GCancellable *cancellable,
247 GAsyncReadyCallback callback,
248 gpointer user_data)
249{
250 GDBusConnection *connection;
251 GTask *task;
252 GFile *file;
253 GVariant *opts = NULL;
254 int i;
255 guint signal_id;
256
257 if (!init_openuri_portal ())
258 {
259 g_task_report_new_error (NULL, callback, callback_data: user_data, NULL,
260 G_IO_ERROR, code: G_IO_ERROR_NOT_INITIALIZED,
261 format: "OpenURI portal is not available");
262 return;
263 }
264
265 connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri));
266
267 if (callback)
268 {
269 GVariantBuilder opt_builder;
270 char *token;
271 char *sender;
272 char *handle;
273
274 task = g_task_new (NULL, cancellable, callback, callback_data: user_data);
275
276 token = g_strdup_printf (format: "gio%d", g_random_int_range (begin: 0, G_MAXINT));
277 sender = g_strdup (str: g_dbus_connection_get_unique_name (connection) + 1);
278 for (i = 0; sender[i]; i++)
279 if (sender[i] == '.')
280 sender[i] = '_';
281
282 handle = g_strdup_printf (format: "/org/freedesktop/portal/desktop/request/%s/%s", sender, token);
283 g_object_set_data_full (G_OBJECT (task), key: "handle", data: handle, destroy: g_free);
284 g_free (mem: sender);
285
286 signal_id = g_dbus_connection_signal_subscribe (connection,
287 sender: "org.freedesktop.portal.Desktop",
288 interface_name: "org.freedesktop.portal.Request",
289 member: "Response",
290 object_path: handle,
291 NULL,
292 flags: G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
293 callback: response_received,
294 user_data: task,
295 NULL);
296 g_object_set_data (G_OBJECT (task), key: "signal-id", GINT_TO_POINTER (signal_id));
297
298 g_variant_builder_init (builder: &opt_builder, G_VARIANT_TYPE_VARDICT);
299 g_variant_builder_add (builder: &opt_builder, format_string: "{sv}", "handle_token", g_variant_new_string (string: token));
300 g_free (mem: token);
301
302 opts = g_variant_builder_end (builder: &opt_builder);
303 }
304 else
305 task = NULL;
306
307 file = g_file_new_for_uri (uri);
308 if (g_file_is_native (file))
309 {
310 char *path = NULL;
311 GUnixFDList *fd_list = NULL;
312 int fd, fd_id, errsv;
313
314 if (task)
315 g_object_set_data (G_OBJECT (task), key: "open-file", GINT_TO_POINTER (TRUE));
316
317 path = g_file_get_path (file);
318 fd = g_open (file: path, O_RDONLY | O_CLOEXEC);
319 errsv = errno;
320 if (fd == -1)
321 {
322 g_task_report_new_error (NULL, callback, callback_data: user_data, NULL,
323 G_IO_ERROR, code: g_io_error_from_errno (err_no: errsv),
324 format: "OpenURI portal is not available");
325 return;
326 }
327
328#ifndef HAVE_O_CLOEXEC
329 fcntl (fd, F_SETFD, FD_CLOEXEC);
330#endif
331 fd_list = g_unix_fd_list_new_from_array (fds: &fd, n_fds: 1);
332 fd = -1;
333 fd_id = 0;
334
335 gxdp_open_uri_call_open_file (openuri,
336 parent_window ? parent_window : "",
337 g_variant_new (format_string: "h", fd_id),
338 opts,
339 fd_list,
340 cancellable,
341 task ? open_call_done : NULL,
342 task);
343 g_object_unref (object: fd_list);
344 g_free (mem: path);
345 }
346 else
347 {
348 gxdp_open_uri_call_open_uri (openuri,
349 parent_window ? parent_window : "",
350 uri,
351 opts,
352 cancellable,
353 task ? open_call_done : NULL,
354 task);
355 }
356
357 g_object_unref (object: file);
358}
359
360gboolean
361g_openuri_portal_open_uri_finish (GAsyncResult *result,
362 GError **error)
363{
364 return g_task_propagate_boolean (G_TASK (result), error);
365}
366

source code of gtk/subprojects/glib/gio/gopenuriportal.c