PulseAudio Qt Bindings

context.cpp
1 /*
2  SPDX-FileCopyrightText: 2014-2015 Harald Sitter <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 
7 #include "context.h"
8 #include "server.h"
9 
10 #include "debug.h"
11 #include <QAbstractEventDispatcher>
12 #include <QDBusConnection>
13 #include <QDBusServiceWatcher>
14 #include <QGuiApplication>
15 #include <QIcon>
16 #include <QTimer>
17 
18 #include <memory>
19 
20 #include "card.h"
21 #include "client.h"
22 #include "module.h"
23 #include "sink.h"
24 #include "sinkinput.h"
25 #include "source.h"
26 #include "sourceoutput.h"
27 #include "streamrestore.h"
28 
29 #include "context_p.h"
30 #include "server_p.h"
31 #include "streamrestore_p.h"
32 
33 namespace PulseAudioQt
34 {
35 qint64 normalVolume()
36 {
37  return PA_VOLUME_NORM;
38 }
39 
40 qint64 minimumVolume()
41 {
42  return PA_VOLUME_MUTED;
43 }
44 
45 qint64 maximumVolume()
46 {
47  return PA_VOLUME_MAX;
48 }
49 
51 {
52  return PA_VOLUME_UI_MAX;
53 }
54 
55 QString ContextPrivate::s_applicationId;
56 
57 #ifndef K_DOXYGEN
58 
59 static bool isGoodState(int eol)
60 {
61  if (eol < 0) {
62  // Error
63  return false;
64  }
65 
66  if (eol > 0) {
67  // End of callback chain
68  return false;
69  }
70 
71  return true;
72 }
73 
74 // --------------------------
75 
76 static void sink_cb(pa_context *context, const pa_sink_info *info, int eol, void *data)
77 {
78  if (!isGoodState(eol))
79  return;
80  Q_ASSERT(context);
81  Q_ASSERT(data);
82  static_cast<ContextPrivate *>(data)->sinkCallback(info);
83 }
84 
85 static void sink_input_callback(pa_context *context, const pa_sink_input_info *info, int eol, void *data)
86 {
87  if (!isGoodState(eol))
88  return;
89  // pulsesink probe is used by gst-pulse only to query sink formats (not for playback)
90  if (qstrcmp(info->name, "pulsesink probe") == 0) {
91  return;
92  }
93  if (const char *id = pa_proplist_gets(info->proplist, "module-stream-restore.id")) {
94  if (qstrcmp(id, "sink-input-by-media-role:event") == 0) {
95  qCDebug(PULSEAUDIOQT) << "Ignoring event role sink input.";
96  return;
97  }
98  }
99  Q_ASSERT(context);
100  Q_ASSERT(data);
101  static_cast<ContextPrivate *>(data)->sinkInputCallback(info);
102 }
103 
104 static void source_cb(pa_context *context, const pa_source_info *info, int eol, void *data)
105 {
106  if (!isGoodState(eol))
107  return;
108  // FIXME: This forces excluding monitors
109  if (info->monitor_of_sink != PA_INVALID_INDEX)
110  return;
111  Q_ASSERT(context);
112  Q_ASSERT(data);
113  static_cast<ContextPrivate *>(data)->sourceCallback(info);
114 }
115 
116 static void source_output_cb(pa_context *context, const pa_source_output_info *info, int eol, void *data)
117 {
118  if (!isGoodState(eol))
119  return;
120  // FIXME: This forces excluding these apps
121  if (const char *app = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_ID)) {
122  if (strcmp(app, "org.PulseAudio.pavucontrol") == 0 //
123  || strcmp(app, "org.gnome.VolumeControl") == 0 //
124  || strcmp(app, "org.kde.kmixd") == 0 //
125  || strcmp(app, "org.kde.plasma-pa") == 0) //
126  return;
127  }
128  Q_ASSERT(context);
129  Q_ASSERT(data);
130  static_cast<ContextPrivate *>(data)->sourceOutputCallback(info);
131 }
132 
133 static void client_cb(pa_context *context, const pa_client_info *info, int eol, void *data)
134 {
135  if (!isGoodState(eol))
136  return;
137  Q_ASSERT(context);
138  Q_ASSERT(data);
139  static_cast<ContextPrivate *>(data)->clientCallback(info);
140 }
141 
142 static void card_cb(pa_context *context, const pa_card_info *info, int eol, void *data)
143 {
144  if (!isGoodState(eol))
145  return;
146  Q_ASSERT(context);
147  Q_ASSERT(data);
148  static_cast<ContextPrivate *>(data)->cardCallback(info);
149 }
150 
151 static void module_info_list_cb(pa_context *context, const pa_module_info *info, int eol, void *data)
152 {
153  if (!isGoodState(eol))
154  return;
155  Q_ASSERT(context);
156  Q_ASSERT(data);
157  static_cast<ContextPrivate *>(data)->moduleCallback(info);
158 }
159 
160 static void server_cb(pa_context *context, const pa_server_info *info, void *data)
161 {
162  Q_ASSERT(context);
163  Q_ASSERT(data);
164  static_cast<ContextPrivate *>(data)->serverCallback(info);
165 }
166 
167 static void context_state_callback(pa_context *context, void *data)
168 {
169  Q_ASSERT(data);
170  static_cast<ContextPrivate *>(data)->contextStateCallback(context);
171 }
172 
173 static void subscribe_cb(pa_context *context, pa_subscription_event_type_t type, uint32_t index, void *data)
174 {
175  Q_ASSERT(data);
176  static_cast<ContextPrivate *>(data)->subscribeCallback(context, type, index);
177 }
178 
179 static void ext_stream_restore_read_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
180 {
181  if (!isGoodState(eol)) {
182  return;
183  }
184  Q_ASSERT(context);
185  Q_ASSERT(data);
186  static_cast<ContextPrivate *>(data)->streamRestoreCallback(info);
187 }
188 
189 static void ext_stream_restore_subscribe_cb(pa_context *context, void *data)
190 {
191  Q_ASSERT(context);
192  Q_ASSERT(data);
193  if (!PAOperation(pa_ext_stream_restore_read(context, ext_stream_restore_read_cb, data))) {
194  qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_read() failed";
195  }
196 }
197 
198 static void ext_stream_restore_change_sink_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
199 {
200  if (!isGoodState(eol)) {
201  return;
202  }
203  Q_ASSERT(context);
204  Q_ASSERT(data);
205  if (qstrncmp(info->name, "sink-input-by", 13) == 0) {
206  ContextPrivate *contextp = static_cast<ContextPrivate *>(data);
207  const QByteArray deviceData = contextp->m_newDefaultSink.toUtf8();
208  pa_ext_stream_restore_info newinfo;
209  newinfo.name = info->name;
210  newinfo.channel_map = info->channel_map;
211  newinfo.volume = info->volume;
212  newinfo.mute = info->mute;
213  newinfo.device = deviceData.constData();
214  contextp->streamRestoreWrite(&newinfo);
215  }
216 }
217 
218 static void ext_stream_restore_change_source_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
219 {
220  if (!isGoodState(eol)) {
221  return;
222  }
223  Q_ASSERT(context);
224  Q_ASSERT(data);
225  if (qstrncmp(info->name, "source-output-by", 16) == 0) {
226  ContextPrivate *contextp = static_cast<ContextPrivate *>(data);
227  const QByteArray deviceData = contextp->m_newDefaultSource.toUtf8();
228  pa_ext_stream_restore_info newinfo;
229  newinfo.name = info->name;
230  newinfo.channel_map = info->channel_map;
231  newinfo.volume = info->volume;
232  newinfo.mute = info->mute;
233  newinfo.device = deviceData.constData();
234  contextp->streamRestoreWrite(&newinfo);
235  }
236 }
237 
238 #endif
239 
240 // --------------------------
241 
242 Context::Context(QObject *parent)
243  : QObject(parent)
244  , d(new ContextPrivate(this))
245 {
246  d->m_server = new Server(this);
247  d->m_context = nullptr;
248  d->m_mainloop = nullptr;
249  d->m_references = 0;
250 
251  d->connectToDaemon();
252 
253  QDBusServiceWatcher *watcher =
254  new QDBusServiceWatcher(QStringLiteral("org.pulseaudio.Server"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this);
255  connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, [this] {
256  d->connectToDaemon();
257  });
258 
259  connect(&d->m_sinks, &MapBaseQObject::added, this, [this](int, QObject *object) {
260  Q_EMIT sinkAdded(static_cast<Sink *>(object));
261  });
262  connect(&d->m_sinks, &MapBaseQObject::removed, this, [this](int, QObject *object) {
263  Q_EMIT sinkRemoved(static_cast<Sink *>(object));
264  });
265 
266  connect(&d->m_sinkInputs, &MapBaseQObject::added, this, [this](int, QObject *object) {
267  Q_EMIT sinkInputAdded(static_cast<SinkInput *>(object));
268  });
269  connect(&d->m_sinkInputs, &MapBaseQObject::removed, this, [this](int, QObject *object) {
270  Q_EMIT sinkInputRemoved(static_cast<SinkInput *>(object));
271  });
272 
273  connect(&d->m_sources, &MapBaseQObject::added, this, [this](int, QObject *object) {
274  Q_EMIT sourceAdded(static_cast<Source *>(object));
275  });
276  connect(&d->m_sources, &MapBaseQObject::removed, this, [this](int, QObject *object) {
277  Q_EMIT sourceRemoved(static_cast<Source *>(object));
278  });
279 
280  connect(&d->m_sourceOutputs, &MapBaseQObject::added, this, [this](int, QObject *object) {
281  Q_EMIT sourceOutputAdded(static_cast<SourceOutput *>(object));
282  });
283  connect(&d->m_sourceOutputs, &MapBaseQObject::removed, this, [this](int, QObject *object) {
284  Q_EMIT sourceOutputRemoved(static_cast<SourceOutput *>(object));
285  });
286 
287  connect(&d->m_clients, &MapBaseQObject::added, this, [this](int, QObject *object) {
288  Q_EMIT clientAdded(static_cast<Client *>(object));
289  });
290  connect(&d->m_clients, &MapBaseQObject::removed, this, [this](int, QObject *object) {
291  Q_EMIT clientRemoved(static_cast<Client *>(object));
292  });
293 
294  connect(&d->m_cards, &MapBaseQObject::added, this, [this](int, QObject *object) {
295  Q_EMIT cardAdded(static_cast<Card *>(object));
296  });
297  connect(&d->m_cards, &MapBaseQObject::removed, this, [this](int, QObject *object) {
298  Q_EMIT cardRemoved(static_cast<Card *>(object));
299  });
300 
301  connect(&d->m_modules, &MapBaseQObject::added, this, [this](int, QObject *object) {
302  Q_EMIT moduleAdded(static_cast<Module *>(object));
303  });
304  connect(&d->m_modules, &MapBaseQObject::removed, this, [this](int, QObject *object) {
305  Q_EMIT moduleRemoved(static_cast<Module *>(object));
306  });
307 
308  connect(&d->m_streamRestores, &MapBaseQObject::added, this, [this](int, QObject *object) {
309  Q_EMIT streamRestoreAdded(static_cast<StreamRestore *>(object));
310  });
311  connect(&d->m_streamRestores, &MapBaseQObject::removed, this, [this](int, QObject *object) {
312  Q_EMIT streamRestoreRemoved(static_cast<StreamRestore *>(object));
313  });
314 }
315 
316 ContextPrivate::ContextPrivate(Context *q)
317  : q(q)
318 {
319 }
320 
321 Context::~Context()
322 {
323  delete d;
324 }
325 
326 ContextPrivate::~ContextPrivate()
327 {
328  if (m_context) {
329  pa_context_unref(m_context);
330  m_context = nullptr;
331  }
332 
333  if (m_mainloop) {
334  pa_glib_mainloop_free(m_mainloop);
335  m_mainloop = nullptr;
336  }
337 
338  reset();
339 }
340 
341 Context *Context::instance()
342 {
343  static std::unique_ptr<Context> context(new Context);
344  return context.get();
345 }
346 
347 void ContextPrivate::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index)
348 {
349  Q_ASSERT(context == m_context);
350 
351  switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
352  case PA_SUBSCRIPTION_EVENT_SINK:
353  if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
354  m_sinks.removeEntry(index);
355  } else {
356  if (!PAOperation(pa_context_get_sink_info_by_index(context, index, sink_cb, this))) {
357  qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_info_by_index() failed";
358  return;
359  }
360  }
361  break;
362 
363  case PA_SUBSCRIPTION_EVENT_SOURCE:
364  if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
365  m_sources.removeEntry(index);
366  } else {
367  if (!PAOperation(pa_context_get_source_info_by_index(context, index, source_cb, this))) {
368  qCWarning(PULSEAUDIOQT) << "pa_context_get_source_info_by_index() failed";
369  return;
370  }
371  }
372  break;
373 
374  case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
375  if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
376  m_sinkInputs.removeEntry(index);
377  } else {
378  if (!PAOperation(pa_context_get_sink_input_info(context, index, sink_input_callback, this))) {
379  qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_input_info() failed";
380  return;
381  }
382  }
383  break;
384 
385  case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
386  if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
387  m_sourceOutputs.removeEntry(index);
388  } else {
389  if (!PAOperation(pa_context_get_source_output_info(context, index, source_output_cb, this))) {
390  qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_input_info() failed";
391  return;
392  }
393  }
394  break;
395 
396  case PA_SUBSCRIPTION_EVENT_CLIENT:
397  if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
398  m_clients.removeEntry(index);
399  } else {
400  if (!PAOperation(pa_context_get_client_info(context, index, client_cb, this))) {
401  qCWarning(PULSEAUDIOQT) << "pa_context_get_client_info() failed";
402  return;
403  }
404  }
405  break;
406 
407  case PA_SUBSCRIPTION_EVENT_CARD:
408  if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
409  m_cards.removeEntry(index);
410  } else {
411  if (!PAOperation(pa_context_get_card_info_by_index(context, index, card_cb, this))) {
412  qCWarning(PULSEAUDIOQT) << "pa_context_get_card_info_by_index() failed";
413  return;
414  }
415  }
416  break;
417 
418  case PA_SUBSCRIPTION_EVENT_MODULE:
419  if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
420  m_modules.removeEntry(index);
421  } else {
422  if (!PAOperation(pa_context_get_module_info_list(context, module_info_list_cb, this))) {
423  qCWarning(PULSEAUDIOQT) << "pa_context_get_module_info_list() failed";
424  return;
425  }
426  }
427  break;
428 
429  case PA_SUBSCRIPTION_EVENT_SERVER:
430  if (!PAOperation(pa_context_get_server_info(context, server_cb, this))) {
431  qCWarning(PULSEAUDIOQT) << "pa_context_get_server_info() failed";
432  return;
433  }
434  break;
435  }
436 }
437 
438 void ContextPrivate::contextStateCallback(pa_context *c)
439 {
440  qCDebug(PULSEAUDIOQT) << "state callback";
441  pa_context_state_t state = pa_context_get_state(c);
442  if (state == PA_CONTEXT_READY) {
443  qCDebug(PULSEAUDIOQT) << "ready";
444 
445  // 1. Register for the stream changes (except during probe)
446  if (m_context == c) {
447  pa_context_set_subscribe_callback(c, subscribe_cb, this);
448 
449  if (!PAOperation(
450  pa_context_subscribe(c,
451  (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_CLIENT
452  | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT
453  | PA_SUBSCRIPTION_MASK_CARD | PA_SUBSCRIPTION_MASK_MODULE | PA_SUBSCRIPTION_MASK_SERVER),
454  nullptr,
455  nullptr))) {
456  qCWarning(PULSEAUDIOQT) << "pa_context_subscribe() failed";
457  return;
458  }
459  }
460 
461  if (!PAOperation(pa_context_get_sink_info_list(c, sink_cb, this))) {
462  qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_info_list() failed";
463  return;
464  }
465 
466  if (!PAOperation(pa_context_get_source_info_list(c, source_cb, this))) {
467  qCWarning(PULSEAUDIOQT) << "pa_context_get_source_info_list() failed";
468  return;
469  }
470 
471  if (!PAOperation(pa_context_get_client_info_list(c, client_cb, this))) {
472  qCWarning(PULSEAUDIOQT) << "pa_context_client_info_list() failed";
473  return;
474  }
475 
476  if (!PAOperation(pa_context_get_card_info_list(c, card_cb, this))) {
477  qCWarning(PULSEAUDIOQT) << "pa_context_get_card_info_list() failed";
478  return;
479  }
480 
481  if (!PAOperation(pa_context_get_sink_input_info_list(c, sink_input_callback, this))) {
482  qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_input_info_list() failed";
483  return;
484  }
485 
486  if (!PAOperation(pa_context_get_source_output_info_list(c, source_output_cb, this))) {
487  qCWarning(PULSEAUDIOQT) << "pa_context_get_source_output_info_list() failed";
488  return;
489  }
490 
491  if (!PAOperation(pa_context_get_module_info_list(c, module_info_list_cb, this))) {
492  qCWarning(PULSEAUDIOQT) << "pa_context_get_module_info_list() failed";
493  return;
494  }
495 
496  if (!PAOperation(pa_context_get_server_info(c, server_cb, this))) {
497  qCWarning(PULSEAUDIOQT) << "pa_context_get_server_info() failed";
498  return;
499  }
500 
501  if (PAOperation(pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, this))) {
502  pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, this);
503  PAOperation(pa_ext_stream_restore_subscribe(c, 1, nullptr, this));
504  } else {
505  qCWarning(PULSEAUDIOQT) << "Failed to initialize stream_restore extension";
506  }
507  } else if (!PA_CONTEXT_IS_GOOD(state)) {
508  qCWarning(PULSEAUDIOQT) << "context kaput";
509  if (m_context) {
510  pa_context_unref(m_context);
511  m_context = nullptr;
512  }
513  reset();
514  QTimer::singleShot(1000, q, [this] {
515  connectToDaemon();
516  });
517  }
518 }
519 
520 void ContextPrivate::sinkCallback(const pa_sink_info *info)
521 {
522  // This parenting here is a bit weird
523  m_sinks.updateEntry(info, q);
524 }
525 
526 void ContextPrivate::sinkInputCallback(const pa_sink_input_info *info)
527 {
528  m_sinkInputs.updateEntry(info, q);
529 }
530 
531 void ContextPrivate::sourceCallback(const pa_source_info *info)
532 {
533  m_sources.updateEntry(info, q);
534 }
535 
536 void ContextPrivate::sourceOutputCallback(const pa_source_output_info *info)
537 {
538  m_sourceOutputs.updateEntry(info, q);
539 }
540 
541 void ContextPrivate::clientCallback(const pa_client_info *info)
542 {
543  m_clients.updateEntry(info, q);
544 }
545 
546 void ContextPrivate::cardCallback(const pa_card_info *info)
547 {
548  m_cards.updateEntry(info, q);
549 }
550 
551 void ContextPrivate::moduleCallback(const pa_module_info *info)
552 {
553  m_modules.updateEntry(info, q);
554 }
555 
556 void ContextPrivate::streamRestoreCallback(const pa_ext_stream_restore_info *info)
557 {
558  if (qstrcmp(info->name, "sink-input-by-media-role:event") != 0) {
559  return;
560  }
561 
562  const int eventRoleIndex = 1;
563  StreamRestore *obj = qobject_cast<StreamRestore *>(m_streamRestores.data().value(eventRoleIndex));
564 
565  if (!obj) {
566  QVariantMap props;
567  props.insert(QStringLiteral("application.icon_name"), QStringLiteral("preferences-desktop-notification"));
568  obj = new StreamRestore(eventRoleIndex, props, q);
569  obj->d->update(info);
570  m_streamRestores.insert(obj);
571  } else {
572  obj->d->update(info);
573  }
574 }
575 
576 void ContextPrivate::serverCallback(const pa_server_info *info)
577 {
578  m_server->d->update(info);
579 }
580 
581 void Context::setCardProfile(quint32 index, const QString &profile)
582 {
583  if (!d->m_context) {
584  return;
585  }
586  qCDebug(PULSEAUDIOQT) << index << profile;
587  if (!PAOperation(pa_context_set_card_profile_by_index(d->m_context, index, profile.toUtf8().constData(), nullptr, nullptr))) {
588  qCWarning(PULSEAUDIOQT) << "pa_context_set_card_profile_by_index failed";
589  return;
590  }
591 }
592 
593 void Context::setDefaultSink(const QString &name)
594 {
595  if (!d->m_context) {
596  return;
597  }
598  const QByteArray nameData = name.toUtf8();
599  if (!PAOperation(pa_context_set_default_sink(d->m_context, nameData.constData(), nullptr, nullptr))) {
600  qCWarning(PULSEAUDIOQT) << "pa_context_set_default_sink failed";
601  }
602 
603  // Change device for all entries in stream-restore database
604  d->m_newDefaultSink = name;
605  if (!PAOperation(pa_ext_stream_restore_read(d->m_context, ext_stream_restore_change_sink_cb, d))) {
606  qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_read failed";
607  }
608 }
609 
610 void Context::setDefaultSource(const QString &name)
611 {
612  if (!d->m_context) {
613  return;
614  }
615  const QByteArray nameData = name.toUtf8();
616  if (!PAOperation(pa_context_set_default_source(d->m_context, nameData.constData(), nullptr, nullptr))) {
617  qCWarning(PULSEAUDIOQT) << "pa_context_set_default_source failed";
618  }
619 
620  // Change device for all entries in stream-restore database
621  d->m_newDefaultSource = name;
622  if (!PAOperation(pa_ext_stream_restore_read(d->m_context, ext_stream_restore_change_source_cb, d))) {
623  qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_read failed";
624  }
625 }
626 
627 void ContextPrivate::streamRestoreWrite(const pa_ext_stream_restore_info *info)
628 {
629  if (!m_context) {
630  return;
631  }
632  if (!PAOperation(pa_ext_stream_restore_write(m_context, PA_UPDATE_REPLACE, info, 1, true, nullptr, nullptr))) {
633  qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_write failed";
634  }
635 }
636 
637 void ContextPrivate::connectToDaemon()
638 {
639  if (m_context) {
640  return;
641  }
642 
643  // We require a glib event loop
644  if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("Glib")) {
645  qCWarning(PULSEAUDIOQT) << "Disabling PulseAudio integration for lack of GLib event loop";
646  return;
647  }
648 
649  qCDebug(PULSEAUDIOQT) << "Attempting connection to PulseAudio sound daemon";
650  if (!m_mainloop) {
651  m_mainloop = pa_glib_mainloop_new(nullptr);
652  Q_ASSERT(m_mainloop);
653  }
654 
655  pa_mainloop_api *api = pa_glib_mainloop_get_api(m_mainloop);
656  Q_ASSERT(api);
657 
658  pa_proplist *proplist = pa_proplist_new();
659  pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, QGuiApplication::applicationDisplayName().toUtf8().constData());
660  if (!s_applicationId.isEmpty()) {
661  pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, s_applicationId.toUtf8().constData());
662  } else {
663  pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, QGuiApplication::desktopFileName().toUtf8().constData());
664  }
665  pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, QGuiApplication::windowIcon().name().toUtf8().constData());
666  m_context = pa_context_new_with_proplist(api, nullptr, proplist);
667  pa_proplist_free(proplist);
668  Q_ASSERT(m_context);
669 
670  if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFAIL, nullptr) < 0) {
671  pa_context_unref(m_context);
672  pa_glib_mainloop_free(m_mainloop);
673  m_context = nullptr;
674  m_mainloop = nullptr;
675  return;
676  }
677  pa_context_set_state_callback(m_context, &context_state_callback, this);
678 }
679 
680 void ContextPrivate::reset()
681 {
682  m_sinks.reset();
683  m_sinkInputs.reset();
684  m_sources.reset();
685  m_sourceOutputs.reset();
686  m_clients.reset();
687  m_cards.reset();
688  m_modules.reset();
689  m_streamRestores.reset();
690  m_server->reset();
691 }
692 
693 bool Context::isValid()
694 {
695  return d->m_context && d->m_mainloop;
696 }
697 
698 QVector<Sink *> Context::sinks() const
699 {
700  return d->m_sinks.data();
701 }
702 
703 QVector<SinkInput *> Context::sinkInputs() const
704 {
705  return d->m_sinkInputs.data();
706 }
707 
708 QVector<Source *> Context::sources() const
709 {
710  return d->m_sources.data();
711 }
712 
713 QVector<SourceOutput *> Context::sourceOutputs() const
714 {
715  return d->m_sourceOutputs.data();
716 }
717 
718 QVector<Client *> Context::clients() const
719 {
720  return d->m_clients.data();
721 }
722 
723 QVector<Card *> Context::cards() const
724 {
725  return d->m_cards.data();
726 }
727 
728 QVector<Module *> Context::modules() const
729 {
730  return d->m_modules.data();
731 }
732 
733 QVector<StreamRestore *> Context::streamRestores() const
734 {
735  return d->m_streamRestores.data();
736 }
737 
738 Server *Context::server() const
739 {
740  return d->m_server;
741 }
742 
743 void ContextPrivate::setGenericVolume(
744  quint32 index,
745  int channel,
746  qint64 newVolume,
747  pa_cvolume cVolume,
748  const std::function<pa_operation *(pa_context *, uint32_t, const pa_cvolume *, pa_context_success_cb_t, void *)> &pa_set_volume)
749 {
750  if (!m_context) {
751  return;
752  }
753  newVolume = qBound<qint64>(0, newVolume, PA_VOLUME_MAX);
754  pa_cvolume newCVolume = cVolume;
755  if (channel == -1) { // -1 all channels
756  const qint64 diff = newVolume - pa_cvolume_max(&cVolume);
757  for (int i = 0; i < newCVolume.channels; ++i) {
758  newCVolume.values[i] = qBound<qint64>(0, newCVolume.values[i] + diff, PA_VOLUME_MAX);
759  }
760  } else {
761  Q_ASSERT(newCVolume.channels > channel);
762  newCVolume.values[channel] = newVolume;
763  }
764  if (!pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr)) {
765  qCWarning(PULSEAUDIOQT) << "pa_set_volume failed";
766  return;
767  }
768 }
769 
770 void ContextPrivate::setGenericMute(quint32 index,
771  bool mute,
772  const std::function<pa_operation *(pa_context *, uint32_t, int, pa_context_success_cb_t, void *)> &pa_set_mute)
773 {
774  if (!m_context) {
775  return;
776  }
777  if (!PAOperation(pa_set_mute(m_context, index, mute, nullptr, nullptr))) {
778  qCWarning(PULSEAUDIOQT) << "pa_set_mute failed";
779  return;
780  }
781 }
782 
783 void ContextPrivate::setGenericPort(quint32 index,
784  const QString &portName,
785  const std::function<pa_operation *(pa_context *, uint32_t, const char *, pa_context_success_cb_t, void *)> &pa_set_port)
786 {
787  if (!m_context) {
788  return;
789  }
790  if (!PAOperation(pa_set_port(m_context, index, portName.toUtf8().constData(), nullptr, nullptr))) {
791  qCWarning(PULSEAUDIOQT) << "pa_set_port failed";
792  return;
793  }
794 }
795 
796 void ContextPrivate::setGenericDeviceForStream(
797  quint32 streamIndex,
798  quint32 deviceIndex,
799  const std::function<pa_operation *(pa_context *, uint32_t, uint32_t, pa_context_success_cb_t, void *)> &pa_move_stream_to_device)
800 {
801  if (!m_context) {
802  return;
803  }
804  if (!PAOperation(pa_move_stream_to_device(m_context, streamIndex, deviceIndex, nullptr, nullptr))) {
805  qCWarning(PULSEAUDIOQT) << "pa_move_stream_to_device failed";
806  return;
807  }
808 }
809 
810 void ContextPrivate::setGenericVolumes(
811  quint32 index,
812  QVector<qint64> channelVolumes,
813  pa_cvolume cVolume,
814  const std::function<pa_operation *(pa_context *, uint32_t, const pa_cvolume *, pa_context_success_cb_t, void *)> &pa_set_volume)
815 {
816  if (!m_context) {
817  return;
818  }
819  Q_ASSERT(channelVolumes.count() == cVolume.channels);
820 
821  pa_cvolume newCVolume = cVolume;
822  for (int i = 0; i < channelVolumes.count(); ++i) {
823  newCVolume.values[i] = qBound<qint64>(0, channelVolumes.at(i), PA_VOLUME_MAX);
824  }
825 
826  if (!PAOperation(pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr))) {
827  qCWarning(PULSEAUDIOQT) << "pa_set_volume failed";
828  return;
829  }
830 }
831 
832 void Context::setApplicationId(const QString &applicationId)
833 {
834  ContextPrivate::s_applicationId = applicationId;
835 }
836 
837 pa_context *Context::context() const
838 {
839  return d->m_context;
840 }
841 
842 } // PulseAudioQt
T * data()
KGuiItem reset()
The primary namespace of PulseAudioQt.
Definition: card.cpp:16
QDBusConnection sessionBus()
qint64 maximumVolume()
The maximum volume PulseAudio can store.
Definition: context.cpp:45
qint64 minimumVolume()
The minimum volume (0%).
Definition: context.cpp:40
const T & at(int i) const const
QByteArray toUtf8() const const
void serviceRegistered(const QString &serviceName)
qint64 normalVolume()
The normal volume (100%, 0 dB).
Definition: context.cpp:35
const char * constData() const const
QString name(StandardShortcut id)
int count(const T &value) const const
qint64 maximumUIVolume()
The maximum volume suitable to display in a UI.
Definition: context.cpp:50
QAbstractEventDispatcher * instance(QThread *thread)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Jun 27 2022 04:15:49 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.