diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8c3499b..7f7e2d7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,68 +1,69 @@ 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 + screencastcommon.cpp screencaststream.cpp screencastwidget.cpp screenchooserdialog.cpp remotedesktop.cpp remotedesktopdialog.cpp waylandintegration.cpp) ki18n_wrap_ui(xdg_desktop_portal_kde_SRCS 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/remotedesktop.cpp b/src/remotedesktop.cpp index 2cd04b3..7121242 100644 --- a/src/remotedesktop.cpp +++ b/src/remotedesktop.cpp @@ -1,186 +1,221 @@ /* * 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 "screencastcommon.h" #include "session.h" +#include "remotedesktopdialog.h" +#include "waylandintegration.h" -#include -#include -#include -#include #include -#include -#include Q_LOGGING_CATEGORY(XdgDesktopPortalKdeRemoteDesktop, "xdp-kde-remotedesktop") RemoteDesktopPortal::RemoteDesktopPortal(QObject *parent) : QDBusAbstractAdaptor(parent) + , m_screenCastCommon(new ScreenCastCommon()) { } RemoteDesktopPortal::~RemoteDesktopPortal() { + if (m_screenCastCommon) { + delete m_screenCastCommon; + } } 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 + m_screenCastCommon->stopStreaming(); }); 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; + RemoteDesktopSession *session = qobject_cast(Session::getSession(session_handle.path())); + + if (!session) { + qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Tried to call start on non-existing session " << session_handle.path(); + return 2; + } + + // TODO check whether we got some outputs? + if (WaylandIntegration::screens().isEmpty()) { + qCWarning(XdgDesktopPortalKdeRemoteDesktop) << "Failed to show dialog as there is no screen to select"; + return 2; + } + + QScopedPointer remoteDesktopDialog(new RemoteDesktopDialog(app_id, session->deviceTypes(), session->screenSharingEnabled(), session->multipleSources())); + + if (remoteDesktopDialog->exec()) { + if (session->screenSharingEnabled()) { + WaylandIntegration::WaylandOutput selectedOutput = WaylandIntegration::screens().value(remoteDesktopDialog->selectedScreens().first()); + + QVariant streams = m_screenCastCommon->startStreaming(selectedOutput); + + if (!streams.isValid()) { + qCWarning(XdgDesktopPortalKdeRemoteDesktop()) << "Pipewire stream is not ready to be streamed"; + return 2; + } + + results.insert(QLatin1String("streams"), streams); + } + + // TODO devices + + return 0; + } + 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 index 7be33b0..6668dad 100644 --- a/src/remotedesktop.h +++ b/src/remotedesktop.h @@ -1,129 +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 ScreenCastCommon; 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: - + ScreenCastCommon *m_screenCastCommon; }; #endif // XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_H diff --git a/src/remotedesktopdialog.cpp b/src/remotedesktopdialog.cpp new file mode 100644 index 0000000..483b44d --- /dev/null +++ b/src/remotedesktopdialog.cpp @@ -0,0 +1,85 @@ +/* + * 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 "remotedesktopdialog.h" +#include "ui_remotedesktopdialog.h" + +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(XdgDesktopPortalKdeRemoteDesktopDialog, "xdp-kde-remote-desktop-dialog") + +RemoteDesktopDialog::RemoteDesktopDialog(const QString &appName, RemoteDesktopPortal::DeviceTypes deviceTypes, bool screenSharingEnabled, + bool multiple, QDialog *parent, Qt::WindowFlags flags) + : QDialog(parent, flags) + , m_dialog(new Ui::RemoteDesktopDialog) +{ + m_dialog->setupUi(this); + + m_dialog->screenCastWidget->setVisible(screenSharingEnabled); + if (screenSharingEnabled) { + connect(m_dialog->screenCastWidget, &QListWidget::itemDoubleClicked, this, &RemoteDesktopDialog::accept); + + if (multiple) { + m_dialog->screenCastWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + } + } + + // TODO + + connect(m_dialog->buttonBox, &QDialogButtonBox::accepted, this, &RemoteDesktopDialog::accept); + connect(m_dialog->buttonBox, &QDialogButtonBox::rejected, this, &RemoteDesktopDialog::reject); + + m_dialog->buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("Share")); + + QString applicationName; + const QString desktopFile = appName + QLatin1String(".desktop"); + const QStringList desktopFilesLocations = QStandardPaths::locateAll(QStandardPaths::ApplicationsLocation, desktopFile, QStandardPaths::LocateFile); + QSettings settings(desktopFile, QSettings::IniFormat); + settings.beginGroup(QLatin1String("Desktop Entry")); + if (settings.contains(QLatin1String("X-GNOME-FullName"))) { + applicationName = settings.value(QLatin1String("X-GNOME-FullName")).toString(); + } else { + applicationName = settings.value(QLatin1String("Name")).toString(); + } + + if (applicationName.isEmpty()) { + setWindowTitle(i18n("Select what to share with the requesting application")); + } else { + setWindowTitle(i18n("Select what to share with %1").arg(applicationName)); + } +} + +RemoteDesktopDialog::~RemoteDesktopDialog() +{ + delete m_dialog; +} + +QList RemoteDesktopDialog::selectedScreens() const +{ + return m_dialog->screenCastWidget->selectedScreens(); +} + +RemoteDesktopPortal::DeviceTypes RemoteDesktopDialog::deviceTypes() const +{ + return 0; +} diff --git a/src/screenchooserdialog.h b/src/remotedesktopdialog.h similarity index 57% copy from src/screenchooserdialog.h copy to src/remotedesktopdialog.h index 5598e63..339e773 100644 --- a/src/screenchooserdialog.h +++ b/src/remotedesktopdialog.h @@ -1,44 +1,49 @@ /* * 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_SCREENCHOOSER_DIALOG_H -#define XDG_DESKTOP_PORTAL_KDE_SCREENCHOOSER_DIALOG_H +#ifndef XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_DIALOG_H +#define XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_DIALOG_H +#include #include +#include "remotedesktop.h" + namespace Ui { -class ScreenChooserDialog; +class RemoteDesktopDialog; } -class ScreenChooserDialog : public QDialog +class RemoteDesktopDialog : public QDialog { Q_OBJECT public: - ScreenChooserDialog(bool multiple, QDialog *parent = nullptr, Qt::WindowFlags flags = {}); - ~ScreenChooserDialog(); + RemoteDesktopDialog(const QString &appName, RemoteDesktopPortal::DeviceTypes deviceTypes, bool screenSharingEnabled = false, + bool multiple = false, QDialog *parent = nullptr, Qt::WindowFlags flags = {}); + ~RemoteDesktopDialog(); QList selectedScreens() const; + RemoteDesktopPortal::DeviceTypes deviceTypes() const; private: - Ui::ScreenChooserDialog *m_dialog; + Ui::RemoteDesktopDialog * m_dialog; }; -#endif // XDG_DESKTOP_PORTAL_KDE_SCREENCHOOSER_DIALOG_H +#endif // XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_DIALOG_H diff --git a/src/remotedesktopdialog.ui b/src/remotedesktopdialog.ui new file mode 100644 index 0000000..ddfbf04 --- /dev/null +++ b/src/remotedesktopdialog.ui @@ -0,0 +1,109 @@ + + + RemoteDesktopDialog + + + + 0 + 0 + 452 + 286 + + + + Dialog + + + + + + QAbstractItemView::SingleSelection + + + + 48 + 48 + + + + false + + + + + + + + + Allow access to: + + + + + + + + + Pointer + + + true + + + + + + + Keyboard + + + true + + + + + + + Touch screen + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + ScreenCastWidget + QListWidget +
screencastwidget.h
+
+
+ + +
diff --git a/src/screencast.cpp b/src/screencast.cpp index cc20167..0de2692 100644 --- a/src/screencast.cpp +++ b/src/screencast.cpp @@ -1,234 +1,168 @@ /* * 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 "screencastcommon.h" #include "screenchooserdialog.h" +#include "session.h" #include "waylandintegration.h" -#include -#include -#include -#include - -#include #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; -} - ScreenCastPortal::ScreenCastPortal(QObject *parent) : QDBusAbstractAdaptor(parent) + , m_screenCastCommon(new ScreenCastCommon()) + { - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); } ScreenCastPortal::~ScreenCastPortal() { - if (m_stream) { - delete m_stream; + if (m_screenCastCommon) { + delete m_screenCastCommon; } } -void ScreenCastPortal::createPipeWireStream(const QSize &resolution) -{ - m_stream = new ScreenCastStream(resolution); - m_stream->init(); -} - 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; Session *session = Session::createSession(this, Session::ScreenCast, app_id, session_handle.path()); if (!session) { return 2; } connect(session, &Session::closed, [this] () { - stopStreaming(); + if (m_screenCastCommon) { + m_screenCastCommon->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; 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; } + // Might be also a RemoteDesktopSession + if (session->type() == Session::RemoteDesktop) { + RemoteDesktopSession *remoteDesktopSession = qobject_cast(session); + if (remoteDesktopSession) { + remoteDesktopSession->setScreenSharingEnabled(true); + } + } + 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; ScreenCastSession *session = qobject_cast(Session::getSession(session_handle.path())); if (!session) { - qCWarning(XdgDesktopPortalKdeScreenCast) << "Tried to select sources on non-existing session " << session_handle.path(); + qCWarning(XdgDesktopPortalKdeScreenCast) << "Tried to call start on non-existing session " << session_handle.path(); return 2; } // TODO check whether we got some outputs? if (WaylandIntegration::screens().isEmpty()) { qCWarning(XdgDesktopPortalKdeScreenCast) << "Failed to show dialog as there is no screen to select"; return 2; } - QScopedPointer screenDialog(new ScreenChooserDialog(session->multipleSources())); + QScopedPointer screenDialog(new ScreenChooserDialog(app_id, session->multipleSources())); if (screenDialog->exec()) { WaylandIntegration::WaylandOutput selectedOutput = WaylandIntegration::screens().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(); + QVariant streams = m_screenCastCommon->startStreaming(selectedOutput); - disconnect(m_stream, &ScreenCastStream::streamReady, this, nullptr); - - if (!streamReady) { + if (!streams.isValid()) { qCWarning(XdgDesktopPortalKdeScreenCast) << "Pipewire stream is not ready to be streamed"; return 2; } - // TODO support multiple outputs - - WaylandIntegration::bindOutput(selectedOutput.waylandOutputName(), selectedOutput.waylandOutputVersion()); - - Stream stream; - stream.nodeId = m_stream->nodeId(); - stream.map = QVariantMap({{QLatin1String("size"), selectedOutput.resolution()}}); - results.insert(QLatin1String("streams"), QVariant::fromValue({stream})); + results.insert(QLatin1String("streams"), streams); return 0; } return 0; } - -void ScreenCastPortal::stopStreaming() -{ - if (m_stream) { - m_stream->stopStream(); - delete m_stream; - m_stream = nullptr; - } -} diff --git a/src/screencast.h b/src/screencast.h index def4550..1a245ec 100644 --- a/src/screencast.h +++ b/src/screencast.h @@ -1,86 +1,73 @@ /* * 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 -class ScreenChooserDialog; -class ScreenCastStream; +class ScreenCastCommon; 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 stopStreaming(); - private: - void createPipeWireStream(const QSize &resolution); - - ScreenCastStream *m_stream; + ScreenCastCommon *m_screenCastCommon; }; #endif // XDG_DESKTOP_PORTAL_KDE_SCREENCAST_H diff --git a/src/screencastcommon.cpp b/src/screencastcommon.cpp new file mode 100644 index 0000000..f691d8c --- /dev/null +++ b/src/screencastcommon.cpp @@ -0,0 +1,126 @@ +/* + * 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 "screencastcommon.h" +#include "screencaststream.h" + +#include +#include + +#include +#include + +const QDBusArgument &operator >> (const QDBusArgument &arg, ScreenCastCommon::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 ScreenCastCommon::Stream &stream) +{ + arg.beginStructure(); + arg << stream.nodeId; + arg << stream.map; + arg.endStructure(); + + return arg; +} + +Q_DECLARE_METATYPE(ScreenCastCommon::Stream); +Q_DECLARE_METATYPE(ScreenCastCommon::Streams); + +ScreenCastCommon::ScreenCastCommon(QObject *parent) + : QObject(parent) + , m_streamingEnabled(false) +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); +} + +ScreenCastCommon::~ScreenCastCommon() +{ +} + +QVariant ScreenCastCommon::startStreaming(const WaylandIntegration::WaylandOutput &output) +{ + m_stream = new ScreenCastStream(output.resolution()); + m_stream->init(); + + connect(WaylandIntegration::waylandIntegration(), &WaylandIntegration::WaylandIntegration::newBuffer, m_stream, &ScreenCastStream::recordFrame); + + connect(m_stream, &ScreenCastStream::startStreaming, this, [this] { + m_streamingEnabled = true; + WaylandIntegration::startStreaming(); + }); + + connect(m_stream, &ScreenCastStream::stopStreaming, this, &ScreenCastCommon::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) { + return QVariant(); + } + + // TODO support multiple outputs + + WaylandIntegration::bindOutput(output.waylandOutputName(), output.waylandOutputVersion()); + + Stream stream; + stream.nodeId = m_stream->nodeId(); + stream.map = QVariantMap({{QLatin1String("size"), output.resolution()}}); + return QVariant::fromValue({stream}); +} + +void ScreenCastCommon::stopStreaming() +{ + if (m_streamingEnabled) { + WaylandIntegration::stopStreaming(); + m_streamingEnabled = false; + if (m_stream) { + delete m_stream; + m_stream = nullptr; + } + } +} diff --git a/src/screenchooserdialog.h b/src/screencastcommon.h similarity index 53% copy from src/screenchooserdialog.h copy to src/screencastcommon.h index 5598e63..6d0f96b 100644 --- a/src/screenchooserdialog.h +++ b/src/screencastcommon.h @@ -1,44 +1,56 @@ /* * 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_SCREENCHOOSER_DIALOG_H -#define XDG_DESKTOP_PORTAL_KDE_SCREENCHOOSER_DIALOG_H +#ifndef XDG_DESKTOP_PORTAL_KDE_SCREENCAST_COMMON_H +#define XDG_DESKTOP_PORTAL_KDE_SCREENCAST_COMMON_H -#include +#include +#include +#include -namespace Ui -{ -class ScreenChooserDialog; -} +#include "waylandintegration.h" + +class ScreenCastStream; -class ScreenChooserDialog : public QDialog +class ScreenCastCommon : public QObject { - Q_OBJECT public: - ScreenChooserDialog(bool multiple, QDialog *parent = nullptr, Qt::WindowFlags flags = {}); - ~ScreenChooserDialog(); + typedef struct { + uint nodeId; + QVariantMap map; + } Stream; + typedef QList Streams; + + explicit ScreenCastCommon(QObject *parent = nullptr); + ~ScreenCastCommon(); - QList selectedScreens() const; + QVariant startStreaming(const WaylandIntegration::WaylandOutput &output); + +public Q_SLOTS: + void stopStreaming(); private: - Ui::ScreenChooserDialog *m_dialog; + bool m_streamingEnabled; + ScreenCastStream *m_stream; }; -#endif // XDG_DESKTOP_PORTAL_KDE_SCREENCHOOSER_DIALOG_H +#endif // XDG_DESKTOP_PORTAL_KDE_SCREENCAST_COMMON_H + + diff --git a/src/screencaststream.cpp b/src/screencaststream.cpp index 9f5e5b8..3a91f70 100644 --- a/src/screencaststream.cpp +++ b/src/screencaststream.cpp @@ -1,489 +1,471 @@ /* * 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 "screencaststream.h" #include "waylandintegration.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 ABS(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; gboolean 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 = (gint) 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 > G_MAXINT || D2 > G_MAXINT) 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 - ((gdouble) N) / D) < MAX_ERROR) break; /* Take reciprocal */ F = 1 / F; } /* fix for overflow */ if (D == 0) { N = G_MAXINT; 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; } static void 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); if (!pw->createStream()) { - Q_EMIT pw->stoppedStreaming(); + Q_EMIT pw->stopStreaming(); } break; default: qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Remote state: " << pw_remote_state_as_string(state); break; } } static void onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message) { Q_UNUSED(old) ScreenCastStream *pw = static_cast(data); 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) << pw->streaming; - if (pw->streaming) { - pw->stopStream(); - } + 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); - pw->streaming = true; - WaylandIntegration::startStreaming(); - Q_EMIT pw->startedStreaming(); + Q_EMIT pw->startStreaming(); break; } } #if defined(PW_API_PRE_0_2_0) static void onStreamFormatChanged(void *data, struct spa_pod *format) #else static void onStreamFormatChanged(void *data, const struct spa_pod *format) #endif // defined(PW_API_PRE_0_2_0) { 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; #if defined(PW_API_PRE_0_2_0) struct spa_pod *params[1]; #else const struct spa_pod *params[1]; #endif // defined(PW_API_PRE_0_2_0) const int bpp = 4; if (!format) { pw_stream_finish_format(pw->pwStream, 0, nullptr, 0); return; } spa_format_video_raw_parse (format, &pw->videoFormat, &pw->pwType->format_video); width = pw->videoFormat.size.width; height =pw->videoFormat.size.height; stride = SPA_ROUND_UP_N (width * bpp, 4); size = height * stride; pod_builder = SPA_POD_BUILDER_INIT (paramsBuffer, sizeof (paramsBuffer)); 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, G_N_ELEMENTS (params)); } static const struct pw_remote_events pwRemoteEvents = { .version = PW_VERSION_REMOTE_EVENTS, .destroy = nullptr, .info_changed = nullptr, .sync_reply = nullptr, .state_changed = onStateChanged, }; static const struct pw_stream_events pwStreamEvents = { .version = PW_VERSION_STREAM_EVENTS, .destroy = nullptr, .state_changed = onStreamStateChanged, .format_changed = onStreamFormatChanged, .add_buffer = nullptr, .remove_buffer = nullptr, #if defined(PW_API_PRE_0_2_0) .new_buffer = nullptr, .need_buffer = nullptr, #else .process = nullptr, #endif // defined(PW_API_PRE_0_2_0) }; ScreenCastStream::ScreenCastStream(const QSize &resolution, QObject *parent) : QObject(parent) , resolution(resolution) { } ScreenCastStream::~ScreenCastStream() { if (pwType) { delete pwType; } if (pwStream) { pw_stream_destroy(pwStream); } if (pwRemote) { pw_remote_destroy(pwRemote); } if (pwCore) { pw_core_destroy(pwCore); } if (pwLoop) { pw_loop_leave(pwLoop); pw_loop_destroy(pwLoop); } } void ScreenCastStream::init() { pw_init(nullptr, nullptr); pwLoop = pw_loop_new(nullptr); socketNotifier.reset(new QSocketNotifier(pw_loop_get_fd(pwLoop), QSocketNotifier::Read)); connect(socketNotifier.data(), &QSocketNotifier::activated, this, &ScreenCastStream::processPipewireEvents); 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); - - connect(WaylandIntegration::waylandIntegration(), &WaylandIntegration::WaylandIntegration::newBuffer, this, &ScreenCastStream::recordFrame); } uint ScreenCastStream::nodeId() { if (pwStream) { return (uint)pw_stream_get_node_id(pwStream); } return 0; } bool ScreenCastStream::createStream() { if (pw_remote_get_state(pwRemote, nullptr) != PW_REMOTE_STATE_CONNECTED) { qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Cannot create pipewire stream"; return false; } 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]; pwStream = pw_stream_new(pwRemote, "kwin-screen-cast", nullptr); 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); 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.xRGB, + ":", 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)); pw_stream_add_listener(pwStream, &streamListener, &pwStreamEvents, this); #if defined(PW_API_PRE_0_2_0) if (pw_stream_connect(pwStream, PW_DIRECTION_OUTPUT, nullptr, PW_STREAM_FLAG_NONE, params, G_N_ELEMENTS(¶ms)) != 0) { #else if (pw_stream_connect(pwStream, PW_DIRECTION_OUTPUT, nullptr, static_cast(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS), params, G_N_ELEMENTS(¶ms)) != 0) { #endif // defined(PW_API_PRE_0_2_0) qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Could not connect to stream"; return false; } return true; } bool ScreenCastStream::recordFrame(uint8_t *screenData) { #if defined(PW_API_PRE_0_2_0) uint32_t bufferId; #else struct pw_buffer *buffer; #endif // defined(PW_API_PRE_0_2_0) struct spa_buffer *spa_buffer; uint8_t *map = nullptr; uint8_t *data = nullptr; // TODO check timestamp like mutter does? if (!pwStream) { return false; } #if defined(PW_API_PRE_0_2_0) bufferId = pw_stream_get_empty_buffer(pwStream); if (bufferId == SPA_ID_INVALID) { qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to get empty stream buffer: " << strerror(errno); return false; } spa_buffer = pw_stream_peek_buffer(pwStream, bufferId); #else buffer = pw_stream_dequeue_buffer(pwStream); #endif // defined(PW_API_PRE_0_2_0) #if defined(PW_API_PRE_0_2_0) if (spa_buffer->datas[0].type == pwCoreType->data.MemFd) { #else spa_buffer = buffer->buffer; if (spa_buffer->datas[0].data) { data = (uint8_t *) spa_buffer->datas[0].data; } else if (spa_buffer->datas[0].type == pwCoreType->data.MemFd) { #endif // defined(PW_API_PRE_0_2_0) map = (uint8_t *)mmap(nullptr, spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset, PROT_READ | PROT_WRITE, MAP_SHARED, spa_buffer->datas[0].fd, 0); if (map == MAP_FAILED) { qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to mmap pipewire stream buffer: " << strerror(errno); return false; } data = SPA_MEMBER(map, spa_buffer->datas[0].mapoffset, uint8_t); #if defined(PW_API_PRE_0_2_0) } else if (spa_buffer->datas[0].type == pwCoreType->data.MemPtr) { data = (uint8_t *) spa_buffer->datas[0].data; #endif // defined(PW_API_PRE_0_2_0) } else { return false; } memcpy(data, screenData, BITS_PER_PIXEL * videoFormat.size.height * videoFormat.size.width * sizeof(uint8_t)); if (map) { munmap(map, spa_buffer->datas[0].maxsize + spa_buffer->datas[0].mapoffset); } spa_buffer->datas[0].chunk->size = spa_buffer->datas[0].maxsize; #if defined(PW_API_PRE_0_2_0) pw_stream_send_buffer(pwStream, bufferId); #else pw_stream_queue_buffer(pwStream, buffer); #endif // defined(PW_API_PRE_0_2_0) 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); } -void ScreenCastStream::stopStream() -{ - qCDebug(XdgDesktopPortalKdeScreenCastStream) << "Stop streaming"; - streaming = false; - - WaylandIntegration::stopStreaming(); - - // removeStream(); - - Q_EMIT stoppedStreaming(); -} - 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); } void ScreenCastStream::processPipewireEvents() { int result = pw_loop_iterate(pwLoop, 0); if (result < 0) { qCWarning(XdgDesktopPortalKdeScreenCastStream) << "Failed to iterate over pipewire loop: " << spa_strerror(result); } } diff --git a/src/screencaststream.h b/src/screencaststream.h index 527d9ed..88fd767 100644 --- a/src/screencaststream.h +++ b/src/screencaststream.h @@ -1,112 +1,110 @@ /* * 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 SCREEN_CAST_STREAM_H #define SCREEN_CAST_STREAM_H #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __has_include #if __has_include() #include #else #define PW_API_PRE_0_2_0 #endif // __has_include() #else #define PW_API_PRE_0_2_0 #endif // __has_include 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; }; class QSocketNotifier; class ScreenCastStream : public QObject { Q_OBJECT public: explicit ScreenCastStream(const QSize &resolution, QObject *parent = nullptr); ~ScreenCastStream(); // Public void init(); uint nodeId(); // Public because we need access from static functions bool createStream(); void removeStream(); - void stopStream(); public Q_SLOTS: bool recordFrame(uint8_t *screenData); Q_SIGNALS: void streamReady(uint nodeId); - void startedStreaming(); - void stoppedStreaming(); + void startStreaming(); + void stopStreaming(); private: void initializePwTypes(); private Q_SLOTS: void processPipewireEvents(); public: - bool streaming = false; pw_core *pwCore = nullptr; pw_loop *pwLoop = nullptr; pw_node *pwNode = nullptr; pw_stream *pwStream = nullptr; pw_type *pwCoreType = nullptr; pw_remote *pwRemote = nullptr; PwType *pwType = nullptr; spa_hook remoteListener; spa_hook streamListener; QSize resolution; QScopedPointer socketNotifier; spa_video_info_raw videoFormat; }; #endif // SCREEN_CAST_STREAM_H diff --git a/src/screenchooserdialog.cpp b/src/screenchooserdialog.cpp index 768e8c9..36b5435 100644 --- a/src/screenchooserdialog.cpp +++ b/src/screenchooserdialog.cpp @@ -1,53 +1,71 @@ /* * 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 "screenchooserdialog.h" #include "ui_screenchooserdialog.h" #include #include +#include +#include -ScreenChooserDialog::ScreenChooserDialog(bool multiple, QDialog *parent, Qt::WindowFlags flags) +ScreenChooserDialog::ScreenChooserDialog(const QString &appName, bool multiple, QDialog *parent, Qt::WindowFlags flags) : QDialog(parent, flags) , m_dialog(new Ui::ScreenChooserDialog) { m_dialog->setupUi(this); if (multiple) { m_dialog->screenView->setSelectionMode(QAbstractItemView::ExtendedSelection); } connect(m_dialog->buttonBox, &QDialogButtonBox::accepted, this, &ScreenChooserDialog::accept); connect(m_dialog->buttonBox, &QDialogButtonBox::rejected, this, &ScreenChooserDialog::reject); connect(m_dialog->screenView, &QListWidget::itemDoubleClicked, this, &ScreenChooserDialog::accept); m_dialog->buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("Share")); - setWindowTitle(i18n("Select screen to share")); + + QString applicationName; + const QString desktopFile = appName + QLatin1String(".desktop"); + const QStringList desktopFilesLocations = QStandardPaths::locateAll(QStandardPaths::ApplicationsLocation, desktopFile, QStandardPaths::LocateFile); + QSettings settings(desktopFile, QSettings::IniFormat); + settings.beginGroup(QLatin1String("Desktop Entry")); + if (settings.contains(QLatin1String("X-GNOME-FullName"))) { + applicationName = settings.value(QLatin1String("X-GNOME-FullName")).toString(); + } else { + applicationName = settings.value(QLatin1String("Name")).toString(); + } + + if (applicationName.isEmpty()) { + setWindowTitle(i18n("Select screen to share with the requesting application")); + } else { + setWindowTitle(i18n("Select screen to share with %1").arg(applicationName)); + } } ScreenChooserDialog::~ScreenChooserDialog() { delete m_dialog; } QList ScreenChooserDialog::selectedScreens() const { return m_dialog->screenView->selectedScreens(); } diff --git a/src/screenchooserdialog.h b/src/screenchooserdialog.h index 5598e63..ff44dc5 100644 --- a/src/screenchooserdialog.h +++ b/src/screenchooserdialog.h @@ -1,44 +1,44 @@ /* * 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_SCREENCHOOSER_DIALOG_H #define XDG_DESKTOP_PORTAL_KDE_SCREENCHOOSER_DIALOG_H #include namespace Ui { class ScreenChooserDialog; } class ScreenChooserDialog : public QDialog { Q_OBJECT public: - ScreenChooserDialog(bool multiple, QDialog *parent = nullptr, Qt::WindowFlags flags = {}); + ScreenChooserDialog(const QString &appName, bool multiple, QDialog *parent = nullptr, Qt::WindowFlags flags = {}); ~ScreenChooserDialog(); QList selectedScreens() const; private: Ui::ScreenChooserDialog *m_dialog; }; #endif // XDG_DESKTOP_PORTAL_KDE_SCREENCHOOSER_DIALOG_H diff --git a/src/session.cpp b/src/session.cpp index 7128cbb..5e72aff 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -1,186 +1,197 @@ /* * 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_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::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 ScreenCastSession::setMultipleSources(bool multipleSources) { m_multipleSources = multipleSources; } RemoteDesktopSession::RemoteDesktopSession(QObject *parent, const QString &appId, const QString &path) : ScreenCastSession(parent, appId, path) + , m_screenSharingEnabled(false) { } RemoteDesktopSession::~RemoteDesktopSession() { } RemoteDesktopPortal::DeviceTypes RemoteDesktopSession::deviceTypes() const { return m_deviceTypes; } void RemoteDesktopSession::setDeviceTypes(RemoteDesktopPortal::DeviceTypes deviceTypes) { m_deviceTypes = deviceTypes; } + +bool RemoteDesktopSession::screenSharingEnabled() const +{ + return m_screenSharingEnabled; +} + +void RemoteDesktopSession::setScreenSharingEnabled(bool enabled) +{ + m_screenSharingEnabled = enabled; +} diff --git a/src/session.h b/src/session.h index 39414ac..184f59e 100644 --- a/src/session.h +++ b/src/session.h @@ -1,86 +1,96 @@ /* * 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(); + virtual SessionType type() const = 0; static Session *createSession(QObject *parent, SessionType type, const QString &appId, const QString &path); static Session *getSession(const QString &sessionHandle); Q_SIGNALS: void closed(); private: 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); + + SessionType type() const override { return SessionType::ScreenCast; } + 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); + bool screenSharingEnabled() const; + void setScreenSharingEnabled(bool enabled); + + SessionType type() const override { return SessionType::RemoteDesktop; } + private: + bool m_screenSharingEnabled; RemoteDesktopPortal::DeviceTypes m_deviceTypes; }; #endif // XDG_DESKTOP_PORTAL_KDE_SESSION_H diff --git a/src/waylandintegration.cpp b/src/waylandintegration.cpp index fe688b3..9d6af7f 100644 --- a/src/waylandintegration.cpp +++ b/src/waylandintegration.cpp @@ -1,414 +1,416 @@ /* * 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 #include #include #include #include // KWayland #include #include #include #include #include // system #include #include Q_LOGGING_CATEGORY(XdgDesktopPortalKdeWaylandIntegration, "xdp-kde-wayland-integration") Q_GLOBAL_STATIC(WaylandIntegration::WaylandIntegrationPrivate, globalWaylandIntegration) void WaylandIntegration::init() { globalWaylandIntegration->initDrm(); globalWaylandIntegration->initEGL(); globalWaylandIntegration->initWayland(); } void WaylandIntegration::bindOutput(int outputName, int outputVersion) { globalWaylandIntegration->bindOutput(outputName, outputVersion); } void WaylandIntegration::startStreaming() { globalWaylandIntegration->startStreaming(); } void WaylandIntegration::stopStreaming() { globalWaylandIntegration->stopStreaming(); } QMap WaylandIntegration::screens() { return globalWaylandIntegration->screens(); } WaylandIntegration::WaylandIntegration * WaylandIntegration::waylandIntegration() { return globalWaylandIntegration; } 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 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("VGA") || type.contains("DVI") || type.contains("HDMI") || type.contains("Panel") || type.contains("DisplayPort") || type.startsWith("DP") || type.contains("unknown")) { m_outputType = OutputType::Monitor; } else if (type.contains("TV")) { m_outputType = OutputType::Television; } else { m_outputType = OutputType::Monitor; } } WaylandIntegration::WaylandIntegrationPrivate::WaylandIntegrationPrivate() : WaylandIntegration() , m_registryInitialized(false) , m_connection(nullptr) , m_queue(nullptr) , m_registry(nullptr) , m_remoteAccessManager(nullptr) { } WaylandIntegration::WaylandIntegrationPrivate::~WaylandIntegrationPrivate() { if (m_remoteAccessManager) { m_remoteAccessManager->destroy(); } if (m_drmFd) { gbm_device_destroy(m_gbmDevice); } } 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::startStreaming() { m_streamingEnabled = true; if (!m_registryInitialized) { qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "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(XdgDesktopPortalKdeWaylandIntegration) << "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); }); }); } } void WaylandIntegration::WaylandIntegrationPrivate::stopStreaming() { - m_remoteAccessManager->release(); - m_remoteAccessManager->destroy(); + if (m_remoteAccessManager) { + m_remoteAccessManager->release(); + m_remoteAccessManager->destroy(); + } m_streamingEnabled = false; qDeleteAll(m_bindOutputs); m_bindOutputs.clear(); } QMap WaylandIntegration::WaylandIntegrationPrivate::screens() { return m_outputMap; } void WaylandIntegration::WaylandIntegrationPrivate::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 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. 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(XdgDesktopPortalKdeWaylandIntegration) << "Egl initialization succeeded"; qCDebug(XdgDesktopPortalKdeWaylandIntegration) << QString("EGL version: %1.%2").arg(major).arg(minor); } void WaylandIntegration::WaylandIntegrationPrivate::initWayland() { qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "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(); } 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.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) << 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(XdgDesktopPortalKdeWaylandIntegration) << "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()); Q_EMIT newBuffer(capture->bits()); gbm_bo_destroy(imported); glDeleteTextures(1, &texture); glDeleteFramebuffers(1, &framebuffer); eglDestroyImageKHR(m_egl.display, image); delete capture; close(gbmHandle); } void WaylandIntegration::WaylandIntegrationPrivate::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, &WaylandIntegrationPrivate::addOutput); connect(m_registry, &KWayland::Client::Registry::outputRemoved, this, &WaylandIntegrationPrivate::removeOutput); 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 08b3b79..c94beee 100644 --- a/src/waylandintegration.h +++ b/src/waylandintegration.h @@ -1,90 +1,89 @@ /* * 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 namespace WaylandIntegration { 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 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; QSize m_resolution; OutputType m_outputType; // Needed for later output binding int m_waylandOutputName; int m_waylandOutputVersion; }; class WaylandIntegration : public QObject { Q_OBJECT Q_SIGNALS: void newBuffer(uint8_t *screenData); }; void init(); void bindOutput(int outputName, int outputVersion); void startStreaming(); void stopStreaming(); QMap screens(); WaylandIntegration *waylandIntegration(); } #endif // XDG_DESKTOP_PORTAL_KDE_WAYLAND_INTEGRATION_H