From c48cebd620d3f5330c104d85ac32f0aaffadaa14 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 18 Aug 2022 20:27:25 +0200 Subject: Replace everything with new "slxmix" (work in progress) --- src/PulseAudioQt/context.cpp | 842 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 842 insertions(+) create mode 100644 src/PulseAudioQt/context.cpp (limited to 'src/PulseAudioQt/context.cpp') 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 + + 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 +#include +#include +#include +#include +#include + +#include + +#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(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(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(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(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(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(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(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(data)->serverCallback(info); +} + +static void context_state_callback(pa_context *context, void *data) +{ + Q_ASSERT(data); + static_cast(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(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(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(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(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(object)); + }); + connect(&d->m_sinks, &MapBaseQObject::removed, this, [this](int, QObject *object) { + Q_EMIT sinkRemoved(static_cast(object)); + }); + + connect(&d->m_sinkInputs, &MapBaseQObject::added, this, [this](int, QObject *object) { + Q_EMIT sinkInputAdded(static_cast(object)); + }); + connect(&d->m_sinkInputs, &MapBaseQObject::removed, this, [this](int, QObject *object) { + Q_EMIT sinkInputRemoved(static_cast(object)); + }); + + connect(&d->m_sources, &MapBaseQObject::added, this, [this](int, QObject *object) { + Q_EMIT sourceAdded(static_cast(object)); + }); + connect(&d->m_sources, &MapBaseQObject::removed, this, [this](int, QObject *object) { + Q_EMIT sourceRemoved(static_cast(object)); + }); + + connect(&d->m_sourceOutputs, &MapBaseQObject::added, this, [this](int, QObject *object) { + Q_EMIT sourceOutputAdded(static_cast(object)); + }); + connect(&d->m_sourceOutputs, &MapBaseQObject::removed, this, [this](int, QObject *object) { + Q_EMIT sourceOutputRemoved(static_cast(object)); + }); + + connect(&d->m_clients, &MapBaseQObject::added, this, [this](int, QObject *object) { + Q_EMIT clientAdded(static_cast(object)); + }); + connect(&d->m_clients, &MapBaseQObject::removed, this, [this](int, QObject *object) { + Q_EMIT clientRemoved(static_cast(object)); + }); + + connect(&d->m_cards, &MapBaseQObject::added, this, [this](int, QObject *object) { + Q_EMIT cardAdded(static_cast(object)); + }); + connect(&d->m_cards, &MapBaseQObject::removed, this, [this](int, QObject *object) { + Q_EMIT cardRemoved(static_cast(object)); + }); + + connect(&d->m_modules, &MapBaseQObject::added, this, [this](int, QObject *object) { + Q_EMIT moduleAdded(static_cast(object)); + }); + connect(&d->m_modules, &MapBaseQObject::removed, this, [this](int, QObject *object) { + Q_EMIT moduleRemoved(static_cast(object)); + }); + + connect(&d->m_streamRestores, &MapBaseQObject::added, this, [this](int, QObject *object) { + Q_EMIT streamRestoreAdded(static_cast(object)); + }); + connect(&d->m_streamRestores, &MapBaseQObject::removed, this, [this](int, QObject *object) { + Q_EMIT streamRestoreRemoved(static_cast(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(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(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 Context::sinks() const +{ + return d->m_sinks.data(); +} + +QVector Context::sinkInputs() const +{ + return d->m_sinkInputs.data(); +} + +QVector Context::sources() const +{ + return d->m_sources.data(); +} + +QVector Context::sourceOutputs() const +{ + return d->m_sourceOutputs.data(); +} + +QVector Context::clients() const +{ + return d->m_clients.data(); +} + +QVector Context::cards() const +{ + return d->m_cards.data(); +} + +QVector Context::modules() const +{ + return d->m_modules.data(); +} + +QVector 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_set_volume) +{ + if (!m_context) { + return; + } + newVolume = qBound(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(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_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_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_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 channelVolumes, + pa_cvolume cVolume, + const std::function &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(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 -- cgit v1.2.3-55-g7522