Commit 084bfebc authored by Aleix Pol Gonzalez's avatar Aleix Pol Gonzalez 🐧
Browse files

Introduce the VirtualMonitor plugin

It allows to use other paired devices as external displays
transparently.
parent 8010739a
Pipeline #180538 passed with stage
in 4 minutes and 58 seconds
......@@ -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)
......
......@@ -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
......
......@@ -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()
{
}
......@@ -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)
{
......
......@@ -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
......
/**
* 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
}
......@@ -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)
......
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
)
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.
{
"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"
]
}
/**
* 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"
/**
* 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
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment