Changeset View
Changeset View
Standalone View
Standalone View
plugins/systemvolume/pulseaudio.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | Copyright 2014-2015 Harald Sitter <sitter@kde.org> | ||||
3 | Copyright 2016 David Rosca <nowrep@gmail.com> | ||||
4 | | ||||
5 | This library is free software; you can redistribute it and/or | ||||
6 | modify it under the terms of the GNU Lesser General Public | ||||
7 | License as published by the Free Software Foundation; either | ||||
8 | version 2.1 of the License, or (at your option) version 3, or any | ||||
9 | later version accepted by the membership of KDE e.V. (or its | ||||
10 | successor approved by the membership of KDE e.V.), which shall | ||||
11 | act as a proxy defined in Section 6 of version 3 of the license. | ||||
12 | | ||||
13 | This library is distributed in the hope that it will be useful, | ||||
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
16 | Lesser General Public License for more details. | ||||
17 | | ||||
18 | You should have received a copy of the GNU Lesser General Public | ||||
19 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||||
20 | */ | ||||
21 | | ||||
22 | #include "pulseaudio.h" | ||||
23 | | ||||
24 | #include "debug.h" | ||||
25 | #include "card.h" | ||||
26 | #include "sink.h" | ||||
27 | #include "sinkinput.h" | ||||
28 | #include "source.h" | ||||
29 | #include "sourceoutput.h" | ||||
30 | #include "server.h" | ||||
31 | #include "streamrestore.h" | ||||
32 | #include "module.h" | ||||
33 | | ||||
34 | #include <QMetaEnum> | ||||
35 | | ||||
36 | namespace QPulseAudio | ||||
37 | { | ||||
38 | | ||||
39 | AbstractModel::AbstractModel(const MapBaseQObject *map, QObject *parent) | ||||
40 | : QAbstractListModel(parent) | ||||
41 | , m_map(map) | ||||
42 | { | ||||
43 | Context::instance()->ref(); | ||||
44 | //deref context after we've deleted this object | ||||
45 | //see https://bugs.kde.org/show_bug.cgi?id=371215 | ||||
46 | connect(this, &QObject::destroyed, []() { | ||||
47 | Context::instance()->unref(); | ||||
48 | }); | ||||
49 | | ||||
50 | connect(m_map, &MapBaseQObject::added, this, &AbstractModel::onDataAdded); | ||||
51 | connect(m_map, &MapBaseQObject::removed, this, &AbstractModel::onDataRemoved); | ||||
52 | } | ||||
53 | | ||||
54 | QHash<int, QByteArray> AbstractModel::roleNames() const | ||||
55 | { | ||||
56 | if (!m_roles.empty()) { | ||||
57 | qCDebug(PLASMAPA) << "returning roles" << m_roles; | ||||
58 | return m_roles; | ||||
59 | } | ||||
60 | Q_UNREACHABLE(); | ||||
61 | return QHash<int, QByteArray>(); | ||||
62 | } | ||||
63 | | ||||
64 | int AbstractModel::rowCount(const QModelIndex &parent) const | ||||
65 | { | ||||
66 | Q_UNUSED(parent); | ||||
67 | return m_map->count(); | ||||
68 | } | ||||
69 | | ||||
70 | QVariant AbstractModel::data(const QModelIndex &index, int role) const | ||||
71 | { | ||||
72 | QObject *data = m_map->objectAt(index.row()); | ||||
73 | Q_ASSERT(data); | ||||
74 | if (role == PulseObjectRole) { | ||||
75 | return QVariant::fromValue(data); | ||||
76 | } | ||||
77 | int property = m_objectProperties.value(role, -1); | ||||
78 | if (property == -1) { | ||||
79 | return QVariant(); | ||||
80 | } | ||||
81 | return data->metaObject()->property(property).read(data); | ||||
82 | } | ||||
83 | | ||||
84 | bool AbstractModel::setData(const QModelIndex &index, const QVariant &value, int role) | ||||
85 | { | ||||
86 | int propertyIndex = m_objectProperties.value(role, -1); | ||||
87 | if (propertyIndex == -1) { | ||||
88 | return false; | ||||
89 | } | ||||
90 | QObject *data = m_map->objectAt(index.row()); | ||||
91 | auto property = data->metaObject()->property(propertyIndex); | ||||
92 | return property.write(data, value); | ||||
93 | } | ||||
94 | | ||||
95 | int AbstractModel::role(const QByteArray &roleName) const | ||||
96 | { | ||||
97 | qCDebug(PLASMAPA) << roleName << m_roles.key(roleName, -1); | ||||
98 | return m_roles.key(roleName, -1); | ||||
99 | } | ||||
100 | | ||||
101 | Context *AbstractModel::context() const | ||||
102 | { | ||||
103 | return Context::instance(); | ||||
104 | } | ||||
105 | | ||||
106 | void AbstractModel::initRoleNames(const QMetaObject &qobjectMetaObject) | ||||
107 | { | ||||
108 | m_roles[PulseObjectRole] = QByteArrayLiteral("PulseObject"); | ||||
109 | | ||||
110 | QMetaEnum enumerator; | ||||
111 | for (int i = 0; i < metaObject()->enumeratorCount(); ++i) { | ||||
112 | if (metaObject()->enumerator(i).name() == QLatin1String("ItemRole")) { | ||||
113 | enumerator = metaObject()->enumerator(i); | ||||
114 | break; | ||||
115 | } | ||||
116 | } | ||||
117 | | ||||
118 | for (int i = 0; i < enumerator.keyCount(); ++i) { | ||||
119 | // Clip the Role suffix and glue it in the hash. | ||||
120 | const int roleLength = 4; | ||||
121 | QByteArray key(enumerator.key(i)); | ||||
122 | // Enum values must end in Role or the enum is crap | ||||
123 | Q_ASSERT(key.right(roleLength) == QByteArrayLiteral("Role")); | ||||
124 | key.chop(roleLength); | ||||
125 | m_roles[enumerator.value(i)] = key; | ||||
126 | } | ||||
127 | | ||||
128 | int maxEnumValue = -1; | ||||
129 | for (auto it = m_roles.constBegin(); it != m_roles.constEnd(); ++it) { | ||||
130 | if (it.key() > maxEnumValue) { | ||||
131 | maxEnumValue = it.key(); | ||||
132 | } | ||||
133 | } | ||||
134 | Q_ASSERT(maxEnumValue != -1); | ||||
135 | auto mo = qobjectMetaObject; | ||||
136 | for (int i = 0; i < mo.propertyCount(); ++i) { | ||||
137 | QMetaProperty property = mo.property(i); | ||||
138 | QString name(property.name()); | ||||
139 | name.replace(0, 1, name.at(0).toUpper()); | ||||
140 | m_roles[++maxEnumValue] = name.toLatin1(); | ||||
141 | m_objectProperties.insert(maxEnumValue, i); | ||||
142 | if (!property.hasNotifySignal()) { | ||||
143 | continue; | ||||
144 | } | ||||
145 | m_signalIndexToProperties.insert(property.notifySignalIndex(), i); | ||||
146 | } | ||||
147 | qCDebug(PLASMAPA) << m_roles; | ||||
148 | | ||||
149 | // Connect to property changes also with objects already in model | ||||
150 | for (int i = 0; i < m_map->count(); ++i) { | ||||
151 | onDataAdded(i); | ||||
152 | } | ||||
153 | } | ||||
154 | | ||||
155 | void AbstractModel::propertyChanged() | ||||
156 | { | ||||
157 | if (!sender() || senderSignalIndex() == -1) { | ||||
158 | return; | ||||
159 | } | ||||
160 | int propertyIndex = m_signalIndexToProperties.value(senderSignalIndex(), -1); | ||||
161 | if (propertyIndex == -1) { | ||||
162 | return; | ||||
163 | } | ||||
164 | int role = m_objectProperties.key(propertyIndex, -1); | ||||
165 | if (role == -1) { | ||||
166 | return; | ||||
167 | } | ||||
168 | int index = m_map->indexOfObject(sender()); | ||||
169 | qCDebug(PLASMAPA) << "PROPERTY CHANGED (" << index << ") :: " << role << roleNames().value(role); | ||||
170 | emit dataChanged(createIndex(index, 0), createIndex(index, 0), {role}); | ||||
171 | } | ||||
172 | | ||||
173 | void AbstractModel::onDataAdded(int index) | ||||
174 | { | ||||
175 | beginInsertRows(QModelIndex(), index, index); | ||||
176 | QObject *data = m_map->objectAt(index); | ||||
177 | const QMetaObject *mo = data->metaObject(); | ||||
178 | // We have all the data changed notify signals already stored | ||||
179 | auto keys = m_signalIndexToProperties.keys(); | ||||
180 | foreach (int index, keys) { | ||||
181 | QMetaMethod meth = mo->method(index); | ||||
182 | connect(data, meth, this, propertyChangedMetaMethod()); | ||||
183 | } | ||||
184 | endInsertRows(); | ||||
185 | } | ||||
186 | | ||||
187 | void AbstractModel::onDataRemoved(int index) | ||||
188 | { | ||||
189 | beginRemoveRows(QModelIndex(), index, index); | ||||
190 | endRemoveRows(); | ||||
191 | } | ||||
192 | | ||||
193 | QMetaMethod AbstractModel::propertyChangedMetaMethod() const | ||||
194 | { | ||||
195 | auto mo = metaObject(); | ||||
196 | int methodIndex = mo->indexOfMethod("propertyChanged()"); | ||||
197 | if (methodIndex == -1) { | ||||
198 | return QMetaMethod(); | ||||
199 | } | ||||
200 | return mo->method(methodIndex); | ||||
201 | } | ||||
202 | | ||||
203 | SinkModel::SinkModel(QObject *parent) | ||||
204 | : AbstractModel(&context()->sinks(), parent) | ||||
205 | , m_preferredSink(nullptr) | ||||
206 | { | ||||
207 | initRoleNames(Sink::staticMetaObject); | ||||
208 | | ||||
209 | for (int i = 0; i < context()->sinks().count(); ++i) { | ||||
210 | sinkAdded(i); | ||||
211 | } | ||||
212 | | ||||
213 | connect(&context()->sinks(), &MapBaseQObject::added, this, &SinkModel::sinkAdded); | ||||
214 | connect(&context()->sinks(), &MapBaseQObject::removed, this, &SinkModel::sinkRemoved); | ||||
215 | | ||||
216 | connect(context()->server(), &Server::defaultSinkChanged, this, [this]() { | ||||
217 | updatePreferredSink(); | ||||
218 | emit defaultSinkChanged(); | ||||
219 | }); | ||||
220 | } | ||||
221 | | ||||
222 | Sink *SinkModel::defaultSink() const | ||||
223 | { | ||||
224 | return context()->server()->defaultSink(); | ||||
225 | } | ||||
226 | | ||||
227 | Sink *SinkModel::preferredSink() const | ||||
228 | { | ||||
229 | return m_preferredSink; | ||||
230 | } | ||||
231 | | ||||
232 | QVariant SinkModel::data(const QModelIndex &index, int role) const | ||||
233 | { | ||||
234 | if (role == SortByDefaultRole) { | ||||
235 | // Workaround QTBUG-1548 | ||||
236 | const QString pulseIndex = data(index, AbstractModel::role(QByteArrayLiteral("Index"))).toString(); | ||||
237 | const QString defaultDevice = data(index, AbstractModel::role(QByteArrayLiteral("Default"))).toString(); | ||||
238 | return defaultDevice + pulseIndex; | ||||
239 | } | ||||
240 | return AbstractModel::data(index, role); | ||||
241 | } | ||||
242 | | ||||
243 | void SinkModel::sinkAdded(int index) | ||||
244 | { | ||||
245 | Q_ASSERT(qobject_cast<Sink *>(context()->sinks().objectAt(index))); | ||||
246 | Sink *sink = static_cast<Sink *>(context()->sinks().objectAt(index)); | ||||
247 | connect(sink, &Sink::stateChanged, this, &SinkModel::updatePreferredSink); | ||||
248 | | ||||
249 | updatePreferredSink(); | ||||
250 | } | ||||
251 | | ||||
252 | void SinkModel::sinkRemoved(int index) | ||||
253 | { | ||||
254 | Q_UNUSED(index); | ||||
255 | | ||||
256 | updatePreferredSink(); | ||||
257 | } | ||||
258 | | ||||
259 | void SinkModel::updatePreferredSink() | ||||
260 | { | ||||
261 | Sink *sink = findPreferredSink(); | ||||
262 | | ||||
263 | if (sink != m_preferredSink) { | ||||
264 | qCDebug(PLASMAPA) << "Changing preferred sink to" << sink << (sink ? sink->name() : ""); | ||||
265 | m_preferredSink = sink; | ||||
266 | emit preferredSinkChanged(); | ||||
267 | } | ||||
268 | } | ||||
269 | | ||||
270 | Sink *SinkModel::findPreferredSink() const | ||||
271 | { | ||||
272 | const auto &sinks = context()->sinks(); | ||||
273 | | ||||
274 | // Only one sink is the preferred one | ||||
275 | if (sinks.count() == 1) { | ||||
276 | return static_cast<Sink *>(sinks.objectAt(0)); | ||||
277 | } | ||||
278 | | ||||
279 | auto lookForState = [this](Device::State state) { | ||||
280 | Sink *ret = nullptr; | ||||
281 | QMapIterator<quint32, Sink *> it(context()->sinks().data()); | ||||
282 | while (it.hasNext()) { | ||||
283 | it.next(); | ||||
284 | if (it.value()->state() != state) { | ||||
285 | continue; | ||||
286 | } | ||||
287 | if (!ret) { | ||||
288 | ret = it.value(); | ||||
289 | } else if (it.value() == defaultSink()) { | ||||
290 | ret = it.value(); | ||||
291 | break; | ||||
292 | } | ||||
293 | } | ||||
294 | return ret; | ||||
295 | }; | ||||
296 | | ||||
297 | Sink *preferred = nullptr; | ||||
298 | | ||||
299 | // Look for playing sinks + prefer default sink | ||||
300 | preferred = lookForState(Device::RunningState); | ||||
301 | if (preferred) { | ||||
302 | return preferred; | ||||
303 | } | ||||
304 | | ||||
305 | // Look for idle sinks + prefer default sink | ||||
306 | preferred = lookForState(Device::IdleState); | ||||
307 | if (preferred) { | ||||
308 | return preferred; | ||||
309 | } | ||||
310 | | ||||
311 | // Fallback to default sink | ||||
312 | return defaultSink(); | ||||
313 | } | ||||
314 | | ||||
315 | SourceModel::SourceModel(QObject *parent) | ||||
316 | : AbstractModel(&context()->sources(), parent) | ||||
317 | { | ||||
318 | initRoleNames(Source::staticMetaObject); | ||||
319 | | ||||
320 | connect(context()->server(), &Server::defaultSourceChanged, this, &SourceModel::defaultSourceChanged); | ||||
321 | } | ||||
322 | | ||||
323 | Source *SourceModel::defaultSource() const | ||||
324 | { | ||||
325 | return context()->server()->defaultSource(); | ||||
326 | } | ||||
327 | | ||||
328 | QVariant SourceModel::data(const QModelIndex &index, int role) const | ||||
329 | { | ||||
330 | if (role == SortByDefaultRole) { | ||||
331 | // Workaround QTBUG-1548 | ||||
332 | const QString pulseIndex = data(index, AbstractModel::role(QByteArrayLiteral("Index"))).toString(); | ||||
333 | const QString defaultDevice = data(index, AbstractModel::role(QByteArrayLiteral("Default"))).toString(); | ||||
334 | return defaultDevice + pulseIndex; | ||||
335 | } | ||||
336 | return AbstractModel::data(index, role); | ||||
337 | } | ||||
338 | | ||||
339 | SinkInputModel::SinkInputModel(QObject *parent) | ||||
340 | : AbstractModel(&context()->sinkInputs(), parent) | ||||
341 | { | ||||
342 | initRoleNames(SinkInput::staticMetaObject); | ||||
343 | } | ||||
344 | | ||||
345 | SourceOutputModel::SourceOutputModel(QObject *parent) | ||||
346 | : AbstractModel(&context()->sourceOutputs(), parent) | ||||
347 | { | ||||
348 | initRoleNames(SourceOutput::staticMetaObject); | ||||
349 | } | ||||
350 | | ||||
351 | CardModel::CardModel(QObject *parent) | ||||
352 | : AbstractModel(&context()->cards(), parent) | ||||
353 | { | ||||
354 | initRoleNames(Card::staticMetaObject); | ||||
355 | } | ||||
356 | | ||||
357 | StreamRestoreModel::StreamRestoreModel(QObject *parent) | ||||
358 | : AbstractModel(&context()->streamRestores(), parent) | ||||
359 | { | ||||
360 | initRoleNames(StreamRestore::staticMetaObject); | ||||
361 | } | ||||
362 | | ||||
363 | ModuleModel::ModuleModel(QObject *parent) | ||||
364 | : AbstractModel(&context()->modules(), parent) | ||||
365 | { | ||||
366 | initRoleNames(Module::staticMetaObject); | ||||
367 | } | ||||
368 | | ||||
369 | } // QPulseAudio |