Skip to content
Snippets Groups Projects
check-xdg-portal.c 9.06 KiB
/*
 * 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-backports-internal.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;
}