File indexing completed on 2024-11-24 04:55:49

0001 // SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
0002 //
0003 // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0004 
0005 #include <csignal>
0006 #include <filesystem>
0007 
0008 #include <QCommandLineParser>
0009 #include <QDebug>
0010 #include <QGuiApplication>
0011 #include <QProcess>
0012 
0013 #include "Cursor.h"
0014 #include "InputHandler.h"
0015 #include "PortalSession.h"
0016 #include "RdpConnection.h"
0017 #include "Server.h"
0018 #include "VideoStream.h"
0019 #if WITH_PLASMA_SESSION == 1
0020 #include "PlasmaScreencastV1Session.h"
0021 #endif
0022 
0023 int main(int argc, char **argv)
0024 {
0025     QGuiApplication application{argc, argv};
0026     application.setApplicationName(u"krdp-server"_qs);
0027     application.setApplicationDisplayName(u"KRDP Example Server"_qs);
0028 
0029     QCommandLineParser parser;
0030     parser.setApplicationDescription(
0031         u"A basic RDP server that exposes the current desktop session over the RDP protocol.\nNote that a valid TLS certificate and key is needed. If not provided, a temporary certificate will be generated."_qs);
0032     parser.addHelpOption();
0033     parser.addOptions({
0034         {{u"u"_qs, u"username"_qs}, u"The username to use for login. Required."_qs, u"username"_qs},
0035             {{u"p"_qs, u"password"_qs}, u"The password to use for login. Required."_qs, u"password"_qs},
0036             {u"port"_qs, u"The port to use for connections. Defaults to 3389."_qs, u"port"_qs, u"3389"_qs},
0037             {u"certificate"_qs, u"The TLS certificate file to use."_qs, u"certificate"_qs, u"server.crt"_qs},
0038             {u"certificate-key"_qs, u"The TLS certificate key to use."_qs, u"certificate-key"_qs, u"server.key"_qs},
0039             {u"monitor"_qs, u"The index of the monitor to use when streaming."_qs, u"monitor"_qs, u"-1"_qs},
0040             {u"quality"_qs, u"Encoding quality of the stream, from 0 (lowest) to 100 (highest)"_qs, u"quality"_qs},
0041 #if WITH_PLASMA_SESSION == 1
0042             {u"plasma"_qs, u"Use Plasma protocols instead of XDP"_qs},
0043 #endif
0044     });
0045     parser.process(application);
0046 
0047     if (!parser.isSet(u"username"_qs) || !parser.isSet(u"password"_qs)) {
0048         qCritical() << "Error: A username and password needs to be provided.";
0049         parser.showHelp(1);
0050     }
0051 
0052     signal(SIGINT, [](int) {
0053         QCoreApplication::exit(0);
0054     });
0055 
0056     auto certificate = std::filesystem::path(parser.value(u"certificate"_qs).toStdString());
0057     auto certificateKey = std::filesystem::path(parser.value(u"certificate-key"_qs).toStdString());
0058     bool certificateGenerated = false;
0059 
0060     if (!std::filesystem::exists(certificate) || !std::filesystem::exists(certificateKey)) {
0061         qWarning() << "Could not find certificate and certificate key, generating temporary certificate...";
0062         QProcess sslProcess;
0063         sslProcess.start(u"openssl"_qs,
0064                          {
0065                              u"req"_qs,
0066                              u"-nodes"_qs,
0067                              u"-new"_qs,
0068                              u"-x509"_qs,
0069                              u"-keyout"_qs,
0070                              u"/tmp/krdp.key"_qs,
0071                              u"-out"_qs,
0072                              u"/tmp/krdp.crt"_qs,
0073                              u"-days"_qs,
0074                              u"1"_qs,
0075                              u"-batch"_qs,
0076                          });
0077         sslProcess.waitForFinished();
0078 
0079         certificate = std::filesystem::path("/tmp/krdp.crt");
0080         certificateKey = std::filesystem::path("/tmp/krdp.key");
0081         certificateGenerated = true;
0082 
0083         if (!std::filesystem::exists(certificate) || !std::filesystem::exists(certificateKey)) {
0084             qCritical() << "Could not generate a certificate and no certificate provided. A valid TLS certificate and key should be provided.";
0085             parser.showHelp(2);
0086         } else {
0087             qWarning() << "Temporary certificate generated; ready to accept connections.";
0088         }
0089     }
0090 
0091     quint32 monitorIndex = parser.value(u"monitor"_qs).toInt();
0092 
0093     KRdp::Server server;
0094 
0095     server.setAddress(QHostAddress::Any);
0096     server.setPort(parser.value(u"port"_qs).toInt());
0097     server.setUserName(parser.value(u"username"_qs));
0098     server.setPassword(parser.value(u"password"_qs));
0099     server.setTlsCertificate(certificate);
0100     server.setTlsCertificateKey(certificateKey);
0101 
0102     std::unique_ptr<KRdp::AbstractSession> session;
0103 #if WITH_PLASMA_SESSION == 1
0104     if (parser.isSet(u"plasma"_qs)) {
0105         session = std::unique_ptr<KRdp::AbstractSession>(new KRdp::PlasmaScreencastV1Session(&server));
0106     } else
0107 #endif
0108     {
0109         session = std::unique_ptr<KRdp::AbstractSession>(new KRdp::PortalSession(&server));
0110     }
0111     KRdp::AbstractSession *portalSession = session.get();
0112     portalSession->setActiveStream(monitorIndex);
0113     if (parser.isSet(u"quality"_qs)) {
0114         portalSession->setVideoQuality(parser.value(u"quality"_qs).toUShort());
0115     }
0116 
0117     QObject::connect(portalSession, &KRdp::AbstractSession::error, []() {
0118         QCoreApplication::exit(-1);
0119     });
0120 
0121     QObject::connect(&server, &KRdp::Server::newConnection, [&portalSession](KRdp::RdpConnection *newConnection) {
0122         QObject::connect(portalSession, &KRdp::AbstractSession::frameReceived, newConnection, [newConnection](const KRdp::VideoFrame &frame) {
0123             newConnection->videoStream()->queueFrame(frame);
0124         });
0125 
0126         QObject::connect(portalSession, &KRdp::AbstractSession::cursorUpdate, newConnection, [newConnection](const PipeWireCursor &cursor) {
0127             KRdp::Cursor::CursorUpdate update;
0128             update.hotspot = cursor.hotspot;
0129             update.image = cursor.texture;
0130             newConnection->cursor()->update(update);
0131         });
0132 
0133         QObject::connect(newConnection->videoStream(), &KRdp::VideoStream::enabledChanged, portalSession, [newConnection, portalSession]() {
0134             if (newConnection->videoStream()->enabled()) {
0135                 portalSession->requestStreamingEnable(newConnection->videoStream());
0136             } else {
0137                 portalSession->requestStreamingDisable(newConnection->videoStream());
0138             }
0139         });
0140 
0141         QObject::connect(newConnection->videoStream(), &KRdp::VideoStream::requestedFrameRateChanged, portalSession, [newConnection, portalSession]() {
0142             portalSession->setVideoFrameRate(newConnection->videoStream()->requestedFrameRate());
0143         });
0144 
0145         QObject::connect(newConnection->inputHandler(), &KRdp::InputHandler::inputEvent, portalSession, [portalSession](QEvent *event) {
0146             portalSession->sendEvent(event);
0147         });
0148     });
0149 
0150     if (!server.start()) {
0151         return -1;
0152     }
0153 
0154     auto result = application.exec();
0155     if (certificateGenerated) {
0156         std::filesystem::remove(certificate);
0157         std::filesystem::remove(certificateKey);
0158     }
0159     return result;
0160 }