File indexing completed on 2024-04-21 16:20:31

0001 /*
0002     SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
0003     SPDX-FileCopyrightText: 2016 David Rosca <nowrep@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "pulseaudio.h"
0009 
0010 #include "card.h"
0011 #include "debug.h"
0012 #include "module.h"
0013 #include "server.h"
0014 #include "sinkinput.h"
0015 #include "sourceoutput.h"
0016 #include "streamrestore.h"
0017 
0018 #include <QMetaEnum>
0019 
0020 namespace QPulseAudio
0021 {
0022 AbstractModel::AbstractModel(const MapBaseQObject *map, QObject *parent)
0023     : QAbstractListModel(parent)
0024     , m_map(map)
0025 {
0026     Context::instance()->ref();
0027 
0028     connect(m_map, &MapBaseQObject::aboutToBeAdded, this, [this](int index) {
0029         beginInsertRows(QModelIndex(), index, index);
0030     });
0031     connect(m_map, &MapBaseQObject::added, this, [this](int index) {
0032         onDataAdded(index);
0033         endInsertRows();
0034         Q_EMIT countChanged();
0035     });
0036     connect(m_map, &MapBaseQObject::aboutToBeRemoved, this, [this](int index) {
0037         beginRemoveRows(QModelIndex(), index, index);
0038     });
0039     connect(m_map, &MapBaseQObject::removed, this, [this](int index) {
0040         Q_UNUSED(index);
0041         endRemoveRows();
0042         Q_EMIT countChanged();
0043     });
0044 }
0045 
0046 AbstractModel::~AbstractModel()
0047 {
0048     // deref context after we've deleted this object
0049     // see https://bugs.kde.org/show_bug.cgi?id=371215
0050     Context::instance()->unref();
0051 }
0052 
0053 QHash<int, QByteArray> AbstractModel::roleNames() const
0054 {
0055     if (!m_roles.empty()) {
0056         qCDebug(PLASMAPA) << "returning roles" << m_roles;
0057         return m_roles;
0058     }
0059     Q_UNREACHABLE();
0060     return {};
0061 }
0062 
0063 int AbstractModel::rowCount(const QModelIndex &parent) const
0064 {
0065     if (parent.isValid()) {
0066         return 0;
0067     }
0068     return m_map->count();
0069 }
0070 
0071 QVariant AbstractModel::data(const QModelIndex &index, int role) const
0072 {
0073     if (!hasIndex(index.row(), index.column())) {
0074         return {};
0075     }
0076     QObject *data = m_map->objectAt(index.row());
0077     Q_ASSERT(data);
0078     if (role == PulseObjectRole) {
0079         return QVariant::fromValue(data);
0080     } else if (role == Qt::DisplayRole) {
0081         return static_cast<PulseObject *>(data)->properties().value(QStringLiteral("name")).toString();
0082     }
0083     int property = m_objectProperties.value(role, -1);
0084     if (property == -1) {
0085         return {};
0086     }
0087     return data->metaObject()->property(property).read(data);
0088 }
0089 
0090 bool AbstractModel::setData(const QModelIndex &index, const QVariant &value, int role)
0091 {
0092     if (!hasIndex(index.row(), index.column())) {
0093         return false;
0094     }
0095     int propertyIndex = m_objectProperties.value(role, -1);
0096     if (propertyIndex == -1) {
0097         return false;
0098     }
0099     QObject *data = m_map->objectAt(index.row());
0100     auto property = data->metaObject()->property(propertyIndex);
0101     return property.write(data, value);
0102 }
0103 
0104 int AbstractModel::role(const QByteArray &roleName) const
0105 {
0106     qCDebug(PLASMAPA) << roleName << m_roles.key(roleName, -1);
0107     return m_roles.key(roleName, -1);
0108 }
0109 
0110 Context *AbstractModel::context() const
0111 {
0112     return Context::instance();
0113 }
0114 
0115 void AbstractModel::initRoleNames(const QMetaObject &qobjectMetaObject)
0116 {
0117     m_roles[PulseObjectRole] = QByteArrayLiteral("PulseObject");
0118 
0119     QMetaEnum enumerator;
0120     for (int i = 0; i < metaObject()->enumeratorCount(); ++i) {
0121         if (metaObject()->enumerator(i).name() == QLatin1String("ItemRole")) {
0122             enumerator = metaObject()->enumerator(i);
0123             break;
0124         }
0125     }
0126 
0127     for (int i = 0; i < enumerator.keyCount(); ++i) {
0128         // Clip the Role suffix and glue it in the hash.
0129         const int roleLength = 4;
0130         QByteArray key(enumerator.key(i));
0131         // Enum values must end in Role or the enum is crap
0132         Q_ASSERT(key.right(roleLength) == QByteArrayLiteral("Role"));
0133         key.chop(roleLength);
0134         m_roles[enumerator.value(i)] = key;
0135     }
0136 
0137     int maxEnumValue = -1;
0138     for (auto it = m_roles.constBegin(); it != m_roles.constEnd(); ++it) {
0139         if (it.key() > maxEnumValue) {
0140             maxEnumValue = it.key();
0141         }
0142     }
0143     Q_ASSERT(maxEnumValue != -1);
0144     auto mo = qobjectMetaObject;
0145     for (int i = 0; i < mo.propertyCount(); ++i) {
0146         QMetaProperty property = mo.property(i);
0147         QString name(property.name());
0148         name.replace(0, 1, name.at(0).toUpper());
0149         m_roles[++maxEnumValue] = name.toLatin1();
0150         m_objectProperties.insert(maxEnumValue, i);
0151         if (!property.hasNotifySignal()) {
0152             continue;
0153         }
0154         m_signalIndexToProperties.insert(property.notifySignalIndex(), i);
0155     }
0156     qCDebug(PLASMAPA) << m_roles;
0157 
0158     // Connect to property changes also with objects already in model
0159     for (int i = 0; i < m_map->count(); ++i) {
0160         onDataAdded(i);
0161     }
0162 }
0163 
0164 void AbstractModel::propertyChanged()
0165 {
0166     if (!sender() || senderSignalIndex() == -1) {
0167         return;
0168     }
0169     int propertyIndex = m_signalIndexToProperties.value(senderSignalIndex(), -1);
0170     if (propertyIndex == -1) {
0171         return;
0172     }
0173     int role = m_objectProperties.key(propertyIndex, -1);
0174     if (role == -1) {
0175         return;
0176     }
0177     int index = m_map->indexOfObject(sender());
0178     qCDebug(PLASMAPA) << "PROPERTY CHANGED (" << index << ") :: " << role << roleNames().value(role);
0179     Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {role});
0180 }
0181 
0182 void AbstractModel::onDataAdded(int index)
0183 {
0184     QObject *data = m_map->objectAt(index);
0185     const QMetaObject *mo = data->metaObject();
0186     // We have all the data changed notify signals already stored
0187     const auto keys = m_signalIndexToProperties.keys();
0188     for (const int index : keys) {
0189         QMetaMethod meth = mo->method(index);
0190         connect(data, meth, this, propertyChangedMetaMethod());
0191     }
0192 }
0193 
0194 QMetaMethod AbstractModel::propertyChangedMetaMethod() const
0195 {
0196     auto mo = metaObject();
0197     int methodIndex = mo->indexOfMethod("propertyChanged()");
0198     if (methodIndex == -1) {
0199         return {};
0200     }
0201     return mo->method(methodIndex);
0202 }
0203 
0204 SinkModel::SinkModel(QObject *parent)
0205     : AbstractModel(&context()->sinks(), parent)
0206     , m_preferredSink(nullptr)
0207 {
0208     initRoleNames(Sink::staticMetaObject);
0209 
0210     for (int i = 0; i < context()->sinks().count(); ++i) {
0211         sinkAdded(i);
0212     }
0213 
0214     connect(&context()->sinks(), &MapBaseQObject::added, this, &SinkModel::sinkAdded);
0215     connect(&context()->sinks(), &MapBaseQObject::removed, this, &SinkModel::sinkRemoved);
0216 
0217     connect(context()->server(), &Server::defaultSinkChanged, this, [this]() {
0218         updatePreferredSink();
0219         Q_EMIT defaultSinkChanged();
0220     });
0221 }
0222 
0223 Sink *SinkModel::defaultSink() const
0224 {
0225     return context()->server()->defaultSink();
0226 }
0227 
0228 Sink *SinkModel::preferredSink() const
0229 {
0230     return m_preferredSink;
0231 }
0232 
0233 QVariant SinkModel::data(const QModelIndex &index, int role) const
0234 {
0235     if (role == SortByDefaultRole) {
0236         // Workaround QTBUG-1548
0237         const QString pulseIndex = data(index, AbstractModel::role(QByteArrayLiteral("Index"))).toString();
0238         const QString defaultDevice = data(index, AbstractModel::role(QByteArrayLiteral("Default"))).toString();
0239         return defaultDevice + pulseIndex;
0240     }
0241     return AbstractModel::data(index, role);
0242 }
0243 
0244 void SinkModel::sinkAdded(int index)
0245 {
0246     Q_ASSERT(qobject_cast<Sink *>(context()->sinks().objectAt(index)));
0247     Sink *sink = static_cast<Sink *>(context()->sinks().objectAt(index));
0248     connect(sink, &Sink::stateChanged, this, &SinkModel::updatePreferredSink);
0249 
0250     updatePreferredSink();
0251 }
0252 
0253 void SinkModel::sinkRemoved(int index)
0254 {
0255     Q_UNUSED(index);
0256 
0257     updatePreferredSink();
0258 }
0259 
0260 void SinkModel::updatePreferredSink()
0261 {
0262     Sink *sink = findPreferredSink();
0263 
0264     if (sink != m_preferredSink) {
0265         qCDebug(PLASMAPA) << "Changing preferred sink to" << sink << (sink ? sink->name() : "");
0266         m_preferredSink = sink;
0267         Q_EMIT preferredSinkChanged();
0268     }
0269 }
0270 
0271 Sink *SinkModel::findPreferredSink() const
0272 {
0273     const auto &sinks = context()->sinks();
0274 
0275     // Only one sink is the preferred one
0276     if (sinks.count() == 1) {
0277         return static_cast<Sink *>(sinks.objectAt(0));
0278     }
0279 
0280     auto lookForState = [this](Device::State state) {
0281         Sink *ret = nullptr;
0282         QMapIterator<quint32, Sink *> it(context()->sinks().data());
0283         while (it.hasNext()) {
0284             it.next();
0285             if ((it.value()->isVirtualDevice() && !it.value()->isDefault()) || it.value()->state() != state) {
0286                 continue;
0287             }
0288             if (!ret) {
0289                 ret = it.value();
0290             } else if (it.value() == defaultSink()) {
0291                 ret = it.value();
0292                 break;
0293             }
0294         }
0295         return ret;
0296     };
0297 
0298     Sink *preferred = nullptr;
0299 
0300     // Look for playing sinks + prefer default sink
0301     preferred = lookForState(Device::RunningState);
0302     if (preferred) {
0303         return preferred;
0304     }
0305 
0306     // Look for idle sinks + prefer default sink
0307     preferred = lookForState(Device::IdleState);
0308     if (preferred) {
0309         return preferred;
0310     }
0311 
0312     // Fallback to default sink
0313     return defaultSink();
0314 }
0315 
0316 SourceModel::SourceModel(QObject *parent)
0317     : AbstractModel(&context()->sources(), parent)
0318 {
0319     initRoleNames(Source::staticMetaObject);
0320 
0321     connect(context()->server(), &Server::defaultSourceChanged, this, &SourceModel::defaultSourceChanged);
0322 }
0323 
0324 Source *SourceModel::defaultSource() const
0325 {
0326     return context()->server()->defaultSource();
0327 }
0328 
0329 QVariant SourceModel::data(const QModelIndex &index, int role) const
0330 {
0331     if (role == SortByDefaultRole) {
0332         // Workaround QTBUG-1548
0333         const QString pulseIndex = data(index, AbstractModel::role(QByteArrayLiteral("Index"))).toString();
0334         const QString defaultDevice = data(index, AbstractModel::role(QByteArrayLiteral("Default"))).toString();
0335         return defaultDevice + pulseIndex;
0336     }
0337     return AbstractModel::data(index, role);
0338 }
0339 
0340 SinkInputModel::SinkInputModel(QObject *parent)
0341     : AbstractModel(&context()->sinkInputs(), parent)
0342 {
0343     initRoleNames(SinkInput::staticMetaObject);
0344 }
0345 
0346 SourceOutputModel::SourceOutputModel(QObject *parent)
0347     : AbstractModel(&context()->sourceOutputs(), parent)
0348 {
0349     initRoleNames(SourceOutput::staticMetaObject);
0350 }
0351 
0352 CardModel::CardModel(QObject *parent)
0353     : AbstractModel(&context()->cards(), parent)
0354 {
0355     initRoleNames(Card::staticMetaObject);
0356 }
0357 
0358 StreamRestoreModel::StreamRestoreModel(QObject *parent)
0359     : AbstractModel(&context()->streamRestores(), parent)
0360 {
0361     initRoleNames(StreamRestore::staticMetaObject);
0362 }
0363 
0364 ModuleModel::ModuleModel(QObject *parent)
0365     : AbstractModel(&context()->modules(), parent)
0366 {
0367     initRoleNames(Module::staticMetaObject);
0368 }
0369 
0370 } // QPulseAudio