diff --git a/bin/system-info.c b/bin/system-info.c index 18c922f6be26cccf4c12b38a70df27dc8ef843e4..02f4ce0d1d0d1979746e4b16999885a52fac68e2 100644 --- a/bin/system-info.c +++ b/bin/system-info.c @@ -323,6 +323,13 @@ jsonify_locale_issues (JsonBuilder *builder, jsonify_flags (builder, SRT_TYPE_LOCALE_ISSUES, issues); } +static void +jsonify_xdg_portal_issues (JsonBuilder *builder, + SrtXdgPortalIssues issues) +{ + jsonify_flags (builder, SRT_TYPE_XDG_PORTAL_ISSUES, issues); +} + static void jsonify_x86_features (JsonBuilder *builder, SrtX86FeatureFlags present, @@ -781,8 +788,11 @@ main (int argc, SrtSteamIssues steam_issues = SRT_STEAM_ISSUES_NONE; SrtRuntimeIssues runtime_issues = SRT_RUNTIME_ISSUES_NONE; SrtLocaleIssues locale_issues = SRT_LOCALE_ISSUES_NONE; + SrtXdgPortalIssues xdg_portal_issues = SRT_XDG_PORTAL_ISSUES_NONE; SrtX86FeatureFlags x86_features = SRT_X86_FEATURE_NONE; SrtX86FeatureFlags known_x86_features = SRT_X86_FEATURE_NONE; + g_autoptr(SrtObjectList) portal_backends = NULL; + g_autoptr(SrtObjectList) portal_interfaces = NULL; char *expectations = NULL; gboolean verbose = FALSE; JsonBuilder *builder; @@ -791,6 +801,7 @@ main (int argc, const gchar *test_json_path = NULL; g_autofree gchar *steamscript_path = NULL; g_autofree gchar *steamscript_version = NULL; + g_autofree gchar *xdg_portal_messages = NULL; gchar *json_output; gchar *version = NULL; gchar *inst_path = NULL; @@ -1285,6 +1296,69 @@ main (int argc, } json_builder_end_array (builder); + json_builder_set_member_name (builder, "xdg-portals"); + json_builder_begin_object (builder); + { + portal_interfaces = srt_system_info_list_xdg_portal_interfaces (info); + portal_backends = srt_system_info_list_xdg_portal_backends (info); + if (portal_interfaces != NULL || portal_backends != NULL) + { + json_builder_set_member_name (builder, "details"); + json_builder_begin_object (builder); + + if (portal_interfaces != NULL) + { + json_builder_set_member_name (builder, "interfaces"); + json_builder_begin_object (builder); + for (const GList *iter = portal_interfaces; iter != NULL; iter = iter->next) + { + gboolean is_available; + json_builder_set_member_name (builder, srt_xdg_portal_interface_get_name (iter->data)); + json_builder_begin_object (builder); + json_builder_set_member_name (builder, "available"); + is_available = srt_xdg_portal_interface_is_available (iter->data); + json_builder_add_boolean_value (builder, is_available); + if (is_available) + { + json_builder_set_member_name (builder, "version"); + json_builder_add_int_value (builder, srt_xdg_portal_interface_get_version (iter->data)); + } + json_builder_end_object (builder); + } + json_builder_end_object (builder); + } + + if (portal_backends != NULL) + { + json_builder_set_member_name (builder, "backends"); + json_builder_begin_object (builder); + for (const GList *iter = portal_backends; iter != NULL; iter = iter->next) + { + json_builder_set_member_name (builder, srt_xdg_portal_backend_get_name (iter->data)); + json_builder_begin_object (builder); + json_builder_set_member_name (builder, "available"); + json_builder_add_boolean_value (builder, srt_xdg_portal_backend_is_available (iter->data)); + json_builder_end_object (builder); + } + json_builder_end_object (builder); + } + json_builder_end_object (builder); + } + + json_builder_set_member_name (builder, "issues"); + json_builder_begin_array (builder); + xdg_portal_issues = srt_system_info_get_xdg_portal_issues (info, &xdg_portal_messages); + jsonify_xdg_portal_issues (builder, xdg_portal_issues); + json_builder_end_array (builder); + + if (xdg_portal_messages != NULL) + { + json_builder_set_member_name (builder, "messages"); + json_builder_add_string_value (builder, xdg_portal_messages); + } + } + json_builder_end_object (builder); + json_builder_set_member_name (builder, "cpu-features"); json_builder_begin_object (builder); { diff --git a/bin/system-info.md b/bin/system-info.md index 25b896cc0d97d3a070ce20801344553c0e835886..95aa8d9c2d398ad5f2278c898d21b462ae7c2aed 100644 --- a/bin/system-info.md +++ b/bin/system-info.md @@ -681,6 +681,55 @@ keys: **steam_uri_handler** : A boolean value indicating whether this entry can open `steam:` URIs. +**xdg-portals** +: An object describing the XDG portal support. Whether `xdg-desktop-portal` + is installed, and working, and which portal backend implementations are + available. The keys are the strings: + + **details** + : An object describing in details which XDG portals have been checked. + The keys are the strings: + + **interfaces** + : An object describing XDG portal interfaces. The keys are the + interface name. The values are objects containing details of + of an interface: + + **available** + : A boolean value indicating whether this interface is available. + + **version** + : An integer representing the version property of this interface. + + **backends** + : An object describing XDG portal backends. The keys are the + backend name. The values are objects containing details of + of an interface: + + **available** + : A boolean value indicating whether this backend is available. + + **issues** + : Problems with the XDG portal support, represented as an array + of strings. The array is empty if no problems were detected. + + **unknown** + : There was an unknown error while checking XDG portal support. + + **timeout** + : The test to check the XDG portal support timed out. + + **missing-interface** + : At least one of the required XDG portal interfaces is missing. + + **no-implementation** + : There isn't a working XDG portal implementation. + + **messages** + : Human-readable string with error messages about the XDG portal + support. This element will not be printed if there aren't any error + messages. + **cpu-features** : An object decribing some of the features that the CPU in use supports. Currently it has the following string keys, each with a boolean diff --git a/helpers/check-xdg-portal.c b/helpers/check-xdg-portal.c new file mode 100644 index 0000000000000000000000000000000000000000..f0869c7f22a446574b8aad50b6d9611f7b9934c6 --- /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 be8331aa4ca80d52fc169ab7752308901a1a01c8..b832a20e9be9252f728c12cc229fdfe2cbed3292 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: diff --git a/steam-runtime-tools/meson.build b/steam-runtime-tools/meson.build index 7be80764574a2f8de0c227e6d6e440a136436aad..e710cc5e2b2b8348a9bcba44aeef03a66b9d84ed 100644 --- a/steam-runtime-tools/meson.build +++ b/steam-runtime-tools/meson.build @@ -47,6 +47,7 @@ libsteamrt_sources = [ 'system-info.c', 'utils-internal.h', 'utils.c', + 'xdg-portal.c', ] libsteamrt_public_headers = [ @@ -62,6 +63,7 @@ libsteamrt_public_headers = [ 'steam.h', 'system-info.h', 'utils.h', + 'xdg-portal.h', ] enums = gnome.mkenums_simple( diff --git a/steam-runtime-tools/steam-runtime-tools.h b/steam-runtime-tools/steam-runtime-tools.h index 244fe89558ba69ddc502f35dd061f97d25dd0788..29e02af44daeb3d28bf12958e8d6a828e884c2c3 100644 --- a/steam-runtime-tools/steam-runtime-tools.h +++ b/steam-runtime-tools/steam-runtime-tools.h @@ -39,5 +39,6 @@ #include <steam-runtime-tools/steam.h> #include <steam-runtime-tools/system-info.h> #include <steam-runtime-tools/utils.h> +#include <steam-runtime-tools/xdg-portal.h> #undef _SRT_IN_SINGLE_HEADER diff --git a/steam-runtime-tools/system-info.c b/steam-runtime-tools/system-info.c index a9996d41f82c5477a64fcf2d929681fc48991a49..346846e05ba31694802325633d1088c96b77afdb 100644 --- a/steam-runtime-tools/system-info.c +++ b/steam-runtime-tools/system-info.c @@ -25,11 +25,14 @@ #include "steam-runtime-tools/system-info.h" +/* Include this at the beginning so that every backports of + * G_DEFINE_AUTOPTR_CLEANUP_FUNC will be visible */ +#include "steam-runtime-tools/glib-backports-internal.h" + #include "steam-runtime-tools/architecture.h" #include "steam-runtime-tools/architecture-internal.h" #include "steam-runtime-tools/cpu-feature-internal.h" #include "steam-runtime-tools/desktop-entry-internal.h" -#include "steam-runtime-tools/glib-backports-internal.h" #include "steam-runtime-tools/graphics.h" #include "steam-runtime-tools/graphics-internal.h" #include "steam-runtime-tools/library-internal.h" @@ -39,6 +42,7 @@ #include "steam-runtime-tools/runtime-internal.h" #include "steam-runtime-tools/steam-internal.h" #include "steam-runtime-tools/utils-internal.h" +#include "steam-runtime-tools/xdg-portal-internal.h" #include <errno.h> #include <fcntl.h> @@ -110,6 +114,7 @@ struct _SrtSystemInfo gboolean immutable_values; GHashTable *cached_hidden_deps; SrtSteam *steam_data; + SrtXdgPortal *xdg_portal_data; struct { /* GQuark => MaybeLocale */ @@ -467,6 +472,15 @@ forget_pinned_libs (SrtSystemInfo *self) self->pinned_libs.have_data = FALSE; } +/* + * Forget any cached information about xdg portals. + */ +static void +forget_xdg_portal (SrtSystemInfo *self) +{ + g_clear_object (&self->xdg_portal_data); +} + static void srt_system_info_finalize (GObject *object) { @@ -481,6 +495,7 @@ srt_system_info_finalize (GObject *object) forget_pinned_libs (self); forget_runtime (self); forget_steam (self); + forget_xdg_portal (self); g_clear_pointer (&self->abis, g_ptr_array_unref); g_free (self->expectations); @@ -733,6 +748,8 @@ srt_system_info_new_from_json (const char *path, info->desktop_entry.have_data = TRUE; info->desktop_entry.values = _srt_get_steam_desktop_entries_from_json_report (json_obj); + info->xdg_portal_data = _srt_xdg_portal_get_info_from_report (json_obj); + info->cpu_features.x86_features = _srt_feature_get_x86_flags_from_report (json_obj, &info->cpu_features.x86_known); @@ -1885,6 +1902,7 @@ srt_system_info_set_environ (SrtSystemInfo *self, forget_graphics_results (self); forget_locales (self); forget_pinned_libs (self); + forget_xdg_portal (self); g_clear_pointer (&self->cached_driver_environment, g_strfreev); g_strfreev (self->env); @@ -3586,3 +3604,106 @@ srt_system_info_dup_steamscript_version (SrtSystemInfo *self) ensure_steam_cached (self); return g_strdup (srt_steam_get_steamscript_version (self->steam_data)); } + +static void +ensure_xdg_portals_cached (SrtSystemInfo *self) +{ + g_return_if_fail (SRT_IS_SYSTEM_INFO (self)); + + if (self->xdg_portal_data == NULL) + { + ensure_container_info (self); + _srt_check_xdg_portals (self->env, + self->helpers_path, + self->test_flags, + self->container.type, + srt_system_info_get_primary_multiarch_tuple (self), + &self->xdg_portal_data); + } +} + +/** + * srt_system_info_list_xdg_portal_backends: + * @self: The #SrtSystemInfo object + * + * List the XDG portal backends that have been checked, with information about + * their eventual availability. + * + * Examples of XDG portal backends are "org.freedesktop.impl.portal.desktop.gtk" + * and "org.freedesktop.impl.portal.desktop.kde". + * + * The returned list is in the same arbitrary order as the check-xdg-portal + * helper used to return them. + * + * Returns: (transfer full) (element-type SrtXdgPortalBackend) (nullable): + * A list of opaque #SrtXdgPortalBackend objects or %NULL if an error + * occurred trying to check the backends availability or if we were unable to + * check them, e.g. if we are in a Flatpak environment. + * Free with `g_list_free_full (backends, g_object_unref)`. + */ +GList * +srt_system_info_list_xdg_portal_backends (SrtSystemInfo *self) +{ + g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), NULL); + + if (!self->immutable_values) + ensure_xdg_portals_cached (self); + + return srt_xdg_portal_get_backends (self->xdg_portal_data); +} + +/** + * srt_system_info_list_xdg_portal_interfaces: + * @self: The #SrtSystemInfo object + * + * List the XDG portal interfaces that have been checked, with information + * about their eventual availability and version property. + * + * Examples of XDG portal interfaces are "org.freedesktop.portal.OpenURI" + * and "org.freedesktop.portal.Email". + * + * The returned list is in the same arbitrary order as the check-xdg-portal + * helper used to return them. + * + * Returns: (transfer full) (element-type SrtXdgPortalInterface) (nullable): + * A list of opaque #SrtXdgPortalInterface objects or %NULL if an error + * occurred trying to check the interfaces availability. Free with + * `g_list_free_full (interfaces, g_object_unref)`. + */ +GList * +srt_system_info_list_xdg_portal_interfaces (SrtSystemInfo *self) +{ + g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), NULL); + + if (!self->immutable_values) + ensure_xdg_portals_cached (self); + + return srt_xdg_portal_get_interfaces (self->xdg_portal_data); +} + +/** + * srt_system_info_get_xdg_portal_issues: + * @self: The #SrtSystemInfo object + * @messages: (optional) (out): If not %NULL, used to return the diagnostic + * messages. Free with g_free(). + * + * Check if the current system supports the XDG portals. + * + * Returns: A bitfield containing problems, or %SRT_XDG_PORTAL_ISSUES_NONE + * if no problems were found. + */ +SrtXdgPortalIssues +srt_system_info_get_xdg_portal_issues (SrtSystemInfo *self, + gchar **messages) +{ + g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), SRT_XDG_PORTAL_ISSUES_UNKNOWN); + g_return_val_if_fail (messages == NULL || *messages == NULL, SRT_XDG_PORTAL_ISSUES_UNKNOWN); + + if (!self->immutable_values) + ensure_xdg_portals_cached (self); + + if (messages != NULL) + *messages = g_strdup (srt_xdg_portal_get_messages (self->xdg_portal_data)); + + return srt_xdg_portal_get_issues (self->xdg_portal_data); +} diff --git a/steam-runtime-tools/system-info.h b/steam-runtime-tools/system-info.h index 0c064004c3eab15f1855b3d0b26f3bd456080b0d..654759f89cc66eedbd407c2833657abe5fa754e5 100644 --- a/steam-runtime-tools/system-info.h +++ b/steam-runtime-tools/system-info.h @@ -39,6 +39,7 @@ #include <steam-runtime-tools/macros.h> #include <steam-runtime-tools/runtime.h> #include <steam-runtime-tools/steam.h> +#include <steam-runtime-tools/xdg-portal.h> /** * SrtTestFlags: @@ -262,6 +263,14 @@ gchar *srt_system_info_dup_steamscript_path (SrtSystemInfo *self); _SRT_PUBLIC gchar *srt_system_info_dup_steamscript_version (SrtSystemInfo *self); +_SRT_PUBLIC +GList *srt_system_info_list_xdg_portal_backends (SrtSystemInfo *self); +_SRT_PUBLIC +GList *srt_system_info_list_xdg_portal_interfaces (SrtSystemInfo *self); +_SRT_PUBLIC +SrtXdgPortalIssues srt_system_info_get_xdg_portal_issues (SrtSystemInfo *self, + gchar **messages); + #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtSystemInfo, g_object_unref) #endif diff --git a/steam-runtime-tools/xdg-portal-internal.h b/steam-runtime-tools/xdg-portal-internal.h new file mode 100644 index 0000000000000000000000000000000000000000..3eeddcfa5ba98de74696a7171ed9bcd661f7db3d --- /dev/null +++ b/steam-runtime-tools/xdg-portal-internal.h @@ -0,0 +1,126 @@ +/*< internal_header >*/ +/* + * 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. + */ + +#pragma once + +#include "steam-runtime-tools/steam-runtime-tools.h" + +#include <json-glib/json-glib.h> + +#ifndef __GTK_DOC_IGNORE__ +static inline SrtXdgPortalInterface *_srt_xdg_portal_interface_new (const char *name, + gboolean is_available, + guint version); + +static inline SrtXdgPortalInterface * +_srt_xdg_portal_interface_new (const char *name, + gboolean is_available, + guint version) +{ + g_return_val_if_fail (name != NULL, NULL); + + return g_object_new (SRT_TYPE_XDG_PORTAL_INTERFACE, + "name", name, + "is-available", is_available, + "version", version, + NULL); +} + +static inline SrtXdgPortalBackend *_srt_xdg_portal_backend_new (const char *name, + gboolean is_available); + +static inline SrtXdgPortalBackend * +_srt_xdg_portal_backend_new (const char *name, + gboolean is_available) +{ + g_return_val_if_fail (name != NULL, NULL); + + return g_object_new (SRT_TYPE_XDG_PORTAL_BACKEND, + "name", name, + "is-available", is_available, + NULL); +} + +typedef struct _SrtXdgPortal SrtXdgPortal; +typedef struct _SrtXdgPortalClass SrtXdgPortalClass; + +#define SRT_TYPE_XDG_PORTAL srt_xdg_portal_get_type () +#define SRT_XDG_PORTAL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SRT_TYPE_XDG_PORTAL, SrtXdgPortal)) +#define SRT_XDG_PORTAL_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), SRT_TYPE_XDG_PORTAL, SrtXdgPortalClass)) +#define SRT_IS_XDG_PORTAL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SRT_TYPE_XDG_PORTAL)) +#define SRT_IS_XDG_PORTAL_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE ((cls), SRT_TYPE_XDG_PORTAL)) +#define SRT_XDG_PORTAL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SRT_TYPE_XDG_PORTAL, SrtXdgPortalClass) + +G_GNUC_INTERNAL +GType srt_xdg_portal_get_type (void); + +/* + * @backends: (transfer none) (element-type SrtXdgPortalBackend): + * @interfaces: (transfer none) (element-type SrtXdgPortalInterface): + */ +static inline SrtXdgPortal *_srt_xdg_portal_new (const char *messages, + SrtXdgPortalIssues issues, + GPtrArray *backends, + GPtrArray *interfaces); + +static inline SrtXdgPortal * +_srt_xdg_portal_new (const char *messages, + SrtXdgPortalIssues issues, + GPtrArray *backends, + GPtrArray *interfaces) +{ + return g_object_new (SRT_TYPE_XDG_PORTAL, + "messages", messages, + "issues", issues, + "backends", backends, + "interfaces", interfaces, + NULL); +} + +G_GNUC_INTERNAL +SrtXdgPortalIssues _srt_check_xdg_portals (gchar **envp, + const char *helpers_path, + SrtTestFlags test_flags, + SrtContainerType container_type, + const char *multiarch_tuple, + SrtXdgPortal **details_out); +#endif + +G_GNUC_INTERNAL +const char *srt_xdg_portal_get_messages (SrtXdgPortal *self); +G_GNUC_INTERNAL +SrtXdgPortalIssues srt_xdg_portal_get_issues (SrtXdgPortal *self); +G_GNUC_INTERNAL +GList *srt_xdg_portal_get_backends (SrtXdgPortal *self); +G_GNUC_INTERNAL +GList *srt_xdg_portal_get_interfaces (SrtXdgPortal *self); + +G_GNUC_INTERNAL +SrtXdgPortal *_srt_xdg_portal_get_info_from_report (JsonObject *json_obj); + +#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtXdgPortal, g_object_unref) +#endif diff --git a/steam-runtime-tools/xdg-portal.c b/steam-runtime-tools/xdg-portal.c new file mode 100644 index 0000000000000000000000000000000000000000..ba4c7af2ed8f4ea20b1e3298712232422655c735 --- /dev/null +++ b/steam-runtime-tools/xdg-portal.c @@ -0,0 +1,944 @@ +/* + * 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 "steam-runtime-tools/xdg-portal.h" + +#include "steam-runtime-tools/enums.h" +#include "steam-runtime-tools/glib-backports-internal.h" +#include "steam-runtime-tools/json-glib-compat.h" +#include "steam-runtime-tools/xdg-portal-internal.h" +#include "steam-runtime-tools/utils.h" +#include "steam-runtime-tools/utils-internal.h" + +#include <errno.h> +#include <string.h> +#include <sys/types.h> + +#include <json-glib/json-glib.h> + +/** + * SECTION:xdg-portal + * @title: XDG portals support check + * @short_description: Get information about system's XDG portals support + * @include: steam-runtime-tools/steam-runtime-tools.h + * + * #SrtXdgPortal is an opaque object representing the XDG portals support. + * This is a reference-counted object: use g_object_ref() and + * g_object_unref() to manage its lifecycle. + * + * #SrtXdgPortalBackend is an opaque object representing an XDG portal + * backend. This is a reference-counted object: use g_object_ref() and + * g_object_unref() to manage its lifecycle. + * + * #SrtXdgPortalInterface is an opaque object representing an XDG portal + * inerface. This is a reference-counted object: use g_object_ref() and + * g_object_unref() to manage its lifecycle. + */ + +struct _SrtXdgPortalInterface +{ + /*< private >*/ + GObject parent; + gchar *name; + gboolean available; + guint version; +}; + +struct _SrtXdgPortalInterfaceClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +enum { + PROP_INTERFACE_0, + PROP_INTERFACE_NAME, + PROP_INTERFACE_VERSION, + PROP_INTERFACE_IS_AVAILABLE, + N_INTERFACE_PROPERTIES, +}; + +G_DEFINE_TYPE (SrtXdgPortalInterface, srt_xdg_portal_interface, G_TYPE_OBJECT) + +static void +srt_xdg_portal_interface_init (SrtXdgPortalInterface *self) +{ +} + +static void +srt_xdg_portal_interface_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SrtXdgPortalInterface *self = SRT_XDG_PORTAL_INTERFACE (object); + + switch (prop_id) + { + case PROP_INTERFACE_NAME: + g_value_set_string (value, self->name); + break; + + case PROP_INTERFACE_VERSION: + g_value_set_uint (value, self->version); + break; + + case PROP_INTERFACE_IS_AVAILABLE: + g_value_set_boolean (value, self->available); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +srt_xdg_portal_interface_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SrtXdgPortalInterface *self = SRT_XDG_PORTAL_INTERFACE (object); + + switch (prop_id) + { + case PROP_INTERFACE_NAME: + /* Construct-only */ + g_return_if_fail (self->name == NULL); + self->name = g_value_dup_string (value); + break; + + case PROP_INTERFACE_VERSION: + /* Construct-only */ + g_return_if_fail (self->version == 0); + self->version = g_value_get_uint (value); + break; + + case PROP_INTERFACE_IS_AVAILABLE: + g_return_if_fail (self->available == FALSE); + self->available = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +srt_xdg_portal_interface_finalize (GObject *object) +{ + SrtXdgPortalInterface *self = SRT_XDG_PORTAL_INTERFACE (object); + + g_free (self->name); + + G_OBJECT_CLASS (srt_xdg_portal_interface_parent_class)->finalize (object); +} + +static GParamSpec *interface_properties[N_INTERFACE_PROPERTIES] = { NULL }; + +static void +srt_xdg_portal_interface_class_init (SrtXdgPortalInterfaceClass *cls) +{ + GObjectClass *object_class = G_OBJECT_CLASS (cls); + + object_class->get_property = srt_xdg_portal_interface_get_property; + object_class->set_property = srt_xdg_portal_interface_set_property; + object_class->finalize = srt_xdg_portal_interface_finalize; + + interface_properties[PROP_INTERFACE_NAME] = + g_param_spec_string ("name", "Name", + "Name of this XDG portal interface, e.g. 'org.freedesktop.portal.Email'", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + interface_properties[PROP_INTERFACE_VERSION] = + g_param_spec_uint ("version", "Version", + "The version property of this XDG portal interface", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + interface_properties[PROP_INTERFACE_IS_AVAILABLE] = + g_param_spec_boolean ("is-available", "Is available?", + "TRUE if this XDG portal is available", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_INTERFACE_PROPERTIES, interface_properties); +} + +struct _SrtXdgPortalBackend +{ + /*< private >*/ + GObject parent; + gchar *name; + gboolean available; +}; + +struct _SrtXdgPortalBackendClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +enum { + PROP_BACKEND_0, + PROP_BACKEND_NAME, + PROP_BACKEND_IS_AVAILABLE, + N_PROP_BACKEND_PROPERTIES, +}; + +G_DEFINE_TYPE (SrtXdgPortalBackend, srt_xdg_portal_backend, G_TYPE_OBJECT) + +static void +srt_xdg_portal_backend_init (SrtXdgPortalBackend *self) +{ +} + +static void +srt_xdg_portal_backend_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SrtXdgPortalBackend *self = SRT_XDG_PORTAL_BACKEND (object); + + switch (prop_id) + { + case PROP_BACKEND_NAME: + g_value_set_string (value, self->name); + break; + + case PROP_BACKEND_IS_AVAILABLE: + g_value_set_boolean (value, self->available); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +srt_xdg_portal_backend_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SrtXdgPortalBackend *self = SRT_XDG_PORTAL_BACKEND (object); + + switch (prop_id) + { + case PROP_BACKEND_NAME: + /* Construct-only */ + g_return_if_fail (self->name == NULL); + self->name = g_value_dup_string (value); + break; + + case PROP_BACKEND_IS_AVAILABLE: + g_return_if_fail (self->available == FALSE); + self->available = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +srt_xdg_portal_backend_finalize (GObject *object) +{ + SrtXdgPortalBackend *self = SRT_XDG_PORTAL_BACKEND (object); + + g_free (self->name); + + G_OBJECT_CLASS (srt_xdg_portal_backend_parent_class)->finalize (object); +} + +static GParamSpec *backend_properties[N_PROP_BACKEND_PROPERTIES] = { NULL }; + +static void +srt_xdg_portal_backend_class_init (SrtXdgPortalBackendClass *cls) +{ + GObjectClass *object_class = G_OBJECT_CLASS (cls); + + object_class->get_property = srt_xdg_portal_backend_get_property; + object_class->set_property = srt_xdg_portal_backend_set_property; + object_class->finalize = srt_xdg_portal_backend_finalize; + + backend_properties[PROP_BACKEND_NAME] = + g_param_spec_string ("name", "Name", + "Name of this XDG portal backend, e.g. 'org.freedesktop.impl.portal.desktop.gtk'", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + backend_properties[PROP_BACKEND_IS_AVAILABLE] = + g_param_spec_boolean ("is-available", "Is available?", + "TRUE if this XDG portal is available", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROP_BACKEND_PROPERTIES, backend_properties); +} + +struct _SrtXdgPortal +{ + /*< private >*/ + GObject parent; + gchar *messages; + SrtXdgPortalIssues issues; + GPtrArray *portals_backends; + GPtrArray *portals_interfaces; +}; + +struct _SrtXdgPortalClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +enum { + PROP_PORTAL_0, + PROP_PORTAL_MESSAGES, + PROP_PORTAL_ISSUES, + PROP_PORTAL_BACKENDS, + PROP_PORTAL_INTERFACES, + N_PORTAL_PROPERTIES, +}; + +G_DEFINE_TYPE (SrtXdgPortal, srt_xdg_portal, G_TYPE_OBJECT) + +static void +srt_xdg_portal_init (SrtXdgPortal *self) +{ +} + +static void +srt_xdg_portal_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SrtXdgPortal *self = SRT_XDG_PORTAL (object); + + switch (prop_id) + { + case PROP_PORTAL_MESSAGES: + g_value_set_string (value, self->messages); + break; + + case PROP_PORTAL_ISSUES: + g_value_set_flags (value, self->issues); + break; + + case PROP_PORTAL_BACKENDS: + g_value_set_boxed (value, self->portals_backends); + break; + + case PROP_PORTAL_INTERFACES: + g_value_set_boxed (value, self->portals_interfaces); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +srt_xdg_portal_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SrtXdgPortal *self = SRT_XDG_PORTAL (object); + + switch (prop_id) + { + case PROP_PORTAL_MESSAGES: + /* Construct-only */ + g_return_if_fail (self->messages == NULL); + self->messages = g_value_dup_string (value); + break; + + case PROP_PORTAL_ISSUES: + /* Construct-only */ + g_return_if_fail (self->issues == 0); + self->issues = g_value_get_flags (value); + break; + + case PROP_PORTAL_BACKENDS: + /* Construct-only */ + g_return_if_fail (self->portals_backends == NULL); + self->portals_backends = g_value_dup_boxed (value); + break; + + case PROP_PORTAL_INTERFACES: + /* Construct-only */ + g_return_if_fail (self->portals_interfaces == NULL); + self->portals_interfaces = g_value_dup_boxed (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +srt_xdg_portal_dispose (GObject *object) +{ + SrtXdgPortal *self = SRT_XDG_PORTAL (object); + + g_clear_pointer (&self->portals_backends, g_ptr_array_unref); + g_clear_pointer (&self->portals_interfaces, g_ptr_array_unref); + + G_OBJECT_CLASS (srt_xdg_portal_parent_class)->dispose (object); +} + +static void +srt_xdg_portal_finalize (GObject *object) +{ + SrtXdgPortal *self = SRT_XDG_PORTAL (object); + + g_free (self->messages); + + G_OBJECT_CLASS (srt_xdg_portal_parent_class)->finalize (object); +} + +static GParamSpec *properties[N_PORTAL_PROPERTIES] = { NULL }; + +static void +srt_xdg_portal_class_init (SrtXdgPortalClass *cls) +{ + GObjectClass *object_class = G_OBJECT_CLASS (cls); + + object_class->get_property = srt_xdg_portal_get_property; + object_class->set_property = srt_xdg_portal_set_property; + object_class->dispose = srt_xdg_portal_dispose; + object_class->finalize = srt_xdg_portal_finalize; + + properties[PROP_PORTAL_MESSAGES] = + g_param_spec_string ("messages", "Messages", + "Diagnostic messages produced while checking XDG portals support", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_PORTAL_ISSUES] = + g_param_spec_flags ("issues", "Issues", "Problems with the XDG portals", + SRT_TYPE_XDG_PORTAL_ISSUES, SRT_XDG_PORTAL_ISSUES_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_PORTAL_BACKENDS] = + g_param_spec_boxed ("backends", "Backends", + "List of XDG portals backends that have been checked", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_PORTAL_INTERFACES] = + g_param_spec_boxed ("interfaces", "Interfaces", + "List of XDG portals interfaces that have been checked", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PORTAL_PROPERTIES, properties); +} + +/** + * _srt_check_xdg_portals: + * @envp: (not nullable): Environment variables to use + * @helpers_path: An optional path to find check-xdg-portal helper, PATH + * is used if null. + * @test_flags: Flags used during automated testing + * @multiarch_tuple: Multiarch tuple of helper executable to use + * @details_out: The SrtXdgPortal object containing the details of the check. + * + * Return the problems found when checking the available XDG portals. + * + * Returns: A bitfield containing problems, or %SRT_XDG_PORTAL_ISSUES_NONE + * if no problems were found + */ +G_GNUC_INTERNAL SrtXdgPortalIssues +_srt_check_xdg_portals (gchar **envp, + const char *helpers_path, + SrtTestFlags test_flags, + SrtContainerType container_type, + const char *multiarch_tuple, + SrtXdgPortal **details_out) +{ + g_autoptr(GError) local_error = NULL; + SrtHelperFlags helper_flags = (SRT_HELPER_FLAGS_TIME_OUT + | SRT_HELPER_FLAGS_SEARCH_PATH); + g_autoptr(GPtrArray) argv = NULL; + g_autofree gchar *output = NULL; + g_autofree gchar *stderr_messages = NULL; + int wait_status = -1; + int exit_status = -1; + int terminating_signal = 0; + JsonObject *object = NULL; + JsonObject *interfaces_object = NULL; + JsonObject *backends_object = NULL; + JsonNode *node = NULL; + g_autoptr(JsonParser) parser = NULL; + g_autoptr(GList) interfaces_members = NULL; + g_autoptr(GList) backends_members = NULL; + g_autoptr(GPtrArray) backends = g_ptr_array_new_full (2, g_object_unref); + g_autoptr(GPtrArray) interfaces = g_ptr_array_new_full (2, g_object_unref); + SrtXdgPortalIssues issues = SRT_XDG_PORTAL_ISSUES_NONE; + gboolean has_implementation = FALSE; + + g_return_val_if_fail (envp != NULL, SRT_XDG_PORTAL_ISSUES_UNKNOWN); + g_return_val_if_fail (multiarch_tuple != NULL, SRT_XDG_PORTAL_ISSUES_UNKNOWN); + g_return_val_if_fail (details_out == NULL || *details_out == NULL, + SRT_XDG_PORTAL_ISSUES_UNKNOWN); + + if (test_flags & SRT_TEST_FLAGS_TIME_OUT_SOONER) + helper_flags |= SRT_HELPER_FLAGS_TIME_OUT_SOONER; + + argv = _srt_get_helper (helpers_path, multiarch_tuple, "check-xdg-portal", + helper_flags, &local_error); + + if (argv == NULL) + { + g_debug ("An error occurred trying to check the D-Bus portals capabilities: %s", + local_error->message); + issues |= SRT_XDG_PORTAL_ISSUES_UNKNOWN; + if (details_out != NULL) + *details_out = _srt_xdg_portal_new (local_error->message, issues, NULL, NULL); + return issues; + } + + /* NULL terminate the array */ + g_ptr_array_add (argv, NULL); + + if (!g_spawn_sync (NULL, /* working directory */ + (gchar **) argv->pdata, + envp, + G_SPAWN_SEARCH_PATH, + _srt_child_setup_unblock_signals, + NULL, /* user data */ + &output, /* stdout */ + &stderr_messages, + &wait_status, + &local_error)) + { + g_debug ("An error occurred calling the helper: %s", local_error->message); + issues |= SRT_XDG_PORTAL_ISSUES_UNKNOWN; + if (details_out != NULL) + *details_out = _srt_xdg_portal_new (local_error->message, issues, NULL, NULL); + return issues; + } + + /* Normalize the empty string (expected to be common) to NULL */ + if (stderr_messages != NULL && stderr_messages[0] == '\0') + g_clear_pointer (&stderr_messages, g_free); + + if (wait_status != 0) + { + g_debug ("... wait status %d", wait_status); + if (_srt_process_timeout_wait_status (wait_status, &exit_status, &terminating_signal)) + { + issues |= SRT_XDG_PORTAL_ISSUES_TIMEOUT; + if (details_out != NULL) + *details_out = _srt_xdg_portal_new (NULL, issues, NULL, NULL); + return issues; + } + } + else + { + exit_status = 0; + } + + if (exit_status == 1) + g_debug ("The helper exited with 1, either a required xdg portal is missing or an error occurred"); + + if (output == NULL || output[0] == '\0') + { + g_print ("The helper exited without printing the expected JSON in output"); + issues |= SRT_XDG_PORTAL_ISSUES_UNKNOWN; + if (details_out != NULL) + *details_out = _srt_xdg_portal_new (stderr_messages, issues, NULL, NULL); + return issues; + } + + /* We can't use `json_from_string()` directly because we are targeting an + * older json-glib version */ + parser = json_parser_new (); + + if (!json_parser_load_from_data (parser, output, -1, &local_error)) + { + g_debug ("The helper output is not a valid JSON: %s", local_error->message); + issues |= SRT_XDG_PORTAL_ISSUES_UNKNOWN; + if (details_out != NULL) + *details_out = _srt_xdg_portal_new (local_error->message, issues, NULL, NULL); + return issues; + } + + node = json_parser_get_root (parser); + + if (node == NULL || !JSON_NODE_HOLDS_OBJECT (node)) + { + g_debug ("The helper output is not a JSON object"); + issues |= SRT_XDG_PORTAL_ISSUES_UNKNOWN; + if (details_out != NULL) + *details_out = _srt_xdg_portal_new (local_error->message, issues, NULL, NULL); + return issues; + } + + object = json_node_get_object (node); + + if (!json_object_has_member (object, "interfaces")) + { + g_debug ("The helper output JSON is malformed or incomplete"); + issues |= SRT_XDG_PORTAL_ISSUES_UNKNOWN; + if (details_out != NULL) + *details_out = _srt_xdg_portal_new (local_error->message, issues, NULL, NULL); + return issues; + } + + interfaces_object = json_object_get_object_member (object, "interfaces"); + interfaces_members = json_object_get_members (interfaces_object); + for (const GList *l = interfaces_members; l != NULL; l = l->next) + { + gboolean available = FALSE; + guint version = 0; + JsonObject *interface_object = NULL; + interface_object = json_object_get_object_member (interfaces_object, l->data); + if (json_object_has_member (interface_object, "available")) + available = json_object_get_boolean_member (interface_object, "available"); + else + g_debug ("The helper output JSON is missing the 'available' field, we assume it to be 'FALSE'"); + if (available) + { + if (json_object_has_member (interface_object, "version")) + version = json_object_get_int_member (interface_object, "version"); + else + g_debug ("The helper output JSON is missing the 'version' field, we assume it to be '0'"); + } + else + { + issues |= SRT_XDG_PORTAL_ISSUES_MISSING_INTERFACE; + } + + if (details_out != NULL) + g_ptr_array_add (interfaces, + _srt_xdg_portal_interface_new (l->data, available, version)); + } + + if (interfaces_members == NULL) + issues |= SRT_XDG_PORTAL_ISSUES_MISSING_INTERFACE; + + /* If 'backends' is missing is not necessarily an error */ + if (json_object_has_member (object, "backends")) + { + backends_object = json_object_get_object_member (object, "backends"); + backends_members = json_object_get_members (backends_object); + for (const GList *l = backends_members; l != NULL; l = l->next) + { + gboolean available = FALSE; + JsonObject *backend_object = NULL; + backend_object = json_object_get_object_member (backends_object, l->data); + if (json_object_has_member (backend_object, "available")) + available = json_object_get_boolean_member (backend_object, "available"); + else + g_debug ("The helper output JSON is missing the 'available' field, we assume it to be 'FALSE'"); + + if (available) + has_implementation = TRUE; + + if (details_out != NULL) + g_ptr_array_add (backends, + _srt_xdg_portal_backend_new (l->data, available)); + } + } + + if (container_type == SRT_CONTAINER_TYPE_FLATPAK) + { + g_debug ("In a Flatpak container we are not allowed to contact the portals implementations"); + } + else if (!has_implementation) + { + issues |= SRT_XDG_PORTAL_ISSUES_NO_IMPLEMENTATION; + } + + if (details_out != NULL) + { + *details_out = _srt_xdg_portal_new (stderr_messages, + issues, + backends, + interfaces); + } + + return issues; +} + +/** + * _srt_xdg_portal_get_info_from_report: + * @json_obj: (not nullable): A JSON Object used to search for the XDG portals info + * + * Returns: A new #SrtXdgPortal. + */ +SrtXdgPortal * +_srt_xdg_portal_get_info_from_report (JsonObject *json_obj) +{ + JsonObject *json_portals_obj = NULL; + JsonObject *json_details_obj = NULL; + JsonObject *json_interfaces_obj = NULL; + JsonObject *json_backends_obj = NULL; + g_autoptr(GList) interfaces_members = NULL; + g_autoptr(GList) backends_members = NULL; + g_autoptr(GPtrArray) backends = g_ptr_array_new_full (2, g_object_unref); + g_autoptr(GPtrArray) interfaces = g_ptr_array_new_full (2, g_object_unref); + SrtXdgPortalIssues issues = SRT_XDG_PORTAL_ISSUES_NONE; + const gchar *messages = NULL; + + g_return_val_if_fail (json_obj != NULL, NULL); + + if (!json_object_has_member (json_obj, "xdg-portals")) + { + g_debug ("'xdg-portals' entry is missing"); + return _srt_xdg_portal_new (NULL, SRT_XDG_PORTAL_ISSUES_UNKNOWN, NULL, NULL); + } + + json_portals_obj = json_object_get_object_member (json_obj, "xdg-portals"); + + issues = srt_get_flags_from_json_array (SRT_TYPE_XDG_PORTAL_ISSUES, + json_portals_obj, + "issues", + SRT_XDG_PORTAL_ISSUES_UNKNOWN); + + if (json_object_has_member (json_portals_obj, "messages")) + messages = json_object_get_string_member (json_portals_obj, "messages"); + + if (!json_object_has_member (json_portals_obj, "details")) + return _srt_xdg_portal_new (messages, issues, NULL, NULL); + + json_details_obj = json_object_get_object_member (json_portals_obj, "details"); + + if (json_object_has_member (json_details_obj, "interfaces")) + { + json_interfaces_obj = json_object_get_object_member (json_details_obj, "interfaces"); + interfaces_members = json_object_get_members (json_interfaces_obj); + for (const GList *l = interfaces_members; l != NULL; l = l->next) + { + gboolean available = FALSE; + gint64 version = 0; + JsonObject *json_portal_obj = json_object_get_object_member (json_interfaces_obj, l->data); + if (json_object_has_member (json_portal_obj, "available")) + available = json_object_get_boolean_member (json_portal_obj, "available"); + + if (json_object_has_member (json_portal_obj, "version")) + version = json_object_get_int_member (json_portal_obj, "version"); + + g_ptr_array_add (interfaces, + _srt_xdg_portal_interface_new (l->data, available, version)); + } + } + + if (json_object_has_member (json_details_obj, "backends")) + { + json_backends_obj = json_object_get_object_member (json_details_obj, "backends"); + backends_members = json_object_get_members (json_backends_obj); + for (const GList *l = backends_members; l != NULL; l = l->next) + { + gboolean available = FALSE; + JsonObject *json_portal_obj = json_object_get_object_member (json_backends_obj, l->data); + if (json_object_has_member (json_portal_obj, "available")) + available = json_object_get_boolean_member (json_portal_obj, "available"); + + g_ptr_array_add (backends, _srt_xdg_portal_backend_new (l->data, available)); + } + } + + return _srt_xdg_portal_new (messages, + issues, + backends, + interfaces); +} + +/** + * srt_xdg_portal_get_messages: + * @self: an XDG portal object + * + * Return the diagnostic messages shown by the XDG portal check, or %NULL if + * none. The returned pointer is valid as long as a reference to @self is held. + * + * Returns: (nullable) (transfer none): #SrtXdgPortal:messages + */ +const char * +srt_xdg_portal_get_messages (SrtXdgPortal *self) +{ + g_return_val_if_fail (SRT_IS_XDG_PORTAL (self), NULL); + return self->messages; +} + +/** + * srt_xdg_portal_get_issues: + * @self: an XDG portal object + * + * Return flags indicating issues found while checking for XDG desktop portals. + * + * Returns: #SrtXdgPortal:issues + */ +SrtXdgPortalIssues +srt_xdg_portal_get_issues (SrtXdgPortal *self) +{ + g_return_val_if_fail (SRT_IS_XDG_PORTAL (self), SRT_XDG_PORTAL_ISSUES_UNKNOWN); + return self->issues; +} + +/** + * srt_xdg_portal_get_backends: + * @self: an XDG portal object + * + * Return the list of XDG portals backends that have been checked. + * + * Returns: (transfer full) (element-type SrtXdgPortalBackend): The backends. + * Free with `g_list_free_full (list, g_object_unref)`. + */ +GList * +srt_xdg_portal_get_backends (SrtXdgPortal *self) +{ + GList *ret = NULL; + guint i; + + g_return_val_if_fail (SRT_IS_XDG_PORTAL (self), NULL); + + for (i = 0; self->portals_backends != NULL && i < self->portals_backends->len; i++) + ret = g_list_prepend (ret, g_object_ref (g_ptr_array_index (self->portals_backends, i))); + + return g_list_reverse (ret); +} + +/** + * srt_xdg_portal_get_interfaces: + * @self: an XDG portal object + * + * Return the list of XDG portals interfaces that have been checked. + * + * Returns: (transfer full) (element-type SrtXdgPortalInterface): The interfaces. + * Free with `g_list_free_full (list, g_object_unref)`. + */ +GList * +srt_xdg_portal_get_interfaces (SrtXdgPortal *self) +{ + GList *ret = NULL; + guint i; + + g_return_val_if_fail (SRT_IS_XDG_PORTAL (self), NULL); + + for (i = 0; self->portals_interfaces != NULL && i < self->portals_interfaces->len; i++) + ret = g_list_prepend (ret, g_object_ref (g_ptr_array_index (self->portals_interfaces, i))); + + return g_list_reverse (ret); +} + +/** + * srt_xdg_portal_backend_get_name: + * @self: an XDG portal backend object + * + * Return the name of the XDG portal backend, for example + * `org.freedesktop.impl.portal.desktop.gtk` for the GTK/GNOME + * implementation. The returned string remains valid as long as + * a reference to @self is held. + * + * Returns: (transfer none) (not nullable): #SrtXdgPortalBackend:name + */ +const char * +srt_xdg_portal_backend_get_name (SrtXdgPortalBackend *self) +{ + g_return_val_if_fail (SRT_IS_XDG_PORTAL_BACKEND (self), NULL); + return self->name; +} + +/** + * srt_xdg_portal_backend_is_available: + * @self: an XDG portal backend object + * + * Return %TRUE if the XDG portal backend is available. + * + * Returns: #SrtXdgPortalBackend:is-available + */ +gboolean +srt_xdg_portal_backend_is_available (SrtXdgPortalBackend *self) +{ + g_return_val_if_fail (SRT_IS_XDG_PORTAL_BACKEND (self), FALSE); + return self->available; +} + +/** + * srt_xdg_portal_interface_get_name: + * @self: an XDG portal interface object + * + * Return the name of the XDG portal interface, for example + * `org.freedesktop.portal.Email`. The returned string + * remains valid as long as a reference to @self is held. + * + * Returns: (transfer none) (not nullable): #SrtXdgPortalInterface:name + */ +const char * +srt_xdg_portal_interface_get_name (SrtXdgPortalInterface *self) +{ + g_return_val_if_fail (SRT_IS_XDG_PORTAL_INTERFACE (self), NULL); + return self->name; +} + +/** + * srt_xdg_portal_interface_is_available: + * @self: an XDG portal interface object + * + * Return %TRUE if the XDG portal interface is available. + * + * Returns: #SrtXdgPortalInterface:is-available + */ +gboolean +srt_xdg_portal_interface_is_available (SrtXdgPortalInterface *self) +{ + g_return_val_if_fail (SRT_IS_XDG_PORTAL_INTERFACE (self), FALSE); + return self->available; +} + +/** + * srt_xdg_portal_interface_get_version: + * @self: an XDG portal interface object + * + * Return the version property of the XDG portal interface, + * or 0 if unknown or unavailable. + * + * Returns: #SrtXdgPortalInterface:version + */ +guint32 +srt_xdg_portal_interface_get_version (SrtXdgPortalInterface *self) +{ + g_return_val_if_fail (SRT_IS_XDG_PORTAL_INTERFACE (self), 0); + return self->version; +} diff --git a/steam-runtime-tools/xdg-portal.h b/steam-runtime-tools/xdg-portal.h new file mode 100644 index 0000000000000000000000000000000000000000..d691ff8adca0b89e20a7fe445ccff207d69d2088 --- /dev/null +++ b/steam-runtime-tools/xdg-portal.h @@ -0,0 +1,102 @@ +/* + * 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. + */ + +#pragma once + +#if !defined(_SRT_IN_SINGLE_HEADER) && !defined(_SRT_COMPILATION) +#error "Do not include directly, use <steam-runtime-tools/steam-runtime-tools.h>" +#endif + +#include <glib.h> +#include <glib-object.h> + +#include <steam-runtime-tools/macros.h> + +typedef struct _SrtXdgPortalInterface SrtXdgPortalInterface; +typedef struct _SrtXdgPortalInterfaceClass SrtXdgPortalInterfaceClass; + +#define SRT_TYPE_XDG_PORTAL_INTERFACE srt_xdg_portal_interface_get_type () +#define SRT_XDG_PORTAL_INTERFACE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SRT_TYPE_XDG_PORTAL_INTERFACE, SrtXdgPortalInterface)) +#define SRT_XDG_PORTAL_INTERFACE_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), SRT_TYPE_XDG_PORTAL_INTERFACE, SrtXdgPortalInterfaceClass)) +#define SRT_IS_XDG_PORTAL_INTERFACE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SRT_TYPE_XDG_PORTAL_INTERFACE)) +#define SRT_IS_XDG_PORTAL_INTERFACE_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE ((cls), SRT_TYPE_XDG_PORTAL_INTERFACE)) +#define SRT_XDG_PORTAL_INTERFACE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SRT_TYPE_XDG_PORTAL_INTERFACE, SrtXdgPortalInterfaceClass) + +_SRT_PUBLIC +GType srt_xdg_portal_interface_get_type (void); + +typedef struct _SrtXdgPortalBackend SrtXdgPortalBackend; +typedef struct _SrtXdgPortalBackendClass SrtXdgPortalBackendClass; + +#define SRT_TYPE_XDG_PORTAL_BACKEND srt_xdg_portal_backend_get_type () +#define SRT_XDG_PORTAL_BACKEND(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SRT_TYPE_XDG_PORTAL_BACKEND, SrtXdgPortalBackend)) +#define SRT_XDG_PORTAL_BACKEND_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), SRT_TYPE_XDG_PORTAL_BACKEND, SrtXdgPortalBackendClass)) +#define SRT_IS_XDG_PORTAL_BACKEND(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SRT_TYPE_XDG_PORTAL_BACKEND)) +#define SRT_IS_XDG_PORTAL_BACKEND_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE ((cls), SRT_TYPE_XDG_PORTAL_BACKEND)) +#define SRT_XDG_PORTAL_BACKEND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SRT_TYPE_XDG_PORTAL_BACKEND, SrtXdgPortalBackendClass) + +_SRT_PUBLIC +GType srt_xdg_portal_backend_get_type (void); + +/** + * SrtXdgPortalIssues: + * @SRT_XDG_PORTAL_ISSUES_NONE: There are no problems + * @SRT_XDG_PORTAL_ISSUES_UNKNOWN: A generic internal error occurred while + * trying to check the XDG portals support, or, while reading a report, + * either an unknown issue flag was encountered or the xdg portal issues + * field was missing + * @SRT_XDG_PORTAL_ISSUES_TIMEOUT: The check for the XDG portals support + * took too long to run and was terminated. This is likely to indicate + * that there are issues that caused the process to hang. + * @SRT_XDG_PORTAL_ISSUES_MISSING_INTERFACE: A certain required XDG portal + * interface is missing. + * @SRT_XDG_PORTAL_ISSUES_NO_IMPLEMENTATION: There isn't a working XDG portal + * implementation. + */ +typedef enum +{ + SRT_XDG_PORTAL_ISSUES_NONE = 0, + SRT_XDG_PORTAL_ISSUES_UNKNOWN = (1 << 0), + SRT_XDG_PORTAL_ISSUES_TIMEOUT = (1 << 1), + SRT_XDG_PORTAL_ISSUES_MISSING_INTERFACE = (1 << 2), + SRT_XDG_PORTAL_ISSUES_NO_IMPLEMENTATION = (1 << 3), +} SrtXdgPortalIssues; + +_SRT_PUBLIC +const char *srt_xdg_portal_backend_get_name (SrtXdgPortalBackend *self); +_SRT_PUBLIC +gboolean srt_xdg_portal_backend_is_available (SrtXdgPortalBackend *self); + +_SRT_PUBLIC +const char *srt_xdg_portal_interface_get_name (SrtXdgPortalInterface *self); +_SRT_PUBLIC +gboolean srt_xdg_portal_interface_is_available (SrtXdgPortalInterface *self); +_SRT_PUBLIC +guint32 srt_xdg_portal_interface_get_version (SrtXdgPortalInterface *self); + +#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtXdgPortalBackend, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtXdgPortalInterface, g_object_unref) +#endif diff --git a/tests/json-report/full-good-report.json b/tests/json-report/full-good-report.json index 9edf332b24bbea8291d145520a645acb4ecfac97..c56e4e85ab08023e8e13e55448af1e0b75b632b6 100644 --- a/tests/json-report/full-good-report.json +++ b/tests/json-report/full-good-report.json @@ -247,6 +247,29 @@ "steam_uri_handler" : true } ], + "xdg-portals" : { + "details" : { + "interfaces" : { + "org.freedesktop.portal.OpenURI" : { + "available" : true, + "version" : 3 + }, + "org.freedesktop.portal.Email" : { + "available" : true, + "version" : 2 + } + }, + "backends" : { + "org.freedesktop.impl.portal.desktop.gtk" : { + "available" : true + }, + "org.freedesktop.impl.portal.desktop.kde" : { + "available" : false + } + } + }, + "issues" : [] + }, "cpu-features" : { "x86-64" : true, "sse3" : false, diff --git a/tests/json-report/partial-report.json b/tests/json-report/partial-report.json index a7fec110712b1f7fd6f2dfc9f6329f85b7afa6df..92d2d5e1b3980fb99d31eab6f87263c48dc2ae0c 100644 --- a/tests/json-report/partial-report.json +++ b/tests/json-report/partial-report.json @@ -41,5 +41,11 @@ "error" : "Something went wrong" } ] + }, + "xdg-portals" : { + "issues" : [ + "timeout" + ], + "messages" : "timeout: failed to run command ‘x86_64-linux-gnu-check-xdg-portal’: No such file or directory\n" } } diff --git a/tests/meson.build b/tests/meson.build index bc04ff99677b9b92aecec8ffc2be4b3ac5bf2e09..42355686599692629b71c6df0261eb7c738ff1b7 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -35,6 +35,7 @@ tests = [ {'name': 'locale'}, {'name': 'system-info'}, {'name': 'utils', 'static': true}, + {'name': 'xdg-portal'}, ] if get_option('bin') @@ -261,10 +262,14 @@ foreach helper : [ 'mock-bad-check-va-api', 'mock-bad-check-vdpau', 'mock-bad-check-vulkan', + 'mock-bad-check-xdg-portal', 'mock-bad-vulkaninfo', 'mock-good-check-va-api', 'mock-good-check-vdpau', + 'mock-good-check-xdg-portal', + 'mock-good-flatpak-check-xdg-portal', 'mock-good-vulkaninfo', + 'mock-hanging-check-xdg-portal', 'mock-hanging-wflinfo', 'mock-sigusr-wflinfo', ] diff --git a/tests/mock-bad-check-xdg-portal.c b/tests/mock-bad-check-xdg-portal.c new file mode 100644 index 0000000000000000000000000000000000000000..f0d5f1577b7c56a4a245fddd05fcc9ea6ac4fc9d --- /dev/null +++ b/tests/mock-bad-check-xdg-portal.c @@ -0,0 +1,57 @@ +/* + * 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 <stdio.h> + +int +main (int argc, + char **argv) +{ + // Give bad output + fprintf (stderr, "The 'version' property is not available for 'org.freedesktop.portal.OpenURI', either there isn't a working xdg-desktop-portal or it is a very old version\n"); + + printf ("{\n" + "\t\"interfaces\" : {\n" + "\t\t\"org.freedesktop.portal.OpenURI\" : {\n" + "\t\t\t\"available\" : false\n" + "\t\t},\n" + "\t\t\"org.freedesktop.portal.Email\" : {\n" + "\t\t\t\"available\" : true,\n" + "\t\t\t\"version\" : 3\n" + "\t\t}\n" + "\t},\n" + "\t\"backends\" : {\n" + "\t\t\"org.freedesktop.impl.portal.desktop.gtk\" : {\n" + "\t\t\t\"available\" : true\n" + "\t\t},\n" + "\t\t\"org.freedesktop.impl.portal.desktop.kde\" : {\n" + "\t\t\t\"available\" : false\n" + "\t\t}\n" + "\t}\n" + "}\n"); + + return 1; +} + diff --git a/tests/mock-good-check-xdg-portal.c b/tests/mock-good-check-xdg-portal.c new file mode 100644 index 0000000000000000000000000000000000000000..ce658f33f632da7ff34a87921a35d3fd83a2c2e0 --- /dev/null +++ b/tests/mock-good-check-xdg-portal.c @@ -0,0 +1,56 @@ +/* + * 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 <stdio.h> + +int +main (int argc, + char **argv) +{ + // Give good output + printf ("{\n" + "\t\"interfaces\" : {\n" + "\t\t\"org.freedesktop.portal.OpenURI\" : {\n" + "\t\t\t\"available\" : true,\n" + "\t\t\t\"version\" : 2\n" + "\t\t},\n" + "\t\t\"org.freedesktop.portal.Email\" : {\n" + "\t\t\t\"available\" : true,\n" + "\t\t\t\"version\" : 3\n" + "\t\t}\n" + "\t},\n" + "\t\"backends\" : {\n" + "\t\t\"org.freedesktop.impl.portal.desktop.gtk\" : {\n" + "\t\t\t\"available\" : true\n" + "\t\t},\n" + "\t\t\"org.freedesktop.impl.portal.desktop.kde\" : {\n" + "\t\t\t\"available\" : false\n" + "\t\t}\n" + "\t}\n" + "}\n"); + + return 0; +} + diff --git a/tests/mock-good-flatpak-check-xdg-portal.c b/tests/mock-good-flatpak-check-xdg-portal.c new file mode 100644 index 0000000000000000000000000000000000000000..0780c7e1efcc44263a38c5b53143a8d0519420f4 --- /dev/null +++ b/tests/mock-good-flatpak-check-xdg-portal.c @@ -0,0 +1,48 @@ +/* + * 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 <stdio.h> + +int +main (int argc, + char **argv) +{ + // Give good output as if we were in a Flatpak environment + printf ("{\n" + "\t\"interfaces\" : {\n" + "\t\t\"org.freedesktop.portal.OpenURI\" : {\n" + "\t\t\t\"available\" : true,\n" + "\t\t\t\"version\" : 3\n" + "\t\t},\n" + "\t\t\"org.freedesktop.portal.Email\" : {\n" + "\t\t\t\"available\" : true,\n" + "\t\t\t\"version\" : 3\n" + "\t\t}\n" + "\t}\n" + "}\n"); + + return 0; +} + diff --git a/tests/mock-hanging-check-xdg-portal.c b/tests/mock-hanging-check-xdg-portal.c new file mode 100644 index 0000000000000000000000000000000000000000..b02ae303cd56852bbad2b668e81e49588e48ec16 --- /dev/null +++ b/tests/mock-hanging-check-xdg-portal.c @@ -0,0 +1,59 @@ +/* + * 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 <stdio.h> +#include <unistd.h> + +int +main (int argc, + char **argv) +{ + sleep (300); // Sleep for 5 minutes + + // Give good output + printf ("{\n" + "\t\"interfaces\" : {\n" + "\t\t\"org.freedesktop.portal.OpenURI\" : {\n" + "\t\t\t\"available\" : true,\n" + "\t\t\t\"version\" : 2\n" + "\t\t},\n" + "\t\t\"org.freedesktop.portal.Email\" : {\n" + "\t\t\t\"available\" : true,\n" + "\t\t\t\"version\" : 3\n" + "\t\t}\n" + "\t},\n" + "\t\"backends\" : {\n" + "\t\t\"org.freedesktop.impl.portal.desktop.gtk\" : {\n" + "\t\t\t\"available\" : true\n" + "\t\t},\n" + "\t\t\"org.freedesktop.impl.portal.desktop.kde\" : {\n" + "\t\t\t\"available\" : false\n" + "\t\t}\n" + "\t}\n" + "}\n"); + + return 0; +} + diff --git a/tests/system-info.c b/tests/system-info.c index 7b4e4fffedc82e9aa5a9eee28bc08e137b3b2fc7..30dc5e4f8d953f6529259b208c8acbed59e577fd 100644 --- a/tests/system-info.c +++ b/tests/system-info.c @@ -2449,6 +2449,21 @@ typedef struct gboolean steam_handler; } DesktopEntryTest; +typedef struct +{ + const gchar *name; + gboolean available; + guint32 version; +} XdgPortalInfoTest; + +typedef struct +{ + XdgPortalInfoTest interfaces[3]; + XdgPortalInfoTest backends[3]; + SrtXdgPortalIssues issues; + const gchar *messages; +} XdgPortalTest; + typedef struct { const gchar *description; @@ -2465,6 +2480,7 @@ typedef struct IcdTest egl_icd[3]; IcdTest vulkan_icd[3]; DesktopEntryTest desktop_entry[3]; + XdgPortalTest xdg_portal; SrtX86FeatureFlags x86_features; SrtX86FeatureFlags x86_known; } JsonTest; @@ -2758,6 +2774,34 @@ static const JsonTest json_test[] = }, }, + .xdg_portal = + { + .interfaces = + { + { + .name = "org.freedesktop.portal.OpenURI", + .available = TRUE, + .version = 3, + }, + { + .name = "org.freedesktop.portal.Email", + .available = TRUE, + .version = 2, + }, + }, + .backends = + { + { + .name = "org.freedesktop.impl.portal.desktop.gtk", + .available = TRUE, + }, + { + .name = "org.freedesktop.impl.portal.desktop.kde", + .available = FALSE, + }, + }, + }, + .x86_features = SRT_X86_FEATURE_X86_64 | SRT_X86_FEATURE_CMPXCHG16B, .x86_known = SRT_X86_FEATURE_X86_64 | SRT_X86_FEATURE_SSE3 | SRT_X86_FEATURE_CMPXCHG16B, @@ -2829,6 +2873,11 @@ static const JsonTest json_test[] = .error_message = "Something went wrong", }, }, + .xdg_portal = + { + .issues = SRT_XDG_PORTAL_ISSUES_TIMEOUT, + .messages = "timeout: failed to run command ‘x86_64-linux-gnu-check-xdg-portal’: No such file or directory\n", + }, .x86_features = SRT_X86_FEATURE_NONE, .x86_known = SRT_X86_FEATURE_NONE, }, /* End Partial JSON report */ @@ -2875,6 +2924,10 @@ static const JsonTest json_test[] = .error_message = "(missing error message)", }, }, + .xdg_portal = + { + .issues = SRT_XDG_PORTAL_ISSUES_UNKNOWN, + }, .x86_features = SRT_X86_FEATURE_NONE, .x86_known = SRT_X86_FEATURE_NONE, }, /* End Partial-2 JSON report */ @@ -2904,6 +2957,10 @@ static const JsonTest json_test[] = }, }, .locale_issues = SRT_LOCALE_ISSUES_UNKNOWN, + .xdg_portal = + { + .issues = SRT_XDG_PORTAL_ISSUES_UNKNOWN, + }, .x86_features = SRT_X86_FEATURE_NONE, .x86_known = SRT_X86_FEATURE_NONE, }, /* End Empty JSON report */ @@ -2946,6 +3003,10 @@ static const JsonTest json_test[] = } }, .locale_issues = SRT_LOCALE_ISSUES_C_UTF8_MISSING | SRT_LOCALE_ISSUES_UNKNOWN, + .xdg_portal = + { + .issues = SRT_XDG_PORTAL_ISSUES_UNKNOWN, + }, .x86_features = SRT_X86_FEATURE_X86_64 | SRT_X86_FEATURE_SSE3 | SRT_X86_FEATURE_UNKNOWN, .x86_known = SRT_X86_FEATURE_X86_64 | SRT_X86_FEATURE_SSE3 | SRT_X86_FEATURE_CMPXCHG16B | SRT_X86_FEATURE_UNKNOWN, }, /* End Newer JSON report */ @@ -2975,8 +3036,12 @@ json_parsing (Fixture *f, gchar *name; gchar *pretty_name; gchar *host_directory; + g_autoptr(SrtObjectList) portal_interfaces = NULL; + g_autoptr(SrtObjectList) portal_backends = NULL; + g_autofree gchar *portal_messages = NULL; g_autofree gchar *steamscript_path = NULL; g_autofree gchar *steamscript_version = NULL; + SrtXdgPortalIssues issues; gchar **pinned_32 = NULL; gchar **messages_32 = NULL; gchar **pinned_64 = NULL; @@ -3211,6 +3276,33 @@ json_parsing (Fixture *f, srt_desktop_entry_is_steam_handler (iter->data)); } + portal_interfaces = srt_system_info_list_xdg_portal_interfaces (info); + for (j = 0, iter = portal_interfaces; iter != NULL; iter = iter->next, j++) + { + g_assert_cmpstr (t->xdg_portal.interfaces[j].name, ==, + srt_xdg_portal_interface_get_name (iter->data)); + g_assert_cmpint (t->xdg_portal.interfaces[j].available, ==, + srt_xdg_portal_interface_is_available (iter->data)); + g_assert_cmpint (t->xdg_portal.interfaces[j].version, ==, + srt_xdg_portal_interface_get_version (iter->data)); + } + g_assert_cmpstr (t->xdg_portal.interfaces[j].name, ==, NULL); + + portal_backends = srt_system_info_list_xdg_portal_backends (info); + for (j = 0, iter = portal_backends; iter != NULL; iter = iter->next, j++) + { + g_assert_cmpstr (t->xdg_portal.backends[j].name, ==, + srt_xdg_portal_backend_get_name (iter->data)); + g_assert_cmpint (t->xdg_portal.backends[j].available, ==, + srt_xdg_portal_backend_is_available (iter->data)); + } + g_assert_cmpstr (t->xdg_portal.backends[j].name, ==, NULL); + + issues = srt_system_info_get_xdg_portal_issues (info, &portal_messages); + + g_assert_cmpint (issues, ==, t->xdg_portal.issues); + g_assert_cmpstr (portal_messages, ==, t->xdg_portal.messages); + g_assert_cmpint (t->x86_features, ==, srt_system_info_get_x86_features (info)); g_assert_cmpint (t->x86_known, ==, srt_system_info_get_known_x86_features (info)); diff --git a/tests/xdg-portal.c b/tests/xdg-portal.c new file mode 100644 index 0000000000000000000000000000000000000000..305105435ed35cdc5177964cab01b702091ab9e1 --- /dev/null +++ b/tests/xdg-portal.c @@ -0,0 +1,257 @@ +/* + * 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 this before steam-runtime-tools.h so that its backport of + * G_DEFINE_AUTOPTR_CLEANUP_FUNC will be visible to it */ +#include "steam-runtime-tools/glib-backports-internal.h" + +#include <steam-runtime-tools/steam-runtime-tools.h> + +#include <glib.h> +#include <glib/gstdio.h> +#include <glib-object.h> + +#include "steam-runtime-tools/xdg-portal-internal.h" +#include "test-utils.h" + +static const char *argv0; + +typedef struct +{ + gchar *builddir; +} Fixture; + +typedef struct +{ + int unused; +} Config; + +static void +setup (Fixture *f, + gconstpointer context) +{ + G_GNUC_UNUSED const Config *config = context; + + f->builddir = g_strdup (g_getenv ("G_TEST_BUILDDIR")); + + if (f->builddir == NULL) + f->builddir = g_path_get_dirname (argv0); +} + +static void +teardown (Fixture *f, + gconstpointer context) +{ + G_GNUC_UNUSED const Config *config = context; + + g_free (f->builddir); +} + +typedef struct +{ + const gchar *name; + gboolean is_available; +} XdgPortalBackendTest; + +typedef struct +{ + const gchar *name; + gboolean is_available; + guint32 version; +} XdgPortalInterfaceTest; + +typedef struct +{ + const gchar *description; + const gchar *multiarch_tuple; + const gchar *messages; + const gchar *sysroot; + SrtTestFlags test_flags; + SrtXdgPortalIssues issues; + XdgPortalInterfaceTest xdg_portal_interfaces_info[3]; + XdgPortalBackendTest xdg_portal_backends_info[3]; +} XdgPortalTest; + +static const XdgPortalTest xdg_portal_test[] = +{ + { + .description = "Missing OpenURI portal", + .multiarch_tuple = "mock-bad", + .messages = "The 'version' property is not available for 'org.freedesktop.portal.OpenURI', " + "either there isn't a working xdg-desktop-portal or it is a very old version\n", + .issues = SRT_XDG_PORTAL_ISSUES_MISSING_INTERFACE, + .xdg_portal_interfaces_info = + { + { + .name = "org.freedesktop.portal.OpenURI", + .is_available = FALSE, + .version = 0, + }, + { + .name = "org.freedesktop.portal.Email", + .is_available = TRUE, + .version = 3, + }, + }, + .xdg_portal_backends_info = + { + { + .name = "org.freedesktop.impl.portal.desktop.gtk", + .is_available = TRUE, + }, + { + .name = "org.freedesktop.impl.portal.desktop.kde", + .is_available = FALSE, + }, + }, + }, + + { + .description = "Good system check", + .multiarch_tuple = "mock-good", + .issues = SRT_XDG_PORTAL_ISSUES_NONE, + .xdg_portal_interfaces_info = + { + { + .name = "org.freedesktop.portal.OpenURI", + .is_available = TRUE, + .version = 2, + }, + { + .name = "org.freedesktop.portal.Email", + .is_available = TRUE, + .version = 3, + }, + }, + .xdg_portal_backends_info = + { + { + .name = "org.freedesktop.impl.portal.desktop.gtk", + .is_available = TRUE, + }, + { + .name = "org.freedesktop.impl.portal.desktop.kde", + .is_available = FALSE, + }, + }, + }, + + { + .description = "Good Flatpak environment", + .multiarch_tuple = "mock-good-flatpak", + .sysroot = "flatpak-example", + .issues = SRT_XDG_PORTAL_ISSUES_NONE, + .xdg_portal_interfaces_info = + { + { + .name = "org.freedesktop.portal.OpenURI", + .is_available = TRUE, + .version = 3, + }, + { + .name = "org.freedesktop.portal.Email", + .is_available = TRUE, + .version = 3, + }, + }, + }, + + { + .description = "XDG portal check timeout", + .multiarch_tuple = "mock-hanging", + .test_flags = SRT_TEST_FLAGS_TIME_OUT_SOONER, + .issues = SRT_XDG_PORTAL_ISSUES_TIMEOUT, + }, +}; + +static void +test_check_xdg_portal (Fixture *f, + gconstpointer context) +{ + for (gsize i = 0; i < G_N_ELEMENTS (xdg_portal_test); i++) + { + const XdgPortalTest *t = &xdg_portal_test[i]; + g_autoptr(SrtSystemInfo) info = NULL; + g_autoptr(SrtObjectList) portal_interfaces = NULL; + GList *portal_backends = NULL; + SrtXdgPortalIssues issues; + g_autofree gchar *messages = NULL; + g_autofree gchar *sysroot = NULL; + GList *iter; + gsize j; + + g_test_message ("%s", t->description); + + info = srt_system_info_new (NULL); + srt_system_info_set_helpers_path (info, f->builddir); + srt_system_info_set_primary_multiarch_tuple (info, t->multiarch_tuple); + srt_system_info_set_test_flags (info, t->test_flags); + if (t->sysroot != NULL) + { + sysroot = g_build_filename (f->builddir, "sysroots", t->sysroot, NULL); + srt_system_info_set_sysroot (info, sysroot); + } + + portal_interfaces = srt_system_info_list_xdg_portal_interfaces (info); + for (j = 0, iter = portal_interfaces; iter != NULL; iter = iter->next, j++) + { + g_assert_cmpstr (t->xdg_portal_interfaces_info[j].name, ==, + srt_xdg_portal_interface_get_name (iter->data)); + g_assert_cmpint (t->xdg_portal_interfaces_info[j].is_available, ==, + srt_xdg_portal_interface_is_available (iter->data)); + g_assert_cmpint (t->xdg_portal_interfaces_info[j].version, ==, + srt_xdg_portal_interface_get_version (iter->data)); + } + g_assert_cmpstr (t->xdg_portal_interfaces_info[j].name, ==, NULL); + + portal_backends = srt_system_info_list_xdg_portal_backends (info); + for (j = 0, iter = portal_backends; iter != NULL; iter = iter->next, j++) + { + g_assert_cmpstr (t->xdg_portal_backends_info[j].name, ==, + srt_xdg_portal_backend_get_name (iter->data)); + g_assert_cmpint (t->xdg_portal_backends_info[j].is_available, ==, + srt_xdg_portal_backend_is_available (iter->data)); + } + g_assert_cmpstr (t->xdg_portal_backends_info[j].name, ==, NULL); + + issues = srt_system_info_get_xdg_portal_issues (info, &messages); + + g_assert_cmpint (issues, ==, t->issues); + g_assert_cmpstr (messages, ==, t->messages); + } +} + +int +main (int argc, + char **argv) +{ + argv0 = argv[0]; + + g_test_init (&argc, &argv, NULL); + g_test_add ("/xdg-portal/test_check_xdg_portal", Fixture, NULL, setup, + test_check_xdg_portal, teardown); + + return g_test_run (); +}