diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,36 +20,6 @@ include(FeatureSummary) -option(ENABLE_PIPEWIRE "Disable PipeWire support. PipeWire is needed for screen sharing and remote desktop" ON) -if(ENABLE_PIPEWIRE) - set(HAVE_PIPEWIRE_SUPPORT 1) -else() - message(STATUS "Disabling PipeWire support") - set(HAVE_PIPEWIRE_SUPPORT 0) -endif() -add_definitions(-DHAVE_PIPEWIRE_SUPPORT=${HAVE_PIPEWIRE_SUPPORT}) - -if(HAVE_PIPEWIRE_SUPPORT) - find_package(PipeWire) - set_package_properties(PipeWire PROPERTIES - TYPE REQUIRED - PURPOSE "Required for screencast portal" - ) - - find_package(GBM) - set_package_properties(GBM PROPERTIES - TYPE REQUIRED - PURPOSE "Required for screencast portal" - ) - - find_package(Epoxy) - set_package_properties(Epoxy PROPERTIES DESCRIPTION "libepoxy" - URL "https://github.com/anholt/libepoxy" - TYPE REQUIRED - PURPOSE "Required for screencast portal" - ) -endif() - find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Concurrent diff --git a/cmake/modules/FindEpoxy.cmake b/cmake/modules/FindEpoxy.cmake deleted file mode 100644 --- a/cmake/modules/FindEpoxy.cmake +++ /dev/null @@ -1,60 +0,0 @@ -#.rst: -# FindEpoxy -# ------- -# -# - Try to find libepoxy. -# -# This will define the following variables: -# -# ``Epoxy_FOUND`` -# TRUE if libepoxy was found -# ``Epoxy_LIBRARIES`` -# Pass this variable to target_link_libraries() -# ``Epoxy_INCLUDE_DIRS`` -# This should be passed to target_include_directories() to use the -# libepoxy headers -# ``Epoxy_DEFINITIONS`` -# Can be passed to target_compile_options() if necessary -# -# ``Epoxy_HAS_GLX`` -# Whether GLX support is available - -# Copyright (c) 2014 Fredrik Höglund -# -# 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 above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. Neither the name of the University nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. - -find_package(PkgConfig QUIET) -pkg_check_modules(PKG_Epoxy QUIET epoxy) - -set(Epoxy_DEFINITIONS "${PKG_Epoxy_CFLAGS}") - -find_path(Epoxy_INCLUDE_DIRS NAMES epoxy/gl.h HINTS ${PKG_Epoxy_INCLUDEDIR} ${PKG_Epoxy_INCLUDE_DIRS}) -find_library(Epoxy_LIBRARIES NAMES epoxy HINTS ${PKG_Epoxy_LIBDIR} ${PKG_Epoxy_LIBRARIES_DIRS}) -find_file(Epoxy_GLX_HEADER NAMES epoxy/glx.h HINTS ${Epoxy_INCLUDE_DIRS} DOC "whether GLX is available") - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Epoxy DEFAULT_MSG Epoxy_LIBRARIES Epoxy_INCLUDE_DIRS) - -mark_as_advanced(Epoxy_INCLUDE_DIRS Epoxy_LIBRARIES Epoxy_HAS_GLX) diff --git a/cmake/modules/FindGBM.cmake b/cmake/modules/FindGBM.cmake deleted file mode 100644 --- a/cmake/modules/FindGBM.cmake +++ /dev/null @@ -1,107 +0,0 @@ -#.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. - -#============================================================================= -# Copyright 2014 Alex Merry -# Copyright 2014 Martin Gräßlin -# -# 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_GBM QUIET gbm) - -set(GBM_DEFINITIONS "${PKG_GBM_CFLAGS_OTHER}") -set(GBM_VERSION "${PKG_GBM_VERSION}") - -find_path(GBM_INCLUDE_DIRS - NAMES - gbm.h - HINTS - ${PKG_GBM_INCLUDE_DIRS} -) -find_library(GBM_LIBRARIES - NAMES - gbm - HINTS - ${PKG_GBM_LIBRARIES_DIRS} -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(GBM - FOUND_VAR - GBM_FOUND - REQUIRED_VARS - GBM_LIBRARIES - GBM_INCLUDE_DIRS - 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_LIBRARIES}" - INTERFACE_COMPILE_OPTIONS "${GBM_DEFINITIONS}" - INTERFACE_INCLUDE_DIRECTORIES "${GBM_INCLUDE_DIRS}" - ) -endif() - -mark_as_advanced(GBM_LIBRARIES GBM_INCLUDE_DIRS) - -include(FeatureSummary) -set_package_properties(GBM PROPERTIES - URL "https://www.mesa3d.org" - DESCRIPTION "Mesa gbm library." -) diff --git a/cmake/modules/FindPipeWire.cmake b/cmake/modules/FindPipeWire.cmake deleted file mode 100644 --- 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/data/CMakeLists.txt b/data/CMakeLists.txt --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,10 +1,6 @@ configure_file(org.freedesktop.impl.portal.desktop.kde.desktop.in org.freedesktop.impl.portal.desktop.kde.desktop @ONLY) configure_file(org.freedesktop.impl.portal.desktop.kde.cmake.in org.freedesktop.impl.portal.desktop.kde.service @ONLY) +install(FILES kde.portal DESTINATION ${DATA_INSTALL_DIR}/xdg-desktop-portal/portals) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.impl.portal.desktop.kde.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}) -if(HAVE_PIPEWIRE_SUPPORT) - install(FILES kde.portal DESTINATION ${DATA_INSTALL_DIR}/xdg-desktop-portal/portals) -else() - install(FILES kde-no-pipewire.portal DESTINATION ${DATA_INSTALL_DIR}/xdg-desktop-portal/portals RENAME kde.portal) -endif() install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.impl.portal.desktop.kde.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/data/kde-no-pipewire.portal b/data/kde-no-pipewire.portal deleted file mode 100644 --- a/data/kde-no-pipewire.portal +++ /dev/null @@ -1,4 +0,0 @@ -[portal] -DBusName=org.freedesktop.impl.portal.desktop.kde -Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Account;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.Email;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Inhibit;org.freedesktop.impl.portal.Notification;org.freedesktop.impl.portal.Print;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Settings -UseIn=KDE diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,28 +24,20 @@ utils.cpp userinfodialog.cpp waylandintegration.cpp + screencast.cpp + screencastwidget.cpp + screenchooserdialog.cpp + remotedesktop.cpp + remotedesktopdialog.cpp ) -if(HAVE_PIPEWIRE_SUPPORT) - set (xdg_desktop_portal_kde_SRCS - ${xdg_desktop_portal_kde_SRCS} - screencast.cpp - screencaststream.cpp - screencastwidget.cpp - screenchooserdialog.cpp - remotedesktop.cpp - remotedesktopdialog.cpp) - - ki18n_wrap_ui(xdg_desktop_portal_kde_SRCS - screenchooserdialog.ui - remotedesktopdialog.ui) -endif() - ki18n_wrap_ui(xdg_desktop_portal_kde_SRCS accessdialog.ui appchooserdialog.ui screenshotdialog.ui userinfodialog.ui + screenchooserdialog.ui + remotedesktopdialog.ui ) set_source_files_properties(../data/org.freedesktop.Accounts.User.xml PROPERTIES NO_NAMESPACE TRUE) @@ -72,13 +64,6 @@ KF5::WindowSystem ) -if (HAVE_PIPEWIRE_SUPPORT) - target_link_libraries(xdg-desktop-portal-kde - PipeWire::PipeWire - ${Epoxy_LIBRARIES} - GBM::GBM) -endif() - install(TARGETS xdg-desktop-portal-kde DESTINATION ${KDE_INSTALL_LIBEXECDIR}) install(FILES @@ -88,4 +73,4 @@ install(FILES xdg-desktop-portal-kde.notifyrc - DESTINATION ${KNOTIFYRC_INSTALL_DIR}) \ No newline at end of file + DESTINATION ${KNOTIFYRC_INSTALL_DIR}) diff --git a/src/desktopportal.h b/src/desktopportal.h --- a/src/desktopportal.h +++ b/src/desktopportal.h @@ -37,10 +37,8 @@ #include "settings.h" #include "waylandintegration.h" -#if HAVE_PIPEWIRE_SUPPORT #include "screencast.h" #include "remotedesktop.h" -#endif class DesktopPortal : public QObject, public QDBusContext { @@ -61,11 +59,8 @@ PrintPortal *m_print; ScreenshotPortal *m_screenshot; SettingsPortal *m_settings; -#if HAVE_PIPEWIRE_SUPPORT ScreenCastPortal *m_screenCast; RemoteDesktopPortal *m_remoteDesktop; -#endif }; #endif // XDG_DESKTOP_PORTAL_KDE_DESKTOP_PORTAL_H - diff --git a/src/desktopportal.cpp b/src/desktopportal.cpp --- a/src/desktopportal.cpp +++ b/src/desktopportal.cpp @@ -39,10 +39,8 @@ const QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP").toUpper(); if (xdgCurrentDesktop == "KDE") { m_background = new BackgroundPortal(this); -#if HAVE_PIPEWIRE_SUPPORT m_screenCast = new ScreenCastPortal(this); m_remoteDesktop = new RemoteDesktopPortal(this); -#endif m_screenshot = new ScreenshotPortal(this); WaylandIntegration::init(); } diff --git a/src/remotedesktop.cpp b/src/remotedesktop.cpp --- a/src/remotedesktop.cpp +++ b/src/remotedesktop.cpp @@ -55,8 +55,8 @@ return 2; } - connect(session, &Session::closed, [this] () { - WaylandIntegration::stopStreaming(); + connect(session, &Session::closed, [] () { + WaylandIntegration::stopAllStreaming(); }); return 0; diff --git a/src/remotedesktopdialog.h b/src/remotedesktopdialog.h --- a/src/remotedesktopdialog.h +++ b/src/remotedesktopdialog.h @@ -22,6 +22,7 @@ #define XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_DIALOG_H #include +#include #include "remotedesktop.h" @@ -38,7 +39,7 @@ bool multiple = false, QDialog *parent = nullptr, Qt::WindowFlags flags = {}); ~RemoteDesktopDialog(); - QList selectedScreens() const; + QVector selectedScreens() const; RemoteDesktopPortal::DeviceTypes deviceTypes() const; private: diff --git a/src/remotedesktopdialog.cpp b/src/remotedesktopdialog.cpp --- a/src/remotedesktopdialog.cpp +++ b/src/remotedesktopdialog.cpp @@ -82,7 +82,7 @@ delete m_dialog; } -QList RemoteDesktopDialog::selectedScreens() const +QVector RemoteDesktopDialog::selectedScreens() const { return m_dialog->screenCastWidget->selectedScreens(); } diff --git a/src/screencast.h b/src/screencast.h --- a/src/screencast.h +++ b/src/screencast.h @@ -31,18 +31,28 @@ Q_CLASSINFO("D-Bus Interface", "org.freedesktop.impl.portal.ScreenCast") Q_PROPERTY(uint version READ version) Q_PROPERTY(uint AvailableSourceTypes READ AvailableSourceTypes) + Q_PROPERTY(uint AvailableCursorModes READ AvailableCursorModes) public: enum SourceType { Any = 0, Monitor, Window }; + Q_ENUM(SourceType); + + enum CursorModes { + Hidden = 1, + Embedded = 2, + Metadata = 4, + }; + Q_ENUM(CursorModes); explicit ScreenCastPortal(QObject *parent); ~ScreenCastPortal(); uint version() const { return 1; } uint AvailableSourceTypes() const { return Monitor; }; + uint AvailableCursorModes() const { return Hidden | Embedded; }; public Q_SLOTS: uint CreateSession(const QDBusObjectPath &handle, diff --git a/src/screencast.cpp b/src/screencast.cpp --- a/src/screencast.cpp +++ b/src/screencast.cpp @@ -1,4 +1,4 @@ -/* + /* * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -57,8 +57,8 @@ return 2; } - connect(session, &Session::closed, [this] () { - WaylandIntegration::stopStreaming(); + connect(session, &Session::closed, [] () { + WaylandIntegration::stopAllStreaming(); }); return 0; @@ -139,11 +139,6 @@ return 2; } - if (!WaylandIntegration::isEGLInitialized()) { - qCWarning(XdgDesktopPortalKdeScreenCast) << "EGL is not properly initialized"; - return 2; - } - QScopedPointer screenDialog(new ScreenChooserDialog(app_id, session->multipleSources())); Utils::setParentWindow(screenDialog.data(), parent_window); diff --git a/src/screencaststream.h b/src/screencaststream.h deleted file mode 100644 --- a/src/screencaststream.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright © 2018-2020 Red Hat, Inc - * - * This program 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 . - * - * Authors: - * Jan Grulich - */ - -#ifndef SCREEN_CAST_STREAM_H -#define SCREEN_CAST_STREAM_H - -#include "waylandintegration.h" - -#include -#include - -#include -#include -#include -#include - -#if PW_CHECK_VERSION(0, 2, 90) -#include -#endif - -#if !PW_CHECK_VERSION(0, 2, 90) -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 - -class ScreenCastStream : public QObject -{ - Q_OBJECT -public: - explicit ScreenCastStream(const QSize &resolution, QObject *parent = nullptr); - ~ScreenCastStream(); - -#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); - - // Public - void init(); - uint framerate(); - uint nodeId(); - - // Public because we need access from static functions - pw_stream *createStream(); - void removeStream(); - -public Q_SLOTS: - bool recordFrame(gbm_bo *bo, quint32 width, quint32 height, quint32 stride); - -Q_SIGNALS: - void streamReady(uint nodeId); - void startStreaming(); - void stopStreaming(); - -#if !PW_CHECK_VERSION(0, 2, 90) -private: - void initializePwTypes(); -#endif - -public: -#if PW_CHECK_VERSION(0, 2, 90) - struct pw_context *pwContext = nullptr; - struct pw_core *pwCore = nullptr; - struct pw_stream *pwStream = nullptr; - struct pw_thread_loop *pwMainLoop = nullptr; - - spa_hook coreListener; - spa_hook streamListener; - - // event handlers - pw_core_events pwCoreEvents = {}; - pw_stream_events pwStreamEvents = {}; - - uint32_t pwNodeId = 0; -#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 - - QSize resolution; - - spa_video_info_raw videoFormat; - -}; - -#endif // SCREEN_CAST_STREAM_H diff --git a/src/screencaststream.cpp b/src/screencaststream.cpp deleted file mode 100644 --- a/src/screencaststream.cpp +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Copyright © 2018-2020 Red Hat, Inc - * - * This program 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 . - * - * Authors: - * Jan Grulich - */ - -#include "screencaststream.h" - -#include -#include -#include -#include - -#include - -#include -#include - -Q_LOGGING_CATEGORY(XdgDesktopPortalKdeScreenCastStream, "xdp-kde-screencast-stream") - -class PwFraction { -public: - int num; - int denom; -}; - -// Stolen from mutter - -#define MAX_TERMS 30 -#define MIN_DIVISOR 1.0e-10 -#define MAX_ERROR 1.0e-20 - -#define PROP_RANGE(min, max) 2, (min), (max) - -#define BITS_PER_PIXEL 4 - -static int greatestCommonDivisor(int a, int b) -{ - while (b != 0) { - int temp = a; - - a = b; - b = temp % b; - } - - return qAbs(a); -} - -static PwFraction pipewireFractionFromDouble(double src) -{ - double V, F; /* double being converted */ - int N, D; /* will contain the result */ - int A; /* current term in continued fraction */ - int64_t N1, D1; /* numerator, denominator of last approx */ - int64_t N2, D2; /* numerator, denominator of previous approx */ - int i; - int gcd; - bool negative = false; - - /* initialize fraction being converted */ - F = src; - if (F < 0.0) { - F = -F; - negative = true; - } - - V = F; - /* initialize fractions with 1/0, 0/1 */ - N1 = 1; - D1 = 0; - N2 = 0; - D2 = 1; - N = 1; - D = 1; - - for (i = 0; i < MAX_TERMS; ++i) { - /* get next term */ - A = (int) F; /* no floor() needed, F is always >= 0 */ - /* get new divisor */ - F = F - A; - - /* calculate new fraction in temp */ - N2 = N1 * A + N2; - D2 = D1 * A + D2; - - /* guard against overflow */ - if (N2 > INT_MAX || D2 > INT_MAX) - break; - - N = N2; - D = D2; - - /* save last two fractions */ - N2 = N1; - D2 = D1; - N1 = N; - D1 = D; - - /* quit if dividing by zero or close enough to target */ - if (F < MIN_DIVISOR || fabs (V - ((double) N) / D) < MAX_ERROR) - break; - - /* Take reciprocal */ - F = 1 / F; - } - - /* fix for overflow */ - if (D == 0) { - N = INT_MAX; - D = 1; - } - - /* fix for negative */ - if (negative) - N = -N; - - /* simplify */ - gcd = greatestCommonDivisor(N, D); - if (gcd) { - N /= gcd; - D /= gcd; - } - - PwFraction fraction; - fraction.num = N; - fraction.denom = D; - - return fraction; -} - -#if PW_CHECK_VERSION(0, 2, 90) -void ScreenCastStream::onCoreError(void *data, uint32_t id, int seq, int res, const char *message) -{ - Q_UNUSED(seq) - ScreenCastStream *pw = static_cast(data); - - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "PipeWire remote error: " << message; - - if (id == PW_ID_CORE) { - if (res == -EPIPE) { - Q_EMIT pw->stopStreaming(); - } - } -} -#else -void ScreenCastStream::onStateChanged(void *data, pw_remote_state old, pw_remote_state state, const char *error) -{ - Q_UNUSED(old); - - ScreenCastStream *pw = static_cast(data); - - switch (state) { - case PW_REMOTE_STATE_ERROR: - // TODO notify error - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Remote error: " << error; - break; - case PW_REMOTE_STATE_CONNECTED: - // TODO notify error - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Remote state: " << pw_remote_state_as_string(state); - pw->pwStream = pw->createStream(); - if (!pw->pwStream) { - Q_EMIT pw->stopStreaming(); - } - break; - default: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Remote state: " << pw_remote_state_as_string(state); - break; - } -} -#endif - -void ScreenCastStream::onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message) -{ - Q_UNUSED(old) - - ScreenCastStream *pw = static_cast(data); - -#if PW_CHECK_VERSION(0, 2, 90) - switch (state) { - case PW_STREAM_STATE_ERROR: - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Stream error: " << error_message; - break; - case PW_STREAM_STATE_PAUSED: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - if (pw->nodeId() == 0 && pw->pwStream) { - pw->pwNodeId = pw_stream_get_node_id(pw->pwStream); - Q_EMIT pw->streamReady(pw->nodeId()); - } - if (WaylandIntegration::isStreamingEnabled()) { - Q_EMIT pw->stopStreaming(); - } - break; - case PW_STREAM_STATE_STREAMING: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - Q_EMIT pw->startStreaming(); - break; - case PW_STREAM_STATE_UNCONNECTED: - case PW_STREAM_STATE_CONNECTING: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - break; - } -#else - switch (state) { - case PW_STREAM_STATE_ERROR: - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Stream error: " << error_message; - break; - case PW_STREAM_STATE_CONFIGURE: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - Q_EMIT pw->streamReady((uint)pw_stream_get_node_id(pw->pwStream)); - break; - case PW_STREAM_STATE_UNCONNECTED: - case PW_STREAM_STATE_CONNECTING: - case PW_STREAM_STATE_READY: - case PW_STREAM_STATE_PAUSED: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - Q_EMIT pw->stopStreaming(); - break; - case PW_STREAM_STATE_STREAMING: - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream state: " << pw_stream_state_as_string(state); - Q_EMIT pw->startStreaming(); - break; - } -#endif -} - -#if PW_CHECK_VERSION(0, 2, 90) -void ScreenCastStream::onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format) -#else -void ScreenCastStream::onStreamFormatChanged(void *data, const struct spa_pod *format) -#endif -{ - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stream format changed"; - - ScreenCastStream *pw = static_cast(data); - - uint8_t paramsBuffer[1024]; - int32_t width, height, stride, size; - struct spa_pod_builder pod_builder; - const struct spa_pod *params[1]; - -#if PW_CHECK_VERSION(0, 2, 90) - if (!format || id != SPA_PARAM_Format) { -#else - if (!format) { - pw_stream_finish_format(pw->pwStream, 0, nullptr, 0); -#endif - return; - } - -#if PW_CHECK_VERSION(0, 2, 90) - spa_format_video_raw_parse (format, &pw->videoFormat); -#else - spa_format_video_raw_parse (format, &pw->videoFormat, &pw->pwType->format_video); -#endif - - width = pw->videoFormat.size.width; - height =pw->videoFormat.size.height; - stride = SPA_ROUND_UP_N (width * BITS_PER_PIXEL, 4); - size = height * stride; - - pod_builder = SPA_POD_BUILDER_INIT (paramsBuffer, sizeof (paramsBuffer)); - -#if PW_CHECK_VERSION(0, 2, 90) - params[0] = (spa_pod*) spa_pod_builder_add_object(&pod_builder, - SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 2, 16), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int (1), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), - SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(stride, stride, INT32_MAX), - SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); - pw_stream_update_params(pw->pwStream, params, 1); -#else - params[0] = (spa_pod*) spa_pod_builder_object(&pod_builder, - pw->pwCoreType->param.idBuffers, pw->pwCoreType->param_buffers.Buffers, - ":", pw->pwCoreType->param_buffers.size, "i", size, - ":", pw->pwCoreType->param_buffers.stride, "i", stride, - ":", pw->pwCoreType->param_buffers.buffers, "iru", 16, PROP_RANGE (2, 16), - ":", pw->pwCoreType->param_buffers.align, "i", 16); - pw_stream_finish_format (pw->pwStream, 0, params, 1); -#endif -} - -ScreenCastStream::ScreenCastStream(const QSize &resolution, QObject *parent) - : QObject(parent) - , resolution(resolution) -{ -#if PW_CHECK_VERSION(0, 2, 90) - pwCoreEvents.version = PW_VERSION_CORE_EVENTS; - pwCoreEvents.error = &onCoreError; - - pwStreamEvents.version = PW_VERSION_STREAM_EVENTS; - pwStreamEvents.state_changed = &onStreamStateChanged; - pwStreamEvents.param_changed = &onStreamParamChanged; -#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; -#endif -} - -ScreenCastStream::~ScreenCastStream() -{ - if (pwMainLoop) { - pw_thread_loop_stop(pwMainLoop); - } - -#if !PW_CHECK_VERSION(0, 2, 90) - 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); - } - - 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 -} - -void ScreenCastStream::init() -{ - pw_init(nullptr, nullptr); - - const auto emitFailureNotification = [](const QString &body) { - KNotification *notification = new KNotification(QStringLiteral("screencastfailure"), KNotification::CloseOnTimeout); - notification->setTitle(i18n("Failed to start screencasting")); - notification->setText(body); - notification->setIconName(QStringLiteral("dialog-error")); - notification->sendEvent(); - }; - -#if PW_CHECK_VERSION(0, 2, 90) - pwMainLoop = pw_thread_loop_new("pipewire-main-loop", nullptr); - pwContext = pw_context_new(pw_thread_loop_get_loop(pwMainLoop), nullptr, 0); - if (!pwContext) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to create PipeWire context"; - emitFailureNotification(i18n("Failed to create PipeWire context")); - return; - } - - pwCore = pw_context_connect(pwContext, nullptr, 0); - if (!pwCore) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to connect PipeWire context"; - emitFailureNotification(i18n("Failed to connect PipeWire context")); - return; - } - - pw_core_add_listener(pwCore, &coreListener, &pwCoreEvents, this); - - pwStream = createStream(); - if (!pwStream) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to create PipeWire stream"; - emitFailureNotification(i18n("Failed to create PipeWire stream")); - return; - } -#else - pwLoop = pw_loop_new(nullptr); - pwMainLoop = pw_thread_loop_new(pwLoop, "pipewire-main-loop"); - pwCore = pw_core_new(pwLoop, nullptr); - pwCoreType = pw_core_get_type(pwCore); - pwRemote = pw_remote_new(pwCore, nullptr, 0); - - initializePwTypes(); - - pw_remote_add_listener(pwRemote, &remoteListener, &pwRemoteEvents, this); - pw_remote_connect(pwRemote); -#endif - - - if (pw_thread_loop_start(pwMainLoop) < 0) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to start main PipeWire loop"; - emitFailureNotification(i18n("Failed to start main PipeWire loop")); - return; - } -} - -uint ScreenCastStream::framerate() -{ - if (pwStream) { - return videoFormat.max_framerate.num / videoFormat.max_framerate.denom; - } - - return 0; -} - -uint ScreenCastStream::nodeId() -{ -#if PW_CHECK_VERSION(0, 2, 90) - return pwNodeId; -#else - if (pwStream) { - return (uint)pw_stream_get_node_id(pwStream); - } - - return 0; -#endif -} - -pw_stream *ScreenCastStream::createStream() -{ -#if !PW_CHECK_VERSION(0, 2, 90) - if (pw_remote_get_state(pwRemote, nullptr) != PW_REMOTE_STATE_CONNECTED) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Cannot create pipewire stream"; - return nullptr; - } -#endif - - uint8_t buffer[1024]; - spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - - const float frameRate = 25; - - spa_fraction maxFramerate; - spa_fraction minFramerate; - const spa_pod *params[1]; - -#if PW_CHECK_VERSION(0, 2, 90) - auto stream = pw_stream_new(pwCore, "kwin-screen-cast", nullptr); -#else - auto stream = pw_stream_new(pwRemote, "kwin-screen-cast", nullptr); -#endif - - PwFraction fraction = pipewireFractionFromDouble(frameRate); - - minFramerate = SPA_FRACTION(1, 1); - maxFramerate = SPA_FRACTION((uint32_t)fraction.num, (uint32_t)fraction.denom); - - spa_rectangle minResolution = SPA_RECTANGLE(1, 1); - spa_rectangle maxResolution = SPA_RECTANGLE((uint32_t)resolution.width(), (uint32_t)resolution.height()); - - spa_fraction paramFraction = SPA_FRACTION(0, 1); - -#if PW_CHECK_VERSION(0, 2, 90) - params[0] = (spa_pod*)spa_pod_builder_add_object(&podBuilder, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBx), - SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&maxResolution, &minResolution, &maxResolution), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(¶mFraction), - SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&maxFramerate, &minFramerate, &maxFramerate)); -#else - params[0] = (spa_pod*)spa_pod_builder_object(&podBuilder, - 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", &maxResolution, SPA_POD_PROP_MIN_MAX(&minResolution, &maxResolution), - ":", pwType->format_video.framerate, "F", ¶mFraction, - ":", pwType->format_video.max_framerate, "Fru", &maxFramerate, PROP_RANGE (&minFramerate, &maxFramerate)); -#endif - - pw_stream_add_listener(stream, &streamListener, &pwStreamEvents, this); - - auto flags = static_cast(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS); - -#if PW_CHECK_VERSION(0, 2, 90) - if (pw_stream_connect(stream, PW_DIRECTION_OUTPUT, SPA_ID_INVALID, flags, params, 1) != 0) { -#else - if (pw_stream_connect(stream, PW_DIRECTION_OUTPUT, nullptr, flags, params, 1) != 0) { -#endif - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Could not connect to stream"; - return nullptr; - } - - return stream; -} - -bool ScreenCastStream::recordFrame(gbm_bo *bo, quint32 width, quint32 height, quint32 stride) -{ - struct pw_buffer *buffer; - struct spa_buffer *spa_buffer; - uint8_t *data = nullptr; - - if (!(buffer = pw_stream_dequeue_buffer(pwStream))) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: couldn't obtain PipeWire buffer"; - return false; - } - - spa_buffer = buffer->buffer; - - if (!(data = (uint8_t *) spa_buffer->datas[0].data)) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: invalid buffer data"; - pw_stream_queue_buffer(pwStream, buffer); - return false; - } - - quint32 streamStride; - const quint32 minStride = SPA_ROUND_UP_N(videoFormat.size.width * BITS_PER_PIXEL, 4); - const quint32 minSrcSize = height * minStride; - - const quint32 srcSize = height * stride; - const quint32 destSize = spa_buffer->datas[0].maxsize; - - // If we can fit source into the pipewire buffer, we can use stride we got from gbm_bo as the client - // should be able to handle it - if (srcSize <= destSize) { - streamStride = stride; - // Fallback to fixed minimum stride, which should be (width * bpp) - } else if (minSrcSize == destSize) { - streamStride = minStride; - } else { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: got buffer with higher stride than we can handle"; - pw_stream_queue_buffer(pwStream, buffer); - return false; - } - - // bind context to render thread - eglMakeCurrent(WaylandIntegration::egl().display, EGL_NO_SURFACE, EGL_NO_SURFACE, WaylandIntegration::egl().context); - - // create EGL image from imported BO - EGLImageKHR image = eglCreateImageKHR(WaylandIntegration::egl().display, nullptr, EGL_NATIVE_PIXMAP_KHR, bo, nullptr); - - if (image == EGL_NO_IMAGE_KHR) { - qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to record frame: Error creating EGLImageKHR - " << WaylandIntegration::formatGLError(glGetError()); - pw_stream_queue_buffer(pwStream, buffer); - return false; - } - - // 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); - - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - - glDeleteTextures(1, &texture); - eglDestroyImageKHR(WaylandIntegration::egl().display, image); - - spa_buffer->datas[0].chunk->offset = 0; - spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize; - spa_buffer->datas[0].chunk->stride = streamStride; - - pw_stream_queue_buffer(pwStream, buffer); - return true; -} - -void ScreenCastStream::removeStream() -{ - // FIXME destroying streams seems to be crashing, Mutter also doesn't remove them, maybe Pipewire does this automatically - // pw_stream_destroy(pwStream); - // pwStream = nullptr; - pw_stream_disconnect(pwStream); -} - -#if !PW_CHECK_VERSION(0, 2, 90) -void ScreenCastStream::initializePwTypes() -{ - // raw C-like ScreenCastStream type map - auto 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 diff --git a/src/screencastwidget.h b/src/screencastwidget.h --- a/src/screencastwidget.h +++ b/src/screencastwidget.h @@ -22,15 +22,16 @@ #define XDG_DESKTOP_PORTAL_KDE_SCREENCAST_WIDGET_H #include +#include class ScreenCastWidget : public QListWidget { Q_OBJECT public: ScreenCastWidget(QWidget *parent = nullptr); ~ScreenCastWidget(); - QList selectedScreens() const; + QVector selectedScreens() const; }; #endif // XDG_DESKTOP_PORTAL_KDE_SCREENCAST_WIDGET_H diff --git a/src/screencastwidget.cpp b/src/screencastwidget.cpp --- a/src/screencastwidget.cpp +++ b/src/screencastwidget.cpp @@ -26,37 +26,28 @@ ScreenCastWidget::ScreenCastWidget(QWidget *parent) : QListWidget(parent) { - QMapIterator it(WaylandIntegration::screens()); + QVectorIterator it(WaylandIntegration::screencastingSources()); while (it.hasNext()) { - it.next(); + const auto current = it.next(); QListWidgetItem *widgetItem = new QListWidgetItem(this); - widgetItem->setData(Qt::UserRole, it.key()); - if (it.value().outputType() == WaylandIntegration::WaylandOutput::Laptop) { - widgetItem->setIcon(QIcon::fromTheme(QStringLiteral("computer-laptop"))); - widgetItem->setText(i18n("Laptop screen\nModel: %1", it.value().model())); - } else if (it.value().outputType() == WaylandIntegration::WaylandOutput::Monitor) { - widgetItem->setIcon(QIcon::fromTheme(QStringLiteral("video-display"))); - widgetItem->setText(i18n("Manufacturer: %1\nModel: %2", it.value().manufacturer(), it.value().model())); - } else { - widgetItem->setIcon(QIcon::fromTheme(QStringLiteral("video-television"))); - widgetItem->setText(i18n("Manufacturer: %1\nModel: %2", it.value().manufacturer(), it.value().model())); - } + widgetItem->setData(Qt::UserRole, QVariant::fromValue(current)); + widgetItem->setIcon(QIcon::fromTheme(current.iconName())); + widgetItem->setText(current.description()); } + Q_ASSERT(count() > 0); itemAt(0, 0)->setSelected(true); } ScreenCastWidget::~ScreenCastWidget() { } -QList ScreenCastWidget::selectedScreens() const +QVector ScreenCastWidget::selectedScreens() const { - QList selectedScreens; - + QVector selectedScreens; for (QListWidgetItem *item : selectedItems()) { - selectedScreens << item->data(Qt::UserRole).toUInt(); + selectedScreens << item->data(Qt::UserRole).value(); } - return selectedScreens; } diff --git a/src/screenchooserdialog.h b/src/screenchooserdialog.h --- a/src/screenchooserdialog.h +++ b/src/screenchooserdialog.h @@ -22,6 +22,7 @@ #define XDG_DESKTOP_PORTAL_KDE_SCREENCHOOSER_DIALOG_H #include +#include namespace Ui { @@ -35,7 +36,7 @@ ScreenChooserDialog(const QString &appName, bool multiple, QDialog *parent = nullptr, Qt::WindowFlags flags = {}); ~ScreenChooserDialog(); - QList selectedScreens() const; + QVector selectedScreens() const; private: Ui::ScreenChooserDialog *m_dialog; diff --git a/src/screenchooserdialog.cpp b/src/screenchooserdialog.cpp --- a/src/screenchooserdialog.cpp +++ b/src/screenchooserdialog.cpp @@ -71,7 +71,7 @@ delete m_dialog; } -QList ScreenChooserDialog::selectedScreens() const +QVector ScreenChooserDialog::selectedScreens() const { return m_dialog->screenView->selectedScreens(); } diff --git a/src/waylandintegration.h b/src/waylandintegration.h --- a/src/waylandintegration.h +++ b/src/waylandintegration.h @@ -26,26 +26,15 @@ #include #include -#if HAVE_PIPEWIRE_SUPPORT -#include -#include -#include -#endif - namespace KWayland { namespace Client { class PlasmaWindowManagement; + class ScreencastingSource; } } namespace WaylandIntegration { -#if HAVE_PIPEWIRE_SUPPORT -struct EGLStruct { - QList extensions; - EGLDisplay display = EGL_NO_DISPLAY; - EGLContext context = EGL_NO_CONTEXT; -}; class WaylandOutput { @@ -87,29 +76,22 @@ int m_waylandOutputName; int m_waylandOutputVersion; }; -#endif class WaylandIntegration : public QObject { Q_OBJECT Q_SIGNALS: -#if HAVE_PIPEWIRE_SUPPORT void newBuffer(uint8_t *screenData); -#endif void plasmaWindowManagementInitialized(); }; -#if HAVE_PIPEWIRE_SUPPORT - const char * formatGLError(GLenum err); - void authenticate(); - bool isEGLInitialized(); bool isStreamingEnabled(); void startStreamingInput(); - bool startStreaming(quint32 outputName); - void stopStreaming(); + bool startStreaming(const KWayland::Client::ScreencastingSource &source); + void stopAllStreaming(); void requestPointerButtonPress(quint32 linuxButton); void requestPointerButtonRelease(quint32 linuxButton); @@ -119,10 +101,10 @@ void requestKeyboardKeycode(int keycode, bool state); - EGLStruct egl(); QMap screens(); + QVector screencastingSources(); QVariant streams(); -#endif + void init(); KWayland::Client::PlasmaWindowManagement *plasmaWindowManagement(); diff --git a/src/waylandintegration.cpp b/src/waylandintegration.cpp --- a/src/waylandintegration.cpp +++ b/src/waylandintegration.cpp @@ -21,14 +21,14 @@ #include "waylandintegration.h" #include "waylandintegration_p.h" - #include #include #include #include #include #include +#include #include @@ -44,37 +44,24 @@ #include #include -#if HAVE_PIPEWIRE_SUPPORT -#include "screencaststream.h" - #include #include -#include -#endif +#include Q_LOGGING_CATEGORY(XdgDesktopPortalKdeWaylandIntegration, "xdp-kde-wayland-integration") Q_GLOBAL_STATIC(WaylandIntegration::WaylandIntegrationPrivate, globalWaylandIntegration) void WaylandIntegration::init() { -#if HAVE_PIPEWIRE_SUPPORT - globalWaylandIntegration->initDrm(); -#endif globalWaylandIntegration->initWayland(); } -#if HAVE_PIPEWIRE_SUPPORT void WaylandIntegration::authenticate() { globalWaylandIntegration->authenticate(); } -bool WaylandIntegration::isEGLInitialized() -{ - return globalWaylandIntegration->isEGLInitialized(); -} - bool WaylandIntegration::isStreamingEnabled() { return globalWaylandIntegration->isStreamingEnabled(); @@ -85,14 +72,14 @@ globalWaylandIntegration->startStreamingInput(); } -bool WaylandIntegration::startStreaming(quint32 outputName) +bool WaylandIntegration::startStreaming(const KWayland::Client::ScreencastingSource &source) { - return globalWaylandIntegration->startStreaming(outputName); + return globalWaylandIntegration->startStreaming(source); } -void WaylandIntegration::stopStreaming() +void WaylandIntegration::stopAllStreaming() { - globalWaylandIntegration->stopStreaming(); + globalWaylandIntegration->stopAllStreaming(); } void WaylandIntegration::requestPointerButtonPress(quint32 linuxButton) @@ -125,11 +112,6 @@ globalWaylandIntegration->requestKeyboardKeycode(keycode, state); } -WaylandIntegration::EGLStruct WaylandIntegration::egl() -{ - return globalWaylandIntegration->egl(); -} - QMap WaylandIntegration::screens() { return globalWaylandIntegration->screens(); @@ -140,28 +122,6 @@ return globalWaylandIntegration->streams(); } -const char * WaylandIntegration::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(); - } -} - // Thank you kscreen void WaylandIntegration::WaylandOutput::setOutputType(const QString &type) { @@ -171,7 +131,7 @@ QLatin1String("LCD") }; for (const QLatin1String &pre : embedded) { - if (type.toUpper().startsWith(pre)) { + if (type.startsWith(pre, Qt::CaseInsensitive)) { m_outputType = OutputType::Laptop; return; } @@ -219,7 +179,6 @@ Q_DECLARE_METATYPE(WaylandIntegration::WaylandIntegrationPrivate::Stream) Q_DECLARE_METATYPE(WaylandIntegration::WaylandIntegrationPrivate::Streams) -#endif KWayland::Client::PlasmaWindowManagement * WaylandIntegration::plasmaWindowManagement() { @@ -237,136 +196,99 @@ , m_connection(nullptr) , m_queue(nullptr) , m_registry(nullptr) -#if HAVE_PIPEWIRE_SUPPORT , m_fakeInput(nullptr) - , m_remoteAccessManager(nullptr) -#endif + , m_screencasting(nullptr) { -#if HAVE_PIPEWIRE_SUPPORT qDBusRegisterMetaType(); qDBusRegisterMetaType(); -#endif } -WaylandIntegration::WaylandIntegrationPrivate::~WaylandIntegrationPrivate() -{ -#if HAVE_PIPEWIRE_SUPPORT - if (m_remoteAccessManager) { - m_remoteAccessManager->destroy(); - } - - if (m_gbmDevice) { - gbm_device_destroy(m_gbmDevice); - } -#endif -} - -#if HAVE_PIPEWIRE_SUPPORT -bool WaylandIntegration::WaylandIntegrationPrivate::isEGLInitialized() const -{ - return m_eglInitialized; -} +WaylandIntegration::WaylandIntegrationPrivate::~WaylandIntegrationPrivate() = default; bool WaylandIntegration::WaylandIntegrationPrivate::isStreamingEnabled() const { - return m_streamingEnabled; -} - -void WaylandIntegration::WaylandIntegrationPrivate::bindOutput(int outputName, int outputVersion) -{ - KWayland::Client::Output *output = new KWayland::Client::Output(this); - output->setup(m_registry->bindOutput(outputName, outputVersion)); - m_bindOutputs << output; + return m_streams.isEmpty(); } void WaylandIntegration::WaylandIntegrationPrivate::startStreamingInput() { m_streamInput = true; } -bool WaylandIntegration::WaylandIntegrationPrivate::startStreaming(quint32 outputName) +QVector WaylandIntegration::screencastingSources() { - WaylandOutput output = m_outputMap.value(outputName); - m_streamedScreenPosition = output.globalPosition(); + return globalWaylandIntegration->videoStreamingSources(); +} - m_stream = new ScreenCastStream(output.resolution()); - m_stream->init(); +bool WaylandIntegration::WaylandIntegrationPrivate::startStreaming(const KWayland::Client::ScreencastingSource &source) +{ + m_streamedScreenPosition = source.geometry().topLeft(); - connect(m_stream, &ScreenCastStream::startStreaming, this, [this, output] { - m_streamingEnabled = true; - startStreamingInput(); - bindOutput(output.waylandOutputName(), output.waylandOutputVersion()); - }); + m_screencasting->create(source); + QEventLoop loop; + bool streamReady = false; + connect(m_screencasting, &KWayland::Client::Screencasting::failed, this, [&] (const KWayland::Client::ScreencastingSource &newSource, const QString &error) { + if (source != newSource) { + return; + } + qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "failed to start streaming" << source.description() << error; - connect(m_stream, &ScreenCastStream::stopStreaming, this, &WaylandIntegrationPrivate::stopStreaming); + KNotification *notification = new KNotification(QStringLiteral("screencastfailure"), KNotification::CloseOnTimeout); + notification->setTitle(i18n("Failed to start screencasting")); + notification->setText(error); + notification->setIconName(QStringLiteral("dialog-error")); + notification->sendEvent(); - bool streamReady = false; - QEventLoop loop; - connect(m_stream, &ScreenCastStream::streamReady, this, [&loop, &streamReady] { + streamReady = false; loop.quit(); - streamReady = true; }); + connect(m_screencasting, &KWayland::Client::Screencasting::created, this, [&] (const KWayland::Client::ScreencastingSource& newSource, uint32_t nodeid) { + if (source != newSource) { + return; + } + m_streams.append({nodeid, {{QLatin1String("size"), source.size()}}}); + startStreamingInput(); - // HACK wait for stream to be ready + streamReady = true; + loop.quit(); + }); QTimer::singleShot(3000, &loop, &QEventLoop::quit); loop.exec(); - disconnect(m_stream, &ScreenCastStream::streamReady, this, nullptr); + connect(m_screencasting, &KWayland::Client::Screencasting::closed, this, &WaylandIntegrationPrivate::stopStreaming); - if (!streamReady) { - if (m_stream) { - delete m_stream; - m_stream = nullptr; - } - return false; - } + disconnect(m_screencasting, &KWayland::Client::Screencasting::created, this, nullptr); - // TODO support multiple outputs + return streamReady; +} - if (m_registry->hasInterface(KWayland::Client::Registry::Interface::RemoteAccessManager)) { - KWayland::Client::Registry::AnnouncedInterface interface = m_registry->interface(KWayland::Client::Registry::Interface::RemoteAccessManager); - if (!interface.name && !interface.version) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to start streaming: remote access manager interface is not initialized yet"; - return false; - } - m_remoteAccessManager = m_registry->createRemoteAccessManager(interface.name, interface.version); - connect(m_remoteAccessManager, &KWayland::Client::RemoteAccessManager::bufferReady, this, [this] (const void *output, const KWayland::Client::RemoteBuffer * rbuf) { - Q_UNUSED(output); - connect(rbuf, &KWayland::Client::RemoteBuffer::parametersObtained, this, [this, rbuf] { - processBuffer(rbuf); - }); - }); - m_output = output.waylandOutputName(); - return true; - } +void WaylandIntegration::WaylandIntegrationPrivate::Stream::close() +{ + globalWaylandIntegration->m_screencasting->close(nodeId); +} - if (m_stream) { - delete m_stream; - m_stream = nullptr; +void WaylandIntegration::WaylandIntegrationPrivate::stopAllStreaming() +{ + for (auto & stream : m_streams) { + stream.close(); } + m_streams.clear(); - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to start streaming: no remote access manager interface"; - return false; + m_streamInput = false; + // First unbound outputs and destroy remote access manager so we no longer receive buffers } -void WaylandIntegration::WaylandIntegrationPrivate::stopStreaming() +void WaylandIntegration::WaylandIntegrationPrivate::stopStreaming(uint32_t nodeid) { - m_streamInput = false; - if (m_streamingEnabled) { - m_streamingEnabled = false; - - // First unbound outputs and destroy remote access manager so we no longer receive buffers - if (m_remoteAccessManager) { - m_remoteAccessManager->release(); - m_remoteAccessManager->destroy(); + for(auto it = m_streams.begin(), itEnd = m_streams.end(); it != itEnd; ++it) { + if (it->nodeId == nodeid) { + m_streams.erase(it); + break; } - qDeleteAll(m_bindOutputs); - m_bindOutputs.clear(); + } - if (m_stream) { - delete m_stream; - m_stream = nullptr; - } + if (m_streams.isEmpty()) { + stopAllStreaming(); } } @@ -416,93 +338,19 @@ } } -WaylandIntegration::EGLStruct WaylandIntegration::WaylandIntegrationPrivate::egl() -{ - return m_egl; -} - QMap WaylandIntegration::WaylandIntegrationPrivate::screens() { return m_outputMap; } QVariant WaylandIntegration::WaylandIntegrationPrivate::streams() { - Stream stream; - stream.nodeId = m_stream->nodeId(); - stream.map = QVariantMap({{QLatin1String("size"), m_outputMap.value(m_output).resolution()}}); - return QVariant::fromValue({stream}); + return QVariant::fromValue(m_streams); } -void WaylandIntegration::WaylandIntegrationPrivate::initDrm() +QVector WaylandIntegration::WaylandIntegrationPrivate::videoStreamingSources() { - m_drmFd = open("/dev/dri/renderD128", O_RDWR); - - if (m_drmFd == -1) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Cannot open render node: " << strerror(errno); - return; - } - - m_gbmDevice = gbm_create_device(m_drmFd); - - if (!m_gbmDevice) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Cannot create GBM device: " << strerror(errno); - } - - initEGL(); -} - -void WaylandIntegration::WaylandIntegrationPrivate::initEGL() -{ - // 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(XdgDesktopPortalKdeWaylandIntegration) << "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(XdgDesktopPortalKdeWaylandIntegration) << "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(XdgDesktopPortalKdeWaylandIntegration) << "Error during obtaining EGL display: " << formatGLError(eglGetError()); - return; - } - - EGLint major, minor; - if (eglInitialize(m_egl.display, &major, &minor) == EGL_FALSE) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Error during eglInitialize: " << formatGLError(eglGetError()); - return; - } - - if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "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(XdgDesktopPortalKdeWaylandIntegration) << "Couldn't create EGL context: " << formatGLError(eglGetError()); - return; - } - - qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Egl initialization succeeded"; - qCDebug(XdgDesktopPortalKdeWaylandIntegration) << QStringLiteral("EGL version: %1.%2").arg(major).arg(minor); - - m_eglInitialized = true; + return m_screencasting->sources(); } void WaylandIntegration::WaylandIntegrationPrivate::authenticate() @@ -513,8 +361,6 @@ } } -#endif - KWayland::Client::PlasmaWindowManagement * WaylandIntegration::WaylandIntegrationPrivate::plasmaWindowManagement() { return m_windowManagement; @@ -555,7 +401,6 @@ m_connection->initConnection(); } -#if HAVE_PIPEWIRE_SUPPORT void WaylandIntegration::WaylandIntegrationPrivate::addOutput(quint32 name, quint32 version) { KWayland::Client::Output *output = new KWayland::Client::Output(this); @@ -590,69 +435,23 @@ qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " model: " << output.model(); } -void WaylandIntegration::WaylandIntegrationPrivate::processBuffer(const KWayland::Client::RemoteBuffer* rbuf) -{ - QScopedPointer guard(rbuf); - - auto gbmHandle = rbuf->fd(); - auto width = rbuf->width(); - auto height = rbuf->height(); - auto stride = rbuf->stride(); - auto format = rbuf->format(); - - qCDebug(XdgDesktopPortalKdeWaylandIntegration) << QStringLiteral("Incoming GBM fd %1, %2x%3, stride %4, fourcc 0x%5").arg(gbmHandle).arg(width).arg(height).arg(stride).arg(QString::number(format, 16)); - - if (!m_streamingEnabled) { - qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Streaming is disabled"; - close(gbmHandle); - return; - } - - if (m_lastFrameTime.isValid() && - m_lastFrameTime.msecsTo(QDateTime::currentDateTime()) < (1000 / m_stream->framerate())) { - close(gbmHandle); - return; - } - - if (!gbm_device_is_format_supported(m_gbmDevice, format, GBM_BO_USE_SCANOUT)) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: GBM format is not supported by device!"; - close(gbmHandle); - return; - } - - // import GBM buffer that was passed from KWin - gbm_import_fd_data importInfo = {gbmHandle, width, height, stride, format}; - gbm_bo *imported = gbm_bo_import(m_gbmDevice, GBM_BO_IMPORT_FD, &importInfo, GBM_BO_USE_SCANOUT); - if (!imported) { - qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: Cannot import passed GBM fd - " << strerror(errno); - close(gbmHandle); - return; - } - - if (m_stream->recordFrame(imported, width, height, stride)) { - m_lastFrameTime = QDateTime::currentDateTime(); - } - - gbm_bo_destroy(imported); - close(gbmHandle); -} -#endif - void WaylandIntegration::WaylandIntegrationPrivate::setupRegistry() { m_queue = new KWayland::Client::EventQueue(this); m_queue->setup(m_connection); m_registry = new KWayland::Client::Registry(this); -#if HAVE_PIPEWIRE_SUPPORT connect(m_registry, &KWayland::Client::Registry::fakeInputAnnounced, this, [this] (quint32 name, quint32 version) { m_fakeInput = m_registry->createFakeInput(name, version, this); }); connect(m_registry, &KWayland::Client::Registry::outputAnnounced, this, &WaylandIntegrationPrivate::addOutput); connect(m_registry, &KWayland::Client::Registry::outputRemoved, this, &WaylandIntegrationPrivate::removeOutput); -#endif + connect(m_registry, &KWayland::Client::Registry::screencastingAnnounced, this, [this] (quint32 name, quint32 version) { + qDebug() << "screen!"; + m_screencasting = m_registry->createScreencasting(name, version, this); + }); connect(m_registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, this, [this] (quint32 name, quint32 version) { m_windowManagement = m_registry->createPlasmaWindowManagement(name, version, this); Q_EMIT waylandIntegration()->plasmaWindowManagementInitialized(); diff --git a/src/waylandintegration_p.h b/src/waylandintegration_p.h --- a/src/waylandintegration_p.h +++ b/src/waylandintegration_p.h @@ -25,24 +25,19 @@ #include #include - -#if HAVE_PIPEWIRE_SUPPORT -class ScreenCastStream; -#endif +#include namespace KWayland { namespace Client { class ConnectionThread; class EventQueue; class Registry; class PlasmaWindow; class PlasmaWindowManagement; -#if HAVE_PIPEWIRE_SUPPORT class FakeInput; class RemoteBuffer; class Output; - class RemoteAccessManager; -#endif + class Screencasting; } } @@ -72,66 +67,54 @@ KWayland::Client::Registry *m_registry = nullptr; KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr; -#if HAVE_PIPEWIRE_SUPPORT public: - typedef struct { + struct Stream { uint nodeId; QVariantMap map; - } Stream; - typedef QList Streams; - void authenticate(); + void close(); + }; + typedef QVector Streams; - void initDrm(); - void initEGL(); + void authenticate(); - bool isEGLInitialized() const; bool isStreamingEnabled() const; - void bindOutput(int outputName, int outputVersion); void startStreamingInput(); - bool startStreaming(quint32 outputName); - void stopStreaming(); + + bool startStreaming(const KWayland::Client::ScreencastingSource &source); + void stopStreaming(uint32_t nodeid); + void stopAllStreaming(); void requestPointerButtonPress(quint32 linuxButton); void requestPointerButtonRelease(quint32 linuxButton); void requestPointerMotion(const QSizeF &delta); void requestPointerMotionAbsolute(const QPointF &pos); void requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta); void requestKeyboardKeycode(int keycode, bool state); - EGLStruct egl(); + QVector videoStreamingSources(); QMap screens(); QVariant streams(); protected Q_SLOTS: void addOutput(quint32 name, quint32 version); void removeOutput(quint32 name); - void processBuffer(const KWayland::Client::RemoteBuffer *rbuf); private: - bool m_eglInitialized = false; - bool m_streamingEnabled = false; bool m_streamInput = false; bool m_waylandAuthenticationRequested = false; quint32 m_output; QDateTime m_lastFrameTime; - ScreenCastStream *m_stream = nullptr; + QVector m_streams; QPoint m_streamedScreenPosition; QMap m_outputMap; - QList m_bindOutputs; KWayland::Client::FakeInput *m_fakeInput = nullptr; - KWayland::Client::RemoteAccessManager *m_remoteAccessManager = nullptr; - - qint32 m_drmFd = 0; // for GBM buffer mmap - gbm_device *m_gbmDevice = nullptr; // for passed GBM buffer retrieval - - EGLStruct m_egl; -#endif + KWayland::Client::Screencasting *m_screencasting = nullptr; }; }