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