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_instance.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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_propertydict_p.h>
7#include <QtMultimedia/private/qpipewire_support_p.h>
8#include <QtMultimedia/private/qtmultimediaglobal_p.h>
9#if QT_CONFIG(pipewire_symbolloader)
10# include <QtMultimedia/private/qpipewire_symbolloader_p.h>
11#endif
12
13#include <QtDBus/qdbusinterface.h>
14#include <QtDBus/qdbusmessage.h>
15
16#include <QtCore/qcoreapplication.h>
17#include <QtCore/qmutex.h>
18
19#include <spa/utils/names.h>
20
21#include <array>
22#include <mutex>
23
24QT_BEGIN_NAMESPACE
25
26namespace QtPipeWire {
27
28using namespace Qt::Literals;
29
30Q_LOGGING_CATEGORY(lcPipewire, "qt.multimedia.pipewire");
31
32namespace {
33
34struct InstanceHolder
35{
36 QMutex mutex;
37 std::weak_ptr<QPipeWireInstance> instance;
38};
39
40Q_GLOBAL_STATIC(InstanceHolder, s_pipeWireInstance);
41
42} // namespace
43
45{
46 pw_init(nullptr, nullptr);
47
48 qCDebug(lcPipewire) << "PipeWire initialized: compiled against" << pw_get_headers_version()
49 << " running " << pw_get_library_version();
50}
51
53{
54 if (pw_check_library_version(0, 3, 49))
55 pw_deinit();
56}
57
58std::shared_ptr<QPipeWireInstance> QPipeWireInstance::instance()
59{
60 std::lock_guard guard{ s_pipeWireInstance->mutex };
61 std::shared_ptr<QPipeWireInstance> ret = s_pipeWireInstance->instance.lock();
62 if (!ret) {
63 ret = QPipeWireInstance::create();
64 if (ret)
65 s_pipeWireInstance->instance = ret;
66 }
67 return ret;
68}
69
71{
72#if QT_CONFIG(pipewire_symbolloader)
73 return qPipewireIsLoaded();
74#else
75 return 1;
76#endif
77}
78
79q23::expected<void, std::error_code> QPipeWireInstance::hasSPAFactory(const char *factoryName)
80{
81 std::array<spa_support, 32> support{};
82 uint32_t n_support = pw_get_support(support.data(), uint32_t(support.size()));
83
84 SpaHandleHandle handle{
85 pw_load_spa_handle(/*lib=*/nullptr, factoryName, /*info=*/nullptr, n_support,
86 support.data()),
87 };
88 if (!handle)
89 return q23::unexpected{ make_error_code() };
90
91 return {};
92}
93
95{
96 return m_eventLoop.isInThread();
97}
98
100{
101 return m_eventLoop.loop();
102}
103
104static std::optional<uint> portalInterfaceVersion(const QString &interface)
105{
106 QDBusInterface properties(u"org.freedesktop.portal.Desktop"_s,
107 u"/org/freedesktop/portal/desktop"_s,
108 u"org.freedesktop.DBus.Properties"_s, QDBusConnection::sessionBus());
109
110 QDBusMessage reply = properties.call(u"Get"_s, interface, u"version"_s);
111
112 if (reply.type() != QDBusMessage::ReplyMessage || reply.arguments().size() != 1) {
113 qCDebug(lcPipewire) << interface << "not available:" << reply.errorName()
114 << reply.errorMessage();
115 return std::nullopt;
116 }
117
118 // The property is a variant, which arrives wrapped in a QDBusVariant.
119 const uint version = reply.arguments().at(0).toUInt();
120 qCDebug(lcPipewire) << interface << "version" << version;
121 return version;
122}
123
125{
126 if (!m_hasScreenCastPortal)
127 m_hasScreenCastPortal =
128 portalInterfaceVersion(u"org.freedesktop.portal.ScreenCast"_s).has_value();
129 return *m_hasScreenCastPortal;
130}
131
133{
134 auto connection = PwCoreConnectionHandle{
135 pw_context_connect(m_context.get(), /*props=*/nullptr,
136 /*user_data_size=*/0),
137 };
138
139 if (!connection)
140 return q23::unexpected(make_error_code(errno));
141 return connection;
142}
143
145{
146 auto connection = PwCoreConnectionHandle{
147 pw_context_connect_fd(m_context.get(), fd, /*props=*/nullptr,
148 /*user_data_size=*/0),
149 };
150
151 if (!connection)
152 return q23::unexpected(make_error_code(errno));
153 return connection;
154}
155
156std::unique_ptr<QPipeWireInstance> QPipeWireInstance::create()
157{
158 auto instance = std::unique_ptr<QPipeWireInstance>(new QPipeWireInstance);
159 if (!instance->m_eventLoop || !instance->m_context)
160 return nullptr;
161
162 return instance;
163}
164
165QPipeWireInstance::QPipeWireInstance()
166{
167 if (!m_eventLoop)
168 return;
169
170 int status = m_eventLoop.start();
171 if (status < 0)
172 qFatal() << "Failed to start event loop" << make_error_code(-status).message();
173
174 m_context = runWithEventLoopLock([&] {
175 auto applicationName = qApp->applicationName().toUtf8();
176 PwPropertiesHandle props = makeProperties({
177 { PW_KEY_APP_NAME, applicationName.data() },
178 });
179
180 return PwContextHandle{
181 pw_context_new(m_eventLoop.loop(), props.release(), /*user_data_size=*/0),
182 };
183 });
184}
185
187{
188 runWithEventLoopLock([&] { m_context = {}; });
189 m_eventLoop.stop();
190}
191
192} // namespace QtPipeWire
193
194QT_END_NAMESPACE
q23::expected< PwCoreConnectionHandle, std::error_code > connectToPortal(int fd)
q23::expected< PwCoreConnectionHandle, std::error_code > connectToDaemon()
static std::optional< uint > portalInterfaceVersion(const QString &interface)
Q_LOGGING_CATEGORY(lcPipewire, "qt.multimedia.pipewire")
Q_DECLARE_LOGGING_CATEGORY(lcPipewire)