Introduce the VirtualMonitor plugin

It allows to use other paired devices as external displays
transparently.
This commit is contained in:
Aleix Pol 2021-10-19 01:25:14 +02:00
parent 8010739a8a
commit 084bfebcc8
12 changed files with 299 additions and 0 deletions

View file

@ -99,6 +99,7 @@ void KdeConnectDeclarativePlugin::registerTypes(const char* uri)
registerFactory<ShareDbusInterface>(uri, "ShareDbusInterfaceFactory");
registerFactory<RemoteSystemVolumeDbusInterface>(uri, "RemoteSystemVolumeDbusInterfaceFactory");
registerFactory<BigscreenDbusInterface>(uri, "BigscreenDbusInterfaceFactory");
registerFactory<VirtualmonitorDbusInterface>(uri, "VirtualmonitorDbusInterfaceFactory");
}
void KdeConnectDeclarativePlugin::initializeEngine(QQmlEngine* engine, const char* uri)

View file

@ -56,6 +56,7 @@ geninterface(${PROJECT_SOURCE_DIR}/plugins/sms/conversationsdbusinterface.h conv
geninterface(${PROJECT_SOURCE_DIR}/plugins/share/shareplugin.h shareinterface)
geninterface(${PROJECT_SOURCE_DIR}/plugins/remotesystemvolume/remotesystemvolumeplugin.h remotesystemvolumeinterface)
geninterface(${PROJECT_SOURCE_DIR}/plugins/bigscreen/bigscreenplugin.h bigscreeninterface)
geninterface(${PROJECT_SOURCE_DIR}/plugins/virtualmonitor/virtualmonitorplugin.h virtualmonitorinterface)
add_library(kdeconnectinterfaces ${libkdeconnect_SRC})
set_target_properties(kdeconnectinterfaces PROPERTIES

View file

@ -200,3 +200,12 @@ BigscreenDbusInterface::BigscreenDbusInterface(const QString& deviceId, QObject*
BigscreenDbusInterface::~BigscreenDbusInterface()
{
}
VirtualmonitorDbusInterface::VirtualmonitorDbusInterface(const QString& deviceId, QObject* parent):
OrgKdeKdeconnectDeviceVirtualmonitorInterface(DaemonDbusInterface::activatedService(), QStringLiteral("/modules/kdeconnect/devices/") + deviceId + QStringLiteral("/virtualmonitor"), QDBusConnection::sessionBus(), parent)
{
}
VirtualmonitorDbusInterface::~VirtualmonitorDbusInterface()
{
}

View file

@ -27,6 +27,7 @@
#include "shareinterface.h"
#include "remotesystemvolumeinterface.h"
#include "bigscreeninterface.h"
#include "virtualmonitorinterface.h"
/**
* Using these "proxy" classes just in case we need to rename the
@ -259,6 +260,15 @@ public:
~BigscreenDbusInterface() override;
};
class KDECONNECTINTERFACES_EXPORT VirtualmonitorDbusInterface
: public OrgKdeKdeconnectDeviceVirtualmonitorInterface
{
Q_OBJECT
public:
explicit VirtualmonitorDbusInterface(const QString& deviceId, QObject* parent = nullptr);
~VirtualmonitorDbusInterface() override;
};
template <typename T, typename W>
static void setWhenAvailable(const QDBusPendingReply<T>& pending, W func, QObject* parent)
{

View file

@ -58,6 +58,7 @@ PlasmaComponents.ListItem
RowLayout
{
width: parent.width
Battery {
id: battery
@ -77,6 +78,20 @@ PlasmaComponents.ListItem
textFormat: Text.PlainText
}
PlasmaComponents3.ToolButton {
VirtualMonitor {
id: vd
device: root.device
}
icon.name: "video-monitor"
text: i18n("Virtual Display")
visible: vd.available
onClicked: {
if (!vd.plugin.requestVirtualMonitor()) {
console.warn("Failed to create the virtual monitor")
}
}
}
RowLayout
{
id: connectionInformation

View file

@ -0,0 +1,24 @@
/**
* SPDX-FileCopyrightText: 2021 Aleix Pol i Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.kdeconnect 1.0
QtObject
{
property alias device: checker.device
readonly property alias available: checker.available
readonly property PluginChecker pluginChecker: PluginChecker {
id: checker
pluginName: "virtualmonitor"
}
readonly property QtObject plugin: available ? VirtualmonitorDbusInterfaceFactory.create(device.id()) : null
}

View file

@ -36,6 +36,7 @@ if(NOT SAILFISHOS)
add_subdirectory(mousepad)
add_subdirectory(sms)
add_subdirectory(screensaver-inhibit)
add_subdirectory(virtualmonitor)
if(NOT WIN32)
add_subdirectory(sendnotifications)

View file

@ -0,0 +1,21 @@
set(debug_file_SRCS)
ecm_qt_declare_logging_category(
debug_file_SRCS HEADER plugin_virtualmonitor_debug.h
IDENTIFIER KDECONNECT_PLUGIN_VIRTUALMONITOR CATEGORY_NAME kdeconnect.plugin.virtualmonitor
DEFAULT_SEVERITY Warning
EXPORT kdeconnect-kde DESCRIPTION "kdeconnect (plugin virtualmonitor)")
set(kdeconnect_virtualmonitor_SRCS
virtualmonitorplugin.cpp
${debug_file_SRCS}
)
kdeconnect_add_plugin(kdeconnect_virtualmonitor
SOURCES ${kdeconnect_virtualmonitor_SRCS})
target_link_libraries(kdeconnect_virtualmonitor
kdeconnectcore
Qt5::Core
Qt5::Multimedia
Qt5::DBus
)

View file

@ -0,0 +1,7 @@
This plugin will allow users to use other kde connect devices as external displays.
It will use krfb-virtualmonitor to create it and feed the information and also request an URL to be opened on the other device that should be rendering the contents.
Upon connection, we'll be notifying under kdeconnect.virtualmonitor about our "resolutions, which include objects with a "resolution" and a "scale". These will provide the information necessary to issue a remote feed.
When a virtual monitor is requested, kdeconnect.virtualmonitor.request will include a "url" field that contains all information necessary to connect to the created feed. The remote client needs to support this url type.

View file

@ -0,0 +1,29 @@
{
"KPlugin": {
"Authors": [
{
"Email": "aleixpol@kde.org",
"Name": "Aleix Pol i Gonzalez"
}
],
"Name": "Virtual Monitor",
"Description": "Use your devices as virtual monitors",
"EnabledByDefault": true,
"Icon": "video-monitor",
"Id": "kdeconnect_virtualmonitor",
"License": "GPL",
"ServiceTypes": [
"KdeConnect/Plugin"
],
"Version": "0.1",
"Website": "https://kde.org"
},
"X-KdeConnect-OutgoingPacketType": [
"kdeconnect.virtualmonitor",
"kdeconnect.virtualmonitor.request"
],
"X-KdeConnect-SupportedPacketType": [
"kdeconnect.virtualmonitor",
"kdeconnect.virtualmonitor.request"
]
}

View file

@ -0,0 +1,138 @@
/**
* SPDX-FileCopyrightText: 2021 Aleix Pol i Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "virtualmonitorplugin.h"
#include <KPluginFactory>
#include <QDesktopServices>
#include <QGuiApplication>
#include <QJsonArray>
#include <QProcess>
#include <QScreen>
#include "plugin_virtualmonitor_debug.h"
K_PLUGIN_CLASS_WITH_JSON(VirtualMonitorPlugin, "kdeconnect_virtualmonitor.json")
#define QS QLatin1String
VirtualMonitorPlugin::VirtualMonitorPlugin(QObject* parent, const QVariantList& args)
: KdeConnectPlugin(parent, args)
{
}
VirtualMonitorPlugin::~VirtualMonitorPlugin()
{
stop();
}
void VirtualMonitorPlugin::stop()
{
if (!m_process)
return;
m_process->terminate();
if (!m_process->waitForFinished()) {
m_process->kill();
m_process->waitForFinished();
}
delete m_process;
m_process = nullptr;
}
void VirtualMonitorPlugin::connected()
{
auto screen = QGuiApplication::primaryScreen();
auto resolution = screen->size();
QString resolutionString = QString::number(resolution.width()) + QLatin1Char('x') + QString::number(resolution.height());
NetworkPacket np(PACKET_TYPE_VIRTUALMONITOR, {
{ QS("resolutions"), QJsonArray {
QJsonObject {
{ QS("resolution"), resolutionString },
{ QS("scale"), screen->devicePixelRatio() },
}
} },
});
sendPacket(np);
}
bool VirtualMonitorPlugin::receivePacket(const NetworkPacket& received)
{
if (received.type() == PACKET_TYPE_VIRTUALMONITOR_REQUEST && received.has(QS("url"))) {
QUrl url(received.get<QString>(QS("url")));
if (!QDesktopServices::openUrl(url)) {
qCWarning(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Failed to open" << url.toDisplayString();
NetworkPacket np(PACKET_TYPE_VIRTUALMONITOR, { { QS("failed"), 0 } });
sendPacket(np);
}
} else if (received.type() == PACKET_TYPE_VIRTUALMONITOR) {
if (received.has(QS("resolutions"))) {
m_remoteResolution = received.get<QJsonArray>(QS("resolutions")).first().toObject();
}
if (received.has(QS("failed"))) {
stop();
}
}
return true;
}
QString VirtualMonitorPlugin::dbusPath() const
{
// Don't offer the feature if krfb-virtualmonitor is not around
if (QStandardPaths::findExecutable(QS("krfb-virtualmonitor")).isEmpty())
return {};
return QS("/modules/kdeconnect/devices/") + device()->id() + QS("/virtualmonitor");
}
bool VirtualMonitorPlugin::requestVirtualMonitor()
{
stop();
if (m_remoteResolution.isEmpty()) {
qCWarning(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Cannot start a request without a resolution";
return false;
}
qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Requesting virtual display " << device()->name();
QUuid uuid = QUuid::createUuid();
static int s_port = 5901;
const QString port = QString::number(s_port++);
m_process = new QProcess(this);
m_process->setProgram(QS("krfb-virtualmonitor"));
const double scale = m_remoteResolution.value(QLatin1String("scale")).toDouble();
m_process->setArguments({QS("--name"), device()->name(), QS("--resolution"), m_remoteResolution.value(QLatin1String("resolution")).toString(), QS("--scale"), QString::number(scale), QS("--password"), uuid.toString(), QS("--port"), port});
connect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this] (int exitCode, QProcess::ExitStatus exitStatus) {
if (m_retries < 5 && (exitCode == 1 || exitStatus == QProcess::CrashExit)) {
m_retries++;
requestVirtualMonitor();
} else {
m_process->deleteLater();
}
qCWarning(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "virtual display finished with" << device()->name() << m_process->readAllStandardError();
});
m_process->start();
if (!m_process->waitForStarted()) {
qCWarning(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Failed to start krfb-virtualmonitor" << m_process->error() << m_process->errorString();
return false;
}
QUrl url;
url.setScheme(QS("vnc"));
url.setUserName(QS("user"));
url.setPassword(uuid.toString());
url.setHost(device()->getLocalIpAddress().toString());
NetworkPacket np(PACKET_TYPE_VIRTUALMONITOR_REQUEST, {
{ QS("url"), url.toEncoded() }
});
sendPacket(np);
return true;
}
#include "virtualmonitorplugin.moc"

View file

@ -0,0 +1,43 @@
/**
* SPDX-FileCopyrightText: 2021 Aleix Pol i Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef VIRTUALMONITORPLUGIN_H
#define VIRTUALMONITORPLUGIN_H
#include <core/kdeconnectplugin.h>
#include <QJsonObject>
#include "plugin_virtualmonitor_debug.h"
#define PACKET_TYPE_VIRTUALMONITOR QStringLiteral("kdeconnect.virtualmonitor")
#define PACKET_TYPE_VIRTUALMONITOR_REQUEST QStringLiteral("kdeconnect.virtualmonitor.request")
class QProcess;
class VirtualMonitorPlugin
: public KdeConnectPlugin
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.virtualmonitor")
public:
explicit VirtualMonitorPlugin(QObject* parent, const QVariantList& args);
~VirtualMonitorPlugin() override;
Q_SCRIPTABLE bool requestVirtualMonitor();
void connected() override;
QString dbusPath() const override;
bool receivePacket(const NetworkPacket& np) override;
private:
void stop();
QProcess *m_process = nullptr;
QJsonObject m_remoteResolution;
uint m_retries = 0;
};
#endif