File indexing completed on 2023-11-26 12:10:35
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.endsWith(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