text: /* GIO - GLib Input, Output and Streaming Library
   *
   * Copyright 2017 Red Hat, Inc.
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, see <http://www.gnu.org/licenses/>.
   */
  
  #include "config.h"
  
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <string.h>
  
  #include "gopenuriportal.h"
  #include "xdp-dbus.h"
  #include "gstdio.h"
  
  #ifdef G_OS_UNIX
  #include "gunixfdlist.h"
  #endif
  
  #ifndef O_CLOEXEC
  #define O_CLOEXEC 0
  #else
  #define HAVE_O_CLOEXEC 1
  #endif
  
  
  static GXdpOpenURI *openuri;
  
  static gboolean
  init_openuri_portal (void)
  {
    static gsize openuri_inited = 0;
  
    if (g_once_init_enter (&openuri_inited))
      {
        GError *error = NULL;
        GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
  
        if (connection != NULL)
          {
            openuri = gxdp_open_uri_proxy_new_sync (connection, 0,
                                                    "org.freedesktop.portal.Desktop",
                                                    "/org/freedesktop/portal/desktop",
                                                    NULL, &error);
            if (openuri == NULL)
              {
                g_warning ("Cannot create document portal proxy: %s", error->message);
                g_error_free (error);
              }
  
            g_object_unref (connection);
          }
        else
          {
            g_warning ("Cannot connect to session bus when initializing document portal: %s",
                       error->message);
            g_error_free (error);
          }
  
        g_once_init_leave (&openuri_inited, 1);
      }
  
    return openuri != NULL;
  }
  
  gboolean
  g_openuri_portal_open_uri (const char  *uri,
                             const char  *parent_window,
                             GError     **error)
  {
    GFile *file = NULL;
    GVariantBuilder opt_builder;
    gboolean res;
  
    if (!init_openuri_portal ())
      {
        g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
                     "OpenURI portal is not available");
        return FALSE;
      }
  
    g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
  
    file = g_file_new_for_uri (uri);
    if (g_file_is_native (file))
      {
        char *path = NULL;
        GUnixFDList *fd_list = NULL;
        int fd, fd_id, errsv;
  
        path = g_file_get_path (file);
  
        fd = g_open (path, O_RDONLY | O_CLOEXEC);
        errsv = errno;
        if (fd == -1)
          {
            g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
                         "Failed to open '%s'", path);
            return FALSE;
          }
  
  #ifndef HAVE_O_CLOEXEC
        fcntl (fd, F_SETFD, FD_CLOEXEC);
  #endif
        fd_list = g_unix_fd_list_new_from_array (&fd, 1);
        fd = -1;
        fd_id = 0;
  
        res = gxdp_open_uri_call_open_file_sync (openuri,
                                                 parent_window ? parent_window : "",
                                                 g_variant_new ("h", fd_id),
                                                 g_variant_builder_end (&opt_builder),
                                                 fd_list,
                                                 NULL,
                                                 NULL,
                                                 NULL,
                                                 error);
        g_free (path);
        g_object_unref (fd_list);
      }
    else
      {
        res = gxdp_open_uri_call_open_uri_sync (openuri,
                                                parent_window ? parent_window : "",
                                                uri,
                                                g_variant_builder_end (&opt_builder),
                                                NULL,
                                                NULL,
                                                error);
      }
  
    g_object_unref (file);
  
    return res;
  }
  
  enum {
    XDG_DESKTOP_PORTAL_SUCCESS   = 0,
    XDG_DESKTOP_PORTAL_CANCELLED = 1,
    XDG_DESKTOP_PORTAL_FAILED    = 2
  };
  
  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)
  {
    GTask *task = user_data;
    guint32 response;
    guint signal_id;
  
    signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
    g_dbus_connection_signal_unsubscribe (connection, signal_id);
  
    g_variant_get (parameters, "(u@a{sv})", &response, NULL);
  
    switch (response)
      {
      case XDG_DESKTOP_PORTAL_SUCCESS:
        g_task_return_boolean (task, TRUE);
        break;
      case XDG_DESKTOP_PORTAL_CANCELLED:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Launch cancelled");
        break;
      case XDG_DESKTOP_PORTAL_FAILED:
      default:
        g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Launch failed");
        break;
      }
  
    g_object_unref (task);
  }
  
  static void
  open_call_done (GObject      *source,
                  GAsyncResult *result,
                  gpointer      user_data)
  {
    GXdpOpenURI *openuri = GXDP_OPEN_URI (source);
    GDBusConnection *connection;
    GTask *task = user_data;
    GError *error = NULL;
    gboolean open_file;
    gboolean res;
    char *path = NULL;
    const char *handle;
    guint signal_id;
  
    connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri));
    open_file = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "open-file"));
  
    if (open_file)
      res = gxdp_open_uri_call_open_file_finish (openuri, &path, NULL, result, &error);
    else
      res = gxdp_open_uri_call_open_uri_finish (openuri, &path, result, &error);
  
    if (!res)
      {
        g_task_return_error (task, error);
        g_object_unref (task);
        g_free (path);
        return;
      }
  
    handle = (const char *)g_object_get_data (G_OBJECT (task), "handle");
    if (g_strcmp0 (handle, path) != 0)
      {
        signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
        g_dbus_connection_signal_unsubscribe (connection, signal_id);
  
        signal_id = g_dbus_connection_signal_subscribe (connection,
                                                        "org.freedesktop.portal.Desktop",
                                                        "org.freedesktop.portal.Request",
                                                        "Response",
                                                        path,
                                                        NULL,
                                                        G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
                                                        response_received,
                                                        task,
                                                        NULL);
        g_object_set_data (G_OBJECT (task), "signal-id", GINT_TO_POINTER (signal_id));
      }
  }
  
  void
  g_openuri_portal_open_uri_async (const char          *uri,
                                   const char          *parent_window,
                                   GCancellable        *cancellable,
                                   GAsyncReadyCallback  callback,
                                   gpointer             user_data)
  {
    GDBusConnection *connection;
    GTask *task;
    GFile *file;
    GVariant *opts = NULL;
    int i;
    guint signal_id;
  
    if (!init_openuri_portal ())
      {
        g_task_report_new_error (NULL, callback, user_data, NULL,
                                 G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
                                 "OpenURI portal is not available");
        return;
      }
  
    connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri));
  
    if (callback)
      {
        GVariantBuilder opt_builder;
        char *token;
        char *sender;
        char *handle;
  
        task = g_task_new (NULL, cancellable, callback, user_data);
  
        token = g_strdup_printf ("gio%d", g_random_int_range (0, G_MAXINT));
        sender = g_strdup (g_dbus_connection_get_unique_name (connection) + 1);
        for (i = 0; sender[i]; i++)
          if (sender[i] == '.')
            sender[i] = '_';
  
        handle = g_strdup_printf ("/org/fredesktop/portal/desktop/request/%s/%s", sender, token);
        g_object_set_data_full (G_OBJECT (task), "handle", handle, g_free);
        g_free (sender);
  
        signal_id = g_dbus_connection_signal_subscribe (connection,
                                                        "org.freedesktop.portal.Desktop",
                                                        "org.freedesktop.portal.Request",
                                                        "Response",
                                                        handle,
                                                        NULL,
                                                        G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
                                                        response_received,
                                                        task,
                                                        NULL);
        g_object_set_data (G_OBJECT (task), "signal-id", GINT_TO_POINTER (signal_id));
  
        g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
        g_variant_builder_add (&opt_builder, "{sv}", "handle_token", g_variant_new_string (token));
        g_free (token);
  
        opts = g_variant_builder_end (&opt_builder);
      }
    else
      task = NULL;
  
    file = g_file_new_for_uri (uri);
    if (g_file_is_native (file))
      {
        char *path = NULL;
        GUnixFDList *fd_list = NULL;
        int fd, fd_id, errsv;
  
        if (task)
          g_object_set_data (G_OBJECT (task), "open-file", GINT_TO_POINTER (TRUE));
  
        path = g_file_get_path (file);
        fd = g_open (path, O_RDONLY | O_CLOEXEC);
        errsv = errno;
        if (fd == -1)
          {
            g_task_report_new_error (NULL, callback, user_data, NULL,
                                     G_IO_ERROR, g_io_error_from_errno (errsv),
                                     "OpenURI portal is not available");
            return;
          }
  
  #ifndef HAVE_O_CLOEXEC
        fcntl (fd, F_SETFD, FD_CLOEXEC);
  #endif
        fd_list = g_unix_fd_list_new_from_array (&fd, 1);
        fd = -1;
        fd_id = 0;
  
        gxdp_open_uri_call_open_file (openuri,
                                      parent_window ? parent_window : "",
                                      g_variant_new ("h", fd_id),
                                      opts,
                                      fd_list,
                                      cancellable,
                                      task ? open_call_done : NULL,
                                      task);
        g_object_unref (fd_list);
        g_free (path);
      }
    else
      {
        gxdp_open_uri_call_open_uri (openuri,
                                     parent_window ? parent_window : "",
                                     uri,
                                     opts,
                                     cancellable,
                                     task ? open_call_done : NULL,
                                     task);
      }
  
    g_object_unref (file);
  }
  
  gboolean
  g_openuri_portal_open_uri_finish (GAsyncResult  *result,
                                    GError       **error)
  {
    return g_task_propagate_boolean (G_TASK (result), error);
  }
  