File indexing completed on 2024-04-14 04:51:52

0001 /**
0002  * SPDX-FileCopyrightText: 2021 Aleix Pol i Gonzalez <aleixpol@kde.org>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "virtualmonitorplugin.h"
0008 
0009 #include <KPluginFactory>
0010 
0011 #include "plugin_virtualmonitor_debug.h"
0012 #include <QDesktopServices>
0013 #include <QGuiApplication>
0014 #include <QJsonArray>
0015 #include <QProcess>
0016 #include <QScreen>
0017 #include <QStandardPaths>
0018 
0019 K_PLUGIN_CLASS_WITH_JSON(VirtualMonitorPlugin, "kdeconnect_virtualmonitor.json")
0020 #define QS QLatin1String
0021 
0022 VirtualMonitorPlugin::~VirtualMonitorPlugin()
0023 {
0024     stop();
0025 }
0026 
0027 void VirtualMonitorPlugin::stop()
0028 {
0029     if (!m_process)
0030         return;
0031 
0032     m_process->terminate();
0033     if (!m_process->waitForFinished()) {
0034         m_process->kill();
0035         m_process->waitForFinished();
0036     }
0037     delete m_process;
0038     m_process = nullptr;
0039 }
0040 
0041 void VirtualMonitorPlugin::connected()
0042 {
0043     auto screen = QGuiApplication::primaryScreen();
0044     auto resolution = screen->size();
0045     QString resolutionString = QString::number(resolution.width()) + QLatin1Char('x') + QString::number(resolution.height());
0046     NetworkPacket np(PACKET_TYPE_VIRTUALMONITOR,
0047                      {
0048                          {QS("resolutions"),
0049                           QJsonArray{QJsonObject{
0050                               {QS("resolution"), resolutionString},
0051                               {QS("scale"), screen->devicePixelRatio()},
0052                           }}},
0053                      });
0054     sendPacket(np);
0055 }
0056 
0057 void VirtualMonitorPlugin::receivePacket(const NetworkPacket &received)
0058 {
0059     if (received.type() == PACKET_TYPE_VIRTUALMONITOR_REQUEST && received.has(QS("url"))) {
0060         QUrl url(received.get<QString>(QS("url")));
0061         if (!QDesktopServices::openUrl(url)) {
0062             qCWarning(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Failed to open" << url.toDisplayString();
0063             NetworkPacket np(PACKET_TYPE_VIRTUALMONITOR, {{QS("failed"), 0}});
0064             sendPacket(np);
0065         }
0066     } else if (received.type() == PACKET_TYPE_VIRTUALMONITOR) {
0067         if (received.has(QS("resolutions"))) {
0068             m_remoteResolution = received.get<QJsonArray>(QS("resolutions")).first().toObject();
0069         }
0070         if (received.has(QS("failed"))) {
0071             stop();
0072         }
0073     }
0074 }
0075 
0076 QString VirtualMonitorPlugin::dbusPath() const
0077 {
0078     // Don't offer the feature if krfb-virtualmonitor is not around
0079     if (QStandardPaths::findExecutable(QS("krfb-virtualmonitor")).isEmpty())
0080         return {};
0081 
0082     return QLatin1String("/modules/kdeconnect/devices/%1/virtualmonitor").arg(device()->id());
0083 }
0084 
0085 bool VirtualMonitorPlugin::requestVirtualMonitor()
0086 {
0087     stop();
0088     if (m_remoteResolution.isEmpty()) {
0089         qCWarning(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Cannot start a request without a resolution";
0090         return false;
0091     }
0092 
0093     qCDebug(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Requesting virtual display " << device()->name();
0094 
0095     QUuid uuid = QUuid::createUuid();
0096     static int s_port = 5901;
0097     const QString port = QString::number(s_port++);
0098 
0099     m_process = new QProcess(this);
0100     m_process->setProgram(QS("krfb-virtualmonitor"));
0101     const double scale = m_remoteResolution.value(QLatin1String("scale")).toDouble();
0102     m_process->setArguments({QS("--name"),
0103                              device()->name(),
0104                              QS("--resolution"),
0105                              m_remoteResolution.value(QLatin1String("resolution")).toString(),
0106                              QS("--scale"),
0107                              QString::number(scale),
0108                              QS("--password"),
0109                              uuid.toString(),
0110                              QS("--port"),
0111                              port});
0112     connect(m_process, &QProcess::finished, this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
0113         qCWarning(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "virtual display finished with" << device()->name() << m_process->readAllStandardError();
0114 
0115         if (m_retries < 5 && (exitCode == 1 || exitStatus == QProcess::CrashExit)) {
0116             m_retries++;
0117             requestVirtualMonitor();
0118         } else {
0119             m_process->deleteLater();
0120             m_process = nullptr;
0121         }
0122     });
0123 
0124     m_process->start();
0125 
0126     if (!m_process->waitForStarted()) {
0127         qCWarning(KDECONNECT_PLUGIN_VIRTUALMONITOR) << "Failed to start krfb-virtualmonitor" << m_process->error() << m_process->errorString();
0128         return false;
0129     }
0130 
0131     QUrl url;
0132     url.setScheme(QS("vnc"));
0133     url.setUserName(QS("user"));
0134     url.setPassword(uuid.toString());
0135     url.setHost(device()->getLocalIpAddress().toString());
0136 
0137     NetworkPacket np(PACKET_TYPE_VIRTUALMONITOR_REQUEST, {{QS("url"), url.toEncoded()}});
0138     sendPacket(np);
0139     return true;
0140 }
0141 
0142 #include "moc_virtualmonitorplugin.cpp"
0143 #include "virtualmonitorplugin.moc"