Merge pull request #6645 from GeorgesStavracas/gbsneto/pipewire-cleanup
Various cleanups to linux-pipewire
This commit is contained in:
commit
7c493ea035
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,180 +0,0 @@
|
||||
/* pipewire-capture.c
|
||||
*
|
||||
* Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
|
||||
*
|
||||
* 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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -21,10 +21,14 @@
|
||||
#include "portal.h"
|
||||
#include "pipewire.h"
|
||||
|
||||
static GDBusConnection *connection = NULL;
|
||||
static GDBusProxy *proxy = NULL;
|
||||
#include <util/dstr.h>
|
||||
|
||||
static void ensure_proxy(void)
|
||||
#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 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;
|
||||
}
|
||||
|
||||
uint32_t portal_get_available_cursor_modes(void)
|
||||
{
|
||||
g_autoptr(GVariant) cached_cursor_modes = NULL;
|
||||
uint32_t available_cursor_modes;
|
||||
|
||||
ensure_proxy();
|
||||
|
||||
if (!proxy)
|
||||
return 0;
|
||||
|
||||
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;
|
||||
|
||||
return available_cursor_modes;
|
||||
}
|
||||
|
||||
uint32_t portal_get_screencast_version(void)
|
||||
{
|
||||
g_autoptr(GVariant) cached_version = NULL;
|
||||
uint32_t version;
|
||||
|
||||
ensure_proxy();
|
||||
|
||||
if (!proxy)
|
||||
return 0;
|
||||
|
||||
cached_version = g_dbus_proxy_get_cached_property(proxy, "version");
|
||||
version = cached_version ? g_variant_get_uint32(cached_version) : 0;
|
||||
|
||||
return version;
|
||||
return sender_name;
|
||||
}
|
||||
|
||||
GDBusConnection *portal_get_dbus_connection(void)
|
||||
{
|
||||
ensure_proxy();
|
||||
ensure_connection();
|
||||
return connection;
|
||||
}
|
||||
|
||||
GDBusProxy *portal_get_dbus_proxy(void)
|
||||
void portal_create_request_path(char **out_path, char **out_token)
|
||||
{
|
||||
ensure_proxy();
|
||||
return proxy;
|
||||
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) {
|
||||
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;
|
||||
|
||||
bfree(sender_name);
|
||||
}
|
||||
}
|
||||
|
||||
void portal_create_session_path(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) {
|
||||
char *sender_name;
|
||||
struct dstr str;
|
||||
|
||||
sender_name = get_sender_name();
|
||||
|
||||
dstr_init(&str);
|
||||
dstr_printf(&str, SESSION_PATH, sender_name,
|
||||
session_token_count);
|
||||
*out_path = str.array;
|
||||
|
||||
bfree(sender_name);
|
||||
}
|
||||
}
|
||||
|
@ -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
plugins/linux-pipewire/screencast-portal.c
Normal file
869
plugins/linux-pipewire/screencast-portal.c
Normal file
@ -0,0 +1,869 @@
|
||||
/* screencast-portal.c
|
||||
*
|
||||
* Copyright 2022 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
|
||||
*
|
||||
* 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);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/* pipewire-capture.h
|
||||
/* screencast-portal.h
|
||||
*
|
||||
* Copyright 2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
|
||||
* Copyright 2022 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
|
||||
*
|
||||
* 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);
|
Loading…
x
Reference in New Issue
Block a user