virtualmonitor: implemented capabilities check
BUG: 485829 ## Summary Currently, the plugin just fails silently if the local device is missing the `krfb` package or if the remote device misses an `vnc://` protocol/scheme handler. You click the button and nothing happens. One issue is, that the plugin is considered `virtualmonitor.available` in the `DeviceDelegate.qml`, even if the check for `krfb-virtualmonitor` fails and no dbus-path is provided. I investigated the behavior a bit, but ignored it in the end as this MR benefits from being shown for device constellations that _could_ provide this feature. A warning is shown with brief instructions, how to get the plugin working correctly. - Check if krfb-virtualmonitor is available locally - Check default scheme handler for vnc:// on device (Linux) - Show warnings / reasons, if no connection could be established ## Test Plan Regarding if the devices have mentioned packages installed, we should see different behaviors. If the remote device has no VNC client, it can not connect to out server. _A warning should be shown._ If the local device hasn't the `krfb-virtualmonitor` available, the remote device couldn't connect. _A warning should be shown._ If both problems are present, _both warnings should be shown._ If none of these are present, no warning should be shown and we should try to establish a connection. The connection attempts failed? _A warning should be shown._
This commit is contained in:
parent
f1392b30a2
commit
b24d629802
5 changed files with 119 additions and 9 deletions
|
@ -36,7 +36,6 @@ QtObject {
|
||||||
function pluginsChanged() {
|
function pluginsChanged() {
|
||||||
availableResponse.setPendingCall(device.hasPlugin("kdeconnect_" + pluginName))
|
availableResponse.setPendingCall(device.hasPlugin("kdeconnect_" + pluginName))
|
||||||
iconResponse.setPendingCall(device.pluginIconName("kdeconnect_" + pluginName))
|
iconResponse.setPendingCall(device.pluginIconName("kdeconnect_" + pluginName))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property var vv: DBusAsyncResponse {
|
readonly property var vv: DBusAsyncResponse {
|
||||||
|
|
|
@ -23,6 +23,14 @@ PlasmaComponents.ItemDelegate
|
||||||
hoverEnabled: false
|
hoverEnabled: false
|
||||||
down: false
|
down: false
|
||||||
|
|
||||||
|
Kirigami.PromptDialog {
|
||||||
|
id: prompt
|
||||||
|
visible: false
|
||||||
|
showCloseButton: true
|
||||||
|
standardButtons: Kirigami.Dialog.NoButton
|
||||||
|
title: i18n("Virtual Monitor is not available")
|
||||||
|
}
|
||||||
|
|
||||||
DropArea {
|
DropArea {
|
||||||
id: fileDropArea
|
id: fileDropArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
@ -76,6 +84,11 @@ PlasmaComponents.ItemDelegate
|
||||||
device: root.device
|
device: root.device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VirtualMonitor {
|
||||||
|
id: virtualmonitor
|
||||||
|
device: root.device
|
||||||
|
}
|
||||||
|
|
||||||
PlasmaComponents.Label {
|
PlasmaComponents.Label {
|
||||||
id: deviceName
|
id: deviceName
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
@ -85,16 +98,25 @@ PlasmaComponents.ItemDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
PlasmaComponents.ToolButton {
|
PlasmaComponents.ToolButton {
|
||||||
VirtualMonitor {
|
|
||||||
id: vd
|
|
||||||
device: root.device
|
|
||||||
}
|
|
||||||
icon.name: "video-monitor"
|
icon.name: "video-monitor"
|
||||||
|
visible: virtualmonitor.available
|
||||||
text: i18n("Virtual Display")
|
text: i18n("Virtual Display")
|
||||||
visible: vd.available
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!vd.plugin.requestVirtualMonitor()) {
|
let err = "";
|
||||||
console.warn("Failed to create the virtual monitor")
|
if (virtualmonitor?.plugin?.hasRemoteVncClient === false) {
|
||||||
|
err = i18n("Remote device does not have a VNC client (eg. krdc) installed.");
|
||||||
|
}
|
||||||
|
if (virtualmonitor?.plugin?.isVirtualMonitorAvailable === false) {
|
||||||
|
err = (err ? err + "\n\n" : "")
|
||||||
|
+ i18n("The krfb package is required on the local device.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
prompt.subtitle = err;
|
||||||
|
prompt.visible = true;
|
||||||
|
} else if (!virtualmonitor.plugin.requestVirtualMonitor()) {
|
||||||
|
prompt.subtitle = i18n("Failed to create the virtual monitor.");
|
||||||
|
prompt.visible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,10 @@
|
||||||
"Name[x-test]": "xxAleix Pol i Gonzalezxx",
|
"Name[x-test]": "xxAleix Pol i Gonzalezxx",
|
||||||
"Name[zh_CN]": "Aleix Pol i Gonzalez",
|
"Name[zh_CN]": "Aleix Pol i Gonzalez",
|
||||||
"Name[zh_TW]": "Aleix Pol i Gonzalez"
|
"Name[zh_TW]": "Aleix Pol i Gonzalez"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Email": "fabian.arndt@root-core.net",
|
||||||
|
"Name": "Fabian Arndt"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Description": "Use your devices as virtual monitors",
|
"Description": "Use your devices as virtual monitors",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* SPDX-FileCopyrightText: 2021 Aleix Pol i Gonzalez <aleixpol@kde.org>
|
* SPDX-FileCopyrightText: 2021 Aleix Pol i Gonzalez <aleixpol@kde.org>
|
||||||
|
* SPDX-FileCopyrightText: 2024 Fabian Arndt <fabian.arndt@root-core.net>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
*/
|
*/
|
||||||
|
@ -16,6 +17,12 @@
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
#include <QSettings>
|
||||||
|
#else
|
||||||
|
#include <KApplicationTrader>
|
||||||
|
#endif
|
||||||
|
|
||||||
K_PLUGIN_CLASS_WITH_JSON(VirtualMonitorPlugin, "kdeconnect_virtualmonitor.json")
|
K_PLUGIN_CLASS_WITH_JSON(VirtualMonitorPlugin, "kdeconnect_virtualmonitor.json")
|
||||||
#define QS QLatin1String
|
#define QS QLatin1String
|
||||||
static const int DEFAULT_PORT = 5901;
|
static const int DEFAULT_PORT = 5901;
|
||||||
|
@ -41,6 +48,13 @@ void VirtualMonitorPlugin::stop()
|
||||||
|
|
||||||
void VirtualMonitorPlugin::connected()
|
void VirtualMonitorPlugin::connected()
|
||||||
{
|
{
|
||||||
|
// Get local capabilities
|
||||||
|
// We test again, since the user may have installed the necessary packages in the meantime
|
||||||
|
m_capabilitiesLocal.vncClient = checkVncClient();
|
||||||
|
m_capabilitiesLocal.virtualMonitor = !QStandardPaths::findExecutable(QS("krfb-virtualmonitor")).isEmpty();
|
||||||
|
qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Local device supports VNC:" << m_capabilitiesLocal.vncClient;
|
||||||
|
qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Local device supports Virtual Monitor:" << m_capabilitiesLocal.virtualMonitor;
|
||||||
|
|
||||||
auto screen = QGuiApplication::primaryScreen();
|
auto screen = QGuiApplication::primaryScreen();
|
||||||
auto resolution = screen->size();
|
auto resolution = screen->size();
|
||||||
QString resolutionString = QString::number(resolution.width()) + QLatin1Char('x') + QString::number(resolution.height());
|
QString resolutionString = QString::number(resolution.width()) + QLatin1Char('x') + QString::number(resolution.height());
|
||||||
|
@ -51,6 +65,8 @@ void VirtualMonitorPlugin::connected()
|
||||||
{QS("resolution"), resolutionString},
|
{QS("resolution"), resolutionString},
|
||||||
{QS("scale"), screen->devicePixelRatio()},
|
{QS("scale"), screen->devicePixelRatio()},
|
||||||
}}},
|
}}},
|
||||||
|
{QS("supports_vnc"), m_capabilitiesLocal.vncClient},
|
||||||
|
{QS("supports_virt_mon"), m_capabilitiesLocal.virtualMonitor},
|
||||||
});
|
});
|
||||||
sendPacket(np);
|
sendPacket(np);
|
||||||
}
|
}
|
||||||
|
@ -92,13 +108,19 @@ void VirtualMonitorPlugin::receivePacket(const NetworkPacket &received)
|
||||||
if (received.has(QS("failed"))) {
|
if (received.has(QS("failed"))) {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get device's capabilities
|
||||||
|
m_capabilitiesRemote.vncClient = received.get<bool>(QS("supports_vnc"), false);
|
||||||
|
m_capabilitiesRemote.virtualMonitor = received.get<bool>(QS("supports_virt_mon"), false);
|
||||||
|
qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Remote device supports VNC:" << m_capabilitiesRemote.vncClient;
|
||||||
|
qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Remote device supports Virtual Monitor:" << m_capabilitiesRemote.virtualMonitor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString VirtualMonitorPlugin::dbusPath() const
|
QString VirtualMonitorPlugin::dbusPath() const
|
||||||
{
|
{
|
||||||
// Don't offer the feature if krfb-virtualmonitor is not around
|
// Don't offer the feature if krfb-virtualmonitor is not around
|
||||||
if (QStandardPaths::findExecutable(QS("krfb-virtualmonitor")).isEmpty())
|
if (!m_capabilitiesLocal.virtualMonitor)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return QLatin1String("/modules/kdeconnect/devices/%1/virtualmonitor").arg(device()->id());
|
return QLatin1String("/modules/kdeconnect/devices/%1/virtualmonitor").arg(device()->id());
|
||||||
|
@ -159,5 +181,43 @@ bool VirtualMonitorPlugin::requestVirtualMonitor()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VirtualMonitorPlugin::checkVncClient() const
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_MAC)
|
||||||
|
// TODO: macOS has some VNC client preinstalled.. does it work for us?
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
// krdc is default on KDE
|
||||||
|
if (!QStandardPaths::findExecutable(QS("krdc")).isEmpty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Check if another client is available
|
||||||
|
return checkDefaultSchemeHandler(QS("vnc"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VirtualMonitorPlugin::checkDefaultSchemeHandler(const QString &scheme) const
|
||||||
|
{
|
||||||
|
// TODO: macOS built-in tool: defaults read com.apple.LaunchServices/com.apple.launchservices.secure
|
||||||
|
// A function to use such a program was implemented and later removed in MR !670 (Commit e72f688ae1fad8846c006a3f87d57e507f6dbcb6)
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
// Scheme handlers are stored in the registry on Windows
|
||||||
|
// https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85)
|
||||||
|
|
||||||
|
// Check if url handler is present
|
||||||
|
QSettings handler(QS("HKEY_CLASSES_ROOT\\") + scheme, QSettings::Registry64Format);
|
||||||
|
if (handler.value("URL Protocol").isNull())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check if a command is actually present
|
||||||
|
QSettings command(QS("HKEY_CLASSES_ROOT\\") + scheme + QS("\\shell\\open\\command"), QSettings::Registry64Format);
|
||||||
|
return !command.value("Default").isNull();
|
||||||
|
#else
|
||||||
|
KService::Ptr service = KApplicationTrader::preferredService(QS("x-scheme-handler/") + scheme);
|
||||||
|
const QString defaultApp = service ? service->desktopEntryName() : QString();
|
||||||
|
return !defaultApp.isEmpty();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_virtualmonitorplugin.cpp"
|
#include "moc_virtualmonitorplugin.cpp"
|
||||||
#include "virtualmonitorplugin.moc"
|
#include "virtualmonitorplugin.moc"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* SPDX-FileCopyrightText: 2021 Aleix Pol i Gonzalez <aleixpol@kde.org>
|
* SPDX-FileCopyrightText: 2021 Aleix Pol i Gonzalez <aleixpol@kde.org>
|
||||||
|
* SPDX-FileCopyrightText: 2024 Fabian Arndt <fabian.arndt@root-core.net>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
*/
|
*/
|
||||||
|
@ -19,6 +20,8 @@ class VirtualMonitorPlugin : public KdeConnectPlugin
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.virtualmonitor")
|
Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.virtualmonitor")
|
||||||
|
Q_PROPERTY(bool hasRemoteVncClient READ hasRemoteVncClient CONSTANT)
|
||||||
|
Q_PROPERTY(bool isVirtualMonitorAvailable READ isVirtualMonitorAvailable CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using KdeConnectPlugin::KdeConnectPlugin;
|
using KdeConnectPlugin::KdeConnectPlugin;
|
||||||
|
@ -30,8 +33,30 @@ public:
|
||||||
QString dbusPath() const override;
|
QString dbusPath() const override;
|
||||||
void receivePacket(const NetworkPacket &np) override;
|
void receivePacket(const NetworkPacket &np) override;
|
||||||
|
|
||||||
|
// The remote device has a VNC client installed
|
||||||
|
bool hasRemoteVncClient() const
|
||||||
|
{
|
||||||
|
return m_capabilitiesRemote.vncClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
// krfb is installed on local device, virtual monitors is supported
|
||||||
|
bool isVirtualMonitorAvailable() const
|
||||||
|
{
|
||||||
|
return m_capabilitiesLocal.virtualMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void stop();
|
void stop();
|
||||||
|
bool checkVncClient() const;
|
||||||
|
bool checkDefaultSchemeHandler(const QString &scheme) const;
|
||||||
|
|
||||||
|
struct Capabilities {
|
||||||
|
bool virtualMonitor = false;
|
||||||
|
bool vncClient = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Capabilities m_capabilitiesLocal;
|
||||||
|
Capabilities m_capabilitiesRemote;
|
||||||
|
|
||||||
QProcess *m_process = nullptr;
|
QProcess *m_process = nullptr;
|
||||||
QJsonObject m_remoteResolution;
|
QJsonObject m_remoteResolution;
|
||||||
|
|
Loading…
Reference in a new issue