Browse Source

Merge pull request #6645 from GeorgesStavracas/gbsneto/pipewire-cleanup

Various cleanups to linux-pipewire
Jim 3 years ago
parent
commit
7c493ea035

+ 3 - 3
plugins/linux-pipewire/CMakeLists.txt

@@ -35,10 +35,10 @@ target_sources(
   PRIVATE linux-pipewire.c
           pipewire.c
           pipewire.h
-          pipewire-capture.c
-          pipewire-capture.h
           portal.c
-          portal.h)
+          portal.h
+          screencast-portal.c
+          screencast-portal.h)
 
 target_link_libraries(
   linux-pipewire PRIVATE OBS::libobs OBS::obsglad PipeWire::PipeWire GIO::GIO

+ 2 - 10
plugins/linux-pipewire/linux-pipewire.c

@@ -23,7 +23,7 @@
 #include <obs-nix-platform.h>
 
 #include <pipewire/pipewire.h>
-#include "pipewire-capture.h"
+#include "screencast-portal.h"
 
 OBS_DECLARE_MODULE()
 OBS_MODULE_USE_DEFAULT_LOCALE("linux-pipewire", "en-US")
@@ -36,15 +36,7 @@ bool obs_module_load(void)
 {
 	pw_init(NULL, NULL);
 
-	// OBS PipeWire Screen Capture
-	switch (obs_get_nix_platform()) {
-#ifdef ENABLE_WAYLAND
-	case OBS_NIX_PLATFORM_WAYLAND:
-#endif
-	case OBS_NIX_PLATFORM_X11_EGL:
-		pipewire_capture_load();
-		break;
-	}
+	screencast_portal_load();
 
 	return true;
 }

+ 0 - 180
plugins/linux-pipewire/pipewire-capture.c

@@ -1,180 +0,0 @@
-/* pipewire-capture.c
- *
- * Copyright 2020 Georges Basile Stavracas Neto <[email protected]>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "pipewire.h"
-#include "portal.h"
-
-/* obs_source_info methods */
-
-static const char *pipewire_desktop_capture_get_name(void *data)
-{
-	UNUSED_PARAMETER(data);
-	return obs_module_text("PipeWireDesktopCapture");
-}
-
-static const char *pipewire_window_capture_get_name(void *data)
-{
-	UNUSED_PARAMETER(data);
-	return obs_module_text("PipeWireWindowCapture");
-}
-
-static void *pipewire_desktop_capture_create(obs_data_t *settings,
-					     obs_source_t *source)
-{
-	return obs_pipewire_create(PORTAL_CAPTURE_TYPE_MONITOR, settings,
-				   source);
-}
-static void *pipewire_window_capture_create(obs_data_t *settings,
-					    obs_source_t *source)
-{
-	return obs_pipewire_create(PORTAL_CAPTURE_TYPE_WINDOW, settings,
-				   source);
-}
-
-static void pipewire_capture_destroy(void *data)
-{
-	obs_pipewire_destroy(data);
-}
-
-static void pipewire_capture_save(void *data, obs_data_t *settings)
-{
-	obs_pipewire_save(data, settings);
-}
-
-static void pipewire_capture_get_defaults(obs_data_t *settings)
-{
-	obs_pipewire_get_defaults(settings);
-}
-
-static obs_properties_t *pipewire_capture_get_properties(void *data)
-{
-	enum portal_capture_type capture_type;
-	obs_pipewire_data *obs_pw = data;
-
-	capture_type = obs_pipewire_get_capture_type(obs_pw);
-
-	switch (capture_type) {
-	case PORTAL_CAPTURE_TYPE_MONITOR:
-		return obs_pipewire_get_properties(data,
-						   "PipeWireSelectMonitor");
-	case PORTAL_CAPTURE_TYPE_WINDOW:
-		return obs_pipewire_get_properties(data,
-						   "PipeWireSelectWindow");
-	case PORTAL_CAPTURE_TYPE_VIRTUAL:
-	default:
-		return NULL;
-	}
-}
-
-static void pipewire_capture_update(void *data, obs_data_t *settings)
-{
-	obs_pipewire_update(data, settings);
-}
-
-static void pipewire_capture_show(void *data)
-{
-	obs_pipewire_show(data);
-}
-
-static void pipewire_capture_hide(void *data)
-{
-	obs_pipewire_hide(data);
-}
-
-static uint32_t pipewire_capture_get_width(void *data)
-{
-	return obs_pipewire_get_width(data);
-}
-
-static uint32_t pipewire_capture_get_height(void *data)
-{
-	return obs_pipewire_get_height(data);
-}
-
-static void pipewire_capture_video_render(void *data, gs_effect_t *effect)
-{
-	obs_pipewire_video_render(data, effect);
-}
-
-static bool initialized = false;
-
-void pipewire_capture_load(void)
-{
-	uint32_t available_capture_types = portal_get_available_capture_types();
-	bool desktop_capture_available =
-		(available_capture_types & PORTAL_CAPTURE_TYPE_MONITOR) != 0;
-	bool window_capture_available =
-		(available_capture_types & PORTAL_CAPTURE_TYPE_WINDOW) != 0;
-
-	if (available_capture_types == 0) {
-		blog(LOG_INFO, "[pipewire] No captures available");
-		return;
-	}
-
-	blog(LOG_INFO, "[pipewire] Available captures:");
-	if (desktop_capture_available)
-		blog(LOG_INFO, "[pipewire]     - Desktop capture");
-	if (window_capture_available)
-		blog(LOG_INFO, "[pipewire]     - Window capture");
-
-	// Desktop capture
-	const struct obs_source_info pipewire_desktop_capture_info = {
-		.id = "pipewire-desktop-capture-source",
-		.type = OBS_SOURCE_TYPE_INPUT,
-		.output_flags = OBS_SOURCE_VIDEO,
-		.get_name = pipewire_desktop_capture_get_name,
-		.create = pipewire_desktop_capture_create,
-		.destroy = pipewire_capture_destroy,
-		.save = pipewire_capture_save,
-		.get_defaults = pipewire_capture_get_defaults,
-		.get_properties = pipewire_capture_get_properties,
-		.update = pipewire_capture_update,
-		.show = pipewire_capture_show,
-		.hide = pipewire_capture_hide,
-		.get_width = pipewire_capture_get_width,
-		.get_height = pipewire_capture_get_height,
-		.video_render = pipewire_capture_video_render,
-		.icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE,
-	};
-	if (desktop_capture_available)
-		obs_register_source(&pipewire_desktop_capture_info);
-
-	// Window capture
-	const struct obs_source_info pipewire_window_capture_info = {
-		.id = "pipewire-window-capture-source",
-		.type = OBS_SOURCE_TYPE_INPUT,
-		.output_flags = OBS_SOURCE_VIDEO,
-		.get_name = pipewire_window_capture_get_name,
-		.create = pipewire_window_capture_create,
-		.destroy = pipewire_capture_destroy,
-		.save = pipewire_capture_save,
-		.get_defaults = pipewire_capture_get_defaults,
-		.get_properties = pipewire_capture_get_properties,
-		.update = pipewire_capture_update,
-		.show = pipewire_capture_show,
-		.hide = pipewire_capture_hide,
-		.get_width = pipewire_capture_get_width,
-		.get_height = pipewire_capture_get_height,
-		.video_render = pipewire_capture_video_render,
-		.icon_type = OBS_ICON_TYPE_WINDOW_CAPTURE,
-	};
-	if (window_capture_available)
-		obs_register_source(&pipewire_window_capture_info);
-}

+ 8 - 619
plugins/linux-pipewire/pipewire.c

@@ -20,10 +20,7 @@
 
 #include "pipewire.h"
 
-#include "portal.h"
-
 #include <util/darray.h>
-#include <util/dstr.h>
 
 #include <gio/gio.h>
 #include <gio/gunixfdlist.h>
@@ -32,6 +29,7 @@
 #include <glad/glad.h>
 #include <linux/dma-buf.h>
 #include <libdrm/drm_fourcc.h>
+#include <pipewire/pipewire.h>
 #include <spa/param/video/format-utils.h>
 #include <spa/debug/format.h>
 #include <spa/debug/types.h>
@@ -42,9 +40,6 @@
 #define SPA_POD_PROP_FLAG_DONT_FIXATE (1 << 4)
 #endif
 
-#define REQUEST_PATH "/org/freedesktop/portal/desktop/request/%s/obs%u"
-#define SESSION_PATH "/org/freedesktop/portal/desktop/session/%s/obs%u"
-
 #define CURSOR_META_SIZE(width, height)                                    \
 	(sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + \
 	 width * height * 4)
@@ -62,18 +57,9 @@ struct format_info {
 };
 
 struct _obs_pipewire_data {
-	GCancellable *cancellable;
-
-	char *sender_name;
-	char *session_handle;
-	char *restore_token;
-
 	uint32_t pipewire_node;
 	int pipewire_fd;
 
-	obs_source_t *source;
-	obs_data_t *settings;
-
 	gs_texture_t *texture;
 
 	struct pw_thread_loop *thread_loop;
@@ -106,20 +92,12 @@ struct _obs_pipewire_data {
 		gs_texture_t *texture;
 	} cursor;
 
-	enum portal_capture_type capture_type;
 	struct obs_video_info video_info;
 	bool negotiated;
 
 	DARRAY(struct format_info) format_info;
 };
 
-struct dbus_call_data {
-	obs_pipewire_data *obs_pw;
-	char *request_path;
-	guint signal_id;
-	gulong cancelled_id;
-};
-
 /* auxiliary methods */
 
 static bool parse_pw_version(struct obs_pw_version *dst, const char *version)
@@ -151,118 +129,6 @@ static void update_pw_versions(obs_pipewire_data *obs_pw, const char *version)
 		blog(LOG_WARNING, "[pipewire] failed to parse server version");
 }
 
-static const char *capture_type_to_string(enum portal_capture_type capture_type)
-{
-	switch (capture_type) {
-	case PORTAL_CAPTURE_TYPE_MONITOR:
-		return "desktop";
-	case PORTAL_CAPTURE_TYPE_WINDOW:
-		return "window";
-	case PORTAL_CAPTURE_TYPE_VIRTUAL:
-	default:
-		return "unknown";
-	}
-	return "unknown";
-}
-
-static void new_request_path(obs_pipewire_data *data, char **out_path,
-			     char **out_token)
-{
-	static uint32_t request_token_count = 0;
-
-	request_token_count++;
-
-	if (out_token) {
-		struct dstr str;
-		dstr_init(&str);
-		dstr_printf(&str, "obs%u", request_token_count);
-		*out_token = str.array;
-	}
-
-	if (out_path) {
-		struct dstr str;
-		dstr_init(&str);
-		dstr_printf(&str, REQUEST_PATH, data->sender_name,
-			    request_token_count);
-		*out_path = str.array;
-	}
-}
-
-static void new_session_path(obs_pipewire_data *data, char **out_path,
-			     char **out_token)
-{
-	static uint32_t session_token_count = 0;
-
-	session_token_count++;
-
-	if (out_token) {
-		struct dstr str;
-		dstr_init(&str);
-		dstr_printf(&str, "obs%u", session_token_count);
-		*out_token = str.array;
-	}
-
-	if (out_path) {
-		struct dstr str;
-		dstr_init(&str);
-		dstr_printf(&str, SESSION_PATH, data->sender_name,
-			    session_token_count);
-		*out_path = str.array;
-	}
-}
-
-static void on_cancelled_cb(GCancellable *cancellable, void *data)
-{
-	UNUSED_PARAMETER(cancellable);
-
-	struct dbus_call_data *call = data;
-
-	blog(LOG_INFO, "[pipewire] Screencast session cancelled");
-
-	g_dbus_connection_call(
-		portal_get_dbus_connection(), "org.freedesktop.portal.Desktop",
-		call->request_path, "org.freedesktop.portal.Request", "Close",
-		NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
-}
-
-static struct dbus_call_data *subscribe_to_signal(obs_pipewire_data *obs_pw,
-						  const char *path,
-						  GDBusSignalCallback callback)
-{
-	struct dbus_call_data *call;
-
-	call = bzalloc(sizeof(struct dbus_call_data));
-	call->obs_pw = obs_pw;
-	call->request_path = bstrdup(path);
-	call->cancelled_id = g_signal_connect(obs_pw->cancellable, "cancelled",
-					      G_CALLBACK(on_cancelled_cb),
-					      call);
-	call->signal_id = g_dbus_connection_signal_subscribe(
-		portal_get_dbus_connection(), "org.freedesktop.portal.Desktop",
-		"org.freedesktop.portal.Request", "Response",
-		call->request_path, NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
-		callback, call, NULL);
-
-	return call;
-}
-
-static void dbus_call_data_free(struct dbus_call_data *call)
-{
-	if (!call)
-		return;
-
-	if (call->signal_id)
-		g_dbus_connection_signal_unsubscribe(
-			portal_get_dbus_connection(), call->signal_id);
-
-	if (call->cancelled_id > 0)
-		g_signal_handler_disconnect(call->obs_pw->cancellable,
-					    call->cancelled_id);
-
-	g_clear_pointer(&call->request_path, bfree);
-	bfree(call);
-}
-
 static void teardown_pipewire(obs_pipewire_data *obs_pw)
 {
 	if (obs_pw->thread_loop) {
@@ -286,25 +152,10 @@ static void teardown_pipewire(obs_pipewire_data *obs_pw)
 
 static void destroy_session(obs_pipewire_data *obs_pw)
 {
-	if (obs_pw->session_handle) {
-		g_dbus_connection_call(portal_get_dbus_connection(),
-				       "org.freedesktop.portal.Desktop",
-				       obs_pw->session_handle,
-				       "org.freedesktop.portal.Session",
-				       "Close", NULL, NULL,
-				       G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL,
-				       NULL);
-
-		g_clear_pointer(&obs_pw->session_handle, g_free);
-	}
-
-	g_clear_pointer(&obs_pw->sender_name, bfree);
 	obs_enter_graphics();
 	g_clear_pointer(&obs_pw->cursor.texture, gs_texture_destroy);
 	g_clear_pointer(&obs_pw->texture, gs_texture_destroy);
 	obs_leave_graphics();
-	g_cancellable_cancel(obs_pw->cancellable);
-	g_clear_object(&obs_pw->cancellable);
 }
 
 static inline bool has_effective_crop(obs_pipewire_data *obs_pw)
@@ -980,447 +831,17 @@ static void play_pipewire_stream(obs_pipewire_data *obs_pw)
 	bfree(params);
 }
 
-/* ------------------------------------------------- */
-
-static void on_pipewire_remote_opened_cb(GObject *source, GAsyncResult *res,
-					 void *user_data)
-{
-	g_autoptr(GUnixFDList) fd_list = NULL;
-	g_autoptr(GVariant) result = NULL;
-	g_autoptr(GError) error = NULL;
-	obs_pipewire_data *obs_pw = user_data;
-	int fd_index;
-
-	result = g_dbus_proxy_call_with_unix_fd_list_finish(
-		G_DBUS_PROXY(source), &fd_list, res, &error);
-	if (error) {
-		if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-			blog(LOG_ERROR,
-			     "[pipewire] Error retrieving pipewire fd: %s",
-			     error->message);
-		return;
-	}
-
-	g_variant_get(result, "(h)", &fd_index, &error);
-
-	obs_pw->pipewire_fd = g_unix_fd_list_get(fd_list, fd_index, &error);
-	if (error) {
-		if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-			blog(LOG_ERROR,
-			     "[pipewire] Error retrieving pipewire fd: %s",
-			     error->message);
-		return;
-	}
-
-	play_pipewire_stream(obs_pw);
-}
-
-static void open_pipewire_remote(obs_pipewire_data *obs_pw)
-{
-	GVariantBuilder builder;
-
-	g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
-
-	g_dbus_proxy_call_with_unix_fd_list(
-		portal_get_dbus_proxy(), "OpenPipeWireRemote",
-		g_variant_new("(oa{sv})", obs_pw->session_handle, &builder),
-		G_DBUS_CALL_FLAGS_NONE, -1, NULL, obs_pw->cancellable,
-		on_pipewire_remote_opened_cb, obs_pw);
-}
-
-/* ------------------------------------------------- */
-
-static void on_start_response_received_cb(GDBusConnection *connection,
-					  const char *sender_name,
-					  const char *object_path,
-					  const char *interface_name,
-					  const char *signal_name,
-					  GVariant *parameters, void *user_data)
-{
-	UNUSED_PARAMETER(connection);
-	UNUSED_PARAMETER(sender_name);
-	UNUSED_PARAMETER(object_path);
-	UNUSED_PARAMETER(interface_name);
-	UNUSED_PARAMETER(signal_name);
-
-	g_autoptr(GVariant) stream_properties = NULL;
-	g_autoptr(GVariant) streams = NULL;
-	g_autoptr(GVariant) result = NULL;
-	struct dbus_call_data *call = user_data;
-	obs_pipewire_data *obs_pw = call->obs_pw;
-	GVariantIter iter;
-	uint32_t response;
-	size_t n_streams;
-
-	g_clear_pointer(&call, dbus_call_data_free);
-
-	g_variant_get(parameters, "(u@a{sv})", &response, &result);
-
-	if (response != 0) {
-		blog(LOG_WARNING,
-		     "[pipewire] Failed to start screencast, denied or cancelled by user");
-		return;
-	}
-
-	streams =
-		g_variant_lookup_value(result, "streams", G_VARIANT_TYPE_ARRAY);
-
-	g_variant_iter_init(&iter, streams);
-
-	n_streams = g_variant_iter_n_children(&iter);
-	if (n_streams != 1) {
-		blog(LOG_WARNING,
-		     "[pipewire] Received more than one stream when only one was expected. "
-		     "This is probably a bug in the desktop portal implementation you are "
-		     "using.");
-
-		// The KDE Desktop portal implementation sometimes sends an invalid
-		// response where more than one stream is attached, and only the
-		// last one is the one we're looking for. This is the only known
-		// buggy implementation, so let's at least try to make it work here.
-		for (size_t i = 0; i < n_streams - 1; i++) {
-			g_autoptr(GVariant) throwaway_properties = NULL;
-			uint32_t throwaway_pipewire_node;
-
-			g_variant_iter_loop(&iter, "(u@a{sv})",
-					    &throwaway_pipewire_node,
-					    &throwaway_properties);
-		}
-	}
-
-	g_variant_iter_loop(&iter, "(u@a{sv})", &obs_pw->pipewire_node,
-			    &stream_properties);
-
-	if (portal_get_screencast_version() >= 4) {
-		g_autoptr(GVariant) restore_token = NULL;
-
-		g_clear_pointer(&obs_pw->restore_token, bfree);
-
-		restore_token = g_variant_lookup_value(result, "restore_token",
-						       G_VARIANT_TYPE_STRING);
-		if (restore_token)
-			obs_pw->restore_token = bstrdup(
-				g_variant_get_string(restore_token, NULL));
-
-		obs_source_save(obs_pw->source);
-	}
-
-	blog(LOG_INFO, "[pipewire] %s selected, setting up screencast",
-	     capture_type_to_string(obs_pw->capture_type));
-
-	open_pipewire_remote(obs_pw);
-}
-
-static void on_started_cb(GObject *source, GAsyncResult *res, void *user_data)
-{
-	UNUSED_PARAMETER(user_data);
-
-	g_autoptr(GVariant) result = NULL;
-	g_autoptr(GError) error = NULL;
-
-	result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
-	if (error) {
-		if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-			blog(LOG_ERROR,
-			     "[pipewire] Error selecting screencast source: %s",
-			     error->message);
-		return;
-	}
-}
-
-static void start(obs_pipewire_data *obs_pw)
-{
-	GVariantBuilder builder;
-	struct dbus_call_data *call;
-	char *request_token;
-	char *request_path;
-
-	new_request_path(obs_pw, &request_path, &request_token);
-
-	blog(LOG_INFO, "[pipewire] Asking for %s",
-	     capture_type_to_string(obs_pw->capture_type));
-
-	call = subscribe_to_signal(obs_pw, request_path,
-				   on_start_response_received_cb);
-
-	g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
-	g_variant_builder_add(&builder, "{sv}", "handle_token",
-			      g_variant_new_string(request_token));
-
-	g_dbus_proxy_call(portal_get_dbus_proxy(), "Start",
-			  g_variant_new("(osa{sv})", obs_pw->session_handle, "",
-					&builder),
-			  G_DBUS_CALL_FLAGS_NONE, -1, obs_pw->cancellable,
-			  on_started_cb, call);
-
-	bfree(request_token);
-	bfree(request_path);
-}
-
-/* ------------------------------------------------- */
-
-static void on_select_source_response_received_cb(
-	GDBusConnection *connection, const char *sender_name,
-	const char *object_path, const char *interface_name,
-	const char *signal_name, GVariant *parameters, void *user_data)
-{
-	UNUSED_PARAMETER(connection);
-	UNUSED_PARAMETER(sender_name);
-	UNUSED_PARAMETER(object_path);
-	UNUSED_PARAMETER(interface_name);
-	UNUSED_PARAMETER(signal_name);
-
-	g_autoptr(GVariant) ret = NULL;
-	struct dbus_call_data *call = user_data;
-	obs_pipewire_data *obs_pw = call->obs_pw;
-	uint32_t response;
-
-	blog(LOG_DEBUG, "[pipewire] Response to select source received");
-
-	g_clear_pointer(&call, dbus_call_data_free);
-
-	g_variant_get(parameters, "(u@a{sv})", &response, &ret);
-
-	if (response != 0) {
-		blog(LOG_WARNING,
-		     "[pipewire] Failed to select source, denied or cancelled by user");
-		return;
-	}
-
-	start(obs_pw);
-}
-
-static void on_source_selected_cb(GObject *source, GAsyncResult *res,
-				  void *user_data)
-{
-	UNUSED_PARAMETER(user_data);
-
-	g_autoptr(GVariant) result = NULL;
-	g_autoptr(GError) error = NULL;
-
-	result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
-	if (error) {
-		if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-			blog(LOG_ERROR,
-			     "[pipewire] Error selecting screencast source: %s",
-			     error->message);
-		return;
-	}
-}
-
-static void select_source(obs_pipewire_data *obs_pw)
-{
-	struct dbus_call_data *call;
-	GVariantBuilder builder;
-	uint32_t available_cursor_modes;
-	char *request_token;
-	char *request_path;
-
-	new_request_path(obs_pw, &request_path, &request_token);
-
-	call = subscribe_to_signal(obs_pw, request_path,
-				   on_select_source_response_received_cb);
-
-	g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
-	g_variant_builder_add(&builder, "{sv}", "types",
-			      g_variant_new_uint32(obs_pw->capture_type));
-	g_variant_builder_add(&builder, "{sv}", "multiple",
-			      g_variant_new_boolean(FALSE));
-	g_variant_builder_add(&builder, "{sv}", "handle_token",
-			      g_variant_new_string(request_token));
-
-	available_cursor_modes = portal_get_available_cursor_modes();
-
-	if (available_cursor_modes & PORTAL_CURSOR_MODE_METADATA)
-		g_variant_builder_add(
-			&builder, "{sv}", "cursor_mode",
-			g_variant_new_uint32(PORTAL_CURSOR_MODE_METADATA));
-	else if ((available_cursor_modes & PORTAL_CURSOR_MODE_EMBEDDED) &&
-		 obs_pw->cursor.visible)
-		g_variant_builder_add(
-			&builder, "{sv}", "cursor_mode",
-			g_variant_new_uint32(PORTAL_CURSOR_MODE_EMBEDDED));
-	else
-		g_variant_builder_add(
-			&builder, "{sv}", "cursor_mode",
-			g_variant_new_uint32(PORTAL_CURSOR_MODE_HIDDEN));
-
-	if (portal_get_screencast_version() >= 4) {
-		g_variant_builder_add(&builder, "{sv}", "persist_mode",
-				      g_variant_new_uint32(2));
-		if (obs_pw->restore_token && *obs_pw->restore_token) {
-			g_variant_builder_add(
-				&builder, "{sv}", "restore_token",
-				g_variant_new_string(obs_pw->restore_token));
-		}
-	}
-
-	g_dbus_proxy_call(portal_get_dbus_proxy(), "SelectSources",
-			  g_variant_new("(oa{sv})", obs_pw->session_handle,
-					&builder),
-			  G_DBUS_CALL_FLAGS_NONE, -1, obs_pw->cancellable,
-			  on_source_selected_cb, call);
-
-	bfree(request_token);
-	bfree(request_path);
-}
-
-/* ------------------------------------------------- */
-
-static void on_create_session_response_received_cb(
-	GDBusConnection *connection, const char *sender_name,
-	const char *object_path, const char *interface_name,
-	const char *signal_name, GVariant *parameters, void *user_data)
-{
-	UNUSED_PARAMETER(connection);
-	UNUSED_PARAMETER(sender_name);
-	UNUSED_PARAMETER(object_path);
-	UNUSED_PARAMETER(interface_name);
-	UNUSED_PARAMETER(signal_name);
-
-	g_autoptr(GVariant) session_handle_variant = NULL;
-	g_autoptr(GVariant) result = NULL;
-	struct dbus_call_data *call = user_data;
-	obs_pipewire_data *obs_pw = call->obs_pw;
-	uint32_t response;
-
-	g_clear_pointer(&call, dbus_call_data_free);
-
-	g_variant_get(parameters, "(u@a{sv})", &response, &result);
-
-	if (response != 0) {
-		blog(LOG_WARNING,
-		     "[pipewire] Failed to create session, denied or cancelled by user");
-		return;
-	}
-
-	blog(LOG_INFO, "[pipewire] Screencast session created");
-
-	session_handle_variant =
-		g_variant_lookup_value(result, "session_handle", NULL);
-	obs_pw->session_handle =
-		g_variant_dup_string(session_handle_variant, NULL);
-
-	select_source(obs_pw);
-}
-
-static void on_session_created_cb(GObject *source, GAsyncResult *res,
-				  void *user_data)
-{
-	UNUSED_PARAMETER(user_data);
-
-	g_autoptr(GVariant) result = NULL;
-	g_autoptr(GError) error = NULL;
-
-	result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
-	if (error) {
-		if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-			blog(LOG_ERROR,
-			     "[pipewire] Error creating screencast session: %s",
-			     error->message);
-		return;
-	}
-}
-
-static void create_session(obs_pipewire_data *obs_pw)
-{
-	struct dbus_call_data *call;
-	GVariantBuilder builder;
-	char *session_token;
-	char *request_token;
-	char *request_path;
-
-	new_request_path(obs_pw, &request_path, &request_token);
-	new_session_path(obs_pw, NULL, &session_token);
-
-	call = subscribe_to_signal(obs_pw, request_path,
-				   on_create_session_response_received_cb);
-
-	g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
-	g_variant_builder_add(&builder, "{sv}", "handle_token",
-			      g_variant_new_string(request_token));
-	g_variant_builder_add(&builder, "{sv}", "session_handle_token",
-			      g_variant_new_string(session_token));
-
-	g_dbus_proxy_call(portal_get_dbus_proxy(), "CreateSession",
-			  g_variant_new("(a{sv})", &builder),
-			  G_DBUS_CALL_FLAGS_NONE, -1, obs_pw->cancellable,
-			  on_session_created_cb, call);
-
-	bfree(session_token);
-	bfree(request_token);
-	bfree(request_path);
-}
-
-/* ------------------------------------------------- */
-
-static gboolean init_obs_pipewire(obs_pipewire_data *obs_pw)
-{
-	GDBusConnection *connection;
-	GDBusProxy *proxy;
-	char *aux;
-
-	obs_pw->cancellable = g_cancellable_new();
-	connection = portal_get_dbus_connection();
-	if (!connection)
-		return FALSE;
-	proxy = portal_get_dbus_proxy();
-	if (!proxy)
-		return FALSE;
-
-	obs_pw->sender_name =
-		bstrdup(g_dbus_connection_get_unique_name(connection) + 1);
-
-	/* Replace dots by underscores */
-	while ((aux = strstr(obs_pw->sender_name, ".")) != NULL)
-		*aux = '_';
-
-	blog(LOG_INFO, "PipeWire initialized (sender name: %s)",
-	     obs_pw->sender_name);
-
-	create_session(obs_pw);
-
-	return TRUE;
-}
-
-static bool reload_session_cb(obs_properties_t *properties,
-			      obs_property_t *property, void *data)
-{
-	UNUSED_PARAMETER(properties);
-	UNUSED_PARAMETER(property);
-
-	obs_pipewire_data *obs_pw = data;
-
-	g_clear_pointer(&obs_pw->restore_token, bfree);
-
-	teardown_pipewire(obs_pw);
-	destroy_session(obs_pw);
-
-	init_obs_pipewire(obs_pw);
-
-	return false;
-}
-
 /* obs_source_info methods */
 
-void *obs_pipewire_create(enum portal_capture_type capture_type,
-			  obs_data_t *settings, obs_source_t *source)
+void *obs_pipewire_create(int pipewire_fd, int pipewire_node)
 {
 	obs_pipewire_data *obs_pw = bzalloc(sizeof(obs_pipewire_data));
 
-	obs_pw->source = source;
-	obs_pw->settings = settings;
-	obs_pw->capture_type = capture_type;
-	obs_pw->cursor.visible = obs_data_get_bool(settings, "ShowCursor");
-	obs_pw->restore_token =
-		bstrdup(obs_data_get_string(settings, "RestoreToken"));
-
-	if (!init_obs_pipewire(obs_pw)) {
-		g_clear_pointer(&obs_pw, bfree);
-		return NULL;
-	}
+	obs_pw->pipewire_fd = pipewire_fd;
+	obs_pw->pipewire_node = pipewire_node;
 
 	init_format_info(obs_pw);
+	play_pipewire_stream(obs_pw);
 
 	return obs_pw;
 }
@@ -1433,43 +854,11 @@ void obs_pipewire_destroy(obs_pipewire_data *obs_pw)
 	teardown_pipewire(obs_pw);
 	destroy_session(obs_pw);
 
-	g_clear_pointer(&obs_pw->restore_token, bfree);
 	clear_format_info(obs_pw);
 
 	bfree(obs_pw);
 }
 
-void obs_pipewire_save(obs_pipewire_data *obs_pw, obs_data_t *settings)
-{
-	obs_data_set_string(settings, "RestoreToken", obs_pw->restore_token);
-}
-
-void obs_pipewire_get_defaults(obs_data_t *settings)
-{
-	obs_data_set_default_bool(settings, "ShowCursor", true);
-	obs_data_set_default_string(settings, "RestoreToken", NULL);
-}
-
-obs_properties_t *obs_pipewire_get_properties(obs_pipewire_data *obs_pw,
-					      const char *reload_string_id)
-{
-	obs_properties_t *properties;
-
-	properties = obs_properties_create();
-	obs_properties_add_button2(properties, "Reload",
-				   obs_module_text(reload_string_id),
-				   reload_session_cb, obs_pw);
-	obs_properties_add_bool(properties, "ShowCursor",
-				obs_module_text("ShowCursor"));
-
-	return properties;
-}
-
-void obs_pipewire_update(obs_pipewire_data *obs_pw, obs_data_t *settings)
-{
-	obs_pw->cursor.visible = obs_data_get_bool(settings, "ShowCursor");
-}
-
 void obs_pipewire_show(obs_pipewire_data *obs_pw)
 {
 	if (obs_pw->stream)
@@ -1538,8 +927,8 @@ void obs_pipewire_video_render(obs_pipewire_data *obs_pw, gs_effect_t *effect)
 	}
 }
 
-enum portal_capture_type
-obs_pipewire_get_capture_type(obs_pipewire_data *obs_pw)
+void obs_pipewire_set_cursor_visible(obs_pipewire_data *obs_pw,
+				     bool cursor_visible)
 {
-	return obs_pw->capture_type;
+	obs_pw->cursor.visible = cursor_visible;
 }

+ 3 - 17
plugins/linux-pipewire/pipewire.h

@@ -21,31 +21,17 @@
 #pragma once
 
 #include <obs-module.h>
-#include <pipewire/pipewire.h>
-
-#include "portal.h"
 
 typedef struct _obs_pipewire_data obs_pipewire_data;
 
-void *obs_pipewire_create(enum portal_capture_type capture_type,
-			  obs_data_t *settings, obs_source_t *source);
-
+void *obs_pipewire_create(int pipewire_fd, int pipewire_node);
 void obs_pipewire_destroy(obs_pipewire_data *obs_pw);
 
-void obs_pipewire_save(obs_pipewire_data *obs_pw, obs_data_t *settings);
-void obs_pipewire_get_defaults(obs_data_t *settings);
-
-obs_properties_t *obs_pipewire_get_properties(obs_pipewire_data *obs_pw,
-					      const char *reload_string_id);
-
-void obs_pipewire_update(obs_pipewire_data *obs_pw, obs_data_t *settings);
-
 void obs_pipewire_show(obs_pipewire_data *obs_pw);
-
 void obs_pipewire_hide(obs_pipewire_data *obs_pw);
 uint32_t obs_pipewire_get_width(obs_pipewire_data *obs_pw);
 uint32_t obs_pipewire_get_height(obs_pipewire_data *obs_pw);
 void obs_pipewire_video_render(obs_pipewire_data *obs_pw, gs_effect_t *effect);
 
-enum portal_capture_type
-obs_pipewire_get_capture_type(obs_pipewire_data *obs_pw);
+void obs_pipewire_set_cursor_visible(obs_pipewire_data *obs_pw,
+				     bool cursor_visible);

+ 62 - 60
plugins/linux-pipewire/portal.c

@@ -21,10 +21,14 @@
 #include "portal.h"
 #include "pipewire.h"
 
+#include <util/dstr.h>
+
+#define REQUEST_PATH "/org/freedesktop/portal/desktop/request/%s/obs%u"
+#define SESSION_PATH "/org/freedesktop/portal/desktop/session/%s/obs%u"
+
 static GDBusConnection *connection = NULL;
-static GDBusProxy *proxy = NULL;
 
-static void ensure_proxy(void)
+static void ensure_connection(void)
 {
 	g_autoptr(GError) error = NULL;
 	if (!connection) {
@@ -37,85 +41,83 @@ static void ensure_proxy(void)
 			return;
 		}
 	}
-
-	if (!proxy) {
-		proxy = g_dbus_proxy_new_sync(
-			connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
-			"org.freedesktop.portal.Desktop",
-			"/org/freedesktop/portal/desktop",
-			"org.freedesktop.portal.ScreenCast", NULL, &error);
-
-		if (error) {
-			blog(LOG_WARNING,
-			     "[portals] Error retrieving D-Bus proxy: %s",
-			     error->message);
-			return;
-		}
-	}
 }
 
-uint32_t portal_get_available_capture_types(void)
+char *get_sender_name(void)
 {
-	g_autoptr(GVariant) cached_source_types = NULL;
-	uint32_t available_source_types;
+	char *sender_name;
+	char *aux;
 
-	ensure_proxy();
+	ensure_connection();
 
-	if (!proxy)
-		return 0;
+	sender_name =
+		bstrdup(g_dbus_connection_get_unique_name(connection) + 1);
 
-	cached_source_types =
-		g_dbus_proxy_get_cached_property(proxy, "AvailableSourceTypes");
-	available_source_types =
-		cached_source_types ? g_variant_get_uint32(cached_source_types)
-				    : 0;
+	/* Replace dots by underscores */
+	while ((aux = strstr(sender_name, ".")) != NULL)
+		*aux = '_';
 
-	return available_source_types;
+	return sender_name;
 }
 
-uint32_t portal_get_available_cursor_modes(void)
+GDBusConnection *portal_get_dbus_connection(void)
 {
-	g_autoptr(GVariant) cached_cursor_modes = NULL;
-	uint32_t available_cursor_modes;
+	ensure_connection();
+	return connection;
+}
 
-	ensure_proxy();
+void portal_create_request_path(char **out_path, char **out_token)
+{
+	static uint32_t request_token_count = 0;
 
-	if (!proxy)
-		return 0;
+	request_token_count++;
 
-	cached_cursor_modes =
-		g_dbus_proxy_get_cached_property(proxy, "AvailableCursorModes");
-	available_cursor_modes =
-		cached_cursor_modes ? g_variant_get_uint32(cached_cursor_modes)
-				    : 0;
+	if (out_token) {
+		struct dstr str;
+		dstr_init(&str);
+		dstr_printf(&str, "obs%u", request_token_count);
+		*out_token = str.array;
+	}
+
+	if (out_path) {
+		char *sender_name;
+		struct dstr str;
+
+		sender_name = get_sender_name();
+
+		dstr_init(&str);
+		dstr_printf(&str, REQUEST_PATH, sender_name,
+			    request_token_count);
+		*out_path = str.array;
 
-	return available_cursor_modes;
+		bfree(sender_name);
+	}
 }
 
-uint32_t portal_get_screencast_version(void)
+void portal_create_session_path(char **out_path, char **out_token)
 {
-	g_autoptr(GVariant) cached_version = NULL;
-	uint32_t version;
+	static uint32_t session_token_count = 0;
 
-	ensure_proxy();
+	session_token_count++;
 
-	if (!proxy)
-		return 0;
+	if (out_token) {
+		struct dstr str;
+		dstr_init(&str);
+		dstr_printf(&str, "obs%u", session_token_count);
+		*out_token = str.array;
+	}
 
-	cached_version = g_dbus_proxy_get_cached_property(proxy, "version");
-	version = cached_version ? g_variant_get_uint32(cached_version) : 0;
+	if (out_path) {
+		char *sender_name;
+		struct dstr str;
 
-	return version;
-}
+		sender_name = get_sender_name();
 
-GDBusConnection *portal_get_dbus_connection(void)
-{
-	ensure_proxy();
-	return connection;
-}
+		dstr_init(&str);
+		dstr_printf(&str, SESSION_PATH, sender_name,
+			    session_token_count);
+		*out_path = str.array;
 
-GDBusProxy *portal_get_dbus_proxy(void)
-{
-	ensure_proxy();
-	return proxy;
+		bfree(sender_name);
+	}
 }

+ 3 - 17
plugins/linux-pipewire/portal.h

@@ -23,21 +23,7 @@
 #include <stdint.h>
 #include <gio/gio.h>
 
-enum portal_capture_type {
-	PORTAL_CAPTURE_TYPE_MONITOR = 1 << 0,
-	PORTAL_CAPTURE_TYPE_WINDOW = 1 << 1,
-	PORTAL_CAPTURE_TYPE_VIRTUAL = 1 << 2,
-};
-
-enum portal_cursor_mode {
-	PORTAL_CURSOR_MODE_HIDDEN = 1 << 0,
-	PORTAL_CURSOR_MODE_EMBEDDED = 1 << 1,
-	PORTAL_CURSOR_MODE_METADATA = 1 << 2,
-};
-
-uint32_t portal_get_available_capture_types(void);
-uint32_t portal_get_available_cursor_modes(void);
-uint32_t portal_get_screencast_version(void);
-
 GDBusConnection *portal_get_dbus_connection(void);
-GDBusProxy *portal_get_dbus_proxy(void);
+
+void portal_create_request_path(char **out_path, char **out_token);
+void portal_create_session_path(char **out_path, char **out_token);

+ 869 - 0
plugins/linux-pipewire/screencast-portal.c

@@ -0,0 +1,869 @@
+/* screencast-portal.c
+ *
+ * Copyright 2022 Georges Basile Stavracas Neto <[email protected]>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "pipewire.h"
+#include "portal.h"
+
+#include <gio/gunixfdlist.h>
+
+enum portal_capture_type {
+	PORTAL_CAPTURE_TYPE_MONITOR = 1 << 0,
+	PORTAL_CAPTURE_TYPE_WINDOW = 1 << 1,
+	PORTAL_CAPTURE_TYPE_VIRTUAL = 1 << 2,
+};
+
+enum portal_cursor_mode {
+	PORTAL_CURSOR_MODE_HIDDEN = 1 << 0,
+	PORTAL_CURSOR_MODE_EMBEDDED = 1 << 1,
+	PORTAL_CURSOR_MODE_METADATA = 1 << 2,
+};
+
+struct screencast_portal_capture {
+	enum portal_capture_type capture_type;
+
+	GCancellable *cancellable;
+
+	char *session_handle;
+	char *restore_token;
+
+	obs_source_t *source;
+	obs_data_t *settings;
+
+	uint32_t pipewire_node;
+	bool cursor_visible;
+
+	obs_pipewire_data *obs_pw;
+};
+
+/* ------------------------------------------------- */
+
+static GDBusProxy *screencast_proxy = NULL;
+
+static void ensure_screencast_portal_proxy(void)
+{
+	g_autoptr(GError) error = NULL;
+	if (!screencast_proxy) {
+		screencast_proxy = g_dbus_proxy_new_sync(
+			portal_get_dbus_connection(), G_DBUS_PROXY_FLAGS_NONE,
+			NULL, "org.freedesktop.portal.Desktop",
+			"/org/freedesktop/portal/desktop",
+			"org.freedesktop.portal.ScreenCast", NULL, &error);
+
+		if (error) {
+			blog(LOG_WARNING,
+			     "[portals] Error retrieving D-Bus proxy: %s",
+			     error->message);
+			return;
+		}
+	}
+}
+
+static GDBusProxy *get_screencast_portal_proxy(void)
+{
+	ensure_screencast_portal_proxy();
+	return screencast_proxy;
+}
+
+static uint32_t get_available_capture_types(void)
+{
+	g_autoptr(GVariant) cached_source_types = NULL;
+	uint32_t available_source_types;
+
+	ensure_screencast_portal_proxy();
+
+	if (!screencast_proxy)
+		return 0;
+
+	cached_source_types = g_dbus_proxy_get_cached_property(
+		screencast_proxy, "AvailableSourceTypes");
+	available_source_types =
+		cached_source_types ? g_variant_get_uint32(cached_source_types)
+				    : 0;
+
+	return available_source_types;
+}
+
+static uint32_t get_available_cursor_modes(void)
+{
+	g_autoptr(GVariant) cached_cursor_modes = NULL;
+	uint32_t available_cursor_modes;
+
+	ensure_screencast_portal_proxy();
+
+	if (!screencast_proxy)
+		return 0;
+
+	cached_cursor_modes = g_dbus_proxy_get_cached_property(
+		screencast_proxy, "AvailableCursorModes");
+	available_cursor_modes =
+		cached_cursor_modes ? g_variant_get_uint32(cached_cursor_modes)
+				    : 0;
+
+	return available_cursor_modes;
+}
+
+static uint32_t get_screencast_version(void)
+{
+	g_autoptr(GVariant) cached_version = NULL;
+	uint32_t version;
+
+	ensure_screencast_portal_proxy();
+
+	if (!screencast_proxy)
+		return 0;
+
+	cached_version =
+		g_dbus_proxy_get_cached_property(screencast_proxy, "version");
+	version = cached_version ? g_variant_get_uint32(cached_version) : 0;
+
+	return version;
+}
+
+/* ------------------------------------------------- */
+
+struct dbus_call_data {
+	struct screencast_portal_capture *capture;
+	char *request_path;
+	guint signal_id;
+	gulong cancelled_id;
+};
+
+static const char *capture_type_to_string(enum portal_capture_type capture_type)
+{
+	switch (capture_type) {
+	case PORTAL_CAPTURE_TYPE_MONITOR:
+		return "desktop";
+	case PORTAL_CAPTURE_TYPE_WINDOW:
+		return "window";
+	case PORTAL_CAPTURE_TYPE_VIRTUAL:
+	default:
+		return "unknown";
+	}
+	return "unknown";
+}
+
+static void on_cancelled_cb(GCancellable *cancellable, void *data)
+{
+	UNUSED_PARAMETER(cancellable);
+
+	struct dbus_call_data *call = data;
+
+	blog(LOG_INFO, "[pipewire] Screencast session cancelled");
+
+	g_dbus_connection_call(
+		portal_get_dbus_connection(), "org.freedesktop.portal.Desktop",
+		call->request_path, "org.freedesktop.portal.Request", "Close",
+		NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static struct dbus_call_data *
+subscribe_to_signal(struct screencast_portal_capture *capture, const char *path,
+		    GDBusSignalCallback callback)
+{
+	struct dbus_call_data *call;
+
+	call = bzalloc(sizeof(struct dbus_call_data));
+	call->capture = capture;
+	call->request_path = bstrdup(path);
+	call->cancelled_id = g_signal_connect(capture->cancellable, "cancelled",
+					      G_CALLBACK(on_cancelled_cb),
+					      call);
+	call->signal_id = g_dbus_connection_signal_subscribe(
+		portal_get_dbus_connection(), "org.freedesktop.portal.Desktop",
+		"org.freedesktop.portal.Request", "Response",
+		call->request_path, NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+		callback, call, NULL);
+
+	return call;
+}
+
+static void dbus_call_data_free(struct dbus_call_data *call)
+{
+	if (!call)
+		return;
+
+	if (call->signal_id)
+		g_dbus_connection_signal_unsubscribe(
+			portal_get_dbus_connection(), call->signal_id);
+
+	if (call->cancelled_id > 0)
+		g_signal_handler_disconnect(call->capture->cancellable,
+					    call->cancelled_id);
+
+	g_clear_pointer(&call->request_path, bfree);
+	bfree(call);
+}
+
+/* ------------------------------------------------- */
+
+static void on_pipewire_remote_opened_cb(GObject *source, GAsyncResult *res,
+					 void *user_data)
+{
+	struct screencast_portal_capture *capture;
+	g_autoptr(GUnixFDList) fd_list = NULL;
+	g_autoptr(GVariant) result = NULL;
+	g_autoptr(GError) error = NULL;
+	int pipewire_fd;
+	int fd_index;
+
+	capture = user_data;
+	result = g_dbus_proxy_call_with_unix_fd_list_finish(
+		G_DBUS_PROXY(source), &fd_list, res, &error);
+	if (error) {
+		if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			blog(LOG_ERROR,
+			     "[pipewire] Error retrieving pipewire fd: %s",
+			     error->message);
+		return;
+	}
+
+	g_variant_get(result, "(h)", &fd_index, &error);
+
+	pipewire_fd = g_unix_fd_list_get(fd_list, fd_index, &error);
+	if (error) {
+		if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			blog(LOG_ERROR,
+			     "[pipewire] Error retrieving pipewire fd: %s",
+			     error->message);
+		return;
+	}
+
+	capture->obs_pw =
+		obs_pipewire_create(pipewire_fd, capture->pipewire_node);
+	obs_pipewire_set_cursor_visible(capture->obs_pw,
+					capture->cursor_visible);
+}
+
+static void open_pipewire_remote(struct screencast_portal_capture *capture)
+{
+	GVariantBuilder builder;
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+
+	g_dbus_proxy_call_with_unix_fd_list(
+		get_screencast_portal_proxy(), "OpenPipeWireRemote",
+		g_variant_new("(oa{sv})", capture->session_handle, &builder),
+		G_DBUS_CALL_FLAGS_NONE, -1, NULL, capture->cancellable,
+		on_pipewire_remote_opened_cb, capture);
+}
+
+/* ------------------------------------------------- */
+
+static void on_start_response_received_cb(GDBusConnection *connection,
+					  const char *sender_name,
+					  const char *object_path,
+					  const char *interface_name,
+					  const char *signal_name,
+					  GVariant *parameters, void *user_data)
+{
+	UNUSED_PARAMETER(connection);
+	UNUSED_PARAMETER(sender_name);
+	UNUSED_PARAMETER(object_path);
+	UNUSED_PARAMETER(interface_name);
+	UNUSED_PARAMETER(signal_name);
+
+	struct screencast_portal_capture *capture;
+	g_autoptr(GVariant) stream_properties = NULL;
+	g_autoptr(GVariant) streams = NULL;
+	g_autoptr(GVariant) result = NULL;
+	struct dbus_call_data *call = user_data;
+	GVariantIter iter;
+	uint32_t response;
+	size_t n_streams;
+
+	capture = call->capture;
+	g_clear_pointer(&call, dbus_call_data_free);
+
+	g_variant_get(parameters, "(u@a{sv})", &response, &result);
+
+	if (response != 0) {
+		blog(LOG_WARNING,
+		     "[pipewire] Failed to start screencast, denied or cancelled by user");
+		return;
+	}
+
+	streams =
+		g_variant_lookup_value(result, "streams", G_VARIANT_TYPE_ARRAY);
+
+	g_variant_iter_init(&iter, streams);
+
+	n_streams = g_variant_iter_n_children(&iter);
+	if (n_streams != 1) {
+		blog(LOG_WARNING,
+		     "[pipewire] Received more than one stream when only one was expected. "
+		     "This is probably a bug in the desktop portal implementation you are "
+		     "using.");
+
+		// The KDE Desktop portal implementation sometimes sends an invalid
+		// response where more than one stream is attached, and only the
+		// last one is the one we're looking for. This is the only known
+		// buggy implementation, so let's at least try to make it work here.
+		for (size_t i = 0; i < n_streams - 1; i++) {
+			g_autoptr(GVariant) throwaway_properties = NULL;
+			uint32_t throwaway_pipewire_node;
+
+			g_variant_iter_loop(&iter, "(u@a{sv})",
+					    &throwaway_pipewire_node,
+					    &throwaway_properties);
+		}
+	}
+
+	g_variant_iter_loop(&iter, "(u@a{sv})", &capture->pipewire_node,
+			    &stream_properties);
+
+	if (get_screencast_version() >= 4) {
+		g_autoptr(GVariant) restore_token = NULL;
+
+		g_clear_pointer(&capture->restore_token, bfree);
+
+		restore_token = g_variant_lookup_value(result, "restore_token",
+						       G_VARIANT_TYPE_STRING);
+		if (restore_token)
+			capture->restore_token = bstrdup(
+				g_variant_get_string(restore_token, NULL));
+
+		obs_source_save(capture->source);
+	}
+
+	blog(LOG_INFO, "[pipewire] %s selected, setting up screencast",
+	     capture_type_to_string(capture->capture_type));
+
+	open_pipewire_remote(capture);
+}
+
+static void on_started_cb(GObject *source, GAsyncResult *res, void *user_data)
+{
+	UNUSED_PARAMETER(user_data);
+
+	g_autoptr(GVariant) result = NULL;
+	g_autoptr(GError) error = NULL;
+
+	result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
+	if (error) {
+		if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			blog(LOG_ERROR,
+			     "[pipewire] Error selecting screencast source: %s",
+			     error->message);
+		return;
+	}
+}
+
+static void start(struct screencast_portal_capture *capture)
+{
+	GVariantBuilder builder;
+	struct dbus_call_data *call;
+	char *request_token;
+	char *request_path;
+
+	portal_create_request_path(&request_path, &request_token);
+
+	blog(LOG_INFO, "[pipewire] Asking for %s",
+	     capture_type_to_string(capture->capture_type));
+
+	call = subscribe_to_signal(capture, request_path,
+				   on_start_response_received_cb);
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add(&builder, "{sv}", "handle_token",
+			      g_variant_new_string(request_token));
+
+	g_dbus_proxy_call(get_screencast_portal_proxy(), "Start",
+			  g_variant_new("(osa{sv})", capture->session_handle,
+					"", &builder),
+			  G_DBUS_CALL_FLAGS_NONE, -1, capture->cancellable,
+			  on_started_cb, call);
+
+	bfree(request_token);
+	bfree(request_path);
+}
+
+/* ------------------------------------------------- */
+
+static void on_select_source_response_received_cb(
+	GDBusConnection *connection, const char *sender_name,
+	const char *object_path, const char *interface_name,
+	const char *signal_name, GVariant *parameters, void *user_data)
+{
+	UNUSED_PARAMETER(connection);
+	UNUSED_PARAMETER(sender_name);
+	UNUSED_PARAMETER(object_path);
+	UNUSED_PARAMETER(interface_name);
+	UNUSED_PARAMETER(signal_name);
+
+	struct screencast_portal_capture *capture;
+	g_autoptr(GVariant) ret = NULL;
+	struct dbus_call_data *call = user_data;
+	uint32_t response;
+
+	capture = call->capture;
+
+	blog(LOG_DEBUG, "[pipewire] Response to select source received");
+
+	g_clear_pointer(&call, dbus_call_data_free);
+
+	g_variant_get(parameters, "(u@a{sv})", &response, &ret);
+
+	if (response != 0) {
+		blog(LOG_WARNING,
+		     "[pipewire] Failed to select source, denied or cancelled by user");
+		return;
+	}
+
+	start(capture);
+}
+
+static void on_source_selected_cb(GObject *source, GAsyncResult *res,
+				  void *user_data)
+{
+	UNUSED_PARAMETER(user_data);
+
+	g_autoptr(GVariant) result = NULL;
+	g_autoptr(GError) error = NULL;
+
+	result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
+	if (error) {
+		if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			blog(LOG_ERROR,
+			     "[pipewire] Error selecting screencast source: %s",
+			     error->message);
+		return;
+	}
+}
+
+static void select_source(struct screencast_portal_capture *capture)
+{
+	struct dbus_call_data *call;
+	GVariantBuilder builder;
+	uint32_t available_cursor_modes;
+	char *request_token;
+	char *request_path;
+
+	portal_create_request_path(&request_path, &request_token);
+
+	call = subscribe_to_signal(capture, request_path,
+				   on_select_source_response_received_cb);
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add(&builder, "{sv}", "types",
+			      g_variant_new_uint32(capture->capture_type));
+	g_variant_builder_add(&builder, "{sv}", "multiple",
+			      g_variant_new_boolean(FALSE));
+	g_variant_builder_add(&builder, "{sv}", "handle_token",
+			      g_variant_new_string(request_token));
+
+	available_cursor_modes = get_available_cursor_modes();
+
+	if (available_cursor_modes & PORTAL_CURSOR_MODE_METADATA)
+		g_variant_builder_add(
+			&builder, "{sv}", "cursor_mode",
+			g_variant_new_uint32(PORTAL_CURSOR_MODE_METADATA));
+	else if ((available_cursor_modes & PORTAL_CURSOR_MODE_EMBEDDED) &&
+		 capture->cursor_visible)
+		g_variant_builder_add(
+			&builder, "{sv}", "cursor_mode",
+			g_variant_new_uint32(PORTAL_CURSOR_MODE_EMBEDDED));
+	else
+		g_variant_builder_add(
+			&builder, "{sv}", "cursor_mode",
+			g_variant_new_uint32(PORTAL_CURSOR_MODE_HIDDEN));
+
+	if (get_screencast_version() >= 4) {
+		g_variant_builder_add(&builder, "{sv}", "persist_mode",
+				      g_variant_new_uint32(2));
+		if (capture->restore_token && *capture->restore_token) {
+			g_variant_builder_add(
+				&builder, "{sv}", "restore_token",
+				g_variant_new_string(capture->restore_token));
+		}
+	}
+
+	g_dbus_proxy_call(get_screencast_portal_proxy(), "SelectSources",
+			  g_variant_new("(oa{sv})", capture->session_handle,
+					&builder),
+			  G_DBUS_CALL_FLAGS_NONE, -1, capture->cancellable,
+			  on_source_selected_cb, call);
+
+	bfree(request_token);
+	bfree(request_path);
+}
+
+/* ------------------------------------------------- */
+
+static void on_create_session_response_received_cb(
+	GDBusConnection *connection, const char *sender_name,
+	const char *object_path, const char *interface_name,
+	const char *signal_name, GVariant *parameters, void *user_data)
+{
+	UNUSED_PARAMETER(connection);
+	UNUSED_PARAMETER(sender_name);
+	UNUSED_PARAMETER(object_path);
+	UNUSED_PARAMETER(interface_name);
+	UNUSED_PARAMETER(signal_name);
+
+	struct screencast_portal_capture *capture;
+	g_autoptr(GVariant) session_handle_variant = NULL;
+	g_autoptr(GVariant) result = NULL;
+	struct dbus_call_data *call = user_data;
+	uint32_t response;
+
+	capture = call->capture;
+
+	g_clear_pointer(&call, dbus_call_data_free);
+
+	g_variant_get(parameters, "(u@a{sv})", &response, &result);
+
+	if (response != 0) {
+		blog(LOG_WARNING,
+		     "[pipewire] Failed to create session, denied or cancelled by user");
+		return;
+	}
+
+	blog(LOG_INFO, "[pipewire] Screencast session created");
+
+	session_handle_variant =
+		g_variant_lookup_value(result, "session_handle", NULL);
+	capture->session_handle =
+		g_variant_dup_string(session_handle_variant, NULL);
+
+	select_source(capture);
+}
+
+static void on_session_created_cb(GObject *source, GAsyncResult *res,
+				  void *user_data)
+{
+	UNUSED_PARAMETER(user_data);
+
+	g_autoptr(GVariant) result = NULL;
+	g_autoptr(GError) error = NULL;
+
+	result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
+	if (error) {
+		if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			blog(LOG_ERROR,
+			     "[pipewire] Error creating screencast session: %s",
+			     error->message);
+		return;
+	}
+}
+
+static void create_session(struct screencast_portal_capture *capture)
+{
+	struct dbus_call_data *call;
+	GVariantBuilder builder;
+	char *session_token;
+	char *request_token;
+	char *request_path;
+
+	portal_create_request_path(&request_path, &request_token);
+	portal_create_session_path(NULL, &session_token);
+
+	call = subscribe_to_signal(capture, request_path,
+				   on_create_session_response_received_cb);
+
+	g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add(&builder, "{sv}", "handle_token",
+			      g_variant_new_string(request_token));
+	g_variant_builder_add(&builder, "{sv}", "session_handle_token",
+			      g_variant_new_string(session_token));
+
+	g_dbus_proxy_call(get_screencast_portal_proxy(), "CreateSession",
+			  g_variant_new("(a{sv})", &builder),
+			  G_DBUS_CALL_FLAGS_NONE, -1, capture->cancellable,
+			  on_session_created_cb, call);
+
+	bfree(session_token);
+	bfree(request_token);
+	bfree(request_path);
+}
+
+/* ------------------------------------------------- */
+
+static gboolean
+init_screencast_capture(struct screencast_portal_capture *capture)
+{
+	GDBusConnection *connection;
+	GDBusProxy *proxy;
+	char *aux;
+
+	capture->cancellable = g_cancellable_new();
+	connection = portal_get_dbus_connection();
+	if (!connection)
+		return FALSE;
+	proxy = get_screencast_portal_proxy();
+	if (!proxy)
+		return FALSE;
+
+	blog(LOG_INFO, "PipeWire initialized");
+
+	create_session(capture);
+
+	return TRUE;
+}
+
+static bool reload_session_cb(obs_properties_t *properties,
+			      obs_property_t *property, void *data)
+{
+	UNUSED_PARAMETER(properties);
+	UNUSED_PARAMETER(property);
+
+	struct screencast_portal_capture *capture = data;
+
+	g_clear_pointer(&capture->restore_token, bfree);
+	g_clear_pointer(&capture->obs_pw, obs_pipewire_destroy);
+
+	init_screencast_capture(capture);
+
+	return false;
+}
+
+/* obs_source_info methods */
+
+static const char *screencast_portal_desktop_capture_get_name(void *data)
+{
+	UNUSED_PARAMETER(data);
+	return obs_module_text("PipeWireDesktopCapture");
+}
+
+static const char *screencast_portal_window_capture_get_name(void *data)
+{
+	UNUSED_PARAMETER(data);
+	return obs_module_text("PipeWireWindowCapture");
+}
+
+static void *screencast_portal_desktop_capture_create(obs_data_t *settings,
+						      obs_source_t *source)
+{
+	struct screencast_portal_capture *capture;
+
+	capture = bzalloc(sizeof(struct screencast_portal_capture));
+	capture->capture_type = PORTAL_CAPTURE_TYPE_MONITOR;
+	capture->cursor_visible = obs_data_get_bool(settings, "ShowCursor");
+	capture->restore_token =
+		bstrdup(obs_data_get_string(settings, "RestoreToken"));
+	capture->source = source;
+
+	init_screencast_capture(capture);
+
+	return capture;
+}
+static void *screencast_portal_window_capture_create(obs_data_t *settings,
+						     obs_source_t *source)
+{
+	struct screencast_portal_capture *capture;
+
+	capture = bzalloc(sizeof(struct screencast_portal_capture));
+	capture->capture_type = PORTAL_CAPTURE_TYPE_WINDOW;
+	capture->cursor_visible = obs_data_get_bool(settings, "ShowCursor");
+	capture->restore_token =
+		bstrdup(obs_data_get_string(settings, "RestoreToken"));
+	capture->source = source;
+
+	init_screencast_capture(capture);
+
+	return capture;
+}
+
+static void screencast_portal_capture_destroy(void *data)
+{
+	struct screencast_portal_capture *capture = data;
+
+	if (!capture)
+		return;
+
+	if (capture->session_handle) {
+		g_dbus_connection_call(portal_get_dbus_connection(),
+				       "org.freedesktop.portal.Desktop",
+				       capture->session_handle,
+				       "org.freedesktop.portal.Session",
+				       "Close", NULL, NULL,
+				       G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL,
+				       NULL);
+
+		g_clear_pointer(&capture->session_handle, g_free);
+	}
+
+	g_clear_pointer(&capture->restore_token, bfree);
+
+	obs_pipewire_destroy(capture->obs_pw);
+	g_cancellable_cancel(capture->cancellable);
+	g_clear_object(&capture->cancellable);
+	bfree(capture);
+}
+
+static void screencast_portal_capture_save(void *data, obs_data_t *settings)
+{
+	struct screencast_portal_capture *capture = data;
+
+	obs_data_set_string(settings, "RestoreToken", capture->restore_token);
+}
+
+static void screencast_portal_capture_get_defaults(obs_data_t *settings)
+{
+	obs_data_set_default_bool(settings, "ShowCursor", true);
+	obs_data_set_default_string(settings, "RestoreToken", NULL);
+}
+
+static obs_properties_t *screencast_portal_capture_get_properties(void *data)
+{
+	struct screencast_portal_capture *capture = data;
+	const char *reload_string_id;
+	obs_properties_t *properties;
+
+	switch (capture->capture_type) {
+	case PORTAL_CAPTURE_TYPE_MONITOR:
+		reload_string_id = "PipeWireSelectMonitor";
+		break;
+	case PORTAL_CAPTURE_TYPE_WINDOW:
+		reload_string_id = "PipeWireSelectWindow";
+		break;
+	case PORTAL_CAPTURE_TYPE_VIRTUAL:
+	default:
+		return NULL;
+	}
+
+	properties = obs_properties_create();
+	obs_properties_add_button2(properties, "Reload",
+				   obs_module_text(reload_string_id),
+				   reload_session_cb, capture);
+	obs_properties_add_bool(properties, "ShowCursor",
+				obs_module_text("ShowCursor"));
+
+	return properties;
+}
+
+static void screencast_portal_capture_update(void *data, obs_data_t *settings)
+{
+	struct screencast_portal_capture *capture = data;
+
+	capture->cursor_visible = obs_data_get_bool(settings, "ShowCursor");
+
+	if (capture->obs_pw)
+		obs_pipewire_set_cursor_visible(capture->obs_pw,
+						capture->cursor_visible);
+}
+
+static void screencast_portal_capture_show(void *data)
+{
+	struct screencast_portal_capture *capture = data;
+
+	if (capture->obs_pw)
+		obs_pipewire_show(capture->obs_pw);
+}
+
+static void screencast_portal_capture_hide(void *data)
+{
+	struct screencast_portal_capture *capture = data;
+
+	if (capture->obs_pw)
+		obs_pipewire_hide(capture->obs_pw);
+}
+
+static uint32_t screencast_portal_capture_get_width(void *data)
+{
+	struct screencast_portal_capture *capture = data;
+
+	if (capture->obs_pw)
+		return obs_pipewire_get_width(capture->obs_pw);
+	else
+		return 0;
+}
+
+static uint32_t screencast_portal_capture_get_height(void *data)
+{
+	struct screencast_portal_capture *capture = data;
+
+	if (capture->obs_pw)
+		return obs_pipewire_get_height(capture->obs_pw);
+	else
+		return 0;
+}
+
+static void screencast_portal_capture_video_render(void *data,
+						   gs_effect_t *effect)
+{
+	struct screencast_portal_capture *capture = data;
+
+	if (capture->obs_pw)
+		obs_pipewire_video_render(capture->obs_pw, effect);
+}
+
+void screencast_portal_load(void)
+{
+	uint32_t available_capture_types = get_available_capture_types();
+	bool desktop_capture_available =
+		(available_capture_types & PORTAL_CAPTURE_TYPE_MONITOR) != 0;
+	bool window_capture_available =
+		(available_capture_types & PORTAL_CAPTURE_TYPE_WINDOW) != 0;
+
+	if (available_capture_types == 0) {
+		blog(LOG_INFO, "[pipewire] No captures available");
+		return;
+	}
+
+	blog(LOG_INFO, "[pipewire] Available captures:");
+	if (desktop_capture_available)
+		blog(LOG_INFO, "[pipewire]     - Desktop capture");
+	if (window_capture_available)
+		blog(LOG_INFO, "[pipewire]     - Window capture");
+
+	// Desktop capture
+	const struct obs_source_info screencast_portal_desktop_capture_info = {
+		.id = "pipewire-desktop-capture-source",
+		.type = OBS_SOURCE_TYPE_INPUT,
+		.output_flags = OBS_SOURCE_VIDEO,
+		.get_name = screencast_portal_desktop_capture_get_name,
+		.create = screencast_portal_desktop_capture_create,
+		.destroy = screencast_portal_capture_destroy,
+		.save = screencast_portal_capture_save,
+		.get_defaults = screencast_portal_capture_get_defaults,
+		.get_properties = screencast_portal_capture_get_properties,
+		.update = screencast_portal_capture_update,
+		.show = screencast_portal_capture_show,
+		.hide = screencast_portal_capture_hide,
+		.get_width = screencast_portal_capture_get_width,
+		.get_height = screencast_portal_capture_get_height,
+		.video_render = screencast_portal_capture_video_render,
+		.icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE,
+	};
+	if (desktop_capture_available)
+		obs_register_source(&screencast_portal_desktop_capture_info);
+
+	// Window capture
+	const struct obs_source_info screencast_portal_window_capture_info = {
+		.id = "pipewire-window-capture-source",
+		.type = OBS_SOURCE_TYPE_INPUT,
+		.output_flags = OBS_SOURCE_VIDEO,
+		.get_name = screencast_portal_window_capture_get_name,
+		.create = screencast_portal_window_capture_create,
+		.destroy = screencast_portal_capture_destroy,
+		.save = screencast_portal_capture_save,
+		.get_defaults = screencast_portal_capture_get_defaults,
+		.get_properties = screencast_portal_capture_get_properties,
+		.update = screencast_portal_capture_update,
+		.show = screencast_portal_capture_show,
+		.hide = screencast_portal_capture_hide,
+		.get_width = screencast_portal_capture_get_width,
+		.get_height = screencast_portal_capture_get_height,
+		.video_render = screencast_portal_capture_video_render,
+		.icon_type = OBS_ICON_TYPE_WINDOW_CAPTURE,
+	};
+	if (window_capture_available)
+		obs_register_source(&screencast_portal_window_capture_info);
+}

+ 3 - 3
plugins/linux-pipewire/pipewire-capture.h → plugins/linux-pipewire/screencast-portal.h

@@ -1,6 +1,6 @@
-/* pipewire-capture.h
+/* screencast-portal.h
  *
- * Copyright 2020 Georges Basile Stavracas Neto <[email protected]>
+ * Copyright 2022 Georges Basile Stavracas Neto <[email protected]>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,4 +20,4 @@
 
 #pragma once
 
-void pipewire_capture_load(void);
+void screencast_portal_load(void);