summaryrefslogtreecommitdiffstats
path: root/src/PulseAudioQt/context.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/PulseAudioQt/context.cpp')
-rw-r--r--src/PulseAudioQt/context.cpp842
1 files changed, 842 insertions, 0 deletions
diff --git a/src/PulseAudioQt/context.cpp b/src/PulseAudioQt/context.cpp
new file mode 100644
index 0000000..9967cea
--- /dev/null
+++ b/src/PulseAudioQt/context.cpp
@@ -0,0 +1,842 @@
+/*
+ SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "context.h"
+#include "server.h"
+
+#include "debug.h"
+#include <QAbstractEventDispatcher>
+#include <QDBusConnection>
+#include <QDBusServiceWatcher>
+#include <QGuiApplication>
+#include <QIcon>
+#include <QTimer>
+
+#include <memory>
+
+#include "card.h"
+#include "client.h"
+#include "module.h"
+#include "sink.h"
+#include "sinkinput.h"
+#include "source.h"
+#include "sourceoutput.h"
+#include "streamrestore.h"
+
+#include "context_p.h"
+#include "server_p.h"
+#include "streamrestore_p.h"
+
+namespace PulseAudioQt
+{
+qint64 normalVolume()
+{
+ return PA_VOLUME_NORM;
+}
+
+qint64 minimumVolume()
+{
+ return PA_VOLUME_MUTED;
+}
+
+qint64 maximumVolume()
+{
+ return PA_VOLUME_MAX;
+}
+
+qint64 maximumUIVolume()
+{
+ return PA_VOLUME_UI_MAX;
+}
+
+QString ContextPrivate::s_applicationId;
+
+#ifndef K_DOXYGEN
+
+static bool isGoodState(int eol)
+{
+ if (eol < 0) {
+ // Error
+ return false;
+ }
+
+ if (eol > 0) {
+ // End of callback chain
+ return false;
+ }
+
+ return true;
+}
+
+// --------------------------
+
+static void sink_cb(pa_context *context, const pa_sink_info *info, int eol, void *data)
+{
+ if (!isGoodState(eol))
+ return;
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->sinkCallback(info);
+}
+
+static void sink_input_callback(pa_context *context, const pa_sink_input_info *info, int eol, void *data)
+{
+ if (!isGoodState(eol))
+ return;
+ // pulsesink probe is used by gst-pulse only to query sink formats (not for playback)
+ if (qstrcmp(info->name, "pulsesink probe") == 0) {
+ return;
+ }
+ if (const char *id = pa_proplist_gets(info->proplist, "module-stream-restore.id")) {
+ if (qstrcmp(id, "sink-input-by-media-role:event") == 0) {
+ qDebug() << "Ignoring event role sink input.";
+ return;
+ }
+ }
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->sinkInputCallback(info);
+}
+
+static void source_cb(pa_context *context, const pa_source_info *info, int eol, void *data)
+{
+ if (!isGoodState(eol))
+ return;
+ // FIXME: This forces excluding monitors
+ if (info->monitor_of_sink != PA_INVALID_INDEX)
+ return;
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->sourceCallback(info);
+}
+
+static void source_output_cb(pa_context *context, const pa_source_output_info *info, int eol, void *data)
+{
+ if (!isGoodState(eol))
+ return;
+ // FIXME: This forces excluding these apps
+ if (const char *app = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_ID)) {
+ if (strcmp(app, "org.PulseAudio.pavucontrol") == 0 //
+ || strcmp(app, "org.gnome.VolumeControl") == 0 //
+ || strcmp(app, "org.kde.kmixd") == 0 //
+ || strcmp(app, "org.kde.plasma-pa") == 0) //
+ return;
+ }
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->sourceOutputCallback(info);
+}
+
+static void client_cb(pa_context *context, const pa_client_info *info, int eol, void *data)
+{
+ if (!isGoodState(eol))
+ return;
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->clientCallback(info);
+}
+
+static void card_cb(pa_context *context, const pa_card_info *info, int eol, void *data)
+{
+ if (!isGoodState(eol))
+ return;
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->cardCallback(info);
+}
+
+static void module_info_list_cb(pa_context *context, const pa_module_info *info, int eol, void *data)
+{
+ if (!isGoodState(eol))
+ return;
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->moduleCallback(info);
+}
+
+static void server_cb(pa_context *context, const pa_server_info *info, void *data)
+{
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->serverCallback(info);
+}
+
+static void context_state_callback(pa_context *context, void *data)
+{
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->contextStateCallback(context);
+}
+
+static void subscribe_cb(pa_context *context, pa_subscription_event_type_t type, uint32_t index, void *data)
+{
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->subscribeCallback(context, type, index);
+}
+
+static void ext_stream_restore_read_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
+{
+ if (!isGoodState(eol)) {
+ return;
+ }
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ static_cast<ContextPrivate *>(data)->streamRestoreCallback(info);
+}
+
+static void ext_stream_restore_subscribe_cb(pa_context *context, void *data)
+{
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ if (!PAOperation(pa_ext_stream_restore_read(context, ext_stream_restore_read_cb, data))) {
+ qWarning() << "pa_ext_stream_restore_read() failed";
+ }
+}
+
+static void ext_stream_restore_change_sink_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
+{
+ if (!isGoodState(eol)) {
+ return;
+ }
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ if (qstrncmp(info->name, "sink-input-by", 13) == 0) {
+ ContextPrivate *contextp = static_cast<ContextPrivate *>(data);
+ const QByteArray deviceData = contextp->m_newDefaultSink.toUtf8();
+ pa_ext_stream_restore_info newinfo;
+ newinfo.name = info->name;
+ newinfo.channel_map = info->channel_map;
+ newinfo.volume = info->volume;
+ newinfo.mute = info->mute;
+ newinfo.device = deviceData.constData();
+ contextp->streamRestoreWrite(&newinfo);
+ }
+}
+
+static void ext_stream_restore_change_source_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
+{
+ if (!isGoodState(eol)) {
+ return;
+ }
+ Q_ASSERT(context);
+ Q_ASSERT(data);
+ if (qstrncmp(info->name, "source-output-by", 16) == 0) {
+ ContextPrivate *contextp = static_cast<ContextPrivate *>(data);
+ const QByteArray deviceData = contextp->m_newDefaultSource.toUtf8();
+ pa_ext_stream_restore_info newinfo;
+ newinfo.name = info->name;
+ newinfo.channel_map = info->channel_map;
+ newinfo.volume = info->volume;
+ newinfo.mute = info->mute;
+ newinfo.device = deviceData.constData();
+ contextp->streamRestoreWrite(&newinfo);
+ }
+}
+
+#endif
+
+// --------------------------
+
+Context::Context(QObject *parent)
+ : QObject(parent)
+ , d(new ContextPrivate(this))
+{
+ d->m_server = new Server(this);
+ d->m_context = nullptr;
+ d->m_mainloop = nullptr;
+ d->m_references = 0;
+
+ d->connectToDaemon();
+
+ QDBusServiceWatcher *watcher =
+ new QDBusServiceWatcher(QStringLiteral("org.pulseaudio.Server"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this);
+ connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, [this] {
+ d->connectToDaemon();
+ });
+
+ connect(&d->m_sinks, &MapBaseQObject::added, this, [this](int, QObject *object) {
+ Q_EMIT sinkAdded(static_cast<Sink *>(object));
+ });
+ connect(&d->m_sinks, &MapBaseQObject::removed, this, [this](int, QObject *object) {
+ Q_EMIT sinkRemoved(static_cast<Sink *>(object));
+ });
+
+ connect(&d->m_sinkInputs, &MapBaseQObject::added, this, [this](int, QObject *object) {
+ Q_EMIT sinkInputAdded(static_cast<SinkInput *>(object));
+ });
+ connect(&d->m_sinkInputs, &MapBaseQObject::removed, this, [this](int, QObject *object) {
+ Q_EMIT sinkInputRemoved(static_cast<SinkInput *>(object));
+ });
+
+ connect(&d->m_sources, &MapBaseQObject::added, this, [this](int, QObject *object) {
+ Q_EMIT sourceAdded(static_cast<Source *>(object));
+ });
+ connect(&d->m_sources, &MapBaseQObject::removed, this, [this](int, QObject *object) {
+ Q_EMIT sourceRemoved(static_cast<Source *>(object));
+ });
+
+ connect(&d->m_sourceOutputs, &MapBaseQObject::added, this, [this](int, QObject *object) {
+ Q_EMIT sourceOutputAdded(static_cast<SourceOutput *>(object));
+ });
+ connect(&d->m_sourceOutputs, &MapBaseQObject::removed, this, [this](int, QObject *object) {
+ Q_EMIT sourceOutputRemoved(static_cast<SourceOutput *>(object));
+ });
+
+ connect(&d->m_clients, &MapBaseQObject::added, this, [this](int, QObject *object) {
+ Q_EMIT clientAdded(static_cast<Client *>(object));
+ });
+ connect(&d->m_clients, &MapBaseQObject::removed, this, [this](int, QObject *object) {
+ Q_EMIT clientRemoved(static_cast<Client *>(object));
+ });
+
+ connect(&d->m_cards, &MapBaseQObject::added, this, [this](int, QObject *object) {
+ Q_EMIT cardAdded(static_cast<Card *>(object));
+ });
+ connect(&d->m_cards, &MapBaseQObject::removed, this, [this](int, QObject *object) {
+ Q_EMIT cardRemoved(static_cast<Card *>(object));
+ });
+
+ connect(&d->m_modules, &MapBaseQObject::added, this, [this](int, QObject *object) {
+ Q_EMIT moduleAdded(static_cast<Module *>(object));
+ });
+ connect(&d->m_modules, &MapBaseQObject::removed, this, [this](int, QObject *object) {
+ Q_EMIT moduleRemoved(static_cast<Module *>(object));
+ });
+
+ connect(&d->m_streamRestores, &MapBaseQObject::added, this, [this](int, QObject *object) {
+ Q_EMIT streamRestoreAdded(static_cast<StreamRestore *>(object));
+ });
+ connect(&d->m_streamRestores, &MapBaseQObject::removed, this, [this](int, QObject *object) {
+ Q_EMIT streamRestoreRemoved(static_cast<StreamRestore *>(object));
+ });
+}
+
+ContextPrivate::ContextPrivate(Context *q)
+ : q(q)
+{
+}
+
+Context::~Context()
+{
+ delete d;
+}
+
+ContextPrivate::~ContextPrivate()
+{
+ if (m_context) {
+ pa_context_unref(m_context);
+ m_context = nullptr;
+ }
+
+ if (m_mainloop) {
+ pa_glib_mainloop_free(m_mainloop);
+ m_mainloop = nullptr;
+ }
+
+ reset();
+}
+
+Context *Context::instance()
+{
+ static std::unique_ptr<Context> context(new Context);
+ return context.get();
+}
+
+void ContextPrivate::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index)
+{
+ Q_ASSERT(context == m_context);
+
+ switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+ case PA_SUBSCRIPTION_EVENT_SINK:
+ if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ m_sinks.removeEntry(index);
+ } else {
+ if (!PAOperation(pa_context_get_sink_info_by_index(context, index, sink_cb, this))) {
+ qWarning() << "pa_context_get_sink_info_by_index() failed";
+ return;
+ }
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SOURCE:
+ if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ m_sources.removeEntry(index);
+ } else {
+ if (!PAOperation(pa_context_get_source_info_by_index(context, index, source_cb, this))) {
+ qWarning() << "pa_context_get_source_info_by_index() failed";
+ return;
+ }
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+ if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ m_sinkInputs.removeEntry(index);
+ } else {
+ if (!PAOperation(pa_context_get_sink_input_info(context, index, sink_input_callback, this))) {
+ qWarning() << "pa_context_get_sink_input_info() failed";
+ return;
+ }
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
+ if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ m_sourceOutputs.removeEntry(index);
+ } else {
+ if (!PAOperation(pa_context_get_source_output_info(context, index, source_output_cb, this))) {
+ qWarning() << "pa_context_get_sink_input_info() failed";
+ return;
+ }
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_CLIENT:
+ if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ m_clients.removeEntry(index);
+ } else {
+ if (!PAOperation(pa_context_get_client_info(context, index, client_cb, this))) {
+ qWarning() << "pa_context_get_client_info() failed";
+ return;
+ }
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_CARD:
+ if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ m_cards.removeEntry(index);
+ } else {
+ if (!PAOperation(pa_context_get_card_info_by_index(context, index, card_cb, this))) {
+ qWarning() << "pa_context_get_card_info_by_index() failed";
+ return;
+ }
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_MODULE:
+ if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ m_modules.removeEntry(index);
+ } else {
+ if (!PAOperation(pa_context_get_module_info_list(context, module_info_list_cb, this))) {
+ qWarning() << "pa_context_get_module_info_list() failed";
+ return;
+ }
+ }
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_SERVER:
+ if (!PAOperation(pa_context_get_server_info(context, server_cb, this))) {
+ qWarning() << "pa_context_get_server_info() failed";
+ return;
+ }
+ break;
+ }
+}
+
+void ContextPrivate::contextStateCallback(pa_context *c)
+{
+ qDebug() << "state callback";
+ pa_context_state_t state = pa_context_get_state(c);
+ if (state == PA_CONTEXT_READY) {
+ qDebug() << "ready";
+
+ // 1. Register for the stream changes (except during probe)
+ if (m_context == c) {
+ pa_context_set_subscribe_callback(c, subscribe_cb, this);
+
+ if (!PAOperation(
+ pa_context_subscribe(c,
+ (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_CLIENT
+ | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT
+ | PA_SUBSCRIPTION_MASK_CARD | PA_SUBSCRIPTION_MASK_MODULE | PA_SUBSCRIPTION_MASK_SERVER),
+ nullptr,
+ nullptr))) {
+ qWarning() << "pa_context_subscribe() failed";
+ return;
+ }
+ }
+
+ if (!PAOperation(pa_context_get_sink_info_list(c, sink_cb, this))) {
+ qWarning() << "pa_context_get_sink_info_list() failed";
+ return;
+ }
+
+ if (!PAOperation(pa_context_get_source_info_list(c, source_cb, this))) {
+ qWarning() << "pa_context_get_source_info_list() failed";
+ return;
+ }
+
+ if (!PAOperation(pa_context_get_client_info_list(c, client_cb, this))) {
+ qWarning() << "pa_context_client_info_list() failed";
+ return;
+ }
+
+ if (!PAOperation(pa_context_get_card_info_list(c, card_cb, this))) {
+ qWarning() << "pa_context_get_card_info_list() failed";
+ return;
+ }
+
+ if (!PAOperation(pa_context_get_sink_input_info_list(c, sink_input_callback, this))) {
+ qWarning() << "pa_context_get_sink_input_info_list() failed";
+ return;
+ }
+
+ if (!PAOperation(pa_context_get_source_output_info_list(c, source_output_cb, this))) {
+ qWarning() << "pa_context_get_source_output_info_list() failed";
+ return;
+ }
+
+ if (!PAOperation(pa_context_get_module_info_list(c, module_info_list_cb, this))) {
+ qWarning() << "pa_context_get_module_info_list() failed";
+ return;
+ }
+
+ if (!PAOperation(pa_context_get_server_info(c, server_cb, this))) {
+ qWarning() << "pa_context_get_server_info() failed";
+ return;
+ }
+
+ if (PAOperation(pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, this))) {
+ pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, this);
+ PAOperation(pa_ext_stream_restore_subscribe(c, 1, nullptr, this));
+ } else {
+ qWarning() << "Failed to initialize stream_restore extension";
+ }
+ } else if (!PA_CONTEXT_IS_GOOD(state)) {
+ qWarning() << "context kaput";
+ if (m_context) {
+ pa_context_unref(m_context);
+ m_context = nullptr;
+ }
+ reset();
+ QTimer::singleShot(1000, q, [this] {
+ connectToDaemon();
+ });
+ }
+}
+
+void ContextPrivate::sinkCallback(const pa_sink_info *info)
+{
+ // This parenting here is a bit weird
+ m_sinks.updateEntry(info, q);
+}
+
+void ContextPrivate::sinkInputCallback(const pa_sink_input_info *info)
+{
+ m_sinkInputs.updateEntry(info, q);
+}
+
+void ContextPrivate::sourceCallback(const pa_source_info *info)
+{
+ m_sources.updateEntry(info, q);
+}
+
+void ContextPrivate::sourceOutputCallback(const pa_source_output_info *info)
+{
+ m_sourceOutputs.updateEntry(info, q);
+}
+
+void ContextPrivate::clientCallback(const pa_client_info *info)
+{
+ m_clients.updateEntry(info, q);
+}
+
+void ContextPrivate::cardCallback(const pa_card_info *info)
+{
+ m_cards.updateEntry(info, q);
+}
+
+void ContextPrivate::moduleCallback(const pa_module_info *info)
+{
+ m_modules.updateEntry(info, q);
+}
+
+void ContextPrivate::streamRestoreCallback(const pa_ext_stream_restore_info *info)
+{
+ if (qstrcmp(info->name, "sink-input-by-media-role:event") != 0) {
+ return;
+ }
+
+ const int eventRoleIndex = 1;
+ StreamRestore *obj = qobject_cast<StreamRestore *>(m_streamRestores.data().value(eventRoleIndex));
+
+ if (!obj) {
+ QVariantMap props;
+ props.insert(QStringLiteral("application.icon_name"), QStringLiteral("preferences-desktop-notification"));
+ obj = new StreamRestore(eventRoleIndex, props, q);
+ obj->d->update(info);
+ m_streamRestores.insert(obj);
+ } else {
+ obj->d->update(info);
+ }
+}
+
+void ContextPrivate::serverCallback(const pa_server_info *info)
+{
+ m_server->d->update(info);
+}
+
+void Context::setCardProfile(quint32 index, const QString &profile)
+{
+ if (!d->m_context) {
+ return;
+ }
+ qDebug() << index << profile;
+ if (!PAOperation(pa_context_set_card_profile_by_index(d->m_context, index, profile.toUtf8().constData(), nullptr, nullptr))) {
+ qWarning() << "pa_context_set_card_profile_by_index failed";
+ return;
+ }
+}
+
+void Context::setDefaultSink(const QString &name)
+{
+ if (!d->m_context) {
+ return;
+ }
+ const QByteArray nameData = name.toUtf8();
+ if (!PAOperation(pa_context_set_default_sink(d->m_context, nameData.constData(), nullptr, nullptr))) {
+ qWarning() << "pa_context_set_default_sink failed";
+ }
+
+ // Change device for all entries in stream-restore database
+ d->m_newDefaultSink = name;
+ if (!PAOperation(pa_ext_stream_restore_read(d->m_context, ext_stream_restore_change_sink_cb, d))) {
+ qWarning() << "pa_ext_stream_restore_read failed";
+ }
+}
+
+void Context::setDefaultSource(const QString &name)
+{
+ if (!d->m_context) {
+ return;
+ }
+ const QByteArray nameData = name.toUtf8();
+ if (!PAOperation(pa_context_set_default_source(d->m_context, nameData.constData(), nullptr, nullptr))) {
+ qWarning() << "pa_context_set_default_source failed";
+ }
+
+ // Change device for all entries in stream-restore database
+ d->m_newDefaultSource = name;
+ if (!PAOperation(pa_ext_stream_restore_read(d->m_context, ext_stream_restore_change_source_cb, d))) {
+ qWarning() << "pa_ext_stream_restore_read failed";
+ }
+}
+
+void ContextPrivate::streamRestoreWrite(const pa_ext_stream_restore_info *info)
+{
+ if (!m_context) {
+ return;
+ }
+ if (!PAOperation(pa_ext_stream_restore_write(m_context, PA_UPDATE_REPLACE, info, 1, true, nullptr, nullptr))) {
+ qWarning() << "pa_ext_stream_restore_write failed";
+ }
+}
+
+void ContextPrivate::connectToDaemon()
+{
+ if (m_context) {
+ return;
+ }
+
+ // We require a glib event loop
+ if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("Glib")) {
+ qWarning() << "Disabling PulseAudio integration for lack of GLib event loop";
+ return;
+ }
+
+ qDebug() << "Attempting connection to PulseAudio sound daemon";
+ if (!m_mainloop) {
+ m_mainloop = pa_glib_mainloop_new(nullptr);
+ Q_ASSERT(m_mainloop);
+ }
+
+ pa_mainloop_api *api = pa_glib_mainloop_get_api(m_mainloop);
+ Q_ASSERT(api);
+
+ pa_proplist *proplist = pa_proplist_new();
+ pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, QGuiApplication::applicationDisplayName().toUtf8().constData());
+ if (!s_applicationId.isEmpty()) {
+ pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, s_applicationId.toUtf8().constData());
+ } else {
+ pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, QGuiApplication::desktopFileName().toUtf8().constData());
+ }
+ pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, QGuiApplication::windowIcon().name().toUtf8().constData());
+ m_context = pa_context_new_with_proplist(api, nullptr, proplist);
+ pa_proplist_free(proplist);
+ Q_ASSERT(m_context);
+
+ if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFAIL, nullptr) < 0) {
+ pa_context_unref(m_context);
+ pa_glib_mainloop_free(m_mainloop);
+ m_context = nullptr;
+ m_mainloop = nullptr;
+ return;
+ }
+ pa_context_set_state_callback(m_context, &context_state_callback, this);
+}
+
+void ContextPrivate::reset()
+{
+ m_sinks.reset();
+ m_sinkInputs.reset();
+ m_sources.reset();
+ m_sourceOutputs.reset();
+ m_clients.reset();
+ m_cards.reset();
+ m_modules.reset();
+ m_streamRestores.reset();
+ m_server->reset();
+}
+
+bool Context::isValid()
+{
+ return d->m_context && d->m_mainloop;
+}
+
+QVector<Sink *> Context::sinks() const
+{
+ return d->m_sinks.data();
+}
+
+QVector<SinkInput *> Context::sinkInputs() const
+{
+ return d->m_sinkInputs.data();
+}
+
+QVector<Source *> Context::sources() const
+{
+ return d->m_sources.data();
+}
+
+QVector<SourceOutput *> Context::sourceOutputs() const
+{
+ return d->m_sourceOutputs.data();
+}
+
+QVector<Client *> Context::clients() const
+{
+ return d->m_clients.data();
+}
+
+QVector<Card *> Context::cards() const
+{
+ return d->m_cards.data();
+}
+
+QVector<Module *> Context::modules() const
+{
+ return d->m_modules.data();
+}
+
+QVector<StreamRestore *> Context::streamRestores() const
+{
+ return d->m_streamRestores.data();
+}
+
+Server *Context::server() const
+{
+ return d->m_server;
+}
+
+void ContextPrivate::setGenericVolume(
+ quint32 index,
+ int channel,
+ qint64 newVolume,
+ pa_cvolume cVolume,
+ const std::function<pa_operation *(pa_context *, uint32_t, const pa_cvolume *, pa_context_success_cb_t, void *)> &pa_set_volume)
+{
+ if (!m_context) {
+ return;
+ }
+ newVolume = qBound<qint64>(0, newVolume, PA_VOLUME_MAX);
+ pa_cvolume newCVolume = cVolume;
+ if (channel == -1) { // -1 all channels
+ const qint64 diff = newVolume - pa_cvolume_max(&cVolume);
+ for (int i = 0; i < newCVolume.channels; ++i) {
+ newCVolume.values[i] = qBound<qint64>(0, newCVolume.values[i] + diff, PA_VOLUME_MAX);
+ }
+ } else {
+ Q_ASSERT(newCVolume.channels > channel);
+ newCVolume.values[channel] = newVolume;
+ }
+ if (!pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr)) {
+ qWarning() << "pa_set_volume failed";
+ return;
+ }
+}
+
+void ContextPrivate::setGenericMute(quint32 index,
+ bool mute,
+ const std::function<pa_operation *(pa_context *, uint32_t, int, pa_context_success_cb_t, void *)> &pa_set_mute)
+{
+ if (!m_context) {
+ return;
+ }
+ if (!PAOperation(pa_set_mute(m_context, index, mute, nullptr, nullptr))) {
+ qWarning() << "pa_set_mute failed";
+ return;
+ }
+}
+
+void ContextPrivate::setGenericPort(quint32 index,
+ const QString &portName,
+ const std::function<pa_operation *(pa_context *, uint32_t, const char *, pa_context_success_cb_t, void *)> &pa_set_port)
+{
+ if (!m_context) {
+ return;
+ }
+ if (!PAOperation(pa_set_port(m_context, index, portName.toUtf8().constData(), nullptr, nullptr))) {
+ qWarning() << "pa_set_port failed";
+ return;
+ }
+}
+
+void ContextPrivate::setGenericDeviceForStream(
+ quint32 streamIndex,
+ quint32 deviceIndex,
+ const std::function<pa_operation *(pa_context *, uint32_t, uint32_t, pa_context_success_cb_t, void *)> &pa_move_stream_to_device)
+{
+ if (!m_context) {
+ return;
+ }
+ if (!PAOperation(pa_move_stream_to_device(m_context, streamIndex, deviceIndex, nullptr, nullptr))) {
+ qWarning() << "pa_move_stream_to_device failed";
+ return;
+ }
+}
+
+void ContextPrivate::setGenericVolumes(
+ quint32 index,
+ QVector<qint64> channelVolumes,
+ pa_cvolume cVolume,
+ const std::function<pa_operation *(pa_context *, uint32_t, const pa_cvolume *, pa_context_success_cb_t, void *)> &pa_set_volume)
+{
+ if (!m_context) {
+ return;
+ }
+ Q_ASSERT(channelVolumes.count() == cVolume.channels);
+
+ pa_cvolume newCVolume = cVolume;
+ for (int i = 0; i < channelVolumes.count(); ++i) {
+ newCVolume.values[i] = qBound<qint64>(0, channelVolumes.at(i), PA_VOLUME_MAX);
+ }
+
+ if (!PAOperation(pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr))) {
+ qWarning() << "pa_set_volume failed";
+ return;
+ }
+}
+
+void Context::setApplicationId(const QString &applicationId)
+{
+ ContextPrivate::s_applicationId = applicationId;
+}
+
+pa_context *Context::context() const
+{
+ return d->m_context;
+}
+
+} // PulseAudioQt