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"