diff --git a/bin/system-info.c b/bin/system-info.c index 8ba71a5bf87d0a0980c2201d67b0c2304b577102..2f58661c87e00cab51cee1637460e7b2d249ac3f 100644 --- a/bin/system-info.c +++ b/bin/system-info.c @@ -759,6 +759,7 @@ main (int argc, int opt; static const char * const multiarch_tuples[] = { SRT_ABI_I386, SRT_ABI_X86_64, NULL }; GList *icds; + GList *desktop_entries; const GList *icd_iter; SrtDriverFlags extra_driver_flags = SRT_DRIVER_FLAGS_INCLUDE_ALL; @@ -1175,6 +1176,43 @@ main (int argc, json_builder_end_array (builder); // vulkan.icds json_builder_end_object (builder); // vulkan + json_builder_set_member_name (builder, "desktop-entries"); + json_builder_begin_array (builder); + { + desktop_entries = srt_system_info_list_desktop_entries (info); + for (GList *iter = desktop_entries; iter != NULL; iter = iter->next) + { + json_builder_begin_object (builder); + if (srt_desktop_entry_get_id (iter->data) != NULL) + { + json_builder_set_member_name (builder, "id"); + json_builder_add_string_value (builder, srt_desktop_entry_get_id (iter->data)); + } + + if (srt_desktop_entry_get_commandline (iter->data) != NULL) + { + json_builder_set_member_name (builder, "commandline"); + json_builder_add_string_value (builder, srt_desktop_entry_get_commandline (iter->data)); + } + + if (srt_desktop_entry_get_filename (iter->data) != NULL) + { + json_builder_set_member_name (builder, "filename"); + json_builder_add_string_value (builder, srt_desktop_entry_get_filename (iter->data)); + } + + json_builder_set_member_name (builder, "default_steam_uri_handler"); + json_builder_add_boolean_value (builder, srt_desktop_entry_is_default_handler (iter->data)); + + json_builder_set_member_name (builder, "steam_uri_handler"); + json_builder_add_boolean_value (builder, srt_desktop_entry_is_steam_handler (iter->data)); + + json_builder_end_object (builder); + } + g_list_free_full (desktop_entries, g_object_unref); + } + json_builder_end_array (builder); + json_builder_end_object (builder); // End global object JsonNode *root = json_builder_get_root (builder); diff --git a/bin/system-info.md b/bin/system-info.md index ef244578cbf00a76c6cc07b85fdc807a91d49dac..498e5fec96089473ebb310b79aabd4d70ef50f48 100644 --- a/bin/system-info.md +++ b/bin/system-info.md @@ -630,6 +630,28 @@ keys: : Vulkan API version implemented by this ICD as a dotted-decimal string, for example **1.1.90** +**desktop-entries** +: An array of objects describing the Steam related desktop entries + that have been found. + Every object can have the following keys: + + **id** + : A string with the ID of the application entry, such as `steam.desktop`. + + **commandline** + : A string with which the application will be started, such as + `/usr/bin/steam %U`. + + **filename** + : Absolute path to the desktop entry file. + + **default_steam_uri_handler** + : A boolean value indicating whether this entry is the default used + to open `steam:` URIs. + + **steam_uri_handler** + : A boolean value indicating whether this entry can open `steam:` URIs. + # EXIT STATUS 0 diff --git a/meson.build b/meson.build index 385916853ac0830c3cdbcec3330b18c3032c419a..3e90ff89bc2b640d6dbb1e27f31199589746a707 100644 --- a/meson.build +++ b/meson.build @@ -181,6 +181,10 @@ gio = dependency( 'gio-2.0', version : '>= 2.32', ) +gio_unix = dependency( + 'gio-unix-2.0', + version : '>= 2.32', +) json_glib = dependency( 'json-glib-1.0', version : '>= 1.0', diff --git a/steam-runtime-tools/desktop-entry-internal.h b/steam-runtime-tools/desktop-entry-internal.h new file mode 100644 index 0000000000000000000000000000000000000000..ffa49466c926c21c3c44515f7a3a5280fb8b3d4a --- /dev/null +++ b/steam-runtime-tools/desktop-entry-internal.h @@ -0,0 +1,70 @@ +/*< 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 "steam-runtime-tools/utils-internal.h" + +/* + * _srt_desktop_entry_new: + * @id: (nullable) + * @commandline: (nullable) + * @filename: (nullable) + * + * Inline convenience function to create a new SrtDesktopEntry. + * This is not part of the public API. + * + * Returns: (transfer full): A new #SrtDesktopEntry + */ +static inline SrtDesktopEntry *_srt_desktop_entry_new (const gchar *id, + const gchar *commandline, + const gchar *filename, + gboolean is_default_handler, + gboolean is_steam_handler); + +#ifndef __GTK_DOC_IGNORE__ + +static inline SrtDesktopEntry * +_srt_desktop_entry_new (const gchar *id, + const gchar *commandline, + const gchar *filename, + gboolean is_default_handler, + gboolean is_steam_handler) +{ + return g_object_new (SRT_TYPE_DESKTOP_ENTRY, + "id", id, + "commandline", commandline, + "filename", filename, + "is-default-handler", is_default_handler, + "is-steam-handler", is_steam_handler, + NULL); +} + +#endif + +G_GNUC_INTERNAL +GList *_srt_list_steam_desktop_entries (void); diff --git a/steam-runtime-tools/desktop-entry.c b/steam-runtime-tools/desktop-entry.c new file mode 100644 index 0000000000000000000000000000000000000000..433494e7697cdd8c6898c53192332df360a3c6e4 --- /dev/null +++ b/steam-runtime-tools/desktop-entry.c @@ -0,0 +1,392 @@ +/* + * 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/desktop-entry.h" + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <glib.h> +#include <gio/gio.h> +#include <gio/gdesktopappinfo.h> + +#include "steam-runtime-tools/desktop-entry-internal.h" +#include "steam-runtime-tools/enums.h" +#include "steam-runtime-tools/glib-compat.h" +#include "steam-runtime-tools/utils.h" + +/** + * SECTION:desktop-entry + * @title: Desktop entry + * @short_description: Information about the Steam desktop entries + * @include: steam-runtime-tools/steam-runtime-tools.h + */ + +struct _SrtDesktopEntry +{ + /*< private >*/ + GObject parent; + gchar *id; + gchar *commandline; + gchar *filename; + gboolean is_default_handler; + gboolean is_steam_handler; +}; + +struct _SrtDesktopEntryClass +{ + /*< private >*/ + GObjectClass parent_class; +}; + +enum { + PROP_0, + PROP_ID, + PROP_COMMANDLINE, + PROP_FILENAME, + PROP_IS_DEFAULT_HANDLER, + PROP_IS_STEAM_HANDLER, + N_PROPERTIES +}; + +G_DEFINE_TYPE (SrtDesktopEntry, srt_desktop_entry, G_TYPE_OBJECT) + +static void +srt_desktop_entry_init (SrtDesktopEntry *self) +{ +} + +static void +srt_desktop_entry_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SrtDesktopEntry *self = SRT_DESKTOP_ENTRY (object); + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, self->id); + break; + + case PROP_COMMANDLINE: + g_value_set_string (value, self->commandline); + break; + + case PROP_FILENAME: + g_value_set_string (value, self->filename); + break; + + case PROP_IS_DEFAULT_HANDLER: + g_value_set_boolean (value, self->is_default_handler); + break; + + case PROP_IS_STEAM_HANDLER: + g_value_set_boolean (value, self->is_steam_handler); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +srt_desktop_entry_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SrtDesktopEntry *self = SRT_DESKTOP_ENTRY (object); + + switch (prop_id) + { + case PROP_ID: + /* Construct-only */ + g_return_if_fail (self->id == NULL); + self->id = g_value_dup_string (value); + break; + + case PROP_COMMANDLINE: + /* Construct only */ + g_return_if_fail (self->commandline == NULL); + self->commandline = g_value_dup_string (value); + break; + + case PROP_FILENAME: + /* Construct only */ + g_return_if_fail (self->filename == NULL); + self->filename = g_value_dup_string (value); + break; + + case PROP_IS_DEFAULT_HANDLER: + /* Construct only */ + self->is_default_handler = g_value_get_boolean (value); + break; + + case PROP_IS_STEAM_HANDLER: + /* Construct only */ + self->is_steam_handler = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +srt_desktop_entry_finalize (GObject *object) +{ + SrtDesktopEntry *self = SRT_DESKTOP_ENTRY (object); + + g_free (self->id); + g_free (self->commandline); + g_free (self->filename); + + G_OBJECT_CLASS (srt_desktop_entry_parent_class)->finalize (object); +} + +static GParamSpec *properties[N_PROPERTIES] = { NULL }; + +static void +srt_desktop_entry_class_init (SrtDesktopEntryClass *cls) +{ + GObjectClass *object_class = G_OBJECT_CLASS (cls); + + object_class->get_property = srt_desktop_entry_get_property; + object_class->set_property = srt_desktop_entry_set_property; + object_class->finalize = srt_desktop_entry_finalize; + + properties[PROP_ID] = + g_param_spec_string ("id", "ID", "Desktop entry ID", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_COMMANDLINE] = + g_param_spec_string ("commandline", "Commandline", + "Commandline with which the application will be started", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_FILENAME] = + g_param_spec_string ("filename", "Filename", + "Full path to the desktop entry file", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_IS_DEFAULT_HANDLER] = + g_param_spec_boolean ("is-default-handler", "Is default handler?", + "TRUE if the entry is the default that handles the URI scheme \"steam\"", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_IS_STEAM_HANDLER] = + g_param_spec_boolean ("is-steam-handler", "Is Steam handler?", + "TRUE if the entry is of the type \"x-scheme-handler/steam\"", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); +} + +/** + * srt_desktop_entry_get_id: + * @self: The #SrtDesktopEntry object + * + * Return the ID of @self. + * + * Returns: (nullable): A string like `steam.desktop`, which is valid as long + * as @self is not destroyed. + */ +const char * +srt_desktop_entry_get_id (SrtDesktopEntry *self) +{ + g_return_val_if_fail (SRT_IS_DESKTOP_ENTRY (self), NULL); + return self->id; +} + +/** + * srt_desktop_entry_get_commandline: + * @self: The #SrtDesktopEntry object + * + * Return the commandline with which the application @self will be started. + * + * Returns: (nullable): A string like `/usr/bin/steam %U`, which is valid as + * long as @self is not destroyed. + */ +const char * +srt_desktop_entry_get_commandline (SrtDesktopEntry *self) +{ + g_return_val_if_fail (SRT_IS_DESKTOP_ENTRY (self), NULL); + return self->commandline; +} + +/** + * srt_desktop_entry_get_filename: + * @self: The #SrtDesktopEntry object + * + * Return the full path to the @self file. + * + * Returns: (type filename) (nullable): A string like + * `/usr/share/applications/steam.desktop`, which is valid as long as @self + * is not destroyed. + */ +const char * +srt_desktop_entry_get_filename (SrtDesktopEntry *self) +{ + g_return_val_if_fail (SRT_IS_DESKTOP_ENTRY (self), NULL); + return self->filename; +} + +/** + * srt_desktop_entry_is_default_handler: + * @self: The #SrtDesktopEntry object + * + * Returns: %TRUE if @self is the default handler for `steam:` URIs + */ +gboolean +srt_desktop_entry_is_default_handler (SrtDesktopEntry *self) +{ + g_return_val_if_fail (SRT_IS_DESKTOP_ENTRY (self), FALSE); + return self->is_default_handler; +} + +/** + * srt_desktop_entry_is_default_handler: + * @self: The #SrtDesktopEntry object + * + * Returns: %TRUE if @self can handle `steam:` URIs + */ +gboolean +srt_desktop_entry_is_steam_handler (SrtDesktopEntry *self) +{ + g_return_val_if_fail (SRT_IS_DESKTOP_ENTRY (self), FALSE); + return self->is_steam_handler; +} + +/** + * _srt_list_steam_desktop_entries: + * + * Implementation of srt_system_info_list_desktop_entries(). + * + * Returns: (transfer full) (element-type SrtDesktopEntry) (nullable): A list of + * opaque #SrtDesktopEntry objects or %NULL if nothing was found. Free with + * `g_list_free_full (entries, g_object_unref)`. + */ +GList * +_srt_list_steam_desktop_entries (void) +{ + GList *ret = NULL; + GList *app_list = NULL; + GHashTable *found_handlers = NULL; + GHashTable *known_ids = NULL; + GAppInfo *default_handler = NULL; + const char *default_handler_id = NULL; + const GList *this_app; + + default_handler = g_app_info_get_default_for_uri_scheme ("steam"); + if (default_handler != NULL) + { + default_handler_id = g_app_info_get_id (default_handler); + g_debug ("Found the default `steam:` handler: %s", default_handler_id); + } + + app_list = g_app_info_get_all_for_type ("x-scheme-handler/steam"); + + found_handlers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + known_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + /* The official Steam package or Debian */ + g_hash_table_add (known_ids, g_strdup ("steam.desktop")); + /* Flathub */ + g_hash_table_add (known_ids, g_strdup ("com.valvesoftware.Steam.desktop")); + /* Arch Linux Steam native */ + g_hash_table_add (known_ids, g_strdup ("steam-native.desktop")); + + for (this_app = app_list; this_app != NULL; this_app = this_app->next) + { + const char *id = NULL; + const char *commandline = NULL; + const char *filename = NULL; + gboolean is_default_handler = FALSE; + gboolean is_steam_handler = TRUE; + SrtDesktopEntry *entry = NULL; + + id = g_app_info_get_id (this_app->data); + commandline = g_app_info_get_commandline (this_app->data); + if (G_IS_DESKTOP_APP_INFO (this_app->data)) + filename = g_desktop_app_info_get_filename (this_app->data); + + if (id != NULL) + { + g_hash_table_add (found_handlers, g_strdup (id)); + is_default_handler = (g_strcmp0 (id, default_handler_id) == 0); + } + + entry = _srt_desktop_entry_new (id, commandline, filename, is_default_handler, is_steam_handler); + ret = g_list_prepend (ret, entry); + } + + g_list_free_full (app_list, g_object_unref); + + /* Get a list of all the desktop apps and filter them if they have a well known ID */ + app_list = g_app_info_get_all (); + for (this_app = app_list; this_app != NULL; this_app = this_app->next) + { + const char *id = NULL; + const char *commandline = NULL; + const char *filename = NULL; + gboolean is_default_handler = FALSE; + gboolean is_steam_handler = FALSE; + SrtDesktopEntry *entry = NULL; + + id = g_app_info_get_id (this_app->data); + if (id == NULL || !g_hash_table_contains (known_ids, id) || g_hash_table_contains (found_handlers, id)) + continue; + + commandline = g_app_info_get_commandline (this_app->data); + if (G_IS_DESKTOP_APP_INFO (this_app->data)) + filename = g_desktop_app_info_get_filename (this_app->data); + + is_default_handler = (id == default_handler_id); + + entry = _srt_desktop_entry_new (id, commandline, filename, is_default_handler, is_steam_handler); + ret = g_list_prepend (ret, entry); + } + + g_hash_table_unref (found_handlers); + g_hash_table_unref (known_ids); + g_list_free_full (app_list, g_object_unref); + if (default_handler != NULL) + g_object_unref (default_handler); + + return g_list_reverse (ret); +} diff --git a/steam-runtime-tools/desktop-entry.h b/steam-runtime-tools/desktop-entry.h new file mode 100644 index 0000000000000000000000000000000000000000..795f7b7d7ee4bc6330862f790d4500a836bf1de1 --- /dev/null +++ b/steam-runtime-tools/desktop-entry.h @@ -0,0 +1,50 @@ +/* + * 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> + +typedef struct _SrtDesktopEntry SrtDesktopEntry; +typedef struct _SrtDesktopEntryClass SrtDesktopEntryClass; + +#define SRT_TYPE_DESKTOP_ENTRY srt_desktop_entry_get_type () +#define SRT_DESKTOP_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SRT_TYPE_DESKTOP_ENTRY, SrtDesktopEntry)) +#define SRT_DESKTOP_ENTRY_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), SRT_TYPE_DESKTOP_ENTRY, SrtDesktopEntryClass)) +#define SRT_IS_DESKTOP_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SRT_TYPE_DESKTOP_ENTRY)) +#define SRT_IS_DESKTOP_ENTRY_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE ((cls), SRT_TYPE_DESKTOP_ENTRY)) +#define SRT_DESKTOP_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SRT_TYPE_DESKTOP_ENTRY, SrtDesktopEntryClass) +GType srt_desktop_entry_get_type (void); + +const char *srt_desktop_entry_get_id (SrtDesktopEntry *self); +const char *srt_desktop_entry_get_commandline (SrtDesktopEntry *self); +const char *srt_desktop_entry_get_filename (SrtDesktopEntry *self); +gboolean srt_desktop_entry_is_default_handler (SrtDesktopEntry *self); +gboolean srt_desktop_entry_is_steam_handler (SrtDesktopEntry *self); diff --git a/steam-runtime-tools/meson.build b/steam-runtime-tools/meson.build index 7f372c4f8225e21e3092953cb029a49fa455044a..4a3b4f3383175ed10350fd5b8e385c74277036cc 100644 --- a/steam-runtime-tools/meson.build +++ b/steam-runtime-tools/meson.build @@ -24,6 +24,8 @@ libsteamrt_sources = [ 'architecture-internal.h', 'architecture.c', + 'desktop-entry-internal.h', + 'desktop-entry.c', 'graphics-internal.h', 'graphics.c', 'library-internal.h', @@ -43,6 +45,7 @@ libsteamrt_sources = [ libsteamrt_public_headers = [ 'architecture.h', + 'desktop-entry.h', 'graphics.h', 'library.h', 'locale.h', @@ -76,7 +79,7 @@ libsteamrt = library( '-D_SRT_API_MAJOR="' + api_major + '"', ], include_directories : project_include_dirs, - dependencies : [libdl, threads, libelf, glib, gobject, json_glib], + dependencies : [libdl, threads, libelf, gio_unix, glib, gobject, json_glib], soversion : abi_major, version : abi_major + '.' + abi_minor, install : true, diff --git a/steam-runtime-tools/steam-runtime-tools.h b/steam-runtime-tools/steam-runtime-tools.h index 694c40a5f70a960995052aecb9381c6b0a6ad315..a2afe659f3f52ca47ccb88a785282bddad28c043 100644 --- a/steam-runtime-tools/steam-runtime-tools.h +++ b/steam-runtime-tools/steam-runtime-tools.h @@ -28,6 +28,7 @@ #define _SRT_IN_SINGLE_HEADER #include <steam-runtime-tools/architecture.h> +#include <steam-runtime-tools/desktop-entry.h> #include <steam-runtime-tools/enums.h> #include <steam-runtime-tools/graphics.h> #include <steam-runtime-tools/library.h> diff --git a/steam-runtime-tools/steam.c b/steam-runtime-tools/steam.c index de86addf8eec806d0619eb73bf0f4650c252c2f3..861e973c6f2d46757a798730897f3e7653dd789c 100644 --- a/steam-runtime-tools/steam.c +++ b/steam-runtime-tools/steam.c @@ -32,6 +32,7 @@ #include <string.h> #include <glib.h> +#include <gio/gio.h> #include "steam-runtime-tools/enums.h" #include "steam-runtime-tools/glib-compat.h" @@ -272,6 +273,9 @@ srt_steam_get_bin32_path (SrtSteam *self) * #SrtSteam object representing information about the current Steam * installation. Free with `g_object_unref()`. * + * Please note that @my_environ can't be used when checking the default + * desktop entry that handles `steam:` URIs. + * * Returns: A bitfield containing problems, or %SRT_STEAM_ISSUES_NONE * if no problems were found */ @@ -284,12 +288,18 @@ _srt_steam_check (const GStrv my_environ, gchar *dot_steam_steam = NULL; gchar *dot_steam_root = NULL; gchar *default_steam_path = NULL; + GAppInfo *default_app = NULL; char *install_path = NULL; char *data_path = NULL; char *bin32 = NULL; const char *home = NULL; const char *user_data = NULL; + const char *steam_script = NULL; + const char *commandline = NULL; + const char *executable = NULL; + const char *app_id = NULL; GStrv env = NULL; + GError *error = NULL; g_return_val_if_fail (_srt_check_not_setuid (), SRT_STEAM_ISSUES_INTERNAL_ERROR); @@ -467,7 +477,82 @@ _srt_steam_check (const GStrv my_environ, g_debug ("Found Steam data at %s", data_path); } + default_app = g_app_info_get_default_for_uri_scheme ("steam"); + + if (default_app == NULL) + { + g_debug ("There isn't a default app that can handle `steam:` URLs"); + issues |= SRT_STEAM_ISSUES_MISSING_STEAM_URI_HANDLER; + } + else + { + executable = g_app_info_get_executable (default_app); + commandline = g_app_info_get_commandline (default_app); + app_id = g_app_info_get_id (default_app); + gboolean found_expected_steam_uri_handler = FALSE; + + if (commandline != NULL) + { + gchar **argvp; + if (g_shell_parse_argv (commandline, NULL, &argvp, &error)) + { + const char *const expectations[] = { executable, "%U", NULL }; + gsize i; + for (i = 0; argvp[i] != NULL && expectations[i] != NULL; i++) + { + if (g_strcmp0 (argvp[i], expectations[i]) != 0) + break; + } + + if (argvp[i] == NULL && expectations[i] == NULL) + found_expected_steam_uri_handler = TRUE; + + g_strfreev (argvp); + } + else + { + g_debug ("Cannot parse \"Exec=%s\" like a shell would: %s", commandline, error->message); + g_clear_error (&error); + } + } + + /* Exclude the special case `/usr/bin/env steam %U` that we use in our unit tests */ + if (!found_expected_steam_uri_handler && (g_strcmp0 (commandline, "/usr/bin/env steam %U") != 0)) + issues |= SRT_STEAM_ISSUES_UNEXPECTED_STEAM_URI_HANDLER; + if (g_strcmp0 (app_id, "steam.desktop") != 0 && g_strcmp0 (app_id, "com.valvesoftware.Steam.desktop") != 0) + { + g_debug ("The default Steam app handler id is not what we expected: %s", app_id ? app_id : "NULL"); + issues |= SRT_STEAM_ISSUES_UNEXPECTED_STEAM_DESKTOP_ID; + } + } + + steam_script = g_environ_getenv (env, "STEAMSCRIPT"); + if (steam_script == NULL) + { + g_debug ("\"STEAMSCRIPT\" environment variable is missing"); + issues |= SRT_STEAM_ISSUES_STEAMSCRIPT_NOT_IN_ENVIRONMENT; + + if (executable != NULL && + g_strcmp0 (executable, "/usr/bin/steam") != 0 && + /* Arch Linux steam.desktop */ + g_strcmp0 (executable, "/usr/bin/steam-runtime") != 0 && + /* Debian steam.desktop */ + g_strcmp0 (executable, "/usr/games/steam")) + { + g_debug ("The default Steam app executable is not what we expected: %s", executable); + issues |= SRT_STEAM_ISSUES_UNEXPECTED_STEAM_URI_HANDLER; + } + } + else + { + if (g_strcmp0 (executable, steam_script) != 0) + { + g_debug ("Unexpectedly \"STEAMSCRIPT\" environment variable and the default Steam app " + "executable point to different paths: \"%s\" and \"%s\"", steam_script, executable); + issues |= SRT_STEAM_ISSUES_UNEXPECTED_STEAM_URI_HANDLER; + } + } if (more_details_out != NULL) *more_details_out = _srt_steam_new (issues, diff --git a/steam-runtime-tools/steam.h b/steam-runtime-tools/steam.h index a0597ce018087e00d0642473067831b210f9cb41..1037209edba8710c35ef40c03761d629a1f0958f 100644 --- a/steam-runtime-tools/steam.h +++ b/steam-runtime-tools/steam.h @@ -70,6 +70,16 @@ GType srt_steam_get_type (void); * neither a directory nor a symbolic link to a directory. * Steam is unlikely to work in this situation. * See srt_system_info_dup_steam_installation_path(). + * @SRT_STEAM_ISSUES_STEAMSCRIPT_NOT_IN_ENVIRONMENT: The environment + * `STEAMSCRIPT` is not set. Probably it's safe to be considered a minor + * issue. + * @SRT_STEAM_ISSUES_MISSING_STEAM_URI_HANDLER: There isn't a default desktop + * application that can handle `steam:` URIs. + * @SRT_STEAM_ISSUES_UNEXPECTED_STEAM_URI_HANDLER: The default Steam URI + * handler executable is either not what we expected or is different from the + * one `STEAMSCRIPT` points to. + * @SRT_STEAM_ISSUES_UNEXPECTED_STEAM_DESKTOP_ID: The default Steam desktop + * application ID is not what we expected. * * A bitfield with flags representing problems with the Steam * installation, or %SRT_STEAM_ISSUES_NONE (which is numerically zero) @@ -86,6 +96,10 @@ typedef enum SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY = (1 << 4), SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_SYMLINK = (1 << 5), SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_DIRECTORY = (1 << 6), + SRT_STEAM_ISSUES_STEAMSCRIPT_NOT_IN_ENVIRONMENT = (1 << 7), + SRT_STEAM_ISSUES_MISSING_STEAM_URI_HANDLER = (1 << 8), + SRT_STEAM_ISSUES_UNEXPECTED_STEAM_URI_HANDLER = (1 << 9), + SRT_STEAM_ISSUES_UNEXPECTED_STEAM_DESKTOP_ID = (1 << 10), SRT_STEAM_ISSUES_NONE = 0 } SrtSteamIssues; diff --git a/steam-runtime-tools/system-info.c b/steam-runtime-tools/system-info.c index 180f44831ea531c432e11c624b0351295b612b40..8136281f994e789b64f19d2bc5c88bd5bf29e733 100644 --- a/steam-runtime-tools/system-info.c +++ b/steam-runtime-tools/system-info.c @@ -27,6 +27,7 @@ #include "steam-runtime-tools/architecture.h" #include "steam-runtime-tools/architecture-internal.h" +#include "steam-runtime-tools/desktop-entry-internal.h" #include "steam-runtime-tools/glib-compat.h" #include "steam-runtime-tools/graphics.h" #include "steam-runtime-tools/graphics-internal.h" @@ -148,6 +149,11 @@ struct _SrtSystemInfo gchar *host_directory; gboolean have_data; } container; + struct + { + GList *values; + gboolean have_data; + } desktop_entry; SrtOsRelease os_release; SrtTestFlags test_flags; Tristate can_write_uinput; @@ -342,6 +348,14 @@ srt_system_info_set_property (GObject *object, } } +static void +forget_desktop_entries (SrtSystemInfo *self) +{ + self->desktop_entry.have_data = FALSE; + g_list_free_full (self->desktop_entry.values, g_object_unref); + self->desktop_entry.values = NULL; +} + /* * Forget any cached information about container information. */ @@ -441,6 +455,7 @@ srt_system_info_finalize (GObject *object) { SrtSystemInfo *self = SRT_SYSTEM_INFO (object); + forget_desktop_entries (self); forget_container_info (self); forget_icds (self); forget_locales (self); @@ -2969,3 +2984,53 @@ srt_system_info_dup_container_host_directory (SrtSystemInfo *self) ensure_container_info (self); return g_strdup (self->container.host_directory); } + +static void +ensure_desktop_entries (SrtSystemInfo *self) +{ + if (self->desktop_entry.have_data) + return; + + g_assert (self->desktop_entry.values == NULL); + + self->desktop_entry.values = _srt_list_steam_desktop_entries (); + self->desktop_entry.have_data = TRUE; +} + +/** + * srt_system_info_list_desktop_entries: + * @self: The #SrtSystemInfo object + * + * List all the available desktop applications that are able to handle the + * type "x-scheme-handler/steam". + * + * This function will also search for well known desktop applications ID like + * the Flathub `com.valvesoftware.Steam.desktop` and they'll be included even + * if they are not able to handle the `steam:` URI. + * Using `srt_desktop_entry_is_steam_handler()` it is possible to filter them out. + * + * The returned list is in no particular order. + * + * Please note that the environment variables of the current process will be used. + * Any possible custom environ set with `srt_system_info_set_environ()` will be + * ignored. + * + * Returns: (transfer full) (element-type SrtDesktopEntry) (nullable): A list of + * opaque #SrtDesktopEntry objects or %NULL if nothing was found. Free with + * `g_list_free_full (entries, g_object_unref)`. + */ +GList * +srt_system_info_list_desktop_entries (SrtSystemInfo *self) +{ + GList *ret = NULL; + const GList *iter; + + g_return_val_if_fail (SRT_IS_SYSTEM_INFO (self), NULL); + + ensure_desktop_entries (self); + + for (iter = self->desktop_entry.values; iter != NULL; iter = iter->next) + ret = g_list_prepend (ret, g_object_ref (iter->data)); + + return g_list_reverse (ret); +} diff --git a/steam-runtime-tools/system-info.h b/steam-runtime-tools/system-info.h index f860b5a0305114e9ec623f636aa295d61338e848..731d5d6227cad7450aafc3159ae760e04b4a9518 100644 --- a/steam-runtime-tools/system-info.h +++ b/steam-runtime-tools/system-info.h @@ -194,6 +194,8 @@ gchar *srt_system_info_dup_os_version_id (SrtSystemInfo *self); gchar **srt_system_info_list_driver_environment (SrtSystemInfo *self); +GList *srt_system_info_list_desktop_entries (SrtSystemInfo *self); + #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtSystemInfo, g_object_unref) #endif diff --git a/tests/desktop-entry.c b/tests/desktop-entry.c new file mode 100644 index 0000000000000000000000000000000000000000..82e7d2cc5e653fbe00a250cc89775a3b80114c04 --- /dev/null +++ b/tests/desktop-entry.c @@ -0,0 +1,192 @@ +/* + * 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/steam-runtime-tools.h> + +#include <string.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gstdio.h> + +#include "steam-runtime-tools/desktop-entry-internal.h" +#include "test-utils.h" +#include "fake-home.h" + +static const char *argv0; +static gchar *fake_home_path; + +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); + + /* We expect that fake_home already cleaned this up, but just to be sure we + * do it too */ + _srt_rm_rf (fake_home_path); +} + +/* + * Test basic functionality of the SrtDesktopEntry object. + */ +static void +test_object (Fixture *f, + gconstpointer context) +{ + SrtDesktopEntry *entry; + gchar *id = NULL; + gchar *cmd = NULL; + gchar *filename = NULL; + gboolean is_default = FALSE; + gboolean is_steam_handler = FALSE; + + entry = _srt_desktop_entry_new ("steam.desktop", + "/usr/bin/steam %U", + "/usr/share/applications/steam.desktop", + TRUE, + TRUE); + g_assert_cmpstr (srt_desktop_entry_get_id (entry), ==, "steam.desktop"); + g_assert_cmpstr (srt_desktop_entry_get_commandline (entry), ==, "/usr/bin/steam %U"); + g_assert_cmpstr (srt_desktop_entry_get_filename (entry), ==, "/usr/share/applications/steam.desktop"); + g_assert_cmpint (srt_desktop_entry_is_default_handler (entry), ==, TRUE); + g_assert_cmpint (srt_desktop_entry_is_steam_handler (entry), ==, TRUE); + g_object_get (entry, + "id", &id, + "commandline", &cmd, + "filename", &filename, + "is-default-handler", &is_default, + "is-steam-handler", &is_steam_handler, + NULL); + g_assert_cmpstr (id, ==, "steam.desktop"); + g_assert_cmpstr (cmd, ==, "/usr/bin/steam %U"); + g_assert_cmpstr (filename, ==, "/usr/share/applications/steam.desktop"); + g_assert_cmpint (is_default, ==, TRUE); + g_assert_cmpint (is_steam_handler, ==, TRUE); + g_free (id); + g_free (cmd); + g_free (filename); + g_object_unref (entry); +} + +static void +test_default_entry (Fixture *f, + gconstpointer context) +{ + SrtSystemInfo *info; + FakeHome *fake_home; + GList *desktop_entries = NULL; + const gchar *filename = NULL; + + fake_home = fake_home_new (fake_home_path); + fake_home->create_pinning_libs = FALSE; + fake_home->create_i386_folders = FALSE; + fake_home->create_amd64_folders = FALSE; + fake_home->create_root_symlink = FALSE; + fake_home->create_steam_symlink = FALSE; + fake_home->create_steamrt_files = FALSE; + fake_home->add_environments = FALSE; + fake_home_create_structure (fake_home); + + info = srt_system_info_new (NULL); + fake_home_apply_to_system_info (fake_home, info); + + desktop_entries = srt_system_info_list_desktop_entries (info); + g_assert_null (desktop_entries->next); + g_assert_cmpstr (srt_desktop_entry_get_id (desktop_entries->data), ==, "steam.desktop"); + g_assert_cmpstr (srt_desktop_entry_get_commandline (desktop_entries->data), ==, "/usr/bin/env steam %U"); + filename = srt_desktop_entry_get_filename (desktop_entries->data); + g_assert_true (g_str_has_prefix (filename, "/")); + g_assert_true (g_str_has_suffix (filename, "/steam.desktop")); + g_assert_true (srt_desktop_entry_is_default_handler (desktop_entries->data)); + g_assert_true (srt_desktop_entry_is_steam_handler (desktop_entries->data)); + + g_list_free_full (desktop_entries, g_object_unref); + + /* Do the check again, this time using the cache */ + desktop_entries = srt_system_info_list_desktop_entries (info); + g_assert_null (desktop_entries->next); + g_assert_cmpstr (srt_desktop_entry_get_id (desktop_entries->data), ==, "steam.desktop"); + g_assert_cmpstr (srt_desktop_entry_get_commandline (desktop_entries->data), ==, "/usr/bin/env steam %U"); + filename = srt_desktop_entry_get_filename (desktop_entries->data); + g_assert_true (g_str_has_prefix (filename, "/")); + g_assert_true (g_str_has_suffix (filename, "/steam.desktop")); + g_assert_true (srt_desktop_entry_is_default_handler (desktop_entries->data)); + g_assert_true (srt_desktop_entry_is_steam_handler (desktop_entries->data)); + + g_list_free_full (desktop_entries, g_object_unref); + fake_home_clean_up (fake_home); + g_clear_object (&info); +} + +int +main (int argc, + char **argv) +{ + int status; + argv0 = argv[0]; + + /* We can't use %G_TEST_OPTION_ISOLATE_DIRS because we are targeting an older glib verion. + * As a workaround we use our function `setup_env()`. + * We can't use an environ because in `_srt_steam_check()` we call `g_app_info_get_*` that + * doesn't support a custom environ. */ + g_test_init (&argc, &argv, NULL); + fake_home_path = _srt_global_setup_private_xdg_dirs (); + + g_test_add ("/desktop-entry/object", Fixture, NULL, + setup, test_object, teardown); + g_test_add ("/desktop-entry/default_entry", Fixture, NULL, + setup, test_default_entry, teardown); + + status = g_test_run (); + + if (!_srt_global_teardown_private_xdg_dirs ()) + g_debug ("Unable to remove the fake home parent directory of: %s", fake_home_path); + + return status; +} diff --git a/tests/fake-home.c b/tests/fake-home.c index d10ec2cfaeb9e4048bf18cf81ebcb59063bd9e21..7bb855b9e9e651871821fc49598f8b6aac2b5720 100644 --- a/tests/fake-home.c +++ b/tests/fake-home.c @@ -37,18 +37,34 @@ /** * fake_home_new: + * @home: (nullable) (type filename): Path to a temporary directory that will + * be used, which must be in a trusted directory (not `/tmp` or `/var/tmp`). + * If it doesn't exist yet, it will be created. If @home is %NULL a + * random temporary directory will be used instead. * * Create a new #FakeHome object and a temporary folder in the file system * * Returns: the newely created #FakeHome object */ FakeHome * -fake_home_new (void) +fake_home_new (const gchar *home) { GError *error = NULL; FakeHome *fake_home = g_slice_new (FakeHome); - fake_home->home = g_dir_make_tmp ("fake-home-XXXXXX", &error); - g_assert_no_error (error); + if (home) + { + gchar *dirname = g_path_get_dirname (home); + g_assert_cmpstr (dirname, !=, "/tmp"); + g_assert_cmpstr (dirname, !=, "/var/tmp"); + fake_home->home = g_strdup (home); + g_assert_cmpint (g_mkdir_with_parents (fake_home->home, 0755), ==, 0); + g_free (dirname); + } + else + { + fake_home->home = g_dir_make_tmp ("fake-home-XXXXXX", &error); + g_assert_no_error (error); + } fake_home->create_pinning_libs = TRUE; fake_home->create_i386_folders = TRUE; @@ -59,6 +75,7 @@ fake_home_new (void) fake_home->add_environments = TRUE; fake_home->has_debian_bug_916303 = FALSE; fake_home->testing_beta_client = FALSE; + fake_home->create_steam_mime_apps = TRUE; return fake_home; } @@ -90,9 +107,33 @@ fake_home_create_structure (FakeHome *f) gchar *ld_path = NULL; gchar *prepended_path = NULL; gchar *ubuntu12_32 = NULL; + gchar *app_home = NULL; + gchar *data_name = NULL; GError *error = NULL; int saved_errno; +/* Instead of `/usr/bin/steam` we set Exec to `/usr/bin/env steam` because it + * needs to point to something that we are sure to have */ +const gchar *steam_data = + "[Desktop Entry]\n" + "Name=Steam\n" + "Exec=/usr/bin/env steam %U\n" + "Type=Application\n" + "Actions=Library;\n" + "MimeType=x-scheme-handler/steam;\n" + "\n" + "[Desktop Action Library]\n" + "Name=Library\n" + "Exec=steam steam://open/games"; + +const gchar *mime_list = + "[Default Applications]\n" + "x-scheme-handler/steam=steam.desktop;\n"; + +const gchar *mime_cache = + "[MIME Cache]\n" + "x-scheme-handler/steam=steam.desktop;\n"; + g_return_val_if_fail (f != NULL, FALSE); dot_steam = g_build_filename (f->home, ".steam", NULL); @@ -274,6 +315,31 @@ fake_home_create_structure (FakeHome *f) f->env = g_environ_setenv (f->env, "PATH", prepended_path, TRUE); } + if (f->create_steam_mime_apps) + { + app_home = g_build_filename (local_share, "applications", NULL); + if (g_mkdir_with_parents (app_home, 0755) != 0) + goto out; + + /* Instead of `/usr/bin/steam` we set it to `/usr/bin/env`, like the "steam.desktop" + * that we are going to create */ + f->env = g_environ_setenv (f->env, "STEAMSCRIPT", "/usr/bin/env", TRUE); + + data_name = g_build_filename (app_home, "steam.desktop", NULL); + g_file_set_contents (data_name, steam_data, -1, &error); + g_assert_no_error (error); + g_free (data_name); + + data_name = g_build_filename (app_home, "mimeapps.list", NULL); + g_file_set_contents (data_name, mime_list, -1, &error); + g_assert_no_error (error); + g_free (data_name); + + data_name = g_build_filename (app_home, "mimeinfo.cache", NULL); + g_file_set_contents (data_name, mime_cache, -1, &error); + g_assert_no_error (error); + } + ret = TRUE; out: @@ -290,6 +356,8 @@ fake_home_create_structure (FakeHome *f) g_free (ld_path); g_free (prepended_path); g_free (ubuntu12_32); + g_free (app_home); + g_free (data_name); if (!ret) g_warning ("Unable to create directories: %s", g_strerror (saved_errno)); diff --git a/tests/fake-home.h b/tests/fake-home.h index bf589aa4cc4a86d0c77b0cb90bea6db6b2bd7684..2c74f07a2a328c5b0e7311c6075dd869eb00e090 100644 --- a/tests/fake-home.h +++ b/tests/fake-home.h @@ -41,6 +41,7 @@ typedef struct gboolean add_environments; gboolean has_debian_bug_916303; gboolean testing_beta_client; + gboolean create_steam_mime_apps; gchar *home; gchar *steam_install; @@ -63,7 +64,7 @@ typedef struct GStrv env; } FakeHome; -FakeHome * fake_home_new (void); +FakeHome * fake_home_new (const gchar *home); gboolean fake_home_create_structure (FakeHome *fake_home); void fake_home_clean_up (FakeHome *f); void fake_home_apply_to_system_info (FakeHome *fake_home, diff --git a/tests/meson.build b/tests/meson.build index 9f03b5b25b62687c05f3eb5bb03534648d903f9c..cb8183b1907352588d1a09197dc8a554ac584027 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -29,6 +29,7 @@ test_env.prepend('PATH', join_paths(meson.current_build_dir(), '..', 'bin')) tests = [ 'architecture', + 'desktop-entry', 'graphics', 'library', 'locale', @@ -39,6 +40,7 @@ tests = [ tests_utils = [ 'fake-home.c', + 'test-utils.c', ] tests_dir = join_paths( diff --git a/tests/system-info-cli.c b/tests/system-info-cli.c index 12051e8725c1f70c10edc5246e6c4c4d8765b424..050220b88c4b3dd94b13a205e494c9b0ead1e7bf 100644 --- a/tests/system-info-cli.c +++ b/tests/system-info-cli.c @@ -500,7 +500,7 @@ steam_presence (Fixture *f, const gchar *argv[] = { "steam-runtime-system-info", NULL }; FakeHome *fake_home; - fake_home = fake_home_new (); + fake_home = fake_home_new (NULL); fake_home_create_structure (fake_home); result = g_spawn_sync (NULL, /* working directory */ @@ -594,7 +594,7 @@ steam_issues (Fixture *f, const gchar *argv[] = { "steam-runtime-system-info", NULL }; FakeHome *fake_home; - fake_home = fake_home_new (); + fake_home = fake_home_new (NULL); fake_home->create_pinning_libs = FALSE; fake_home->create_steam_symlink = FALSE; fake_home->create_steamrt_files = FALSE; diff --git a/tests/system-info.c b/tests/system-info.c index 6d6baa17c4d66fe7a38d01fd2aa87fa4eb25a969..7905874e465197e00ead9b929631e92fd37c10ce 100644 --- a/tests/system-info.c +++ b/tests/system-info.c @@ -42,6 +42,7 @@ #include "fake-home.h" static const char *argv0; +static gchar *fake_home_path; typedef struct { @@ -78,6 +79,10 @@ teardown (Fixture *f, g_free (f->srcdir); g_free (f->builddir); + + /* We expect that fake_home already cleaned this up, but just to be sure we + * do it too */ + _srt_rm_rf (fake_home_path); } /* @@ -732,7 +737,7 @@ steam_runtime (Fixture *f, gchar *installation_path = NULL; FakeHome *fake_home; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home_create_structure (fake_home); info = srt_system_info_new (NULL); @@ -758,11 +763,11 @@ steam_runtime (Fixture *f, /* Check for Steam issues */ steam_issues = srt_system_info_get_steam_issues (info); - g_assert_cmpint (steam_issues, ==, SRT_RUNTIME_ISSUES_NONE); + g_assert_cmpint (steam_issues, ==, SRT_STEAM_ISSUES_NONE); /* Do the check again, this time using the cache */ steam_issues = srt_system_info_get_steam_issues (info); - g_assert_cmpint (steam_issues, ==, SRT_RUNTIME_ISSUES_NONE); + g_assert_cmpint (steam_issues, ==, SRT_STEAM_ISSUES_NONE); fake_home_clean_up (fake_home); g_object_unref (info); @@ -780,7 +785,7 @@ steam_runtime_missing (Fixture *f, gchar *full_ld_path = NULL; FakeHome *fake_home; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home_create_structure (fake_home); full_ld_path = g_strdup (g_environ_getenv (fake_home->env, "LD_LIBRARY_PATH")); @@ -825,7 +830,7 @@ steam_runtime_pinned (Fixture *f, gchar *ld_path = NULL; FakeHome *fake_home; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home_create_structure (fake_home); full_ld_path = g_strdup (g_environ_getenv (fake_home->env, "LD_LIBRARY_PATH")); @@ -890,7 +895,7 @@ runtime_disabled_or_missing (Fixture *f, gchar *runtime_path = NULL; FakeHome *fake_home; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home->create_steamrt_files = FALSE; fake_home_create_structure (fake_home); @@ -944,7 +949,7 @@ runtime_version (Fixture *f, GError *error = NULL; FakeHome *fake_home; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home_create_structure (fake_home); version = g_build_filename (fake_home->runtime, "version.txt", NULL); @@ -1058,7 +1063,7 @@ runtime_unexpected_location (Fixture *f, GError *error = NULL; FakeHome *fake_home; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home->create_root_symlink = FALSE; fake_home_create_structure (fake_home); @@ -1131,7 +1136,7 @@ steam_symlink (Fixture *f, GError *error = NULL; FakeHome *fake_home; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home->create_steam_symlink = FALSE; fake_home_create_structure (fake_home); @@ -1200,7 +1205,7 @@ debian_bug_916303 (Fixture *f, gchar *installation_path; gchar *data_path; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home->has_debian_bug_916303 = TRUE; fake_home_create_structure (fake_home); @@ -1233,7 +1238,7 @@ testing_beta_client (Fixture *f, gchar *installation_path; gchar *data_path; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home->testing_beta_client = TRUE; fake_home_create_structure (fake_home); @@ -1837,7 +1842,7 @@ pinned_libraries (Fixture *f, gsize i; GError *error = NULL; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home_create_structure (fake_home); info = srt_system_info_new (NULL); @@ -1985,7 +1990,7 @@ pinned_libraries_permission (Fixture *f, gboolean seen_no_access; gsize i; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home_create_structure (fake_home); info = srt_system_info_new (NULL); @@ -2069,7 +2074,7 @@ pinned_libraries_missing (Fixture *f, gchar **values = NULL; gchar **messages = NULL; - fake_home = fake_home_new (); + fake_home = fake_home_new (fake_home_path); fake_home_create_structure (fake_home); info = srt_system_info_new (NULL); @@ -2255,9 +2260,16 @@ int main (int argc, char **argv) { + int status; argv0 = argv[0]; + /* We can't use %G_TEST_OPTION_ISOLATE_DIRS because we are targeting an older glib verion. + * As a workaround we use our function `setup_env()`. + * We can't use an environ because in `_srt_steam_check()` we call `g_app_info_get_*` that + * doesn't support a custom environ. */ g_test_init (&argc, &argv, NULL); + fake_home_path = _srt_global_setup_private_xdg_dirs (); + g_test_add ("/system-info/object", Fixture, NULL, setup, test_object, teardown); g_test_add ("/system-info/libraries_presence", Fixture, NULL, @@ -2324,5 +2336,10 @@ main (int argc, g_test_add ("/system-info/containers", Fixture, NULL, setup, test_containers, teardown); - return g_test_run (); + status = g_test_run (); + + if (!_srt_global_teardown_private_xdg_dirs ()) + g_debug ("Unable to remove the fake home parent directory of: %s", fake_home_path); + + return status; } diff --git a/tests/test-utils.c b/tests/test-utils.c new file mode 100644 index 0000000000000000000000000000000000000000..c3a6dded9d3da0343345ee8f5488902c522b7bd6 --- /dev/null +++ b/tests/test-utils.c @@ -0,0 +1,94 @@ +/* + * 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 "test-utils.h" + +#include <glib-object.h> +#include "steam-runtime-tools/glib-compat.h" +#include <steam-runtime-tools/steam-runtime-tools.h> + +static gchar *fake_home_parent = NULL; + +/** + * _srt_global_setup_private_xdg_dirs: + * + * Setup a fake home directory and, following the [XDG standard] + * (https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-1.0.html) + * mask every XDG used in the MIME lookup to avoid altering the tests if there + * were already other user defined MIME lists. + * + * Call this function one time before launching the tests because changing the + * environment variables is not thread safe. + * + * Returns: (type filename) (transfer full): Absolute path to the newly created + * fake home directory. + */ +gchar * +_srt_global_setup_private_xdg_dirs (void) +{ + GError *error = NULL; + gchar *fake_home_path = NULL; + gchar *xdg_data_home = NULL; + + g_return_val_if_fail (fake_home_parent == NULL, NULL); + + /* Create a directory that we control, and then put the fake home + * directory inside it, so we can delete and recreate the fake home + * directory without being vulnerable to symlink attacks. */ + fake_home_parent = g_dir_make_tmp ("fake-home-XXXXXX", &error); + g_assert_no_error (error); + fake_home_path = g_build_filename (fake_home_parent, "home", NULL); + + xdg_data_home = g_build_filename (fake_home_path, ".local", "share", NULL); + + g_setenv ("XDG_CONFIG_HOME", xdg_data_home, TRUE); + g_setenv ("XDG_CONFIG_DIRS", xdg_data_home, TRUE); + g_setenv ("XDG_DATA_HOME", xdg_data_home, TRUE); + g_setenv ("XDG_DATA_DIRS", xdg_data_home, TRUE); + + g_free (xdg_data_home); + + return fake_home_path; +} + +/** + * _srt_global_teardown_private_xdg_dirs: + * + * Teardown the previously created temporary directory. + * + * Returns: %TRUE if no errors occurred removing the temporary directory. + */ +gboolean +_srt_global_teardown_private_xdg_dirs (void) +{ + gboolean result; + g_return_val_if_fail (fake_home_parent != NULL, FALSE); + + result = _srt_rm_rf (fake_home_parent); + g_free (fake_home_parent); + fake_home_parent = NULL; + + return result; +} \ No newline at end of file diff --git a/tests/test-utils.h b/tests/test-utils.h index e963a25495cc255fcff308d79ac4162e1ff079f8..2973e2bc720ad216d58e6319635d8e18d262bcbc 100644 --- a/tests/test-utils.h +++ b/tests/test-utils.h @@ -64,3 +64,6 @@ #if !GLIB_CHECK_VERSION(2, 38, 0) #define g_test_skip(msg) g_test_message ("SKIP: %s", msg) #endif + +gchar *_srt_global_setup_private_xdg_dirs (void); +gboolean _srt_global_teardown_private_xdg_dirs (void);