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