11 #include <QAbstractEventDispatcher>
12 #include <QDBusConnection>
13 #include <QDBusServiceWatcher>
14 #include <QGuiApplication>
24 #include "sinkinput.h"
26 #include "sourceoutput.h"
27 #include "streamrestore.h"
29 #include "context_p.h"
31 #include "streamrestore_p.h"
37 return PA_VOLUME_NORM;
42 return PA_VOLUME_MUTED;
52 return PA_VOLUME_UI_MAX;
55 QString ContextPrivate::s_applicationId;
59 static bool isGoodState(
int eol)
76 static void sink_cb(pa_context *context,
const pa_sink_info *info,
int eol,
void *data)
78 if (!isGoodState(eol))
82 static_cast<ContextPrivate *
>(data)->sinkCallback(info);
85 static void sink_input_callback(pa_context *context,
const pa_sink_input_info *info,
int eol,
void *data)
87 if (!isGoodState(eol))
90 if (qstrcmp(info->name,
"pulsesink probe") == 0) {
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.";
101 static_cast<ContextPrivate *
>(data)->sinkInputCallback(info);
104 static void source_cb(pa_context *context,
const pa_source_info *info,
int eol,
void *data)
106 if (!isGoodState(eol))
109 if (info->monitor_of_sink != PA_INVALID_INDEX)
113 static_cast<ContextPrivate *
>(data)->sourceCallback(info);
116 static void source_output_cb(pa_context *context,
const pa_source_output_info *info,
int eol,
void *data)
118 if (!isGoodState(eol))
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)
130 static_cast<ContextPrivate *
>(data)->sourceOutputCallback(info);
133 static void client_cb(pa_context *context,
const pa_client_info *info,
int eol,
void *data)
135 if (!isGoodState(eol))
139 static_cast<ContextPrivate *
>(data)->clientCallback(info);
142 static void card_cb(pa_context *context,
const pa_card_info *info,
int eol,
void *data)
144 if (!isGoodState(eol))
148 static_cast<ContextPrivate *
>(data)->cardCallback(info);
151 static void module_info_list_cb(pa_context *context,
const pa_module_info *info,
int eol,
void *data)
153 if (!isGoodState(eol))
157 static_cast<ContextPrivate *
>(data)->moduleCallback(info);
160 static void server_cb(pa_context *context,
const pa_server_info *info,
void *data)
164 static_cast<ContextPrivate *
>(data)->serverCallback(info);
167 static void context_state_callback(pa_context *context,
void *data)
170 static_cast<ContextPrivate *
>(data)->contextStateCallback(context);
173 static void subscribe_cb(pa_context *context, pa_subscription_event_type_t type, uint32_t index,
void *data)
176 static_cast<ContextPrivate *
>(data)->subscribeCallback(context, type, index);
179 static void ext_stream_restore_read_cb(pa_context *context,
const pa_ext_stream_restore_info *info,
int eol,
void *data)
181 if (!isGoodState(eol)) {
186 static_cast<ContextPrivate *
>(data)->streamRestoreCallback(info);
189 static void ext_stream_restore_subscribe_cb(pa_context *context,
void *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";
198 static void ext_stream_restore_change_sink_cb(pa_context *context,
const pa_ext_stream_restore_info *info,
int eol,
void *data)
200 if (!isGoodState(eol)) {
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;
214 contextp->streamRestoreWrite(&newinfo);
218 static void ext_stream_restore_change_source_cb(pa_context *context,
const pa_ext_stream_restore_info *info,
int eol,
void *data)
220 if (!isGoodState(eol)) {
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;
234 contextp->streamRestoreWrite(&newinfo);
242 Context::Context(
QObject *parent)
244 , d(new ContextPrivate(this))
246 d->m_server =
new Server(
this);
247 d->m_context =
nullptr;
248 d->m_mainloop =
nullptr;
251 d->connectToDaemon();
256 d->connectToDaemon();
259 connect(&d->m_sinks, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
260 Q_EMIT sinkAdded(static_cast<Sink *>(object));
262 connect(&d->m_sinks, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
263 Q_EMIT sinkRemoved(static_cast<Sink *>(object));
266 connect(&d->m_sinkInputs, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
267 Q_EMIT sinkInputAdded(static_cast<SinkInput *>(object));
269 connect(&d->m_sinkInputs, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
270 Q_EMIT sinkInputRemoved(static_cast<SinkInput *>(object));
273 connect(&d->m_sources, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
274 Q_EMIT sourceAdded(static_cast<Source *>(object));
276 connect(&d->m_sources, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
277 Q_EMIT sourceRemoved(static_cast<Source *>(object));
280 connect(&d->m_sourceOutputs, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
281 Q_EMIT sourceOutputAdded(static_cast<SourceOutput *>(object));
283 connect(&d->m_sourceOutputs, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
284 Q_EMIT sourceOutputRemoved(static_cast<SourceOutput *>(object));
287 connect(&d->m_clients, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
288 Q_EMIT clientAdded(static_cast<Client *>(object));
290 connect(&d->m_clients, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
291 Q_EMIT clientRemoved(static_cast<Client *>(object));
294 connect(&d->m_cards, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
295 Q_EMIT cardAdded(static_cast<Card *>(object));
297 connect(&d->m_cards, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
298 Q_EMIT cardRemoved(static_cast<Card *>(object));
301 connect(&d->m_modules, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
302 Q_EMIT moduleAdded(static_cast<Module *>(object));
304 connect(&d->m_modules, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
305 Q_EMIT moduleRemoved(static_cast<Module *>(object));
308 connect(&d->m_streamRestores, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
309 Q_EMIT streamRestoreAdded(static_cast<StreamRestore *>(object));
311 connect(&d->m_streamRestores, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
312 Q_EMIT streamRestoreRemoved(static_cast<StreamRestore *>(object));
316 ContextPrivate::ContextPrivate(Context *q)
326 ContextPrivate::~ContextPrivate()
329 pa_context_unref(m_context);
334 pa_glib_mainloop_free(m_mainloop);
335 m_mainloop =
nullptr;
341 Context *Context::instance()
343 static std::unique_ptr<Context> context(
new Context);
344 return context.get();
347 void ContextPrivate::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index)
349 Q_ASSERT(context == m_context);
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);
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";
363 case PA_SUBSCRIPTION_EVENT_SOURCE:
364 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
365 m_sources.removeEntry(index);
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";
374 case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
375 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
376 m_sinkInputs.removeEntry(index);
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";
385 case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
386 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
387 m_sourceOutputs.removeEntry(index);
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";
396 case PA_SUBSCRIPTION_EVENT_CLIENT:
397 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
398 m_clients.removeEntry(index);
400 if (!PAOperation(pa_context_get_client_info(context, index, client_cb,
this))) {
401 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_client_info() failed";
407 case PA_SUBSCRIPTION_EVENT_CARD:
408 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
409 m_cards.removeEntry(index);
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";
418 case PA_SUBSCRIPTION_EVENT_MODULE:
419 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
420 m_modules.removeEntry(index);
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";
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";
438 void ContextPrivate::contextStateCallback(pa_context *c)
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";
446 if (m_context == c) {
447 pa_context_set_subscribe_callback(c, subscribe_cb,
this);
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),
456 qCWarning(PULSEAUDIOQT) <<
"pa_context_subscribe() failed";
461 if (!PAOperation(pa_context_get_sink_info_list(c, sink_cb,
this))) {
462 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_sink_info_list() failed";
466 if (!PAOperation(pa_context_get_source_info_list(c, source_cb,
this))) {
467 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_source_info_list() failed";
471 if (!PAOperation(pa_context_get_client_info_list(c, client_cb,
this))) {
472 qCWarning(PULSEAUDIOQT) <<
"pa_context_client_info_list() failed";
476 if (!PAOperation(pa_context_get_card_info_list(c, card_cb,
this))) {
477 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_card_info_list() failed";
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";
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";
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";
496 if (!PAOperation(pa_context_get_server_info(c, server_cb,
this))) {
497 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_server_info() failed";
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));
505 qCWarning(PULSEAUDIOQT) <<
"Failed to initialize stream_restore extension";
507 }
else if (!PA_CONTEXT_IS_GOOD(state)) {
508 qCWarning(PULSEAUDIOQT) <<
"context kaput";
510 pa_context_unref(m_context);
520 void ContextPrivate::sinkCallback(
const pa_sink_info *info)
523 m_sinks.updateEntry(info, q);
526 void ContextPrivate::sinkInputCallback(
const pa_sink_input_info *info)
528 m_sinkInputs.updateEntry(info, q);
531 void ContextPrivate::sourceCallback(
const pa_source_info *info)
533 m_sources.updateEntry(info, q);
536 void ContextPrivate::sourceOutputCallback(
const pa_source_output_info *info)
538 m_sourceOutputs.updateEntry(info, q);
541 void ContextPrivate::clientCallback(
const pa_client_info *info)
543 m_clients.updateEntry(info, q);
546 void ContextPrivate::cardCallback(
const pa_card_info *info)
548 m_cards.updateEntry(info, q);
551 void ContextPrivate::moduleCallback(
const pa_module_info *info)
553 m_modules.updateEntry(info, q);
556 void ContextPrivate::streamRestoreCallback(
const pa_ext_stream_restore_info *info)
558 if (qstrcmp(info->name,
"sink-input-by-media-role:event") != 0) {
562 const int eventRoleIndex = 1;
563 StreamRestore *obj = qobject_cast<StreamRestore *>(m_streamRestores.data().value(eventRoleIndex));
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);
572 obj->d->update(info);
576 void ContextPrivate::serverCallback(
const pa_server_info *info)
578 m_server->d->update(info);
581 void Context::setCardProfile(quint32 index,
const QString &profile)
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";
593 void Context::setDefaultSink(
const QString &name)
599 if (!PAOperation(pa_context_set_default_sink(d->m_context, nameData.
constData(),
nullptr,
nullptr))) {
600 qCWarning(PULSEAUDIOQT) <<
"pa_context_set_default_sink failed";
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";
610 void Context::setDefaultSource(
const QString &name)
616 if (!PAOperation(pa_context_set_default_source(d->m_context, nameData.
constData(),
nullptr,
nullptr))) {
617 qCWarning(PULSEAUDIOQT) <<
"pa_context_set_default_source failed";
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";
627 void ContextPrivate::streamRestoreWrite(
const pa_ext_stream_restore_info *info)
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";
637 void ContextPrivate::connectToDaemon()
645 qCWarning(PULSEAUDIOQT) <<
"Disabling PulseAudio integration for lack of GLib event loop";
649 qCDebug(PULSEAUDIOQT) <<
"Attempting connection to PulseAudio sound daemon";
651 m_mainloop = pa_glib_mainloop_new(
nullptr);
652 Q_ASSERT(m_mainloop);
655 pa_mainloop_api *api = pa_glib_mainloop_get_api(m_mainloop);
658 pa_proplist *proplist = pa_proplist_new();
660 if (!s_applicationId.isEmpty()) {
661 pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, s_applicationId.toUtf8().constData());
666 m_context = pa_context_new_with_proplist(api,
nullptr, proplist);
667 pa_proplist_free(proplist);
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);
674 m_mainloop =
nullptr;
677 pa_context_set_state_callback(m_context, &context_state_callback,
this);
680 void ContextPrivate::reset()
683 m_sinkInputs.reset();
685 m_sourceOutputs.reset();
689 m_streamRestores.reset();
693 bool Context::isValid()
695 return d->m_context && d->m_mainloop;
700 return d->m_sinks.
data();
705 return d->m_sinkInputs.
data();
710 return d->m_sources.
data();
715 return d->m_sourceOutputs.
data();
720 return d->m_clients.
data();
725 return d->m_cards.
data();
730 return d->m_modules.
data();
735 return d->m_streamRestores.
data();
738 Server *Context::server()
const
743 void ContextPrivate::setGenericVolume(
748 const std::function<pa_operation *(pa_context *, uint32_t,
const pa_cvolume *, pa_context_success_cb_t,
void *)> &pa_set_volume)
753 newVolume = qBound<qint64>(0, newVolume, PA_VOLUME_MAX);
754 pa_cvolume newCVolume = cVolume;
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);
761 Q_ASSERT(newCVolume.channels > channel);
762 newCVolume.values[channel] = newVolume;
764 if (!pa_set_volume(m_context, index, &newCVolume,
nullptr,
nullptr)) {
765 qCWarning(PULSEAUDIOQT) <<
"pa_set_volume failed";
770 void ContextPrivate::setGenericMute(quint32 index,
772 const std::function<pa_operation *(pa_context *, uint32_t,
int, pa_context_success_cb_t,
void *)> &pa_set_mute)
777 if (!PAOperation(pa_set_mute(m_context, index, mute,
nullptr,
nullptr))) {
778 qCWarning(PULSEAUDIOQT) <<
"pa_set_mute failed";
783 void ContextPrivate::setGenericPort(quint32 index,
785 const std::function<pa_operation *(pa_context *, uint32_t,
const char *, pa_context_success_cb_t,
void *)> &pa_set_port)
790 if (!PAOperation(pa_set_port(m_context, index, portName.
toUtf8().
constData(),
nullptr,
nullptr))) {
791 qCWarning(PULSEAUDIOQT) <<
"pa_set_port failed";
796 void ContextPrivate::setGenericDeviceForStream(
799 const std::function<pa_operation *(pa_context *, uint32_t, uint32_t, pa_context_success_cb_t,
void *)> &pa_move_stream_to_device)
804 if (!PAOperation(pa_move_stream_to_device(m_context, streamIndex, deviceIndex,
nullptr,
nullptr))) {
805 qCWarning(PULSEAUDIOQT) <<
"pa_move_stream_to_device failed";
810 void ContextPrivate::setGenericVolumes(
814 const std::function<pa_operation *(pa_context *, uint32_t,
const pa_cvolume *, pa_context_success_cb_t,
void *)> &pa_set_volume)
819 Q_ASSERT(channelVolumes.
count() == cVolume.channels);
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);
826 if (!PAOperation(pa_set_volume(m_context, index, &newCVolume,
nullptr,
nullptr))) {
827 qCWarning(PULSEAUDIOQT) <<
"pa_set_volume failed";
832 void Context::setApplicationId(
const QString &applicationId)
834 ContextPrivate::s_applicationId = applicationId;
837 pa_context *Context::context()
const