Bläddra i källkod

libobs: Add portal inhibitor

XDG Portals provide a plethora of features meant to be used inside and
outside sandboxed environments. OBS Studio currently uses portals to
implement Wayland-compatible monitor and window captures.

However, OBS Studio performs another action that can be done through
portals: inhibit the screensaver. Under the Desktop portal (the same
used by the captures mentioned above), there is an "Inhibit" portal
that provides session inhibition.

Add a new portal-based inhibitor. This inhibitor is only used when the
Desktop portal is available and running; the previous D-Bus implementation
is used in the absence of the portal. Because it's basically another set
of D-Bus operations, wrap the new portal inhibitor under the HAVE_DBUS
call too.
Georges Basile Stavracas Neto 4 år sedan
förälder
incheckning
99559aab5a
3 ändrade filer med 220 tillägg och 2 borttagningar
  1. 2 1
      libobs/CMakeLists.txt
  2. 205 0
      libobs/util/platform-nix-portal.c
  3. 13 1
      libobs/util/platform-nix.c

+ 2 - 1
libobs/CMakeLists.txt

@@ -226,7 +226,8 @@ elseif(UNIX)
 	endif()
 	endif()
 	if(GIO_FOUND)
 	if(GIO_FOUND)
 		set(libobs_PLATFORM_SOURCES ${libobs_PLATFORM_SOURCES}
 		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})
 		include_directories(${GIO_INCLUDE_DIRS})
 		add_definitions(
 		add_definitions(
 			${GIO_DEFINITIONS})
 			${GIO_DEFINITIONS})

+ 205 - 0
libobs/util/platform-nix-portal.c

@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2015 Hugh Bailey <[email protected]>
+ *               2021 Georges Basile Stavracas Neto <[email protected]>
+ *
+ * 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 <assert.h>
+#include <gio/gio.h>
+#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);
+}

+ 13 - 1
libobs/util/platform-nix.c

@@ -631,16 +631,23 @@ int os_chdir(const char *path)
 
 
 #if HAVE_DBUS
 #if HAVE_DBUS
 struct dbus_sleep_info;
 struct dbus_sleep_info;
+struct portal_inhibit_info;
 
 
 extern struct dbus_sleep_info *dbus_sleep_info_create(void);
 extern struct dbus_sleep_info *dbus_sleep_info_create(void);
 extern void dbus_inhibit_sleep(struct dbus_sleep_info *dbus, const char *sleep,
 extern void dbus_inhibit_sleep(struct dbus_sleep_info *dbus, const char *sleep,
 			       bool active);
 			       bool active);
 extern void dbus_sleep_info_destroy(struct dbus_sleep_info *dbus);
 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
 #endif
 
 
 struct os_inhibit_info {
 struct os_inhibit_info {
 #if HAVE_DBUS
 #if HAVE_DBUS
 	struct dbus_sleep_info *dbus;
 	struct dbus_sleep_info *dbus;
+	struct portal_inhibit_info *portal;
 #endif
 #endif
 	pthread_t screensaver_thread;
 	pthread_t screensaver_thread;
 	os_event_t *stop_event;
 	os_event_t *stop_event;
@@ -655,7 +662,9 @@ os_inhibit_t *os_inhibit_sleep_create(const char *reason)
 	sigset_t set;
 	sigset_t set;
 
 
 #if HAVE_DBUS
 #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
 #endif
 
 
 	os_event_init(&info->stop_event, OS_EVENT_TYPE_AUTO);
 	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;
 		return false;
 
 
 #if HAVE_DBUS
 #if HAVE_DBUS
+	if (info->portal)
+		portal_inhibit(info->portal, info->reason, active);
 	if (info->dbus)
 	if (info->dbus)
 		dbus_inhibit_sleep(info->dbus, info->reason, active);
 		dbus_inhibit_sleep(info->dbus, info->reason, active);
 #endif
 #endif
@@ -739,6 +750,7 @@ void os_inhibit_sleep_destroy(os_inhibit_t *info)
 	if (info) {
 	if (info) {
 		os_inhibit_sleep_set_active(info, false);
 		os_inhibit_sleep_set_active(info, false);
 #if HAVE_DBUS
 #if HAVE_DBUS
+		portal_inhibit_info_destroy(info->portal);
 		dbus_sleep_info_destroy(info->dbus);
 		dbus_sleep_info_destroy(info->dbus);
 #endif
 #endif
 		os_event_destroy(info->stop_event);
 		os_event_destroy(info->stop_event);