Changeset View
Changeset View
Standalone View
Standalone View
plugins/systemvolume/context.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | Copyright 2014-2015 Harald Sitter <sitter@kde.org> | ||||
3 | | ||||
4 | This library is free software; you can redistribute it and/or | ||||
5 | modify it under the terms of the GNU Lesser General Public | ||||
6 | License as published by the Free Software Foundation; either | ||||
7 | version 2.1 of the License, or (at your option) version 3, or any | ||||
8 | later version accepted by the membership of KDE e.V. (or its | ||||
9 | successor approved by the membership of KDE e.V.), which shall | ||||
10 | act as a proxy defined in Section 6 of version 3 of the license. | ||||
11 | | ||||
12 | This library is distributed in the hope that it will be useful, | ||||
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
15 | Lesser General Public License for more details. | ||||
16 | | ||||
17 | You should have received a copy of the GNU Lesser General Public | ||||
18 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||||
19 | */ | ||||
20 | | ||||
21 | #include "context.h" | ||||
22 | #include "server.h" | ||||
23 | | ||||
24 | #include <QAbstractEventDispatcher> | ||||
25 | #include "debug.h" | ||||
26 | #include <QMutexLocker> | ||||
27 | #include <QTimer> | ||||
28 | #include <QDBusServiceWatcher> | ||||
29 | #include <QDBusConnection> | ||||
30 | | ||||
31 | #include "card.h" | ||||
32 | #include "client.h" | ||||
33 | #include "sink.h" | ||||
34 | #include "sinkinput.h" | ||||
35 | #include "source.h" | ||||
36 | #include "sourceoutput.h" | ||||
37 | #include "streamrestore.h" | ||||
38 | #include "module.h" | ||||
39 | | ||||
40 | namespace QPulseAudio | ||||
41 | { | ||||
42 | | ||||
43 | Context* Context::s_context = nullptr; | ||||
44 | | ||||
45 | static bool isGoodState(int eol) | ||||
46 | { | ||||
47 | if (eol < 0) { | ||||
48 | // Error | ||||
49 | return false; | ||||
50 | } | ||||
51 | | ||||
52 | if (eol > 0) { | ||||
53 | // End of callback chain | ||||
54 | return false; | ||||
55 | } | ||||
56 | | ||||
57 | return true; | ||||
58 | } | ||||
59 | | ||||
60 | // -------------------------- | ||||
61 | | ||||
62 | static void sink_cb(pa_context *context, const pa_sink_info *info, int eol, void *data) | ||||
63 | { | ||||
64 | if (!isGoodState(eol)) | ||||
65 | return; | ||||
66 | Q_ASSERT(context); | ||||
67 | Q_ASSERT(data); | ||||
68 | ((Context *)data)->sinkCallback(info); | ||||
69 | } | ||||
70 | | ||||
71 | static void sink_input_callback(pa_context *context, const pa_sink_input_info *info, int eol, void *data) | ||||
72 | { | ||||
73 | if (!isGoodState(eol)) | ||||
74 | return; | ||||
75 | // pulsesink probe is used by gst-pulse only to query sink formats (not for playback) | ||||
76 | if (qstrcmp(info->name, "pulsesink probe") == 0) { | ||||
77 | return; | ||||
78 | } | ||||
79 | if (const char *id = pa_proplist_gets(info->proplist, "module-stream-restore.id")) { | ||||
80 | if (qstrcmp(id, "sink-input-by-media-role:event") == 0) { | ||||
81 | qCDebug(PLASMAPA) << "Ignoring event role sink input."; | ||||
82 | return; | ||||
83 | } | ||||
84 | } | ||||
85 | Q_ASSERT(context); | ||||
86 | Q_ASSERT(data); | ||||
87 | ((Context *)data)->sinkInputCallback(info); | ||||
88 | } | ||||
89 | | ||||
90 | static void source_cb(pa_context *context, const pa_source_info *info, int eol, void *data) | ||||
91 | { | ||||
92 | if (!isGoodState(eol)) | ||||
93 | return; | ||||
94 | // FIXME: This forces excluding monitors | ||||
95 | if (info->monitor_of_sink != PA_INVALID_INDEX) | ||||
96 | return; | ||||
97 | Q_ASSERT(context); | ||||
98 | Q_ASSERT(data); | ||||
99 | ((Context *)data)->sourceCallback(info); | ||||
100 | } | ||||
101 | | ||||
102 | static void source_output_cb(pa_context *context, const pa_source_output_info *info, int eol, void *data) | ||||
103 | { | ||||
104 | if (!isGoodState(eol)) | ||||
105 | return; | ||||
106 | // FIXME: This forces excluding these apps | ||||
107 | if (const char *app = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_ID)) { | ||||
108 | if (strcmp(app, "org.PulseAudio.pavucontrol") == 0 | ||||
109 | || strcmp(app, "org.gnome.VolumeControl") == 0 | ||||
110 | || strcmp(app, "org.kde.kmixd") == 0) | ||||
111 | return; | ||||
112 | } | ||||
113 | Q_ASSERT(context); | ||||
114 | Q_ASSERT(data); | ||||
115 | ((Context *)data)->sourceOutputCallback(info); | ||||
116 | } | ||||
117 | | ||||
118 | static void client_cb(pa_context *context, const pa_client_info *info, int eol, void *data) | ||||
119 | { | ||||
120 | if (!isGoodState(eol)) | ||||
121 | return; | ||||
122 | Q_ASSERT(context); | ||||
123 | Q_ASSERT(data); | ||||
124 | ((Context *)data)->clientCallback(info); | ||||
125 | } | ||||
126 | | ||||
127 | static void card_cb(pa_context *context, const pa_card_info *info, int eol, void *data) | ||||
128 | { | ||||
129 | if (!isGoodState(eol)) | ||||
130 | return; | ||||
131 | Q_ASSERT(context); | ||||
132 | Q_ASSERT(data); | ||||
133 | ((Context *)data)->cardCallback(info); | ||||
134 | } | ||||
135 | | ||||
136 | static void module_info_list_cb(pa_context *context, const pa_module_info *info, int eol, void *data) | ||||
137 | { | ||||
138 | if (!isGoodState(eol)) | ||||
139 | return; | ||||
140 | Q_ASSERT(context); | ||||
141 | Q_ASSERT(data); | ||||
142 | ((Context *)data)->moduleCallback(info); | ||||
143 | } | ||||
144 | | ||||
145 | static void server_cb(pa_context *context, const pa_server_info *info, void *data) | ||||
146 | { | ||||
147 | Q_ASSERT(context); | ||||
148 | Q_ASSERT(data); | ||||
149 | ((Context *)data)->serverCallback(info); | ||||
150 | } | ||||
151 | | ||||
152 | static void context_state_callback(pa_context *context, void *data) | ||||
153 | { | ||||
154 | Q_ASSERT(data); | ||||
155 | ((Context *)data)->contextStateCallback(context); | ||||
156 | } | ||||
157 | | ||||
158 | static void subscribe_cb(pa_context *context, pa_subscription_event_type_t type, uint32_t index, void *data) | ||||
159 | { | ||||
160 | Q_ASSERT(data); | ||||
161 | ((Context *)data)->subscribeCallback(context, type, index); | ||||
162 | } | ||||
163 | | ||||
164 | static void ext_stream_restore_read_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data) | ||||
165 | { | ||||
166 | if (!isGoodState(eol)) { | ||||
167 | return; | ||||
168 | } | ||||
169 | Q_ASSERT(context); | ||||
170 | Q_ASSERT(data); | ||||
171 | ((Context *)data)->streamRestoreCallback(info); | ||||
172 | } | ||||
173 | | ||||
174 | static void ext_stream_restore_subscribe_cb(pa_context *context, void *data) | ||||
175 | { | ||||
176 | Q_ASSERT(context); | ||||
177 | Q_ASSERT(data); | ||||
178 | if (!PAOperation(pa_ext_stream_restore_read(context, ext_stream_restore_read_cb, data))) { | ||||
179 | qCWarning(PLASMAPA) << "pa_ext_stream_restore_read() failed"; | ||||
180 | } | ||||
181 | } | ||||
182 | | ||||
183 | static void ext_stream_restore_change_sink_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data) | ||||
184 | { | ||||
185 | if (!isGoodState(eol)) { | ||||
186 | return; | ||||
187 | } | ||||
188 | Q_ASSERT(context); | ||||
189 | Q_ASSERT(data); | ||||
190 | if (qstrncmp(info->name, "sink-input-by", 13) == 0) { | ||||
191 | Context *context = static_cast<Context *>(data); | ||||
192 | const QByteArray deviceData = context->newDefaultSink().toUtf8(); | ||||
193 | pa_ext_stream_restore_info newinfo; | ||||
194 | newinfo.name = info->name; | ||||
195 | newinfo.channel_map = info->channel_map; | ||||
196 | newinfo.volume = info->volume; | ||||
197 | newinfo.mute = info->mute; | ||||
198 | newinfo.device = deviceData.constData(); | ||||
199 | context->streamRestoreWrite(&newinfo); | ||||
200 | } | ||||
201 | } | ||||
202 | | ||||
203 | static void ext_stream_restore_change_source_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data) | ||||
204 | { | ||||
205 | if (!isGoodState(eol)) { | ||||
206 | return; | ||||
207 | } | ||||
208 | Q_ASSERT(context); | ||||
209 | Q_ASSERT(data); | ||||
210 | if (qstrncmp(info->name, "source-output-by", 16) == 0) { | ||||
211 | Context *context = static_cast<Context *>(data); | ||||
212 | const QByteArray deviceData = context->newDefaultSource().toUtf8(); | ||||
213 | pa_ext_stream_restore_info newinfo; | ||||
214 | newinfo.name = info->name; | ||||
215 | newinfo.channel_map = info->channel_map; | ||||
216 | newinfo.volume = info->volume; | ||||
217 | newinfo.mute = info->mute; | ||||
218 | newinfo.device = deviceData.constData(); | ||||
219 | context->streamRestoreWrite(&newinfo); | ||||
220 | } | ||||
221 | } | ||||
222 | | ||||
223 | // -------------------------- | ||||
224 | | ||||
225 | Context::Context(QObject *parent) | ||||
226 | : QObject(parent) | ||||
227 | , m_server(new Server(this)) | ||||
228 | , m_context(nullptr) | ||||
229 | , m_mainloop(nullptr) | ||||
230 | , m_references(0) | ||||
231 | { | ||||
232 | QDBusServiceWatcher *watcher = new QDBusServiceWatcher(QStringLiteral("org.pulseaudio.Server"), | ||||
233 | QDBusConnection::sessionBus(), | ||||
234 | QDBusServiceWatcher::WatchForRegistration, | ||||
235 | this); | ||||
236 | connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &Context::connectToDaemon); | ||||
237 | connectToDaemon(); | ||||
238 | } | ||||
239 | | ||||
240 | Context::~Context() | ||||
241 | { | ||||
242 | if (m_context) { | ||||
243 | pa_context_unref(m_context); | ||||
244 | m_context = nullptr; | ||||
245 | } | ||||
246 | | ||||
247 | if (m_mainloop) { | ||||
248 | pa_glib_mainloop_free(m_mainloop); | ||||
249 | m_mainloop = nullptr; | ||||
250 | } | ||||
251 | | ||||
252 | reset(); | ||||
253 | } | ||||
254 | | ||||
255 | Context *Context::instance() | ||||
256 | { | ||||
257 | if (!s_context) { | ||||
258 | s_context = new Context; | ||||
259 | } | ||||
260 | return s_context; | ||||
261 | } | ||||
262 | | ||||
263 | void Context::ref() | ||||
264 | { | ||||
265 | ++m_references; | ||||
266 | } | ||||
267 | | ||||
268 | void Context::unref() | ||||
269 | { | ||||
270 | if (--m_references == 0) { | ||||
271 | delete this; | ||||
272 | s_context = nullptr; | ||||
273 | } | ||||
274 | } | ||||
275 | | ||||
276 | void Context::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index) | ||||
277 | { | ||||
278 | Q_ASSERT(context == m_context); | ||||
279 | | ||||
280 | switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { | ||||
281 | case PA_SUBSCRIPTION_EVENT_SINK: | ||||
282 | if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { | ||||
283 | m_sinks.removeEntry(index); | ||||
284 | } else { | ||||
285 | if (!PAOperation(pa_context_get_sink_info_by_index(context, index, sink_cb, this))) { | ||||
286 | qCWarning(PLASMAPA) << "pa_context_get_sink_info_by_index() failed"; | ||||
287 | return; | ||||
288 | } | ||||
289 | } | ||||
290 | break; | ||||
291 | | ||||
292 | case PA_SUBSCRIPTION_EVENT_SOURCE: | ||||
293 | if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { | ||||
294 | m_sources.removeEntry(index); | ||||
295 | } else { | ||||
296 | if (!PAOperation(pa_context_get_source_info_by_index(context, index, source_cb, this))) { | ||||
297 | qCWarning(PLASMAPA) << "pa_context_get_source_info_by_index() failed"; | ||||
298 | return; | ||||
299 | } | ||||
300 | } | ||||
301 | break; | ||||
302 | | ||||
303 | case PA_SUBSCRIPTION_EVENT_SINK_INPUT: | ||||
304 | if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { | ||||
305 | m_sinkInputs.removeEntry(index); | ||||
306 | } else { | ||||
307 | if (!PAOperation(pa_context_get_sink_input_info(context, index, sink_input_callback, this))) { | ||||
308 | qCWarning(PLASMAPA) << "pa_context_get_sink_input_info() failed"; | ||||
309 | return; | ||||
310 | } | ||||
311 | } | ||||
312 | break; | ||||
313 | | ||||
314 | case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: | ||||
315 | if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { | ||||
316 | m_sourceOutputs.removeEntry(index); | ||||
317 | } else { | ||||
318 | if (!PAOperation(pa_context_get_source_output_info(context, index, source_output_cb, this))) { | ||||
319 | qCWarning(PLASMAPA) << "pa_context_get_sink_input_info() failed"; | ||||
320 | return; | ||||
321 | } | ||||
322 | } | ||||
323 | break; | ||||
324 | | ||||
325 | case PA_SUBSCRIPTION_EVENT_CLIENT: | ||||
326 | if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { | ||||
327 | m_clients.removeEntry(index); | ||||
328 | } else { | ||||
329 | if (!PAOperation(pa_context_get_client_info(context, index, client_cb, this))) { | ||||
330 | qCWarning(PLASMAPA) << "pa_context_get_client_info() failed"; | ||||
331 | return; | ||||
332 | } | ||||
333 | } | ||||
334 | break; | ||||
335 | | ||||
336 | case PA_SUBSCRIPTION_EVENT_CARD: | ||||
337 | if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { | ||||
338 | m_cards.removeEntry(index); | ||||
339 | } else { | ||||
340 | if (!PAOperation(pa_context_get_card_info_by_index(context, index, card_cb, this))) { | ||||
341 | qCWarning(PLASMAPA) << "pa_context_get_card_info_by_index() failed"; | ||||
342 | return; | ||||
343 | } | ||||
344 | } | ||||
345 | break; | ||||
346 | | ||||
347 | case PA_SUBSCRIPTION_EVENT_MODULE: | ||||
348 | if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { | ||||
349 | m_modules.removeEntry(index); | ||||
350 | } else { | ||||
351 | if (!PAOperation(pa_context_get_module_info_list(context, module_info_list_cb, this))) { | ||||
352 | qCWarning(PLASMAPA) << "pa_context_get_module_info_list() failed"; | ||||
353 | return; | ||||
354 | } | ||||
355 | } | ||||
356 | break; | ||||
357 | | ||||
358 | | ||||
359 | case PA_SUBSCRIPTION_EVENT_SERVER: | ||||
360 | if (!PAOperation(pa_context_get_server_info(context, server_cb, this))) { | ||||
361 | qCWarning(PLASMAPA) << "pa_context_get_server_info() failed"; | ||||
362 | return; | ||||
363 | } | ||||
364 | break; | ||||
365 | | ||||
366 | } | ||||
367 | } | ||||
368 | | ||||
369 | void Context::contextStateCallback(pa_context *c) | ||||
370 | { | ||||
371 | qCDebug(PLASMAPA) << "state callback"; | ||||
372 | pa_context_state_t state = pa_context_get_state(c); | ||||
373 | if (state == PA_CONTEXT_READY) { | ||||
374 | qCDebug(PLASMAPA) << "ready"; | ||||
375 | | ||||
376 | // 1. Register for the stream changes (except during probe) | ||||
377 | if (m_context == c) { | ||||
378 | pa_context_set_subscribe_callback(c, subscribe_cb, this); | ||||
379 | | ||||
380 | if (!PAOperation(pa_context_subscribe(c, (pa_subscription_mask_t) | ||||
381 | (PA_SUBSCRIPTION_MASK_SINK| | ||||
382 | PA_SUBSCRIPTION_MASK_SOURCE| | ||||
383 | PA_SUBSCRIPTION_MASK_CLIENT| | ||||
384 | PA_SUBSCRIPTION_MASK_SINK_INPUT| | ||||
385 | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT| | ||||
386 | PA_SUBSCRIPTION_MASK_CARD| | ||||
387 | PA_SUBSCRIPTION_MASK_MODULE| | ||||
388 | PA_SUBSCRIPTION_MASK_SERVER), nullptr, nullptr))) { | ||||
389 | qCWarning(PLASMAPA) << "pa_context_subscribe() failed"; | ||||
390 | return; | ||||
391 | } | ||||
392 | } | ||||
393 | | ||||
394 | if (!PAOperation(pa_context_get_sink_info_list(c, sink_cb, this))) { | ||||
395 | qCWarning(PLASMAPA) << "pa_context_get_sink_info_list() failed"; | ||||
396 | return; | ||||
397 | } | ||||
398 | | ||||
399 | if (!PAOperation(pa_context_get_source_info_list(c, source_cb, this))) { | ||||
400 | qCWarning(PLASMAPA) << "pa_context_get_source_info_list() failed"; | ||||
401 | return; | ||||
402 | } | ||||
403 | | ||||
404 | if (!PAOperation(pa_context_get_client_info_list(c, client_cb, this))) { | ||||
405 | qCWarning(PLASMAPA) << "pa_context_client_info_list() failed"; | ||||
406 | return; | ||||
407 | } | ||||
408 | | ||||
409 | if (!PAOperation(pa_context_get_card_info_list(c, card_cb, this))) { | ||||
410 | qCWarning(PLASMAPA) << "pa_context_get_card_info_list() failed"; | ||||
411 | return; | ||||
412 | } | ||||
413 | | ||||
414 | if (!PAOperation(pa_context_get_sink_input_info_list(c, sink_input_callback, this))) { | ||||
415 | qCWarning(PLASMAPA) << "pa_context_get_sink_input_info_list() failed"; | ||||
416 | return; | ||||
417 | } | ||||
418 | | ||||
419 | if (!PAOperation(pa_context_get_source_output_info_list(c, source_output_cb, this))) { | ||||
420 | qCWarning(PLASMAPA) << "pa_context_get_source_output_info_list() failed"; | ||||
421 | return; | ||||
422 | } | ||||
423 | | ||||
424 | if (!PAOperation(pa_context_get_module_info_list(c, module_info_list_cb, this))) { | ||||
425 | qCWarning(PLASMAPA) << "pa_context_get_module_info_list() failed"; | ||||
426 | return; | ||||
427 | } | ||||
428 | | ||||
429 | if (!PAOperation(pa_context_get_server_info(c, server_cb, this))) { | ||||
430 | qCWarning(PLASMAPA) << "pa_context_get_server_info() failed"; | ||||
431 | return; | ||||
432 | } | ||||
433 | | ||||
434 | if (PAOperation(pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, this))) { | ||||
435 | pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, this); | ||||
436 | PAOperation(pa_ext_stream_restore_subscribe(c, 1, nullptr, this)); | ||||
437 | } else { | ||||
438 | qCWarning(PLASMAPA) << "Failed to initialize stream_restore extension"; | ||||
439 | } | ||||
440 | } else if (!PA_CONTEXT_IS_GOOD(state)) { | ||||
441 | qCWarning(PLASMAPA) << "context kaput"; | ||||
442 | if (m_context) { | ||||
443 | pa_context_unref(m_context); | ||||
444 | m_context = nullptr; | ||||
445 | } | ||||
446 | reset(); | ||||
447 | QTimer::singleShot(1000, this, &Context::connectToDaemon); | ||||
448 | } | ||||
449 | } | ||||
450 | | ||||
451 | void Context::sinkCallback(const pa_sink_info *info) | ||||
452 | { | ||||
453 | // This parenting here is a bit weird | ||||
454 | m_sinks.updateEntry(info, this); | ||||
455 | } | ||||
456 | | ||||
457 | void Context::sinkInputCallback(const pa_sink_input_info *info) | ||||
458 | { | ||||
459 | m_sinkInputs.updateEntry(info, this); | ||||
460 | } | ||||
461 | | ||||
462 | void Context::sourceCallback(const pa_source_info *info) | ||||
463 | { | ||||
464 | m_sources.updateEntry(info, this); | ||||
465 | } | ||||
466 | | ||||
467 | void Context::sourceOutputCallback(const pa_source_output_info *info) | ||||
468 | { | ||||
469 | m_sourceOutputs.updateEntry(info, this); | ||||
470 | } | ||||
471 | | ||||
472 | void Context::clientCallback(const pa_client_info *info) | ||||
473 | { | ||||
474 | m_clients.updateEntry(info, this); | ||||
475 | } | ||||
476 | | ||||
477 | void Context::cardCallback(const pa_card_info *info) | ||||
478 | { | ||||
479 | m_cards.updateEntry(info, this); | ||||
480 | } | ||||
481 | | ||||
482 | void Context::moduleCallback(const pa_module_info *info) | ||||
483 | { | ||||
484 | m_modules.updateEntry(info, this); | ||||
485 | } | ||||
486 | | ||||
487 | void Context::streamRestoreCallback(const pa_ext_stream_restore_info *info) | ||||
488 | { | ||||
489 | if (qstrcmp(info->name, "sink-input-by-media-role:event") != 0) { | ||||
490 | return; | ||||
491 | } | ||||
492 | | ||||
493 | const int eventRoleIndex = 1; | ||||
494 | StreamRestore *obj = qobject_cast<StreamRestore *>(m_streamRestores.data().value(eventRoleIndex)); | ||||
495 | | ||||
496 | if (!obj) { | ||||
497 | QVariantMap props; | ||||
498 | props.insert(QStringLiteral("application.icon_name"), | ||||
499 | QStringLiteral("preferences-desktop-notification")); | ||||
500 | obj = new StreamRestore(eventRoleIndex, props, this); | ||||
501 | m_streamRestores.insert(obj); | ||||
502 | } | ||||
503 | | ||||
504 | obj->update(info); | ||||
505 | } | ||||
506 | | ||||
507 | void Context::serverCallback(const pa_server_info *info) | ||||
508 | { | ||||
509 | m_server->update(info); | ||||
510 | } | ||||
511 | | ||||
512 | void Context::setCardProfile(quint32 index, const QString &profile) | ||||
513 | { | ||||
514 | if (!m_context) { | ||||
515 | return; | ||||
516 | } | ||||
517 | qCDebug(PLASMAPA) << index << profile; | ||||
518 | if (!PAOperation(pa_context_set_card_profile_by_index(m_context, | ||||
519 | index, | ||||
520 | profile.toUtf8().constData(), | ||||
521 | nullptr, nullptr))) { | ||||
522 | qCWarning(PLASMAPA) << "pa_context_set_card_profile_by_index failed"; | ||||
523 | return; | ||||
524 | } | ||||
525 | } | ||||
526 | | ||||
527 | void Context::setDefaultSink(const QString &name) | ||||
528 | { | ||||
529 | if (!m_context) { | ||||
530 | return; | ||||
531 | } | ||||
532 | const QByteArray nameData = name.toUtf8(); | ||||
533 | if (!PAOperation(pa_context_set_default_sink(m_context, | ||||
534 | nameData.constData(), | ||||
535 | nullptr, | ||||
536 | nullptr))) { | ||||
537 | qCWarning(PLASMAPA) << "pa_context_set_default_sink failed"; | ||||
538 | } | ||||
539 | | ||||
540 | // Change device for all entries in stream-restore database | ||||
541 | m_newDefaultSink = name; | ||||
542 | if (!PAOperation(pa_ext_stream_restore_read(m_context, | ||||
543 | ext_stream_restore_change_sink_cb, | ||||
544 | this))) { | ||||
545 | qCWarning(PLASMAPA) << "pa_ext_stream_restore_read failed"; | ||||
546 | } | ||||
547 | } | ||||
548 | | ||||
549 | void Context::setDefaultSource(const QString &name) | ||||
550 | { | ||||
551 | if (!m_context) { | ||||
552 | return; | ||||
553 | } | ||||
554 | const QByteArray nameData = name.toUtf8(); | ||||
555 | if (!PAOperation(pa_context_set_default_source(m_context, | ||||
556 | nameData.constData(), | ||||
557 | nullptr, | ||||
558 | nullptr))) { | ||||
559 | qCWarning(PLASMAPA) << "pa_context_set_default_source failed"; | ||||
560 | } | ||||
561 | | ||||
562 | // Change device for all entries in stream-restore database | ||||
563 | m_newDefaultSource = name; | ||||
564 | if (!PAOperation(pa_ext_stream_restore_read(m_context, | ||||
565 | ext_stream_restore_change_source_cb, | ||||
566 | this))) { | ||||
567 | qCWarning(PLASMAPA) << "pa_ext_stream_restore_read failed"; | ||||
568 | } | ||||
569 | } | ||||
570 | | ||||
571 | void Context::streamRestoreWrite(const pa_ext_stream_restore_info *info) | ||||
572 | { | ||||
573 | if (!m_context) { | ||||
574 | return; | ||||
575 | } | ||||
576 | if (!PAOperation(pa_ext_stream_restore_write(m_context, | ||||
577 | PA_UPDATE_REPLACE, | ||||
578 | info, | ||||
579 | 1, | ||||
580 | true, | ||||
581 | nullptr, | ||||
582 | nullptr))) { | ||||
583 | qCWarning(PLASMAPA) << "pa_ext_stream_restore_write failed"; | ||||
584 | } | ||||
585 | } | ||||
586 | | ||||
587 | void Context::connectToDaemon() | ||||
588 | { | ||||
589 | if (m_context) { | ||||
590 | return; | ||||
591 | } | ||||
592 | | ||||
593 | // We require a glib event loop | ||||
594 | if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("EventDispatcherGlib")) { | ||||
595 | qCWarning(PLASMAPA) << "Disabling PulseAudio integration for lack of GLib event loop"; | ||||
596 | return; | ||||
597 | } | ||||
598 | | ||||
599 | qCDebug(PLASMAPA) << "Attempting connection to PulseAudio sound daemon"; | ||||
600 | if (!m_mainloop) { | ||||
601 | m_mainloop = pa_glib_mainloop_new(nullptr); | ||||
602 | Q_ASSERT(m_mainloop); | ||||
603 | } | ||||
604 | | ||||
605 | pa_mainloop_api *api = pa_glib_mainloop_get_api(m_mainloop); | ||||
606 | Q_ASSERT(api); | ||||
607 | m_context = pa_context_new(api, "QPulse"); | ||||
608 | Q_ASSERT(m_context); | ||||
609 | | ||||
610 | if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFAIL, nullptr) < 0) { | ||||
611 | pa_context_unref(m_context); | ||||
612 | pa_glib_mainloop_free(m_mainloop); | ||||
613 | m_context = nullptr; | ||||
614 | m_mainloop = nullptr; | ||||
615 | return; | ||||
616 | } | ||||
617 | pa_context_set_state_callback(m_context, &context_state_callback, this); | ||||
618 | } | ||||
619 | | ||||
620 | void Context::reset() | ||||
621 | { | ||||
622 | m_sinks.reset(); | ||||
623 | m_sinkInputs.reset(); | ||||
624 | m_sources.reset(); | ||||
625 | m_sourceOutputs.reset(); | ||||
626 | m_clients.reset(); | ||||
627 | m_cards.reset(); | ||||
628 | m_modules.reset(); | ||||
629 | m_streamRestores.reset(); | ||||
630 | m_server->reset(); | ||||
631 | } | ||||
632 | | ||||
633 | } // QPulseAudio |