diff --git a/data/kde.portal b/data/kde.portal index 2036d64..cc2dac4 100644 --- a/data/kde.portal +++ b/data/kde.portal @@ -1,4 +1,4 @@ [portal] DBusName=org.freedesktop.impl.portal.desktop.kde -Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;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.ScreenCast;org.freedesktop.impl.portal.Screenshot +Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;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.ScreenCast;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.RemoteDesktop UseIn=KDE diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 084b148..a08ead7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,63 +1,66 @@ 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 appchooser.cpp appchooserdialog.cpp appchooserdialogitem.cpp desktopportal.cpp email.cpp filechooser.cpp inhibit.cpp notification.cpp print.cpp request.cpp session.cpp screenshot.cpp screenshotdialog.cpp ) if (SCREENCAST_ENABLED) set (xdg_desktop_portal_kde_SRCS ${xdg_desktop_portal_kde_SRCS} screencast.cpp screencaststream.cpp - screenchooserdialog.cpp) + screenchooserdialog.cpp + remotedesktop.cpp + remotedesktopdialog.cpp) ki18n_wrap_ui(xdg_desktop_portal_kde_SRCS - screenchooserdialog.ui) + screenchooserdialog.ui + remotedesktopdialog.ui) endif() ki18n_wrap_ui(xdg_desktop_portal_kde_SRCS accessdialog.ui screenshotdialog.ui ) 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::Widgets KF5::CoreAddons KF5::I18n KF5::Notifications KF5::WaylandClient KF5::WidgetsAddons ) if (SCREENCAST_ENABLED) target_link_libraries(xdg-desktop-portal-kde PipeWire::PipeWire GLIB2::GLIB2 ${Epoxy_LIBRARIES} GBM::GBM) endif() install(TARGETS xdg-desktop-portal-kde DESTINATION ${KDE_INSTALL_LIBEXECDIR}) diff --git a/src/desktopportal.cpp b/src/desktopportal.cpp index 388e6ff..8c9aa1e 100644 --- a/src/desktopportal.cpp +++ b/src/desktopportal.cpp @@ -1,49 +1,50 @@ /* * 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 #include #include #include #include Q_LOGGING_CATEGORY(XdgDesktopPortalKdeDesktopPortal, "xdp-kde-desktop-portal") DesktopPortal::DesktopPortal(QObject *parent) : QObject(parent) , m_access(new AccessPortal(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)) #if SCREENCAST_ENABLED , m_screenCast(new ScreenCastPortal(this)) + , m_remoteDesktop(new RemoteDesktopPortal(this)) #endif , m_screenshot(new ScreenshotPortal(this)) { } DesktopPortal::~DesktopPortal() { } diff --git a/src/desktopportal.h b/src/desktopportal.h index 3b77777..d65d0a1 100644 --- a/src/desktopportal.h +++ b/src/desktopportal.h @@ -1,61 +1,63 @@ /* * 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 "appchooser.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" class DesktopPortal : public QObject { Q_OBJECT public: explicit DesktopPortal(QObject *parent = nullptr); ~DesktopPortal(); private: AccessPortal *m_access; AppChooserPortal *m_appChooser; EmailPortal *m_email; FileChooserPortal *m_fileChooser; InhibitPortal *m_inhibit; NotificationPortal *m_notification; PrintPortal *m_print; #if SCREENCAST_ENABLED ScreenCastPortal *m_screenCast; + RemoteDesktopPortal *m_remoteDesktop; #endif ScreenshotPortal *m_screenshot; }; #endif // XDG_DESKTOP_PORTAL_KDE_DESKTOP_PORTAL_H diff --git a/src/remotedesktop.cpp b/src/remotedesktop.cpp new file mode 100644 index 0000000..2cd04b3 --- /dev/null +++ b/src/remotedesktop.cpp @@ -0,0 +1,186 @@ +/* + * 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 "remotedesktop.h" +#include "session.h" + +#include +#include +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(XdgDesktopPortalKdeRemoteDesktop, "xdp-kde-remotedesktop") + +RemoteDesktopPortal::RemoteDesktopPortal(QObject *parent) + : QDBusAbstractAdaptor(parent) +{ +} + +RemoteDesktopPortal::~RemoteDesktopPortal() +{ +} + +uint RemoteDesktopPortal::CreateSession(const QDBusObjectPath &handle, + const QDBusObjectPath &session_handle, + const QString &app_id, + const QVariantMap &options, + QVariantMap &results) +{ + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "CreateSession called with parameters:"; + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " handle: " << handle.path(); + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " session_handle: " << session_handle.path(); + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " app_id: " << app_id; + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " options: " << options; + + Session *session = Session::createSession(this, Session::RemoteDesktop, app_id, session_handle.path()); + + if (!session) { + return 2; + } + + connect(session, &Session::closed, [this] () { + // TODO + }); + + return 0; +} + +uint RemoteDesktopPortal::SelectDevices(const QDBusObjectPath &handle, + const QDBusObjectPath &session_handle, + const QString &app_id, + const QVariantMap &options, + QVariantMap &results) +{ + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "SelectDevices called with parameters:"; + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " handle: " << handle.path(); + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " session_handle: " << session_handle.path(); + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " app_id: " << app_id; + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " options: " << options; + + RemoteDesktopPortal::DeviceTypes types = RemoteDesktopPortal::All; + + RemoteDesktopSession *session = qobject_cast(Session::getSession(session_handle.path())); + + if (!session) { + qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to select sources on non-existing session " << session_handle.path(); + return 2; + } + + if (options.contains(QLatin1String("types"))) { + types = (DeviceTypes)(options.value(QLatin1String("types")).toUInt()); + } + session->setDeviceTypes(types); + + return 0; +} + +uint RemoteDesktopPortal::Start(const QDBusObjectPath &handle, + const QDBusObjectPath &session_handle, + const QString &app_id, + const QString &parent_window, + const QVariantMap &options, + QVariantMap &results) +{ + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << "Start called with parameters:"; + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " handle: " << handle.path(); + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " session_handle: " << session_handle.path(); + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " app_id: " << app_id; + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " parent_window: " << parent_window; + qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " options: " << options; + + return 0; +} + +void RemoteDesktopPortal::NotifyPointerMotion(const QDBusObjectPath &session_handle, + const QVariantMap &options, + double dx, + double dy) +{ +} + +void RemoteDesktopPortal::NotifyPointerMotionAbsolute(const QDBusObjectPath &session_handle, + const QVariantMap &options, + uint stream, + double dx, + double dy) +{ +} + +void RemoteDesktopPortal::NotifyPointerButton(const QDBusObjectPath &session_handle, + const QVariantMap &options, + int button, + uint state) +{ +} + +void RemoteDesktopPortal::NotifyPointerAxis(const QDBusObjectPath &session_handle, + const QVariantMap &options, + double dx, + double dy) +{ +} + +void RemoteDesktopPortal::NotifyPointerAxisDiscrete(const QDBusObjectPath &session_handle, + const QVariantMap &options, + uint axis, + int steps) +{ +} + +void RemoteDesktopPortal::NotifyKeyboardKeysym(const QDBusObjectPath &session_handle, + const QVariantMap &options, + int keysym, + uint state) +{ +} + +void RemoteDesktopPortal::NotifyKeyboardKeycode(const QDBusObjectPath &session_handle, + const QVariantMap &options, + int keycode, + uint state) +{ +} + +void RemoteDesktopPortal::NotifyTouchDown(const QDBusObjectPath &session_handle, + const QVariantMap &options, + uint stream, + uint slot, + int x, + int y) +{ +} + +void RemoteDesktopPortal::NotifyTouchMotion(const QDBusObjectPath &session_handle, + const QVariantMap &options, + uint stream, + uint slot, + int x, + int y) +{ +} + +void RemoteDesktopPortal::NotifyTouchUp(const QDBusObjectPath &session_handle, + const QVariantMap &options, + uint slot) +{ +} diff --git a/src/remotedesktop.h b/src/remotedesktop.h new file mode 100644 index 0000000..7be33b0 --- /dev/null +++ b/src/remotedesktop.h @@ -0,0 +1,129 @@ +/* + * 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_REMOTEDESKTOP_H +#define XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_H + +#include +#include + +class Session; + +class RemoteDesktopPortal : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.impl.portal.RemoteDesktop") +public: + explicit RemoteDesktopPortal(QObject *parent); + ~RemoteDesktopPortal(); + + enum DeviceType { + None = 0x0, + Keyboard = 0x1, + Pointer = 0x2, + TouchScreen = 0x4, + All = (Keyboard | Pointer | TouchScreen) + }; + Q_DECLARE_FLAGS(DeviceTypes, DeviceType) + + uint version() const { return 1; } + uint AvailableDeviceTypes() const { return All; }; + +public Q_SLOTS: + uint CreateSession(const QDBusObjectPath &handle, + const QDBusObjectPath &session_handle, + const QString &app_id, + const QVariantMap &options, + QVariantMap &results); + + uint SelectDevices(const QDBusObjectPath &handle, + const QDBusObjectPath &session_handle, + const QString &app_id, + const QVariantMap &options, + QVariantMap &results); + + uint Start(const QDBusObjectPath &handle, + const QDBusObjectPath &session_handle, + const QString &app_id, + const QString &parent_window, + const QVariantMap &options, + QVariantMap &results); + + void NotifyPointerMotion(const QDBusObjectPath &session_handle, + const QVariantMap &options, + double dx, + double dy); + + void NotifyPointerMotionAbsolute(const QDBusObjectPath &session_handle, + const QVariantMap &options, + uint stream, + double dx, + double dy); + + void NotifyPointerButton(const QDBusObjectPath &session_handle, + const QVariantMap &options, + int button, + uint state); + + void NotifyPointerAxis(const QDBusObjectPath &session_handle, + const QVariantMap &options, + double dx, + double dy); + + void NotifyPointerAxisDiscrete(const QDBusObjectPath &session_handle, + const QVariantMap &options, + uint axis, + int steps); + + void NotifyKeyboardKeycode(const QDBusObjectPath &session_handle, + const QVariantMap &options, + int keycode, + uint state); + + void NotifyKeyboardKeysym(const QDBusObjectPath &session_handle, + const QVariantMap &options, + int keysym, + uint state); + + void NotifyTouchDown(const QDBusObjectPath &session_handle, + const QVariantMap &options, + uint stream, + uint slot, + int x, + int y); + + void NotifyTouchMotion(const QDBusObjectPath &session_handle, + const QVariantMap &options, + uint stream, + uint slot, + int x, + int y); + + void NotifyTouchUp(const QDBusObjectPath &session_handle, + const QVariantMap &options, + uint slot); + +private: + + +}; + +#endif // XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_H + diff --git a/src/screencast.cpp b/src/screencast.cpp index 0c92997..87a4aec 100644 --- a/src/screencast.cpp +++ b/src/screencast.cpp @@ -1,579 +1,571 @@ /* * 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 "screencast.h" #include "session.h" #include "screencaststream.h" #include "screenchooserdialog.h" #include #include #include #include #include #include #include #include // KWayland #include #include #include #include #include // system #include #include Q_LOGGING_CATEGORY(XdgDesktopPortalKdeScreenCast, "xdp-kde-screencast") Q_DECLARE_METATYPE(ScreenCastPortal::Stream); Q_DECLARE_METATYPE(ScreenCastPortal::Streams); const QDBusArgument &operator >> (const QDBusArgument &arg, ScreenCastPortal::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 ScreenCastPortal::Stream &stream) { arg.beginStructure(); arg << stream.nodeId; arg << stream.map; arg.endStructure(); return arg; } static const char * formatGLError(GLenum err) { switch(err) { case GL_NO_ERROR: return "GL_NO_ERROR"; case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; case GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW"; case GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW"; case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; default: return (QLatin1String("0x") + QString::number(err, 16)).toLocal8Bit().constData(); } } // Thank you kscreen void ScreenCastPortalOutput::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)) { outputType = OutputType::Laptop; return; } } if (type.contains("VGA") || type.contains("DVI") || type.contains("HDMI") || type.contains("Panel") || type.contains("DisplayPort") || type.startsWith("DP") || type.contains("unknown")) { outputType = OutputType::Monitor; } else if (type.contains("TV")) { outputType = OutputType::Television; } else { outputType = OutputType::Monitor; } } ScreenCastPortal::ScreenCastPortal(QObject *parent) : QDBusAbstractAdaptor(parent) , m_registryInitialized(false) , m_streamingEnabled(false) , m_connection(nullptr) , m_queue(nullptr) , m_registry(nullptr) , m_remoteAccessManager(nullptr) { initDrm(); initEGL(); initWayland(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); } ScreenCastPortal::~ScreenCastPortal() { if (m_remoteAccessManager) { m_remoteAccessManager->destroy(); } if (m_drmFd) { gbm_device_destroy(m_gbmDevice); } m_stream->deleteLater(); } void ScreenCastPortal::createPipeWireStream(const QSize &resolution) { m_stream = new ScreenCastStream(resolution); m_stream->init(); connect(m_stream, &ScreenCastStream::streamReady, this, [] (uint nodeId) { qCDebug(XdgDesktopPortalKdeScreenCast) << "Pipewire stream is ready: " << nodeId; }); connect(m_stream, &ScreenCastStream::startStreaming, this, [this] { qCDebug(XdgDesktopPortalKdeScreenCast) << "Start streaming"; m_streamingEnabled = true; if (!m_registryInitialized) { qCWarning(XdgDesktopPortalKdeScreenCast) << "Cannot start stream because registry is not initialized yet"; return; } 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(XdgDesktopPortalKdeScreenCast) << "Cannot start stream because remote access interface is not initialized yet"; return; } 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); }); }); } }); connect(m_stream, &ScreenCastStream::stopStreaming, this, &ScreenCastPortal::stopStreaming); } void ScreenCastPortal::initDrm() { m_drmFd = open("/dev/dri/renderD128", O_RDWR); m_gbmDevice = gbm_create_device(m_drmFd); if (!m_gbmDevice) { qFatal("Cannot create GBM device: %s", strerror(errno)); } } void ScreenCastPortal::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. qFatal("No client extensions defined! %s", formatGLError(eglGetError())); } 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"))) { qFatal("One of required EGL extensions is missing"); } m_egl.display = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, m_gbmDevice, nullptr); if (m_egl.display == EGL_NO_DISPLAY) { qFatal("Error during obtaining EGL display: %s", formatGLError(eglGetError())); } EGLint major, minor; if (eglInitialize(m_egl.display, &major, &minor) == EGL_FALSE) { qFatal("Error during eglInitialize: %s", formatGLError(eglGetError())); } if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { qFatal("bind OpenGL API failed"); } m_egl.context = eglCreateContext(m_egl.display, nullptr, EGL_NO_CONTEXT, nullptr); if (m_egl.context == EGL_NO_CONTEXT) { qFatal("Couldn't create EGL context: %s", formatGLError(eglGetError())); } qCDebug(XdgDesktopPortalKdeScreenCast) << "Egl initialization succeeded"; qCDebug(XdgDesktopPortalKdeScreenCast) << QString("EGL version: %1.%2").arg(major).arg(minor); } void ScreenCastPortal::initWayland() { m_thread = new QThread(this); m_connection = new KWayland::Client::ConnectionThread; connect(m_connection, &KWayland::Client::ConnectionThread::connected, this, &ScreenCastPortal::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(); } uint ScreenCastPortal::CreateSession(const QDBusObjectPath &handle, const QDBusObjectPath &session_handle, const QString &app_id, const QVariantMap &options, QVariantMap &results) { Q_UNUSED(results) qCDebug(XdgDesktopPortalKdeScreenCast) << "CreateSession called with parameters:"; qCDebug(XdgDesktopPortalKdeScreenCast) << " handle: " << handle.path(); qCDebug(XdgDesktopPortalKdeScreenCast) << " session_handle: " << session_handle.path(); qCDebug(XdgDesktopPortalKdeScreenCast) << " app_id: " << app_id; qCDebug(XdgDesktopPortalKdeScreenCast) << " options: " << options; - QDBusConnection sessionBus = QDBusConnection::sessionBus(); - Session *session = new Session(this, app_id, session_handle.path()); - if (sessionBus.registerVirtualObject(session_handle.path(), session, QDBusConnection::VirtualObjectRegisterOption::SubPath)) { - connect(session, &Session::closed, [this, session, session_handle] () { - m_sessionList.remove(session_handle.path()); - QDBusConnection::sessionBus().unregisterObject(session_handle.path()); - session->deleteLater(); - stopStreaming(); - }); - m_sessionList.insert(session_handle.path(), session); - return 0; - } else { - qCDebug(XdgDesktopPortalKdeScreenCast) << sessionBus.lastError().message(); - qCDebug(XdgDesktopPortalKdeScreenCast) << "Failed to register session object: " << session_handle.path(); - session->deleteLater(); + Session *session = Session::createSession(this, Session::ScreenCast, app_id, session_handle.path()); + + if (!session) { return 2; } + + connect(session, &Session::closed, [this] () { + stopStreaming(); + }); + + return 0; } uint ScreenCastPortal::SelectSources(const QDBusObjectPath &handle, const QDBusObjectPath &session_handle, const QString &app_id, const QVariantMap &options, QVariantMap &results) { Q_UNUSED(results) qCDebug(XdgDesktopPortalKdeScreenCast) << "SelectSource called with parameters:"; qCDebug(XdgDesktopPortalKdeScreenCast) << " handle: " << handle.path(); qCDebug(XdgDesktopPortalKdeScreenCast) << " session_handle: " << session_handle.path(); qCDebug(XdgDesktopPortalKdeScreenCast) << " app_id: " << app_id; qCDebug(XdgDesktopPortalKdeScreenCast) << " options: " << options; uint types = Monitor; - Session *session = nullptr; - session = m_sessionList.value(session_handle.path()); + ScreenCastSession *session = qobject_cast(Session::getSession(session_handle.path())); if (!session) { qCWarning(XdgDesktopPortalKdeScreenCast) << "Tried to select sources on non-existing session " << session_handle.path(); return 2; } if (options.contains(QLatin1String("multiple"))) { session->setMultipleSources(options.value(QLatin1String("multiple")).toBool()); } if (options.contains(QLatin1String("types"))) { types = (SourceType)(options.value(QLatin1String("types")).toUInt()); } if (types == Window) { qCWarning(XdgDesktopPortalKdeScreenCast) << "Screen cast of a window is not implemented"; return 2; } return 0; } uint ScreenCastPortal::Start(const QDBusObjectPath &handle, const QDBusObjectPath &session_handle, const QString &app_id, const QString &parent_window, const QVariantMap &options, QVariantMap &results) { Q_UNUSED(results) qCDebug(XdgDesktopPortalKdeScreenCast) << "Start called with parameters:"; qCDebug(XdgDesktopPortalKdeScreenCast) << " handle: " << handle.path(); qCDebug(XdgDesktopPortalKdeScreenCast) << " session_handle: " << session_handle.path(); qCDebug(XdgDesktopPortalKdeScreenCast) << " app_id: " << app_id; qCDebug(XdgDesktopPortalKdeScreenCast) << " parent_window: " << parent_window; qCDebug(XdgDesktopPortalKdeScreenCast) << " options: " << options; - Session *session = nullptr; - session = m_sessionList.value(session_handle.path()); + ScreenCastSession *session = qobject_cast(Session::getSession(session_handle.path())); if (!session) { qCWarning(XdgDesktopPortalKdeScreenCast) << "Tried to select sources on non-existing session " << session_handle.path(); return 2; } // TODO check whether we got some outputs? if (m_outputMap.isEmpty()) { qCWarning(XdgDesktopPortalKdeScreenCast) << "Failed to show dialog as there is no screen to select"; return 2; } QScopedPointer screenDialog(new ScreenChooserDialog(m_outputMap, session->multipleSources())); if (screenDialog->exec()) { ScreenCastPortalOutput selectedOutput = m_outputMap.value(screenDialog->selectedScreens().first()); // Initialize PipeWire createPipeWireStream(selectedOutput.resolution); // HACK wait for stream to be ready bool streamReady = false; QEventLoop loop; connect(m_stream, &ScreenCastStream::streamReady, this, [&loop, &streamReady] { loop.quit(); streamReady = true; }); QTimer::singleShot(3000, &loop, &QEventLoop::quit); loop.exec(); disconnect(m_stream, &ScreenCastStream::streamReady, this, nullptr); if (!streamReady) { qCWarning(XdgDesktopPortalKdeScreenCast) << "Pipewire stream is not ready to be streamed"; return 2; } // TODO support multiple outputs qCDebug(XdgDesktopPortalKdeScreenCast) << "Pipewire node id: " << m_stream->nodeId(); KWayland::Client::Output *output = new KWayland::Client::Output(this); output->setup(m_registry->bindOutput(selectedOutput.waylandOutputName, selectedOutput.waylandOutputVersion)); m_bindOutputs << output; Stream stream; stream.nodeId = m_stream->nodeId(); stream.map = QVariantMap({{QLatin1String("size"), selectedOutput.resolution}}); results.insert(QLatin1String("streams"), QVariant::fromValue({stream})); return 0; } return 0; } void ScreenCastPortal::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(XdgDesktopPortalKdeScreenCast) << "Adding output:"; qCDebug(XdgDesktopPortalKdeScreenCast) << " manufacturer: " << output->manufacturer(); qCDebug(XdgDesktopPortalKdeScreenCast) << " model: " << output->model(); qCDebug(XdgDesktopPortalKdeScreenCast) << " resolution: " << output->pixelSize(); ScreenCastPortalOutput portalOutput; portalOutput.manufacturer = output->manufacturer(); portalOutput.model = output->model(); portalOutput.resolution = output->pixelSize(); portalOutput.waylandOutputName = name; portalOutput.waylandOutputVersion = version; portalOutput.setOutputType(output->model()); m_outputMap.insert(name, portalOutput); delete output; }); } void ScreenCastPortal::removeOutput(quint32 name) { ScreenCastPortalOutput output = m_outputMap.take(name); qCDebug(XdgDesktopPortalKdeScreenCast) << "Removing output:"; qCDebug(XdgDesktopPortalKdeScreenCast) << " manufacturer: " << output.manufacturer; qCDebug(XdgDesktopPortalKdeScreenCast) << " model: " << output.model; } void ScreenCastPortal::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(XdgDesktopPortalKdeScreenCast) << QString("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(XdgDesktopPortalKdeScreenCast) << "Streaming is disabled"; close(gbmHandle); return; } if (!gbm_device_is_format_supported(m_gbmDevice, format, GBM_BO_USE_SCANOUT)) { qCritical() << "GBM format is not supported by device!"; } // 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) { qCritical() << "Cannot import passed GBM fd:" << strerror(errno); } // bind context to render thread eglMakeCurrent(m_egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, m_egl.context); // create EGL image from imported BO EGLImageKHR image = eglCreateImageKHR(m_egl.display, nullptr, EGL_NATIVE_PIXMAP_KHR, imported, nullptr); if (image == EGL_NO_IMAGE_KHR) { qCritical() << "Error creating EGLImageKHR" << formatGLError(glGetError()); return; } // create GL 2D texture for framebuffer GLuint texture; glGenTextures(1, &texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, texture); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); // bind framebuffer to copy pixels from GLuint framebuffer; glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { qCritical() << "glCheckFramebufferStatus failed:" << formatGLError(glGetError()); glDeleteTextures(1, &texture); glDeleteFramebuffers(1, &framebuffer); eglDestroyImageKHR(m_egl.display, image); return; } auto capture = new QImage(QSize(width, height), QImage::Format_RGBA8888); glViewport(0, 0, width, height); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, capture->bits()); m_stream->recordFrame(capture->bits()); gbm_bo_destroy(imported); glDeleteTextures(1, &texture); glDeleteFramebuffers(1, &framebuffer); eglDestroyImageKHR(m_egl.display, image); delete capture; close(gbmHandle); } void ScreenCastPortal::setupRegistry() { m_queue = new KWayland::Client::EventQueue(this); m_queue->setup(m_connection); m_registry = new KWayland::Client::Registry(this); connect(m_registry, &KWayland::Client::Registry::outputAnnounced, this, &ScreenCastPortal::addOutput); connect(m_registry, &KWayland::Client::Registry::outputRemoved, this, &ScreenCastPortal::removeOutput); connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced, this, [this] { m_registryInitialized = true; qCDebug(XdgDesktopPortalKdeScreenCast) << "Registry initialized"; }); m_registry->create(m_connection); m_registry->setEventQueue(m_queue); m_registry->setup(); } void ScreenCastPortal::stopStreaming() { if (m_streamingEnabled) { qCDebug(XdgDesktopPortalKdeScreenCast) << "Stop streaming"; m_remoteAccessManager->release(); m_remoteAccessManager->destroy(); m_streamingEnabled = false; qDeleteAll(m_bindOutputs); m_bindOutputs.clear(); delete m_stream; m_stream = nullptr; } } diff --git a/src/screencast.h b/src/screencast.h index 0865eff..3a19cf6 100644 --- a/src/screencast.h +++ b/src/screencast.h @@ -1,157 +1,155 @@ /* * 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_SCREENCAST_H #define XDG_DESKTOP_PORTAL_KDE_SCREENCAST_H #include #include #include #include #include #include namespace KWayland { namespace Client { class ConnectionThread; class EventQueue; class OutputDevice; class Registry; class RemoteAccessManager; class RemoteBuffer; class Output; } } -class Session; class ScreenChooserDialog; class ScreenCastStream; class ScreenCastPortalOutput { enum OutputType { Laptop, Monitor, Television }; void setOutputType(const QString &type); QString manufacturer; QString model; QSize resolution; OutputType outputType; // Needed for later output binding int waylandOutputName; int waylandOutputVersion; friend class ScreenCastPortal; friend class ScreenChooserDialog; }; class ScreenCastPortal : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.freedesktop.impl.portal.ScreenCast") Q_PROPERTY(uint version READ version) Q_PROPERTY(uint AvailableSourceTypes READ AvailableSourceTypes) public: typedef struct { uint nodeId; QVariantMap map; } Stream; typedef QList Streams; enum SourceType { Any = 0, Monitor, Window }; explicit ScreenCastPortal(QObject *parent); ~ScreenCastPortal(); uint version() const { return 1; } uint AvailableSourceTypes() const { return Monitor; }; public Q_SLOTS: uint CreateSession(const QDBusObjectPath &handle, const QDBusObjectPath &session_handle, const QString &app_id, const QVariantMap &options, QVariantMap &results); uint SelectSources(const QDBusObjectPath &handle, const QDBusObjectPath &session_handle, const QString &app_id, const QVariantMap &options, QVariantMap &results); uint Start(const QDBusObjectPath &handle, const QDBusObjectPath &session_handle, const QString &app_id, const QString &parent_window, const QVariantMap &options, QVariantMap &results); private Q_SLOTS: void addOutput(quint32 name, quint32 version); void removeOutput(quint32 name); void processBuffer(const KWayland::Client::RemoteBuffer *rbuf); void setupRegistry(); void stopStreaming(); private: void createPipeWireStream(const QSize &resolution); void initDrm(); void initEGL(); void initWayland(); bool m_registryInitialized; bool m_streamingEnabled; - QMap m_sessionList; QMap m_outputMap; QList m_bindOutputs; QThread *m_thread; ScreenCastStream *m_stream; KWayland::Client::ConnectionThread *m_connection; KWayland::Client::EventQueue *m_queue; KWayland::Client::Registry *m_registry; KWayland::Client::RemoteAccessManager *m_remoteAccessManager; qint32 m_drmFd = 0; // for GBM buffer mmap gbm_device *m_gbmDevice = nullptr; // for passed GBM buffer retrieval struct { QList extensions; EGLDisplay display = EGL_NO_DISPLAY; EGLContext context = EGL_NO_CONTEXT; } m_egl; }; #endif // XDG_DESKTOP_PORTAL_KDE_SCREENCAST_H diff --git a/src/session.cpp b/src/session.cpp index e790e88..7128cbb 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -1,126 +1,186 @@ /* * 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 "session.h" #include "desktopportal.h" #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(XdgSessionKdeSession, "xdp-kde-session") +static QMap sessionList; + Session::Session(QObject *parent, const QString &appId, const QString &path) : QDBusVirtualObject(parent) - , m_multipleSources(false) , m_appId(appId) , m_path(path) { } Session::~Session() { } bool Session::handleMessage(const QDBusMessage &message, const QDBusConnection &connection) { Q_UNUSED(connection); if (message.path() != m_path) { return false; } /* Check to make sure we're getting properties on our interface */ if (message.type() != QDBusMessage::MessageType::MethodCallMessage) { return false; } qCDebug(XdgSessionKdeSession) << message.interface(); qCDebug(XdgSessionKdeSession) << message.member(); qCDebug(XdgSessionKdeSession) << message.path(); if (message.interface() == QLatin1String("org.freedesktop.impl.portal.Session")) { if (message.member() == QLatin1String("Close")) { Q_EMIT closed(); QDBusMessage reply = message.createReply(); return connection.send(reply); } } else if (message.interface() == QLatin1String("org.freedesktop.DBus.Properties")) { if (message.member() == QLatin1String("Get")) { if (message.arguments().count() == 2) { const QString interface = message.arguments().at(0).toString(); const QString property = message.arguments().at(1).toString(); if (interface == QLatin1String("org.freedesktop.impl.portal.Session") && property == QLatin1String("version")) { QList arguments; arguments << 1; QDBusMessage reply = message.createReply(); reply.setArguments(arguments); return connection.send(reply); } } } } return false; } QString Session::introspect(const QString &path) const { QString nodes; if (path.startsWith(QLatin1String("/org/freedesktop/portal/desktop/session/"))) { nodes = QStringLiteral( "" " " " " "" "" "" ""); } qCDebug(XdgSessionKdeSession) << nodes; return nodes; } -bool Session::multipleSources() const +bool Session::close() +{ + QDBusMessage reply = QDBusMessage::createSignal(m_path, QLatin1String("org.freedesktop.impl.portal.Session"), QLatin1String("Closed")); + return QDBusConnection::sessionBus().send(reply); +} + +Session * Session::createSession(QObject *parent, SessionType type, const QString &appId, const QString &path) +{ + QDBusConnection sessionBus = QDBusConnection::sessionBus(); + + Session *session = nullptr; + if (type == ScreenCast) { + session = new ScreenCastSession(parent, appId, path); + } else { + session = new RemoteDesktopSession(parent, appId, path); + } + + if (sessionBus.registerVirtualObject(path, session, QDBusConnection::VirtualObjectRegisterOption::SubPath)) { + connect(session, &Session::closed, [session, path] () { + sessionList.remove(path); + QDBusConnection::sessionBus().unregisterObject(path); + session->deleteLater(); + }); + sessionList.insert(path, session); + return session; + } else { + qCDebug(XdgSessionKdeSession) << sessionBus.lastError().message(); + qCDebug(XdgSessionKdeSession) << "Failed to register session object: " << path; + session->deleteLater(); + return nullptr; + } +} + +Session * Session::getSession(const QString &sessionHandle) +{ + return sessionList.value(sessionHandle); +} + +ScreenCastSession::ScreenCastSession(QObject *parent, const QString &appId, const QString &path) + : Session(parent, appId, path) +{ +} + +ScreenCastSession::~ScreenCastSession() +{ +} + +bool ScreenCastSession::multipleSources() const { return m_multipleSources; } -void Session::setMultipleSources(bool multipleSources) +void ScreenCastSession::setMultipleSources(bool multipleSources) { m_multipleSources = multipleSources; } -bool Session::close() +RemoteDesktopSession::RemoteDesktopSession(QObject *parent, const QString &appId, const QString &path) + : ScreenCastSession(parent, appId, path) { - QDBusMessage reply = QDBusMessage::createSignal(m_path, QLatin1String("org.freedesktop.impl.portal.Session"), QLatin1String("Closed")); - return QDBusConnection::sessionBus().send(reply); } +RemoteDesktopSession::~RemoteDesktopSession() +{ +} + +RemoteDesktopPortal::DeviceTypes RemoteDesktopSession::deviceTypes() const +{ + return m_deviceTypes; +} + +void RemoteDesktopSession::setDeviceTypes(RemoteDesktopPortal::DeviceTypes deviceTypes) +{ + m_deviceTypes = deviceTypes; +} diff --git a/src/session.h b/src/session.h index 2e1fa04..39414ac 100644 --- a/src/session.h +++ b/src/session.h @@ -1,52 +1,86 @@ /* * 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_SESSION_H #define XDG_DESKTOP_PORTAL_KDE_SESSION_H #include #include +#include "remotedesktop.h" + class Session : public QDBusVirtualObject { Q_OBJECT public: explicit Session(QObject *parent = nullptr, const QString &appId = QString(), const QString &path = QString()); ~Session(); + enum SessionType { + ScreenCast = 0, + RemoteDesktop = 1 + }; + bool handleMessage(const QDBusMessage &message, const QDBusConnection &connection) override; QString introspect(const QString &path) const override; bool close(); - bool multipleSources() const; - void setMultipleSources(bool multipleSources); + static Session *createSession(QObject *parent, SessionType type, const QString &appId, const QString &path); + static Session *getSession(const QString &sessionHandle); Q_SIGNALS: void closed(); private: - bool m_multipleSources; const QString m_appId; const QString m_path; }; +class ScreenCastSession : public Session +{ + Q_OBJECT +public: + explicit ScreenCastSession(QObject *parent = nullptr, const QString &appId = QString(), const QString &path = QString()); + ~ScreenCastSession(); + + bool multipleSources() const; + void setMultipleSources(bool multipleSources); +private: + bool m_multipleSources; + // TODO type +}; + +class RemoteDesktopSession : public ScreenCastSession +{ + Q_OBJECT +public: + explicit RemoteDesktopSession(QObject *parent = nullptr, const QString &appId = QString(), const QString &path = QString()); + ~RemoteDesktopSession(); + + RemoteDesktopPortal::DeviceTypes deviceTypes() const; + void setDeviceTypes(RemoteDesktopPortal::DeviceTypes deviceTypes); + +private: + RemoteDesktopPortal::DeviceTypes m_deviceTypes; +}; + #endif // XDG_DESKTOP_PORTAL_KDE_SESSION_H