• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
41 static GXdpOpenURI *openuri;
42 
43 static gboolean
init_openuri_portal(void)44 init_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 (G_BUS_TYPE_SESSION, NULL, &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 (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 
80 gboolean
g_openuri_portal_open_uri(const char * uri,const char * parent_window,GError ** error)81 g_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 (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
92                    "OpenURI portal is not available");
93       return FALSE;
94     }
95 
96   g_variant_builder_init (&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 (path, O_RDONLY | O_CLOEXEC);
108       errsv = errno;
109       if (fd == -1)
110         {
111           g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
112                        "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 (&fd, 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 ("h", fd_id),
126                                                g_variant_builder_end (&opt_builder),
127                                                fd_list,
128                                                NULL,
129                                                NULL,
130                                                NULL,
131                                                error);
132       g_free (path);
133       g_object_unref (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 (&opt_builder),
141                                               NULL,
142                                               NULL,
143                                               error);
144     }
145 
146   g_object_unref (file);
147 
148   return res;
149 }
150 
151 enum {
152   XDG_DESKTOP_PORTAL_SUCCESS   = 0,
153   XDG_DESKTOP_PORTAL_CANCELLED = 1,
154   XDG_DESKTOP_PORTAL_FAILED    = 2
155 };
156 
157 static void
response_received(GDBusConnection * connection,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer user_data)158 response_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, signal_id);
172 
173   g_variant_get (parameters, "(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, G_IO_ERROR_CANCELLED, "Launch cancelled");
182       break;
183     case XDG_DESKTOP_PORTAL_FAILED:
184     default:
185       g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Launch failed");
186       break;
187     }
188 
189   g_object_unref (task);
190 }
191 
192 static void
open_call_done(GObject * source,GAsyncResult * result,gpointer user_data)193 open_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 (task);
219       g_free (path);
220       return;
221     }
222 
223   handle = (const char *)g_object_get_data (G_OBJECT (task), "handle");
224   if (g_strcmp0 (handle, 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, signal_id);
228 
229       signal_id = g_dbus_connection_signal_subscribe (connection,
230                                                       "org.freedesktop.portal.Desktop",
231                                                       "org.freedesktop.portal.Request",
232                                                       "Response",
233                                                       path,
234                                                       NULL,
235                                                       G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
236                                                       response_received,
237                                                       task,
238                                                       NULL);
239       g_object_set_data (G_OBJECT (task), "signal-id", GINT_TO_POINTER (signal_id));
240     }
241 }
242 
243 void
g_openuri_portal_open_uri_async(const char * uri,const char * parent_window,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)244 g_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, user_data, NULL,
260                                G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
261                                "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, user_data);
275 
276       token = g_strdup_printf ("gio%d", g_random_int_range (0, G_MAXINT));
277       sender = g_strdup (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 ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token);
283       g_object_set_data_full (G_OBJECT (task), "handle", handle, g_free);
284       g_free (sender);
285 
286       signal_id = g_dbus_connection_signal_subscribe (connection,
287                                                       "org.freedesktop.portal.Desktop",
288                                                       "org.freedesktop.portal.Request",
289                                                       "Response",
290                                                       handle,
291                                                       NULL,
292                                                       G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
293                                                       response_received,
294                                                       task,
295                                                       NULL);
296       g_object_set_data (G_OBJECT (task), "signal-id", GINT_TO_POINTER (signal_id));
297 
298       g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
299       g_variant_builder_add (&opt_builder, "{sv}", "handle_token", g_variant_new_string (token));
300       g_free (token);
301 
302       opts = g_variant_builder_end (&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), "open-file", GINT_TO_POINTER (TRUE));
316 
317       path = g_file_get_path (file);
318       fd = g_open (path, O_RDONLY | O_CLOEXEC);
319       errsv = errno;
320       if (fd == -1)
321         {
322           g_task_report_new_error (NULL, callback, user_data, NULL,
323                                    G_IO_ERROR, g_io_error_from_errno (errsv),
324                                    "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 (&fd, 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 ("h", fd_id),
338                                     opts,
339                                     fd_list,
340                                     cancellable,
341                                     task ? open_call_done : NULL,
342                                     task);
343       g_object_unref (fd_list);
344       g_free (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 (file);
358 }
359 
360 gboolean
g_openuri_portal_open_uri_finish(GAsyncResult * result,GError ** error)361 g_openuri_portal_open_uri_finish (GAsyncResult  *result,
362                                   GError       **error)
363 {
364   return g_task_propagate_boolean (G_TASK (result), error);
365 }
366