diff --git a/include/grabber/linux/pipewire/PipewireHandler.h b/include/grabber/linux/pipewire/PipewireHandler.h index ee119e076..04ae7c1e2 100644 --- a/include/grabber/linux/pipewire/PipewireHandler.h +++ b/include/grabber/linux/pipewire/PipewireHandler.h @@ -83,6 +83,7 @@ namespace sdbus{ class IProxy; } class ScreenCastProxy; +class RemoteDesktopProxy; class PipewireHandler : public QObject { @@ -93,7 +94,7 @@ class PipewireHandler : public QObject PipewireHandler(); ~PipewireHandler(); - void startSession(QString restorationToken, uint32_t requestedFPS, bool enableEGL, int targetMaxSize); + void startSession(QString restorationToken, uint32_t requestedFPS, bool enableEGL, int targetMaxSize, int selectedDisplay); void closeSession(); bool hasError(); bool isRestartNeeded(); @@ -148,7 +149,9 @@ public Q_SLOTS: QString _errorMessage; bool _portalStatus; bool _isError; + bool _useRemoteDesktopPortal; int _version; + int _remoteDesktopVersion; uint _streamNodeId; QString _sender; @@ -166,6 +169,7 @@ public Q_SLOTS: struct pw_buffer* _workingFrame; int _targetMaxSize; + int _selectedDisplay; int _frameWidth; int _frameHeight; bool _frameOrderRgb; @@ -184,6 +188,7 @@ public Q_SLOTS: std::unique_ptr _dbusConnection; std::unique_ptr _screenCastProxy; + std::unique_ptr _remoteDesktopProxy; std::unique_ptr _createSessionProxy; std::unique_ptr _selectSourceProxy; std::unique_ptr _startProxy; diff --git a/include/grabber/linux/pipewire/RemoteDesktopProxy.h b/include/grabber/linux/pipewire/RemoteDesktopProxy.h new file mode 100644 index 000000000..1415ef448 --- /dev/null +++ b/include/grabber/linux/pipewire/RemoteDesktopProxy.h @@ -0,0 +1,135 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__include_grabber_linux_pipewire_RemoteDesktopProxy_h__proxy__H__ +#define __sdbuscpp__include_grabber_linux_pipewire_RemoteDesktopProxy_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace freedesktop { +namespace portal { + +class RemoteDesktop_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.freedesktop.portal.RemoteDesktop"; + +protected: + RemoteDesktop_proxy(sdbus::IProxy& proxy) + : m_proxy(proxy) + { + } + + RemoteDesktop_proxy(const RemoteDesktop_proxy&) = delete; + RemoteDesktop_proxy& operator=(const RemoteDesktop_proxy&) = delete; + RemoteDesktop_proxy(RemoteDesktop_proxy&&) = delete; + RemoteDesktop_proxy& operator=(RemoteDesktop_proxy&&) = delete; + + ~RemoteDesktop_proxy() = default; + + void registerProxy() + { + } + +public: + sdbus::ObjectPath CreateSession(const std::map& options) + { + sdbus::ObjectPath result; + m_proxy.callMethod("CreateSession").onInterface(INTERFACE_NAME).withArguments(options).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath SelectDevices(const sdbus::ObjectPath& session_handle, const std::map& options) + { + sdbus::ObjectPath result; + m_proxy.callMethod("SelectDevices").onInterface(INTERFACE_NAME).withArguments(session_handle, options).storeResultsTo(result); + return result; + } + + sdbus::ObjectPath Start(const sdbus::ObjectPath& session_handle, const std::string& parent_window, const std::map& options) + { + sdbus::ObjectPath result; + m_proxy.callMethod("Start").onInterface(INTERFACE_NAME).withArguments(session_handle, parent_window, options).storeResultsTo(result); + return result; + } + + void NotifyPointerMotion(const sdbus::ObjectPath& session_handle, const std::map& options, const double& dx, const double& dy) + { + m_proxy.callMethod("NotifyPointerMotion").onInterface(INTERFACE_NAME).withArguments(session_handle, options, dx, dy); + } + + void NotifyPointerMotionAbsolute(const sdbus::ObjectPath& session_handle, const std::map& options, const uint32_t& stream, const double& x, const double& y) + { + m_proxy.callMethod("NotifyPointerMotionAbsolute").onInterface(INTERFACE_NAME).withArguments(session_handle, options, stream, x, y); + } + + void NotifyPointerButton(const sdbus::ObjectPath& session_handle, const std::map& options, const int32_t& button, const uint32_t& state) + { + m_proxy.callMethod("NotifyPointerButton").onInterface(INTERFACE_NAME).withArguments(session_handle, options, button, state); + } + + void NotifyPointerAxis(const sdbus::ObjectPath& session_handle, const std::map& options, const double& dx, const double& dy) + { + m_proxy.callMethod("NotifyPointerAxis").onInterface(INTERFACE_NAME).withArguments(session_handle, options, dx, dy); + } + + void NotifyPointerAxisDiscrete(const sdbus::ObjectPath& session_handle, const std::map& options, const uint32_t& axis, const int32_t& steps) + { + m_proxy.callMethod("NotifyPointerAxisDiscrete").onInterface(INTERFACE_NAME).withArguments(session_handle, options, axis, steps); + } + + void NotifyKeyboardKeycode(const sdbus::ObjectPath& session_handle, const std::map& options, const int32_t& keycode, const uint32_t& state) + { + m_proxy.callMethod("NotifyKeyboardKeycode").onInterface(INTERFACE_NAME).withArguments(session_handle, options, keycode, state); + } + + void NotifyKeyboardKeysym(const sdbus::ObjectPath& session_handle, const std::map& options, const int32_t& keysym, const uint32_t& state) + { + m_proxy.callMethod("NotifyKeyboardKeysym").onInterface(INTERFACE_NAME).withArguments(session_handle, options, keysym, state); + } + + void NotifyTouchDown(const sdbus::ObjectPath& session_handle, const std::map& options, const uint32_t& stream, const uint32_t& slot, const double& x, const double& y) + { + m_proxy.callMethod("NotifyTouchDown").onInterface(INTERFACE_NAME).withArguments(session_handle, options, stream, slot, x, y); + } + + void NotifyTouchMotion(const sdbus::ObjectPath& session_handle, const std::map& options, const uint32_t& stream, const uint32_t& slot, const double& x, const double& y) + { + m_proxy.callMethod("NotifyTouchMotion").onInterface(INTERFACE_NAME).withArguments(session_handle, options, stream, slot, x, y); + } + + void NotifyTouchUp(const sdbus::ObjectPath& session_handle, const std::map& options, const uint32_t& slot) + { + m_proxy.callMethod("NotifyTouchUp").onInterface(INTERFACE_NAME).withArguments(session_handle, options, slot); + } + + sdbus::UnixFd ConnectToEIS(const sdbus::ObjectPath& session_handle, const std::map& options) + { + sdbus::UnixFd result; + m_proxy.callMethod("ConnectToEIS").onInterface(INTERFACE_NAME).withArguments(session_handle, options).storeResultsTo(result); + return result; + } + +public: + uint32_t AvailableDeviceTypes() + { + return m_proxy.getProperty("AvailableDeviceTypes").onInterface(INTERFACE_NAME).get(); + } + + uint32_t version() + { + return m_proxy.getProperty("version").onInterface(INTERFACE_NAME).get(); + } + +private: + sdbus::IProxy& m_proxy; +}; + +}}} // namespaces + +#endif diff --git a/include/grabber/linux/pipewire/smartPipewire.h b/include/grabber/linux/pipewire/smartPipewire.h index 6d30480c9..5a33c80f6 100644 --- a/include/grabber/linux/pipewire/smartPipewire.h +++ b/include/grabber/linux/pipewire/smartPipewire.h @@ -10,12 +10,20 @@ struct PipewireImage bool isOrderRgb; uint8_t* data; }; + +namespace PipewirePortal +{ + constexpr int ScreenID_ScreenCast = 1; + constexpr int ScreenID_RemoteDesktop = 2; + constexpr int MinRemoteDesktopPortalVersion = 2; +} + extern "C" const char* getPipewireToken(); extern "C" const char* getPipewireError(); extern "C" bool hasPipewire(); -extern "C" void initPipewireDisplay(const char* restorationToken, uint32_t requestedFPS, bool enableEGL, int targetMaxSize); +extern "C" bool hasPipewireRemoteDesktop(); +extern "C" void initPipewireDisplay(const char* restorationToken, uint32_t requestedFPS, bool enableEGL, int targetMaxSize, int selectedDisplay); extern "C" void uninitPipewireDisplay(); extern "C" PipewireImage getFramePipewire(); extern "C" void releaseFramePipewire(); extern "C" bool isRestartNeeded(); - diff --git a/sources/grabber/linux/pipewire/CMakeLists.txt b/sources/grabber/linux/pipewire/CMakeLists.txt index 2e18bca77..a3de68ccb 100644 --- a/sources/grabber/linux/pipewire/CMakeLists.txt +++ b/sources/grabber/linux/pipewire/CMakeLists.txt @@ -2,7 +2,7 @@ SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber/linux/pipewire) SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/sources/grabber/linux/pipewire) -FILE ( GLOB SMARTPIPEWIRE_SOURCES "${CURRENT_HEADER_DIR}/smartPipewire.h" "${CURRENT_HEADER_DIR}/PipewireHandler.h" "${CURRENT_HEADER_DIR}/ScreenCastProxy.h" "${CURRENT_SOURCE_DIR}/smartPipewire.cpp" "${CURRENT_SOURCE_DIR}/PipewireHandler.cpp" ) +FILE ( GLOB SMARTPIPEWIRE_SOURCES "${CURRENT_HEADER_DIR}/smartPipewire.h" "${CURRENT_HEADER_DIR}/PipewireHandler.h" "${CURRENT_HEADER_DIR}/ScreenCastProxy.h" "${CURRENT_HEADER_DIR}/RemoteDesktopProxy.h" "${CURRENT_SOURCE_DIR}/smartPipewire.cpp" "${CURRENT_SOURCE_DIR}/PipewireHandler.cpp" ) add_library(smart-pipewire SHARED ${SMARTPIPEWIRE_SOURCES} ) diff --git a/sources/grabber/linux/pipewire/PipewireGrabber.cpp b/sources/grabber/linux/pipewire/PipewireGrabber.cpp index eb326aef4..8ffb06c68 100644 --- a/sources/grabber/linux/pipewire/PipewireGrabber.cpp +++ b/sources/grabber/linux/pipewire/PipewireGrabber.cpp @@ -53,12 +53,13 @@ namespace { bool (*_hasPipewire)() = nullptr; const char* (*_getPipewireError)() = nullptr; - void (*_initPipewireDisplay)(const char* restorationToken, uint32_t requestedFPS, bool enableEGL, int targetMaxSize) = nullptr; + void (*_initPipewireDisplay)(const char* restorationToken, uint32_t requestedFPS, bool enableEGL, int targetMaxSize, int selectedDisplay) = nullptr; void (*_uninitPipewireDisplay)() = nullptr; PipewireImage(*_getFramePipewire)() = nullptr; void (*_releaseFramePipewire)() = nullptr; const char* (*_getPipewireToken)() = nullptr; bool (*_isRestartNeeded)() = nullptr; + bool (*_hasPipewireRemoteDesktop)() = nullptr; } PipewireGrabber::PipewireGrabber(const QString& device, const QString& configurationPath) @@ -88,17 +89,19 @@ PipewireGrabber::PipewireGrabber(const QString& device, const QString& configura _getPipewireToken = (const char* (*)()) dlsym(_library, "getPipewireToken"); _getPipewireError = (const char* (*)()) dlsym(_library, "getPipewireError"); _hasPipewire = (bool (*)()) dlsym(_library, "hasPipewire"); - _initPipewireDisplay = (void (*)(const char*, uint32_t, bool, int)) dlsym(_library, "initPipewireDisplay"); + _initPipewireDisplay = (void (*)(const char*, uint32_t, bool, int, int)) dlsym(_library, "initPipewireDisplay"); _uninitPipewireDisplay = (void (*)()) dlsym(_library, "uninitPipewireDisplay"); _getFramePipewire = (PipewireImage (*)()) dlsym(_library, "getFramePipewire"); _releaseFramePipewire = (void (*)()) dlsym(_library, "releaseFramePipewire"); _isRestartNeeded = (bool (*)()) dlsym(_library, "isRestartNeeded"); + _hasPipewireRemoteDesktop = (bool (*)()) dlsym(_library, "hasPipewireRemoteDesktop"); } else Warning(_log, "Could not load Pipewire proxy library. Error: {:s}", dlerror()); if (_library && (_getPipewireToken == nullptr || _hasPipewire == nullptr || _releaseFramePipewire == nullptr || - _initPipewireDisplay == nullptr || _uninitPipewireDisplay == nullptr || _getFramePipewire == nullptr || _isRestartNeeded == nullptr)) + _initPipewireDisplay == nullptr || _uninitPipewireDisplay == nullptr || _getFramePipewire == nullptr || _isRestartNeeded == nullptr || + _hasPipewireRemoteDesktop == nullptr)) { Error(_log, "Could not load Pipewire proxy library definition. Error: {:s}", dlerror()); @@ -264,11 +267,22 @@ void PipewireGrabber::enumerateDevices(bool silent) DeviceProperties properties; DevicePropertiesItem dpi; - QString id = "Pipewire System Dialog selection"; - dpi.input = 1; + QString id = "Pipewire System Dialog selection (ScreenCast API)"; + dpi.input = PipewirePortal::ScreenID_ScreenCast; properties.valid.append(dpi); - _deviceProperties.insert(id, properties); + _deviceProperties.insert(id, properties); + + if (_hasPipewireRemoteDesktop()) + { + DeviceProperties remoteProperties; + DevicePropertiesItem remoteDpi; + + remoteDpi.input = PipewirePortal::ScreenID_RemoteDesktop; + remoteProperties.valid.append(remoteDpi); + + _deviceProperties.insert("Pipewire All Screens (RemoteDesktop API)", remoteProperties); + } } } @@ -322,7 +336,7 @@ bool PipewireGrabber::init_device(int _display) token = ""; else Info(_log, "Loading restoration token: {:s}", (maskToken(token))); - _initPipewireDisplay(token.toLatin1().constData(), _fps, _hardware, _width); + _initPipewireDisplay(token.toLatin1().constData(), _fps, _hardware, _width, _actualDisplay); return true; } diff --git a/sources/grabber/linux/pipewire/PipewireHandler.cpp b/sources/grabber/linux/pipewire/PipewireHandler.cpp index 3ad93198d..8864b5216 100644 --- a/sources/grabber/linux/pipewire/PipewireHandler.cpp +++ b/sources/grabber/linux/pipewire/PipewireHandler.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include #ifndef DRM_FORMAT_MOD_INVALID @@ -80,6 +81,21 @@ class ScreenCastProxy final : public sdbus::ProxyInterfaces +{ +public: + RemoteDesktopProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath path) + : ProxyInterfaces(connection, std::move(destination), std::move(path)) + { + registerProxy(); + } + + ~RemoteDesktopProxy() + { + unregisterProxy(); + } +}; + // Pipewire screen grabber using Portal access interface Q_DECLARE_METATYPE(QList); @@ -97,10 +113,10 @@ constexpr const int DEFAULT_UPDATE_NUMBER = 4; PipewireHandler::PipewireHandler() : _sessionHandle(""), _restorationToken(""), _errorMessage(""), _portalStatus(false), - _isError(false), _version(-1), _streamNodeId(0), + _isError(false), _useRemoteDesktopPortal(false), _version(-1), _remoteDesktopVersion(-1), _streamNodeId(0), _sender(""), _replySessionPath(""), _sourceReplyPath(""), _startReplyPath(""), _pwMainThreadLoop(nullptr), _pwNewContext(nullptr), _pwContextConnection(nullptr), _pwStream(nullptr), - _targetMaxSize(512), _frameWidth(0),_frameHeight(0),_frameOrderRgb(false), _requestedFPS(10), _incomingFrame(nullptr), + _targetMaxSize(512), _selectedDisplay(0), _frameWidth(0),_frameHeight(0),_frameOrderRgb(false), _requestedFPS(10), _incomingFrame(nullptr), _infoUpdate(DEFAULT_UPDATE_NUMBER), _initEGL(false), _enableEGL(true), _libEglHandle(nullptr), _libGlHandle(nullptr), _frameDrmFormat(DRM_FORMAT_MOD_INVALID), _frameDrmModifier(DRM_FORMAT_MOD_INVALID), _image{} { @@ -205,6 +221,7 @@ void PipewireHandler::closeSession() _sessionHandle = ""; _screenCastProxy = nullptr; + _remoteDesktopProxy = nullptr; _createSessionProxy = nullptr; _selectSourceProxy = nullptr; _startProxy = nullptr; @@ -214,8 +231,10 @@ void PipewireHandler::closeSession() _pwCoreListener = {}; _portalStatus = false; _isError = false; + _useRemoteDesktopPortal = false; _errorMessage = ""; _streamNodeId = 0; + _selectedDisplay = 0; _frameWidth = 0; _frameHeight = 0; _frameOrderRgb = false; @@ -340,7 +359,7 @@ int PipewireHandler::readVersion() return version; } -void PipewireHandler::startSession(QString restorationToken, uint32_t requestedFPS, bool enableEGL, int targetMaxSize) +void PipewireHandler::startSession(QString restorationToken, uint32_t requestedFPS, bool enableEGL, int targetMaxSize, int selectedDisplay) { qDebug().nospace() << "Pipewire: initialization invoked. Cleaning up first..."; @@ -348,6 +367,7 @@ void PipewireHandler::startSession(QString restorationToken, uint32_t requestedF _enableEGL = enableEGL; _targetMaxSize = targetMaxSize; + _selectedDisplay = selectedDisplay; #ifdef ENABLE_PIPEWIRE_EGL qDebug().nospace() << "Pipewire: support for EGL is " << ((_enableEGL) ? "enabled" : "disabled"); @@ -377,6 +397,22 @@ void PipewireHandler::startSession(QString restorationToken, uint32_t requestedF _version = -1; } + if (_dbusConnection != nullptr) + { + try + { + _remoteDesktopProxy = std::make_unique(*_dbusConnection, ServiceName{ DESKTOP_SERVICE }, ObjectPath{ DESKTOP_PATH }); + _remoteDesktopVersion = _remoteDesktopProxy->version(); + _useRemoteDesktopPortal = (_selectedDisplay == PipewirePortal::ScreenID_RemoteDesktop && _remoteDesktopVersion >= PipewirePortal::MinRemoteDesktopPortalVersion); + } + catch(std::exception& e) + { + qWarning().nospace() << "Pipewire: could not read Portal RemoteDesktop version, falling back to ScreenCast session"; + _remoteDesktopVersion = -1; + _useRemoteDesktopPortal = false; + } + } + _restorationToken = QString("%1").arg(restorationToken); _image.version = _version; @@ -394,6 +430,7 @@ void PipewireHandler::startSession(QString restorationToken, uint32_t requestedF _sender = _sender.right(_sender.length()-1); qDebug().nospace() << "Sender: " << qPrintable(_sender); + qDebug().nospace() << "Portal.RemoteDesktop: protocol version = " << _remoteDesktopVersion << ", screen capture via remote desktop = " << ((_useRemoteDesktopPortal) ? "true" : "false"); QString requestUUID = getRequestToken(); @@ -417,7 +454,9 @@ void PipewireHandler::startSession(QString restorationToken, uint32_t requestedF } }; - sdbus::ObjectPath requestPath = _screenCastProxy->CreateSession(createSessionParams); + sdbus::ObjectPath requestPath = (_useRemoteDesktopPortal) + ? _remoteDesktopProxy->CreateSession(createSessionParams) + : _screenCastProxy->CreateSession(createSessionParams); _replySessionPath = QString::fromStdString(static_cast(requestPath)); _createSessionProxy = sdbus::createProxy(*_dbusConnection, sdbus::ServiceName{ DESKTOP_SERVICE }, sdbus::ObjectPath{_replySessionPath.toStdString()}); @@ -451,13 +490,17 @@ void PipewireHandler::createSessionResponse(uint response, QString session) { "multiple", sdbus::Variant(false)}, { "types", sdbus::Variant((uint)1)}, { "cursor_mode", sdbus::Variant((uint)1) }, - { "handle_token", sdbus::Variant(requestUUID.toStdString()) }, - { "persist_mode", sdbus::Variant((uint)2) } }; + { "handle_token", sdbus::Variant(requestUUID.toStdString()) } }; - if (!_restorationToken.isEmpty()) + if (!_useRemoteDesktopPortal) { - selectSourceParams["restore_token"] = sdbus::Variant(_restorationToken.toStdString()); - qDebug().nospace() << "Pipewire: Has restoration token: " << qPrintable(QString(_restorationToken).right(12)); + selectSourceParams["persist_mode"] = sdbus::Variant((uint)2); + + if (!_restorationToken.isEmpty()) + { + selectSourceParams["restore_token"] = sdbus::Variant(_restorationToken.toStdString()); + qDebug().nospace() << "Pipewire: Has restoration token: " << qPrintable(QString(_restorationToken).right(12)); + } } try @@ -491,6 +534,9 @@ void PipewireHandler::selectSourcesResponse(uint response) return; } + if (_useRemoteDesktopPortal) + qDebug().nospace() << "Pipewire: Starting remote desktop session without input devices"; + QString requestUUID = getRequestToken(); std::map startParams{{ "handle_token", sdbus::Variant(requestUUID.toStdString()) }}; @@ -562,7 +608,9 @@ void PipewireHandler::selectSourcesResponse(uint response) } }; - sdbus::ObjectPath startRequestPath = _screenCastProxy->Start(ObjectPath{ _sessionHandle.toStdString() }, "", startParams); + sdbus::ObjectPath startRequestPath = (_useRemoteDesktopPortal) + ? _remoteDesktopProxy->Start(ObjectPath{ _sessionHandle.toStdString() }, "", startParams) + : _screenCastProxy->Start(ObjectPath{ _sessionHandle.toStdString() }, "", startParams); _startReplyPath = QString::fromStdString(static_cast(startRequestPath)); @@ -1676,4 +1724,3 @@ pw_stream* PipewireHandler::createCapturingStream() return stream; } - diff --git a/sources/grabber/linux/pipewire/smartPipewire.cpp b/sources/grabber/linux/pipewire/smartPipewire.cpp index 621dee227..218d59345 100644 --- a/sources/grabber/linux/pipewire/smartPipewire.cpp +++ b/sources/grabber/linux/pipewire/smartPipewire.cpp @@ -47,14 +47,21 @@ #include #include +#include + +using namespace sdbus; +using namespace org::freedesktop::portal; + +constexpr const char* DESKTOP_SERVICE = "org.freedesktop.portal.Desktop"; +constexpr const char* DESKTOP_PATH = "/org/freedesktop/portal/desktop"; PipewireHandler pipewireHandler; -void initPipewireDisplay(const char* restorationToken, uint32_t requestedFPS, bool enableEGL, int targetMaxSize) +void initPipewireDisplay(const char* restorationToken, uint32_t requestedFPS, bool enableEGL, int targetMaxSize, int selectedDisplay) { QString qRestorationToken = QString("%1").arg(restorationToken); - pipewireHandler.startSession(qRestorationToken, requestedFPS, enableEGL, targetMaxSize); + pipewireHandler.startSession(qRestorationToken, requestedFPS, enableEGL, targetMaxSize, selectedDisplay); } void releaseFramePipewire() @@ -122,6 +129,26 @@ bool hasPipewire() return false; } +bool hasPipewireRemoteDesktop() +{ + try + { + auto bus = sdbus::createSessionBusConnection(); + auto proxy = sdbus::createProxy(*bus, ServiceName{ DESKTOP_SERVICE }, ObjectPath{ DESKTOP_PATH }); + + int version = proxy->getProperty("version").onInterface(RemoteDesktop_proxy::INTERFACE_NAME).get(); + + std::cout << "Portal.RemoteDesktop: protocol version = " << version << std::endl; + + return version >= PipewirePortal::MinRemoteDesktopPortalVersion; + } + catch (...) + { + } + + return false; +} + PipewireImage getFramePipewire() { PipewireImage retVal{};