mirror of
https://invent.kde.org/network/krfb
synced 2024-07-01 07:24:29 +00:00
Implement Pipewire framebuffer
This commit is contained in:
parent
6fc934b0ef
commit
211b046906
|
@ -71,6 +71,18 @@ set(CMAKE_MODULE_PATH
|
||||||
|
|
||||||
find_package(LibVNCServer REQUIRED)
|
find_package(LibVNCServer REQUIRED)
|
||||||
|
|
||||||
|
find_package(PipeWire)
|
||||||
|
set_package_properties(PipeWire PROPERTIES
|
||||||
|
TYPE OPTIONAL
|
||||||
|
PURPOSE "Required for pipewire screencast plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
find_package(SPA)
|
||||||
|
set_package_properties(SPA PROPERTIES
|
||||||
|
TYPE OPTIONAL
|
||||||
|
PURPOSE "Required for pipewire screencast plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
include_directories ("${CMAKE_CURRENT_BINARY_DIR}/krfb"
|
include_directories ("${CMAKE_CURRENT_BINARY_DIR}/krfb"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/krfb"
|
"${CMAKE_CURRENT_SOURCE_DIR}/krfb"
|
||||||
|
|
109
cmake/modules/FindPipeWire.cmake
Normal file
109
cmake/modules/FindPipeWire.cmake
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
#.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 <alex.merry@kde.org>
|
||||||
|
# Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
|
||||||
|
# Copyright 2018 Jan Grulich <jgrulich@redhat.com>
|
||||||
|
#
|
||||||
|
# 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_check_modules(PKG_PipeWire QUIET libpipewire-0.1)
|
||||||
|
|
||||||
|
set(PipeWire_DEFINITIONS "${PKG_PipeWire_CFLAGS_OTHER}")
|
||||||
|
set(PipeWire_VERSION "${PKG_PipeWire_VERSION}")
|
||||||
|
|
||||||
|
find_path(PipeWire_INCLUDE_DIRS
|
||||||
|
NAMES
|
||||||
|
pipewire/pipewire.h
|
||||||
|
HINTS
|
||||||
|
${PKG_PipeWire_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(PipeWire_LIBRARIES
|
||||||
|
NAMES
|
||||||
|
pipewire-0.1
|
||||||
|
HINTS
|
||||||
|
${PKG_PipeWire_LIBRARIES_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(PipeWire
|
||||||
|
FOUND_VAR
|
||||||
|
PipeWire_FOUND
|
||||||
|
REQUIRED_VARS
|
||||||
|
PipeWire_LIBRARIES
|
||||||
|
PipeWire_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}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
mark_as_advanced(PipeWire_LIBRARIES PipeWire_INCLUDE_DIRS)
|
||||||
|
|
||||||
|
include(FeatureSummary)
|
||||||
|
set_package_properties(PipeWire PROPERTIES
|
||||||
|
URL "http://www.pipewire.org"
|
||||||
|
DESCRIPTION "PipeWire - multimedia processing"
|
||||||
|
)
|
109
cmake/modules/FindSPA.cmake
Normal file
109
cmake/modules/FindSPA.cmake
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
#.rst:
|
||||||
|
# FindSPA
|
||||||
|
# -------
|
||||||
|
#
|
||||||
|
# Try to find the Simple Plugin API (SPA) on a Unix system.
|
||||||
|
#
|
||||||
|
# This will define the following variables:
|
||||||
|
#
|
||||||
|
# ``SPA_FOUND``
|
||||||
|
# True if (the requested version of) SPA is available
|
||||||
|
# ``SPA_VERSION``
|
||||||
|
# The version of SPA
|
||||||
|
# ``SPA_LIBRARIES``
|
||||||
|
# This can be passed to target_link_libraries() instead of the ``SPA::SPA``
|
||||||
|
# target
|
||||||
|
# ``SPA_INCLUDE_DIRSS``
|
||||||
|
# This should be passed to target_include_directories() if the target is not
|
||||||
|
# used for linking
|
||||||
|
# ``SPA_DEFINITIONS``
|
||||||
|
# This should be passed to target_compile_options() if the target is not
|
||||||
|
# used for linking
|
||||||
|
#
|
||||||
|
# If ``SPA_FOUND`` is TRUE, it will also define the following imported target:
|
||||||
|
#
|
||||||
|
# ``SPA::SPA``
|
||||||
|
# The SPA 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 <alex.merry@kde.org>
|
||||||
|
# Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
|
||||||
|
# Copyright 2018 Jan Grulich <jgrulich@redhat.com>
|
||||||
|
|
||||||
|
# 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_check_modules(PKG_SPA QUIET libspa-0.1)
|
||||||
|
|
||||||
|
set(SPA_DEFINITIONS "${PKG_SPA_CFLAGS_OTHER}")
|
||||||
|
set(SPA_VERSION "${PKG_SPA_VERSION}")
|
||||||
|
|
||||||
|
find_path(SPA_INCLUDE_DIRS
|
||||||
|
NAMES
|
||||||
|
spa/pod/pod.h
|
||||||
|
HINTS
|
||||||
|
${PKG_SPA_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(SPA_LIBRARIES
|
||||||
|
NAMES
|
||||||
|
spa-lib
|
||||||
|
HINTS
|
||||||
|
${PKG_SPA_LIBRARIES_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(SPA
|
||||||
|
FOUND_VAR
|
||||||
|
SPA_FOUND
|
||||||
|
REQUIRED_VARS
|
||||||
|
SPA_LIBRARIES
|
||||||
|
SPA_INCLUDE_DIRS
|
||||||
|
VERSION_VAR
|
||||||
|
SPA_VERSION
|
||||||
|
)
|
||||||
|
|
||||||
|
if(SPA_FOUND AND NOT TARGET SPA::SPA)
|
||||||
|
add_library(SPA::SPA UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(SPA::SPA PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${SPA_LIBRARIES}"
|
||||||
|
INTERFACE_COMPILE_OPTIONS "${SPA_DEFINITIONS}"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${SPA_INCLUDE_DIRS}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
mark_as_advanced(SPA_LIBRARIES SPA_INCLUDE_DIRS)
|
||||||
|
|
||||||
|
include(FeatureSummary)
|
||||||
|
set_package_properties(SPA PROPERTIES
|
||||||
|
DESCRIPTION "Simple Plugin API"
|
||||||
|
)
|
|
@ -1,5 +1,9 @@
|
||||||
add_subdirectory (qt)
|
add_subdirectory (qt)
|
||||||
|
|
||||||
if (${XCB_DAMAGE_FOUND} AND ${XCB_SHM_FOUND} AND ${XCB_IMAGE_FOUND})
|
if (${XCB_DAMAGE_FOUND} AND ${XCB_SHM_FOUND} AND ${XCB_IMAGE_FOUND})
|
||||||
add_subdirectory (xcb)
|
add_subdirectory (xcb)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (${PipeWire_FOUND} AND ${SPA_FOUND})
|
||||||
|
add_subdirectory(pipewire)
|
||||||
endif()
|
endif()
|
||||||
|
|
33
framebuffers/pipewire/CMakeLists.txt
Normal file
33
framebuffers/pipewire/CMakeLists.txt
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
include_directories (${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
set (krfb_framebuffer_pw_SRCS
|
||||||
|
pw_framebuffer.cpp
|
||||||
|
pw_framebufferplugin.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
qt5_add_dbus_interface(
|
||||||
|
krfb_framebuffer_pw_SRCS
|
||||||
|
xdp_dbus_interface.xml
|
||||||
|
xdp_dbus_interface
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(krfb_framebuffer_pw
|
||||||
|
MODULE
|
||||||
|
${krfb_framebuffer_pw_SRCS}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries (krfb_framebuffer_pw
|
||||||
|
Qt5::Core
|
||||||
|
Qt5::Gui
|
||||||
|
Qt5::DBus
|
||||||
|
KF5::CoreAddons
|
||||||
|
krfbprivate
|
||||||
|
PipeWire::PipeWire
|
||||||
|
SPA::SPA
|
||||||
|
)
|
||||||
|
|
||||||
|
install (TARGETS krfb_framebuffer_pw
|
||||||
|
DESTINATION ${PLUGIN_INSTALL_DIR}/krfb
|
||||||
|
)
|
17
framebuffers/pipewire/krfb_framebuffer_pw.json
Normal file
17
framebuffers/pipewire/krfb_framebuffer_pw.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"Encoding": "UTF-8",
|
||||||
|
"KPlugin": {
|
||||||
|
"Description": "PipeWire based Framebuffer for KRfb.",
|
||||||
|
"Description[x-test]": "xxPipeWire based Framebuffer for KRfb.xx",
|
||||||
|
"EnabledByDefault": true,
|
||||||
|
"Id": "pw",
|
||||||
|
"License": "GPL3",
|
||||||
|
"Name": "PipeWire Framebuffer for KRfb",
|
||||||
|
"Name[x-test]": "xxPipeWire Framebuffer for KRfbxx",
|
||||||
|
"ServiceTypes": [
|
||||||
|
"krfb/framebuffer"
|
||||||
|
],
|
||||||
|
"Version": "0.1",
|
||||||
|
"Website": "http://www.kde.org"
|
||||||
|
}
|
||||||
|
}
|
650
framebuffers/pipewire/pw_framebuffer.cpp
Normal file
650
framebuffers/pipewire/pw_framebuffer.cpp
Normal file
|
@ -0,0 +1,650 @@
|
||||||
|
/* This file is part of the KDE project
|
||||||
|
Copyright (C) 2018 Oleg Chernovskiy <kanedias@xaker.ru>
|
||||||
|
|
||||||
|
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 3 of the License, or (at your option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// system
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <cstring>
|
||||||
|
// Qt
|
||||||
|
#include <QX11Info>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <KRandom>
|
||||||
|
// pipewire
|
||||||
|
#include <pipewire/pipewire.h>
|
||||||
|
#include <pipewire/global.h>
|
||||||
|
#include <spa/param/video/format-utils.h>
|
||||||
|
#include <spa/param/format-utils.h>
|
||||||
|
#include <spa/param/props.h>
|
||||||
|
#include <spa/lib/debug.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include "pw_framebuffer.h"
|
||||||
|
#include "xdp_dbus_interface.h"
|
||||||
|
|
||||||
|
static const uint MIN_SUPPORTED_XDP_KDE_SC_VERSION = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The PWFrameBuffer::Private class - private counterpart of PWFramebuffer class. This is the entity where
|
||||||
|
* whole logic resides, for more info search for "d-pointer pattern" information.
|
||||||
|
*/
|
||||||
|
class PWFrameBuffer::Private {
|
||||||
|
public:
|
||||||
|
Private(PWFrameBuffer *q);
|
||||||
|
~Private();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class PWFrameBuffer;
|
||||||
|
|
||||||
|
static void onStateChanged(void *data, pw_remote_state old, pw_remote_state state, const char *error);
|
||||||
|
static void onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message);
|
||||||
|
static void onStreamFormatChanged(void *data, struct spa_pod *format);
|
||||||
|
static void onNewBuffer(void *data, uint32_t id);
|
||||||
|
|
||||||
|
void initWayland();
|
||||||
|
void initDbus();
|
||||||
|
void initPw();
|
||||||
|
void initializePwTypes();
|
||||||
|
|
||||||
|
// dbus handling
|
||||||
|
void handleSessionCreated(quint32 &code, QVariantMap &results);
|
||||||
|
void handleSourcesSelected(quint32 &code, QVariantMap &results);
|
||||||
|
void handleScreencastStarted(quint32 &code, QVariantMap &results);
|
||||||
|
|
||||||
|
// pw handling
|
||||||
|
void processPwEvents();
|
||||||
|
void createReceivingStream();
|
||||||
|
void handleFrame(spa_buffer *buf);
|
||||||
|
|
||||||
|
// link to public interface
|
||||||
|
PWFrameBuffer *q;
|
||||||
|
|
||||||
|
// pipewire stuff
|
||||||
|
pw_core *pwCore = nullptr;
|
||||||
|
pw_type *pwCoreType = nullptr;
|
||||||
|
pw_remote *pwRemote = nullptr;
|
||||||
|
pw_stream *pwStream = nullptr;
|
||||||
|
pw_loop *pwLoop = nullptr;
|
||||||
|
QScopedPointer<PwType> pwType;
|
||||||
|
|
||||||
|
// event handlers
|
||||||
|
pw_remote_events pwRemoteEvents = {};
|
||||||
|
pw_stream_events pwStreamEvents = {};
|
||||||
|
|
||||||
|
// wayland-like listeners
|
||||||
|
// ...of events that happen in pipewire server
|
||||||
|
spa_hook remoteListener = {};
|
||||||
|
// ...of events that happen with the stream we consume
|
||||||
|
spa_hook streamListener = {};
|
||||||
|
|
||||||
|
// negotiated video format
|
||||||
|
QScopedPointer<spa_video_info_raw> videoFormat;
|
||||||
|
|
||||||
|
// listens on pipewire socket
|
||||||
|
QScopedPointer<QSocketNotifier> socketNotifier;
|
||||||
|
|
||||||
|
// requests a session from XDG Desktop Portal
|
||||||
|
// auto-generated and compiled from xdp_dbus_interface.xml file
|
||||||
|
QScopedPointer<OrgFreedesktopPortalScreenCastInterface> dbusXdpService;
|
||||||
|
|
||||||
|
// XDP screencast session handle
|
||||||
|
QDBusObjectPath sessionPath;
|
||||||
|
// Pipewire file descriptor
|
||||||
|
QDBusUnixFileDescriptor pipewireFd;
|
||||||
|
|
||||||
|
// counters for dbus exchange
|
||||||
|
quint32 requestCounter = 0;
|
||||||
|
quint32 sessionCounter = 0;
|
||||||
|
|
||||||
|
// screen geometry holder
|
||||||
|
struct {
|
||||||
|
quint32 width;
|
||||||
|
quint32 height;
|
||||||
|
} screenGeometry;
|
||||||
|
|
||||||
|
// real image with allocated memory which poses as a destination when we get a buffer from pipewire
|
||||||
|
// and as source when we pass the frame back to protocol
|
||||||
|
QImage fbImage;
|
||||||
|
|
||||||
|
// sanity indicator
|
||||||
|
bool isValid = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
PWFrameBuffer::Private::Private(PWFrameBuffer *q) : q(q)
|
||||||
|
{
|
||||||
|
// 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.new_buffer = &onNewBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::initWayland - initializes screen info and Wayland connectivity.
|
||||||
|
* For now just grabs first available screen and uses its dimensions for framebuffer.
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::initWayland()
|
||||||
|
{
|
||||||
|
qInfo() << "Initializing screen info";
|
||||||
|
auto screen = qApp->screens().at(0);
|
||||||
|
auto screenSize = screen->geometry();
|
||||||
|
screenGeometry.width = static_cast<quint32>(screenSize.width());
|
||||||
|
screenGeometry.height = static_cast<quint32>(screenSize.height());
|
||||||
|
fbImage = QImage(screenSize.width(), screenSize.height(), QImage::Format_RGBX8888);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::initDbus - initialize D-Bus connectivity with XDG Desktop Portal.
|
||||||
|
* Based on XDG_CURRENT_DESKTOP environment variable it will give us implementation that we need,
|
||||||
|
* in case of KDE it is xdg-desktop-portal-kde binary.
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::initDbus()
|
||||||
|
{
|
||||||
|
qInfo() << "Initializing D-Bus connectivity with XDG Desktop Portal";
|
||||||
|
dbusXdpService.reset(new OrgFreedesktopPortalScreenCastInterface(QLatin1String("org.freedesktop.portal.Desktop"),
|
||||||
|
QLatin1String("/org/freedesktop/portal/desktop"),
|
||||||
|
QDBusConnection::sessionBus()));
|
||||||
|
if (!dbusXdpService->isValid()) {
|
||||||
|
qWarning("Can't find XDG Portal screencast interface");
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto version = dbusXdpService->version();
|
||||||
|
if (version < MIN_SUPPORTED_XDP_KDE_SC_VERSION) {
|
||||||
|
qWarning() << "Unsupported XDG Portal screencast interface version:" << version;
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create session
|
||||||
|
auto sessionParameters = QVariantMap {
|
||||||
|
{ QLatin1String("session_handle_token"), QString::number(sessionCounter++) },
|
||||||
|
{ QLatin1String("handle_token"), QString::number(requestCounter++) }
|
||||||
|
};
|
||||||
|
auto sessionReply = dbusXdpService->CreateSession(sessionParameters);
|
||||||
|
sessionReply.waitForFinished();
|
||||||
|
if (!sessionReply.isValid()) {
|
||||||
|
qWarning("Couldn't initialize XDP-KDE screencast session");
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qInfo() << "DBus session created: " << sessionReply.value().path();
|
||||||
|
QDBusConnection::sessionBus().connect(QString(),
|
||||||
|
sessionReply.value().path(),
|
||||||
|
QLatin1String("org.freedesktop.portal.Request"),
|
||||||
|
QLatin1String("Response"),
|
||||||
|
this->q,
|
||||||
|
SLOT(handleXdpSessionCreated(uint, QVariantMap)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PWFrameBuffer::handleXdpSessionCreated(quint32 code, QVariantMap results)
|
||||||
|
{
|
||||||
|
d->handleSessionCreated(code, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::handleSessionCreated - handle creation of ScreenCast session.
|
||||||
|
* XDG Portal answers with session path if it was able to successfully create the screencast.
|
||||||
|
*
|
||||||
|
* @param code return code for dbus call. Zero is success, non-zero means error
|
||||||
|
* @param results map with results of call.
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::handleSessionCreated(quint32 &code, QVariantMap &results)
|
||||||
|
{
|
||||||
|
if (code != 0) {
|
||||||
|
qWarning() << "Failed to create session: " << code;
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionPath = QDBusObjectPath(results.value(QLatin1String("session_handle")).toString());
|
||||||
|
|
||||||
|
// select sources for the session
|
||||||
|
auto selectionOptions = QVariantMap {
|
||||||
|
{ QLatin1String("types"), 1u }, // only MONITOR is supported
|
||||||
|
{ QLatin1String("multiple"), false },
|
||||||
|
{ QLatin1String("handle_token"), QString::number(requestCounter++) }
|
||||||
|
};
|
||||||
|
auto selectorReply = dbusXdpService->SelectSources(sessionPath, selectionOptions);
|
||||||
|
selectorReply.waitForFinished();
|
||||||
|
if (!selectorReply.isValid()) {
|
||||||
|
qWarning() << "Couldn't select sources for the screen-casting session";
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QDBusConnection::sessionBus().connect(QString(),
|
||||||
|
selectorReply.value().path(),
|
||||||
|
QLatin1String("org.freedesktop.portal.Request"),
|
||||||
|
QLatin1String("Response"),
|
||||||
|
this->q,
|
||||||
|
SLOT(handleXdpSourcesSelected(uint, QVariantMap)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PWFrameBuffer::handleXdpSourcesSelected(quint32 code, QVariantMap results)
|
||||||
|
{
|
||||||
|
d->handleSourcesSelected(code, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::handleSourcesSelected - handle Screencast sources selection.
|
||||||
|
* XDG Portal shows a dialog at this point which allows you to select monitor from the list.
|
||||||
|
* This function is called after you make a selection.
|
||||||
|
*
|
||||||
|
* @param code return code for dbus call. Zero is success, non-zero means error
|
||||||
|
* @param results map with results of call.
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::handleSourcesSelected(quint32 &code, QVariantMap &)
|
||||||
|
{
|
||||||
|
if (code != 0) {
|
||||||
|
qWarning() << "Failed to select sources: " << code;
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start session
|
||||||
|
auto startParameters = QVariantMap {
|
||||||
|
{ QLatin1String("handle_token"), QString::number(requestCounter++) }
|
||||||
|
};
|
||||||
|
auto startReply = dbusXdpService->Start(sessionPath, QString(), startParameters);
|
||||||
|
startReply.waitForFinished();
|
||||||
|
QDBusConnection::sessionBus().connect(QString(),
|
||||||
|
startReply.value().path(),
|
||||||
|
QLatin1String("org.freedesktop.portal.Request"),
|
||||||
|
QLatin1String("Response"),
|
||||||
|
this->q,
|
||||||
|
SLOT(handleXdpScreenCastStarted(uint, QVariantMap)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PWFrameBuffer::handleXdpScreenCastStarted(quint32 code, QVariantMap results)
|
||||||
|
{
|
||||||
|
d->handleScreencastStarted(code, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::handleScreencastStarted - handle Screencast start.
|
||||||
|
* At this point there shall be ready pipewire stream to consume.
|
||||||
|
*
|
||||||
|
* @param code return code for dbus call. Zero is success, non-zero means error
|
||||||
|
* @param results map with results of call.
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::handleScreencastStarted(quint32 &code, QVariantMap &results)
|
||||||
|
{
|
||||||
|
if (code != 0) {
|
||||||
|
qWarning() << "Failed to start screencast: " << code;
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// there should be only one stream
|
||||||
|
auto streams = results.value("streams");
|
||||||
|
if (streams.isNull()) {
|
||||||
|
// maybe we should check deeper with qdbus_cast but this suffices for now
|
||||||
|
qWarning() << "Failed to get screencast streams";
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto streamReply = dbusXdpService->OpenPipeWireRemote(sessionPath, QVariantMap());
|
||||||
|
streamReply.waitForFinished();
|
||||||
|
if (!streamReply.isValid()) {
|
||||||
|
qWarning() << "Couldn't open pipewire remote for the screen-casting session";
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pipewireFd = streamReply.value();
|
||||||
|
if (!pipewireFd.isValid()) {
|
||||||
|
qWarning() << "Couldn't get pipewire connection file descriptor";
|
||||||
|
isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initPw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::initPw - initialize Pipewire socket connectivity.
|
||||||
|
* pipewireFd should be pointing to existing file descriptor that was passed by D-Bus at this point.
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::initPw() {
|
||||||
|
qInfo() << "Initializing Pipewire connectivity";
|
||||||
|
|
||||||
|
// init pipewire (required)
|
||||||
|
pw_init(nullptr, nullptr); // args are not used anyways
|
||||||
|
|
||||||
|
// initialize our source
|
||||||
|
pwLoop = pw_loop_new(nullptr);
|
||||||
|
socketNotifier.reset(new QSocketNotifier(pw_loop_get_fd(pwLoop), QSocketNotifier::Read));
|
||||||
|
QObject::connect(socketNotifier.data(), &QSocketNotifier::activated, this->q, &PWFrameBuffer::processPwEvents);
|
||||||
|
|
||||||
|
// create PipeWire core object (required)
|
||||||
|
pwCore = pw_core_new(pwLoop, nullptr);
|
||||||
|
pwCoreType = pw_core_get_type(pwCore);
|
||||||
|
|
||||||
|
// pw_remote should be initialized before type maps or connection error will happen
|
||||||
|
pwRemote = pw_remote_new(pwCore, nullptr, 0);
|
||||||
|
|
||||||
|
// init type maps
|
||||||
|
initializePwTypes();
|
||||||
|
|
||||||
|
// init PipeWire remote, add listener to handle events
|
||||||
|
pw_remote_add_listener(pwRemote, &remoteListener, &pwRemoteEvents, this);
|
||||||
|
pw_remote_connect_fd(pwRemote, pipewireFd.fileDescriptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
auto map = pwCoreType->map;
|
||||||
|
|
||||||
|
pwType.reset(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);
|
||||||
|
|
||||||
|
// must be called after type system is mapped
|
||||||
|
// calling it before causes unpredictable memory corruption and errors
|
||||||
|
spa_debug_set_type_map(pwCoreType->map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
PWFrameBuffer::Private *d = static_cast<PWFrameBuffer::Private*>(data);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case PW_REMOTE_STATE_ERROR:
|
||||||
|
qWarning() << "remote error: " << error;
|
||||||
|
break;
|
||||||
|
case PW_REMOTE_STATE_CONNECTED:
|
||||||
|
d->createReceivingStream();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qInfo() << "remote state: " << pw_remote_state_as_string(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::onStreamStateChanged - called whenever stream state changes on pipewire server
|
||||||
|
* @param data pointer that you have set in pw_stream_add_listener call's last argument
|
||||||
|
* @param state new state that stream has changed to
|
||||||
|
* @param error_message optional error message, is set to non-null if state is error
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::onStreamStateChanged(void *data, pw_stream_state /*old*/, pw_stream_state state, const char *error_message)
|
||||||
|
{
|
||||||
|
qInfo() << "Stream state changed: " << pw_stream_state_as_string(state);
|
||||||
|
|
||||||
|
auto *d = static_cast<PWFrameBuffer::Private *>(data);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case PW_STREAM_STATE_ERROR:
|
||||||
|
qWarning() << "pipewire stream error: " << error_message;
|
||||||
|
break;
|
||||||
|
case PW_STREAM_STATE_CONFIGURE:
|
||||||
|
pw_stream_set_active(d->pwStream, true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::onStreamFormatChanged - being executed after stream is set to active
|
||||||
|
* and after setup has been requested to connect to it. The actual video format is being negotiated here.
|
||||||
|
* @param data pointer that you have set in pw_stream_add_listener call's last argument
|
||||||
|
* @param format format that's being proposed
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::onStreamFormatChanged(void *data, struct spa_pod *format)
|
||||||
|
{
|
||||||
|
qInfo() << "Stream format changed";
|
||||||
|
auto *d = static_cast<PWFrameBuffer::Private *>(data);
|
||||||
|
|
||||||
|
const int bpp = 4;
|
||||||
|
|
||||||
|
if (!format) {
|
||||||
|
pw_stream_finish_format(d->pwStream, 0, nullptr, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
d->videoFormat.reset(new spa_video_info_raw);
|
||||||
|
spa_format_video_raw_parse(format, d->videoFormat.data(), &d->pwType->format_video);
|
||||||
|
|
||||||
|
auto width = d->videoFormat->size.width;
|
||||||
|
auto height = d->videoFormat->size.height;
|
||||||
|
auto stride = SPA_ROUND_UP_N(width * bpp, 4);
|
||||||
|
auto size = height * stride;
|
||||||
|
|
||||||
|
uint8_t buffer[1024];
|
||||||
|
auto builder = spa_pod_builder {buffer, sizeof(buffer)};
|
||||||
|
|
||||||
|
// setup buffers and meta header for new format
|
||||||
|
struct spa_pod *params[2];
|
||||||
|
params[0] = reinterpret_cast<spa_pod *>(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 *>(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::onNewBuffer - called when new buffer is available in pipewire stream
|
||||||
|
* @param data pointer that you have set in pw_stream_add_listener call's last argument
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::onNewBuffer(void *data, uint32_t id)
|
||||||
|
{
|
||||||
|
qDebug() << "New buffer received" << id;
|
||||||
|
auto *d = static_cast<PWFrameBuffer::Private *>(data);
|
||||||
|
|
||||||
|
auto buf = pw_stream_peek_buffer(d->pwStream, id);
|
||||||
|
d->handleFrame(buf);
|
||||||
|
|
||||||
|
pw_stream_recycle_buffer(d->pwStream, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PWFrameBuffer::Private::handleFrame(spa_buffer *buf)
|
||||||
|
{
|
||||||
|
auto mapLength = buf->datas[0].maxsize + buf->datas[0].mapoffset;
|
||||||
|
|
||||||
|
void *mapped; // full length of mapped data
|
||||||
|
void *src; // real pixel data in this buffer
|
||||||
|
if (buf->datas[0].type == pwCoreType->data.MemFd || buf->datas[0].type == pwCoreType->data.DmaBuf) {
|
||||||
|
mapped = mmap(nullptr, mapLength, PROT_READ, MAP_PRIVATE, buf->datas[0].fd, 0);
|
||||||
|
src = SPA_MEMBER(mapped, buf->datas[0].mapoffset, void);
|
||||||
|
} else if (buf->datas[0].type == pwCoreType->data.MemPtr) {
|
||||||
|
mapped = nullptr;
|
||||||
|
src = buf->datas[0].data;
|
||||||
|
} else {
|
||||||
|
qWarning() << "Got unsupported buffer type" << buf->datas[0].type;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint32 srcStride = buf->datas[0].chunk->stride;
|
||||||
|
if (srcStride != q->paddedWidth()) {
|
||||||
|
qWarning() << "Got buffer with stride different from screen stride" << srcStride << "!=" << q->paddedWidth();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fbImage.bits();
|
||||||
|
q->tiles.append(fbImage.rect());
|
||||||
|
std::memcpy(fbImage.bits(), src, buf->datas[0].maxsize);
|
||||||
|
|
||||||
|
if (mapped)
|
||||||
|
munmap(mapped, mapLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::processPwEvents - called when Pipewire socket notifies there's
|
||||||
|
* data to process by remote interface.
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::processPwEvents() {
|
||||||
|
qDebug() << "Iterating over pipewire loop...";
|
||||||
|
|
||||||
|
int result = pw_loop_iterate(pwLoop, 0);
|
||||||
|
if (result < 0) {
|
||||||
|
qWarning() << "Failed to iterate over pipewire loop: " << spa_strerror(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PWFrameBuffer::Private::createReceivingStream - create a stream that will consume Pipewire buffers
|
||||||
|
* and copy the framebuffer to the existing image that we track. The state of the stream and configuration
|
||||||
|
* are later handled by the corresponding listener.
|
||||||
|
*/
|
||||||
|
void PWFrameBuffer::Private::createReceivingStream()
|
||||||
|
{
|
||||||
|
auto pwScreenBounds = spa_rectangle {screenGeometry.width, screenGeometry.height};
|
||||||
|
|
||||||
|
auto pwFramerate = spa_fraction {25, 1};
|
||||||
|
auto pwFramerateMin = spa_fraction {0, 1};
|
||||||
|
auto pwFramerateMax = spa_fraction {60, 1};
|
||||||
|
|
||||||
|
auto reuseProps = pw_properties_new("pipewire.client.reuse", "1", nullptr); // null marks end of varargs
|
||||||
|
pwStream = pw_stream_new(pwRemote, "krfb-fb-consume-stream", reuseProps);
|
||||||
|
|
||||||
|
uint8_t buffer[1024] = {};
|
||||||
|
const spa_pod *params[1];
|
||||||
|
auto builder = spa_pod_builder{buffer, sizeof(buffer)};
|
||||||
|
params[0] = reinterpret_cast<spa_pod *>(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, "R", &pwScreenBounds,
|
||||||
|
":", pwType->format_video.framerate, "F", &pwFramerateMin,
|
||||||
|
":", pwType->format_video.max_framerate, "Fr", &pwFramerate, 2, &pwFramerateMin, &pwFramerateMax));
|
||||||
|
spa_debug_pod(params[0], SPA_DEBUG_FLAG_FORMAT);
|
||||||
|
|
||||||
|
pw_stream_add_listener(pwStream, &streamListener, &pwStreamEvents, this);
|
||||||
|
auto flags = static_cast<pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE);
|
||||||
|
if (pw_stream_connect(pwStream, PW_DIRECTION_INPUT, nullptr, flags, params, 1) != 0) {
|
||||||
|
qWarning() << "Could not connect receiving stream";
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PWFrameBuffer::Private::~Private()
|
||||||
|
{
|
||||||
|
if (pwStream) {
|
||||||
|
pw_stream_disconnect(pwStream);
|
||||||
|
pw_stream_destroy(pwStream);
|
||||||
|
}
|
||||||
|
if (pwRemote) {
|
||||||
|
pw_remote_disconnect(pwRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pwCore)
|
||||||
|
pw_core_destroy(pwCore);
|
||||||
|
|
||||||
|
if (pwLoop) {
|
||||||
|
pw_loop_leave(pwLoop);
|
||||||
|
pw_loop_destroy(pwLoop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PWFrameBuffer::PWFrameBuffer(WId winid, QObject *parent)
|
||||||
|
: FrameBuffer (winid, parent),
|
||||||
|
d(new Private(this))
|
||||||
|
{
|
||||||
|
// D-Bus is most important in init chain, no toys for us if something is wrong with XDP
|
||||||
|
// PipeWire connectivity is initialized after D-Bus session is started
|
||||||
|
d->initDbus();
|
||||||
|
|
||||||
|
// connect to Wayland and PipeWire sockets
|
||||||
|
d->initWayland();
|
||||||
|
|
||||||
|
// framebuffer from public interface will point directly to image data
|
||||||
|
fb = reinterpret_cast<char *>(d->fbImage.bits());
|
||||||
|
}
|
||||||
|
|
||||||
|
PWFrameBuffer::~PWFrameBuffer()
|
||||||
|
{
|
||||||
|
fb = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PWFrameBuffer::processPwEvents()
|
||||||
|
{
|
||||||
|
d->processPwEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
int PWFrameBuffer::depth()
|
||||||
|
{
|
||||||
|
return 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PWFrameBuffer::height()
|
||||||
|
{
|
||||||
|
return static_cast<qint32>(d->screenGeometry.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
int PWFrameBuffer::width()
|
||||||
|
{
|
||||||
|
return static_cast<qint32>(d->screenGeometry.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PWFrameBuffer::getServerFormat(rfbPixelFormat &format)
|
||||||
|
{
|
||||||
|
format.bitsPerPixel = 32;
|
||||||
|
format.depth = 32;
|
||||||
|
format.trueColour = true;
|
||||||
|
format.bigEndian = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PWFrameBuffer::startMonitor()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PWFrameBuffer::stopMonitor()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PWFrameBuffer::isValid() const
|
||||||
|
{
|
||||||
|
return d->isValid;
|
||||||
|
}
|
50
framebuffers/pipewire/pw_framebuffer.h
Normal file
50
framebuffers/pipewire/pw_framebuffer.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/* This file is part of the KDE project
|
||||||
|
Copyright (C) 2018 Oleg Chernovskiy <kanedias@xaker.ru>
|
||||||
|
|
||||||
|
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 3 of the License, or (at your option) any later version.
|
||||||
|
*/
|
||||||
|
#ifndef KRFB_FRAMEBUFFER_XCB_XCB_FRAMEBUFFER_H
|
||||||
|
#define KRFB_FRAMEBUFFER_XCB_XCB_FRAMEBUFFER_H
|
||||||
|
|
||||||
|
#include "framebuffer.h"
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The PWFrameBuffer class - framebuffer implementation based on XDG Desktop Portal ScreenCast interface.
|
||||||
|
* The design relies heavily on a presence of XDG D-Bus service and PipeWire daemon.
|
||||||
|
*
|
||||||
|
* @author Oleg Chernovskiy <kanedias@xaker.ru>
|
||||||
|
*/
|
||||||
|
class PWFrameBuffer: public FrameBuffer
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
PWFrameBuffer(WId winid, QObject *parent = nullptr);
|
||||||
|
virtual ~PWFrameBuffer() override;
|
||||||
|
|
||||||
|
int depth() override;
|
||||||
|
int height() override;
|
||||||
|
int width() override;
|
||||||
|
void getServerFormat(rfbPixelFormat &format) override;
|
||||||
|
void startMonitor() override;
|
||||||
|
void stopMonitor() override;
|
||||||
|
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleXdpSessionCreated(quint32 code, QVariantMap results);
|
||||||
|
void handleXdpSourcesSelected(quint32 code, QVariantMap results);
|
||||||
|
void handleXdpScreenCastStarted(quint32 code, QVariantMap results);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void processPwEvents();
|
||||||
|
|
||||||
|
class Private;
|
||||||
|
const QScopedPointer<Private> d;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
53
framebuffers/pipewire/pw_framebufferplugin.cpp
Normal file
53
framebuffers/pipewire/pw_framebufferplugin.cpp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/* This file is part of the KDE project
|
||||||
|
Copyright (C) 2018 Oleg Chernovskiy <kanedias@xaker.ru>
|
||||||
|
|
||||||
|
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 3 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; see the file COPYING. If not, write to
|
||||||
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "pw_framebufferplugin.h"
|
||||||
|
#include "pw_framebuffer.h"
|
||||||
|
#include <KPluginFactory>
|
||||||
|
|
||||||
|
|
||||||
|
K_PLUGIN_FACTORY_WITH_JSON(PWFrameBufferPluginFactory, "krfb_framebuffer_pw.json",
|
||||||
|
registerPlugin<PWFrameBufferPlugin>();)
|
||||||
|
|
||||||
|
PWFrameBufferPlugin::PWFrameBufferPlugin(QObject *parent, const QVariantList &args)
|
||||||
|
: FrameBufferPlugin(parent, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PWFrameBufferPlugin::~PWFrameBufferPlugin()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FrameBuffer *PWFrameBufferPlugin::frameBuffer(WId id)
|
||||||
|
{
|
||||||
|
auto pwfb = new PWFrameBuffer(id);
|
||||||
|
|
||||||
|
// sanity check for dbus/wayland/pipewire errors
|
||||||
|
if (!pwfb->isValid()) {
|
||||||
|
delete pwfb;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pwfb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "pw_framebufferplugin.moc"
|
45
framebuffers/pipewire/pw_framebufferplugin.h
Normal file
45
framebuffers/pipewire/pw_framebufferplugin.h
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/* This file is part of the KDE project
|
||||||
|
Copyright (C) 2018 Oleg Chernovskiy <kanedias@xaker.ru>
|
||||||
|
|
||||||
|
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 3 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; see the file COPYING. If not, write to
|
||||||
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KRFB_FRAMEBUFFER_PW_PWFRAMEBUFFERPLUGIN_H
|
||||||
|
#define KRFB_FRAMEBUFFER_PW_PWFRAMEBUFFERPLUGIN_H
|
||||||
|
|
||||||
|
|
||||||
|
#include "framebufferplugin.h"
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
|
||||||
|
class FrameBuffer;
|
||||||
|
|
||||||
|
class PWFrameBufferPlugin: public FrameBufferPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
PWFrameBufferPlugin(QObject *parent, const QVariantList &args);
|
||||||
|
virtual ~PWFrameBufferPlugin() override;
|
||||||
|
|
||||||
|
FrameBuffer *frameBuffer(WId id) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_DISABLE_COPY(PWFrameBufferPlugin)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // Header guard
|
187
framebuffers/pipewire/xdp_dbus_interface.xml
Normal file
187
framebuffers/pipewire/xdp_dbus_interface.xml
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2017-2018 Red Hat, Inc.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
|
||||||
|
<!--
|
||||||
|
org.freedesktop.portal.ScreenCast:
|
||||||
|
@short_description: Screen cast portal
|
||||||
|
-->
|
||||||
|
<interface name="org.freedesktop.portal.ScreenCast">
|
||||||
|
<!--
|
||||||
|
CreateSession:
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
|
||||||
|
|
||||||
|
Create a screen cast session. A successfully created session can at
|
||||||
|
any time be closed using org.freedesktop.portal.Session::Close, or may
|
||||||
|
at any time be closed by the portal implementation, which will be
|
||||||
|
signalled via org.freedesktop.portal.Session::Closed.
|
||||||
|
|
||||||
|
The following results get returned via the #org.freedesktop.portal.Request::Response signal:
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>session_handle o</term>
|
||||||
|
<listitem><para>
|
||||||
|
The session handle. An object path for the
|
||||||
|
#org.freedesktop.portal.Session object representing the created
|
||||||
|
session.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
-->
|
||||||
|
<method name="CreateSession">
|
||||||
|
<arg type="a{sv}" name="options" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
|
||||||
|
<arg type="o" name="handle" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<!--
|
||||||
|
SelectSources:
|
||||||
|
@session_handle: Object path for the #org.freedesktop.portal.Session object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
|
||||||
|
|
||||||
|
Configure what the screen cast session should record. This method must
|
||||||
|
be called before starting the session.
|
||||||
|
|
||||||
|
Passing invalid input to this method will cause the session to be
|
||||||
|
closed. An application may only attempt to select sources once per
|
||||||
|
session.
|
||||||
|
|
||||||
|
Supported keys in the @options vardict include:
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>types u</term>
|
||||||
|
<listitem><para>
|
||||||
|
Bitmask of what types of content to record. Default is MONITOR.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>multiple b</term>
|
||||||
|
<listitem><para>
|
||||||
|
Whether to allow selecting multiple sources. Default is no.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
For available source types, see the AvailableSourceTypes property.
|
||||||
|
-->
|
||||||
|
<method name="SelectSources">
|
||||||
|
<arg type="o" name="session_handle" direction="in"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
|
||||||
|
<arg type="o" name="handle" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<!--
|
||||||
|
Start:
|
||||||
|
@session_handle: Object path for the #org.freedesktop.portal.Session object
|
||||||
|
@parent_window: Identifier for the application window
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
|
||||||
|
|
||||||
|
Start the screen cast session. This will typically result the portal
|
||||||
|
presenting a dialog letting the user do the selection set up by
|
||||||
|
SelectSources. An application can only attempt start a session once.
|
||||||
|
|
||||||
|
A screen cast session may only be started after having selected sources
|
||||||
|
using org.freedesktop.portal.ScreenCast::SelectSources.
|
||||||
|
|
||||||
|
The @parent_window identifier must be of the form "x11:$XID" for an X11
|
||||||
|
window. Support for other window systems may be added in the future.
|
||||||
|
|
||||||
|
The following results get returned via the
|
||||||
|
#org.freedesktop.portal.Request::Response signal:
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>streams a(ua{sv})</term>
|
||||||
|
<listitem><para>
|
||||||
|
An array of PipeWire streams. Each stream consists of a PipeWire
|
||||||
|
node ID (the first element in the tuple, and a Vardict of
|
||||||
|
properties.
|
||||||
|
|
||||||
|
The array will contain a single stream if 'multiple' (see
|
||||||
|
SelectSources) was set to 'false', or at least one stream if
|
||||||
|
'multiple' was set to 'true' as part of the SelectSources method.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
Stream properties include:
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>position (ii)</term>
|
||||||
|
<listitem><para>
|
||||||
|
A tuple consisting of the position (x, y) in the compositor
|
||||||
|
coordinate space. Note that the position may not be equivalent to a
|
||||||
|
position in a pixel coordinate space. Only available for monitor
|
||||||
|
streams.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>size (ii)</term>
|
||||||
|
<listitem><para>
|
||||||
|
A tuple consisting of (width, height). The size represents the size
|
||||||
|
of the stream as it is displayed in the compositor coordinate
|
||||||
|
space. Note that this size may not be equivalent to a size in a
|
||||||
|
pixel coordinate space. The size may differ from the size of the
|
||||||
|
stream.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
-->
|
||||||
|
<method name="Start">
|
||||||
|
<arg type="o" name="session_handle" direction="in"/>
|
||||||
|
<arg type="s" name="parent_window" direction="in"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
|
||||||
|
<arg type="o" name="handle" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<!--
|
||||||
|
OpenPipeWireRemote:
|
||||||
|
@session_handle: Object path for the #org.freedesktop.portal.Session object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
@fd: File descriptor of an open PipeWire remote.
|
||||||
|
|
||||||
|
Open a file descriptor to the PipeWire remote where the screen cast
|
||||||
|
streams are available. The file descriptor should be used to create a
|
||||||
|
<classname>pw_remote</classname> object, by using
|
||||||
|
<function>pw_remote_connect_fd</function>. Only the screen cast stream
|
||||||
|
nodes will be available from this PipeWire node.
|
||||||
|
-->
|
||||||
|
<method name="OpenPipeWireRemote">
|
||||||
|
<annotation name="org.gtk.GDBus.C.Name" value="open_pipewire_remote"/>
|
||||||
|
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
|
||||||
|
<arg type="o" name="session_handle" direction="in"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
|
||||||
|
<arg type="h" name="fd" direction="out"/>
|
||||||
|
</method>
|
||||||
|
<!--
|
||||||
|
AvailableSourceTypes:
|
||||||
|
|
||||||
|
A bitmask of available source types. Currently defined types are:
|
||||||
|
|
||||||
|
<simplelist>
|
||||||
|
<member>1: MONITOR</member>
|
||||||
|
<member>2: WINDOW</member>
|
||||||
|
</simplelist>
|
||||||
|
-->
|
||||||
|
<property name="AvailableSourceTypes" type="u" access="read"/>
|
||||||
|
<property name="version" type="u" access="read"/>
|
||||||
|
</interface>
|
||||||
|
</node>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user