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