diff --git a/host/platform/Linux/capture/pipewire/CMakeLists.txt b/host/platform/Linux/capture/pipewire/CMakeLists.txt index d5310292..8b417293 100644 --- a/host/platform/Linux/capture/pipewire/CMakeLists.txt +++ b/host/platform/Linux/capture/pipewire/CMakeLists.txt @@ -1,11 +1,21 @@ cmake_minimum_required(VERSION 3.0) 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 src/pipewire.c + src/portal.c ) target_link_libraries(capture_pipewire + PkgConfig::CAPTURE_PIPEWIRE lg_common ) diff --git a/host/platform/Linux/capture/pipewire/src/pipewire.c b/host/platform/Linux/capture/pipewire/src/pipewire.c index 592cfcdf..26c8202e 100644 --- a/host/platform/Linux/capture/pipewire/src/pipewire.c +++ b/host/platform/Linux/capture/pipewire/src/pipewire.c @@ -18,14 +18,38 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "portal.h" #include "interface/capture.h" #include "interface/platform.h" #include "common/debug.h" +#include "common/stringutils.h" #include #include +#include + +#include +#include +#include +#include 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; @@ -38,50 +62,381 @@ static bool pipewire_deinit(); static const char * pipewire_getName(void) { - return "Pipewire"; + return "PipeWire"; } static bool pipewire_create(CaptureGetPointerBuffer getPointerBufferFn, CapturePostPointerBuffer postPointerBufferFn) { DEBUG_ASSERT(!this); + pw_init(NULL, NULL); this = calloc(1, sizeof(*this)); 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, ¶m, 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, ¶m, 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) { - 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: pipewire_deinit(); 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) { - 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) { + DEBUG_ASSERT(this); + pw_deinit(); free(this); this = NULL; } 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, 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, 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 = @@ -91,6 +446,7 @@ struct CaptureInterface Capture_pipewire = .getName = pipewire_getName, .create = pipewire_create, .init = pipewire_init, + .stop = pipewire_stop, .deinit = pipewire_deinit, .free = pipewire_free, .capture = pipewire_capture, diff --git a/host/platform/Linux/capture/pipewire/src/portal.c b/host/platform/Linux/capture/pipewire/src/portal.c new file mode 100644 index 00000000..a494afce --- /dev/null +++ b/host/platform/Linux/capture/pipewire/src/portal.c @@ -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 +#include + +#include +#include + +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; +} diff --git a/host/platform/Linux/capture/pipewire/src/portal.h b/host/platform/Linux/capture/pipewire/src/portal.h new file mode 100644 index 00000000..7e5fc2a7 --- /dev/null +++ b/host/platform/Linux/capture/pipewire/src/portal.h @@ -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 +#include + +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);