File indexing completed on 2024-04-14 14:55:43

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