File indexing completed on 2023-11-26 04:41:12

0001 /*
0002     SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "context.h"
0008 #include "server.h"
0009 
0010 #include "debug.h"
0011 #include <QAbstractEventDispatcher>
0012 #include <QDBusConnection>
0013 #include <QDBusServiceWatcher>
0014 #include <QGuiApplication>
0015 #include <QIcon>
0016 #include <QTimer>
0017 
0018 #include <memory>
0019 
0020 #include "card.h"
0021 #include "client.h"
0022 #include "module.h"
0023 #include "sink.h"
0024 #include "sinkinput.h"
0025 #include "source.h"
0026 #include "sourceoutput.h"
0027 #include "streamrestore.h"
0028 
0029 #include "context_p.h"
0030 #include "server_p.h"
0031 #include "streamrestore_p.h"
0032 
0033 namespace PulseAudioQt
0034 {
0035 qint64 normalVolume()
0036 {
0037     return PA_VOLUME_NORM;
0038 }
0039 
0040 qint64 minimumVolume()
0041 {
0042     return PA_VOLUME_MUTED;
0043 }
0044 
0045 qint64 maximumVolume()
0046 {
0047     return PA_VOLUME_MAX;
0048 }
0049 
0050 qint64 maximumUIVolume()
0051 {
0052     return PA_VOLUME_UI_MAX;
0053 }
0054 
0055 QString ContextPrivate::s_applicationId;
0056 
0057 #ifndef K_DOXYGEN
0058 
0059 static bool isGoodState(int eol)
0060 {
0061     if (eol < 0) {
0062         // Error
0063         return false;
0064     }
0065 
0066     if (eol > 0) {
0067         // End of callback chain
0068         return false;
0069     }
0070 
0071     return true;
0072 }
0073 
0074 // --------------------------
0075 
0076 static void sink_cb(pa_context *context, const pa_sink_info *info, int eol, void *data)
0077 {
0078     if (!isGoodState(eol))
0079         return;
0080     Q_ASSERT(context);
0081     Q_ASSERT(data);
0082     static_cast<ContextPrivate *>(data)->sinkCallback(info);
0083 }
0084 
0085 static void sink_input_callback(pa_context *context, const pa_sink_input_info *info, int eol, void *data)
0086 {
0087     if (!isGoodState(eol))
0088         return;
0089     // pulsesink probe is used by gst-pulse only to query sink formats (not for playback)
0090     if (qstrcmp(info->name, "pulsesink probe") == 0) {
0091         return;
0092     }
0093     if (const char *id = pa_proplist_gets(info->proplist, "module-stream-restore.id")) {
0094         if (qstrcmp(id, "sink-input-by-media-role:event") == 0) {
0095             qCDebug(PULSEAUDIOQT) << "Ignoring event role sink input.";
0096             return;
0097         }
0098     }
0099     Q_ASSERT(context);
0100     Q_ASSERT(data);
0101     static_cast<ContextPrivate *>(data)->sinkInputCallback(info);
0102 }
0103 
0104 static void source_cb(pa_context *context, const pa_source_info *info, int eol, void *data)
0105 {
0106     if (!isGoodState(eol))
0107         return;
0108     // FIXME: This forces excluding monitors
0109     if (info->monitor_of_sink != PA_INVALID_INDEX)
0110         return;
0111     Q_ASSERT(context);
0112     Q_ASSERT(data);
0113     static_cast<ContextPrivate *>(data)->sourceCallback(info);
0114 }
0115 
0116 static void source_output_cb(pa_context *context, const pa_source_output_info *info, int eol, void *data)
0117 {
0118     if (!isGoodState(eol))
0119         return;
0120     // FIXME: This forces excluding these apps
0121     if (const char *app = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_ID)) {
0122         if (strcmp(app, "org.PulseAudio.pavucontrol") == 0 //
0123             || strcmp(app, "org.gnome.VolumeControl") == 0 //
0124             || strcmp(app, "org.kde.kmixd") == 0 //
0125             || strcmp(app, "org.kde.plasma-pa") == 0) //
0126             return;
0127     }
0128     Q_ASSERT(context);
0129     Q_ASSERT(data);
0130     static_cast<ContextPrivate *>(data)->sourceOutputCallback(info);
0131 }
0132 
0133 static void client_cb(pa_context *context, const pa_client_info *info, int eol, void *data)
0134 {
0135     if (!isGoodState(eol))
0136         return;
0137     Q_ASSERT(context);
0138     Q_ASSERT(data);
0139     static_cast<ContextPrivate *>(data)->clientCallback(info);
0140 }
0141 
0142 static void card_cb(pa_context *context, const pa_card_info *info, int eol, void *data)
0143 {
0144     if (!isGoodState(eol))
0145         return;
0146     Q_ASSERT(context);
0147     Q_ASSERT(data);
0148     static_cast<ContextPrivate *>(data)->cardCallback(info);
0149 }
0150 
0151 static void module_info_list_cb(pa_context *context, const pa_module_info *info, int eol, void *data)
0152 {
0153     if (!isGoodState(eol))
0154         return;
0155     Q_ASSERT(context);
0156     Q_ASSERT(data);
0157     static_cast<ContextPrivate *>(data)->moduleCallback(info);
0158 }
0159 
0160 static void server_cb(pa_context *context, const pa_server_info *info, void *data)
0161 {
0162     Q_ASSERT(context);
0163     Q_ASSERT(data);
0164     if (!info) {
0165         // info may be nullptr when e.g. the server doesn't reply in time (e.g. it is stuck)
0166         // https://bugs.kde.org/show_bug.cgi?id=454647
0167         qCWarning(PULSEAUDIOQT) << "server_cb() called without info!";
0168         return;
0169     }
0170     static_cast<ContextPrivate *>(data)->serverCallback(info);
0171 }
0172 
0173 static void context_state_callback(pa_context *context, void *data)
0174 {
0175     Q_ASSERT(data);
0176     static_cast<ContextPrivate *>(data)->contextStateCallback(context);
0177 }
0178 
0179 static void subscribe_cb(pa_context *context, pa_subscription_event_type_t type, uint32_t index, void *data)
0180 {
0181     Q_ASSERT(data);
0182     static_cast<ContextPrivate *>(data)->subscribeCallback(context, type, index);
0183 }
0184 
0185 static void ext_stream_restore_read_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
0186 {
0187     if (!isGoodState(eol)) {
0188         return;
0189     }
0190     Q_ASSERT(context);
0191     Q_ASSERT(data);
0192     static_cast<ContextPrivate *>(data)->streamRestoreCallback(info);
0193 }
0194 
0195 static void ext_stream_restore_subscribe_cb(pa_context *context, void *data)
0196 {
0197     Q_ASSERT(context);
0198     Q_ASSERT(data);
0199     if (!PAOperation(pa_ext_stream_restore_read(context, ext_stream_restore_read_cb, data))) {
0200         qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_read() failed";
0201     }
0202 }
0203 
0204 static void ext_stream_restore_change_sink_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
0205 {
0206     if (!isGoodState(eol)) {
0207         return;
0208     }
0209     Q_ASSERT(context);
0210     Q_ASSERT(data);
0211     if (qstrncmp(info->name, "sink-input-by", 13) == 0) {
0212         ContextPrivate *contextp = static_cast<ContextPrivate *>(data);
0213         const QByteArray deviceData = contextp->m_newDefaultSink.toUtf8();
0214         pa_ext_stream_restore_info newinfo;
0215         newinfo.name = info->name;
0216         newinfo.channel_map = info->channel_map;
0217         newinfo.volume = info->volume;
0218         newinfo.mute = info->mute;
0219         newinfo.device = deviceData.constData();
0220         contextp->streamRestoreWrite(&newinfo);
0221     }
0222 }
0223 
0224 static void ext_stream_restore_change_source_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
0225 {
0226     if (!isGoodState(eol)) {
0227         return;
0228     }
0229     Q_ASSERT(context);
0230     Q_ASSERT(data);
0231     if (qstrncmp(info->name, "source-output-by", 16) == 0) {
0232         ContextPrivate *contextp = static_cast<ContextPrivate *>(data);
0233         const QByteArray deviceData = contextp->m_newDefaultSource.toUtf8();
0234         pa_ext_stream_restore_info newinfo;
0235         newinfo.name = info->name;
0236         newinfo.channel_map = info->channel_map;
0237         newinfo.volume = info->volume;
0238         newinfo.mute = info->mute;
0239         newinfo.device = deviceData.constData();
0240         contextp->streamRestoreWrite(&newinfo);
0241     }
0242 }
0243 
0244 #endif
0245 
0246 // --------------------------
0247 
0248 Context::Context(QObject *parent)
0249     : QObject(parent)
0250     , d(new ContextPrivate(this))
0251 {
0252     d->m_server = new Server(this);
0253     d->m_context = nullptr;
0254     d->m_mainloop = nullptr;
0255     d->m_references = 0;
0256 
0257     d->connectToDaemon();
0258 
0259     QDBusServiceWatcher *watcher =
0260         new QDBusServiceWatcher(QStringLiteral("org.pulseaudio.Server"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this);
0261     connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, [this] {
0262         d->connectToDaemon();
0263     });
0264 
0265     connect(&d->m_connectTimer, &QTimer::timeout, this, [this] {
0266         d->connectToDaemon();
0267         d->checkConnectTries();
0268     });
0269 
0270     connect(&d->m_sinks, &MapBaseQObject::added, this, [this](int, QObject *object) {
0271         Q_EMIT sinkAdded(static_cast<Sink *>(object));
0272     });
0273     connect(&d->m_sinks, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0274         Q_EMIT sinkRemoved(static_cast<Sink *>(object));
0275     });
0276 
0277     connect(&d->m_sinkInputs, &MapBaseQObject::added, this, [this](int, QObject *object) {
0278         Q_EMIT sinkInputAdded(static_cast<SinkInput *>(object));
0279     });
0280     connect(&d->m_sinkInputs, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0281         Q_EMIT sinkInputRemoved(static_cast<SinkInput *>(object));
0282     });
0283 
0284     connect(&d->m_sources, &MapBaseQObject::added, this, [this](int, QObject *object) {
0285         Q_EMIT sourceAdded(static_cast<Source *>(object));
0286     });
0287     connect(&d->m_sources, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0288         Q_EMIT sourceRemoved(static_cast<Source *>(object));
0289     });
0290 
0291     connect(&d->m_sourceOutputs, &MapBaseQObject::added, this, [this](int, QObject *object) {
0292         Q_EMIT sourceOutputAdded(static_cast<SourceOutput *>(object));
0293     });
0294     connect(&d->m_sourceOutputs, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0295         Q_EMIT sourceOutputRemoved(static_cast<SourceOutput *>(object));
0296     });
0297 
0298     connect(&d->m_clients, &MapBaseQObject::added, this, [this](int, QObject *object) {
0299         Q_EMIT clientAdded(static_cast<Client *>(object));
0300     });
0301     connect(&d->m_clients, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0302         Q_EMIT clientRemoved(static_cast<Client *>(object));
0303     });
0304 
0305     connect(&d->m_cards, &MapBaseQObject::added, this, [this](int, QObject *object) {
0306         Q_EMIT cardAdded(static_cast<Card *>(object));
0307     });
0308     connect(&d->m_cards, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0309         Q_EMIT cardRemoved(static_cast<Card *>(object));
0310     });
0311 
0312     connect(&d->m_modules, &MapBaseQObject::added, this, [this](int, QObject *object) {
0313         Q_EMIT moduleAdded(static_cast<Module *>(object));
0314     });
0315     connect(&d->m_modules, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0316         Q_EMIT moduleRemoved(static_cast<Module *>(object));
0317     });
0318 
0319     connect(&d->m_streamRestores, &MapBaseQObject::added, this, [this](int, QObject *object) {
0320         Q_EMIT streamRestoreAdded(static_cast<StreamRestore *>(object));
0321     });
0322     connect(&d->m_streamRestores, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0323         Q_EMIT streamRestoreRemoved(static_cast<StreamRestore *>(object));
0324     });
0325 }
0326 
0327 ContextPrivate::ContextPrivate(Context *q)
0328     : q(q)
0329 {
0330 }
0331 
0332 Context::~Context()
0333 {
0334     delete d;
0335 }
0336 
0337 ContextPrivate::~ContextPrivate()
0338 {
0339     if (m_context) {
0340         pa_context_unref(m_context);
0341         m_context = nullptr;
0342     }
0343 
0344     if (m_mainloop) {
0345         pa_glib_mainloop_free(m_mainloop);
0346         m_mainloop = nullptr;
0347     }
0348 
0349     reset();
0350 }
0351 
0352 Context *Context::instance()
0353 {
0354     static std::unique_ptr<Context> context(new Context);
0355     return context.get();
0356 }
0357 
0358 void ContextPrivate::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index)
0359 {
0360     Q_ASSERT(context == m_context);
0361 
0362     switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
0363     case PA_SUBSCRIPTION_EVENT_SINK:
0364         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0365             m_sinks.removeEntry(index);
0366         } else {
0367             if (!PAOperation(pa_context_get_sink_info_by_index(context, index, sink_cb, this))) {
0368                 qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_info_by_index() failed";
0369                 return;
0370             }
0371         }
0372         break;
0373 
0374     case PA_SUBSCRIPTION_EVENT_SOURCE:
0375         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0376             m_sources.removeEntry(index);
0377         } else {
0378             if (!PAOperation(pa_context_get_source_info_by_index(context, index, source_cb, this))) {
0379                 qCWarning(PULSEAUDIOQT) << "pa_context_get_source_info_by_index() failed";
0380                 return;
0381             }
0382         }
0383         break;
0384 
0385     case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
0386         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0387             m_sinkInputs.removeEntry(index);
0388         } else {
0389             if (!PAOperation(pa_context_get_sink_input_info(context, index, sink_input_callback, this))) {
0390                 qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_input_info() failed";
0391                 return;
0392             }
0393         }
0394         break;
0395 
0396     case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
0397         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0398             m_sourceOutputs.removeEntry(index);
0399         } else {
0400             if (!PAOperation(pa_context_get_source_output_info(context, index, source_output_cb, this))) {
0401                 qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_input_info() failed";
0402                 return;
0403             }
0404         }
0405         break;
0406 
0407     case PA_SUBSCRIPTION_EVENT_CLIENT:
0408         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0409             m_clients.removeEntry(index);
0410         } else {
0411             if (!PAOperation(pa_context_get_client_info(context, index, client_cb, this))) {
0412                 qCWarning(PULSEAUDIOQT) << "pa_context_get_client_info() failed";
0413                 return;
0414             }
0415         }
0416         break;
0417 
0418     case PA_SUBSCRIPTION_EVENT_CARD:
0419         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0420             m_cards.removeEntry(index);
0421         } else {
0422             if (!PAOperation(pa_context_get_card_info_by_index(context, index, card_cb, this))) {
0423                 qCWarning(PULSEAUDIOQT) << "pa_context_get_card_info_by_index() failed";
0424                 return;
0425             }
0426         }
0427         break;
0428 
0429     case PA_SUBSCRIPTION_EVENT_MODULE:
0430         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0431             m_modules.removeEntry(index);
0432         } else {
0433             if (!PAOperation(pa_context_get_module_info_list(context, module_info_list_cb, this))) {
0434                 qCWarning(PULSEAUDIOQT) << "pa_context_get_module_info_list() failed";
0435                 return;
0436             }
0437         }
0438         break;
0439 
0440     case PA_SUBSCRIPTION_EVENT_SERVER:
0441         if (!PAOperation(pa_context_get_server_info(context, server_cb, this))) {
0442             qCWarning(PULSEAUDIOQT) << "pa_context_get_server_info() failed";
0443             return;
0444         }
0445         break;
0446     }
0447 }
0448 
0449 void ContextPrivate::contextStateCallback(pa_context *c)
0450 {
0451     qCDebug(PULSEAUDIOQT) << "state callback";
0452     pa_context_state_t state = pa_context_get_state(c);
0453     if (state == PA_CONTEXT_READY) {
0454         qCDebug(PULSEAUDIOQT) << "ready, stopping connect timer";
0455         m_connectTimer.stop();
0456 
0457         // 1. Register for the stream changes (except during probe)
0458         if (m_context == c) {
0459             pa_context_set_subscribe_callback(c, subscribe_cb, this);
0460 
0461             if (!PAOperation(
0462                     pa_context_subscribe(c,
0463                                          (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_CLIENT
0464                                                                   | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT
0465                                                                   | PA_SUBSCRIPTION_MASK_CARD | PA_SUBSCRIPTION_MASK_MODULE | PA_SUBSCRIPTION_MASK_SERVER),
0466                                          nullptr,
0467                                          nullptr))) {
0468                 qCWarning(PULSEAUDIOQT) << "pa_context_subscribe() failed";
0469                 return;
0470             }
0471         }
0472 
0473         if (!PAOperation(pa_context_get_sink_info_list(c, sink_cb, this))) {
0474             qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_info_list() failed";
0475             return;
0476         }
0477 
0478         if (!PAOperation(pa_context_get_source_info_list(c, source_cb, this))) {
0479             qCWarning(PULSEAUDIOQT) << "pa_context_get_source_info_list() failed";
0480             return;
0481         }
0482 
0483         if (!PAOperation(pa_context_get_client_info_list(c, client_cb, this))) {
0484             qCWarning(PULSEAUDIOQT) << "pa_context_client_info_list() failed";
0485             return;
0486         }
0487 
0488         if (!PAOperation(pa_context_get_card_info_list(c, card_cb, this))) {
0489             qCWarning(PULSEAUDIOQT) << "pa_context_get_card_info_list() failed";
0490             return;
0491         }
0492 
0493         if (!PAOperation(pa_context_get_sink_input_info_list(c, sink_input_callback, this))) {
0494             qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_input_info_list() failed";
0495             return;
0496         }
0497 
0498         if (!PAOperation(pa_context_get_source_output_info_list(c, source_output_cb, this))) {
0499             qCWarning(PULSEAUDIOQT) << "pa_context_get_source_output_info_list() failed";
0500             return;
0501         }
0502 
0503         if (!PAOperation(pa_context_get_module_info_list(c, module_info_list_cb, this))) {
0504             qCWarning(PULSEAUDIOQT) << "pa_context_get_module_info_list() failed";
0505             return;
0506         }
0507 
0508         if (!PAOperation(pa_context_get_server_info(c, server_cb, this))) {
0509             qCWarning(PULSEAUDIOQT) << "pa_context_get_server_info() failed";
0510             return;
0511         }
0512 
0513         if (PAOperation(pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, this))) {
0514             pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, this);
0515             PAOperation(pa_ext_stream_restore_subscribe(c, 1, nullptr, this));
0516         } else {
0517             qCWarning(PULSEAUDIOQT) << "Failed to initialize stream_restore extension";
0518         }
0519     } else if (!PA_CONTEXT_IS_GOOD(state)) {
0520         qCWarning(PULSEAUDIOQT) << "context kaput";
0521         if (m_context) {
0522             pa_context_unref(m_context);
0523             m_context = nullptr;
0524         }
0525         reset();
0526         qCDebug(PULSEAUDIOQT) << "Starting connect timer";
0527         m_connectTimer.start(std::chrono::seconds(5));
0528     }
0529 }
0530 
0531 void ContextPrivate::sinkCallback(const pa_sink_info *info)
0532 {
0533     // This parenting here is a bit weird
0534     m_sinks.updateEntry(info, q);
0535 }
0536 
0537 void ContextPrivate::sinkInputCallback(const pa_sink_input_info *info)
0538 {
0539     m_sinkInputs.updateEntry(info, q);
0540 }
0541 
0542 void ContextPrivate::sourceCallback(const pa_source_info *info)
0543 {
0544     m_sources.updateEntry(info, q);
0545 }
0546 
0547 void ContextPrivate::sourceOutputCallback(const pa_source_output_info *info)
0548 {
0549     m_sourceOutputs.updateEntry(info, q);
0550 }
0551 
0552 void ContextPrivate::clientCallback(const pa_client_info *info)
0553 {
0554     m_clients.updateEntry(info, q);
0555 }
0556 
0557 void ContextPrivate::cardCallback(const pa_card_info *info)
0558 {
0559     m_cards.updateEntry(info, q);
0560 }
0561 
0562 void ContextPrivate::moduleCallback(const pa_module_info *info)
0563 {
0564     m_modules.updateEntry(info, q);
0565 }
0566 
0567 void ContextPrivate::streamRestoreCallback(const pa_ext_stream_restore_info *info)
0568 {
0569     if (qstrcmp(info->name, "sink-input-by-media-role:event") != 0) {
0570         return;
0571     }
0572 
0573     const int eventRoleIndex = 1;
0574     StreamRestore *obj = qobject_cast<StreamRestore *>(m_streamRestores.data().value(eventRoleIndex));
0575 
0576     if (!obj) {
0577         QVariantMap props;
0578         props.insert(QStringLiteral("application.icon_name"), QStringLiteral("preferences-desktop-notification"));
0579         obj = new StreamRestore(eventRoleIndex, props, q);
0580         obj->d->update(info);
0581         m_streamRestores.insert(obj);
0582     } else {
0583         obj->d->update(info);
0584     }
0585 }
0586 
0587 void ContextPrivate::serverCallback(const pa_server_info *info)
0588 {
0589     m_server->d->update(info);
0590 }
0591 
0592 void Context::setCardProfile(quint32 index, const QString &profile)
0593 {
0594     if (!d->m_context) {
0595         return;
0596     }
0597     qCDebug(PULSEAUDIOQT) << index << profile;
0598     if (!PAOperation(pa_context_set_card_profile_by_index(d->m_context, index, profile.toUtf8().constData(), nullptr, nullptr))) {
0599         qCWarning(PULSEAUDIOQT) << "pa_context_set_card_profile_by_index failed";
0600         return;
0601     }
0602 }
0603 
0604 void Context::setDefaultSink(const QString &name)
0605 {
0606     if (!d->m_context) {
0607         return;
0608     }
0609     const QByteArray nameData = name.toUtf8();
0610     if (!PAOperation(pa_context_set_default_sink(d->m_context, nameData.constData(), nullptr, nullptr))) {
0611         qCWarning(PULSEAUDIOQT) << "pa_context_set_default_sink failed";
0612     }
0613 
0614     // Change device for all entries in stream-restore database
0615     d->m_newDefaultSink = name;
0616     if (!PAOperation(pa_ext_stream_restore_read(d->m_context, ext_stream_restore_change_sink_cb, d))) {
0617         qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_read failed";
0618     }
0619 }
0620 
0621 void Context::setDefaultSource(const QString &name)
0622 {
0623     if (!d->m_context) {
0624         return;
0625     }
0626     const QByteArray nameData = name.toUtf8();
0627     if (!PAOperation(pa_context_set_default_source(d->m_context, nameData.constData(), nullptr, nullptr))) {
0628         qCWarning(PULSEAUDIOQT) << "pa_context_set_default_source failed";
0629     }
0630 
0631     // Change device for all entries in stream-restore database
0632     d->m_newDefaultSource = name;
0633     if (!PAOperation(pa_ext_stream_restore_read(d->m_context, ext_stream_restore_change_source_cb, d))) {
0634         qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_read failed";
0635     }
0636 }
0637 
0638 void ContextPrivate::streamRestoreWrite(const pa_ext_stream_restore_info *info)
0639 {
0640     if (!m_context) {
0641         return;
0642     }
0643     if (!PAOperation(pa_ext_stream_restore_write(m_context, PA_UPDATE_REPLACE, info, 1, true, nullptr, nullptr))) {
0644         qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_write failed";
0645     }
0646 }
0647 
0648 void ContextPrivate::connectToDaemon()
0649 {
0650     if (m_context) {
0651         return;
0652     }
0653 
0654     // We require a glib event loop
0655     if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("Glib")) {
0656         qCWarning(PULSEAUDIOQT) << "Disabling PulseAudio integration for lack of GLib event loop";
0657         return;
0658     }
0659 
0660     qCDebug(PULSEAUDIOQT) << "Attempting connection to PulseAudio sound daemon";
0661     if (!m_mainloop) {
0662         m_mainloop = pa_glib_mainloop_new(nullptr);
0663         Q_ASSERT(m_mainloop);
0664     }
0665 
0666     pa_mainloop_api *api = pa_glib_mainloop_get_api(m_mainloop);
0667     Q_ASSERT(api);
0668 
0669     pa_proplist *proplist = pa_proplist_new();
0670     pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, QGuiApplication::applicationDisplayName().toUtf8().constData());
0671     if (!s_applicationId.isEmpty()) {
0672         pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, s_applicationId.toUtf8().constData());
0673     } else {
0674         pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, QGuiApplication::desktopFileName().toUtf8().constData());
0675     }
0676     pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, QGuiApplication::windowIcon().name().toUtf8().constData());
0677     m_context = pa_context_new_with_proplist(api, nullptr, proplist);
0678     pa_proplist_free(proplist);
0679     Q_ASSERT(m_context);
0680 
0681     if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFAIL, nullptr) < 0) {
0682         pa_context_unref(m_context);
0683         pa_glib_mainloop_free(m_mainloop);
0684         m_context = nullptr;
0685         m_mainloop = nullptr;
0686         return;
0687     }
0688     pa_context_set_state_callback(m_context, &context_state_callback, this);
0689 }
0690 
0691 void ContextPrivate::checkConnectTries()
0692 {
0693     if (++m_connectTries == 5) {
0694         qCWarning(PULSEAUDIOQT) << "Giving up after" << m_connectTries << "tries to connect";
0695         m_connectTimer.stop();
0696     }
0697 }
0698 
0699 void ContextPrivate::reset()
0700 {
0701     m_sinks.reset();
0702     m_sinkInputs.reset();
0703     m_sources.reset();
0704     m_sourceOutputs.reset();
0705     m_clients.reset();
0706     m_cards.reset();
0707     m_modules.reset();
0708     m_streamRestores.reset();
0709     m_server->reset();
0710     m_connectTries = 0;
0711 }
0712 
0713 bool Context::isValid()
0714 {
0715     return d->m_context && d->m_mainloop;
0716 }
0717 
0718 QVector<Sink *> Context::sinks() const
0719 {
0720     return d->m_sinks.data();
0721 }
0722 
0723 QVector<SinkInput *> Context::sinkInputs() const
0724 {
0725     return d->m_sinkInputs.data();
0726 }
0727 
0728 QVector<Source *> Context::sources() const
0729 {
0730     return d->m_sources.data();
0731 }
0732 
0733 QVector<SourceOutput *> Context::sourceOutputs() const
0734 {
0735     return d->m_sourceOutputs.data();
0736 }
0737 
0738 QVector<Client *> Context::clients() const
0739 {
0740     return d->m_clients.data();
0741 }
0742 
0743 QVector<Card *> Context::cards() const
0744 {
0745     return d->m_cards.data();
0746 }
0747 
0748 QVector<Module *> Context::modules() const
0749 {
0750     return d->m_modules.data();
0751 }
0752 
0753 QVector<StreamRestore *> Context::streamRestores() const
0754 {
0755     return d->m_streamRestores.data();
0756 }
0757 
0758 Server *Context::server() const
0759 {
0760     return d->m_server;
0761 }
0762 
0763 void ContextPrivate::setGenericVolume(
0764     quint32 index,
0765     int channel,
0766     qint64 newVolume,
0767     pa_cvolume cVolume,
0768     const std::function<pa_operation *(pa_context *, uint32_t, const pa_cvolume *, pa_context_success_cb_t, void *)> &pa_set_volume)
0769 {
0770     if (!m_context) {
0771         return;
0772     }
0773     newVolume = qBound<qint64>(0, newVolume, PA_VOLUME_MAX);
0774     pa_cvolume newCVolume = cVolume;
0775     if (channel == -1) { // -1 all channels
0776         const qint64 diff = newVolume - pa_cvolume_max(&cVolume);
0777         for (int i = 0; i < newCVolume.channels; ++i) {
0778             newCVolume.values[i] = qBound<qint64>(0, newCVolume.values[i] + diff, PA_VOLUME_MAX);
0779         }
0780     } else {
0781         Q_ASSERT(newCVolume.channels > channel);
0782         newCVolume.values[channel] = newVolume;
0783     }
0784     if (!pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr)) {
0785         qCWarning(PULSEAUDIOQT) << "pa_set_volume failed";
0786         return;
0787     }
0788 }
0789 
0790 void ContextPrivate::setGenericMute(quint32 index,
0791                                     bool mute,
0792                                     const std::function<pa_operation *(pa_context *, uint32_t, int, pa_context_success_cb_t, void *)> &pa_set_mute)
0793 {
0794     if (!m_context) {
0795         return;
0796     }
0797     if (!PAOperation(pa_set_mute(m_context, index, mute, nullptr, nullptr))) {
0798         qCWarning(PULSEAUDIOQT) << "pa_set_mute failed";
0799         return;
0800     }
0801 }
0802 
0803 void ContextPrivate::setGenericPort(quint32 index,
0804                                     const QString &portName,
0805                                     const std::function<pa_operation *(pa_context *, uint32_t, const char *, pa_context_success_cb_t, void *)> &pa_set_port)
0806 {
0807     if (!m_context) {
0808         return;
0809     }
0810     if (!PAOperation(pa_set_port(m_context, index, portName.toUtf8().constData(), nullptr, nullptr))) {
0811         qCWarning(PULSEAUDIOQT) << "pa_set_port failed";
0812         return;
0813     }
0814 }
0815 
0816 void ContextPrivate::setGenericDeviceForStream(
0817     quint32 streamIndex,
0818     quint32 deviceIndex,
0819     const std::function<pa_operation *(pa_context *, uint32_t, uint32_t, pa_context_success_cb_t, void *)> &pa_move_stream_to_device)
0820 {
0821     if (!m_context) {
0822         return;
0823     }
0824     if (!PAOperation(pa_move_stream_to_device(m_context, streamIndex, deviceIndex, nullptr, nullptr))) {
0825         qCWarning(PULSEAUDIOQT) << "pa_move_stream_to_device failed";
0826         return;
0827     }
0828 }
0829 
0830 void ContextPrivate::setGenericVolumes(
0831     quint32 index,
0832     QVector<qint64> channelVolumes,
0833     pa_cvolume cVolume,
0834     const std::function<pa_operation *(pa_context *, uint32_t, const pa_cvolume *, pa_context_success_cb_t, void *)> &pa_set_volume)
0835 {
0836     if (!m_context) {
0837         return;
0838     }
0839     Q_ASSERT(channelVolumes.count() == cVolume.channels);
0840 
0841     pa_cvolume newCVolume = cVolume;
0842     for (int i = 0; i < channelVolumes.count(); ++i) {
0843         newCVolume.values[i] = qBound<qint64>(0, channelVolumes.at(i), PA_VOLUME_MAX);
0844     }
0845 
0846     if (!PAOperation(pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr))) {
0847         qCWarning(PULSEAUDIOQT) << "pa_set_volume failed";
0848         return;
0849     }
0850 }
0851 
0852 void Context::setApplicationId(const QString &applicationId)
0853 {
0854     ContextPrivate::s_applicationId = applicationId;
0855 }
0856 
0857 pa_context *Context::context() const
0858 {
0859     return d->m_context;
0860 }
0861 
0862 } // PulseAudioQt