File indexing completed on 2024-04-28 04:58:58
0001 /* This file is part of the KDE project 0002 Copyright (C) 2018-2021 Jan Grulich <jgrulich@redhat.com> 0003 Copyright (C) 2018 Oleg Chernovskiy <kanedias@xaker.ru> 0004 0005 This program is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU General Public 0007 License as published by the Free Software Foundation; either 0008 version 3 of the License, or (at your option) any later version. 0009 */ 0010 0011 #include "config-krfb.h" 0012 0013 // system 0014 #include <sys/mman.h> 0015 #include <cstring> 0016 0017 // Qt 0018 #include <QCoreApplication> 0019 #include <QGuiApplication> 0020 #include <QScreen> 0021 #include <QSocketNotifier> 0022 #include <QDebug> 0023 #include <QRandomGenerator> 0024 0025 #include <KWayland/Client/connection_thread.h> 0026 #include <KWayland/Client/registry.h> 0027 0028 // pipewire 0029 #include <climits> 0030 0031 #include "pw_framebuffer.h" 0032 #include "xdp_dbus_screencast_interface.h" 0033 #include "xdp_dbus_remotedesktop_interface.h" 0034 #include "krfb_fb_pipewire_debug.h" 0035 #include "screencasting.h" 0036 #include <PipeWireSourceStream> 0037 #include <DmaBufHandler> 0038 0039 static const int BYTES_PER_PIXEL = 4; 0040 static const uint MIN_SUPPORTED_XDP_KDE_SC_VERSION = 1; 0041 0042 Q_DECLARE_METATYPE(PWFrameBuffer::Stream); 0043 Q_DECLARE_METATYPE(PWFrameBuffer::Streams); 0044 0045 const QDBusArgument &operator >> (const QDBusArgument &arg, PWFrameBuffer::Stream &stream) 0046 { 0047 arg.beginStructure(); 0048 arg >> stream.nodeId; 0049 0050 arg.beginMap(); 0051 while (!arg.atEnd()) { 0052 QString key; 0053 QVariant map; 0054 arg.beginMapEntry(); 0055 arg >> key >> map; 0056 arg.endMapEntry(); 0057 stream.map.insert(key, map); 0058 } 0059 arg.endMap(); 0060 arg.endStructure(); 0061 0062 return arg; 0063 } 0064 0065 /** 0066 * @brief The PWFrameBuffer::Private class - private counterpart of PWFramebuffer class. This is the entity where 0067 * whole logic resides, for more info search for "d-pointer pattern" information. 0068 */ 0069 class PWFrameBuffer::Private { 0070 public: 0071 Private(PWFrameBuffer *q); 0072 ~Private(); 0073 0074 private: 0075 friend class PWFrameBuffer; 0076 0077 void initDbus(); 0078 0079 // dbus handling 0080 void handleSessionCreated(quint32 code, const QVariantMap &results); 0081 void handleDevicesSelected(quint32 code, const QVariantMap &results); 0082 void handleSourcesSelected(quint32 code, const QVariantMap &results); 0083 void handleRemoteDesktopStarted(quint32 code, const QVariantMap &results); 0084 void setVideoSize(const QSize &size); 0085 0086 // pw handling 0087 void handleFrame(const PipeWireFrame &frame); 0088 0089 // link to public interface 0090 PWFrameBuffer *q; 0091 0092 // requests a session from XDG Desktop Portal 0093 // auto-generated and compiled from xdp_dbus_interface.xml file 0094 QScopedPointer<OrgFreedesktopPortalScreenCastInterface> dbusXdpScreenCastService; 0095 QScopedPointer<OrgFreedesktopPortalRemoteDesktopInterface> dbusXdpRemoteDesktopService; 0096 0097 // XDP screencast session handle 0098 QDBusObjectPath sessionPath; 0099 0100 // screen geometry holder 0101 QSize videoSize; 0102 0103 // sanity indicator 0104 bool isValid = true; 0105 std::unique_ptr<PipeWireSourceStream> stream; 0106 std::optional<PipeWireCursor> cursor; 0107 DmaBufHandler m_dmabufHandler; 0108 }; 0109 0110 PWFrameBuffer::Private::Private(PWFrameBuffer *q) 0111 : q(q) 0112 , stream(new PipeWireSourceStream(q)) 0113 { 0114 QObject::connect(stream.get(), &PipeWireSourceStream::frameReceived, q, [this] (const PipeWireFrame &frame) { 0115 handleFrame(frame); 0116 }); 0117 } 0118 0119 /** 0120 * @brief PWFrameBuffer::Private::initDbus - initialize D-Bus connectivity with XDG Desktop Portal. 0121 * Based on XDG_CURRENT_DESKTOP environment variable it will give us implementation that we need, 0122 * in case of KDE it is xdg-desktop-portal-kde binary. 0123 */ 0124 void PWFrameBuffer::Private::initDbus() 0125 { 0126 qInfo() << "Initializing D-Bus connectivity with XDG Desktop Portal"; 0127 dbusXdpScreenCastService.reset(new OrgFreedesktopPortalScreenCastInterface(QStringLiteral("org.freedesktop.portal.Desktop"), 0128 QStringLiteral("/org/freedesktop/portal/desktop"), 0129 QDBusConnection::sessionBus())); 0130 dbusXdpRemoteDesktopService.reset(new OrgFreedesktopPortalRemoteDesktopInterface(QStringLiteral("org.freedesktop.portal.Desktop"), 0131 QStringLiteral("/org/freedesktop/portal/desktop"), 0132 QDBusConnection::sessionBus())); 0133 auto version = dbusXdpScreenCastService->version(); 0134 if (version < MIN_SUPPORTED_XDP_KDE_SC_VERSION) { 0135 qCWarning(KRFB_FB_PIPEWIRE) << "Unsupported XDG Portal screencast interface version:" << version; 0136 isValid = false; 0137 return; 0138 } 0139 0140 // create session 0141 auto sessionParameters = QVariantMap { 0142 { QStringLiteral("session_handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) }, 0143 { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } 0144 }; 0145 auto sessionReply = dbusXdpRemoteDesktopService->CreateSession(sessionParameters); 0146 sessionReply.waitForFinished(); 0147 if (!sessionReply.isValid()) { 0148 qWarning("Couldn't initialize XDP-KDE screencast session"); 0149 isValid = false; 0150 return; 0151 } 0152 0153 qInfo() << "DBus session created: " << sessionReply.value().path(); 0154 QDBusConnection::sessionBus().connect(QString(), 0155 sessionReply.value().path(), 0156 QStringLiteral("org.freedesktop.portal.Request"), 0157 QStringLiteral("Response"), 0158 this->q, 0159 SLOT(handleXdpSessionCreated(uint, QVariantMap))); 0160 } 0161 0162 void PWFrameBuffer::handleXdpSessionCreated(quint32 code, const QVariantMap &results) 0163 { 0164 d->handleSessionCreated(code, results); 0165 } 0166 0167 /** 0168 * @brief PWFrameBuffer::Private::handleSessionCreated - handle creation of ScreenCast session. 0169 * XDG Portal answers with session path if it was able to successfully create the screencast. 0170 * 0171 * @param code return code for dbus call. Zero is success, non-zero means error 0172 * @param results map with results of call. 0173 */ 0174 void PWFrameBuffer::Private::handleSessionCreated(quint32 code, const QVariantMap &results) 0175 { 0176 if (code != 0) { 0177 qCWarning(KRFB_FB_PIPEWIRE) << "Failed to create session: " << code; 0178 isValid = false; 0179 return; 0180 } 0181 0182 sessionPath = QDBusObjectPath(results.value(QStringLiteral("session_handle")).toString()); 0183 0184 // select sources for the session 0185 auto selectionOptions = QVariantMap { 0186 // We have to specify it's an uint, otherwise xdg-desktop-portal will not forward it to backend implementation 0187 { QStringLiteral("types"), QVariant::fromValue<uint>(7) }, // request all (KeyBoard, Pointer, TouchScreen) 0188 { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } 0189 }; 0190 auto selectorReply = dbusXdpRemoteDesktopService->SelectDevices(sessionPath, selectionOptions); 0191 selectorReply.waitForFinished(); 0192 if (!selectorReply.isValid()) { 0193 qCWarning(KRFB_FB_PIPEWIRE) << "Couldn't select devices for the remote-desktop session"; 0194 isValid = false; 0195 return; 0196 } 0197 QDBusConnection::sessionBus().connect(QString(), 0198 selectorReply.value().path(), 0199 QStringLiteral("org.freedesktop.portal.Request"), 0200 QStringLiteral("Response"), 0201 this->q, 0202 SLOT(handleXdpDevicesSelected(uint, QVariantMap))); 0203 } 0204 0205 void PWFrameBuffer::handleXdpDevicesSelected(quint32 code, const QVariantMap &results) 0206 { 0207 d->handleDevicesSelected(code, results); 0208 } 0209 0210 /** 0211 * @brief PWFrameBuffer::Private::handleDevicesCreated - handle selection of devices we want to use for remote desktop 0212 * 0213 * @param code return code for dbus call. Zero is success, non-zero means error 0214 * @param results map with results of call. 0215 */ 0216 void PWFrameBuffer::Private::handleDevicesSelected(quint32 code, const QVariantMap &results) 0217 { 0218 Q_UNUSED(results) 0219 if (code != 0) { 0220 qCWarning(KRFB_FB_PIPEWIRE) << "Failed to select devices: " << code; 0221 isValid = false; 0222 return; 0223 } 0224 0225 // select sources for the session 0226 auto selectionOptions = QVariantMap { 0227 { QStringLiteral("types"), QVariant::fromValue<uint>(1) }, // only MONITOR is supported 0228 { QStringLiteral("multiple"), false }, 0229 { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } 0230 }; 0231 auto selectorReply = dbusXdpScreenCastService->SelectSources(sessionPath, selectionOptions); 0232 selectorReply.waitForFinished(); 0233 if (!selectorReply.isValid()) { 0234 qCWarning(KRFB_FB_PIPEWIRE) << "Couldn't select sources for the screen-casting session"; 0235 isValid = false; 0236 return; 0237 } 0238 QDBusConnection::sessionBus().connect(QString(), 0239 selectorReply.value().path(), 0240 QStringLiteral("org.freedesktop.portal.Request"), 0241 QStringLiteral("Response"), 0242 this->q, 0243 SLOT(handleXdpSourcesSelected(uint, QVariantMap))); 0244 } 0245 0246 void PWFrameBuffer::handleXdpSourcesSelected(quint32 code, const QVariantMap &results) 0247 { 0248 d->handleSourcesSelected(code, results); 0249 } 0250 0251 /** 0252 * @brief PWFrameBuffer::Private::handleSourcesSelected - handle Screencast sources selection. 0253 * XDG Portal shows a dialog at this point which allows you to select monitor from the list. 0254 * This function is called after you make a selection. 0255 * 0256 * @param code return code for dbus call. Zero is success, non-zero means error 0257 * @param results map with results of call. 0258 */ 0259 void PWFrameBuffer::Private::handleSourcesSelected(quint32 code, const QVariantMap &) 0260 { 0261 if (code != 0) { 0262 qCWarning(KRFB_FB_PIPEWIRE) << "Failed to select sources: " << code; 0263 isValid = false; 0264 return; 0265 } 0266 0267 // start session 0268 auto startParameters = QVariantMap { 0269 { QStringLiteral("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } 0270 }; 0271 auto startReply = dbusXdpRemoteDesktopService->Start(sessionPath, QString(), startParameters); 0272 startReply.waitForFinished(); 0273 QDBusConnection::sessionBus().connect(QString(), 0274 startReply.value().path(), 0275 QStringLiteral("org.freedesktop.portal.Request"), 0276 QStringLiteral("Response"), 0277 this->q, 0278 SLOT(handleXdpRemoteDesktopStarted(uint, QVariantMap))); 0279 } 0280 0281 0282 void PWFrameBuffer::handleXdpRemoteDesktopStarted(quint32 code, const QVariantMap &results) 0283 { 0284 d->handleRemoteDesktopStarted(code, results); 0285 } 0286 0287 /** 0288 * @brief PWFrameBuffer::Private::handleScreencastStarted - handle Screencast start. 0289 * At this point there shall be ready pipewire stream to consume. 0290 * 0291 * @param code return code for dbus call. Zero is success, non-zero means error 0292 * @param results map with results of call. 0293 */ 0294 void PWFrameBuffer::Private::handleRemoteDesktopStarted(quint32 code, const QVariantMap &results) 0295 { 0296 if (code != 0) { 0297 qCWarning(KRFB_FB_PIPEWIRE) << "Failed to start screencast: " << code; 0298 isValid = false; 0299 return; 0300 } 0301 0302 if (results.value(QStringLiteral("devices")).toUInt() == 0) { 0303 qCWarning(KRFB_FB_PIPEWIRE) << "No devices were granted" << results; 0304 isValid = false; 0305 return; 0306 } 0307 0308 // there should be only one stream 0309 const Streams streams = qdbus_cast<Streams>(results.value(QStringLiteral("streams"))); 0310 if (streams.isEmpty()) { 0311 // maybe we should check deeper with qdbus_cast but this suffices for now 0312 qCWarning(KRFB_FB_PIPEWIRE) << "Failed to get screencast streams"; 0313 isValid = false; 0314 return; 0315 } 0316 0317 auto streamReply = dbusXdpScreenCastService->OpenPipeWireRemote(sessionPath, QVariantMap()); 0318 streamReply.waitForFinished(); 0319 if (!streamReply.isValid()) { 0320 qCWarning(KRFB_FB_PIPEWIRE) << "Couldn't open pipewire remote for the screen-casting session"; 0321 isValid = false; 0322 return; 0323 } 0324 0325 QDBusUnixFileDescriptor pipewireFd = streamReply.value(); 0326 if (!pipewireFd.isValid()) { 0327 qCWarning(KRFB_FB_PIPEWIRE) << "Couldn't get pipewire connection file descriptor"; 0328 isValid = false; 0329 return; 0330 } 0331 0332 if (!stream->createStream(streams.first().nodeId, pipewireFd.takeFileDescriptor())) { 0333 qCWarning(KRFB_FB_PIPEWIRE) << "Couldn't create the pipewire stream"; 0334 isValid = false; 0335 return; 0336 } 0337 setVideoSize(qdbus_cast<QSize>(streams.first().map[QStringLiteral("size")].value<QDBusArgument>())); 0338 } 0339 0340 void PWFrameBuffer::Private::handleFrame(const PipeWireFrame &frame) 0341 { 0342 cursor = frame.cursor; 0343 0344 if (!frame.dmabuf && !frame.dataFrame) { 0345 qCDebug(KRFB_FB_PIPEWIRE) << "Got empty buffer. The buffer possibly carried only " 0346 "information about the mouse cursor."; 0347 return; 0348 } 0349 0350 if (frame.dataFrame) { 0351 memcpy(q->fb, frame.dataFrame->data, frame.dataFrame->size.width() * frame.dataFrame->stride); 0352 setVideoSize(frame.dataFrame->size); 0353 } 0354 else if (frame.dmabuf) { 0355 QImage src((uchar*) q->fb, videoSize.width(), videoSize.height(), QImage::Format_RGB32); 0356 if (!m_dmabufHandler.downloadFrame(src, frame)) { 0357 stream->renegotiateModifierFailed(frame.format, frame.dmabuf->modifier); 0358 qCDebug(KRFB_FB_PIPEWIRE) << "Failed to download frame."; 0359 return; 0360 } 0361 setVideoSize(src.size()); 0362 } else { 0363 qCDebug(KRFB_FB_PIPEWIRE) << "Unknown kind of frame"; 0364 } 0365 0366 if (auto damage = frame.damage) { 0367 for (const auto &rect : *damage) { 0368 q->tiles.append(rect); 0369 } 0370 } else { 0371 q->tiles.append(QRect(0, 0, videoSize.width(), videoSize.height())); 0372 } 0373 } 0374 0375 void PWFrameBuffer::Private::setVideoSize(const QSize &size) 0376 { 0377 if (q->fb && videoSize == size) { 0378 return; 0379 } 0380 0381 free(q->fb); 0382 q->fb = static_cast<char*>(malloc(size.width() * size.height() * BYTES_PER_PIXEL)); 0383 if (!q->fb) { 0384 qCWarning(KRFB_FB_PIPEWIRE) << "Failed to allocate buffer"; 0385 isValid = false; 0386 return; 0387 } 0388 videoSize = size; 0389 0390 Q_EMIT q->frameBufferChanged(); 0391 } 0392 0393 PWFrameBuffer::Private::~Private() 0394 { 0395 } 0396 0397 PWFrameBuffer::PWFrameBuffer(QObject *parent) 0398 : FrameBuffer (parent), 0399 d(new Private(this)) 0400 { 0401 } 0402 0403 PWFrameBuffer::~PWFrameBuffer() 0404 { 0405 free(fb); 0406 fb = nullptr; 0407 } 0408 0409 void PWFrameBuffer::initDBus() 0410 { 0411 d->initDbus(); 0412 } 0413 0414 void PWFrameBuffer::startVirtualMonitor(const QString& name, const QSize& resolution, qreal dpr) 0415 { 0416 d->videoSize = resolution * dpr; 0417 using namespace KWayland::Client; 0418 auto connection = ConnectionThread::fromApplication(this); 0419 if (!connection) { 0420 qWarning() << "Failed getting Wayland connection from QPA"; 0421 QCoreApplication::exit(1); 0422 return; 0423 } 0424 0425 auto registry = new Registry(this); 0426 connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry, name, dpr, resolution] (const QByteArray &interfaceName, quint32 wlname, quint32 version) { 0427 if (interfaceName != "zkde_screencast_unstable_v1") 0428 return; 0429 0430 auto screencasting = new Screencasting(registry, wlname, version, this); 0431 auto r = screencasting->createVirtualMonitorStream(name, resolution, dpr, Screencasting::Metadata); 0432 connect(r, &ScreencastingStream::created, this, [this] (quint32 nodeId) { 0433 d->stream->createStream(nodeId, 0); 0434 }); 0435 }); 0436 registry->create(connection); 0437 registry->setup(); 0438 } 0439 0440 int PWFrameBuffer::depth() 0441 { 0442 return 32; 0443 } 0444 0445 int PWFrameBuffer::height() 0446 { 0447 if (!d->videoSize.isValid()) { 0448 return 0; 0449 } 0450 return d->videoSize.height(); 0451 } 0452 0453 int PWFrameBuffer::width() 0454 { 0455 if (!d->videoSize.isValid()) { 0456 return 0; 0457 } 0458 return d->videoSize.width(); 0459 } 0460 0461 int PWFrameBuffer::paddedWidth() 0462 { 0463 return width() * 4; 0464 } 0465 0466 void PWFrameBuffer::getServerFormat(rfbPixelFormat &format) 0467 { 0468 format.bitsPerPixel = 32; 0469 format.depth = 32; 0470 format.trueColour = true; 0471 format.bigEndian = false; 0472 } 0473 0474 void PWFrameBuffer::startMonitor() 0475 { 0476 0477 } 0478 0479 void PWFrameBuffer::stopMonitor() 0480 { 0481 0482 } 0483 0484 QVariant PWFrameBuffer::customProperty(const QString &property) const 0485 { 0486 if (property == QLatin1String("stream_node_id")) { 0487 return QVariant::fromValue<uint>(d->stream->nodeId()); 0488 } if (property == QLatin1String("session_handle")) { 0489 return QVariant::fromValue<QDBusObjectPath>(d->sessionPath); 0490 } 0491 0492 return FrameBuffer::customProperty(property); 0493 } 0494 0495 bool PWFrameBuffer::isValid() const 0496 { 0497 return d->isValid; 0498 } 0499 0500 QPoint PWFrameBuffer::cursorPosition() 0501 { 0502 return d->cursor->position; 0503 }