11#include <QAbstractEventDispatcher>
12#include <QDBusConnection>
13#include <QDBusServiceWatcher>
14#include <QGuiApplication>
26#include "sourceoutput.h"
27#include "streamrestore.h"
31#include "streamrestore_p.h"
37 return PA_VOLUME_NORM;
42 return PA_VOLUME_MUTED;
52 return PA_VOLUME_UI_MAX;
55QString ContextPrivate::s_applicationId;
61constexpr auto EVENT_ROLE =
"sink-input-by-media-role:event";
64static bool isGoodState(
int eol)
81static void sink_cb(pa_context *context,
const pa_sink_info *info,
int eol,
void *data)
83 if (!isGoodState(eol))
87 static_cast<ContextPrivate *
>(data)->sinkCallback(info);
90static void sink_input_callback(pa_context *context,
const pa_sink_input_info *info,
int eol,
void *data)
92 if (!isGoodState(eol))
95 if (qstrcmp(info->name,
"pulsesink probe") == 0) {
98 if (
const char *
id = pa_proplist_gets(info->proplist,
"module-stream-restore.id")) {
99 if (qstrcmp(
id, EVENT_ROLE) == 0) {
100 qCDebug(PULSEAUDIOQT) <<
"Ignoring event role sink input.";
106 static_cast<ContextPrivate *
>(data)->sinkInputCallback(info);
109static void source_cb(pa_context *context,
const pa_source_info *info,
int eol,
void *data)
111 if (!isGoodState(eol))
114 if (info->monitor_of_sink != PA_INVALID_INDEX)
118 static_cast<ContextPrivate *
>(data)->sourceCallback(info);
121static void source_output_cb(pa_context *context,
const pa_source_output_info *info,
int eol,
void *data)
123 if (!isGoodState(eol))
126 if (
const char *app = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_ID)) {
127 if (strcmp(app,
"org.PulseAudio.pavucontrol") == 0
128 || strcmp(app,
"org.gnome.VolumeControl") == 0
129 || strcmp(app,
"org.kde.kmixd") == 0
130 || strcmp(app,
"org.kde.plasma-pa") == 0)
135 static_cast<ContextPrivate *
>(data)->sourceOutputCallback(info);
138static void client_cb(pa_context *context,
const pa_client_info *info,
int eol,
void *data)
140 if (!isGoodState(eol))
144 static_cast<ContextPrivate *
>(data)->clientCallback(info);
147static void card_cb(pa_context *context,
const pa_card_info *info,
int eol,
void *data)
149 if (!isGoodState(eol))
153 static_cast<ContextPrivate *
>(data)->cardCallback(info);
156static void module_info_list_cb(pa_context *context,
const pa_module_info *info,
int eol,
void *data)
158 if (!isGoodState(eol))
162 static_cast<ContextPrivate *
>(data)->moduleCallback(info);
165static void server_cb(pa_context *context,
const pa_server_info *info,
void *data)
172 qCWarning(PULSEAUDIOQT) <<
"server_cb() called without info!";
175 static_cast<ContextPrivate *
>(data)->serverCallback(info);
178static void context_state_callback(pa_context *context,
void *data)
181 static_cast<ContextPrivate *
>(data)->contextStateCallback(context);
184static void subscribe_cb(pa_context *context, pa_subscription_event_type_t type, uint32_t index,
void *data)
187 static_cast<ContextPrivate *
>(data)->subscribeCallback(context, type, index);
190static void ext_stream_restore_read_cb(pa_context *context,
const pa_ext_stream_restore_info *info,
int eol,
void *data)
192 if (!isGoodState(eol)) {
197 static_cast<ContextPrivate *
>(data)->streamRestoreCallback(info);
200static void ext_stream_restore_subscribe_cb(pa_context *context,
void *data)
204 if (!PAOperation(pa_ext_stream_restore_read(context, ext_stream_restore_read_cb, data))) {
205 qCWarning(PULSEAUDIOQT) <<
"pa_ext_stream_restore_read() failed";
209static void ext_stream_restore_change_sink_cb(pa_context *context,
const pa_ext_stream_restore_info *info,
int eol,
void *data)
211 if (!isGoodState(eol)) {
216 if (qstrncmp(info->name,
"sink-input-by", 13) == 0) {
217 ContextPrivate *contextp =
static_cast<ContextPrivate *
>(data);
218 const QByteArray deviceData = contextp->m_newDefaultSink.toUtf8();
219 pa_ext_stream_restore_info newinfo;
220 newinfo.name = info->name;
221 newinfo.channel_map = info->channel_map;
222 newinfo.volume = info->volume;
223 newinfo.mute = info->mute;
225 contextp->streamRestoreWrite(&newinfo);
229static void ext_stream_restore_change_source_cb(pa_context *context,
const pa_ext_stream_restore_info *info,
int eol,
void *data)
231 if (!isGoodState(eol)) {
236 if (qstrncmp(info->name,
"source-output-by", 16) == 0) {
237 ContextPrivate *contextp =
static_cast<ContextPrivate *
>(data);
238 const QByteArray deviceData = contextp->m_newDefaultSource.toUtf8();
239 pa_ext_stream_restore_info newinfo;
240 newinfo.name = info->name;
241 newinfo.channel_map = info->channel_map;
242 newinfo.volume = info->volume;
243 newinfo.mute = info->mute;
245 contextp->streamRestoreWrite(&newinfo);
253Context::Context(
QObject *parent)
255 , d(new ContextPrivate(this))
257 connect(
this, &Context::stateChanged,
this, [
this] {
258 qCDebug(PULSEAUDIOQT) <<
"context state changed:" << d->m_state;
261 d->m_server =
new Server(
this);
262 d->m_context =
nullptr;
263 d->m_mainloop =
nullptr;
265 d->connectToDaemon();
270 d->connectToDaemon();
274 d->forceDisconnect();
275 d->connectToDaemon();
276 d->checkConnectTries();
279 connect(&d->m_sinks, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
280 Q_EMIT sinkAdded(static_cast<Sink *>(object));
282 connect(&d->m_sinks, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
283 Q_EMIT sinkRemoved(static_cast<Sink *>(object));
286 connect(&d->m_sinkInputs, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
287 Q_EMIT sinkInputAdded(static_cast<SinkInput *>(object));
289 connect(&d->m_sinkInputs, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
290 Q_EMIT sinkInputRemoved(static_cast<SinkInput *>(object));
293 connect(&d->m_sources, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
294 Q_EMIT sourceAdded(static_cast<Source *>(object));
296 connect(&d->m_sources, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
297 Q_EMIT sourceRemoved(static_cast<Source *>(object));
300 connect(&d->m_sourceOutputs, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
301 Q_EMIT sourceOutputAdded(static_cast<SourceOutput *>(object));
303 connect(&d->m_sourceOutputs, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
304 Q_EMIT sourceOutputRemoved(static_cast<SourceOutput *>(object));
307 connect(&d->m_clients, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
308 Q_EMIT clientAdded(static_cast<Client *>(object));
310 connect(&d->m_clients, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
311 Q_EMIT clientRemoved(static_cast<Client *>(object));
314 connect(&d->m_cards, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
315 Q_EMIT cardAdded(static_cast<Card *>(object));
317 connect(&d->m_cards, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
318 Q_EMIT cardRemoved(static_cast<Card *>(object));
321 connect(&d->m_modules, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
322 Q_EMIT moduleAdded(static_cast<Module *>(object));
324 connect(&d->m_modules, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
325 Q_EMIT moduleRemoved(static_cast<Module *>(object));
328 connect(&d->m_streamRestores, &MapBaseQObject::added,
this, [
this](
int,
QObject *
object) {
329 Q_EMIT streamRestoreAdded(static_cast<StreamRestore *>(object));
331 connect(&d->m_streamRestores, &MapBaseQObject::removed,
this, [
this](
int,
QObject *
object) {
332 Q_EMIT streamRestoreRemoved(static_cast<StreamRestore *>(object));
336ContextPrivate::ContextPrivate(Context *q)
345ContextPrivate::~ContextPrivate()
348 pa_context_unref(m_context);
353 pa_glib_mainloop_free(m_mainloop);
354 m_mainloop =
nullptr;
361Context *Context::instance()
363 static std::unique_ptr<Context> context(
new Context);
364 return context.get();
367void ContextPrivate::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index)
369 Q_ASSERT(context == m_context);
371 switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
372 case PA_SUBSCRIPTION_EVENT_SINK:
373 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
374 m_sinks.removeEntry(index);
376 if (!PAOperation(pa_context_get_sink_info_by_index(context, index, sink_cb,
this))) {
377 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_sink_info_by_index() failed";
383 case PA_SUBSCRIPTION_EVENT_SOURCE:
384 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
385 m_sources.removeEntry(index);
387 if (!PAOperation(pa_context_get_source_info_by_index(context, index, source_cb,
this))) {
388 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_source_info_by_index() failed";
394 case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
395 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
396 m_sinkInputs.removeEntry(index);
398 if (!PAOperation(pa_context_get_sink_input_info(context, index, sink_input_callback,
this))) {
399 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_sink_input_info() failed";
405 case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
406 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
407 m_sourceOutputs.removeEntry(index);
409 if (!PAOperation(pa_context_get_source_output_info(context, index, source_output_cb,
this))) {
410 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_sink_input_info() failed";
416 case PA_SUBSCRIPTION_EVENT_CLIENT:
417 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
418 m_clients.removeEntry(index);
420 if (!PAOperation(pa_context_get_client_info(context, index, client_cb,
this))) {
421 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_client_info() failed";
427 case PA_SUBSCRIPTION_EVENT_CARD:
428 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
429 m_cards.removeEntry(index);
431 if (!PAOperation(pa_context_get_card_info_by_index(context, index, card_cb,
this))) {
432 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_card_info_by_index() failed";
438 case PA_SUBSCRIPTION_EVENT_MODULE:
439 if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
440 m_modules.removeEntry(index);
442 if (!PAOperation(pa_context_get_module_info_list(context, module_info_list_cb,
this))) {
443 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_module_info_list() failed";
449 case PA_SUBSCRIPTION_EVENT_SERVER:
450 if (!PAOperation(pa_context_get_server_info(context, server_cb,
this))) {
451 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_server_info() failed";
458void ContextPrivate::contextStateCallback(pa_context *c)
460 pa_context_state_t state = pa_context_get_state(c);
461 qCDebug(PULSEAUDIOQT) <<
"state callback" << state;
463 m_state = [state]() -> Context::State {
465 case PA_CONTEXT_UNCONNECTED:
466 return Context::State::Unconnected;
467 case PA_CONTEXT_CONNECTING:
468 return Context::State::Connecting;
469 case PA_CONTEXT_AUTHORIZING:
470 return Context::State::Authorizing;
471 case PA_CONTEXT_SETTING_NAME:
472 return Context::State::SettingName;
473 case PA_CONTEXT_READY:
474 return Context::State::Ready;
475 case PA_CONTEXT_FAILED:
476 return Context::State::Failed;
477 case PA_CONTEXT_TERMINATED:
478 return Context::State::Terminated;
480 return Context::State::Unconnected;
485 if (state == PA_CONTEXT_READY) {
486 qCDebug(PULSEAUDIOQT) <<
"ready, stopping connect timer";
487 m_connectTimer.stop();
488 Q_EMIT q->autoConnectingChanged();
491 if (m_context == c) {
492 pa_context_set_subscribe_callback(c, subscribe_cb,
this);
495 pa_context_subscribe(c,
496 (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_CLIENT
497 | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT
498 | PA_SUBSCRIPTION_MASK_CARD | PA_SUBSCRIPTION_MASK_MODULE | PA_SUBSCRIPTION_MASK_SERVER),
501 qCWarning(PULSEAUDIOQT) <<
"pa_context_subscribe() failed";
506 if (!PAOperation(pa_context_get_sink_info_list(c, sink_cb,
this))) {
507 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_sink_info_list() failed";
511 if (!PAOperation(pa_context_get_source_info_list(c, source_cb,
this))) {
512 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_source_info_list() failed";
516 if (!PAOperation(pa_context_get_client_info_list(c, client_cb,
this))) {
517 qCWarning(PULSEAUDIOQT) <<
"pa_context_client_info_list() failed";
521 if (!PAOperation(pa_context_get_card_info_list(c, card_cb,
this))) {
522 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_card_info_list() failed";
526 if (!PAOperation(pa_context_get_sink_input_info_list(c, sink_input_callback,
this))) {
527 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_sink_input_info_list() failed";
531 if (!PAOperation(pa_context_get_source_output_info_list(c, source_output_cb,
this))) {
532 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_source_output_info_list() failed";
536 if (!PAOperation(pa_context_get_module_info_list(c, module_info_list_cb,
this))) {
537 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_module_info_list() failed";
541 if (!PAOperation(pa_context_get_server_info(c, server_cb,
this))) {
542 qCWarning(PULSEAUDIOQT) <<
"pa_context_get_server_info() failed";
549 synthesizeEventStream();
550 if (PAOperation(pa_ext_stream_restore_read(c, ext_stream_restore_read_cb,
this))) {
551 pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb,
this);
552 PAOperation(pa_ext_stream_restore_subscribe(c, 1,
nullptr,
this));
554 m_streamRestores.reset();
555 qCWarning(PULSEAUDIOQT) <<
"Failed to initialize stream_restore extension";
557 }
else if (!PA_CONTEXT_IS_GOOD(state)) {
558 qCWarning(PULSEAUDIOQT) <<
"context kaput";
560 pa_context_unref(m_context);
563 if (!m_connectTimer.isActive() && hasConnectionTriesLeft()) {
565 qCDebug(PULSEAUDIOQT) <<
"Starting connect timer";
566 m_connectTimer.start(std::chrono::seconds(5));
567 Q_EMIT q->autoConnectingChanged();
572void ContextPrivate::sinkCallback(
const pa_sink_info *info)
575 m_sinks.updateEntry(info, q);
578void ContextPrivate::sinkInputCallback(
const pa_sink_input_info *info)
580 m_sinkInputs.updateEntry(info, q);
583void ContextPrivate::sourceCallback(
const pa_source_info *info)
585 m_sources.updateEntry(info, q);
588void ContextPrivate::sourceOutputCallback(
const pa_source_output_info *info)
590 m_sourceOutputs.updateEntry(info, q);
593void ContextPrivate::clientCallback(
const pa_client_info *info)
595 m_clients.updateEntry(info, q);
598void ContextPrivate::cardCallback(
const pa_card_info *info)
600 m_cards.updateEntry(info, q);
603void ContextPrivate::moduleCallback(
const pa_module_info *info)
605 m_modules.updateEntry(info, q);
608void ContextPrivate::streamRestoreCallback(
const pa_ext_stream_restore_info *info)
610 if (qstrcmp(info->name, EVENT_ROLE) != 0) {
614 const int eventRoleIndex = 0;
615 StreamRestore *obj = qobject_cast<StreamRestore *>(m_streamRestores.data().value(eventRoleIndex));
619 props.insert(QStringLiteral(
"application.icon_name"), QStringLiteral(
"preferences-desktop-notification"));
620 obj =
new StreamRestore(eventRoleIndex, props, q);
621 obj->d->update(info);
622 m_streamRestores.insert(obj);
624 obj->d->update(info);
628void ContextPrivate::serverCallback(
const pa_server_info *info)
630 m_server->d->update(info);
633void Context::setCardProfile(quint32 index,
const QString &profile)
638 qCDebug(PULSEAUDIOQT) << index << profile;
639 if (!PAOperation(pa_context_set_card_profile_by_index(d->m_context, index, profile.toUtf8().constData(),
nullptr,
nullptr))) {
640 qCWarning(PULSEAUDIOQT) <<
"pa_context_set_card_profile_by_index failed";
645void Context::setDefaultSink(
const QString &name)
651 if (!PAOperation(pa_context_set_default_sink(d->m_context, nameData.
constData(),
nullptr,
nullptr))) {
652 qCWarning(PULSEAUDIOQT) <<
"pa_context_set_default_sink failed";
656 d->m_newDefaultSink =
name;
657 if (!PAOperation(pa_ext_stream_restore_read(d->m_context, ext_stream_restore_change_sink_cb, d.get()))) {
658 qCWarning(PULSEAUDIOQT) <<
"pa_ext_stream_restore_read failed";
662void Context::setDefaultSource(
const QString &name)
668 if (!PAOperation(pa_context_set_default_source(d->m_context, nameData.
constData(),
nullptr,
nullptr))) {
669 qCWarning(PULSEAUDIOQT) <<
"pa_context_set_default_source failed";
673 d->m_newDefaultSource =
name;
674 if (!PAOperation(pa_ext_stream_restore_read(d->m_context, ext_stream_restore_change_source_cb, d.get()))) {
675 qCWarning(PULSEAUDIOQT) <<
"pa_ext_stream_restore_read failed";
679void ContextPrivate::streamRestoreWrite(
const pa_ext_stream_restore_info *info)
684 if (!PAOperation(pa_ext_stream_restore_write(m_context, PA_UPDATE_REPLACE, info, 1,
true,
nullptr,
nullptr))) {
685 qCWarning(PULSEAUDIOQT) <<
"pa_ext_stream_restore_write failed";
689void ContextPrivate::connectToDaemon()
695 qCDebug(PULSEAUDIOQT) <<
"Connecting to daemon.";
697 m_state = Context::State::Connecting;
698 Q_EMIT q->stateChanged();
702 qCWarning(PULSEAUDIOQT) <<
"Disabling PulseAudio integration for lack of GLib event loop";
706 qCDebug(PULSEAUDIOQT) <<
"Attempting connection to PulseAudio sound daemon";
708 m_mainloop = pa_glib_mainloop_new(
nullptr);
709 Q_ASSERT(m_mainloop);
712 pa_mainloop_api *api = pa_glib_mainloop_get_api(m_mainloop);
715 pa_proplist *proplist = pa_proplist_new();
717 if (!s_applicationId.isEmpty()) {
718 pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, s_applicationId.toUtf8().constData());
723 m_context = pa_context_new_with_proplist(api,
nullptr, proplist);
724 pa_proplist_free(proplist);
727 if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFAIL,
nullptr) < 0) {
728 qCWarning(PULSEAUDIOQT) <<
"Failed to connect context";
729 pa_context_unref(m_context);
730 pa_glib_mainloop_free(m_mainloop);
733 m_mainloop =
nullptr;
734 m_state = Context::State::Unconnected;
735 Q_EMIT q->stateChanged();
738 pa_context_set_state_callback(m_context, &context_state_callback,
this);
741void ContextPrivate::checkConnectTries()
743 if (++m_connectTries; !hasConnectionTriesLeft()) {
744 qCWarning(PULSEAUDIOQT) <<
"Giving up after" << m_connectTries <<
"tries to connect";
745 m_connectTimer.stop();
746 Q_EMIT q->autoConnectingChanged();
750void ContextPrivate::disconnectSignals()
752 m_sinks.disconnectSignals();
753 m_sinkInputs.disconnectSignals();
754 m_sources.disconnectSignals();
755 m_sourceOutputs.disconnectSignals();
756 m_clients.disconnectSignals();
757 m_cards.disconnectSignals();
758 m_modules.disconnectSignals();
759 m_streamRestores.disconnectSignals();
761 m_server->disconnectSignals();
767void ContextPrivate::reset()
770 m_sinkInputs.reset();
772 m_sourceOutputs.reset();
776 m_streamRestores.reset();
779 m_state = Context::State::Unconnected;
780 Q_EMIT q->stateChanged();
783bool Context::isValid()
785 return d->m_context && d->m_mainloop;
790 return d->m_sinks.
data();
795 return d->m_sinkInputs.
data();
800 return d->m_sources.
data();
805 return d->m_sourceOutputs.
data();
810 return d->m_clients.
data();
815 return d->m_cards.
data();
820 return d->m_modules.
data();
825 return d->m_streamRestores.
data();
828Server *Context::server()
const
833void ContextPrivate::setGenericVolume(
838 const std::function<pa_operation *(pa_context *, uint32_t,
const pa_cvolume *, pa_context_success_cb_t,
void *)> &pa_set_volume)
843 newVolume = qBound<qint64>(0, newVolume, PA_VOLUME_MAX);
844 pa_cvolume newCVolume = cVolume;
846 const qint64 orig = pa_cvolume_max(&cVolume);
847 const qint64 diff = newVolume - orig;
848 for (
int i = 0; i < newCVolume.channels; ++i) {
849 const qint64 channel = newCVolume.values[i];
850 const qint64 channelDiff = orig == 0 ? diff : diff * channel / orig;
851 newCVolume.values[i] = qBound<qint64>(0, newCVolume.values[i] + channelDiff, PA_VOLUME_MAX);
854 Q_ASSERT(newCVolume.channels > channel);
855 newCVolume.values[channel] = newVolume;
857 if (!PAOperation(pa_set_volume(m_context, index, &newCVolume,
nullptr,
nullptr))) {
858 qCWarning(PULSEAUDIOQT) <<
"pa_set_volume failed";
863void ContextPrivate::setGenericMute(quint32 index,
865 const std::function<pa_operation *(pa_context *, uint32_t,
int, pa_context_success_cb_t,
void *)> &pa_set_mute)
870 if (!PAOperation(pa_set_mute(m_context, index, mute,
nullptr,
nullptr))) {
871 qCWarning(PULSEAUDIOQT) <<
"pa_set_mute failed";
876void ContextPrivate::setGenericPort(quint32 index,
878 const std::function<pa_operation *(pa_context *, uint32_t,
const char *, pa_context_success_cb_t,
void *)> &pa_set_port)
883 if (!PAOperation(pa_set_port(m_context, index, portName.
toUtf8().
constData(),
nullptr,
nullptr))) {
884 qCWarning(PULSEAUDIOQT) <<
"pa_set_port failed";
889void ContextPrivate::setGenericDeviceForStream(
892 const std::function<pa_operation *(pa_context *, uint32_t, uint32_t, pa_context_success_cb_t,
void *)> &pa_move_stream_to_device)
897 if (!PAOperation(pa_move_stream_to_device(m_context, streamIndex, deviceIndex,
nullptr,
nullptr))) {
898 qCWarning(PULSEAUDIOQT) <<
"pa_move_stream_to_device failed";
903void ContextPrivate::setGenericVolumes(
907 const std::function<pa_operation *(pa_context *, uint32_t,
const pa_cvolume *, pa_context_success_cb_t,
void *)> &pa_set_volume)
912 Q_ASSERT(channelVolumes.
count() == cVolume.channels);
914 pa_cvolume newCVolume = cVolume;
915 for (
int i = 0; i < channelVolumes.
count(); ++i) {
916 newCVolume.values[i] = qBound<qint64>(0, channelVolumes.
at(i), PA_VOLUME_MAX);
919 if (!PAOperation(pa_set_volume(m_context, index, &newCVolume,
nullptr,
nullptr))) {
920 qCWarning(PULSEAUDIOQT) <<
"pa_set_volume failed";
925void Context::setApplicationId(
const QString &applicationId)
927 ContextPrivate::s_applicationId = applicationId;
930pa_context *Context::context()
const
935Context::State Context::state()
const
940bool Context::isAutoConnecting()
const
942 return d->m_connectTimer.isActive();
945void Context::reconnectDaemon()
947 if (isAutoConnecting()) {
948 qCDebug(PULSEAUDIOQT) <<
"Already in the process of auto connecting. Not connecting again.";
952 d->forceDisconnect();
953 return d->connectToDaemon();
956void ContextPrivate::forceDisconnect()
959 pa_context_unref(m_context);
964 pa_glib_mainloop_free(m_mainloop);
965 m_mainloop =
nullptr;
969bool ContextPrivate::hasConnectionTriesLeft()
const
971 constexpr auto maxTries = 5;
972 return m_connectTries < maxTries;
975void ContextPrivate::synthesizeEventStream()
977 const pa_ext_stream_restore_info info{
979 .channel_map = {.channels = 1, .map = {PA_CHANNEL_POSITION_MONO}},
980 .volume = {.channels = 1, .values = {PA_VOLUME_NORM}},
984 streamRestoreCallback(&info);
987void Context::loadModule(
const QString &name,
const QString &argument)
992 if (!PAOperation(pa_context_load_module(d->m_context, qUtf8Printable(name), qUtf8Printable(argument),
nullptr,
nullptr))) {
993 qCWarning(PULSEAUDIOQT) <<
"pa_context_load_module() failed" <<
name << argument;
997void Context::unloadModule(PulseAudioQt::Module *module)
999 if (!PAOperation(pa_context_unload_module(d->m_context, module->index(),
nullptr,
nullptr))) {
1000 qCWarning(PULSEAUDIOQT) <<
"pa_context_load_module() failed" <<
module->index() << module->name() << module->argument();
QString name(StandardAction id)
The primary namespace of PulseAudioQt.
qint64 normalVolume()
The normal volume (100%, 0 dB).
qint64 maximumUIVolume()
The maximum volume suitable to display in a UI.
qint64 minimumVolume()
The minimum volume (0%).
qint64 maximumVolume()
The maximum volume PulseAudio can store.
QAbstractEventDispatcher * instance(QThread *thread)
const char * constData() const const
QDBusConnection sessionBus()
void serviceRegistered(const QString &serviceName)
const_reference at(qsizetype i) const const
qsizetype count() const const
bool disconnect(const QMetaObject::Connection &connection)
QByteArray toUtf8() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)