File indexing completed on 2024-05-26 05:31:25
0001 /* 0002 SPDX-FileCopyrightText: 2023 Collabora Ltd. 0003 SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "mediamonitor.h" 0009 0010 #include <cstdlib> 0011 0012 #include <QDebug> 0013 #include <QMetaEnum> 0014 0015 #include "pipewirecore_p.h" 0016 0017 namespace 0018 { 0019 struct Node { 0020 MediaMonitor *monitor; 0021 QString deviceName; 0022 QString objectSerial; 0023 NodeState::State state = NodeState::Error; 0024 spa_hook proxyListener; 0025 spa_hook objectListener; 0026 }; 0027 0028 void updateProp(const spa_dict *props, const char *key, QString &prop, int role, QList<int> &changedRoles) 0029 { 0030 const char *new_prop = spa_dict_lookup(props, key); 0031 if (!new_prop) { 0032 return; 0033 } 0034 if (QString newProp = QString::fromUtf8(new_prop); prop != newProp) { 0035 prop = std::move(newProp); 0036 changedRoles << role; 0037 } 0038 } 0039 } 0040 0041 pw_registry_events MediaMonitor::s_pwRegistryEvents = { 0042 .version = PW_VERSION_REGISTRY_EVENTS, 0043 .global = &MediaMonitor::onRegistryEventGlobal, 0044 .global_remove = &MediaMonitor::onRegistryEventGlobalRemove, 0045 }; 0046 0047 pw_proxy_events MediaMonitor::s_pwProxyEvents = { 0048 .version = PW_VERSION_PROXY_EVENTS, 0049 .destroy = &MediaMonitor::onProxyDestroy, 0050 }; 0051 0052 pw_node_events MediaMonitor::s_pwNodeEvents = { 0053 .version = PW_VERSION_NODE_EVENTS, 0054 .info = &MediaMonitor::onNodeEventInfo, 0055 }; 0056 0057 MediaMonitor::MediaMonitor(QObject *parent) 0058 : QAbstractListModel(parent) 0059 { 0060 connect(this, &QAbstractListModel::rowsInserted, this, &MediaMonitor::countChanged); 0061 connect(this, &QAbstractListModel::rowsRemoved, this, &MediaMonitor::countChanged); 0062 connect(this, &QAbstractListModel::modelReset, this, &MediaMonitor::countChanged); 0063 0064 m_reconnectTimer.setSingleShot(true); 0065 m_reconnectTimer.setInterval(5000); 0066 connect(&m_reconnectTimer, &QTimer::timeout, this, &MediaMonitor::connectToCore); 0067 } 0068 0069 MediaMonitor::~MediaMonitor() 0070 { 0071 m_inDestructor = true; 0072 disconnectFromCore(); 0073 } 0074 0075 QVariant MediaMonitor::data(const QModelIndex &index, int role) const 0076 { 0077 if (!checkIndex(index, CheckIndexOption::IndexIsValid)) { 0078 return {}; 0079 } 0080 0081 pw_proxy *const proxy = m_nodeList.at(index.row()).get(); 0082 const Node *const node = static_cast<Node *>(pw_proxy_get_user_data(proxy)); 0083 0084 switch (role) { 0085 case Qt::DisplayRole: 0086 return node->deviceName; 0087 case StateRole: 0088 return node->state; 0089 case ObjectSerialRole: 0090 return node->objectSerial; 0091 default: 0092 return QVariant(); 0093 } 0094 } 0095 0096 int MediaMonitor::rowCount(const QModelIndex &parent) const 0097 { 0098 return parent.isValid() ? 0 : m_nodeList.size(); 0099 } 0100 0101 QHash<int, QByteArray> MediaMonitor::roleNames() const 0102 { 0103 return { 0104 {Qt::DisplayRole, QByteArrayLiteral("display")}, 0105 {StateRole, QByteArrayLiteral("state")}, 0106 {ObjectSerialRole, QByteArrayLiteral("objectSerial")}, 0107 }; 0108 } 0109 0110 MediaRole::Role MediaMonitor::role() const 0111 { 0112 return m_role; 0113 } 0114 0115 void MediaMonitor::setRole(MediaRole::Role newRole) 0116 { 0117 if (m_role == newRole) { 0118 return; 0119 } 0120 Q_ASSERT(newRole >= MediaRole::Unknown && newRole <= MediaRole::Last); 0121 m_role = std::clamp(newRole, MediaRole::Unknown, MediaRole::Last); 0122 0123 if (m_reconnectTimer.isActive()) { 0124 Q_EMIT roleChanged(); 0125 return; 0126 } 0127 0128 disconnectFromCore(); 0129 connectToCore(); 0130 0131 Q_EMIT roleChanged(); 0132 } 0133 0134 bool MediaMonitor::detectionAvailable() const 0135 { 0136 return m_detectionAvailable; 0137 } 0138 0139 int MediaMonitor::runningCount() const 0140 { 0141 return m_runningCount; 0142 } 0143 0144 int MediaMonitor::idleCount() const 0145 { 0146 return m_idleCount; 0147 } 0148 0149 void MediaMonitor::connectToCore() 0150 { 0151 Q_ASSERT(!m_registry); 0152 if (!m_componentReady || m_role == MediaRole::Unknown) { 0153 return; 0154 } 0155 0156 if (!m_pwCore) { 0157 m_pwCore = PipeWireCore::fetch(0); 0158 } 0159 if (!m_pwCore->error().isEmpty()) { 0160 qDebug() << "received error while creating the stream" << m_pwCore->error() << "Media monitor will not work."; 0161 m_pwCore.clear(); 0162 m_reconnectTimer.start(); 0163 return; 0164 } 0165 0166 m_registry = pw_core_get_registry(**m_pwCore.get(), PW_VERSION_REGISTRY, 0); 0167 pw_registry_add_listener(m_registry, &m_registryListener, &s_pwRegistryEvents, this /*user data*/); 0168 0169 m_detectionAvailable = true; 0170 Q_EMIT detectionAvailableChanged(); 0171 0172 connect(m_pwCore.get(), &PipeWireCore::pipeBroken, this, &MediaMonitor::onPipeBroken); 0173 } 0174 0175 void MediaMonitor::onPipeBroken() 0176 { 0177 m_registry = nullptr; // When pipe is broken, the registered object is also gone 0178 disconnectFromCore(); 0179 reconnectOnIdle(); 0180 } 0181 0182 void MediaMonitor::onRegistryEventGlobal(void *data, uint32_t id, uint32_t /*permissions*/, const char *type, uint32_t /*version*/, const spa_dict *props) 0183 { 0184 auto monitor = static_cast<MediaMonitor *>(data); 0185 0186 if (!props || !(spa_streq(type, PW_TYPE_INTERFACE_Node))) { 0187 return; 0188 } 0189 0190 static const QMetaEnum metaEnum = QMetaEnum::fromType<MediaRole::Role>(); 0191 if (const char *prop_str = spa_dict_lookup(props, PW_KEY_MEDIA_ROLE); !prop_str || (strcmp(prop_str, metaEnum.valueToKey(monitor->m_role)) != 0)) { 0192 return; 0193 } 0194 0195 auto proxy = static_cast<pw_proxy *>(pw_registry_bind(monitor->m_registry, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, sizeof(Node))); 0196 auto node = static_cast<Node *>(pw_proxy_get_user_data(proxy)); 0197 node->monitor = monitor; 0198 readProps(props, proxy, false); 0199 0200 monitor->beginInsertRows(QModelIndex(), monitor->m_nodeList.size(), monitor->m_nodeList.size()); 0201 monitor->m_nodeList.emplace_back(proxy); 0202 monitor->endInsertRows(); 0203 0204 pw_proxy_add_listener(proxy, &node->proxyListener, &s_pwProxyEvents, node); 0205 pw_proxy_add_object_listener(proxy, &node->objectListener, &s_pwNodeEvents, node); 0206 } 0207 0208 void MediaMonitor::onRegistryEventGlobalRemove(void *data, uint32_t id) 0209 { 0210 auto monitor = static_cast<MediaMonitor *>(data); 0211 const auto proxyIt = std::find_if(monitor->m_nodeList.cbegin(), monitor->m_nodeList.cend(), [id](const auto &proxy) { 0212 return pw_proxy_get_bound_id(proxy.get()) == id; 0213 }); 0214 if (proxyIt == monitor->m_nodeList.cend()) { 0215 return; 0216 } 0217 const int row = std::distance(monitor->m_nodeList.cbegin(), proxyIt); 0218 monitor->beginRemoveRows(QModelIndex(), row, row); 0219 monitor->m_nodeList.erase(proxyIt); 0220 monitor->endRemoveRows(); 0221 } 0222 0223 void MediaMonitor::onProxyDestroy(void *data) 0224 { 0225 auto node = static_cast<Node *>(data); 0226 spa_hook_remove(&node->proxyListener); 0227 spa_hook_remove(&node->objectListener); 0228 } 0229 0230 void MediaMonitor::onNodeEventInfo(void *data, const pw_node_info *info) 0231 { 0232 auto node = static_cast<Node *>(data); 0233 0234 NodeState::State newState; 0235 switch (info->state) { 0236 case PW_NODE_STATE_ERROR: 0237 newState = NodeState::Error; 0238 break; 0239 case PW_NODE_STATE_CREATING: 0240 newState = NodeState::Creating; 0241 break; 0242 case PW_NODE_STATE_SUSPENDED: 0243 newState = NodeState::Suspended; 0244 break; 0245 case PW_NODE_STATE_IDLE: 0246 newState = NodeState::Idle; 0247 break; 0248 case PW_NODE_STATE_RUNNING: 0249 newState = NodeState::Running; 0250 break; 0251 default: 0252 Q_ASSERT_X(false, "MediaMonitor", "Unknown node state"); 0253 return; 0254 } 0255 0256 const auto proxyIt = std::find_if(node->monitor->m_nodeList.cbegin(), node->monitor->m_nodeList.cend(), [data](const auto &proxy) { 0257 return pw_proxy_get_user_data(proxy.get()) == data; 0258 }); 0259 if (node->state != newState) { 0260 node->state = newState; 0261 const int row = std::distance(node->monitor->m_nodeList.cbegin(), proxyIt); 0262 const QModelIndex idx = node->monitor->index(row, 0); 0263 node->monitor->dataChanged(idx, idx, {StateRole}); 0264 } 0265 0266 readProps(info->props, proxyIt->get(), true); 0267 node->monitor->updateState(); 0268 } 0269 0270 void MediaMonitor::readProps(const spa_dict *props, pw_proxy *proxy, bool emitSignal) 0271 { 0272 auto node = static_cast<Node *>(pw_proxy_get_user_data(proxy)); 0273 QList<int> changedRoles; 0274 0275 updateProp(props, PW_KEY_NODE_NICK, node->deviceName, Qt::DisplayRole, changedRoles); 0276 if (node->deviceName.isEmpty()) { 0277 changedRoles.clear(); 0278 updateProp(props, PW_KEY_NODE_NAME, node->deviceName, Qt::DisplayRole, changedRoles); 0279 } 0280 if (node->deviceName.isEmpty()) { 0281 changedRoles.clear(); 0282 updateProp(props, PW_KEY_NODE_DESCRIPTION, node->deviceName, Qt::DisplayRole, changedRoles); 0283 } 0284 0285 updateProp(props, PW_KEY_OBJECT_SERIAL, node->objectSerial, ObjectSerialRole, changedRoles); 0286 0287 if (emitSignal && !changedRoles.empty()) { 0288 const auto proxyIt = std::find_if(node->monitor->m_nodeList.cbegin(), node->monitor->m_nodeList.cend(), [proxy](const auto &p) { 0289 return p.get() == proxy; 0290 }); 0291 const int row = std::distance(node->monitor->m_nodeList.cbegin(), proxyIt); 0292 const QModelIndex idx = node->monitor->index(row, 0); 0293 node->monitor->dataChanged(idx, idx, changedRoles); 0294 } 0295 } 0296 0297 void MediaMonitor::classBegin() 0298 { 0299 } 0300 0301 void MediaMonitor::componentComplete() 0302 { 0303 m_componentReady = true; 0304 connectToCore(); 0305 } 0306 0307 void MediaMonitor::disconnectFromCore() 0308 { 0309 if (!m_pwCore) { 0310 return; 0311 } 0312 0313 if (m_runningCount) { 0314 m_runningCount = 0; 0315 Q_EMIT runningCountChanged(); 0316 } 0317 0318 if (m_idleCount) { 0319 m_idleCount = 0; 0320 Q_EMIT idleCountChanged(); 0321 } 0322 0323 m_detectionAvailable = false; 0324 Q_EMIT detectionAvailableChanged(); 0325 0326 if (!m_inDestructor) { 0327 beginResetModel(); 0328 m_nodeList.clear(); 0329 endResetModel(); 0330 } 0331 0332 if (m_registry) { 0333 pw_proxy_destroy(reinterpret_cast<struct pw_proxy *>(m_registry)); 0334 spa_hook_remove(&m_registryListener); 0335 m_registry = nullptr; 0336 } 0337 disconnect(m_pwCore.get(), &PipeWireCore::pipeBroken, this, &MediaMonitor::onPipeBroken); 0338 } 0339 0340 void MediaMonitor::reconnectOnIdle() 0341 { 0342 if (m_reconnectTimer.isActive()) { 0343 return; 0344 } 0345 0346 static unsigned retryCount = 0; 0347 if (retryCount > 100) { 0348 qWarning() << "Camera indicator receives too many errors. Aborting..."; 0349 return; 0350 } 0351 ++retryCount; 0352 m_reconnectTimer.start(); 0353 } 0354 0355 void MediaMonitor::updateState() 0356 { 0357 int newIdleCount = 0; 0358 int newRunningCount = 0; 0359 for (const auto &proxy : m_nodeList) { 0360 switch (static_cast<Node *>(pw_proxy_get_user_data(proxy.get()))->state) { 0361 case NodeState::Idle: 0362 ++newIdleCount; 0363 break; 0364 case NodeState::Running: 0365 ++newRunningCount; 0366 break; 0367 default: 0368 break; 0369 } 0370 } 0371 0372 const bool idleChanged = m_idleCount != newIdleCount; 0373 m_idleCount = newIdleCount; 0374 const bool runningChanged = m_runningCount != newRunningCount; 0375 m_runningCount = newRunningCount; 0376 0377 if (idleChanged) { 0378 Q_EMIT idleCountChanged(); 0379 } 0380 if (runningChanged) { 0381 Q_EMIT runningCountChanged(); 0382 } 0383 } 0384 0385 #include "moc_mediamonitor.cpp"