From acc70e4ceece4252894d51d9e501654e4da683d7 Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Wed, 2 Jun 2021 12:25:00 +0000 Subject: [PATCH] PipeWire support improvements --- CMakeLists.txt | 25 +- cmake/modules/FindPipeWire.cmake | 122 ----- cmake/modules/Findgbm.cmake | 104 ++++ framebuffers/pipewire/CMakeLists.txt | 27 +- framebuffers/pipewire/pw_framebuffer.cpp | 657 +++++++++++------------ krfb/config-krfb.h.cmake | 3 + 6 files changed, 464 insertions(+), 474 deletions(-) delete mode 100644 cmake/modules/FindPipeWire.cmake create mode 100644 cmake/modules/Findgbm.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 58e05c5..f3fb9d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,12 +77,31 @@ include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ) find_package(LibVNCServer REQUIRED) -find_package(PipeWire) -set_package_properties(PipeWire PROPERTIES + +pkg_check_modules(PipeWire IMPORTED_TARGET libpipewire-0.3) +add_feature_info(PipeWire PipeWire_FOUND "Required for pipewire screencast plugin") + +find_package(gbm) +set_package_properties(gbm PROPERTIES TYPE OPTIONAL - PURPOSE "Required for pipewire screencast plugin" + PURPOSE "Required for dma-buf support in pipewire screencast plugin." ) +find_package(EGL) +set_package_properties(EGL PROPERTIES + TYPE OPTIONAL + PURPOSE "Required for dma-buf support in pipewire screencast plugin." +) + +find_package(epoxy) +set_package_properties(Epoxy PROPERTIES DESCRIPTION "libepoxy" + URL "https://github.com/anholt/libepoxy" + TYPE OPTIONAL + PURPOSE "Required for dma-buf support in pipewire screencast plugin." +) + +set(HAVE_DMA_BUF ${EGL_FOUND} AND ${gbm_FOUND} AND ${Epoxy_FOUND}) + ecm_setup_version(PROJECT VARIABLE_PREFIX KRFB VERSION_HEADER "krfb_version.h") diff --git a/cmake/modules/FindPipeWire.cmake b/cmake/modules/FindPipeWire.cmake deleted file mode 100644 index 9717ac0..0000000 --- a/cmake/modules/FindPipeWire.cmake +++ /dev/null @@ -1,122 +0,0 @@ -#.rst: -# FindPipeWire -# ------- -# -# Try to find PipeWire on a Unix system. -# -# This will define the following variables: -# -# ``PipeWire_FOUND`` -# True if (the requested version of) PipeWire is available -# ``PipeWire_VERSION`` -# The version of PipeWire -# ``PipeWire_LIBRARIES`` -# This can be passed to target_link_libraries() instead of the ``PipeWire::PipeWire`` -# target -# ``PipeWire_INCLUDE_DIRS`` -# This should be passed to target_include_directories() if the target is not -# used for linking -# ``PipeWire_DEFINITIONS`` -# This should be passed to target_compile_options() if the target is not -# used for linking -# -# If ``PipeWire_FOUND`` is TRUE, it will also define the following imported target: -# -# ``PipeWire::PipeWire`` -# The PipeWire library -# -# In general we recommend using the imported target, as it is easier to use. -# Bear in mind, however, that if the target is in the link interface of an -# exported library, it must be made available by the package config file. - -#============================================================================= -# Copyright 2014 Alex Merry -# Copyright 2014 Martin Gräßlin -# Copyright 2018-2020 Jan Grulich -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#============================================================================= - -# Use pkg-config to get the directories and then use these values -# in the FIND_PATH() and FIND_LIBRARY() calls -find_package(PkgConfig QUIET) - -pkg_search_module(PKG_PipeWire QUIET libpipewire-0.3 libpipewire-0.2) -pkg_search_module(PKG_Spa QUIET libspa-0.2 libspa-0.1) - -set(PipeWire_DEFINITIONS "${PKG_PipeWire_CFLAGS}" "${PKG_Spa_CFLAGS}") -set(PipeWire_VERSION "${PKG_PipeWire_VERSION}") - -find_path(PipeWire_INCLUDE_DIRS - NAMES - pipewire/pipewire.h - HINTS - ${PKG_PipeWire_INCLUDE_DIRS} - ${PKG_PipeWire_INCLUDE_DIRS}/pipewire-0.3 -) - -find_path(Spa_INCLUDE_DIRS - NAMES - spa/param/props.h - HINTS - ${PKG_Spa_INCLUDE_DIRS} - ${PKG_Spa_INCLUDE_DIRS}/spa-0.2 -) - -find_library(PipeWire_LIBRARIES - NAMES - pipewire-0.3 - pipewire-0.2 - HINTS - ${PKG_PipeWire_LIBRARY_DIRS} -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(PipeWire - FOUND_VAR - PipeWire_FOUND - REQUIRED_VARS - PipeWire_LIBRARIES - PipeWire_INCLUDE_DIRS - Spa_INCLUDE_DIRS - VERSION_VAR - PipeWire_VERSION -) - -if(PipeWire_FOUND AND NOT TARGET PipeWire::PipeWire) - add_library(PipeWire::PipeWire UNKNOWN IMPORTED) - set_target_properties(PipeWire::PipeWire PROPERTIES - IMPORTED_LOCATION "${PipeWire_LIBRARIES}" - INTERFACE_COMPILE_OPTIONS "${PipeWire_DEFINITIONS}" - INTERFACE_INCLUDE_DIRECTORIES "${PipeWire_INCLUDE_DIRS};${Spa_INCLUDE_DIRS}" - ) -endif() - -mark_as_advanced(PipeWire_LIBRARIES PipeWire_INCLUDE_DIRS) - -include(FeatureSummary) -set_package_properties(PipeWire PROPERTIES - URL "https://www.pipewire.org" - DESCRIPTION "PipeWire - multimedia processing" -) diff --git a/cmake/modules/Findgbm.cmake b/cmake/modules/Findgbm.cmake new file mode 100644 index 0000000..12ef45f --- /dev/null +++ b/cmake/modules/Findgbm.cmake @@ -0,0 +1,104 @@ +#.rst: +# Findgbm +# ------- +# +# Try to find gbm on a Unix system. +# +# This will define the following variables: +# +# ``gbm_FOUND`` +# True if (the requested version of) gbm is available +# ``gbm_VERSION`` +# The version of gbm +# ``gbm_LIBRARIES`` +# This can be passed to target_link_libraries() instead of the ``gbm::gbm`` +# target +# ``gbm_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if the target is not +# used for linking +# ``gbm_DEFINITIONS`` +# This should be passed to target_compile_options() if the target is not +# used for linking +# +# If ``gbm_FOUND`` is TRUE, it will also define the following imported target: +# +# ``gbm::gbm`` +# The gbm library +# +# In general we recommend using the imported target, as it is easier to use. +# Bear in mind, however, that if the target is in the link interface of an +# exported library, it must be made available by the package config file. + +#============================================================================= +# SPDX-FileCopyrightText: 2014 Alex Merry +# SPDX-FileCopyrightText: 2014 Martin Gräßlin +# +# SPDX-License-Identifier: BSD-3-Clause +#============================================================================= + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "CMake 2.8.12 is required by Findgbm.cmake") +endif() +if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) + message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use Findgbm.cmake") +endif() + +if(NOT WIN32) + # Use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + pkg_check_modules(PKG_gbm QUIET gbm) + + set(gbm_DEFINITIONS ${PKG_gbm_CFLAGS_OTHER}) + set(gbm_VERSION ${PKG_gbm_VERSION}) + + find_path(gbm_INCLUDE_DIR + NAMES + gbm.h + HINTS + ${PKG_gbm_INCLUDE_DIRS} + ) + find_library(gbm_LIBRARY + NAMES + gbm + HINTS + ${PKG_gbm_LIBRARY_DIRS} + ) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(gbm + FOUND_VAR + gbm_FOUND + REQUIRED_VARS + gbm_LIBRARY + gbm_INCLUDE_DIR + VERSION_VAR + gbm_VERSION + ) + + if(gbm_FOUND AND NOT TARGET gbm::gbm) + add_library(gbm::gbm UNKNOWN IMPORTED) + set_target_properties(gbm::gbm PROPERTIES + IMPORTED_LOCATION "${gbm_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${gbm_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${gbm_INCLUDE_DIR}" + ) + endif() + + mark_as_advanced(gbm_LIBRARY gbm_INCLUDE_DIR) + + # compatibility variables + set(gbm_LIBRARIES ${gbm_LIBRARY}) + set(gbm_INCLUDE_DIRS ${gbm_INCLUDE_DIR}) + set(gbm_VERSION_STRING ${gbm_VERSION}) + +else() + message(STATUS "Findgbm.cmake cannot find gbm on Windows systems.") + set(gbm_FOUND FALSE) +endif() + +include(FeatureSummary) +set_package_properties(gbm PROPERTIES + URL "https://www.mesa3d.org" + DESCRIPTION "Mesa gbm library." +) diff --git a/framebuffers/pipewire/CMakeLists.txt b/framebuffers/pipewire/CMakeLists.txt index 205f7d4..617f0c1 100644 --- a/framebuffers/pipewire/CMakeLists.txt +++ b/framebuffers/pipewire/CMakeLists.txt @@ -33,21 +33,22 @@ add_library(krfb_framebuffer_pw ${krfb_framebuffer_pw_SRCS} ) - -check_include_file("linux/dma-buf.h" HAVE_LINUX_DMABUF_H) -if (HAVE_LINUX_DMABUF_H) - target_compile_definitions(krfb_framebuffer_pw PRIVATE -DHAVE_LINUX_DMABUF_H) -endif () - -target_link_libraries (krfb_framebuffer_pw - Qt5::Core - Qt5::Gui - Qt5::DBus - KF5::CoreAddons - krfbprivate - PipeWire::PipeWire +target_link_libraries(krfb_framebuffer_pw + Qt5::Core + Qt5::Gui + Qt5::DBus + KF5::CoreAddons + krfbprivate + PkgConfig::PipeWire ) +if (HAVE_DMA_BUF) + target_link_libraries(krfb_framebuffer_pw + ${epoxy_LIBRARIES} + gbm::gbm + ) +endif() + install (TARGETS krfb_framebuffer_pw DESTINATION ${KDE_INSTALL_PLUGINDIR}/krfb/framebuffer ) diff --git a/framebuffers/pipewire/pw_framebuffer.cpp b/framebuffers/pipewire/pw_framebuffer.cpp index 5be74cc..0250233 100644 --- a/framebuffers/pipewire/pw_framebuffer.cpp +++ b/framebuffers/pipewire/pw_framebuffer.cpp @@ -1,6 +1,6 @@ /* This file is part of the KDE project + Copyright (C) 2018-2021 Jan Grulich Copyright (C) 2018 Oleg Chernovskiy - Copyright (C) 2018 Jan Grulich This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public @@ -8,6 +8,8 @@ version 3 of the License, or (at your option) any later version. */ +#include "config-krfb.h" + // system #include #include @@ -18,24 +20,15 @@ #include #include #include -#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) #include -#endif // pipewire -#include - -#if PW_CHECK_VERSION(0, 2, 90) -#include -#ifdef HAVE_LINUX_DMABUF_H -#include -#endif #include -#endif #include #include #include +#include #include @@ -46,6 +39,16 @@ #include "xdp_dbus_remotedesktop_interface.h" #include "krfb_fb_pipewire_debug.h" +#if HAVE_DMA_BUF +#include +#include + +#include +#include +#include +#endif /* HAVE_DMA_BUF */ + +static const int BYTES_PER_PIXEL = 4; static const uint MIN_SUPPORTED_XDP_KDE_SC_VERSION = 1; Q_DECLARE_METATYPE(PWFrameBuffer::Stream); @@ -71,18 +74,27 @@ const QDBusArgument &operator >> (const QDBusArgument &arg, PWFrameBuffer::Strea return arg; } -#if !PW_CHECK_VERSION(0, 2, 90) -/** - * @brief The PwType class - helper class to contain pointers to raw C pipewire media mappings - */ -class PwType { -public: - spa_type_media_type media_type; - spa_type_media_subtype media_subtype; - spa_type_format_video format_video; - spa_type_video_format video_format; -}; -#endif +const char * formatGLError(GLenum err) +{ + switch(err) { + case GL_NO_ERROR: + return "GL_NO_ERROR"; + case GL_INVALID_ENUM: + return "GL_INVALID_ENUM"; + case GL_INVALID_VALUE: + return "GL_INVALID_VALUE"; + case GL_INVALID_OPERATION: + return "GL_INVALID_OPERATION"; + case GL_STACK_OVERFLOW: + return "GL_STACK_OVERFLOW"; + case GL_STACK_UNDERFLOW: + return "GL_STACK_UNDERFLOW"; + case GL_OUT_OF_MEMORY: + return "GL_OUT_OF_MEMORY"; + default: + return (QLatin1String("0x") + QString::number(err, 16)).toLocal8Bit().constData(); + } +} /** * @brief The PWFrameBuffer::Private class - private counterpart of PWFramebuffer class. This is the entity where @@ -96,21 +108,13 @@ public: private: friend class PWFrameBuffer; -#if PW_CHECK_VERSION(0, 2, 90) static void onCoreError(void *data, uint32_t id, int seq, int res, const char *message); static void onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format); -#else - static void onStateChanged(void *data, pw_remote_state old, pw_remote_state state, const char *error); - static void onStreamFormatChanged(void *data, const struct spa_pod *format); -#endif static void onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message); static void onStreamProcess(void *data); void initDbus(); void initPw(); -#if !PW_CHECK_VERSION(0, 2, 90) - void initializePwTypes(); -#endif // dbus handling void handleSessionCreated(quint32 &code, QVariantMap &results); @@ -126,7 +130,6 @@ private: PWFrameBuffer *q; // pipewire stuff -#if PW_CHECK_VERSION(0, 2, 90) struct pw_context *pwContext = nullptr; struct pw_core *pwCore = nullptr; struct pw_stream *pwStream = nullptr; @@ -140,22 +143,6 @@ private: // event handlers pw_core_events pwCoreEvents = {}; pw_stream_events pwStreamEvents = {}; -#else - pw_core *pwCore = nullptr; - pw_loop *pwLoop = nullptr; - pw_thread_loop *pwMainLoop = nullptr; - pw_stream *pwStream = nullptr; - pw_remote *pwRemote = nullptr; - pw_type *pwCoreType = nullptr; - PwType *pwType = nullptr; - - spa_hook remoteListener = {}; - spa_hook streamListener = {}; - - // event handlers - pw_remote_events pwRemoteEvents = {}; - pw_stream_events pwStreamEvents = {}; -#endif uint pwStreamNodeId = 0; @@ -173,21 +160,32 @@ private: QDBusUnixFileDescriptor pipewireFd; // screen geometry holder - struct { - quint32 width; - quint32 height; - } screenGeometry = {}; + QSize streamSize; + QSize videoSize; // Allowed devices uint devices = 0; // sanity indicator bool isValid = true; + +#if HAVE_DMA_BUF + struct EGLStruct { + QList extensions; + EGLDisplay display = EGL_NO_DISPLAY; + EGLContext context = EGL_NO_CONTEXT; + }; + + bool m_eglInitialized = false; + qint32 m_drmFd = 0; // for GBM buffer mmap + gbm_device *m_gbmDevice = nullptr; // for passed GBM buffer retrieval + + EGLStruct m_egl; +#endif /* HAVE_DMA_BUF */ }; PWFrameBuffer::Private::Private(PWFrameBuffer *q) : q(q) { -#if PW_CHECK_VERSION(0, 2, 90) pwCoreEvents.version = PW_VERSION_CORE_EVENTS; pwCoreEvents.error = &onCoreError; @@ -195,16 +193,72 @@ PWFrameBuffer::Private::Private(PWFrameBuffer *q) : q(q) pwStreamEvents.state_changed = &onStreamStateChanged; pwStreamEvents.param_changed = &onStreamParamChanged; pwStreamEvents.process = &onStreamProcess; -#else - // initialize event handlers, remote end and stream-related - pwRemoteEvents.version = PW_VERSION_REMOTE_EVENTS; - pwRemoteEvents.state_changed = &onStateChanged; - pwStreamEvents.version = PW_VERSION_STREAM_EVENTS; - pwStreamEvents.state_changed = &onStreamStateChanged; - pwStreamEvents.format_changed = &onStreamFormatChanged; - pwStreamEvents.process = &onStreamProcess; -#endif +#if HAVE_DMA_BUF + m_drmFd = open("/dev/dri/renderD128", O_RDWR); + + if (m_drmFd < 0) { + qCWarning(KRFB_FB_PIPEWIRE) << "Failed to open drm render node: " << strerror(errno); + return; + } + + m_gbmDevice = gbm_create_device(m_drmFd); + + if (!m_gbmDevice) { + qCWarning(KRFB_FB_PIPEWIRE) << "Cannot create GBM device: " << strerror(errno); + return; + } + + // Get the list of client extensions + const char* clientExtensionsCString = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + const QByteArray clientExtensionsString = QByteArray::fromRawData(clientExtensionsCString, qstrlen(clientExtensionsCString)); + if (clientExtensionsString.isEmpty()) { + // If eglQueryString() returned NULL, the implementation doesn't support + // EGL_EXT_client_extensions. Expect an EGL_BAD_DISPLAY error. + qCWarning(KRFB_FB_PIPEWIRE) << "No client extensions defined! " << formatGLError(eglGetError()); + return; + } + + m_egl.extensions = clientExtensionsString.split(' '); + + // Use eglGetPlatformDisplayEXT() to get the display pointer + // if the implementation supports it. + if (!m_egl.extensions.contains(QByteArrayLiteral("EGL_EXT_platform_base")) || + !m_egl.extensions.contains(QByteArrayLiteral("EGL_MESA_platform_gbm"))) { + qCWarning(KRFB_FB_PIPEWIRE) << "One of required EGL extensions is missing"; + return; + } + + m_egl.display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, m_gbmDevice, nullptr); + + if (m_egl.display == EGL_NO_DISPLAY) { + qCWarning(KRFB_FB_PIPEWIRE) << "Error during obtaining EGL display: " << formatGLError(eglGetError()); + return; + } + + EGLint major, minor; + if (eglInitialize(m_egl.display, &major, &minor) == EGL_FALSE) { + qCWarning(KRFB_FB_PIPEWIRE) << "Error during eglInitialize: " << formatGLError(eglGetError()); + return; + } + + if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { + qCWarning(KRFB_FB_PIPEWIRE) << "bind OpenGL API failed"; + return; + } + + m_egl.context = eglCreateContext(m_egl.display, nullptr, EGL_NO_CONTEXT, nullptr); + + if (m_egl.context == EGL_NO_CONTEXT) { + qCWarning(KRFB_FB_PIPEWIRE) << "Couldn't create EGL context: " << formatGLError(eglGetError()); + return; + } + + qCDebug(KRFB_FB_PIPEWIRE) << "Egl initialization succeeded"; + qCDebug(KRFB_FB_PIPEWIRE) << QStringLiteral("EGL version: %1.%2").arg(major).arg(minor); + + m_eglInitialized = true; +#endif /* HAVE_DMA_BUF */ } /** @@ -230,13 +284,8 @@ void PWFrameBuffer::Private::initDbus() // create session auto sessionParameters = QVariantMap { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) { QStringLiteral("session_handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) }, { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } -#else - { QStringLiteral("session_handle_token"), QStringLiteral("krfb%1").arg(qrand()) }, - { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } -#endif }; auto sessionReply = dbusXdpRemoteDesktopService->CreateSession(sessionParameters); sessionReply.waitForFinished(); @@ -281,11 +330,7 @@ void PWFrameBuffer::Private::handleSessionCreated(quint32 &code, QVariantMap &re auto selectionOptions = QVariantMap { // We have to specify it's an uint, otherwise xdg-desktop-portal will not forward it to backend implementation { QStringLiteral("types"), QVariant::fromValue(7) }, // request all (KeyBoard, Pointer, TouchScreen) -#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } -#else - { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } -#endif }; auto selectorReply = dbusXdpRemoteDesktopService->SelectDevices(sessionPath, selectionOptions); selectorReply.waitForFinished(); @@ -326,11 +371,7 @@ void PWFrameBuffer::Private::handleDevicesSelected(quint32 &code, QVariantMap &r auto selectionOptions = QVariantMap { { QStringLiteral("types"), QVariant::fromValue(1) }, // only MONITOR is supported { QStringLiteral("multiple"), false }, -#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } -#else - { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } -#endif }; auto selectorReply = dbusXdpScreenCastService->SelectSources(sessionPath, selectionOptions); selectorReply.waitForFinished(); @@ -370,11 +411,7 @@ void PWFrameBuffer::Private::handleSourcesSelected(quint32 &code, QVariantMap &) // start session auto startParameters = QVariantMap { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } -#else - { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } -#endif }; auto startReply = dbusXdpRemoteDesktopService->Start(sessionPath, QString(), startParameters); startReply.waitForFinished(); @@ -431,25 +468,10 @@ void PWFrameBuffer::Private::handleRemoteDesktopStarted(quint32 &code, QVariantM return; } - QSize streamResolution = qdbus_cast(streams.first().map.value(QStringLiteral("size"))); - screenGeometry.width = streamResolution.width(); - screenGeometry.height = streamResolution.height(); - devices = results.value(QStringLiteral("types")).toUInt(); pwStreamNodeId = streams.first().nodeId; - // Reallocate our buffer with actual needed size - q->fb = static_cast(malloc(screenGeometry.width * screenGeometry.height * 4)); - - if (!q->fb) { - qCWarning(KRFB_FB_PIPEWIRE) << "Failed to allocate buffer"; - isValid = false; - return; - } - - Q_EMIT q->frameBufferChanged(); - initPw(); } @@ -463,8 +485,9 @@ void PWFrameBuffer::Private::initPw() { // init pipewire (required) pw_init(nullptr, nullptr); // args are not used anyways -#if PW_CHECK_VERSION(0, 2, 90) pwMainLoop = pw_thread_loop_new("pipewire-main-loop", nullptr); + pw_thread_loop_lock(pwMainLoop); + pwContext = pw_context_new(pw_thread_loop_get_loop(pwMainLoop), nullptr, 0); if (!pwContext) { qCWarning(KRFB_FB_PIPEWIRE) << "Failed to create PipeWire context"; @@ -484,49 +507,15 @@ void PWFrameBuffer::Private::initPw() { qCWarning(KRFB_FB_PIPEWIRE) << "Failed to create PipeWire stream"; return; } -#else - // initialize our source - pwLoop = pw_loop_new(nullptr); - pwMainLoop = pw_thread_loop_new(pwLoop, "pipewire-main-loop"); - // create PipeWire core object (required) - pwCore = pw_core_new(pwLoop, nullptr); - pwCoreType = pw_core_get_type(pwCore); - - initializePwTypes(); - - // pw_remote should be initialized before type maps or connection error will happen - pwRemote = pw_remote_new(pwCore, nullptr, 0); - // init PipeWire remote, add listener to handle events - pw_remote_add_listener(pwRemote, &remoteListener, &pwRemoteEvents, this); - pw_remote_connect_fd(pwRemote, pipewireFd.fileDescriptor()); -#endif if (pw_thread_loop_start(pwMainLoop) < 0) { qCWarning(KRFB_FB_PIPEWIRE) << "Failed to start main PipeWire loop"; isValid = false; } + + pw_thread_loop_unlock(pwMainLoop); } -#if !PW_CHECK_VERSION(0, 2, 90) -/** - * @brief PWFrameBuffer::Private::initializePwTypes - helper method to initialize and map all needed - * Pipewire types from core to type structure. - */ -void PWFrameBuffer::Private::initializePwTypes() -{ - // raw C-like PipeWire type map - spa_type_map *map = pwCoreType->map; - pwType = new PwType(); - - spa_type_media_type_map(map, &pwType->media_type); - spa_type_media_subtype_map(map, &pwType->media_subtype); - spa_type_format_video_map(map, &pwType->format_video); - spa_type_video_format_map(map, &pwType->video_format); -} -#endif - - -#if PW_CHECK_VERSION(0, 2, 90) void PWFrameBuffer::Private::onCoreError(void *data, uint32_t id, int seq, int res, const char *message) { Q_UNUSED(data); @@ -536,32 +525,6 @@ void PWFrameBuffer::Private::onCoreError(void *data, uint32_t id, int seq, int r qInfo() << "core error: " << message; } -#else -/** - * @brief PWFrameBuffer::Private::onStateChanged - global state tracking for pipewire connection - * @param data pointer that you have set in pw_remote_add_listener call's last argument - * @param state new state that connection has changed to - * @param error optional error message, is set to non-null if state is error - */ -void PWFrameBuffer::Private::onStateChanged(void *data, pw_remote_state /*old*/, pw_remote_state state, const char *error) -{ - qInfo() << "remote state: " << pw_remote_state_as_string(state); - - auto d = static_cast(data); - - switch (state) { - case PW_REMOTE_STATE_ERROR: - qCWarning(KRFB_FB_PIPEWIRE) << "remote error: " << error; - break; - case PW_REMOTE_STATE_CONNECTED: - d->pwStream = d->createReceivingStream(); - break; - default: - qInfo() << "remote state: " << pw_remote_state_as_string(state); - break; - } -} -#endif /** * @brief PWFrameBuffer::Private::onStreamStateChanged - called whenever stream state changes on pipewire server @@ -571,35 +534,20 @@ void PWFrameBuffer::Private::onStateChanged(void *data, pw_remote_state /*old*/, */ void PWFrameBuffer::Private::onStreamStateChanged(void *data, pw_stream_state /*old*/, pw_stream_state state, const char *error_message) { + Q_UNUSED(data); + qInfo() << "Stream state changed: " << pw_stream_state_as_string(state); - auto *d = static_cast(data); - -#if PW_CHECK_VERSION(0, 2, 90) switch (state) { case PW_STREAM_STATE_ERROR: qCWarning(KRFB_FB_PIPEWIRE) << "pipewire stream error: " << error_message; break; case PW_STREAM_STATE_PAUSED: - pw_stream_set_active(d->pwStream, true); - break; case PW_STREAM_STATE_STREAMING: case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_CONNECTING: break; } -#else - switch (state) { - case PW_STREAM_STATE_ERROR: - qCWarning(KRFB_FB_PIPEWIRE) << "pipewire stream error: " << error_message; - break; - case PW_STREAM_STATE_CONFIGURE: - pw_stream_set_active(d->pwStream, true); - break; - default: - break; - } -#endif } /** @@ -608,69 +556,53 @@ void PWFrameBuffer::Private::onStreamStateChanged(void *data, pw_stream_state /* * @param data pointer that you have set in pw_stream_add_listener call's last argument * @param format format that's being proposed */ -#if PW_CHECK_VERSION(0, 2, 90) void PWFrameBuffer::Private::onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format) -#else -void PWFrameBuffer::Private::onStreamFormatChanged(void *data, const struct spa_pod *format) -#endif { qInfo() << "Stream format changed"; auto *d = static_cast(data); - const int bpp = 4; - -#if PW_CHECK_VERSION(0, 2, 90) if (!format || id != SPA_PARAM_Format) { -#else - if (!format) { - pw_stream_finish_format(d->pwStream, 0, nullptr, 0); -#endif return; } d->videoFormat = new spa_video_info_raw(); -#if PW_CHECK_VERSION(0, 2, 90) spa_format_video_raw_parse(format, d->videoFormat); -#else - spa_format_video_raw_parse(format, d->videoFormat, &d->pwType->format_video); -#endif auto width = d->videoFormat->size.width; auto height = d->videoFormat->size.height; - auto stride = SPA_ROUND_UP_N(width * bpp, 4); + auto stride = SPA_ROUND_UP_N(width * BYTES_PER_PIXEL, 4); auto size = height * stride; + d->streamSize = QSize(width, height); uint8_t buffer[1024]; auto builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); // setup buffers and meta header for new format - const struct spa_pod *params[2]; + const struct spa_pod *params[3]; + +#if HAVE_DMA_BUF + const auto bufferTypes = d->m_eglInitialized ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr) : + (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr); +#else + const auto bufferTypes = (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr); +#endif /* HAVE_DMA_BUF */ -#if PW_CHECK_VERSION(0, 2, 90) params[0] = reinterpret_cast(spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 1, 32), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_align, SPA_POD_Int(16))); + SPA_PARAM_BUFFERS_align, SPA_POD_Int(16), + SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(bufferTypes))); params[1] = reinterpret_cast(spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)))); - pw_stream_update_params(d->pwStream, params, 2); -#else - params[0] = reinterpret_cast(spa_pod_builder_object(&builder, - d->pwCoreType->param.idBuffers, d->pwCoreType->param_buffers.Buffers, - ":", d->pwCoreType->param_buffers.size, "i", size, - ":", d->pwCoreType->param_buffers.stride, "i", stride, - ":", d->pwCoreType->param_buffers.buffers, "iru", 8, SPA_POD_PROP_MIN_MAX(1, 32), - ":", d->pwCoreType->param_buffers.align, "i", 16)); - params[1] = reinterpret_cast(spa_pod_builder_object(&builder, - d->pwCoreType->param.idMeta, d->pwCoreType->param_meta.Meta, - ":", d->pwCoreType->param_meta.type, "I", d->pwCoreType->meta.Header, - ":", d->pwCoreType->param_meta.size, "i", sizeof(struct spa_meta_header))); - pw_stream_finish_format(d->pwStream, 0, params, 2); -#endif + params[2] = reinterpret_cast(spa_pod_builder_add_object(&builder, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, + SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, + SPA_POD_Int(sizeof(struct spa_meta_region)))); + pw_stream_update_params(d->pwStream, params, 3); } /** @@ -682,73 +614,39 @@ void PWFrameBuffer::Private::onStreamProcess(void *data) { auto *d = static_cast(data); - pw_buffer *buf; - if (!(buf = pw_stream_dequeue_buffer(d->pwStream))) { + pw_buffer* next_buffer; + pw_buffer* buffer = nullptr; + + next_buffer = pw_stream_dequeue_buffer(d->pwStream); + while (next_buffer) { + buffer = next_buffer; + next_buffer = pw_stream_dequeue_buffer(d->pwStream); + + if (next_buffer) { + pw_stream_queue_buffer(d->pwStream, buffer); + } + } + + if (!buffer) { return; } - d->handleFrame(buf); + d->handleFrame(buffer); - pw_stream_queue_buffer(d->pwStream, buf); + pw_stream_queue_buffer(d->pwStream, buffer); } -#if PW_CHECK_VERSION(0, 2, 90) && defined(HAVE_LINUX_DMABUF_H) -static void syncDmaBuf(int fd, uint64_t start_or_end) -{ - struct dma_buf_sync sync = { 0 }; - sync.flags = start_or_end | DMA_BUF_SYNC_READ; - - while(true) { - int ret; - ret = ioctl (fd, DMA_BUF_IOCTL_SYNC, &sync); - if (ret == -1 && errno == EINTR) { - continue; - } else if (ret == -1) { - qCWarning(KRFB_FB_PIPEWIRE) << "Failed to synchronize DMA buffer: " << strerror(errno); - break; - } else { - break; - } - } -} -#endif - void PWFrameBuffer::Private::handleFrame(pw_buffer *pwBuffer) { auto *spaBuffer = pwBuffer->buffer; - void *src = spaBuffer->datas[0].data; + uint8_t *src = nullptr; -#if PW_CHECK_VERSION(0, 2, 90) - if (!src && spaBuffer->datas->type != SPA_DATA_DmaBuf) { + if (spaBuffer->datas[0].chunk->size == 0) { qCDebug(KRFB_FB_PIPEWIRE) << "discarding null buffer"; return; } -#endif - - const quint32 maxSize = spaBuffer->datas[0].maxsize; std::function cleanup; -#if PW_CHECK_VERSION(0, 2, 90) -#ifdef HAVE_LINUX_DMABUF_H - if (spaBuffer->datas->type == SPA_DATA_DmaBuf) { - const int fd = spaBuffer->datas[0].fd; - auto map = mmap( - nullptr, spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset, - PROT_READ, MAP_PRIVATE, fd, 0); - if (map == MAP_FAILED) { - qCWarning(KRFB_FB_PIPEWIRE) << "Failed to mmap the dmabuf: " << strerror(errno); - return; - } - - syncDmaBuf(fd, DMA_BUF_SYNC_START); - src = SPA_MEMBER(map, spaBuffer->datas[0].mapoffset, uint8_t); - - cleanup = [map, spaBuffer, fd] { - syncDmaBuf(fd, DMA_BUF_SYNC_END); - munmap(map, spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset); - }; - } else -#endif if (spaBuffer->datas->type == SPA_DATA_MemFd) { uint8_t *map = static_cast(mmap( nullptr, spaBuffer->datas->maxsize + spaBuffer->datas->mapoffset, @@ -763,36 +661,175 @@ void PWFrameBuffer::Private::handleFrame(pw_buffer *pwBuffer) cleanup = [map, spaBuffer] { munmap(map, spaBuffer->datas->maxsize + spaBuffer->datas->mapoffset); }; + } else if (spaBuffer->datas[0].type == SPA_DATA_MemPtr) { + src = static_cast(spaBuffer->datas[0].data); } -#endif +#if HAVE_DMA_BUF + else if (spaBuffer->datas->type == SPA_DATA_DmaBuf) { + if (!m_eglInitialized) { + // Shouldn't reach this + qCWarning(KRFB_FB_PIPEWIRE) << "Failed to process DMA buffer."; + return; + } - const qint32 srcStride = spaBuffer->datas[0].chunk->stride; - if (srcStride != q->paddedWidth()) { - qCWarning(KRFB_FB_PIPEWIRE) << "Got buffer with stride different from screen stride" << srcStride << "!=" << q->paddedWidth(); + gbm_import_fd_data importInfo = {static_cast(spaBuffer->datas->fd), static_cast(streamSize.width()), + static_cast(streamSize.height()), static_cast(spaBuffer->datas[0].chunk->stride), GBM_BO_FORMAT_ARGB8888}; + gbm_bo *imported = gbm_bo_import(m_gbmDevice, GBM_BO_IMPORT_FD, &importInfo, GBM_BO_USE_SCANOUT); + if (!imported) { + qCWarning(KRFB_FB_PIPEWIRE) << "Failed to process buffer: Cannot import passed GBM fd - " << strerror(errno); + return; + } + + // bind context to render thread + eglMakeCurrent(m_egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, m_egl.context); + + // create EGL image from imported BO + EGLImageKHR image = eglCreateImageKHR(m_egl.display, nullptr, EGL_NATIVE_PIXMAP_KHR, imported, nullptr); + + if (image == EGL_NO_IMAGE_KHR) { + qCWarning(KRFB_FB_PIPEWIRE) << "Failed to record frame: Error creating EGLImageKHR - " << formatGLError(glGetError()); + gbm_bo_destroy(imported); + return; + } + + // create GL 2D texture for framebuffer + GLuint texture; + glGenTextures(1, &texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, texture); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); + + src = static_cast(malloc(streamSize.width() * streamSize.height() * BYTES_PER_PIXEL)); + + GLenum glFormat = GL_BGRA; + switch (videoFormat->format) { + case SPA_VIDEO_FORMAT_RGBx: + glFormat = GL_RGBA; + break; + case SPA_VIDEO_FORMAT_RGBA: + glFormat = GL_RGBA; + break; + case SPA_VIDEO_FORMAT_BGRx: + glFormat = GL_BGRA; + break; + case SPA_VIDEO_FORMAT_RGB: + glFormat = GL_RGB; + break; + case SPA_VIDEO_FORMAT_BGR: + glFormat = GL_BGR; + break; + default: + glFormat = GL_BGRA; + break; + } + glGetTexImage(GL_TEXTURE_2D, 0, glFormat, GL_UNSIGNED_BYTE, src); + + if (!src) { + qCWarning(KRFB_FB_PIPEWIRE) << "Failed to get image from DMA buffer."; + gbm_bo_destroy(imported); + return; + } + + cleanup = [src] { + free(src); + }; + + glDeleteTextures(1, &texture); + eglDestroyImageKHR(m_egl.display, image); + + gbm_bo_destroy(imported); + } +#endif /* HAVE_DMA_BUF */ + + struct spa_meta_region* videoMetadata = + static_cast(spa_buffer_find_meta_data( + spaBuffer, SPA_META_VideoCrop, sizeof(*videoMetadata))); + + if (videoMetadata && (videoMetadata->region.size.width > static_cast(streamSize.width()) || + videoMetadata->region.size.height > static_cast(streamSize.height()))) { + qCWarning(KRFB_FB_PIPEWIRE) << "Stream metadata sizes are wrong!"; return; } - q->tiles.append(QRect(0, 0, q->width(), q->height())); - std::memcpy(q->fb, src, maxSize); - cleanup(); + // Use video metadata when video size from metadata is set and smaller than + // video stream size, so we need to adjust it. + bool videoFullWidth = true; + bool videoFullHeight = true; + if (videoMetadata && videoMetadata->region.size.width != 0 && + videoMetadata->region.size.height != 0) { + if (videoMetadata->region.size.width < static_cast(streamSize.width())) { + videoFullWidth = false; + } else if (videoMetadata->region.size.height < static_cast(streamSize.height())) { + videoFullHeight = false; + } + } -#if PW_CHECK_VERSION(0, 2, 90) - if (videoFormat->format == SPA_VIDEO_FORMAT_BGRA || videoFormat->format == SPA_VIDEO_FORMAT_BGRx) { - for (uint y = 0; y < videoFormat->size.height; y++) { - for (uint x = 0; x < videoFormat->size.width; x++) { - uint offset = y * spaBuffer->datas->chunk->stride + x * 4; - std::swap(q->fb[offset], q->fb[offset + 2]); + QSize prevVideoSize = videoSize; + if (!videoFullHeight || !videoFullWidth) { + videoSize = QSize(videoMetadata->region.size.width, videoMetadata->region.size.height); + } else { + videoSize = streamSize; + } + + if (!q->fb || videoSize != prevVideoSize) { + if (q->fb) { + free(q->fb); + } + q->fb = static_cast(malloc(videoSize.width() * videoSize.height() * BYTES_PER_PIXEL)); + + if (!q->fb) { + qCWarning(KRFB_FB_PIPEWIRE) << "Failed to allocate buffer"; + isValid = false; + return; + } + + Q_EMIT q->frameBufferChanged(); + } + + const qint32 dstStride = videoSize.width() * BYTES_PER_PIXEL; + const qint32 srcStride = spaBuffer->datas[0].chunk->stride; + + if (!videoFullHeight && (videoMetadata->region.position.y + videoSize.height() <= streamSize.height())) { + src += srcStride * videoMetadata->region.position.y; + } + + const int xOffset = !videoFullWidth && (videoMetadata->region.position.x + videoSize.width() <= streamSize.width()) + ? videoMetadata->region.position.x * BYTES_PER_PIXEL : 0; + + char *dst = q->fb; + for (int i = 0; i < videoSize.height(); ++i) { + // Adjust source content based on crop video position if needed + src += xOffset; + std::memcpy(dst, src, dstStride); + + if (videoFormat->format == SPA_VIDEO_FORMAT_BGRA || videoFormat->format == SPA_VIDEO_FORMAT_BGRx) { + for (int j = 0; j < dstStride; j += 4) { + std::swap(dst[j], dst[j + 2]); } } - } else if (videoFormat->format != SPA_VIDEO_FORMAT_RGB) { + + src += srcStride - xOffset; + dst += dstStride; + } + + if (spaBuffer->datas->type == SPA_DATA_MemFd || + spaBuffer->datas->type == SPA_DATA_DmaBuf) { + cleanup(); + } + + if (videoFormat->format != SPA_VIDEO_FORMAT_RGB) { const QImage::Format format = videoFormat->format == SPA_VIDEO_FORMAT_BGR ? QImage::Format_BGR888 : videoFormat->format == SPA_VIDEO_FORMAT_RGBx ? QImage::Format_RGBX8888 : QImage::Format_RGB32; - QImage img((uchar*) q->fb, videoFormat->size.width, videoFormat->size.height, spaBuffer->datas->chunk->stride, format); + QImage img((uchar*) q->fb, videoSize.width(), videoSize.height(), dstStride, format); img.convertTo(QImage::Format_RGB888); } -#endif + + q->tiles.append(QRect(0, 0, videoSize.width(), videoSize.height())); } /** @@ -803,28 +840,19 @@ void PWFrameBuffer::Private::handleFrame(pw_buffer *pwBuffer) pw_stream *PWFrameBuffer::Private::createReceivingStream() { spa_rectangle pwMinScreenBounds = SPA_RECTANGLE(1, 1); - spa_rectangle pwMaxScreenBounds = SPA_RECTANGLE(screenGeometry.width, screenGeometry.height); + spa_rectangle pwMaxScreenBounds = SPA_RECTANGLE(UINT32_MAX, UINT32_MAX); spa_fraction pwFramerateMin = SPA_FRACTION(0, 1); spa_fraction pwFramerateMax = SPA_FRACTION(60, 1); -#if PW_CHECK_VERSION(0, 2, 90) - auto stream = pw_stream_new_simple(pw_thread_loop_get_loop(pwMainLoop), "krfb-fb-consume-stream", - pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Screen", - nullptr), - &pwStreamEvents, this); + pw_properties* reuseProps = pw_properties_new_string("pipewire.client.reuse=1"); + + auto stream = pw_stream_new(pwCore, "krfb-fb-consume-stream", reuseProps); -#else - auto reuseProps = pw_properties_new("pipewire.client.reuse", "1", nullptr); // null marks end of varargs - auto stream = pw_stream_new(pwRemote, "krfb-fb-consume-stream", reuseProps); -#endif uint8_t buffer[1024] = {}; const spa_pod *params[1]; auto builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); -#if PW_CHECK_VERSION(0, 2, 90) params[0] = reinterpret_cast(spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), @@ -836,25 +864,10 @@ pw_stream *PWFrameBuffer::Private::createReceivingStream() SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&pwMaxScreenBounds, &pwMinScreenBounds, &pwMaxScreenBounds), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&pwFramerateMin), SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&pwFramerateMax, &pwFramerateMin, &pwFramerateMax))); -#else - params[0] = reinterpret_cast(spa_pod_builder_object(&builder, - pwCoreType->param.idEnumFormat, pwCoreType->spa_format, - "I", pwType->media_type.video, - "I", pwType->media_subtype.raw, - ":", pwType->format_video.format, "I", pwType->video_format.RGBx, - ":", pwType->format_video.size, "Rru", &pwMaxScreenBounds, SPA_POD_PROP_MIN_MAX(&pwMinScreenBounds, &pwMaxScreenBounds), - ":", pwType->format_video.framerate, "F", &pwFramerateMin, - ":", pwType->format_video.max_framerate, "Fru", &pwFramerateMax, 2, &pwFramerateMin, &pwFramerateMax)); - pw_stream_add_listener(stream, &streamListener, &pwStreamEvents, this); -#endif - auto flags = static_cast(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS); -#if PW_CHECK_VERSION(0, 2, 90) - if (pw_stream_connect(stream, PW_DIRECTION_INPUT, PW_ID_ANY, flags, params, 1) != 0) { -#else - if (pw_stream_connect(stream, PW_DIRECTION_INPUT, nullptr, flags, params, 1) != 0) { -#endif - qCWarning(KRFB_FB_PIPEWIRE) << "Could not connect receiving stream"; + pw_stream_add_listener(stream, &streamListener, &pwStreamEvents, this); + + if (pw_stream_connect(stream, PW_DIRECTION_INPUT, pwStreamNodeId, PW_STREAM_FLAG_AUTOCONNECT, params, 1) != 0) { isValid = false; } @@ -867,23 +880,10 @@ PWFrameBuffer::Private::~Private() pw_thread_loop_stop(pwMainLoop); } -#if !PW_CHECK_VERSION(0, 2, 9) - if (pwType) { - delete pwType; - } -#endif - if (pwStream) { pw_stream_destroy(pwStream); } -#if !PW_CHECK_VERSION(0, 2, 90) - if (pwRemote) { - pw_remote_destroy(pwRemote); - } -#endif - -#if PW_CHECK_VERSION(0, 2, 90) if (pwCore) { pw_core_disconnect(pwCore); } @@ -891,22 +891,10 @@ PWFrameBuffer::Private::~Private() if (pwContext) { pw_context_destroy(pwContext); } -#else - if (pwCore) { - pw_core_destroy(pwCore); - } -#endif if (pwMainLoop) { pw_thread_loop_destroy(pwMainLoop); } - -#if !PW_CHECK_VERSION(0, 2, 90) - if (pwLoop) { - pw_loop_leave(pwLoop); - pw_loop_destroy(pwLoop); - } -#endif } PWFrameBuffer::PWFrameBuffer(WId winid, QObject *parent) @@ -917,9 +905,6 @@ PWFrameBuffer::PWFrameBuffer(WId winid, QObject *parent) // PipeWire connectivity is initialized after D-Bus session is started d->initDbus(); - // FIXME: for now use some initial size, later on we will reallocate this with the actual size we get from portal - d->screenGeometry.width = 800; - d->screenGeometry.height = 600; fb = nullptr; } @@ -936,12 +921,12 @@ int PWFrameBuffer::depth() int PWFrameBuffer::height() { - return static_cast(d->screenGeometry.height); + return d->videoSize.height(); } int PWFrameBuffer::width() { - return static_cast(d->screenGeometry.width); + return d->videoSize.width(); } int PWFrameBuffer::paddedWidth() diff --git a/krfb/config-krfb.h.cmake b/krfb/config-krfb.h.cmake index 38d479c..3855983 100644 --- a/krfb/config-krfb.h.cmake +++ b/krfb/config-krfb.h.cmake @@ -9,3 +9,6 @@ /* Define if XShm is available */ #cmakedefine HAVE_XSHM 1 + +/* Define if DMA-BUF support is available */ +#cmakedefine HAVE_DMA_BUF 1