diff --git a/CMakeLists.txt b/CMakeLists.txt index 20ea1e3..5bed1e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,86 +1,88 @@ cmake_minimum_required(VERSION 3.0) project(xdg-desktop-portal-kde) set(PROJECT_VERSION "5.18.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.14.0") set(KF5_MIN_VERSION "5.66.0") ################# set KDE specific information ################# find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDEClangFormat) include(FeatureSummary) -find_package(PipeWire) -set_package_properties(PipeWire PROPERTIES - TYPE OPTIONAL - PURPOSE "Required for screencast portal" -) - -find_package(GBM) -set_package_properties(GBM PROPERTIES - TYPE OPTIONAL - PURPOSE "Required for screencast portal" -) - -find_package(Epoxy) -set_package_properties(Epoxy PROPERTIES DESCRIPTION "libepoxy" - URL "https://github.com/anholt/libepoxy" - TYPE OPTIONAL - PURPOSE "Required for screencast portal" -) - -if (PipeWire_FOUND AND GBM_FOUND AND Epoxy_FOUND) - set (SCREENCAST_ENABLED true) +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() - set (SCREENCAST_ENABLED false) + 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() -add_definitions(-DSCREENCAST_ENABLED=${SCREENCAST_ENABLED}) - -add_feature_info ("Screencast portal" ${SCREENCAST_ENABLED} "Support for screen sharing") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Concurrent DBus PrintSupport QuickWidgets Widgets ) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED CoreAddons Config I18n Declarative KIO Kirigami2 Notifications Plasma Wayland WidgetsAddons WindowSystem ) if (EXISTS "${CMAKE_SOURCE_DIR}/.git") add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054200) endif() add_subdirectory(data) add_subdirectory(src) # add clang-format target for all our real source files file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index cc80053..f7ef514 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,6 +1,10 @@ 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 ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.impl.portal.desktop.kde.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}) -install(FILES kde.portal DESTINATION ${DATA_INSTALL_DIR}/xdg-desktop-portal/portals) +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 new file mode 100644 index 0000000..69a4db6 --- /dev/null +++ b/data/kde-no-pipewire.portal @@ -0,0 +1,4 @@ +[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 index cca3404..e59330d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,91 +1,91 @@ add_definitions(-DTRANSLATION_DOMAIN="xdg-desktop-portal-kde") include_directories(${Qt5PrintSupport_PRIVATE_INCLUDE_DIRS}) set(xdg_desktop_portal_kde_SRCS xdg-desktop-portal-kde.cpp access.cpp accessdialog.cpp account.cpp appchooser.cpp appchooserdialog.cpp background.cpp desktopportal.cpp email.cpp filechooser.cpp inhibit.cpp notification.cpp print.cpp request.cpp session.cpp screenshot.cpp screenshotdialog.cpp settings.cpp utils.cpp userinfodialog.cpp + waylandintegration.cpp ) -if (SCREENCAST_ENABLED) +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 - waylandintegration.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 ) set_source_files_properties(../data/org.freedesktop.Accounts.User.xml PROPERTIES NO_NAMESPACE TRUE) qt5_add_dbus_interface(xdg_desktop_portal_kde_SRCS ../data/org.freedesktop.Accounts.User.xml user_interface) add_executable(xdg-desktop-portal-kde ${xdg_desktop_portal_kde_SRCS}) target_link_libraries(xdg-desktop-portal-kde Qt5::Core Qt5::DBus Qt5::Concurrent Qt5::PrintSupport Qt5::QuickWidgets Qt5::Widgets KF5::CoreAddons KF5::ConfigCore KF5::Declarative KF5::I18n KF5::KIOFileWidgets KF5::Notifications KF5::WaylandClient KF5::WidgetsAddons KF5::WindowSystem ) -if (SCREENCAST_ENABLED) +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 qml/AppChooserDialog.qml qml/UserInfoDialog.qml DESTINATION ${KDE_INSTALL_DATADIR}/xdg-desktop-portal-kde/qml) install(FILES xdg-desktop-portal-kde.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) \ No newline at end of file diff --git a/src/desktopportal.cpp b/src/desktopportal.cpp index 2bd751e..e8ec94d 100644 --- a/src/desktopportal.cpp +++ b/src/desktopportal.cpp @@ -1,53 +1,53 @@ /* * Copyright © 2016 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 "desktopportal.h" #include Q_LOGGING_CATEGORY(XdgDesktopPortalKdeDesktopPortal, "xdp-kde-desktop-portal") DesktopPortal::DesktopPortal(QObject *parent) : QObject(parent) , m_access(new AccessPortal(this)) , m_account(new AccountPortal(this)) , m_appChooser(new AppChooserPortal(this)) , m_email(new EmailPortal(this)) , m_fileChooser(new FileChooserPortal(this)) , m_inhibit(new InhibitPortal(this)) , m_notification(new NotificationPortal(this)) , m_print(new PrintPortal(this)) , m_settings(new SettingsPortal(this)) { const QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP").toUpper(); if (xdgCurrentDesktop == "KDE") { m_background = new BackgroundPortal(this); - m_screenshot = new ScreenshotPortal(this); -#if SCREENCAST_ENABLED +#if HAVE_PIPEWIRE_SUPPORT m_screenCast = new ScreenCastPortal(this); m_remoteDesktop = new RemoteDesktopPortal(this); #endif + m_screenshot = new ScreenshotPortal(this); WaylandIntegration::init(); } } DesktopPortal::~DesktopPortal() { } diff --git a/src/desktopportal.h b/src/desktopportal.h index 49da246..b832558 100644 --- a/src/desktopportal.h +++ b/src/desktopportal.h @@ -1,70 +1,71 @@ /* * Copyright © 2016 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 XDG_DESKTOP_PORTAL_KDE_DESKTOP_PORTAL_H #define XDG_DESKTOP_PORTAL_KDE_DESKTOP_PORTAL_H #include #include #include "access.h" #include "account.h" #include "appchooser.h" #include "background.h" #include "email.h" #include "filechooser.h" #include "inhibit.h" #include "notification.h" #include "print.h" -#if SCREENCAST_ENABLED -#include "screencast.h" -#include "remotedesktop.h" -#endif #include "screenshot.h" #include "settings.h" #include "waylandintegration.h" +#if HAVE_PIPEWIRE_SUPPORT +#include "screencast.h" +#include "remotedesktop.h" +#endif + class DesktopPortal : public QObject, public QDBusContext { Q_OBJECT public: explicit DesktopPortal(QObject *parent = nullptr); ~DesktopPortal(); private: AccessPortal *m_access; AccountPortal *m_account; AppChooserPortal *m_appChooser; BackgroundPortal *m_background; EmailPortal *m_email; FileChooserPortal *m_fileChooser; InhibitPortal *m_inhibit; NotificationPortal *m_notification; PrintPortal *m_print; -#if SCREENCAST_ENABLED + ScreenshotPortal *m_screenshot; + SettingsPortal *m_settings; +#if HAVE_PIPEWIRE_SUPPORT ScreenCastPortal *m_screenCast; RemoteDesktopPortal *m_remoteDesktop; #endif - ScreenshotPortal *m_screenshot; - SettingsPortal *m_settings; }; #endif // XDG_DESKTOP_PORTAL_KDE_DESKTOP_PORTAL_H diff --git a/src/waylandintegration.cpp b/src/waylandintegration.cpp index 928bdd2..e06f83b 100644 --- a/src/waylandintegration.cpp +++ b/src/waylandintegration.cpp @@ -1,652 +1,669 @@ /* * Copyright © 2018 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 "waylandintegration.h" #include "waylandintegration_p.h" -#include "screencaststream.h" + #include #include #include #include #include #include #include #include // KWayland #include #include -#include #include -#include -#include #include // system #include #include +#if HAVE_PIPEWIRE_SUPPORT +#include "screencaststream.h" + +#include +#include +#include +#endif + Q_LOGGING_CATEGORY(XdgDesktopPortalKdeWaylandIntegration, "xdp-kde-wayland-integration") Q_GLOBAL_STATIC(WaylandIntegration::WaylandIntegrationPrivate, globalWaylandIntegration) -void WaylandIntegration::authenticate() -{ - globalWaylandIntegration->authenticate(); -} - void WaylandIntegration::init() { -#if SCREENCAST_ENABLED +#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(); } void WaylandIntegration::startStreamingInput() { globalWaylandIntegration->startStreamingInput(); } bool WaylandIntegration::startStreaming(quint32 outputName) { return globalWaylandIntegration->startStreaming(outputName); } void WaylandIntegration::stopStreaming() { globalWaylandIntegration->stopStreaming(); } void WaylandIntegration::requestPointerButtonPress(quint32 linuxButton) { globalWaylandIntegration->requestPointerButtonPress(linuxButton); } void WaylandIntegration::requestPointerButtonRelease(quint32 linuxButton) { globalWaylandIntegration->requestPointerButtonRelease(linuxButton); } void WaylandIntegration::requestPointerMotion(const QSizeF &delta) { globalWaylandIntegration->requestPointerMotion(delta); } void WaylandIntegration::requestPointerMotionAbsolute(const QPointF &pos) { globalWaylandIntegration->requestPointerMotionAbsolute(pos); } void WaylandIntegration::requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta) { globalWaylandIntegration->requestPointerAxisDiscrete(axis, delta); } void WaylandIntegration::requestKeyboardKeycode(int keycode, bool state) { globalWaylandIntegration->requestKeyboardKeycode(keycode, state); } WaylandIntegration::EGLStruct WaylandIntegration::egl() { return globalWaylandIntegration->egl(); } -KWayland::Client::PlasmaWindowManagement * WaylandIntegration::plasmaWindowManagement() -{ - return globalWaylandIntegration->plasmaWindowManagement(); -} - QMap WaylandIntegration::screens() { return globalWaylandIntegration->screens(); } QVariant WaylandIntegration::streams() { return globalWaylandIntegration->streams(); } -WaylandIntegration::WaylandIntegration * WaylandIntegration::waylandIntegration() -{ - return globalWaylandIntegration; -} - 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) { const auto embedded = { QLatin1String("LVDS"), QLatin1String("IDP"), QLatin1String("EDP"), QLatin1String("LCD") }; for (const QLatin1String &pre : embedded) { if (type.toUpper().startsWith(pre)) { m_outputType = OutputType::Laptop; return; } } if (type.contains(QLatin1String("VGA")) || type.contains(QLatin1String("DVI")) || type.contains(QLatin1String("HDMI")) || type.contains(QLatin1String("Panel")) || type.contains(QLatin1String("DisplayPort")) || type.startsWith(QLatin1String("DP")) || type.contains(QLatin1String("unknown"))) { m_outputType = OutputType::Monitor; } else if (type.contains(QLatin1String("TV"))) { m_outputType = OutputType::Television; } else { m_outputType = OutputType::Monitor; } } const QDBusArgument &operator >> (const QDBusArgument &arg, WaylandIntegration::WaylandIntegrationPrivate::Stream &stream) { arg.beginStructure(); arg >> stream.nodeId; arg.beginMap(); while (!arg.atEnd()) { QString key; QVariant map; arg.beginMapEntry(); arg >> key >> map; arg.endMapEntry(); stream.map.insert(key, map); } arg.endMap(); arg.endStructure(); return arg; } const QDBusArgument &operator << (QDBusArgument &arg, const WaylandIntegration::WaylandIntegrationPrivate::Stream &stream) { arg.beginStructure(); arg << stream.nodeId; arg << stream.map; arg.endStructure(); return arg; } Q_DECLARE_METATYPE(WaylandIntegration::WaylandIntegrationPrivate::Stream) Q_DECLARE_METATYPE(WaylandIntegration::WaylandIntegrationPrivate::Streams) +#endif + +KWayland::Client::PlasmaWindowManagement * WaylandIntegration::plasmaWindowManagement() +{ + return globalWaylandIntegration->plasmaWindowManagement(); +} + +WaylandIntegration::WaylandIntegration * WaylandIntegration::waylandIntegration() +{ + return globalWaylandIntegration; +} WaylandIntegration::WaylandIntegrationPrivate::WaylandIntegrationPrivate() : WaylandIntegration() - , m_eglInitialized(false) , m_registryInitialized(false) - , m_waylandAuthenticationRequested(false) , m_connection(nullptr) , m_queue(nullptr) - , m_fakeInput(nullptr) , m_registry(nullptr) +#if HAVE_PIPEWIRE_SUPPORT + , m_fakeInput(nullptr) , m_remoteAccessManager(nullptr) +#endif { +#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; } 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; } void WaylandIntegration::WaylandIntegrationPrivate::startStreamingInput() { m_streamInput = true; } bool WaylandIntegration::WaylandIntegrationPrivate::startStreaming(quint32 outputName) { WaylandOutput output = m_outputMap.value(outputName); m_streamedScreenPosition = output.globalPosition(); m_stream = new ScreenCastStream(output.resolution()); m_stream->init(); connect(m_stream, &ScreenCastStream::startStreaming, this, [this, output] { m_streamingEnabled = true; startStreamingInput(); bindOutput(output.waylandOutputName(), output.waylandOutputVersion()); }); connect(m_stream, &ScreenCastStream::stopStreaming, this, &WaylandIntegrationPrivate::stopStreaming); bool streamReady = false; QEventLoop loop; connect(m_stream, &ScreenCastStream::streamReady, this, [&loop, &streamReady] { loop.quit(); streamReady = true; }); // HACK wait for stream to be ready QTimer::singleShot(3000, &loop, &QEventLoop::quit); loop.exec(); disconnect(m_stream, &ScreenCastStream::streamReady, this, nullptr); if (!streamReady) { if (m_stream) { delete m_stream; m_stream = nullptr; } return false; } // TODO support multiple outputs 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; } if (m_stream) { delete m_stream; m_stream = nullptr; } qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to start streaming: no remote access manager interface"; return false; } void WaylandIntegration::WaylandIntegrationPrivate::stopStreaming() { 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(); } qDeleteAll(m_bindOutputs); m_bindOutputs.clear(); if (m_stream) { delete m_stream; m_stream = nullptr; } } } void WaylandIntegration::WaylandIntegrationPrivate::requestPointerButtonPress(quint32 linuxButton) { if (m_streamInput && m_fakeInput) { m_fakeInput->requestPointerButtonPress(linuxButton); } } void WaylandIntegration::WaylandIntegrationPrivate::requestPointerButtonRelease(quint32 linuxButton) { if (m_streamInput && m_fakeInput) { m_fakeInput->requestPointerButtonRelease(linuxButton); } } void WaylandIntegration::WaylandIntegrationPrivate::requestPointerMotion(const QSizeF &delta) { if (m_streamInput && m_fakeInput) { m_fakeInput->requestPointerMove(delta); } } void WaylandIntegration::WaylandIntegrationPrivate::requestPointerMotionAbsolute(const QPointF &pos) { if (m_streamInput && m_fakeInput) { m_fakeInput->requestPointerMoveAbsolute(pos + m_streamedScreenPosition); } } void WaylandIntegration::WaylandIntegrationPrivate::requestPointerAxisDiscrete(Qt::Orientation axis, qreal delta) { if (m_streamInput && m_fakeInput) { m_fakeInput->requestPointerAxis(axis, delta); } } void WaylandIntegration::WaylandIntegrationPrivate::requestKeyboardKeycode(int keycode, bool state) { if (m_streamInput && m_fakeInput) { if (state) { m_fakeInput->requestKeyboardKeyPress(keycode); } else { m_fakeInput->requestKeyboardKeyRelease(keycode); } } } WaylandIntegration::EGLStruct WaylandIntegration::WaylandIntegrationPrivate::egl() { return m_egl; } QMap WaylandIntegration::WaylandIntegrationPrivate::screens() { return m_outputMap; } -KWayland::Client::PlasmaWindowManagement * WaylandIntegration::WaylandIntegrationPrivate::plasmaWindowManagement() -{ - return m_windowManagement; -} - 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}); } -void WaylandIntegration::WaylandIntegrationPrivate::authenticate() -{ - if (!m_waylandAuthenticationRequested) { - m_fakeInput->authenticate(i18n("xdg-desktop-portals-kde"), i18n("Remote desktop")); - m_waylandAuthenticationRequested = true; - } -} - void WaylandIntegration::WaylandIntegrationPrivate::initDrm() { 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; } +void WaylandIntegration::WaylandIntegrationPrivate::authenticate() +{ + if (!m_waylandAuthenticationRequested) { + m_fakeInput->authenticate(i18n("xdg-desktop-portals-kde"), i18n("Remote desktop")); + m_waylandAuthenticationRequested = true; + } +} + +#endif + +KWayland::Client::PlasmaWindowManagement * WaylandIntegration::WaylandIntegrationPrivate::plasmaWindowManagement() +{ + return m_windowManagement; +} + void WaylandIntegration::WaylandIntegrationPrivate::initWayland() { m_thread = new QThread(this); m_connection = new KWayland::Client::ConnectionThread; connect(m_connection, &KWayland::Client::ConnectionThread::connected, this, &WaylandIntegrationPrivate::setupRegistry, Qt::QueuedConnection); connect(m_connection, &KWayland::Client::ConnectionThread::connectionDied, this, [this] { if (m_queue) { delete m_queue; m_queue = nullptr; } m_connection->deleteLater(); m_connection = nullptr; if (m_thread) { m_thread->quit(); if (!m_thread->wait(3000)) { m_thread->terminate(); m_thread->wait(); } delete m_thread; m_thread = nullptr; } }); connect(m_connection, &KWayland::Client::ConnectionThread::failed, this, [this] { m_thread->quit(); m_thread->wait(); }); m_thread->start(); m_connection->moveToThread(m_thread); m_connection->initConnection(); } +#if HAVE_PIPEWIRE_SUPPORT void WaylandIntegration::WaylandIntegrationPrivate::addOutput(quint32 name, quint32 version) { KWayland::Client::Output *output = new KWayland::Client::Output(this); output->setup(m_registry->bindOutput(name, version)); connect(output, &KWayland::Client::Output::changed, this, [this, name, version, output] () { qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Adding output:"; qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " manufacturer: " << output->manufacturer(); qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " model: " << output->model(); qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " resolution: " << output->pixelSize(); WaylandOutput portalOutput; portalOutput.setManufacturer(output->manufacturer()); portalOutput.setModel(output->model()); portalOutput.setOutputType(output->model()); portalOutput.setGlobalPosition(output->globalPosition()); portalOutput.setResolution(output->pixelSize()); portalOutput.setWaylandOutputName(name); portalOutput.setWaylandOutputVersion(version); m_outputMap.insert(name, portalOutput); delete output; }); } void WaylandIntegration::WaylandIntegrationPrivate::removeOutput(quint32 name) { WaylandOutput output = m_outputMap.take(name); qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Removing output:"; qCDebug(XdgDesktopPortalKdeWaylandIntegration) << " manufacturer: " << output.manufacturer(); 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 SCREENCAST_ENABLED +#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::plasmaWindowManagementAnnounced, this, [this] (quint32 name, quint32 version) { m_windowManagement = m_registry->createPlasmaWindowManagement(name, version, this); Q_EMIT waylandIntegration()->plasmaWindowManagementInitialized(); }); connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced, this, [this] { m_registryInitialized = true; qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "Registry initialized"; }); m_registry->create(m_connection); m_registry->setEventQueue(m_queue); m_registry->setup(); } diff --git a/src/waylandintegration.h b/src/waylandintegration.h index a6da42a..4888aee 100644 --- a/src/waylandintegration.h +++ b/src/waylandintegration.h @@ -1,128 +1,135 @@ /* * Copyright © 2018 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 XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_H #define XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_H #include #include #include #include +#if HAVE_PIPEWIRE_SUPPORT #include - #include #include +#endif namespace KWayland { namespace Client { class PlasmaWindowManagement; } } namespace WaylandIntegration { - +#if HAVE_PIPEWIRE_SUPPORT struct EGLStruct { QList extensions; EGLDisplay display = EGL_NO_DISPLAY; EGLContext context = EGL_NO_CONTEXT; }; class WaylandOutput { public: enum OutputType { Laptop, Monitor, Television }; void setManufacturer(const QString &manufacturer) { m_manufacturer = manufacturer; } QString manufacturer() const { return m_manufacturer; } void setModel(const QString &model) { m_model = model; } QString model() const { return m_model; } void setGlobalPosition(const QPoint &pos) { m_globalPosition = pos; } QPoint globalPosition() const { return m_globalPosition; } void setResolution(const QSize &resolution) { m_resolution = resolution; } QSize resolution() const { return m_resolution; } void setOutputType(const QString &type); OutputType outputType() const { return m_outputType; } void setWaylandOutputName(int outputName) { m_waylandOutputName = outputName; } int waylandOutputName() const { return m_waylandOutputName; } void setWaylandOutputVersion(int outputVersion) { m_waylandOutputVersion = outputVersion; } int waylandOutputVersion() const { return m_waylandOutputVersion; } private: QString m_manufacturer; QString m_model; QPoint m_globalPosition; QSize m_resolution; OutputType m_outputType; // Needed for later output binding 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(); - void init(); bool isEGLInitialized(); bool isStreamingEnabled(); void startStreamingInput(); bool startStreaming(quint32 outputName); void stopStreaming(); 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(); - - KWayland::Client::PlasmaWindowManagement *plasmaWindowManagement(); QMap screens(); QVariant streams(); +#endif + void init(); + + KWayland::Client::PlasmaWindowManagement *plasmaWindowManagement(); WaylandIntegration *waylandIntegration(); } #endif // XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_H diff --git a/src/waylandintegration_p.h b/src/waylandintegration_p.h index 50f21e4..8b434a0 100644 --- a/src/waylandintegration_p.h +++ b/src/waylandintegration_p.h @@ -1,127 +1,141 @@ /* * Copyright © 2018 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 XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_P_H #define XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_P_H #include "waylandintegration.h" #include #include +#if HAVE_PIPEWIRE_SUPPORT class ScreenCastStream; +#endif namespace KWayland { namespace Client { class ConnectionThread; class EventQueue; - class FakeInput; class Registry; - class RemoteAccessManager; - class RemoteBuffer; - class Output; class PlasmaWindow; class PlasmaWindowManagement; +#if HAVE_PIPEWIRE_SUPPORT + class FakeInput; + class RemoteBuffer; + class Output; + class RemoteAccessManager; +#endif } } namespace WaylandIntegration { class WaylandIntegrationPrivate : public WaylandIntegration::WaylandIntegration { Q_OBJECT +public: + WaylandIntegrationPrivate(); + ~WaylandIntegrationPrivate(); + + void initWayland(); + + KWayland::Client::PlasmaWindowManagement *plasmaWindowManagement(); + +protected Q_SLOTS: + void setupRegistry(); + +private: + bool m_registryInitialized = false; + + QThread *m_thread = nullptr; + KWayland::Client::ConnectionThread *m_connection = nullptr; + KWayland::Client::EventQueue *m_queue = nullptr; + KWayland::Client::Registry *m_registry = nullptr; + KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr; + +#if HAVE_PIPEWIRE_SUPPORT public: typedef struct { uint nodeId; QVariantMap map; } Stream; typedef QList Streams; - WaylandIntegrationPrivate(); - ~WaylandIntegrationPrivate(); - void authenticate(); + void initDrm(); void initEGL(); - void initWayland(); bool isEGLInitialized() const; bool isStreamingEnabled() const; void bindOutput(int outputName, int outputVersion); void startStreamingInput(); bool startStreaming(quint32 outputName); void stopStreaming(); 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(); - KWayland::Client::PlasmaWindowManagement *plasmaWindowManagement(); QMap screens(); QVariant streams(); protected Q_SLOTS: void addOutput(quint32 name, quint32 version); void removeOutput(quint32 name); void processBuffer(const KWayland::Client::RemoteBuffer *rbuf); - void setupRegistry(); private: bool m_eglInitialized = false; bool m_streamingEnabled = false; bool m_streamInput = false; - bool m_registryInitialized = false; bool m_waylandAuthenticationRequested = false; quint32 m_output; QDateTime m_lastFrameTime; ScreenCastStream *m_stream = nullptr; - QThread *m_thread = nullptr; - QPoint m_streamedScreenPosition; QMap m_outputMap; QList m_bindOutputs; - KWayland::Client::ConnectionThread *m_connection = nullptr; - KWayland::Client::EventQueue *m_queue = nullptr; KWayland::Client::FakeInput *m_fakeInput = nullptr; - KWayland::Client::Registry *m_registry = nullptr; KWayland::Client::RemoteAccessManager *m_remoteAccessManager = nullptr; - KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr; qint32 m_drmFd = 0; // for GBM buffer mmap gbm_device *m_gbmDevice = nullptr; // for passed GBM buffer retrieval EGLStruct m_egl; +#endif }; } #endif // XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_P_H