File indexing completed on 2023-12-10 11:57:38
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 auto keys = d->m_signalIndexToProperties.keys(); 0200 foreach (int 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