Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qpipewire_screencapturehelper.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtMultimedia/private/qpipewire_instance_p.h>
7#include <QtMultimedia/private/qpipewire_videoformat_support_p.h>
8#include <QtMultimedia/private/qmemoryvideobuffer_p.h>
9#include <QtMultimedia/private/qpipewire_async_support_p.h>
10#include <QtMultimedia/private/qvideoframe_p.h>
11#include <QtGui/private/qdesktopunixservices_p.h>
12#include <QtGui/private/qguiapplication_p.h>
13#include <QtGui/qguiapplication.h>
14#include <QtGui/qpa/qplatformintegration.h>
15#include <QtCore/qloggingcategory.h>
16#include <QtCore/quuid.h>
17#include <QtCore/qvariantmap.h>
18#include <QtDBus/qdbusconnection.h>
19#include <QtDBus/qdbusinterface.h>
20#include <QtDBus/qdbusmessage.h>
21#include <QtDBus/qdbusreply.h>
22#include <QtDBus/qdbusunixfiledescriptor.h>
23
24#include <spa/debug/types.h>
25#include <spa/param/video/format-utils.h>
26#include <spa/param/video/type-info.h>
27
28#include <mutex>
29
30// pipewire's macros tend to emit unused value warnings
31QT_WARNING_PUSH
32QT_WARNING_DISABLE_CLANG("-Wunused-value")
33
34QT_BEGIN_NAMESPACE
35
36using namespace Qt::StringLiterals;
37using namespace std::chrono_literals;
38using namespace std::string_view_literals;
39
40Q_STATIC_LOGGING_CATEGORY(qLcPipeWireCapture, "qt.multimedia.pipewire.capture");
41Q_STATIC_LOGGING_CATEGORY(qLcPipeWireCaptureMore, "qt.multimedia.pipewire.capture.more");
42
43namespace QtPipeWire {
44
45QPipeWireCaptureHelper::QPipeWireCaptureHelper(QPipeWireCapture &capture,
46 std::shared_ptr<QPipeWireInstance> instance)
47 : QSurfaceCaptureGrabber(CreateGrabbingThread),
48 m_pwInstance(std::move(instance)),
49 m_requestTokenPrefix(QUuid::createUuid().toString(QUuid::Id128))
50{
51 Q_ASSERT(m_pwInstance);
52
53 addFrameCallback(&capture, &QPipeWireCapture::newVideoFrame);
54 connect(this, &QSurfaceCaptureGrabber::errorUpdated, &capture, &QPipeWireCapture::updateError);
55}
56
61
63{
64 return std::move(m_currentFrame);
65}
66
68{
69 QSurfaceCaptureGrabber::initializeGrabbingContext();
70 if (m_state == NoState)
71 createInterface();
72}
73
75{
76 unsubscribeFromRequestResponse();
77 m_operationState = NoOperation;
78
79 if (m_state != NoState)
80 destroy();
81 QSurfaceCaptureGrabber::finalizeGrabbingContext();
82}
83
85{
86 return m_videoFrameFormat;
87}
88
89void QPipeWireCaptureHelper::gotRequestResponse(uint result, const QVariantMap &map)
90{
91 qCDebug(qLcPipeWireCapture) << Q_FUNC_INFO << "result=" << result << "map=" << map;
92
93 // this is the response to the request we subscribed to, it will not be emitted again
94 unsubscribeFromRequestResponse();
95
96 if (result != 0) {
97 m_operationState = NoOperation;
98 qWarning() << "Failed to capture screen via pipewire, perhaps because user cancelled the "
99 "operation.";
100 return;
101 }
102
103 switch (m_operationState) {
104 case CreateSession:
105 selectSources(QDBusObjectPath{ map[u"session_handle"_s].toString() });
106 break;
107 case SelectSources:
108 startStream();
109 break;
110 case StartStream:
111 updateStreams(map[u"streams"_s].value<QDBusArgument>());
112 openPipeWireRemote();
113 m_operationState = NoOperation;
114 m_state = Streaming;
115 break;
116 case OpenPipeWireRemote:
117 m_operationState = NoOperation;
118 break;
119 default:
120 break;
121 }
122}
123
124QString QPipeWireCaptureHelper::allocateToken()
125{
126 return u"qtmm_%1_%2"_s.arg(m_requestTokenPrefix).arg(++m_tokenCounter);
127}
128
129bool QPipeWireCaptureHelper::subscribeToRequestResponse(const QString &handleToken)
130{
131 Q_ASSERT(m_pendingRequestPath.isEmpty());
132
133 QDBusConnection bus = QDBusConnection::sessionBus();
134
135 QString sender = bus.baseService(); // ":1.23"
136 if (sender.startsWith(u':'))
137 sender.remove(0, 1);
138 sender.replace(u'.', u'_');
139
140 QString path = u"/org/freedesktop/portal/desktop/request/%1/%2"_s.arg(sender, handleToken);
141
142 const bool ok = bus.connect(u"org.freedesktop.portal.Desktop"_s, path,
143 u"org.freedesktop.portal.Request"_s, u"Response"_s, this,
144 SLOT(gotRequestResponse(uint, QVariantMap)));
145 if (!ok) {
146 updateError(QPlatformSurfaceCapture::Error::InternalError,
147 u"Failed to subscribe to org.freedesktop.portal.Request response for "_s
148 + path);
149 return false;
150 }
151
152 m_pendingRequestPath = std::move(path);
153 return true;
154}
155
156void QPipeWireCaptureHelper::unsubscribeFromRequestResponse()
157{
158 if (m_pendingRequestPath.isEmpty())
159 return;
160
161 QDBusConnection::sessionBus().disconnect(u"org.freedesktop.portal.Desktop"_s,
162 m_pendingRequestPath,
163 u"org.freedesktop.portal.Request"_s, u"Response"_s,
164 this, SLOT(gotRequestResponse(uint, QVariantMap)));
165
166 m_pendingRequestPath.clear();
167}
168
169void QPipeWireCaptureHelper::createInterface()
170{
171 Q_ASSERT(m_pwInstance->hasScreenCastPortal());
172
173 m_operationState = NoOperation;
174
175 if (!m_screenCastInterface) {
176 m_screenCastInterface = std::make_unique<QDBusInterface>(
177 u"org.freedesktop.portal.Desktop"_s, u"/org/freedesktop/portal/desktop"_s,
178 u"org.freedesktop.portal.ScreenCast"_s, QDBusConnection::sessionBus());
179 }
180 createSession();
181}
182
183void QPipeWireCaptureHelper::createSession()
184{
185 if (!m_screenCastInterface)
186 return;
187
188 const QString handleToken = allocateToken();
189 if (!subscribeToRequestResponse(handleToken))
190 return;
191
192 QVariantMap options{
193 { u"handle_token"_s, handleToken },
194 { u"session_handle_token"_s, allocateToken() },
195 };
196 QDBusMessage reply = m_screenCastInterface->call(u"CreateSession"_s, options);
197 if (!reply.errorMessage().isEmpty()) {
198 unsubscribeFromRequestResponse();
199 updateError(QPlatformSurfaceCapture::Error::InternalError,
200 u"Failed to create session for org.freedesktop.portal.ScreenCast. Error: "_s
201 + reply.errorName() + u": "_s + reply.errorMessage());
202 return;
203 }
204
205 m_operationState = CreateSession;
206}
207
208void QPipeWireCaptureHelper::selectSources(const QDBusObjectPath &sessionHandle)
209{
210 if (!m_screenCastInterface)
211 return;
212
213 m_sessionHandle = sessionHandle;
214 m_sessionInterface = std::make_unique<QDBusInterface>(
215 u"org.freedesktop.portal.Desktop"_s, sessionHandle.path(),
216 u"org.freedesktop.portal.Session"_s, QDBusConnection::sessionBus());
217 m_sessionInterface->connection().connect(
218 u"org.freedesktop.portal.Desktop"_s, sessionHandle.path(),
219 u"org.freedesktop.portal.Session"_s, u"Closed"_s, this, SLOT(sessionClosed()));
220
221 const QString handleToken = allocateToken();
222 if (!subscribeToRequestResponse(handleToken))
223 return;
224
225 QVariantMap options{
226 { u"handle_token"_s, handleToken },
227 { u"types"_s, (uint)1 },
228 { u"multiple"_s, false },
229 { u"cursor_mode"_s, (uint)1 },
230 { u"persist_mode"_s, (uint)0 },
231 };
232 QDBusMessage reply = m_screenCastInterface->call(u"SelectSources"_s, sessionHandle, options);
233 if (!reply.errorMessage().isEmpty()) {
234 unsubscribeFromRequestResponse();
235 updateError(QPlatformSurfaceCapture::Error::InternalError,
236 u"Failed to select sources for org.freedesktop.portal.ScreenCast. Error: "_s
237 + reply.errorName() + u": "_s + reply.errorMessage());
238 return;
239 }
240
241 m_operationState = SelectSources;
242}
243
244void QPipeWireCaptureHelper::startStream()
245{
246 if (!m_screenCastInterface)
247 return;
248
249 const QString handleToken = allocateToken();
250 if (!subscribeToRequestResponse(handleToken))
251 return;
252
253 QVariantMap options{
254 { u"handle_token"_s, handleToken },
255 };
256
257 auto *unixServices = dynamic_cast<QDesktopUnixServices *>(QGuiApplicationPrivate::platformIntegration()->services());
258 const QString parentWindow = QGuiApplication::focusWindow() && unixServices
259 ? unixServices->portalWindowIdentifier(QGuiApplication::focusWindow())
260 : QString();
261 QDBusMessage reply = m_screenCastInterface->call("Start"_L1, QDBusObjectPath(m_sessionHandle),
262 parentWindow, options);
263 if (!reply.errorMessage().isEmpty()) {
264 unsubscribeFromRequestResponse();
265 updateError(QPlatformSurfaceCapture::Error::InternalError,
266 u"Failed to start stream for org.freedesktop.portal.ScreenCast. Error: "_s
267 + reply.errorName() + u": "_s + reply.errorMessage());
268 return;
269 }
270
271 m_operationState = StartStream;
272}
273
274void QPipeWireCaptureHelper::updateStreams(const QDBusArgument &streamsInfo)
275{
276 m_streams.clear();
277
278 streamsInfo.beginStructure();
279 streamsInfo.beginArray();
280
281 while (!streamsInfo.atEnd()) {
282 quint32 nodeId = 0;
283 streamsInfo >> nodeId;
284 QMap<QString, QVariant> properties;
285 streamsInfo >> properties;
286
287 qint32 x = 0;
288 qint32 y = 0;
289 if (properties.contains(u"position"_s)) {
290 const auto position = properties[u"position"_s].value<QDBusArgument>();
291 position.beginStructure();
292 position >> x;
293 position >> y;
294 position.endStructure();
295 }
296
297 qint32 width = 0;
298 qint32 height = 0;
299 if (properties.contains(u"size"_s)) {
300 const auto size = properties[u"size"_s].value<QDBusArgument>();
301 size.beginStructure();
302 size >> width;
303 size >> height;
304 size.endStructure();
305 }
306
307 uint sourceType = 0;
308 if (properties.contains(u"source_type"_s))
309 sourceType = properties[u"source_type"_s].toUInt();
310
311 StreamInfo streamInfo;
312 streamInfo.nodeId = nodeId;
313 streamInfo.sourceType = sourceType;
314 streamInfo.rect = {x, y, width, height};
315 m_streams << streamInfo;
316 }
317
318 streamsInfo.endArray();
319 streamsInfo.endStructure();
320
321}
322
323void QPipeWireCaptureHelper::openPipeWireRemote()
324{
325 if (!m_screenCastInterface)
326 return;
327
328 QVariantMap options;
329 QDBusReply<QDBusUnixFileDescriptor> reply =
330 m_screenCastInterface->call(u"OpenPipeWireRemote"_s, m_sessionHandle, options);
331 if (!reply.isValid()) {
332 updateError(
333 QPlatformSurfaceCapture::Error::InternalError,
334 u"Failed to open pipewire remote for org.freedesktop.portal.ScreenCast. Error: name="_s
335 + reply.error().name() + u", message="_s + reply.error().message());
336 return;
337 }
338
339 bool ok = open(reply.value());
340 qCDebug(qLcPipeWireCapture) << "open() result=" << ok;
341 if (!ok) {
342 updateError(QPlatformSurfaceCapture::Error::InternalError,
343 u"Failed to open pipewire remote file descriptor"_s);
344 return;
345 }
346
347 m_operationState = OpenPipeWireRemote;
348}
349
350bool QPipeWireCaptureHelper::open(QDBusUnixFileDescriptor portalFd)
351{
352 if (m_streams.isEmpty())
353 return false;
354
355 Q_ASSERT(m_pwInstance->hasScreenCastPortal());
356
357 std::unique_lock pwLock = m_pwInstance->eventLoopLock();
358 auto connection = m_pwInstance->connectToPortal(portalFd.takeFileDescriptor());
359 if (!connection) {
360 m_err = true;
361 pwLock.unlock();
362 updateError(QPlatformSurfaceCapture::Error::InternalError,
363 u"QPipeWireCaptureHelper failed at pw_context_connect_fd()."_s);
364 return false;
365 }
366 m_core = std::move(*connection);
367
368 m_registry = PwRegistryHandle{
369 pw_core_get_registry(m_core.get(), PW_VERSION_REGISTRY, 0),
370 };
371 if (!m_registry) {
372 m_err = true;
373 pwLock.unlock();
374 updateError(QPlatformSurfaceCapture::Error::InternalError,
375 u"QPipeWireCaptureHelper failed at pw_core_get_registry()."_s);
376 return false;
377 }
378
379 struct RegistryCallbackContext
380 {
381 bool hasSource{};
382 };
383
384 spa_hook registryListener = {};
385 const pw_registry_events registryEvents{
386 .version = PW_VERSION_REGISTRY_EVENTS,
387 .global =
388 [](void *data, uint32_t /*id*/, uint32_t /*permissions*/, const char *type,
389 uint32_t /*version*/, const spa_dict *props) {
390 if (type != std::string_view(PW_TYPE_INTERFACE_Node))
391 return;
392
393 const char *media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
394 if (!media_class)
395 return;
396
397 if (media_class != "Stream/Output/Video"sv && media_class != "Video/Source"sv)
398 return;
399 auto *registryContext = reinterpret_cast<RegistryCallbackContext *>(data);
400 registryContext->hasSource = true;
401 },
402 .global_remove = nullptr,
403 };
404
405 RegistryCallbackContext registryContext;
406 pw_registry_add_listener(m_registry.get(), &registryListener, &registryEvents,
407 &registryContext);
408
409 CoreEventDoneListener doneListener;
410 bool initDone = false;
411 doneListener.asyncWait(m_core.get(), [&] {
412 initDone = true;
413 m_pwInstance->pwEventLoop().signal(false);
414 });
415
416 while (!initDone) {
417 if (m_pwInstance->pwEventLoop().wait_for(2s) != 0)
418 break;
419 }
420
421 spa_hook_remove(&registryListener);
422
423 if (registryContext.hasSource)
424 recreateStream();
425
426 return initDone && registryContext.hasSource;
427}
428
429void QPipeWireCaptureHelper::recreateStream()
430{
431 static const pw_stream_events streamEvents = {
432 .version = PW_VERSION_STREAM_EVENTS,
433 .destroy = [](void *data) {
434 Q_UNUSED(data)
435 },
436 .state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) {
437 reinterpret_cast<QPipeWireCaptureHelper *>(data)->onStateChanged(old, state, error);
438 },
439 .control_info = [](void *data, uint32_t id, const struct pw_stream_control *control) {
440 Q_UNUSED(data)
441 Q_UNUSED(id)
442 Q_UNUSED(control)
443 },
444 .io_changed = [](void *data, uint32_t id, void *area, uint32_t size) {
445 Q_UNUSED(data)
446 Q_UNUSED(id)
447 Q_UNUSED(area)
448 Q_UNUSED(size)
449 },
450 .param_changed = [](void *data, uint32_t id, const struct spa_pod *param) {
451 reinterpret_cast<QPipeWireCaptureHelper *>(data)->onParamChanged(id, param);
452 },
453 .add_buffer = [](void *data, struct pw_buffer *buffer) {
454 Q_UNUSED(data)
455 Q_UNUSED(buffer)
456 },
457 .remove_buffer = [](void *data, struct pw_buffer *buffer) {
458 Q_UNUSED(data)
459 Q_UNUSED(buffer)
460 },
461 .process = [](void *data) {
462 reinterpret_cast<QPipeWireCaptureHelper *>(data)->onProcess();
463 },
464 .drained = [](void *data) {
465 Q_UNUSED(data)
466 },
467#if PW_VERSION_STREAM_EVENTS >= 1
468 .command = [](void *data, const struct spa_command *command) {
469 Q_UNUSED(data)
470 Q_UNUSED(command)
471 },
472#endif
473#if PW_VERSION_STREAM_EVENTS >= 2
474 .trigger_done = [](void *data) {
475 Q_UNUSED(data)
476 },
477#endif
478 };
479
480 destroyStream(true);
481
482 auto streamInfo = m_streams[0];
483 const struct std::array<spa_dict_item, 3> items{
484 SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_TYPE, "Video"),
485 SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_CATEGORY, "Capture"),
486 SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_ROLE, "Screen"),
487 };
488 const struct spa_dict info = SPA_DICT_INIT(items.data(), items.size());
489 auto *props = pw_properties_new_dict(&info);
490
491 std::unique_lock locker = m_pwInstance->eventLoopLock();
492
493 m_stream = PwStreamHandle{
494 pw_stream_new(m_core.get(), "video-capture", props),
495 };
496 if (!m_stream) {
497 m_err = true;
498 locker.unlock();
499 updateError(QPlatformSurfaceCapture::Error::InternalError,
500 u"QPipeWireCaptureHelper failed at pw_stream_new()."_s);
501 return;
502 }
503
504 m_streamListener = {};
505 pw_stream_add_listener(m_stream.get(), &m_streamListener, &streamEvents, this);
506
507 QT_WARNING_PUSH
508 // QTBUG-129587: libpipewire=1.2.5 warning
509 QT_WARNING_DISABLE_GCC("-Wmissing-field-initializers")
510 QT_WARNING_DISABLE_CLANG("-Wmissing-field-initializers")
511
512 std::array<uint8_t, 4096> buffer;
513 struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer.data(), sizeof(buffer));
514 struct spa_rectangle defsize = SPA_RECTANGLE(quint32(streamInfo.rect.width()), quint32(streamInfo.rect.height()));
515 struct spa_rectangle maxsize = SPA_RECTANGLE(4096, 4096);
516 struct spa_rectangle minsize = SPA_RECTANGLE(1,1);
517
518 // Considering the framerate as always variable rate, but with our target set as maximum.
519 struct spa_fraction maxrate = SPA_FRACTION(1000, 1);
520 struct spa_fraction minrate = SPA_FRACTION(0, 1);
521 auto rate = rateFromFps(frameRate());
522
523 std::array<const struct spa_pod *, 1> params{ static_cast<const spa_pod*>(spa_pod_builder_add_object(
524 &builder,
525 SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
526 SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
527 SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
528 SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(6,
529 SPA_VIDEO_FORMAT_RGB,
530 SPA_VIDEO_FORMAT_BGR,
531 SPA_VIDEO_FORMAT_RGBA,
532 SPA_VIDEO_FORMAT_BGRA,
533 SPA_VIDEO_FORMAT_RGBx,
534 SPA_VIDEO_FORMAT_BGRx),
535 SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
536 &defsize, &minsize, &maxsize),
537 SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(
538 &rate.frac, &minrate, &maxrate),
539 SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_Fraction(&rate.frac))
540 )};
541 QT_WARNING_POP
542
543 const int connectErr = pw_stream_connect(
544 m_stream.get(), PW_DIRECTION_INPUT, streamInfo.nodeId,
545 static_cast<pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS),
546 params.data(), 1);
547 if (connectErr != 0) {
548 m_err = true;
549 locker.unlock();
550 updateError(QPlatformSurfaceCapture::Error::InternalError,
551 u"QPipeWireCaptureHelper failed at pw_stream_connect()."_s);
552 return;
553 }
554}
555void QPipeWireCaptureHelper::destroyStream(bool waitForStreamEnd)
556{
557 if (!m_stream)
558 return;
559
560 std::unique_lock locker(m_pwInstance->eventLoopLock());
561 m_ignoreStateChange = true;
562 pw_stream_disconnect(m_stream.get());
563 m_stream = {};
564 m_ignoreStateChange = false;
565
566 if (waitForStreamEnd) {
567 while (m_streamState > PW_STREAM_STATE_UNCONNECTED && !m_err) {
568 if (m_pwInstance->pwEventLoop().wait_for(1s) != 0)
569 break;
570 }
571 }
572}
573
574void QPipeWireCaptureHelper::signalLoop(bool onProcessDone, bool err)
575{
576 if (err)
577 m_err = true;
578 if (onProcessDone)
579 m_processed = true;
580 m_pwInstance->pwEventLoop().signal(false);
581}
582
583void QPipeWireCaptureHelper::onStateChanged(pw_stream_state old, pw_stream_state state, const char *error)
584{
585 Q_UNUSED(old)
586 Q_UNUSED(error)
587
588 m_streamState = state;
589
590 switch (state) {
591 case PW_STREAM_STATE_UNCONNECTED:
592 signalLoop(false, true);
593 break;
594 case PW_STREAM_STATE_PAUSED:
595 signalLoop(false, false);
596 break;
597 case PW_STREAM_STATE_STREAMING:
598 signalLoop(false, false);
599 break;
600 default:
601 break;
602 }
603}
604void QPipeWireCaptureHelper::onProcess()
605{
606 struct pw_buffer *pwBuffer = pw_stream_dequeue_buffer(m_stream.get());
607 if (!pwBuffer) {
608 updateError(QPlatformSurfaceCapture::Error::InternalError,
609 u"Out of buffers in pipewire stream dequeue."_s);
610 return;
611 }
612
613 struct spa_buffer *buf = pwBuffer->buffer;
614 void *sdata = buf->datas[0].data;
615 if (!sdata)
616 return;
617
618 int sstride = buf->datas[0].chunk->stride;
619 if (sstride == 0)
620 sstride = int(buf->datas[0].chunk->size / m_size.height());
621 qsizetype size = buf->datas[0].chunk->size;
622
623 if (m_videoFrameFormat.frameSize() != m_size || m_videoFrameFormat.pixelFormat() != m_pixelFormat)
624 m_videoFrameFormat = QVideoFrameFormat(m_size, m_pixelFormat);
625
626 m_currentFrame = QVideoFramePrivate::createFrame(
627 std::make_unique<QMemoryVideoBuffer>(QByteArray(static_cast<const char *>(sdata), size), sstride),
628 m_videoFrameFormat);
629 qCDebug(qLcPipeWireCaptureMore) << "got a frame of size " << buf->datas[0].chunk->size;
630
631 pw_stream_queue_buffer(m_stream.get(), pwBuffer);
632
633 signalLoop(true, false);
634}
635
636void QPipeWireCaptureHelper::destroy()
637{
638 Q_ASSERT(m_pwInstance->hasScreenCastPortal());
639
640 m_state = Stopping;
641 destroyStream(false);
642
643 m_pwInstance->runWithEventLoopLock([&] {
644 m_registry = {};
645 m_core = {};
646 });
647
648 unsubscribeFromRequestResponse();
649 closeSession();
650
651 m_state = NoState;
652}
653
654void QPipeWireCaptureHelper::closeSession()
655{
656 if (m_sessionHandle.path().isEmpty())
657 return;
658
659 if (m_sessionInterface) {
660 m_sessionInterface->connection().disconnect(
661 u"org.freedesktop.portal.Desktop"_s, m_sessionHandle.path(),
662 u"org.freedesktop.portal.Session"_s, u"Closed"_s, this, SLOT(sessionClosed()));
663 m_sessionInterface->asyncCall(u"Close"_s);
664 m_sessionInterface.reset();
665 }
666
667 m_sessionHandle = {};
668}
669
670void QPipeWireCaptureHelper::sessionClosed()
671{
672 if (m_sessionHandle.path().isEmpty())
673 return;
674
675 qCWarning(qLcPipeWireCapture) << "org.freedesktop.portal.Session was closed externally";
676 m_sessionHandle = {};
677 m_sessionInterface.reset();
678 updateError(QPlatformSurfaceCapture::Error::InternalError,
679 u"PipeWire screen capture session was closed"_s);
680 stop();
681}
682
683void QPipeWireCaptureHelper::onParamChanged(uint32_t id, const struct spa_pod *param)
684{
685 if (param == nullptr || id != SPA_PARAM_Format)
686 return;
687
688 if (spa_format_parse(param, &m_format.media_type, &m_format.media_subtype) < 0)
689 return;
690
691 if (m_format.media_type != SPA_MEDIA_TYPE_video
692 || m_format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
693 return;
694
695 if (spa_format_video_raw_parse(param, &m_format.info.raw) < 0)
696 return;
697
698 qCDebug(qLcPipeWireCapture) << "got video format:";
699 qCDebug(qLcPipeWireCapture) << " format: " << m_format.info.raw.format
700 << " (" << spa_debug_type_find_name(spa_type_video_format, m_format.info.raw.format) << ")";
701 qCDebug(qLcPipeWireCapture) << " size: " << toQSize(m_format.info.raw.size);
702 qCDebug(qLcPipeWireCapture) << " framerate: " << m_format.info.raw.framerate.num
703 << " / " << m_format.info.raw.framerate.denom;
704
705 m_size = toQSize(m_format.info.raw.size);
706 m_pixelFormat = toQtPixelFormat(m_format.info.raw.format);
707 qCDebug(qLcPipeWireCapture) << "m_pixelFormat=" << m_pixelFormat;
708}
709
710} // namespace QtPipeWire
711
712QT_END_NAMESPACE
713
714QT_WARNING_POP
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)