Commit fcbbedd8 authored by Jan Grulich's avatar Jan Grulich

Make remote desktop portal to start screen sharing

parent da725066
......@@ -25,6 +25,7 @@ if (SCREENCAST_ENABLED)
set (xdg_desktop_portal_kde_SRCS
${xdg_desktop_portal_kde_SRCS}
screencast.cpp
screencastcommon.cpp
screencaststream.cpp
screencastwidget.cpp
screenchooserdialog.cpp
......
......@@ -19,25 +19,26 @@
*/
#include "remotedesktop.h"
#include "screencastcommon.h"
#include "session.h"
#include "remotedesktopdialog.h"
#include "waylandintegration.h"
#include <QDateTime>
#include <QtDBus/QtDBus>
#include <QDBusArgument>
#include <QDBusReply>
#include <QLoggingCategory>
#include <QStandardPaths>
#include <QPointer>
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,
......@@ -59,7 +60,7 @@ uint RemoteDesktopPortal::CreateSession(const QDBusObjectPath &handle,
}
connect(session, &Session::closed, [this] () {
// TODO
m_screenCastCommon->stopStreaming();
});
return 0;
......@@ -108,6 +109,40 @@ uint RemoteDesktopPortal::Start(const QDBusObjectPath &handle,
qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " parent_window: " << parent_window;
qCDebug(XdgDesktopPortalKdeRemoteDesktop) << " options: " << options;
RemoteDesktopSession *session = qobject_cast<RemoteDesktopSession*>(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, QScopedPointerDeleteLater> 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;
}
......
......@@ -24,7 +24,7 @@
#include <QDBusAbstractAdaptor>
#include <QDBusObjectPath>
class Session;
class ScreenCastCommon;
class RemoteDesktopPortal : public QDBusAbstractAdaptor
{
......@@ -121,7 +121,7 @@ public Q_SLOTS:
uint slot);
private:
ScreenCastCommon *m_screenCastCommon;
};
......
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#include "remotedesktopdialog.h"
#include "ui_remotedesktopdialog.h"
#include <QLoggingCategory>
#include <QStandardPaths>
#include <QSettings>
#include <QPushButton>
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<quint32> RemoteDesktopDialog::selectedScreens() const
{
return m_dialog->screenCastWidget->selectedScreens();
}
RemoteDesktopPortal::DeviceTypes RemoteDesktopDialog::deviceTypes() const
{
return 0;
}
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#ifndef XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_DIALOG_H
#define XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_DIALOG_H
#include <QAbstractListModel>
#include <QDialog>
#include "remotedesktop.h"
namespace Ui
{
class RemoteDesktopDialog;
}
class RemoteDesktopDialog : public QDialog
{
Q_OBJECT
public:
RemoteDesktopDialog(const QString &appName, RemoteDesktopPortal::DeviceTypes deviceTypes, bool screenSharingEnabled = false,
bool multiple = false, QDialog *parent = nullptr, Qt::WindowFlags flags = {});
~RemoteDesktopDialog();
QList<quint32> selectedScreens() const;
RemoteDesktopPortal::DeviceTypes deviceTypes() const;
private:
Ui::RemoteDesktopDialog * m_dialog;
};
#endif // XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_DIALOG_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RemoteDesktopDialog</class>
<widget class="QDialog" name="RemoteDesktopDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>452</width>
<height>286</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="ScreenCastWidget" name="screenCastWidget">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="uniformItemSizes">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Allow access to:</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="pointerCheckbox">
<property name="text">
<string>Pointer</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="keyboardCheckbox">
<property name="text">
<string>Keyboard</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="touchScreenCheckbox">
<property name="text">
<string>Touch screen</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ScreenCastWidget</class>
<extends>QListWidget</extends>
<header>screencastwidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
......@@ -19,75 +19,29 @@
*/
#include "screencast.h"
#include "session.h"
#include "screencaststream.h"
#include "screencastcommon.h"
#include "screenchooserdialog.h"
#include "session.h"
#include "waylandintegration.h"
#include <QDBusMetaType>
#include <QDBusError>
#include <QDBusArgument>
#include <QDBusConnection>
#include <QEventLoop>
#include <QLoggingCategory>
#include <QTimer>
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<ScreenCastPortal::Stream>();
qDBusRegisterMetaType<ScreenCastPortal::Streams>();
}
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,
......@@ -109,7 +63,9 @@ uint ScreenCastPortal::CreateSession(const QDBusObjectPath &handle,
}
connect(session, &Session::closed, [this] () {
stopStreaming();
if (m_screenCastCommon) {
m_screenCastCommon->stopStreaming();
}
});
return 0;
......@@ -151,6 +107,14 @@ uint ScreenCastPortal::SelectSources(const QDBusObjectPath &handle,
return 2;
}
// Might be also a RemoteDesktopSession
if (session->type() == Session::RemoteDesktop) {
RemoteDesktopSession *remoteDesktopSession = qobject_cast<RemoteDesktopSession*>(session);
if (remoteDesktopSession) {
remoteDesktopSession->setScreenSharingEnabled(true);
}
}
return 0;
}
......@@ -173,7 +137,7 @@ uint ScreenCastPortal::Start(const QDBusObjectPath &handle,
ScreenCastSession *session = qobject_cast<ScreenCastSession*>(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;
}
......@@ -183,52 +147,22 @@ uint ScreenCastPortal::Start(const QDBusObjectPath &handle,
return 2;
}
QScopedPointer<ScreenChooserDialog, QScopedPointerDeleteLater> screenDialog(new ScreenChooserDialog(session->multipleSources()));
QScopedPointer<ScreenChooserDialog, QScopedPointerDeleteLater> 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<ScreenCastPortal::Streams>({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;
}
}
......@@ -23,10 +23,8 @@
#include <QDBusAbstractAdaptor>
#include <QDBusObjectPath>
#include <QSize>
class ScreenChooserDialog;
class ScreenCastStream;
class ScreenCastCommon;
class ScreenCastPortal : public QDBusAbstractAdaptor
{
......@@ -35,12 +33,6 @@ class ScreenCastPortal : public QDBusAbstractAdaptor
Q_PROPERTY(uint version READ version)
Q_PROPERTY(uint AvailableSourceTypes READ AvailableSourceTypes)
public:
typedef struct {
uint nodeId;
QVariantMap map;
} Stream;
typedef QList<Stream> Streams;
enum SourceType {
Any = 0,
Monitor,
......@@ -73,13 +65,8 @@ public Q_SLOTS:
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
......
/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#include "screencastcommon.h"
#include "screencaststream.h"
#include <QDBusArgument>
#include <QDBusMetaType>
#include <QEventLoop>
#include <QTimer>
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<ScreenCastCommon::Stream>();
qDBusRegisterMetaType<ScreenCastCommon::Streams>();
}
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<ScreenCastCommon::Streams>({stream});
}
void ScreenCastCommon::stopStreaming()
{
if (m_streamingEnabled) {
WaylandIntegration::stopStreaming();
m_streamingEnabled = false;
if (m_stream) {
delete m_stream;
m_stream = nullptr;
}
}
}