[host] pipewire: implement basic capture

It works, with the following limitations:
1. user is forced to select the monitor through platform-specific mechanisms
   every time the client starts.
2. cursor is composed onto the screen, and no position can be reported.
This commit is contained in:
Quantum 2021-08-20 03:08:31 -04:00 committed by Geoffrey McRae
parent 28eae3bd86
commit 1e2caf4c9f
4 changed files with 840 additions and 6 deletions

View file

@ -1,11 +1,21 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.0)
project(capture_pipewire LANGUAGES C) project(capture_pipewire LANGUAGES C)
find_package(PkgConfig)
pkg_check_modules(CAPTURE_PIPEWIRE REQUIRED IMPORTED_TARGET
gio-2.0
gio-unix-2.0
libpipewire-0.3
libspa-0.2
)
add_library(capture_pipewire STATIC add_library(capture_pipewire STATIC
src/pipewire.c src/pipewire.c
src/portal.c
) )
target_link_libraries(capture_pipewire target_link_libraries(capture_pipewire
PkgConfig::CAPTURE_PIPEWIRE
lg_common lg_common
) )

View file

@ -18,14 +18,38 @@
* Temple Place, Suite 330, Boston, MA 02111-1307 USA * Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include "portal.h"
#include "interface/capture.h" #include "interface/capture.h"
#include "interface/platform.h" #include "interface/platform.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/stringutils.h"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include <pipewire/pipewire.h>
#include <spa/pod/builder.h>
#include <spa/param/format.h>
#include <spa/param/video/format-utils.h>
struct pipewire struct pipewire
{ {
struct Portal * portal;
char * sessionHandle;
struct pw_thread_loop * threadLoop;
struct pw_context * context;
struct pw_core * core;
struct spa_hook coreListener;
struct pw_stream * stream;
struct spa_hook streamListener;
bool stop;
bool hasFormat;
bool formatChanged;
int width, height;
CaptureFormat format;
uint8_t * frameData;
unsigned int formatVer;
}; };
static struct pipewire * this = NULL; static struct pipewire * this = NULL;
@ -38,50 +62,381 @@ static bool pipewire_deinit();
static const char * pipewire_getName(void) static const char * pipewire_getName(void)
{ {
return "Pipewire"; return "PipeWire";
} }
static bool pipewire_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostPointerBuffer postPointerBufferFn) static bool pipewire_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostPointerBuffer postPointerBufferFn)
{ {
DEBUG_ASSERT(!this); DEBUG_ASSERT(!this);
pw_init(NULL, NULL);
this = calloc(1, sizeof(*this)); this = calloc(1, sizeof(*this));
return true; return true;
} }
static void coreErrorCallback(void * opaque, uint32_t id, int seq, int res, const char * message)
{
DEBUG_ERROR("pipewire error: id %" PRIu32 ", seq: %d, res: %d (%s): %s",
id, seq, res, strerror(res), message);
}
static const struct pw_core_events coreEvents = {
PW_VERSION_CORE_EVENTS,
.error = coreErrorCallback,
};
static bool startStream(struct pw_stream * stream, uint32_t node)
{
char buffer[1024];
struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
const struct spa_pod * param = spa_pod_builder_add_object(
&builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(6,
SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA,
SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx,
SPA_VIDEO_FORMAT_r210, SPA_VIDEO_FORMAT_RGBA_F16),
SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
&SPA_RECTANGLE(1920, 1080), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(8192, 4320)),
SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(
&SPA_FRACTION(60, 1), &SPA_FRACTION(0, 1), &SPA_FRACTION(360, 1)));
return pw_stream_connect(stream, PW_DIRECTION_INPUT, node,
PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, &param, 1) >= 0;
}
static void streamProcessCallback(void * opaque)
{
if (!this->hasFormat)
return;
struct pw_buffer * pwBuffer = NULL;
// dequeue all buffers to get the latest one
while (true)
{
struct pw_buffer * tmp = pw_stream_dequeue_buffer(this->stream);
if (!tmp)
break;
if (pwBuffer)
pw_stream_queue_buffer(this->stream, pwBuffer);
pwBuffer = tmp;
}
if (!pwBuffer)
{
DEBUG_WARN("Pipewire out of buffers");
return;
}
struct spa_buffer * buffer = pwBuffer->buffer;
if (!buffer->datas[0].chunk->size)
return;
this->frameData = buffer->datas[0].data;
pw_thread_loop_signal(this->threadLoop, true);
pw_stream_queue_buffer(this->stream, pwBuffer);
}
static CaptureFormat convertSpaFormat(enum spa_video_format spa)
{
switch (spa)
{
case SPA_VIDEO_FORMAT_RGBA:
case SPA_VIDEO_FORMAT_RGBx:
return CAPTURE_FMT_RGBA;
case SPA_VIDEO_FORMAT_BGRA:
case SPA_VIDEO_FORMAT_BGRx:
return CAPTURE_FMT_BGRA;
case SPA_VIDEO_FORMAT_r210:
return CAPTURE_FMT_RGBA10;
case SPA_VIDEO_FORMAT_RGBA_F16:
return CAPTURE_FMT_RGBA16F;
default:
return -1;
}
}
static void streamParamChangedCallback(void * opaque, uint32_t id,
const struct spa_pod * param)
{
if (!param || id != SPA_PARAM_Format)
return;
uint32_t mediaType, mediaSubtype;
if (spa_format_parse(param, &mediaType, &mediaSubtype) < 0 ||
mediaType != SPA_MEDIA_TYPE_video ||
mediaSubtype != SPA_MEDIA_SUBTYPE_raw)
return;
struct spa_video_info_raw info;
if (spa_format_video_raw_parse(param, &info) < 0)
{
DEBUG_ERROR("Failed to parse video info");
return;
}
this->width = info.size.width;
this->height = info.size.height;
this->format = convertSpaFormat(info.format);
if (this->hasFormat)
{
this->formatChanged = true;
return;
}
char buffer[1024];
struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
param = spa_pod_builder_add_object(
&builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_dataType, SPA_POD_Int(1 << SPA_DATA_MemPtr));
pw_stream_update_params(this->stream, &param, 1);
this->hasFormat = true;
pw_thread_loop_signal(this->threadLoop, true);
}
static const struct pw_stream_events streamEvents = {
PW_VERSION_STREAM_EVENTS,
.process = streamProcessCallback,
.param_changed = streamParamChangedCallback,
};
static bool pipewire_init(void) static bool pipewire_init(void)
{ {
goto fail; DEBUG_ASSERT(this);
this->stop = false;
this->portal = portal_create();
if (!this->portal)
{
DEBUG_ERROR("Failed to create xdg-desktop-portal for screencasting");
goto fail;
}
if (!portal_createScreenCastSession(this->portal, &this->sessionHandle))
{
DEBUG_ERROR("Failed to create ScreenCast session");
goto fail;
}
DEBUG_INFO("Got session handle: %s", this->sessionHandle);
if (!portal_selectSource(this->portal, this->sessionHandle))
{
DEBUG_ERROR("Failed to select source");
goto fail;
}
uint32_t pipewireNode = portal_getPipewireNode(this->portal, this->sessionHandle);
if (!pipewireNode)
{
DEBUG_ERROR("Failed to get pipewire node");
goto fail;
}
int pipewireFd = portal_openPipewireRemote(this->portal, this->sessionHandle);
if (pipewireFd < 0)
{
DEBUG_ERROR("Failed to get pipewire fd");
goto fail;
}
this->threadLoop = pw_thread_loop_new("lg-pipewire-capture", NULL);
if (!this->threadLoop)
{
DEBUG_ERROR("Failed to create pipewire thread loop");
close(pipewireFd);
goto fail;
}
this->context = pw_context_new(pw_thread_loop_get_loop(this->threadLoop), NULL, 0);
if (!this->context)
{
DEBUG_ERROR("Failed to create pipewire context");
close(pipewireFd);
goto fail;
}
if (pw_thread_loop_start(this->threadLoop) < 0)
{
DEBUG_ERROR("Failed to start pipewire thread loop");
close(pipewireFd);
goto fail;
}
pw_thread_loop_lock(this->threadLoop);
this->core = pw_context_connect_fd(this->context, pipewireFd, NULL, 0);
if (!this->core)
{
DEBUG_ERROR("Failed to create pipewire core: %s", strerror(errno));
pw_thread_loop_unlock(this->threadLoop);
close(pipewireFd);
goto fail;
}
pw_core_add_listener(this->core, &this->coreListener, &coreEvents, NULL);
this->stream = pw_stream_new(this->core, "Looking Glass (host)", pw_properties_new(
PW_KEY_MEDIA_TYPE, "Video",
PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_MEDIA_ROLE, "Screen", NULL));
if (!this->stream)
{
DEBUG_ERROR("Failed to create pipewire stream");
pw_thread_loop_unlock(this->threadLoop);
goto fail;
}
this->hasFormat = false;
this->formatChanged = false;
this->frameData = NULL;
pw_stream_add_listener(this->stream, &this->streamListener, &streamEvents, NULL);
if (!startStream(this->stream, pipewireNode))
{
DEBUG_ERROR("Failed to start pipewire stream");
pw_thread_loop_unlock(this->threadLoop);
goto fail;
}
pw_thread_loop_unlock(this->threadLoop);
while (!this->hasFormat)
pw_thread_loop_wait(this->threadLoop);
if (this->format < 0)
{
DEBUG_ERROR("Unknown frame format");
pw_thread_loop_accept(this->threadLoop);
goto fail;
}
DEBUG_INFO("Frame size : %dx%d", this->width, this->height);
pw_thread_loop_accept(this->threadLoop);
return true;
fail: fail:
pipewire_deinit(); pipewire_deinit();
return false; return false;
} }
static void pipewire_stop(void)
{
this->stop = true;
pw_stream_disconnect(this->stream);
pw_thread_loop_signal(this->threadLoop, false);
}
static bool pipewire_deinit(void) static bool pipewire_deinit(void)
{ {
return false; if (this->stream)
{
pw_stream_disconnect(this->stream);
this->stream = NULL;
}
if (this->core)
{
pw_core_disconnect(this->core);
this->core = NULL;
}
if (this->threadLoop)
{
pw_thread_loop_stop(this->threadLoop);
pw_thread_loop_destroy(this->threadLoop);
this->threadLoop = NULL;
}
if (this->sessionHandle)
portal_destroySession(this->portal, &this->sessionHandle);
if (this->portal)
{
portal_free(this->portal);
this->portal = NULL;
}
return true;
} }
static void pipewire_free(void) static void pipewire_free(void)
{ {
DEBUG_ASSERT(this);
pw_deinit();
free(this); free(this);
this = NULL; this = NULL;
} }
static CaptureResult pipewire_capture(void) static CaptureResult pipewire_capture(void)
{ {
return CAPTURE_RESULT_ERROR; int result;
restart:
result = pw_thread_loop_timed_wait(this->threadLoop, 1);
if (this->stop)
return CAPTURE_RESULT_REINIT;
if (result == ETIMEDOUT)
return CAPTURE_RESULT_TIMEOUT;
if (this->formatChanged)
{
++this->formatVer;
this->formatChanged = false;
pw_thread_loop_accept(this->threadLoop);
goto restart;
}
return CAPTURE_RESULT_OK;
} }
static CaptureResult pipewire_waitFrame(CaptureFrame * frame, static CaptureResult pipewire_waitFrame(CaptureFrame * frame,
const size_t maxFrameSize) const size_t maxFrameSize)
{ {
return CAPTURE_RESULT_ERROR; if (this->stop)
return CAPTURE_RESULT_REINIT;
const int bpp = this->format == CAPTURE_FMT_RGBA16F ? 8 : 4;
const unsigned int maxHeight = maxFrameSize / (this->width * bpp);
frame->formatVer = this->formatVer;
frame->format = this->format;
frame->width = this->width;
frame->height = maxHeight > this->height ? this->height : maxHeight;
frame->realHeight = this->height;
frame->pitch = this->width * bpp;
frame->stride = this->width;
frame->rotation = CAPTURE_ROT_0;
// TODO: implement damage.
frame->damageRectsCount = 0;
return CAPTURE_RESULT_OK;
} }
static CaptureResult pipewire_getFrame(FrameBuffer * frame, static CaptureResult pipewire_getFrame(FrameBuffer * frame,
const unsigned int height, int frameIndex) const unsigned int height, int frameIndex)
{ {
return CAPTURE_RESULT_ERROR; if (this->stop || !this->frameData)
return CAPTURE_RESULT_REINIT;
const int bpp = this->format == CAPTURE_FMT_RGBA16F ? 8 : 4;
framebuffer_write(frame, this->frameData, height * this->width * bpp);
pw_thread_loop_accept(this->threadLoop);
return CAPTURE_RESULT_OK;
} }
struct CaptureInterface Capture_pipewire = struct CaptureInterface Capture_pipewire =
@ -91,6 +446,7 @@ struct CaptureInterface Capture_pipewire =
.getName = pipewire_getName, .getName = pipewire_getName,
.create = pipewire_create, .create = pipewire_create,
.init = pipewire_init, .init = pipewire_init,
.stop = pipewire_stop,
.deinit = pipewire_deinit, .deinit = pipewire_deinit,
.free = pipewire_free, .free = pipewire_free,
.capture = pipewire_capture, .capture = pipewire_capture,

View file

@ -0,0 +1,428 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* 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, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "portal.h"
#include "common/debug.h"
#include "common/stringutils.h"
#include <string.h>
#include <stdlib.h>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
struct Portal
{
GDBusConnection * conn;
GDBusProxy * screenCast;
char * senderName;
};
struct DBusCallback
{
guint id;
bool completed;
void * opaque;
};
struct Portal * portal_create(void)
{
struct Portal * portal = calloc(1, sizeof(*portal));
if (!portal)
{
DEBUG_ERROR("Failed to allocate memory");
return NULL;
}
g_autoptr(GError) err = NULL;
portal->conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &err);
if (err)
{
DEBUG_ERROR("Failed to get dbus session: %s", err->message);
goto fail;
}
const gchar * uniqueName = g_dbus_connection_get_unique_name(portal->conn);
if (!uniqueName)
{
DEBUG_ERROR("Failed to get dbus connection unique name");
goto fail;
}
portal->senderName = strdup(uniqueName + 1);
if (!portal->senderName)
{
DEBUG_ERROR("Failed to allocate memory");
goto fail;
}
char * ptr = portal->senderName;
while ((ptr = strchr(ptr, '.')))
*ptr++ = '_';
portal->screenCast = g_dbus_proxy_new_sync(portal->conn, G_DBUS_PROXY_FLAGS_NONE, NULL,
"org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
"org.freedesktop.portal.ScreenCast", NULL, &err);
if (err)
{
DEBUG_ERROR("Failed to get ScreenCast portal: %s", err->message);
goto fail;
}
return portal;
fail:
portal_free(portal);
return NULL;
}
void portal_free(struct Portal * portal)
{
if (!portal)
return;
if (portal->screenCast)
g_object_unref(portal->screenCast);
free(portal->senderName);
if (portal->conn)
g_object_unref(portal->conn);
free(portal);
}
static void callbackRegister(struct Portal * portal, struct DBusCallback * data,
const char * path, GDBusSignalCallback func)
{
data->id = g_dbus_connection_signal_subscribe(portal->conn, "org.freedesktop.portal.Desktop",
"org.freedesktop.portal.Request", "Response", path, NULL,
G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, func, data, NULL);
}
static void callbackUnregister(struct Portal * portal, struct DBusCallback * data)
{
if (!data->id)
return;
g_dbus_connection_signal_unsubscribe(portal->conn, data->id);
}
static bool getRequestPath(struct Portal * portal, char ** path, char ** token)
{
static uint64_t counter = 0;
alloc_sprintf(token, "lg%" PRIu64, counter);
if (!*token)
return false;
alloc_sprintf(path, "/org/freedesktop/portal/desktop/request/%s/lg%" PRIu64,
portal->senderName, counter);
if (!path)
{
free(*token);
*token = NULL;
return false;
}
return true;
}
static bool getSessionToken(char ** token)
{
static uint64_t counter = 0;
alloc_sprintf(token, "lg%" PRIu64, counter);
return !!*token;
}
static void createSessionCallback(GDBusConnection * conn, const char * senderName,
const char *objectPath, const char * interfaceName, const char * signalName,
GVariant * params, void * opaque)
{
struct DBusCallback * callback = opaque;
uint32_t status;
g_autoptr(GVariant) result = NULL;
g_variant_get(params, "(u@a{sv})", &status, &result);
if (status != 0)
DEBUG_ERROR("Failed to create ScreenCast: %" PRIu32, status);
else
g_variant_lookup(result, "session_handle", "s", callback->opaque);
callback->completed = true;
}
bool portal_createScreenCastSession(struct Portal * portal, char ** handle)
{
g_autoptr(GError) err = NULL;
char * requestPath = NULL;
char * requestToken = NULL;
char * sessionToken = NULL;
bool result = false;
struct DBusCallback callback = {0, .opaque = handle};
if (!getRequestPath(portal, &requestPath, &requestToken))
{
DEBUG_ERROR("Failed to get request path and token");
goto cleanup;
}
if (!getSessionToken(&sessionToken))
{
DEBUG_ERROR("Failed to get session token");
goto cleanup;
}
*handle = NULL;
callbackRegister(portal, &callback, requestPath, createSessionCallback);
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(requestToken));
g_variant_builder_add(&builder, "{sv}", "session_handle_token", g_variant_new_string(sessionToken));
g_autoptr(GVariant) response = g_dbus_proxy_call_sync(portal->screenCast, "CreateSession",
g_variant_new("(a{sv})", &builder), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err);
if (err)
{
DEBUG_ERROR("Failed to create ScreenCast session: %s", err->message);
goto cleanup;
}
while (!callback.completed)
g_main_context_iteration(NULL, TRUE);
if (*handle)
result = true;
cleanup:
callbackUnregister(portal, &callback);
free(requestPath);
free(requestToken);
free(sessionToken);
return result;
}
void portal_destroySession(struct Portal * portal, char ** sessionHandle)
{
if (!*sessionHandle)
return;
g_dbus_connection_call_sync(portal->conn, "org.freedesktop.portal.Desktop",
*sessionHandle, "org.freedesktop.portal.Session", "Close", NULL, NULL,
G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL);
g_free(*sessionHandle);
sessionHandle = NULL;
}
static void selectSourceCallback(GDBusConnection * conn, const char * senderName,
const char *objectPath, const char * interfaceName, const char * signalName,
GVariant * params, void * opaque)
{
struct DBusCallback * callback = opaque;
uint32_t status;
g_autoptr(GVariant) result = NULL;
g_variant_get(params, "(u@a{sv})", &status, &result);
if (status != 0)
DEBUG_ERROR("Failed select sources: %" PRIu32, status);
else
callback->opaque = (void *) 1;
callback->completed = true;
}
bool portal_selectSource(struct Portal * portal, const char * sessionHandle)
{
g_autoptr(GError) err = NULL;
bool result = false;
char * requestPath = NULL;
char * requestToken = NULL;
struct DBusCallback callback = {0};
if (!getRequestPath(portal, &requestPath, &requestToken))
{
DEBUG_ERROR("Failed to get request path and token");
goto cleanup;
}
callbackRegister(portal, &callback, requestPath, selectSourceCallback);
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&builder, "{sv}", "types", g_variant_new_uint32(PIPEWIRE_CAPTURE_DESKTOP));
g_variant_builder_add(&builder, "{sv}", "multiple", g_variant_new_boolean(FALSE));
g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(requestToken));
g_autoptr(GVariant) cursorModes_ = g_dbus_proxy_get_cached_property(
portal->screenCast, "AvailableCursorModes");
uint32_t cursorModes = cursorModes_ ? g_variant_get_uint32(cursorModes_) : 0;
// TODO: support mode 4 (separate cursor)
if (cursorModes & 2)
{
DEBUG_INFO("Cursor mode : embedded");
g_variant_builder_add(&builder, "{sv}", "cursor_mode", g_variant_new_uint32(2));
}
else if (cursorModes & 1)
{
DEBUG_INFO("Cursor mode : none");
g_variant_builder_add(&builder, "{sv}", "cursor_mode", g_variant_new_uint32(1));
}
else
{
DEBUG_ERROR("No known cursor mode found");
goto cleanup;
}
g_autoptr(GVariant) response = g_dbus_proxy_call_sync(portal->screenCast, "SelectSources",
g_variant_new("(oa{sv})", sessionHandle, &builder), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err);
if (err)
{
DEBUG_ERROR("Failed to call SelectSources: %s", err->message);
goto cleanup;
}
while (!callback.completed)
g_main_context_iteration(NULL, TRUE);
result = callback.opaque;
cleanup:
callbackUnregister(portal, &callback);
free(requestPath);
free(requestToken);
return result;
}
static void pipewireNodeCallback(GDBusConnection * conn, const char * senderName,
const char *objectPath, const char * interfaceName, const char * signalName,
GVariant * params, void * opaque)
{
struct DBusCallback * callback = opaque;
callback->completed = true;
uint32_t status;
g_autoptr(GVariant) result = NULL;
g_variant_get(params, "(u@a{sv})", &status, &result);
if (status != 0)
{
DEBUG_ERROR("Failed start screencast: %" PRIu32, status);
return;
}
g_autoptr(GVariant) streams = g_variant_lookup_value(result, "streams", G_VARIANT_TYPE_ARRAY);
GVariantIter iter;
g_variant_iter_init(&iter, streams);
size_t count = g_variant_iter_n_children(&iter);
if (count != 1)
{
DEBUG_WARN("Received more than one stream, discarding all but last one");
while (count-- > 1)
{
g_autoptr(GVariant) prop = NULL;
uint32_t node;
g_variant_iter_loop(&iter, "(u@a{sv})", &node, &prop);
}
}
g_autoptr(GVariant) prop = NULL;
g_variant_iter_loop(&iter, "(u@a{sv})", callback->opaque, &prop);
}
uint32_t portal_getPipewireNode(struct Portal * portal, const char * sessionHandle)
{
g_autoptr(GError) err = NULL;
char * requestPath = NULL;
char * requestToken = NULL;
uint32_t result = 0;
struct DBusCallback callback = {0, .opaque = &result};
if (!getRequestPath(portal, &requestPath, &requestToken))
{
DEBUG_ERROR("Failed to get request path and token");
goto cleanup;
}
callbackRegister(portal, &callback, requestPath, pipewireNodeCallback);
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(requestToken));
g_autoptr(GVariant) response = g_dbus_proxy_call_sync(portal->screenCast, "Start",
g_variant_new("(osa{sv})", sessionHandle, "", &builder), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err);
if (err)
{
DEBUG_ERROR("Failed to call Start on session: %s", err->message);
goto cleanup;
}
while (!callback.completed)
g_main_context_iteration(NULL, TRUE);
cleanup:
callbackUnregister(portal, &callback);
free(requestPath);
free(requestToken);
return result;
}
int portal_openPipewireRemote(struct Portal * portal, const char * sessionHandle)
{
g_autoptr(GError) err = NULL;
g_autoptr(GUnixFDList) fdList = NULL;
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_autoptr(GVariant) response = g_dbus_proxy_call_with_unix_fd_list_sync(portal->screenCast,
"OpenPipeWireRemote", g_variant_new("(oa{sv})", sessionHandle, &builder),
G_DBUS_CALL_FLAGS_NONE, -1, NULL, &fdList, NULL, &err);
if (err)
{
DEBUG_ERROR("Failed to call OpenPipeWireRemote on session: %s", err->message);
return -1;
}
gint32 index;
g_variant_get(response, "(h)", &index, &err);
if (err)
{
DEBUG_ERROR("Failed to get pipewire fd index: %s", err->message);
return -1;
}
int fd = g_unix_fd_list_get(fdList, index, &err);
if (err)
{
DEBUG_ERROR("Failed to get pipewire fd: %s", err->message);
return -1;
}
return fd;
}

View file

@ -0,0 +1,40 @@
/**
* Looking Glass
* Copyright © 2017-2021 The Looking Glass Authors
* https://looking-glass.io
*
* 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, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
enum PipewireCaptureType
{
PIPEWIRE_CAPTURE_DESKTOP = 1,
PIPEWIRE_CAPTURE_WINDOW = 2,
};
struct Portal;
struct Portal * portal_create(void);
void portal_free(struct Portal * portal);
bool portal_createScreenCastSession(struct Portal * portal, char ** handle);
void portal_destroySession(struct Portal * portal, char ** sessionHandle);
bool portal_selectSource(struct Portal * portal, const char * sessionHandle);
uint32_t portal_getPipewireNode(struct Portal * portal, const char * sessionHandle);
int portal_openPipewireRemote(struct Portal * portal, const char * sessionHandle);