From dc28fb9c3dbe3e22025f2caba1f20bf85a42c4a2 Mon Sep 17 00:00:00 2001
From: Ludovico de Nittis <ludovico.denittis@collabora.com>
Date: Thu, 8 Oct 2020 17:02:16 +0200
Subject: [PATCH] check-xdg-portal: Add helper to check xdg-desktop-portal

With this helper we are able to check if `xdg-desktop-portal` is
currently installed and working. It also checks if there is at least a
portal backend implementation.

It returns 0 if `xdg-desktop-portal` is available and there is at least
a backend implementation.

Signed-off-by: Ludovico de Nittis <ludovico.denittis@collabora.com>
---
 helpers/check-xdg-portal.c | 255 +++++++++++++++++++++++++++++++++++++
 helpers/meson.build        |  13 ++
 2 files changed, 268 insertions(+)
 create mode 100644 helpers/check-xdg-portal.c

diff --git a/helpers/check-xdg-portal.c b/helpers/check-xdg-portal.c
new file mode 100644
index 000000000..f0869c7f2
--- /dev/null
+++ b/helpers/check-xdg-portal.c
@@ -0,0 +1,255 @@
+/*
+ * Copyright © 2020 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <json-glib/json-glib.h>
+
+#include <steam-runtime-tools/glib-backports-internal.h>
+#include <steam-runtime-tools/json-glib-compat.h>
+#include <steam-runtime-tools/utils-internal.h>
+
+#if !GLIB_CHECK_VERSION(2, 43, 4)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GDBusProxy, g_object_unref)
+#endif
+
+static gboolean opt_print_version = FALSE;
+
+static const GOptionEntry option_entries[] =
+{
+  { "version", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_print_version,
+    "Print version number and exit", NULL },
+  { NULL }
+};
+
+int
+main (int argc,
+      char **argv)
+{
+  g_autofree gchar *json = NULL;
+  g_autoptr(FILE) original_stdout = NULL;
+  g_autoptr(JsonNode) root = NULL;
+  g_autoptr(JsonBuilder) builder = NULL;
+  g_autoptr(JsonGenerator) generator = NULL;
+  g_autoptr(GDBusConnection) connection = NULL;
+  g_autoptr(GVariant) variant_version = NULL;
+  g_autoptr(GError) local_error = NULL;
+  g_autoptr(GOptionContext) option_context = NULL;
+  guint32 version;
+  GError **error = &local_error;
+  gboolean impl_available = FALSE;
+  int ret = EXIT_SUCCESS;
+  gsize i;
+
+  static const gchar * const portal_interface_name[] = {
+    "org.freedesktop.portal.OpenURI",
+    "org.freedesktop.portal.Email",
+    NULL,
+  };
+
+  static const gchar * const portal_impl_name[] = {
+    "org.freedesktop.impl.portal.desktop.gtk",
+    "org.freedesktop.impl.portal.desktop.kde",
+    NULL,
+  };
+
+  option_context = g_option_context_new ("");
+  g_option_context_add_main_entries (option_context, option_entries, NULL);
+
+  if (!g_option_context_parse (option_context, &argc, &argv, &local_error))
+    return 2;
+
+  if (opt_print_version)
+    {
+      /* Output version number as YAML for machine-readability,
+       * inspired by `ostree --version` and `docker version` */
+      g_print (
+          "%s:\n"
+          " Package: steam-runtime-tools\n"
+          " Version: %s\n",
+          argv[0], VERSION);
+      return EXIT_SUCCESS;
+    }
+
+  /* stdout is reserved for machine-readable output, so avoid having
+   * things like g_debug() pollute it. */
+  original_stdout = _srt_divert_stdout_to_stderr (error);
+
+  if (original_stdout == NULL)
+    {
+      g_printerr ("Unable to divert stdout to stderr: %s", local_error->message);
+      return EXIT_FAILURE;
+    }
+
+  builder = json_builder_new ();
+  json_builder_begin_object (builder);
+
+  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+  if (connection == NULL)
+    {
+      g_printerr ("Unable to get the session bus: %s\n", local_error->message);
+      return EXIT_FAILURE;
+    }
+
+  json_builder_set_member_name (builder, "interfaces");
+  json_builder_begin_object (builder);
+  for (i = 0; portal_interface_name[i] != NULL; i++)
+    {
+      g_autoptr(GDBusProxy) portal_proxy = NULL;
+      g_autofree gchar *name_owner = NULL;
+      portal_proxy = g_dbus_proxy_new_sync (connection,
+                                            G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+                                            NULL,
+                                            "org.freedesktop.portal.Desktop",
+                                            "/org/freedesktop/portal/desktop",
+                                            portal_interface_name[i],
+                                            NULL,
+                                            error);
+
+      json_builder_set_member_name (builder, portal_interface_name[i]);
+      json_builder_begin_object (builder);
+      json_builder_set_member_name (builder, "available");
+
+      if (portal_proxy == NULL)
+        {
+          g_printerr ("Failed to contact 'org.freedesktop.portal.Desktop': %s\n",
+                      local_error->message);
+          json_builder_add_boolean_value (builder, FALSE);
+          json_builder_end_object (builder);
+          ret = EXIT_FAILURE;
+          continue;
+        }
+
+      name_owner = g_dbus_proxy_get_name_owner (portal_proxy);
+      if (name_owner == NULL)
+        {
+          g_printerr ("Failed to connect to 'org.freedesktop.portal.Desktop'\n");
+          json_builder_add_boolean_value (builder, FALSE);
+          json_builder_end_object (builder);
+          ret = EXIT_FAILURE;
+          continue;
+        }
+
+      variant_version = g_dbus_proxy_get_cached_property (portal_proxy, "version");
+
+      if (variant_version == NULL
+          || g_variant_classify (variant_version) != G_VARIANT_CLASS_UINT32)
+        {
+          g_printerr ("The 'version' property is not available for '%s', "
+                      "either there isn't a working xdg-desktop-portal or "
+                      "it is a very old version\n", portal_interface_name[i]);
+
+          json_builder_add_boolean_value (builder, FALSE);
+          ret = EXIT_FAILURE;
+        }
+      else
+        {
+          json_builder_add_boolean_value (builder, TRUE);
+          json_builder_set_member_name (builder, "version");
+          version = g_variant_get_uint32 (variant_version);
+          json_builder_add_int_value (builder, version);
+        }
+      json_builder_end_object (builder);
+    }
+  json_builder_end_object (builder);
+
+  /* If we are in a Flatpak container we are not allowed to contact the
+   * portals implementations. So we just skip this test */
+  if (!g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR))
+    {
+      json_builder_set_member_name (builder, "backends");
+      json_builder_begin_object (builder);
+      for (i = 0; portal_impl_name[i] != NULL; i++)
+        {
+          g_autoptr(GDBusProxy) portal_impl_proxy = NULL;
+          g_autofree gchar *impl_name_owner = NULL;
+          portal_impl_proxy = g_dbus_proxy_new_sync (connection,
+                                                     (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+                                                      G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
+                                                     NULL,
+                                                     portal_impl_name[i],
+                                                     "/org/freedesktop/portal/desktop",
+                                                     "org.freedesktop.DBus.Peer",
+                                                     NULL,
+                                                     error);
+
+          json_builder_set_member_name (builder, portal_impl_name[i]);
+          json_builder_begin_object (builder);
+          json_builder_set_member_name (builder, "available");
+
+          if (portal_impl_proxy == NULL)
+            {
+              g_debug ("Failed to contact '%s': %s\n",
+                       portal_impl_name[i], local_error->message);
+              g_clear_error (error);
+              json_builder_add_boolean_value (builder, FALSE);
+              json_builder_end_object (builder);
+              continue;
+            }
+
+          impl_name_owner = g_dbus_proxy_get_name_owner (portal_impl_proxy);
+          if (impl_name_owner == NULL)
+            {
+              g_debug ("Failed to connect to '%s'\n", portal_impl_name[i]);
+              json_builder_add_boolean_value (builder, FALSE);
+            }
+          else
+            {
+              impl_available = TRUE;
+              json_builder_add_boolean_value (builder, TRUE);
+            }
+
+          json_builder_end_object (builder);
+        }
+      json_builder_end_object (builder);
+
+      /* We just need a single portal implementation to be available */
+      if (!impl_available)
+        {
+          g_printerr ("There isn't a working portal implementation\n");
+          ret = EXIT_FAILURE;
+        }
+    }
+
+  json_builder_end_object (builder);
+
+  root = json_builder_get_root (builder);
+  generator = json_generator_new ();
+  json_generator_set_pretty (generator, TRUE);
+  json_generator_set_root (generator, root);
+  json = json_generator_to_data (generator, NULL);
+  if (fputs (json, original_stdout) < 0)
+    g_warning ("Unable to write output: %s", g_strerror (errno));
+
+  if (fputs ("\n", original_stdout) < 0)
+    g_warning ("Unable to write final newline: %s", g_strerror (errno));
+
+  return ret;
+}
diff --git a/helpers/meson.build b/helpers/meson.build
index be8331aa4..b832a20e9 100644
--- a/helpers/meson.build
+++ b/helpers/meson.build
@@ -125,4 +125,17 @@ executable(
   )
 )
 
+executable(
+  multiarch + '-check-xdg-portal',
+  'check-xdg-portal.c',
+  include_directories : project_include_dirs,
+  dependencies : [glib, gio_unix, libglnx_dep, libsteamrt_dep, json_glib],
+  install : true,
+  install_dir : pkglibexecdir,
+  # Use the adjacent libsteam-runtime-tools and json-glib, ignoring
+  # LD_LIBRARY_PATH even if set
+  build_rpath : pkglibexec_rpath,
+  install_rpath : pkglibexec_rpath,
+)
+
 # vim:set sw=2 sts=2 et:
-- 
GitLab