diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index d7832bc24..c50b3cd56 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -226,7 +226,8 @@ elseif(UNIX) endif() if(GIO_FOUND) set(libobs_PLATFORM_SOURCES ${libobs_PLATFORM_SOURCES} - util/platform-nix-dbus.c) + util/platform-nix-dbus.c + util/platform-nix-portal.c) include_directories(${GIO_INCLUDE_DIRS}) add_definitions( ${GIO_DEFINITIONS}) diff --git a/libobs/util/platform-nix-portal.c b/libobs/util/platform-nix-portal.c new file mode 100644 index 000000000..d86eeccb2 --- /dev/null +++ b/libobs/util/platform-nix-portal.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2015 Hugh Bailey + * 2021 Georges Basile Stavracas Neto + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include "bmem.h" +#include "dstr.h" + +#define PORTAL_NAME "org.freedesktop.portal.Desktop" + +struct portal_inhibit_info { + GDBusConnection *c; + unsigned int signal_id; + char *sender_name; + char *request_path; + bool active; +}; + +static void new_request(struct portal_inhibit_info *info, char **out_token, + char **out_path) +{ + struct dstr token; + struct dstr path; + uint32_t id; + + id = rand(); + + dstr_init(&token); + dstr_printf(&token, "obs_inhibit_portal%u", id); + *out_token = token.array; + + dstr_init(&path); + dstr_printf(&path, "/org/freedesktop/portal/desktop/request/%s/%s", + info->sender_name, token.array); + *out_path = path.array; +} + +static inline void unsubscribe_from_request(struct portal_inhibit_info *info) +{ + if (info->signal_id > 0) { + g_dbus_connection_signal_unsubscribe(info->c, info->signal_id); + info->signal_id = 0; + } +} + +static inline void remove_inhibit_data(struct portal_inhibit_info *info) +{ + g_clear_pointer(&info->request_path, bfree); + info->active = false; +} + +static void response_received(GDBusConnection *bus, const char *sender_name, + const char *object_path, + const char *interface_name, + const char *signal_name, GVariant *parameters, + gpointer data) +{ + UNUSED_PARAMETER(bus); + UNUSED_PARAMETER(sender_name); + UNUSED_PARAMETER(object_path); + UNUSED_PARAMETER(interface_name); + UNUSED_PARAMETER(signal_name); + + struct portal_inhibit_info *info = data; + g_autoptr(GVariant) ret = NULL; + uint32_t response; + + g_variant_get(parameters, "(u@a{sv})", &response, &ret); + + if (response != 0) { + if (response == 1) + blog(LOG_WARNING, "Inhibit denied by user"); + + remove_inhibit_data(info); + } + + unsubscribe_from_request(info); +} + +static void do_inhibit(struct portal_inhibit_info *info, const char *reason) +{ + g_autoptr(GVariant) reply = NULL; + g_autoptr(GError) error = NULL; + GVariantBuilder options; + uint32_t flags = 0xC; + char *token; + + info->active = true; + + new_request(info, &token, &info->request_path); + + info->signal_id = g_dbus_connection_signal_subscribe( + info->c, PORTAL_NAME, "org.freedesktop.portal.Request", + "Response", info->request_path, NULL, + G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, response_received, info, + NULL); + + g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&options, "{sv}", "handle_token", + g_variant_new_string(token)); + g_variant_builder_add(&options, "{sv}", "reason", + g_variant_new_string(reason)); + + bfree(token); + + reply = g_dbus_connection_call_sync( + info->c, PORTAL_NAME, "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Inhibit", "Inhibit", + g_variant_new("(sua{sv})", "", flags, &options), NULL, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); + + if (error != NULL) { + blog(LOG_ERROR, "Failed to inhibit: %s", error->message); + unsubscribe_from_request(info); + remove_inhibit_data(info); + return; + } +} + +static void do_uninhibit(struct portal_inhibit_info *info) +{ + g_autoptr(GError) error = NULL; + + g_dbus_connection_call_sync(info->c, PORTAL_NAME, info->request_path, + "org.freedesktop.portal.Request", "Close", + g_variant_new("()"), G_VARIANT_TYPE_UNIT, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); + + remove_inhibit_data(info); + + if (error) + blog(LOG_WARNING, "Error uninhibiting: %s", error->message); +} + +void portal_inhibit_info_destroy(struct portal_inhibit_info *info) +{ + if (info) { + unsubscribe_from_request(info); + remove_inhibit_data(info); + g_clear_pointer(&info->sender_name, bfree); + g_clear_object(&info->c); + bfree(info); + } +} + +struct portal_inhibit_info *portal_inhibit_info_create(void) +{ + struct portal_inhibit_info *info = bzalloc(sizeof(*info)); + g_autoptr(GVariant) reply = NULL; + g_autoptr(GError) error = NULL; + char *aux; + + info->c = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error); + if (!info->c) { + blog(LOG_ERROR, "Could not create dbus connection: %s", + error->message); + bfree(info); + return NULL; + } + + info->sender_name = + bstrdup(g_dbus_connection_get_unique_name(info->c) + 1); + while ((aux = strstr(info->sender_name, ".")) != NULL) + *aux = '_'; + + reply = g_dbus_connection_call_sync( + info->c, "org.freedesktop.DBus", "/org/freedesktop/DBus", + "org.freedesktop.DBus", "GetNameOwner", + g_variant_new("(s)", PORTAL_NAME), NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, NULL); + + if (reply != NULL) { + blog(LOG_DEBUG, "Found portal inhibitor"); + return info; + } + + portal_inhibit_info_destroy(info); + return NULL; +} + +void portal_inhibit(struct portal_inhibit_info *info, const char *reason, + bool active) +{ + if (active == info->active) + return; + + if (active) + do_inhibit(info, reason); + else + do_uninhibit(info); +} diff --git a/libobs/util/platform-nix.c b/libobs/util/platform-nix.c index 76e87e7b6..431ddfd29 100644 --- a/libobs/util/platform-nix.c +++ b/libobs/util/platform-nix.c @@ -631,16 +631,23 @@ int os_chdir(const char *path) #if HAVE_DBUS struct dbus_sleep_info; +struct portal_inhibit_info; extern struct dbus_sleep_info *dbus_sleep_info_create(void); extern void dbus_inhibit_sleep(struct dbus_sleep_info *dbus, const char *sleep, bool active); extern void dbus_sleep_info_destroy(struct dbus_sleep_info *dbus); + +extern struct portal_inhibit_info *portal_inhibit_info_create(void); +extern void portal_inhibit(struct portal_inhibit_info *portal, + const char *reason, bool active); +extern void portal_inhibit_info_destroy(struct portal_inhibit_info *portal); #endif struct os_inhibit_info { #if HAVE_DBUS struct dbus_sleep_info *dbus; + struct portal_inhibit_info *portal; #endif pthread_t screensaver_thread; os_event_t *stop_event; @@ -655,7 +662,9 @@ os_inhibit_t *os_inhibit_sleep_create(const char *reason) sigset_t set; #if HAVE_DBUS - info->dbus = dbus_sleep_info_create(); + info->portal = portal_inhibit_info_create(); + if (!info->portal) + info->dbus = dbus_sleep_info_create(); #endif os_event_init(&info->stop_event, OS_EVENT_TYPE_AUTO); @@ -710,6 +719,8 @@ bool os_inhibit_sleep_set_active(os_inhibit_t *info, bool active) return false; #if HAVE_DBUS + if (info->portal) + portal_inhibit(info->portal, info->reason, active); if (info->dbus) dbus_inhibit_sleep(info->dbus, info->reason, active); #endif @@ -739,6 +750,7 @@ void os_inhibit_sleep_destroy(os_inhibit_t *info) if (info) { os_inhibit_sleep_set_active(info, false); #if HAVE_DBUS + portal_inhibit_info_destroy(info->portal); dbus_sleep_info_destroy(info->dbus); #endif os_event_destroy(info->stop_event);