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 }