KPipewire

mediamonitor.cpp
1/*
2 SPDX-FileCopyrightText: 2023 Collabora Ltd.
3 SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8#include "mediamonitor.h"
9
10#include <cstdlib>
11
12#include <QDebug>
13#include <QMetaEnum>
14
15#include "pipewirecore_p.h"
16
17namespace
18{
19struct Node {
20 MediaMonitor *monitor;
21 QString deviceName;
22 QString objectSerial;
23 NodeState::State state = NodeState::Error;
24 spa_hook proxyListener;
25 spa_hook objectListener;
26};
27
28void updateProp(const spa_dict *props, const char *key, QString &prop, int role, QList<int> &changedRoles)
29{
30 const char *new_prop = spa_dict_lookup(props, key);
31 if (!new_prop) {
32 return;
33 }
34 if (QString newProp = QString::fromUtf8(new_prop); prop != newProp) {
35 prop = std::move(newProp);
36 changedRoles << role;
37 }
38}
39}
40
41pw_registry_events MediaMonitor::s_pwRegistryEvents = {
42 .version = PW_VERSION_REGISTRY_EVENTS,
43 .global = &MediaMonitor::onRegistryEventGlobal,
44 .global_remove = &MediaMonitor::onRegistryEventGlobalRemove,
45};
46
47pw_proxy_events MediaMonitor::s_pwProxyEvents = {
48 .version = PW_VERSION_PROXY_EVENTS,
49 .destroy = &MediaMonitor::onProxyDestroy,
50};
51
52pw_node_events MediaMonitor::s_pwNodeEvents = {
53 .version = PW_VERSION_NODE_EVENTS,
54 .info = &MediaMonitor::onNodeEventInfo,
55};
56
57MediaMonitor::MediaMonitor(QObject *parent)
58 : QAbstractListModel(parent)
59{
60 connect(this, &QAbstractListModel::rowsInserted, this, &MediaMonitor::countChanged);
61 connect(this, &QAbstractListModel::rowsRemoved, this, &MediaMonitor::countChanged);
62 connect(this, &QAbstractListModel::modelReset, this, &MediaMonitor::countChanged);
63
64 m_reconnectTimer.setSingleShot(true);
65 m_reconnectTimer.setInterval(5000);
66 connect(&m_reconnectTimer, &QTimer::timeout, this, &MediaMonitor::connectToCore);
67}
68
69MediaMonitor::~MediaMonitor()
70{
71 m_inDestructor = true;
72 disconnectFromCore();
73}
74
75QVariant MediaMonitor::data(const QModelIndex &index, int role) const
76{
77 if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
78 return {};
79 }
80
81 pw_proxy *const proxy = m_nodeList.at(index.row()).get();
82 const Node *const node = static_cast<Node *>(pw_proxy_get_user_data(proxy));
83
84 switch (role) {
85 case Qt::DisplayRole:
86 return node->deviceName;
87 case StateRole:
88 return node->state;
89 case ObjectSerialRole:
90 return node->objectSerial;
91 default:
92 return QVariant();
93 }
94}
95
96int MediaMonitor::rowCount(const QModelIndex &parent) const
97{
98 return parent.isValid() ? 0 : m_nodeList.size();
99}
100
101QHash<int, QByteArray> MediaMonitor::roleNames() const
102{
103 return {
104 {Qt::DisplayRole, QByteArrayLiteral("display")},
105 {StateRole, QByteArrayLiteral("state")},
106 {ObjectSerialRole, QByteArrayLiteral("objectSerial")},
107 };
108}
109
110MediaRole::Role MediaMonitor::role() const
111{
112 return m_role;
113}
114
115void MediaMonitor::setRole(MediaRole::Role newRole)
116{
117 if (m_role == newRole) {
118 return;
119 }
120 Q_ASSERT(newRole >= MediaRole::Unknown && newRole <= MediaRole::Last);
121 m_role = std::clamp(newRole, MediaRole::Unknown, MediaRole::Last);
122
123 if (m_reconnectTimer.isActive()) {
124 Q_EMIT roleChanged();
125 return;
126 }
127
128 disconnectFromCore();
129 connectToCore();
130
131 Q_EMIT roleChanged();
132}
133
134bool MediaMonitor::detectionAvailable() const
135{
136 return m_detectionAvailable;
137}
138
139int MediaMonitor::runningCount() const
140{
141 return m_runningCount;
142}
143
144int MediaMonitor::idleCount() const
145{
146 return m_idleCount;
147}
148
149void MediaMonitor::connectToCore()
150{
151 Q_ASSERT(!m_registry);
152 if (!m_componentReady || m_role == MediaRole::Unknown) {
153 return;
154 }
155
156 if (!m_pwCore) {
157 m_pwCore = PipeWireCore::fetch(0);
158 }
159 if (!m_pwCore->error().isEmpty()) {
160 qDebug() << "received error while creating the stream" << m_pwCore->error() << "Media monitor will not work.";
161 m_pwCore.clear();
162 m_reconnectTimer.start();
163 return;
164 }
165
166 m_registry = pw_core_get_registry(**m_pwCore.get(), PW_VERSION_REGISTRY, 0);
167 pw_registry_add_listener(m_registry, &m_registryListener, &s_pwRegistryEvents, this /*user data*/);
168
169 m_detectionAvailable = true;
170 Q_EMIT detectionAvailableChanged();
171
172 connect(m_pwCore.get(), &PipeWireCore::pipeBroken, this, &MediaMonitor::onPipeBroken);
173}
174
175void MediaMonitor::onPipeBroken()
176{
177 m_registry = nullptr; // When pipe is broken, the registered object is also gone
178 disconnectFromCore();
179 reconnectOnIdle();
180}
181
182void MediaMonitor::onRegistryEventGlobal(void *data, uint32_t id, uint32_t /*permissions*/, const char *type, uint32_t /*version*/, const spa_dict *props)
183{
184 auto monitor = static_cast<MediaMonitor *>(data);
185
186 if (!props || !(spa_streq(type, PW_TYPE_INTERFACE_Node))) {
187 return;
188 }
189
190 static const QMetaEnum metaEnum = QMetaEnum::fromType<MediaRole::Role>();
191 if (const char *prop_str = spa_dict_lookup(props, PW_KEY_MEDIA_ROLE); !prop_str || (strcmp(prop_str, metaEnum.valueToKey(monitor->m_role)) != 0)) {
192 return;
193 }
194
195 auto proxy = static_cast<pw_proxy *>(pw_registry_bind(monitor->m_registry, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, sizeof(Node)));
196 auto node = static_cast<Node *>(pw_proxy_get_user_data(proxy));
197 node->monitor = monitor;
198 readProps(props, proxy, false);
199
200 monitor->beginInsertRows(QModelIndex(), monitor->m_nodeList.size(), monitor->m_nodeList.size());
201 monitor->m_nodeList.emplace_back(proxy);
202 monitor->endInsertRows();
203
204 pw_proxy_add_listener(proxy, &node->proxyListener, &s_pwProxyEvents, node);
205 pw_proxy_add_object_listener(proxy, &node->objectListener, &s_pwNodeEvents, node);
206}
207
208void MediaMonitor::onRegistryEventGlobalRemove(void *data, uint32_t id)
209{
210 auto monitor = static_cast<MediaMonitor *>(data);
211 const auto proxyIt = std::find_if(monitor->m_nodeList.cbegin(), monitor->m_nodeList.cend(), [id](const auto &proxy) {
212 return pw_proxy_get_bound_id(proxy.get()) == id;
213 });
214 if (proxyIt == monitor->m_nodeList.cend()) {
215 return;
216 }
217 const int row = std::distance(monitor->m_nodeList.cbegin(), proxyIt);
218 monitor->beginRemoveRows(QModelIndex(), row, row);
219 monitor->m_nodeList.erase(proxyIt);
220 monitor->endRemoveRows();
221}
222
223void MediaMonitor::onProxyDestroy(void *data)
224{
225 auto node = static_cast<Node *>(data);
226 spa_hook_remove(&node->proxyListener);
227 spa_hook_remove(&node->objectListener);
228}
229
230void MediaMonitor::onNodeEventInfo(void *data, const pw_node_info *info)
231{
232 auto node = static_cast<Node *>(data);
233
234 NodeState::State newState;
235 switch (info->state) {
236 case PW_NODE_STATE_ERROR:
237 newState = NodeState::Error;
238 break;
239 case PW_NODE_STATE_CREATING:
240 newState = NodeState::Creating;
241 break;
242 case PW_NODE_STATE_SUSPENDED:
243 newState = NodeState::Suspended;
244 break;
245 case PW_NODE_STATE_IDLE:
246 newState = NodeState::Idle;
247 break;
248 case PW_NODE_STATE_RUNNING:
249 newState = NodeState::Running;
250 break;
251 default:
252 Q_ASSERT_X(false, "MediaMonitor", "Unknown node state");
253 return;
254 }
255
256 const auto proxyIt = std::find_if(node->monitor->m_nodeList.cbegin(), node->monitor->m_nodeList.cend(), [data](const auto &proxy) {
257 return pw_proxy_get_user_data(proxy.get()) == data;
258 });
259 if (node->state != newState) {
260 node->state = newState;
261 const int row = std::distance(node->monitor->m_nodeList.cbegin(), proxyIt);
262 const QModelIndex idx = node->monitor->index(row, 0);
263 node->monitor->dataChanged(idx, idx, {StateRole});
264 }
265
266 readProps(info->props, proxyIt->get(), true);
267 node->monitor->updateState();
268}
269
270void MediaMonitor::readProps(const spa_dict *props, pw_proxy *proxy, bool emitSignal)
271{
272 auto node = static_cast<Node *>(pw_proxy_get_user_data(proxy));
273 QList<int> changedRoles;
274
275 updateProp(props, PW_KEY_NODE_NICK, node->deviceName, Qt::DisplayRole, changedRoles);
276 if (node->deviceName.isEmpty()) {
277 changedRoles.clear();
278 updateProp(props, PW_KEY_NODE_NAME, node->deviceName, Qt::DisplayRole, changedRoles);
279 }
280 if (node->deviceName.isEmpty()) {
281 changedRoles.clear();
282 updateProp(props, PW_KEY_NODE_DESCRIPTION, node->deviceName, Qt::DisplayRole, changedRoles);
283 }
284
285 updateProp(props, PW_KEY_OBJECT_SERIAL, node->objectSerial, ObjectSerialRole, changedRoles);
286
287 if (emitSignal && !changedRoles.empty()) {
288 const auto proxyIt = std::find_if(node->monitor->m_nodeList.cbegin(), node->monitor->m_nodeList.cend(), [proxy](const auto &p) {
289 return p.get() == proxy;
290 });
291 const int row = std::distance(node->monitor->m_nodeList.cbegin(), proxyIt);
292 const QModelIndex idx = node->monitor->index(row, 0);
293 node->monitor->dataChanged(idx, idx, changedRoles);
294 }
295}
296
297void MediaMonitor::classBegin()
298{
299}
300
301void MediaMonitor::componentComplete()
302{
303 m_componentReady = true;
304 connectToCore();
305}
306
307void MediaMonitor::disconnectFromCore()
308{
309 if (!m_pwCore) {
310 return;
311 }
312
313 if (m_runningCount) {
314 m_runningCount = 0;
315 Q_EMIT runningCountChanged();
316 }
317
318 if (m_idleCount) {
319 m_idleCount = 0;
320 Q_EMIT idleCountChanged();
321 }
322
323 m_detectionAvailable = false;
324 Q_EMIT detectionAvailableChanged();
325
326 if (!m_inDestructor) {
328 m_nodeList.clear();
330 }
331
332 if (m_registry) {
333 pw_proxy_destroy(reinterpret_cast<struct pw_proxy *>(m_registry));
334 spa_hook_remove(&m_registryListener);
335 m_registry = nullptr;
336 }
337 disconnect(m_pwCore.get(), &PipeWireCore::pipeBroken, this, &MediaMonitor::onPipeBroken);
338}
339
340void MediaMonitor::reconnectOnIdle()
341{
342 if (m_reconnectTimer.isActive()) {
343 return;
344 }
345
346 static unsigned retryCount = 0;
347 if (retryCount > 100) {
348 qWarning() << "Camera indicator receives too many errors. Aborting...";
349 return;
350 }
351 ++retryCount;
352 m_reconnectTimer.start();
353}
354
355void MediaMonitor::updateState()
356{
357 int newIdleCount = 0;
358 int newRunningCount = 0;
359 for (const auto &proxy : m_nodeList) {
360 switch (static_cast<Node *>(pw_proxy_get_user_data(proxy.get()))->state) {
361 case NodeState::Idle:
362 ++newIdleCount;
363 break;
364 case NodeState::Running:
365 ++newRunningCount;
366 break;
367 default:
368 break;
369 }
370 }
371
372 const bool idleChanged = m_idleCount != newIdleCount;
373 m_idleCount = newIdleCount;
374 const bool runningChanged = m_runningCount != newRunningCount;
375 m_runningCount = newRunningCount;
376
377 if (idleChanged) {
378 Q_EMIT idleCountChanged();
379 }
380 if (runningChanged) {
381 Q_EMIT runningCountChanged();
382 }
383}
384
385#include "moc_mediamonitor.cpp"
int index() const
bool checkIndex(const QModelIndex &index, CheckIndexOptions options) const const
void rowsInserted(const QModelIndex &parent, int first, int last)
void rowsRemoved(const QModelIndex &parent, int first, int last)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
void clear()
bool empty() const const
QMetaEnum fromType()
const char * valueToKey(int value) const const
int row() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
T * get() const const
QString fromUtf8(QByteArrayView str)
DisplayRole
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isActive() const const
void start()
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:05:20 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.