virtualmonitor: Allow using krdpserver to extend to another device

This commit is contained in:
Aleix Pol 2024-08-20 01:50:55 +02:00
parent 0e389f0826
commit a2ec47b2a6
2 changed files with 106 additions and 5 deletions

View file

@ -46,13 +46,33 @@ void VirtualMonitorPlugin::stop()
m_process = nullptr; m_process = nullptr;
} }
static bool krdpHasVirtualMonitor()
{
static std::optional<bool> hasIt;
if (!hasIt.has_value()) {
QProcess p;
p.start(QS("krdpserver"), {QS("--help")});
if (!p.waitForStarted()) {
*hasIt = false;
return false;
}
if (!p.waitForFinished(1000)) {
*hasIt = false;
return false;
}
hasIt = p.readAllStandardOutput().contains("--virtual-monitor");
}
return *hasIt;
}
void VirtualMonitorPlugin::connected() void VirtualMonitorPlugin::connected()
{ {
// Get local capabilities // Get local capabilities
// We test again, since the user may have installed the necessary packages in the meantime // We test again, since the user may have installed the necessary packages in the meantime
m_capabilitiesLocal.vncClient = checkVncClient(); m_capabilitiesLocal.vncClient = checkVncClient();
m_capabilitiesLocal.virtualMonitor = !QStandardPaths::findExecutable(QS("krfb-virtualmonitor")).isEmpty(); m_capabilitiesLocal.rdpClient = checkRdpClient();
qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Local device supports VNC:" << m_capabilitiesLocal.vncClient; m_capabilitiesLocal.virtualMonitor = !QStandardPaths::findExecutable(QS("krfb-virtualmonitor")).isEmpty() || krdpHasVirtualMonitor();
qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Local device supports VNC:" << m_capabilitiesLocal.vncClient << "RDP: " << m_capabilitiesLocal.rdpClient;
qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Local device supports Virtual Monitor:" << m_capabilitiesLocal.virtualMonitor; qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Local device supports Virtual Monitor:" << m_capabilitiesLocal.virtualMonitor;
auto screen = QGuiApplication::primaryScreen(); auto screen = QGuiApplication::primaryScreen();
@ -66,6 +86,7 @@ void VirtualMonitorPlugin::connected()
{QS("scale"), screen->devicePixelRatio()}, {QS("scale"), screen->devicePixelRatio()},
}}}, }}},
{QS("supports_vnc"), m_capabilitiesLocal.vncClient}, {QS("supports_vnc"), m_capabilitiesLocal.vncClient},
{QS("supports_rdp"), m_capabilitiesLocal.rdpClient},
{QS("supports_virt_mon"), m_capabilitiesLocal.virtualMonitor}, {QS("supports_virt_mon"), m_capabilitiesLocal.virtualMonitor},
}); });
sendPacket(np); sendPacket(np);
@ -88,7 +109,11 @@ void VirtualMonitorPlugin::receivePacket(const NetworkPacket &received)
} }
QUrl url; QUrl url;
if (m_capabilitiesRemote.rdpClient) {
url.setScheme(received.get<QString>(QS("protocol"), QS("rdp")));
} else {
url.setScheme(received.get<QString>(QS("protocol"), QS("vnc"))); url.setScheme(received.get<QString>(QS("protocol"), QS("vnc")));
}
url.setUserName(received.get<QString>(QS("username"), QS("user"))); url.setUserName(received.get<QString>(QS("username"), QS("user")));
url.setPassword(received.get<QString>(QS("password"))); url.setPassword(received.get<QString>(QS("password")));
url.setPort(received.get<int>(QS("port"), DEFAULT_PORT)); url.setPort(received.get<int>(QS("port"), DEFAULT_PORT));
@ -111,6 +136,7 @@ void VirtualMonitorPlugin::receivePacket(const NetworkPacket &received)
// Get device's capabilities // Get device's capabilities
m_capabilitiesRemote.vncClient = received.get<bool>(QS("supports_vnc"), false); m_capabilitiesRemote.vncClient = received.get<bool>(QS("supports_vnc"), false);
m_capabilitiesRemote.rdpClient = received.get<bool>(QS("supports_rdp"), false);
m_capabilitiesRemote.virtualMonitor = received.get<bool>(QS("supports_virt_mon"), 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 VNC:" << m_capabilitiesRemote.vncClient;
qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Remote device supports Virtual Monitor:" << m_capabilitiesRemote.virtualMonitor; qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Remote device supports Virtual Monitor:" << m_capabilitiesRemote.virtualMonitor;
@ -131,9 +157,19 @@ bool VirtualMonitorPlugin::requestVirtualMonitor()
} }
qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Requesting virtual display " << device()->name(); qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Requesting virtual display " << device()->name();
bool ret = false;
if (m_capabilitiesRemote.rdpClient && krdpHasVirtualMonitor()) {
ret = requestRdp();
} else {
ret = requestVnc();
}
return ret;
}
uint VirtualMonitorPlugin::s_port = DEFAULT_PORT;
bool VirtualMonitorPlugin::requestVnc()
{
QUuid uuid = QUuid::createUuid(); QUuid uuid = QUuid::createUuid();
static int s_port = DEFAULT_PORT;
const QString port = QString::number(s_port++); const QString port = QString::number(s_port++);
m_process = new QProcess(this); m_process = new QProcess(this);
@ -154,7 +190,7 @@ bool VirtualMonitorPlugin::requestVirtualMonitor()
if (m_retries < 5 && (exitCode == 1 || exitStatus == QProcess::CrashExit)) { if (m_retries < 5 && (exitCode == 1 || exitStatus == QProcess::CrashExit)) {
m_retries++; m_retries++;
requestVirtualMonitor(); requestVnc();
} else { } else {
m_process->deleteLater(); m_process->deleteLater();
m_process = nullptr; m_process = nullptr;
@ -177,6 +213,55 @@ bool VirtualMonitorPlugin::requestVirtualMonitor()
return true; return true;
} }
bool VirtualMonitorPlugin::requestRdp()
{
QUuid uuid = QUuid::createUuid();
const QString port = QString::number(s_port++);
m_process = new QProcess(this);
m_process->setProgram(QS("krdpserver"));
const QString scale = m_remoteResolution.value(QLatin1String("scale")).toString();
QStringList args = {QS("--virtual-monitor"),
m_remoteResolution.value(QLatin1String("resolution")).toString() + u'@' + scale,
QS("--username"),
QS("user"),
QS("--password"),
uuid.toString(),
QS("--port"),
port};
static const bool s_plasma = qgetenv("XDG_CURRENT_DESKTOP").compare("KDE", Qt::CaseInsensitive) == 0;
if (s_plasma) {
args << QS("--plasma");
}
m_process->setArguments(args);
connect(m_process, &QProcess::finished, this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
qCWarning(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "krdp virtual display finished with" << device()->name() << m_process->readAllStandardError();
if (m_retries < 5 && (exitCode == 1 || exitStatus == QProcess::CrashExit)) {
m_retries++;
requestVnc();
} else {
m_process->deleteLater();
m_process = nullptr;
}
});
m_process->start();
if (!m_process->waitForStarted()) {
qCWarning(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Failed to start krfb-virtualmonitor" << m_process->error() << m_process->errorString();
return false;
}
NetworkPacket np(PACKET_TYPE_VIRTUALMONITOR_REQUEST);
np.set(QS("protocol"), QS("rdp"));
np.set(QS("username"), QS("user"));
np.set(QS("password"), uuid.toString());
np.set(QS("port"), port);
sendPacket(np);
return true;
}
bool VirtualMonitorPlugin::checkVncClient() const bool VirtualMonitorPlugin::checkVncClient() const
{ {
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
@ -192,6 +277,16 @@ bool VirtualMonitorPlugin::checkVncClient() const
#endif #endif
} }
bool VirtualMonitorPlugin::checkRdpClient() const
{
// krdc is default on KDE
if (!QStandardPaths::findExecutable(QS("krdc")).isEmpty())
return true;
// Check if another client is available
return checkDefaultSchemeHandler(QS("rdp"));
}
bool VirtualMonitorPlugin::checkDefaultSchemeHandler(const QString &scheme) const bool VirtualMonitorPlugin::checkDefaultSchemeHandler(const QString &scheme) const
{ {
// TODO: macOS built-in tool: defaults read com.apple.LaunchServices/com.apple.launchservices.secure // TODO: macOS built-in tool: defaults read com.apple.LaunchServices/com.apple.launchservices.secure

View file

@ -48,11 +48,15 @@ public:
private: private:
void stop(); void stop();
bool checkVncClient() const; bool checkVncClient() const;
bool checkRdpClient() const;
bool checkDefaultSchemeHandler(const QString &scheme) const; bool checkDefaultSchemeHandler(const QString &scheme) const;
bool requestVnc();
bool requestRdp();
struct Capabilities { struct Capabilities {
bool virtualMonitor = false; bool virtualMonitor = false;
bool vncClient = false; bool vncClient = false;
bool rdpClient = false;
}; };
Capabilities m_capabilitiesLocal; Capabilities m_capabilitiesLocal;
@ -61,4 +65,6 @@ private:
QProcess *m_process = nullptr; QProcess *m_process = nullptr;
QJsonObject m_remoteResolution; QJsonObject m_remoteResolution;
uint m_retries = 0; uint m_retries = 0;
static uint s_port;
}; };