Introduce the VirtualMonitor plugin
It allows to use other paired devices as external displays transparently.
This commit is contained in:
parent
8010739a8a
commit
084bfebcc8
12 changed files with 299 additions and 0 deletions
|
@ -99,6 +99,7 @@ void KdeConnectDeclarativePlugin::registerTypes(const char* uri)
|
||||||
registerFactory<ShareDbusInterface>(uri, "ShareDbusInterfaceFactory");
|
registerFactory<ShareDbusInterface>(uri, "ShareDbusInterfaceFactory");
|
||||||
registerFactory<RemoteSystemVolumeDbusInterface>(uri, "RemoteSystemVolumeDbusInterfaceFactory");
|
registerFactory<RemoteSystemVolumeDbusInterface>(uri, "RemoteSystemVolumeDbusInterfaceFactory");
|
||||||
registerFactory<BigscreenDbusInterface>(uri, "BigscreenDbusInterfaceFactory");
|
registerFactory<BigscreenDbusInterface>(uri, "BigscreenDbusInterfaceFactory");
|
||||||
|
registerFactory<VirtualmonitorDbusInterface>(uri, "VirtualmonitorDbusInterfaceFactory");
|
||||||
}
|
}
|
||||||
|
|
||||||
void KdeConnectDeclarativePlugin::initializeEngine(QQmlEngine* engine, const char* uri)
|
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/share/shareplugin.h shareinterface)
|
||||||
geninterface(${PROJECT_SOURCE_DIR}/plugins/remotesystemvolume/remotesystemvolumeplugin.h remotesystemvolumeinterface)
|
geninterface(${PROJECT_SOURCE_DIR}/plugins/remotesystemvolume/remotesystemvolumeplugin.h remotesystemvolumeinterface)
|
||||||
geninterface(${PROJECT_SOURCE_DIR}/plugins/bigscreen/bigscreenplugin.h bigscreeninterface)
|
geninterface(${PROJECT_SOURCE_DIR}/plugins/bigscreen/bigscreenplugin.h bigscreeninterface)
|
||||||
|
geninterface(${PROJECT_SOURCE_DIR}/plugins/virtualmonitor/virtualmonitorplugin.h virtualmonitorinterface)
|
||||||
|
|
||||||
add_library(kdeconnectinterfaces ${libkdeconnect_SRC})
|
add_library(kdeconnectinterfaces ${libkdeconnect_SRC})
|
||||||
set_target_properties(kdeconnectinterfaces PROPERTIES
|
set_target_properties(kdeconnectinterfaces PROPERTIES
|
||||||
|
|
|
@ -200,3 +200,12 @@ BigscreenDbusInterface::BigscreenDbusInterface(const QString& deviceId, QObject*
|
||||||
BigscreenDbusInterface::~BigscreenDbusInterface()
|
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 "shareinterface.h"
|
||||||
#include "remotesystemvolumeinterface.h"
|
#include "remotesystemvolumeinterface.h"
|
||||||
#include "bigscreeninterface.h"
|
#include "bigscreeninterface.h"
|
||||||
|
#include "virtualmonitorinterface.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using these "proxy" classes just in case we need to rename the
|
* Using these "proxy" classes just in case we need to rename the
|
||||||
|
@ -259,6 +260,15 @@ public:
|
||||||
~BigscreenDbusInterface() override;
|
~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>
|
template <typename T, typename W>
|
||||||
static void setWhenAvailable(const QDBusPendingReply<T>& pending, W func, QObject* parent)
|
static void setWhenAvailable(const QDBusPendingReply<T>& pending, W func, QObject* parent)
|
||||||
{
|
{
|
||||||
|
|
|
@ -58,6 +58,7 @@ PlasmaComponents.ListItem
|
||||||
|
|
||||||
RowLayout
|
RowLayout
|
||||||
{
|
{
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
Battery {
|
Battery {
|
||||||
id: battery
|
id: battery
|
||||||
|
@ -77,6 +78,20 @@ PlasmaComponents.ListItem
|
||||||
textFormat: Text.PlainText
|
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
|
RowLayout
|
||||||
{
|
{
|
||||||
id: connectionInformation
|
id: connectionInformation
|
||||||
|
|
24
plasmoid/package/contents/ui/VirtualMonitor.qml
Normal file
24
plasmoid/package/contents/ui/VirtualMonitor.qml
Normal 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
|
||||||
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ if(NOT SAILFISHOS)
|
||||||
add_subdirectory(mousepad)
|
add_subdirectory(mousepad)
|
||||||
add_subdirectory(sms)
|
add_subdirectory(sms)
|
||||||
add_subdirectory(screensaver-inhibit)
|
add_subdirectory(screensaver-inhibit)
|
||||||
|
add_subdirectory(virtualmonitor)
|
||||||
|
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
add_subdirectory(sendnotifications)
|
add_subdirectory(sendnotifications)
|
||||||
|
|
21
plugins/virtualmonitor/CMakeLists.txt
Normal file
21
plugins/virtualmonitor/CMakeLists.txt
Normal 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
|
||||||
|
)
|
7
plugins/virtualmonitor/README
Normal file
7
plugins/virtualmonitor/README
Normal 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.
|
29
plugins/virtualmonitor/kdeconnect_virtualmonitor.json
Normal file
29
plugins/virtualmonitor/kdeconnect_virtualmonitor.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
138
plugins/virtualmonitor/virtualmonitorplugin.cpp
Normal file
138
plugins/virtualmonitor/virtualmonitorplugin.cpp
Normal 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"
|
43
plugins/virtualmonitor/virtualmonitorplugin.h
Normal file
43
plugins/virtualmonitor/virtualmonitorplugin.h
Normal 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
|
Loading…
Reference in a new issue