/* * Copyright © 2017 Red Hat, Inc * Copyright © 2021 Collabora Ltd. * * SPDX-License-Identifier: LGPL-2.1-or-later * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, see <http://www.gnu.org/licenses/>. * * Authors: * Florian Müllner <fmuellner@gnome.org> * Matthias Clasen <mclasen@redhat.com> */ /* * Alternative executable to the canonical 'xdg-open' with a better handling * of Steam's URLs. * Loosely based on the xdg-open implementation of flatpak-xdg-utils. */ #include <libglnx.h> #include "steam-runtime-tools/glib-backports-internal.h" #include <glib.h> #include <glib-object.h> #include <gio/gunixfdlist.h> #include <steam-runtime-tools/utils-internal.h> typedef GUnixFDList AutoUnixFDList; G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoUnixFDList, g_object_unref) static gchar **uris = NULL; static gboolean opt_print_help = FALSE; static gboolean opt_print_version = FALSE; static const GOptionEntry option_entries[] = { /* Imitate the allowed options of the real 'xdg-open' */ { "manual", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_print_help, NULL, NULL }, { "version", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_print_version, "Print version number and exit", NULL }, { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, &uris, NULL, NULL }, { NULL } }; static gboolean open_with_portal (const char *uri_or_filename, GError **error) { g_autoptr(GDBusConnection) connection = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); if (connection != NULL) { const gchar *portal_bus_name = "org.freedesktop.portal.Desktop"; const gchar *portal_object_path = "/org/freedesktop/portal/desktop"; const gchar *portal_iface_name = "org.freedesktop.portal.OpenURI"; const gchar *method_name = NULL; GVariant *arguments = NULL; /* floating */ GVariantBuilder opt_builder; g_autoptr(AutoUnixFDList) fd_list = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GVariant) result = NULL; g_debug ("Trying the D-Bus desktop portal"); g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); file = g_file_new_for_commandline_arg (uri_or_filename); if (g_file_is_native (file)) { /* The canonical 'xdg-open' also handles paths. We try to replicate * that too, but it might not always work because the container * inside and outside filesystem structure might be different. */ g_autofree gchar *path = NULL; int fd; path = g_file_get_path (file); fd = open (path, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); if (fd < 0) { return glnx_throw_errno_prefix (error, "Failed to open '%s'", path); } fd_list = g_unix_fd_list_new_from_array (&fd, 1); /* adopts the fd */ arguments = g_variant_new ("(sh@a{sv})", "", 0, g_variant_builder_end (&opt_builder)); method_name = "OpenFile"; } else { arguments = g_variant_new ("(ss@a{sv})", "", uri_or_filename, g_variant_builder_end (&opt_builder)); method_name = "OpenURI"; } result = g_dbus_connection_call_with_unix_fd_list_sync (connection, portal_bus_name, portal_object_path, portal_iface_name, method_name, /* sinks floating reference */ g_steal_pointer (&arguments), NULL, G_DBUS_CALL_FLAGS_NONE, -1, fd_list, NULL, NULL, error); if (result == NULL) return glnx_prefix_error (error, "Unable to open URL with xdg-desktop-portal"); else return TRUE; } else { return glnx_prefix_error (error, "Unable to connect to D-Bus session bus"); } } int main (int argc, char **argv) { const gchar *uri; g_autofree gchar *scheme = NULL; g_autoptr(GOptionContext) option_context = NULL; g_autoptr(GError) portal_error = NULL; g_autoptr(GError) error = NULL; g_set_prgname ("steam-runtime-urlopen"); option_context = g_option_context_new ("{ file | URL }"); g_option_context_add_main_entries (option_context, option_entries, NULL); if (!g_option_context_parse (option_context, &argc, &argv, &error)) { g_printerr ("%s: %s\n", g_get_prgname (), error->message); return 1; } if (opt_print_version) { /* Simply print the version number, similarly to the real xdg-open */ g_print ("%s\n", VERSION); return EXIT_SUCCESS; } if (uris == NULL || g_strv_length (uris) > 1) { g_autofree gchar *help = g_option_context_get_help (option_context, TRUE, NULL); g_print ("%s\n", help); return 1; } /* In reality this could also be a path, but we call it "uri" for simplicity */ uri = uris[0]; scheme = g_uri_parse_scheme (uri); if (scheme != NULL && (g_ascii_strcasecmp (scheme, "steamlink") == 0 || g_ascii_strcasecmp (scheme, "steam") == 0)) { g_debug ("Passing the URL '%s' to the Steam pipe", uri); if (!_srt_steam_command_via_pipe (&uri, 1, &error)) { g_printerr ("%s: %s\n", g_get_prgname (), error->message); return 4; } return EXIT_SUCCESS; } if (open_with_portal (uri, &portal_error)) return EXIT_SUCCESS; if (scheme != NULL && (g_ascii_strcasecmp (scheme, "http") == 0 || g_ascii_strcasecmp (scheme, "https") == 0)) { g_autofree gchar *steam_url = NULL; steam_url = g_strjoin ("/", "steam://openurl", uri, NULL); g_debug ("Passing the URL '%s' to the Steam pipe", steam_url); if (!_srt_steam_command_via_pipe ((const gchar **) &steam_url, 1, &error)) { g_printerr ("%s: %s\n", g_get_prgname (), error->message); return 4; } return EXIT_SUCCESS; } g_printerr ("%s: %s\n", g_get_prgname (), portal_error->message); return 4; }