linux-pipewire: Move all portal code to screencast-portal.c

Sorry this is such a massive commit. The entangled nature of the
code in pipewire.c makes it impossible to reasonably split this
in smaller commits.

Move all D-Bus / portals code from pipewire.c into the recently
introduced screencast-portal.c.
This commit is contained in:
Georges Basile Stavracas Neto 2022-06-22 13:33:21 -03:00
parent 45afcf1997
commit 0e9c208fc5
3 changed files with 590 additions and 591 deletions

View File

@ -59,17 +59,9 @@ struct format_info {
};
struct _obs_pipewire_data {
GCancellable *cancellable;
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;
@ -102,20 +94,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)
@ -147,72 +131,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 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) {
@ -236,24 +154,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);
}
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)
@ -929,439 +833,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;
portal_create_request_path(&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;
portal_create_request_path(&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;
portal_create_request_path(&request_path, &request_token);
portal_create_session_path(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;
blog(LOG_INFO, "PipeWire initialized");
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;
}
@ -1374,43 +856,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)
@ -1479,8 +929,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;
}

View File

@ -27,25 +27,14 @@
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);

View File

@ -21,10 +21,522 @@
#include "pipewire.h"
#include "portal.h"
#include <gio/gunixfdlist.h>
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;
};
/* ------------------------------------------------- */
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(
portal_get_dbus_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 (portal_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(portal_get_dbus_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 = 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) &&
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 (portal_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(portal_get_dbus_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(portal_get_dbus_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 = portal_get_dbus_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)
@ -45,8 +557,13 @@ static void *screencast_portal_desktop_capture_create(obs_data_t *settings,
struct screencast_portal_capture *capture;
capture = bzalloc(sizeof(struct screencast_portal_capture));
capture->obs_pw = obs_pipewire_create(PORTAL_CAPTURE_TYPE_MONITOR,
settings, source);
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;
}
@ -56,8 +573,13 @@ static void *screencast_portal_window_capture_create(obs_data_t *settings,
struct screencast_portal_capture *capture;
capture = bzalloc(sizeof(struct screencast_portal_capture));
capture->obs_pw = obs_pipewire_create(PORTAL_CAPTURE_TYPE_WINDOW,
settings, source);
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;
}
@ -69,7 +591,23 @@ static void screencast_portal_capture_destroy(void *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);
}
@ -77,67 +615,88 @@ static void screencast_portal_capture_save(void *data, obs_data_t *settings)
{
struct screencast_portal_capture *capture = data;
obs_pipewire_save(capture->obs_pw, settings);
obs_data_set_string(settings, "RestoreToken", capture->restore_token);
}
static void screencast_portal_capture_get_defaults(obs_data_t *settings)
{
obs_pipewire_get_defaults(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;
enum portal_capture_type capture_type;
const char *reload_string_id;
obs_properties_t *properties;
capture_type = obs_pipewire_get_capture_type(capture->obs_pw);
switch (capture_type) {
switch (capture->capture_type) {
case PORTAL_CAPTURE_TYPE_MONITOR:
return obs_pipewire_get_properties(capture->obs_pw,
"PipeWireSelectMonitor");
reload_string_id = "PipeWireSelectMonitor";
break;
case PORTAL_CAPTURE_TYPE_WINDOW:
return obs_pipewire_get_properties(capture->obs_pw,
"PipeWireSelectWindow");
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;
obs_pipewire_update(capture->obs_pw, settings);
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;
obs_pipewire_show(capture->obs_pw);
if (capture->obs_pw)
obs_pipewire_show(capture->obs_pw);
}
static void screencast_portal_capture_hide(void *data)
{
struct screencast_portal_capture *capture = data;
obs_pipewire_hide(capture->obs_pw);
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;
return obs_pipewire_get_width(capture->obs_pw);
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;
return obs_pipewire_get_height(capture->obs_pw);
if (capture->obs_pw)
return obs_pipewire_get_height(capture->obs_pw);
else
return 0;
}
static void screencast_portal_capture_video_render(void *data,
@ -145,7 +704,8 @@ static void screencast_portal_capture_video_render(void *data,
{
struct screencast_portal_capture *capture = data;
obs_pipewire_video_render(capture->obs_pw, effect);
if (capture->obs_pw)
obs_pipewire_video_render(capture->obs_pw, effect);
}
void screencast_portal_load(void)