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<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
|
||||
|
|
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(sms)
|
||||
add_subdirectory(screensaver-inhibit)
|
||||
add_subdirectory(virtualmonitor)
|
||||
|
||||
if(NOT WIN32)
|
||||
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