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
+
+
+
+
+
+
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