7#include <QtCore/QMutexLocker>
9#include <QtCore/QRandomGenerator>
11#include <QtCore/QVariantMap>
15#include <QtCore/private/qcore_unix_p.h>
17#include <QtCore/QFileInfo>
18#include <QtCore/QUrlQuery>
20#include <QtDBus/QDBusConnection>
21#include <QtDBus/QDBusInterface>
22#include <QtDBus/QDBusMessage>
23#include <QtDBus/QDBusReply>
24#include <QtDBus/QDBusPendingCall>
25#include <QtDBus/QDBusPendingCallWatcher>
26#include <QtDBus/QDBusPendingReply>
27#include <QtDBus/QDBusUnixFileDescriptor>
40#include "private/qvideoframe_p.h"
41#include "private/qcapturablewindow_p.h"
42#include "private/qmemoryvideobuffer_p.h"
43#include "private/qvideoframeconversionhelper_p.h"
60 pw_init(
nullptr,
nullptr);
74 "/org/freedesktop/portal/desktop"_L1,
75 "org.freedesktop.DBus.Properties"_L1,
80 args <<
"org.freedesktop.portal.ScreenCast"_L1
86 <<
"v3=" <<
reply.arguments().at(0).toUInt();
107 if (!globalState->hasScreenCastPortal)
110 m_pipewire = globalState->pipewire.lock();
112 m_pipewire = std::make_shared<Pipewire>();
113 globalState->pipewire = m_pipewire;
125 if (active && m_state == NoState)
127 if (!active && m_state == Streaming)
134 "There is no ScreenCast service available in org.freedesktop.portal!"_L1);
162 if (m_state != NoState)
168 return m_videoFrameFormat;
176 return globalState->hasScreenCastPortal;
184 m_operationState = NoOperation;
185 qWarning() <<
"Failed to capture screen via pipewire, perhaps because user cancelled the operation.";
190 switch (m_operationState) {
198 updateStreams(
map[
"streams"].value<QDBusArgument>());
199 openPipeWireRemote();
200 m_operationState = NoOperation;
203 case OpenPipeWireRemote:
204 m_operationState = NoOperation;
211QString QPipeWireCaptureHelper::getRequestToken()
213 if (m_requestToken <= 0)
214 m_requestToken = generateRequestToken();
215 return QStringLiteral(
"u%1%2").arg(m_requestTokenPrefix).arg(m_requestToken);
218int QPipeWireCaptureHelper::generateRequestToken()
223void QPipeWireCaptureHelper::createInterface()
227 if (!globalState->hasScreenCastPortal)
230 m_operationState = NoOperation;
232 if (!m_screenCastInterface) {
233 m_screenCastInterface = std::make_unique<QDBusInterface>(
"org.freedesktop.portal.Desktop"_L1,
234 "/org/freedesktop/portal/desktop"_L1,
235 "org.freedesktop.portal.ScreenCast"_L1,
237 bool ok = m_screenCastInterface->connection()
238 .connect(
"org.freedesktop.portal.Desktop"_L1,
""_L1,
239 "org.freedesktop.portal.Request"_L1,
"Response"_L1,
244 "Failed to connect to org.freedesktop.portal.ScreenCast dbus interface."_L1);
251void QPipeWireCaptureHelper::createSession()
253 if (!m_screenCastInterface)
258 {
"session_handle_token"_L1, getRequestToken()},
261 if (!
reply.errorMessage().isEmpty()) {
263 "Failed to create session for org.freedesktop.portal.ScreenCast. Error: "_L1 +
reply.errorName()
264 +
": "_L1 +
reply.errorMessage());
268 m_operationState = CreateSession;
271void QPipeWireCaptureHelper::selectSources(
const QString &sessionHandle)
273 if (!m_screenCastInterface)
276 m_sessionHandle = sessionHandle;
278 {
"handle_token"_L1 , getRequestToken()},
279 {
"types"_L1 , (
uint)1},
280 {
"multiple"_L1 ,
false},
281 {
"cursor_mode"_L1 , (
uint)1},
282 {
"persist_mode"_L1 , (
uint)0},
286 if (!
reply.errorMessage().isEmpty()) {
288 "Failed to select sources for org.freedesktop.portal.ScreenCast. Error: "_L1 +
reply.errorName()
289 +
": "_L1 +
reply.errorMessage());
293 m_operationState = SelectSources;
296void QPipeWireCaptureHelper::startStream()
298 if (!m_screenCastInterface)
302 {
"handle_token"_L1 , getRequestToken()},
306 if (!
reply.errorMessage().isEmpty()) {
308 "Failed to start stream for org.freedesktop.portal.ScreenCast. Error: "_L1 +
reply.errorName()
309 +
": "_L1 +
reply.errorMessage());
313 m_operationState = StartStream;
316void QPipeWireCaptureHelper::updateStreams(
const QDBusArgument &streamsInfo)
320 streamsInfo.beginStructure();
321 streamsInfo.beginArray();
323 while (!streamsInfo.atEnd()) {
325 streamsInfo >> nodeId;
351 sourceType =
properties[
"source_type"].toUInt();
353 StreamInfo streamInfo;
354 streamInfo.nodeId = nodeId;
355 streamInfo.sourceType = sourceType;
357 m_streams << streamInfo;
360 streamsInfo.endArray();
361 streamsInfo.endStructure();
365void QPipeWireCaptureHelper::openPipeWireRemote()
367 if (!m_screenCastInterface)
371 QDBusReply<QDBusUnixFileDescriptor>
reply = m_screenCastInterface->call(
"OpenPipeWireRemote"_L1,
373 if (!
reply.isValid()) {
375 "Failed to open pipewire remote for org.freedesktop.portal.ScreenCast. Error: name="_L1
380 m_pipewireFd =
reply.value().fileDescriptor();
381 bool ok =
open(m_pipewireFd);
382 qCDebug(qLcPipeWireCapture) <<
"open(" << m_pipewireFd <<
") result=" <<
ok;
384 m_operationState = OpenPipeWireRemote;
391 LoopLocker(pw_thread_loop *threadLoop)
392 : m_threadLoop(threadLoop) {
401 pw_thread_loop_lock(m_threadLoop);
406 pw_thread_loop_unlock(m_threadLoop);
407 m_threadLoop =
nullptr;
412 pw_thread_loop *m_threadLoop =
nullptr;
418 if (m_streams.isEmpty())
426 static const pw_core_events coreEvents = {
427 .version = PW_VERSION_CORE_EVENTS,
428 .info = [](
void *
data,
const struct pw_core_info *
info) {
432 .done = [](
void *
object, uint32_t
id,
int seq) {
435 .ping = [](
void *
data, uint32_t
id,
int seq) {
440 .error = [](
void *
data, uint32_t
id,
int seq,
int res,
const char *
message) {
447 .remove_id = [](
void *
data, uint32_t
id) {
451 .bound_id = [](
void *
data, uint32_t
id, uint32_t global_id) {
463 .remove_mem = [](
void *
data, uint32_t
id) {
467#if defined(PW_CORE_EVENT_BOUND_PROPS)
468 .bound_props = [](
void *
data, uint32_t
id, uint32_t global_id,
const struct spa_dict *
props) {
477 static const pw_registry_events registryEvents = {
478 .version = PW_VERSION_REGISTRY_EVENTS,
479 .global = [](
void *
object, uint32_t
id, uint32_t permissions,
const char *
type, uint32_t version,
const spa_dict *
props) {
482 .global_remove = [](
void *
data, uint32_t
id) {
488 m_threadLoop = pw_thread_loop_new(
"qt-multimedia-pipewire-loop",
nullptr);
492 "QPipeWireCaptureHelper failed at pw_thread_loop_new()."_L1);
496 m_context = pw_context_new(pw_thread_loop_get_loop(m_threadLoop),
nullptr, 0);
500 "QPipeWireCaptureHelper failed at pw_context_new()."_L1);
504 m_core = pw_context_connect_fd(m_context,
505 fcntl(pipewireFd, F_DUPFD_CLOEXEC, 5),
511 "QPipeWireCaptureHelper failed at pw_context_connect_fd()."_L1);
515 pw_core_add_listener(m_core, &m_coreListener, &coreEvents,
this);
517 m_registry = pw_core_get_registry(m_core, PW_VERSION_REGISTRY, 0);
521 "QPipeWireCaptureHelper failed at pw_core_get_registry()."_L1);
524 pw_registry_add_listener(m_registry, &m_registryListener, ®istryEvents,
this);
528 if (pw_thread_loop_start(m_threadLoop) != 0) {
531 "QPipeWireCaptureHelper failed at pw_thread_loop_start()."_L1);
535 LoopLocker locker(m_threadLoop);
536 while (!m_initDone) {
537 if (pw_thread_loop_timed_wait(m_threadLoop, 2) != 0)
541 return m_initDone && m_hasSource;
544void QPipeWireCaptureHelper::updateCoreInitSeq()
546 m_coreInitSeq = pw_core_sync(m_core, PW_ID_CORE, m_coreInitSeq);
549void QPipeWireCaptureHelper::onCoreEventDone(uint32_t
id,
int seq)
551 if (
id == PW_ID_CORE && seq == m_coreInitSeq) {
552 spa_hook_remove(&m_registryListener);
553 spa_hook_remove(&m_coreListener);
556 pw_thread_loop_signal(m_threadLoop,
false);
560void QPipeWireCaptureHelper::onRegistryEventGlobal(uint32_t
id, uint32_t permissions,
const char *
type, uint32_t version,
const spa_dict *
props)
569 auto media_class = spa_dict_lookup(
props, PW_KEY_MEDIA_CLASS);
573 if (
qstrcmp(media_class,
"Stream/Output/Video") != 0)
583void QPipeWireCaptureHelper::recreateStream()
585 static const pw_stream_events streamEvents = {
586 .version = PW_VERSION_STREAM_EVENTS,
587 .destroy = [](
void *
data) {
590 .state_changed = [](
void *
data, pw_stream_state old, pw_stream_state
state,
const char *
error) {
593 .control_info = [](
void *
data, uint32_t
id,
const struct pw_stream_control *control) {
598 .io_changed = [](
void *
data, uint32_t
id,
void *
area, uint32_t
size) {
604 .param_changed = [](
void *
data, uint32_t
id,
const struct spa_pod *
param) {
607 .add_buffer = [](
void *
data,
struct pw_buffer *
buffer) {
611 .remove_buffer = [](
void *
data,
struct pw_buffer *
buffer) {
615 .process = [](
void *
data) {
618 .drained = [](
void *
data) {
621#if PW_VERSION_STREAM_EVENTS >= 1
622 .command = [](
void *
data,
const struct spa_command *command) {
627#if PW_VERSION_STREAM_EVENTS >= 2
628 .trigger_done = [](
void *
data) {
636 auto streamInfo = m_streams[0];
637 struct spa_dict_item
items[4];
638 struct spa_dict
info;
639 items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_TYPE,
"Video");
640 items[1] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_CATEGORY,
"Capture");
641 items[2] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_ROLE,
"Screen");
643 auto props = pw_properties_new_dict(&
info);
645 LoopLocker locker(m_threadLoop);
647 m_stream = pw_stream_new(m_core,
"video-capture",
props);
652 "QPipeWireCaptureHelper failed at pw_stream_new()."_L1);
656 m_streamListener = {};
657 pw_stream_add_listener(m_stream, &m_streamListener, &streamEvents,
this);
660 struct spa_pod_builder
b = SPA_POD_BUILDER_INIT(
buffer,
sizeof(
buffer));
661 const struct spa_pod *
params[1];
662 struct spa_rectangle defsize = SPA_RECTANGLE(
quint32(streamInfo.rect.width()),
quint32(streamInfo.rect.height()));
663 struct spa_rectangle maxsize = SPA_RECTANGLE(4096, 4096);
664 struct spa_rectangle minsize = SPA_RECTANGLE(1,1);
665 struct spa_fraction defrate = SPA_FRACTION(25, 1);
666 struct spa_fraction maxrate = SPA_FRACTION(1000, 1);
667 struct spa_fraction minrate = SPA_FRACTION(0, 1);
669 params[0] =
static_cast<const spa_pod*
>(spa_pod_builder_add_object(
671 SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
672 SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
673 SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
674 SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(6,
675 SPA_VIDEO_FORMAT_RGB,
676 SPA_VIDEO_FORMAT_BGR,
677 SPA_VIDEO_FORMAT_RGBA,
678 SPA_VIDEO_FORMAT_BGRA,
679 SPA_VIDEO_FORMAT_RGBx,
680 SPA_VIDEO_FORMAT_BGRx),
681 SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
682 &defsize, &minsize, &maxsize),
683 SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(
684 &defrate, &minrate, &maxrate))
687 const int connectErr = pw_stream_connect(
691 static_cast<pw_stream_flags
>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS),
695 if (connectErr != 0) {
699 "QPipeWireCaptureHelper failed at pw_stream_connect()."_L1);
703void QPipeWireCaptureHelper::destroyStream(
bool forceDrain)
709 LoopLocker locker(m_threadLoop);
710 while (!m_streamPaused && !m_silence && !m_err) {
711 if (pw_thread_loop_timed_wait(m_threadLoop, 1) != 0)
716 LoopLocker locker(m_threadLoop);
717 m_ignoreStateChange =
true;
718 pw_stream_disconnect(m_stream);
719 pw_stream_destroy(m_stream);
720 m_ignoreStateChange =
false;
726void QPipeWireCaptureHelper::signalLoop(
bool onProcessDone,
bool err)
732 pw_thread_loop_signal(m_threadLoop,
false);
735void QPipeWireCaptureHelper::onStateChanged(pw_stream_state old, pw_stream_state
state,
const char *
error)
740 if (m_ignoreStateChange)
745 case PW_STREAM_STATE_UNCONNECTED:
746 signalLoop(
false,
true);
748 case PW_STREAM_STATE_PAUSED:
749 m_streamPaused =
true;
750 signalLoop(
false,
false);
752 case PW_STREAM_STATE_STREAMING:
753 m_streamPaused =
false;
754 signalLoop(
false,
false);
760void QPipeWireCaptureHelper::onProcess()
763 struct spa_buffer *
buf;
768 if ((
b = pw_stream_dequeue_buffer(m_stream)) == NULL) {
770 "Out of buffers in pipewire stream dequeue."_L1);
775 if ((sdata =
buf->datas[0].data) == NULL)
778 sstride =
buf->datas[0].chunk->stride;
780 sstride =
buf->datas[0].chunk->size / m_size.
height();
781 size =
buf->datas[0].chunk->size;
783 if (m_videoFrameFormat.
frameSize() != m_size || m_videoFrameFormat.
pixelFormat() != m_pixelFormat)
787 std::make_unique<QMemoryVideoBuffer>(
QByteArray(
static_cast<const char *
>(sdata),
size), sstride),
790 qCDebug(qLcPipeWireCaptureMore) <<
"got a frame of size " <<
buf->datas[0].chunk->size;
792 pw_stream_queue_buffer(m_stream,
b);
794 signalLoop(
true,
false);
797void QPipeWireCaptureHelper::destroy()
802 destroyStream(
false);
804 pw_thread_loop_stop(m_threadLoop);
807 pw_proxy_destroy(
reinterpret_cast<pw_proxy *
>(m_registry));
810 pw_core_disconnect(m_core);
813 pw_context_destroy(m_context);
815 pw_thread_loop_destroy(m_threadLoop);
823void QPipeWireCaptureHelper::onParamChanged(uint32_t
id,
const struct spa_pod *
param)
825 if (
param == NULL ||
id != SPA_PARAM_Format)
828 if (spa_format_parse(
param,
829 &m_format.media_type,
830 &m_format.media_subtype) < 0)
833 if (m_format.media_type != SPA_MEDIA_TYPE_video
834 || m_format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
837 if (spa_format_video_raw_parse(
param, &m_format.info.raw) < 0)
840 qCDebug(qLcPipeWireCapture) <<
"got video format:";
841 qCDebug(qLcPipeWireCapture) <<
" format: " << m_format.info.raw.format
842 <<
" (" << spa_debug_type_find_name(spa_type_video_format, m_format.info.raw.format) <<
")";
843 qCDebug(qLcPipeWireCapture) <<
" size: " << m_format.info.raw.size.width
844 <<
" x " << m_format.info.raw.size.height;
845 qCDebug(qLcPipeWireCapture) <<
" framerate: " << m_format.info.raw.framerate.num
846 <<
" / " << m_format.info.raw.framerate.denom;
848 m_size =
QSize(m_format.info.raw.size.width, m_format.info.raw.size.height);
849 m_pixelFormat = QPipeWireCaptureHelper::toQtPixelFormat(m_format.info.raw.format);
850 qCDebug(qLcPipeWireCapture) <<
"m_pixelFormat=" << m_pixelFormat;
858 switch (spaVideoFormat) {
861 case SPA_VIDEO_FORMAT_I420:
863 case SPA_VIDEO_FORMAT_Y42B:
865 case SPA_VIDEO_FORMAT_YV12:
867 case SPA_VIDEO_FORMAT_UYVY:
869 case SPA_VIDEO_FORMAT_YUY2:
871 case SPA_VIDEO_FORMAT_NV12:
873 case SPA_VIDEO_FORMAT_NV21:
875 case SPA_VIDEO_FORMAT_AYUV:
877 case SPA_VIDEO_FORMAT_GRAY8:
879 case SPA_VIDEO_FORMAT_xRGB:
881 case SPA_VIDEO_FORMAT_xBGR:
883 case SPA_VIDEO_FORMAT_RGBx:
885 case SPA_VIDEO_FORMAT_BGRx:
887 case SPA_VIDEO_FORMAT_ARGB:
889 case SPA_VIDEO_FORMAT_ABGR:
891 case SPA_VIDEO_FORMAT_RGBA:
893 case SPA_VIDEO_FORMAT_BGRA:
895#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
896 case SPA_VIDEO_FORMAT_GRAY16_LE:
898 case SPA_VIDEO_FORMAT_P010_10LE:
901 case SPA_VIDEO_FORMAT_GRAY16_BE:
903 case SPA_VIDEO_FORMAT_P010_10BE:
913 switch (pixelFormat) {
917 return SPA_VIDEO_FORMAT_I420;
919 return SPA_VIDEO_FORMAT_Y42B;
921 return SPA_VIDEO_FORMAT_YV12;
923 return SPA_VIDEO_FORMAT_UYVY;
925 return SPA_VIDEO_FORMAT_YUY2;
927 return SPA_VIDEO_FORMAT_NV12;
929 return SPA_VIDEO_FORMAT_NV21;
931 return SPA_VIDEO_FORMAT_AYUV;
933 return SPA_VIDEO_FORMAT_GRAY8;
935 return SPA_VIDEO_FORMAT_xRGB;
937 return SPA_VIDEO_FORMAT_xBGR;
939 return SPA_VIDEO_FORMAT_RGBx;
941 return SPA_VIDEO_FORMAT_BGRx;
943 return SPA_VIDEO_FORMAT_ARGB;
945 return SPA_VIDEO_FORMAT_ABGR;
947 return SPA_VIDEO_FORMAT_RGBA;
949 return SPA_VIDEO_FORMAT_BGRA;
950#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
952 return SPA_VIDEO_FORMAT_GRAY16_LE;
954 return SPA_VIDEO_FORMAT_P010_10LE;
957 return SPA_VIDEO_FORMAT_GRAY16_BE;
959 return SPA_VIDEO_FORMAT_P010_10BE;
963 return SPA_VIDEO_FORMAT_UNKNOWN;
void beginStructure()
Opens a new D-Bus structure suitable for appending new arguments.
static QDBusConnection sessionBus()
Returns a QDBusConnection object opened with the session bus.
virtual qint64 size() const
For open random-access devices, this function returns the size of the device.
NetworkError error() const
Returns the error that was found during the processing of this request.
static bool isSupported()
bool setActiveInternal(bool active)
QVideoFrameFormat frameFormat() const
QPipeWireCaptureHelper(QPipeWireCapture &capture)
void updateError(QPlatformSurfaceCapture::Error error, const QString &description={})
~QPipeWireCaptureHelper() override
static bool isSupported()
static Q_DECL_CONST_FUNCTION QRandomGenerator * global()
\threadsafe
constexpr int height() const noexcept
Returns the height.
\macro QT_RESTRICTED_CAST_FROM_ASCII
static QVideoFrame createFrame(std::unique_ptr< Buffer > buffer, QVideoFrameFormat format)
QMap< QString, QString > map
[6]
Combined button and popup list for selecting options.
Q_CORE_EXPORT int qstrcmp(const char *str1, const char *str2)
static const QCssKnownValue properties[NumProperties - 1]
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char * interface
DBusConnection const char DBusError * error
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
static qint64 area(const QSize &s)
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLboolean GLboolean GLboolean b
GLint GLint GLint GLint GLint x
[0]
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint GLenum GLsizei const GLchar * buf
GLuint GLsizei const GLchar * message
GLenum GLuint GLsizei const GLenum * props
GLenum const GLint * param
QT_BEGIN_NAMESPACE bool isPipewireLoaded()
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define QStringLiteral(str)
char * toString(const MyType &t)
[31]
std::weak_ptr< Pipewire > pipewire
PipeWireCaptureGlobalState()