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