File indexing completed on 2024-04-14 14:55:42

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 
0256     d->connectToDaemon();
0257 
0258     QDBusServiceWatcher *watcher =
0259         new QDBusServiceWatcher(QStringLiteral("org.pulseaudio.Server"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this);
0260     connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, [this] {
0261         d->connectToDaemon();
0262     });
0263 
0264     connect(&d->m_connectTimer, &QTimer::timeout, this, [this] {
0265         d->connectToDaemon();
0266         d->checkConnectTries();
0267     });
0268 
0269     connect(&d->m_sinks, &MapBaseQObject::added, this, [this](int, QObject *object) {
0270         Q_EMIT sinkAdded(static_cast<Sink *>(object));
0271     });
0272     connect(&d->m_sinks, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0273         Q_EMIT sinkRemoved(static_cast<Sink *>(object));
0274     });
0275 
0276     connect(&d->m_sinkInputs, &MapBaseQObject::added, this, [this](int, QObject *object) {
0277         Q_EMIT sinkInputAdded(static_cast<SinkInput *>(object));
0278     });
0279     connect(&d->m_sinkInputs, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0280         Q_EMIT sinkInputRemoved(static_cast<SinkInput *>(object));
0281     });
0282 
0283     connect(&d->m_sources, &MapBaseQObject::added, this, [this](int, QObject *object) {
0284         Q_EMIT sourceAdded(static_cast<Source *>(object));
0285     });
0286     connect(&d->m_sources, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0287         Q_EMIT sourceRemoved(static_cast<Source *>(object));
0288     });
0289 
0290     connect(&d->m_sourceOutputs, &MapBaseQObject::added, this, [this](int, QObject *object) {
0291         Q_EMIT sourceOutputAdded(static_cast<SourceOutput *>(object));
0292     });
0293     connect(&d->m_sourceOutputs, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0294         Q_EMIT sourceOutputRemoved(static_cast<SourceOutput *>(object));
0295     });
0296 
0297     connect(&d->m_clients, &MapBaseQObject::added, this, [this](int, QObject *object) {
0298         Q_EMIT clientAdded(static_cast<Client *>(object));
0299     });
0300     connect(&d->m_clients, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0301         Q_EMIT clientRemoved(static_cast<Client *>(object));
0302     });
0303 
0304     connect(&d->m_cards, &MapBaseQObject::added, this, [this](int, QObject *object) {
0305         Q_EMIT cardAdded(static_cast<Card *>(object));
0306     });
0307     connect(&d->m_cards, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0308         Q_EMIT cardRemoved(static_cast<Card *>(object));
0309     });
0310 
0311     connect(&d->m_modules, &MapBaseQObject::added, this, [this](int, QObject *object) {
0312         Q_EMIT moduleAdded(static_cast<Module *>(object));
0313     });
0314     connect(&d->m_modules, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0315         Q_EMIT moduleRemoved(static_cast<Module *>(object));
0316     });
0317 
0318     connect(&d->m_streamRestores, &MapBaseQObject::added, this, [this](int, QObject *object) {
0319         Q_EMIT streamRestoreAdded(static_cast<StreamRestore *>(object));
0320     });
0321     connect(&d->m_streamRestores, &MapBaseQObject::removed, this, [this](int, QObject *object) {
0322         Q_EMIT streamRestoreRemoved(static_cast<StreamRestore *>(object));
0323     });
0324 }
0325 
0326 ContextPrivate::ContextPrivate(Context *q)
0327     : q(q)
0328 {
0329 }
0330 
0331 Context::~Context()
0332 {
0333     delete d;
0334 }
0335 
0336 ContextPrivate::~ContextPrivate()
0337 {
0338     if (m_context) {
0339         pa_context_unref(m_context);
0340         m_context = nullptr;
0341     }
0342 
0343     if (m_mainloop) {
0344         pa_glib_mainloop_free(m_mainloop);
0345         m_mainloop = nullptr;
0346     }
0347 
0348     reset();
0349 }
0350 
0351 Context *Context::instance()
0352 {
0353     static std::unique_ptr<Context> context(new Context);
0354     return context.get();
0355 }
0356 
0357 void ContextPrivate::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index)
0358 {
0359     Q_ASSERT(context == m_context);
0360 
0361     switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
0362     case PA_SUBSCRIPTION_EVENT_SINK:
0363         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0364             m_sinks.removeEntry(index);
0365         } else {
0366             if (!PAOperation(pa_context_get_sink_info_by_index(context, index, sink_cb, this))) {
0367                 qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_info_by_index() failed";
0368                 return;
0369             }
0370         }
0371         break;
0372 
0373     case PA_SUBSCRIPTION_EVENT_SOURCE:
0374         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0375             m_sources.removeEntry(index);
0376         } else {
0377             if (!PAOperation(pa_context_get_source_info_by_index(context, index, source_cb, this))) {
0378                 qCWarning(PULSEAUDIOQT) << "pa_context_get_source_info_by_index() failed";
0379                 return;
0380             }
0381         }
0382         break;
0383 
0384     case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
0385         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0386             m_sinkInputs.removeEntry(index);
0387         } else {
0388             if (!PAOperation(pa_context_get_sink_input_info(context, index, sink_input_callback, this))) {
0389                 qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_input_info() failed";
0390                 return;
0391             }
0392         }
0393         break;
0394 
0395     case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
0396         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0397             m_sourceOutputs.removeEntry(index);
0398         } else {
0399             if (!PAOperation(pa_context_get_source_output_info(context, index, source_output_cb, this))) {
0400                 qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_input_info() failed";
0401                 return;
0402             }
0403         }
0404         break;
0405 
0406     case PA_SUBSCRIPTION_EVENT_CLIENT:
0407         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0408             m_clients.removeEntry(index);
0409         } else {
0410             if (!PAOperation(pa_context_get_client_info(context, index, client_cb, this))) {
0411                 qCWarning(PULSEAUDIOQT) << "pa_context_get_client_info() failed";
0412                 return;
0413             }
0414         }
0415         break;
0416 
0417     case PA_SUBSCRIPTION_EVENT_CARD:
0418         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0419             m_cards.removeEntry(index);
0420         } else {
0421             if (!PAOperation(pa_context_get_card_info_by_index(context, index, card_cb, this))) {
0422                 qCWarning(PULSEAUDIOQT) << "pa_context_get_card_info_by_index() failed";
0423                 return;
0424             }
0425         }
0426         break;
0427 
0428     case PA_SUBSCRIPTION_EVENT_MODULE:
0429         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0430             m_modules.removeEntry(index);
0431         } else {
0432             if (!PAOperation(pa_context_get_module_info_list(context, module_info_list_cb, this))) {
0433                 qCWarning(PULSEAUDIOQT) << "pa_context_get_module_info_list() failed";
0434                 return;
0435             }
0436         }
0437         break;
0438 
0439     case PA_SUBSCRIPTION_EVENT_SERVER:
0440         if (!PAOperation(pa_context_get_server_info(context, server_cb, this))) {
0441             qCWarning(PULSEAUDIOQT) << "pa_context_get_server_info() failed";
0442             return;
0443         }
0444         break;
0445     }
0446 }
0447 
0448 void ContextPrivate::contextStateCallback(pa_context *c)
0449 {
0450     qCDebug(PULSEAUDIOQT) << "state callback";
0451     pa_context_state_t state = pa_context_get_state(c);
0452     if (state == PA_CONTEXT_READY) {
0453         qCDebug(PULSEAUDIOQT) << "ready, stopping connect timer";
0454         m_connectTimer.stop();
0455 
0456         // 1. Register for the stream changes (except during probe)
0457         if (m_context == c) {
0458             pa_context_set_subscribe_callback(c, subscribe_cb, this);
0459 
0460             if (!PAOperation(
0461                     pa_context_subscribe(c,
0462                                          (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_CLIENT
0463                                                                   | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT
0464                                                                   | PA_SUBSCRIPTION_MASK_CARD | PA_SUBSCRIPTION_MASK_MODULE | PA_SUBSCRIPTION_MASK_SERVER),
0465                                          nullptr,
0466                                          nullptr))) {
0467                 qCWarning(PULSEAUDIOQT) << "pa_context_subscribe() failed";
0468                 return;
0469             }
0470         }
0471 
0472         if (!PAOperation(pa_context_get_sink_info_list(c, sink_cb, this))) {
0473             qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_info_list() failed";
0474             return;
0475         }
0476 
0477         if (!PAOperation(pa_context_get_source_info_list(c, source_cb, this))) {
0478             qCWarning(PULSEAUDIOQT) << "pa_context_get_source_info_list() failed";
0479             return;
0480         }
0481 
0482         if (!PAOperation(pa_context_get_client_info_list(c, client_cb, this))) {
0483             qCWarning(PULSEAUDIOQT) << "pa_context_client_info_list() failed";
0484             return;
0485         }
0486 
0487         if (!PAOperation(pa_context_get_card_info_list(c, card_cb, this))) {
0488             qCWarning(PULSEAUDIOQT) << "pa_context_get_card_info_list() failed";
0489             return;
0490         }
0491 
0492         if (!PAOperation(pa_context_get_sink_input_info_list(c, sink_input_callback, this))) {
0493             qCWarning(PULSEAUDIOQT) << "pa_context_get_sink_input_info_list() failed";
0494             return;
0495         }
0496 
0497         if (!PAOperation(pa_context_get_source_output_info_list(c, source_output_cb, this))) {
0498             qCWarning(PULSEAUDIOQT) << "pa_context_get_source_output_info_list() failed";
0499             return;
0500         }
0501 
0502         if (!PAOperation(pa_context_get_module_info_list(c, module_info_list_cb, this))) {
0503             qCWarning(PULSEAUDIOQT) << "pa_context_get_module_info_list() failed";
0504             return;
0505         }
0506 
0507         if (!PAOperation(pa_context_get_server_info(c, server_cb, this))) {
0508             qCWarning(PULSEAUDIOQT) << "pa_context_get_server_info() failed";
0509             return;
0510         }
0511 
0512         if (PAOperation(pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, this))) {
0513             pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, this);
0514             PAOperation(pa_ext_stream_restore_subscribe(c, 1, nullptr, this));
0515         } else {
0516             qCWarning(PULSEAUDIOQT) << "Failed to initialize stream_restore extension";
0517         }
0518     } else if (!PA_CONTEXT_IS_GOOD(state)) {
0519         qCWarning(PULSEAUDIOQT) << "context kaput";
0520         if (m_context) {
0521             pa_context_unref(m_context);
0522             m_context = nullptr;
0523         }
0524         reset();
0525         qCDebug(PULSEAUDIOQT) << "Starting connect timer";
0526         m_connectTimer.start(std::chrono::seconds(5));
0527     }
0528 }
0529 
0530 void ContextPrivate::sinkCallback(const pa_sink_info *info)
0531 {
0532     // This parenting here is a bit weird
0533     m_sinks.updateEntry(info, q);
0534 }
0535 
0536 void ContextPrivate::sinkInputCallback(const pa_sink_input_info *info)
0537 {
0538     m_sinkInputs.updateEntry(info, q);
0539 }
0540 
0541 void ContextPrivate::sourceCallback(const pa_source_info *info)
0542 {
0543     m_sources.updateEntry(info, q);
0544 }
0545 
0546 void ContextPrivate::sourceOutputCallback(const pa_source_output_info *info)
0547 {
0548     m_sourceOutputs.updateEntry(info, q);
0549 }
0550 
0551 void ContextPrivate::clientCallback(const pa_client_info *info)
0552 {
0553     m_clients.updateEntry(info, q);
0554 }
0555 
0556 void ContextPrivate::cardCallback(const pa_card_info *info)
0557 {
0558     m_cards.updateEntry(info, q);
0559 }
0560 
0561 void ContextPrivate::moduleCallback(const pa_module_info *info)
0562 {
0563     m_modules.updateEntry(info, q);
0564 }
0565 
0566 void ContextPrivate::streamRestoreCallback(const pa_ext_stream_restore_info *info)
0567 {
0568     if (qstrcmp(info->name, "sink-input-by-media-role:event") != 0) {
0569         return;
0570     }
0571 
0572     const int eventRoleIndex = 1;
0573     StreamRestore *obj = qobject_cast<StreamRestore *>(m_streamRestores.data().value(eventRoleIndex));
0574 
0575     if (!obj) {
0576         QVariantMap props;
0577         props.insert(QStringLiteral("application.icon_name"), QStringLiteral("preferences-desktop-notification"));
0578         obj = new StreamRestore(eventRoleIndex, props, q);
0579         obj->d->update(info);
0580         m_streamRestores.insert(obj);
0581     } else {
0582         obj->d->update(info);
0583     }
0584 }
0585 
0586 void ContextPrivate::serverCallback(const pa_server_info *info)
0587 {
0588     m_server->d->update(info);
0589 }
0590 
0591 void Context::setCardProfile(quint32 index, const QString &profile)
0592 {
0593     if (!d->m_context) {
0594         return;
0595     }
0596     qCDebug(PULSEAUDIOQT) << index << profile;
0597     if (!PAOperation(pa_context_set_card_profile_by_index(d->m_context, index, profile.toUtf8().constData(), nullptr, nullptr))) {
0598         qCWarning(PULSEAUDIOQT) << "pa_context_set_card_profile_by_index failed";
0599         return;
0600     }
0601 }
0602 
0603 void Context::setDefaultSink(const QString &name)
0604 {
0605     if (!d->m_context) {
0606         return;
0607     }
0608     const QByteArray nameData = name.toUtf8();
0609     if (!PAOperation(pa_context_set_default_sink(d->m_context, nameData.constData(), nullptr, nullptr))) {
0610         qCWarning(PULSEAUDIOQT) << "pa_context_set_default_sink failed";
0611     }
0612 
0613     // Change device for all entries in stream-restore database
0614     d->m_newDefaultSink = name;
0615     if (!PAOperation(pa_ext_stream_restore_read(d->m_context, ext_stream_restore_change_sink_cb, d))) {
0616         qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_read failed";
0617     }
0618 }
0619 
0620 void Context::setDefaultSource(const QString &name)
0621 {
0622     if (!d->m_context) {
0623         return;
0624     }
0625     const QByteArray nameData = name.toUtf8();
0626     if (!PAOperation(pa_context_set_default_source(d->m_context, nameData.constData(), nullptr, nullptr))) {
0627         qCWarning(PULSEAUDIOQT) << "pa_context_set_default_source failed";
0628     }
0629 
0630     // Change device for all entries in stream-restore database
0631     d->m_newDefaultSource = name;
0632     if (!PAOperation(pa_ext_stream_restore_read(d->m_context, ext_stream_restore_change_source_cb, d))) {
0633         qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_read failed";
0634     }
0635 }
0636 
0637 void ContextPrivate::streamRestoreWrite(const pa_ext_stream_restore_info *info)
0638 {
0639     if (!m_context) {
0640         return;
0641     }
0642     if (!PAOperation(pa_ext_stream_restore_write(m_context, PA_UPDATE_REPLACE, info, 1, true, nullptr, nullptr))) {
0643         qCWarning(PULSEAUDIOQT) << "pa_ext_stream_restore_write failed";
0644     }
0645 }
0646 
0647 void ContextPrivate::connectToDaemon()
0648 {
0649     if (m_context) {
0650         return;
0651     }
0652 
0653     // We require a glib event loop
0654     if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("Glib")) {
0655         qCWarning(PULSEAUDIOQT) << "Disabling PulseAudio integration for lack of GLib event loop";
0656         return;
0657     }
0658 
0659     qCDebug(PULSEAUDIOQT) << "Attempting connection to PulseAudio sound daemon";
0660     if (!m_mainloop) {
0661         m_mainloop = pa_glib_mainloop_new(nullptr);
0662         Q_ASSERT(m_mainloop);
0663     }
0664 
0665     pa_mainloop_api *api = pa_glib_mainloop_get_api(m_mainloop);
0666     Q_ASSERT(api);
0667 
0668     pa_proplist *proplist = pa_proplist_new();
0669     pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, QGuiApplication::applicationDisplayName().toUtf8().constData());
0670     if (!s_applicationId.isEmpty()) {
0671         pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, s_applicationId.toUtf8().constData());
0672     } else {
0673         pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, QGuiApplication::desktopFileName().toUtf8().constData());
0674     }
0675     pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, QGuiApplication::windowIcon().name().toUtf8().constData());
0676     m_context = pa_context_new_with_proplist(api, nullptr, proplist);
0677     pa_proplist_free(proplist);
0678     Q_ASSERT(m_context);
0679 
0680     if (pa_context_connect(m_context, NULL, PA_CONTEXT_NOFAIL, nullptr) < 0) {
0681         pa_context_unref(m_context);
0682         pa_glib_mainloop_free(m_mainloop);
0683         m_context = nullptr;
0684         m_mainloop = nullptr;
0685         return;
0686     }
0687     pa_context_set_state_callback(m_context, &context_state_callback, this);
0688 }
0689 
0690 void ContextPrivate::checkConnectTries()
0691 {
0692     if (++m_connectTries == 5) {
0693         qCWarning(PULSEAUDIOQT) << "Giving up after" << m_connectTries << "tries to connect";
0694         m_connectTimer.stop();
0695     }
0696 }
0697 
0698 void ContextPrivate::reset()
0699 {
0700     m_sinks.reset();
0701     m_sinkInputs.reset();
0702     m_sources.reset();
0703     m_sourceOutputs.reset();
0704     m_clients.reset();
0705     m_cards.reset();
0706     m_modules.reset();
0707     m_streamRestores.reset();
0708     m_server->reset();
0709     m_connectTries = 0;
0710 }
0711 
0712 bool Context::isValid()
0713 {
0714     return d->m_context && d->m_mainloop;
0715 }
0716 
0717 QVector<Sink *> Context::sinks() const
0718 {
0719     return d->m_sinks.data();
0720 }
0721 
0722 QVector<SinkInput *> Context::sinkInputs() const
0723 {
0724     return d->m_sinkInputs.data();
0725 }
0726 
0727 QVector<Source *> Context::sources() const
0728 {
0729     return d->m_sources.data();
0730 }
0731 
0732 QVector<SourceOutput *> Context::sourceOutputs() const
0733 {
0734     return d->m_sourceOutputs.data();
0735 }
0736 
0737 QVector<Client *> Context::clients() const
0738 {
0739     return d->m_clients.data();
0740 }
0741 
0742 QVector<Card *> Context::cards() const
0743 {
0744     return d->m_cards.data();
0745 }
0746 
0747 QVector<Module *> Context::modules() const
0748 {
0749     return d->m_modules.data();
0750 }
0751 
0752 QVector<StreamRestore *> Context::streamRestores() const
0753 {
0754     return d->m_streamRestores.data();
0755 }
0756 
0757 Server *Context::server() const
0758 {
0759     return d->m_server;
0760 }
0761 
0762 void ContextPrivate::setGenericVolume(
0763     quint32 index,
0764     int channel,
0765     qint64 newVolume,
0766     pa_cvolume cVolume,
0767     const std::function<pa_operation *(pa_context *, uint32_t, const pa_cvolume *, pa_context_success_cb_t, void *)> &pa_set_volume)
0768 {
0769     if (!m_context) {
0770         return;
0771     }
0772     newVolume = qBound<qint64>(0, newVolume, PA_VOLUME_MAX);
0773     pa_cvolume newCVolume = cVolume;
0774     if (channel == -1) { // -1 all channels
0775         const qint64 diff = newVolume - pa_cvolume_max(&cVolume);
0776         for (int i = 0; i < newCVolume.channels; ++i) {
0777             newCVolume.values[i] = qBound<qint64>(0, newCVolume.values[i] + diff, PA_VOLUME_MAX);
0778         }
0779     } else {
0780         Q_ASSERT(newCVolume.channels > channel);
0781         newCVolume.values[channel] = newVolume;
0782     }
0783     if (!pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr)) {
0784         qCWarning(PULSEAUDIOQT) << "pa_set_volume failed";
0785         return;
0786     }
0787 }
0788 
0789 void ContextPrivate::setGenericMute(quint32 index,
0790                                     bool mute,
0791                                     const std::function<pa_operation *(pa_context *, uint32_t, int, pa_context_success_cb_t, void *)> &pa_set_mute)
0792 {
0793     if (!m_context) {
0794         return;
0795     }
0796     if (!PAOperation(pa_set_mute(m_context, index, mute, nullptr, nullptr))) {
0797         qCWarning(PULSEAUDIOQT) << "pa_set_mute failed";
0798         return;
0799     }
0800 }
0801 
0802 void ContextPrivate::setGenericPort(quint32 index,
0803                                     const QString &portName,
0804                                     const std::function<pa_operation *(pa_context *, uint32_t, const char *, pa_context_success_cb_t, void *)> &pa_set_port)
0805 {
0806     if (!m_context) {
0807         return;
0808     }
0809     if (!PAOperation(pa_set_port(m_context, index, portName.toUtf8().constData(), nullptr, nullptr))) {
0810         qCWarning(PULSEAUDIOQT) << "pa_set_port failed";
0811         return;
0812     }
0813 }
0814 
0815 void ContextPrivate::setGenericDeviceForStream(
0816     quint32 streamIndex,
0817     quint32 deviceIndex,
0818     const std::function<pa_operation *(pa_context *, uint32_t, uint32_t, pa_context_success_cb_t, void *)> &pa_move_stream_to_device)
0819 {
0820     if (!m_context) {
0821         return;
0822     }
0823     if (!PAOperation(pa_move_stream_to_device(m_context, streamIndex, deviceIndex, nullptr, nullptr))) {
0824         qCWarning(PULSEAUDIOQT) << "pa_move_stream_to_device failed";
0825         return;
0826     }
0827 }
0828 
0829 void ContextPrivate::setGenericVolumes(
0830     quint32 index,
0831     QVector<qint64> channelVolumes,
0832     pa_cvolume cVolume,
0833     const std::function<pa_operation *(pa_context *, uint32_t, const pa_cvolume *, pa_context_success_cb_t, void *)> &pa_set_volume)
0834 {
0835     if (!m_context) {
0836         return;
0837     }
0838     Q_ASSERT(channelVolumes.count() == cVolume.channels);
0839 
0840     pa_cvolume newCVolume = cVolume;
0841     for (int i = 0; i < channelVolumes.count(); ++i) {
0842         newCVolume.values[i] = qBound<qint64>(0, channelVolumes.at(i), PA_VOLUME_MAX);
0843     }
0844 
0845     if (!PAOperation(pa_set_volume(m_context, index, &newCVolume, nullptr, nullptr))) {
0846         qCWarning(PULSEAUDIOQT) << "pa_set_volume failed";
0847         return;
0848     }
0849 }
0850 
0851 void Context::setApplicationId(const QString &applicationId)
0852 {
0853     ContextPrivate::s_applicationId = applicationId;
0854 }
0855 
0856 pa_context *Context::context() const
0857 {
0858     return d->m_context;
0859 }
0860 
0861 } // PulseAudioQt