File indexing completed on 2024-04-21 13:11:09

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 <QMutexLocker>
0016 #include <QTimer>
0017 
0018 #include "card.h"
0019 #include "client.h"
0020 #include "module.h"
0021 #include "sink.h"
0022 #include "sinkinput.h"
0023 #include "source.h"
0024 #include "sourceoutput.h"
0025 #include "streamrestore.h"
0026 
0027 #include <KLocalizedString>
0028 
0029 namespace QPulseAudio
0030 {
0031 Context *Context::s_context = nullptr;
0032 QString Context::s_applicationId;
0033 
0034 const qint64 Context::NormalVolume = PA_VOLUME_NORM;
0035 const qint64 Context::MinimalVolume = 0;
0036 const qint64 Context::MaximalVolume = (PA_VOLUME_NORM / 100.0) * 150;
0037 
0038 static bool isGoodState(int eol)
0039 {
0040     if (eol < 0) {
0041         // Error
0042         return false;
0043     }
0044 
0045     if (eol > 0) {
0046         // End of callback chain
0047         return false;
0048     }
0049 
0050     return true;
0051 }
0052 
0053 // --------------------------
0054 
0055 static void sink_cb(pa_context *context, const pa_sink_info *info, int eol, void *data)
0056 {
0057     if (!isGoodState(eol)) {
0058         return;
0059     }
0060     Q_ASSERT(context);
0061     Q_ASSERT(data);
0062     ((Context *)data)->sinkCallback(info);
0063 }
0064 
0065 static void sink_input_callback(pa_context *context, const pa_sink_input_info *info, int eol, void *data)
0066 {
0067     if (!isGoodState(eol)) {
0068         return;
0069     }
0070     // pulsesink probe is used by gst-pulse only to query sink formats (not for playback)
0071     if (qstrcmp(info->name, "pulsesink probe") == 0) {
0072         return;
0073     }
0074     if (const char *id = pa_proplist_gets(info->proplist, "module-stream-restore.id")) {
0075         if (qstrcmp(id, "sink-input-by-media-role:event") == 0) {
0076             qCDebug(PLASMAPA) << "Ignoring event role sink input.";
0077             return;
0078         }
0079     }
0080     Q_ASSERT(context);
0081     Q_ASSERT(data);
0082     ((Context *)data)->sinkInputCallback(info);
0083 }
0084 
0085 static void source_cb(pa_context *context, const pa_source_info *info, int eol, void *data)
0086 {
0087     if (!isGoodState(eol)) {
0088         return;
0089     }
0090     // FIXME: This forces excluding monitors
0091     if (info->monitor_of_sink != PA_INVALID_INDEX) {
0092         return;
0093     }
0094     Q_ASSERT(context);
0095     Q_ASSERT(data);
0096     ((Context *)data)->sourceCallback(info);
0097 }
0098 
0099 static void source_output_cb(pa_context *context, const pa_source_output_info *info, int eol, void *data)
0100 {
0101     if (!isGoodState(eol)) {
0102         return;
0103     }
0104     // FIXME: This forces excluding these apps
0105     if (const char *app = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_ID)) {
0106         if (strcmp(app, "org.PulseAudio.pavucontrol") == 0 //
0107             || strcmp(app, "org.gnome.VolumeControl") == 0 //
0108             || strcmp(app, "org.kde.kmixd") == 0 //
0109             || strcmp(app, "org.kde.plasma-pa") == 0) {
0110             return;
0111         }
0112     }
0113     Q_ASSERT(context);
0114     Q_ASSERT(data);
0115     ((Context *)data)->sourceOutputCallback(info);
0116 }
0117 
0118 static void client_cb(pa_context *context, const pa_client_info *info, int eol, void *data)
0119 {
0120     if (!isGoodState(eol)) {
0121         return;
0122     }
0123     Q_ASSERT(context);
0124     Q_ASSERT(data);
0125     ((Context *)data)->clientCallback(info);
0126 }
0127 
0128 static void card_cb(pa_context *context, const pa_card_info *info, int eol, void *data)
0129 {
0130     if (!isGoodState(eol)) {
0131         return;
0132     }
0133     Q_ASSERT(context);
0134     Q_ASSERT(data);
0135     ((Context *)data)->cardCallback(info);
0136 }
0137 
0138 static void module_info_list_cb(pa_context *context, const pa_module_info *info, int eol, void *data)
0139 {
0140     if (!isGoodState(eol)) {
0141         return;
0142     }
0143     Q_ASSERT(context);
0144     Q_ASSERT(data);
0145     ((Context *)data)->moduleCallback(info);
0146 }
0147 
0148 static void server_cb(pa_context *context, const pa_server_info *info, void *data)
0149 {
0150     Q_ASSERT(context);
0151     Q_ASSERT(data);
0152     if (!info) {
0153         // info may be nullptr when e.g. the server doesn't reply in time (e.g. it is stuck)
0154         // https://bugs.kde.org/show_bug.cgi?id=454647
0155         qCWarning(PLASMAPA) << "server_cb() called without info!";
0156         return;
0157     }
0158     ((Context *)data)->serverCallback(info);
0159 }
0160 
0161 static void context_state_callback(pa_context *context, void *data)
0162 {
0163     Q_ASSERT(data);
0164     ((Context *)data)->contextStateCallback(context);
0165 }
0166 
0167 static void subscribe_cb(pa_context *context, pa_subscription_event_type_t type, uint32_t index, void *data)
0168 {
0169     Q_ASSERT(data);
0170     ((Context *)data)->subscribeCallback(context, type, index);
0171 }
0172 
0173 static void ext_stream_restore_read_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
0174 {
0175     if (!isGoodState(eol)) {
0176         return;
0177     }
0178     Q_ASSERT(context);
0179     Q_ASSERT(data);
0180     ((Context *)data)->streamRestoreCallback(info);
0181 }
0182 
0183 static void ext_stream_restore_subscribe_cb(pa_context *context, void *data)
0184 {
0185     Q_ASSERT(context);
0186     Q_ASSERT(data);
0187     if (!PAOperation(pa_ext_stream_restore_read(context, ext_stream_restore_read_cb, data))) {
0188         qCWarning(PLASMAPA) << "pa_ext_stream_restore_read() failed";
0189     }
0190 }
0191 
0192 static void ext_stream_restore_change_sink_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
0193 {
0194     if (!isGoodState(eol)) {
0195         return;
0196     }
0197     Q_ASSERT(context);
0198     Q_ASSERT(data);
0199     if (qstrncmp(info->name, "sink-input-by", 13) == 0) {
0200         auto *context = static_cast<Context *>(data);
0201         const QByteArray deviceData = context->newDefaultSink().toUtf8();
0202         pa_ext_stream_restore_info newinfo;
0203         newinfo.name = info->name;
0204         newinfo.channel_map = info->channel_map;
0205         newinfo.volume = info->volume;
0206         newinfo.mute = info->mute;
0207         newinfo.device = deviceData.constData();
0208         context->streamRestoreWrite(&newinfo);
0209     }
0210 }
0211 
0212 static void ext_stream_restore_change_source_cb(pa_context *context, const pa_ext_stream_restore_info *info, int eol, void *data)
0213 {
0214     if (!isGoodState(eol)) {
0215         return;
0216     }
0217     Q_ASSERT(context);
0218     Q_ASSERT(data);
0219     if (qstrncmp(info->name, "source-output-by", 16) == 0) {
0220         auto *context = static_cast<Context *>(data);
0221         const QByteArray deviceData = context->newDefaultSource().toUtf8();
0222         pa_ext_stream_restore_info newinfo;
0223         newinfo.name = info->name;
0224         newinfo.channel_map = info->channel_map;
0225         newinfo.volume = info->volume;
0226         newinfo.mute = info->mute;
0227         newinfo.device = deviceData.constData();
0228         context->streamRestoreWrite(&newinfo);
0229     }
0230 }
0231 
0232 // --------------------------
0233 
0234 Context::Context(QObject *parent)
0235     : QObject(parent)
0236     , m_server(new Server(this))
0237     , m_context(nullptr)
0238     , m_mainloop(nullptr)
0239     , m_references(0)
0240 {
0241     auto *watcher = new QDBusServiceWatcher(QStringLiteral("org.pulseaudio.Server"), //
0242                                             QDBusConnection::sessionBus(),
0243                                             QDBusServiceWatcher::WatchForRegistration,
0244                                             this);
0245     connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &Context::connectToDaemon);
0246     connectToDaemon();
0247 }
0248 
0249 Context::~Context()
0250 {
0251     if (m_context) {
0252         pa_context_unref(m_context);
0253         m_context = nullptr;
0254     }
0255 
0256     if (m_mainloop) {
0257         pa_glib_mainloop_free(m_mainloop);
0258         m_mainloop = nullptr;
0259     }
0260 
0261     reset();
0262 }
0263 
0264 Context *Context::instance()
0265 {
0266     if (!s_context) {
0267         s_context = new Context;
0268     }
0269     return s_context;
0270 }
0271 
0272 void Context::ref()
0273 {
0274     ++m_references;
0275 }
0276 
0277 void Context::unref()
0278 {
0279     if (--m_references == 0) {
0280         delete this;
0281         s_context = nullptr;
0282     }
0283 }
0284 
0285 void Context::subscribeCallback(pa_context *context, pa_subscription_event_type_t type, uint32_t index)
0286 {
0287     Q_ASSERT(context == m_context);
0288 
0289     switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
0290     case PA_SUBSCRIPTION_EVENT_SINK:
0291         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0292             m_sinks.removeEntry(index);
0293         } else {
0294             if (!PAOperation(pa_context_get_sink_info_by_index(context, index, sink_cb, this))) {
0295                 qCWarning(PLASMAPA) << "pa_context_get_sink_info_by_index() failed";
0296                 return;
0297             }
0298         }
0299         break;
0300 
0301     case PA_SUBSCRIPTION_EVENT_SOURCE:
0302         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0303             m_sources.removeEntry(index);
0304         } else {
0305             if (!PAOperation(pa_context_get_source_info_by_index(context, index, source_cb, this))) {
0306                 qCWarning(PLASMAPA) << "pa_context_get_source_info_by_index() failed";
0307                 return;
0308             }
0309         }
0310         break;
0311 
0312     case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
0313         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0314             m_sinkInputs.removeEntry(index);
0315         } else {
0316             if (!PAOperation(pa_context_get_sink_input_info(context, index, sink_input_callback, this))) {
0317                 qCWarning(PLASMAPA) << "pa_context_get_sink_input_info() failed";
0318                 return;
0319             }
0320         }
0321         break;
0322 
0323     case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
0324         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0325             m_sourceOutputs.removeEntry(index);
0326         } else {
0327             if (!PAOperation(pa_context_get_source_output_info(context, index, source_output_cb, this))) {
0328                 qCWarning(PLASMAPA) << "pa_context_get_sink_input_info() failed";
0329                 return;
0330             }
0331         }
0332         break;
0333 
0334     case PA_SUBSCRIPTION_EVENT_CLIENT:
0335         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0336             m_clients.removeEntry(index);
0337         } else {
0338             if (!PAOperation(pa_context_get_client_info(context, index, client_cb, this))) {
0339                 qCWarning(PLASMAPA) << "pa_context_get_client_info() failed";
0340                 return;
0341             }
0342         }
0343         break;
0344 
0345     case PA_SUBSCRIPTION_EVENT_CARD:
0346         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0347             m_cards.removeEntry(index);
0348         } else {
0349             if (!PAOperation(pa_context_get_card_info_by_index(context, index, card_cb, this))) {
0350                 qCWarning(PLASMAPA) << "pa_context_get_card_info_by_index() failed";
0351                 return;
0352             }
0353         }
0354         break;
0355 
0356     case PA_SUBSCRIPTION_EVENT_MODULE:
0357         if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0358             m_modules.removeEntry(index);
0359         } else {
0360             if (!PAOperation(pa_context_get_module_info_list(context, module_info_list_cb, this))) {
0361                 qCWarning(PLASMAPA) << "pa_context_get_module_info_list() failed";
0362                 return;
0363             }
0364         }
0365         break;
0366 
0367     case PA_SUBSCRIPTION_EVENT_SERVER:
0368         if (!PAOperation(pa_context_get_server_info(context, server_cb, this))) {
0369             qCWarning(PLASMAPA) << "pa_context_get_server_info() failed";
0370             return;
0371         }
0372         break;
0373     }
0374 }
0375 
0376 void Context::contextStateCallback(pa_context *c)
0377 {
0378     qCDebug(PLASMAPA) << "state callback";
0379     pa_context_state_t state = pa_context_get_state(c);
0380     if (state == PA_CONTEXT_READY) {
0381         qCDebug(PLASMAPA) << "ready";
0382 
0383         // 1. Register for the stream changes (except during probe)
0384         if (m_context == c) {
0385             pa_context_set_subscribe_callback(c, subscribe_cb, this);
0386 
0387             if (!PAOperation(
0388                     pa_context_subscribe(c,
0389                                          (pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_CLIENT
0390                                                                   | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT
0391                                                                   | PA_SUBSCRIPTION_MASK_CARD | PA_SUBSCRIPTION_MASK_MODULE | PA_SUBSCRIPTION_MASK_SERVER),
0392                                          nullptr,
0393                                          nullptr))) {
0394                 qCWarning(PLASMAPA) << "pa_context_subscribe() failed";
0395                 return;
0396             }
0397         }
0398 
0399         if (!PAOperation(pa_context_get_sink_info_list(c, sink_cb, this))) {
0400             qCWarning(PLASMAPA) << "pa_context_get_sink_info_list() failed";
0401             return;
0402         }
0403 
0404         if (!PAOperation(pa_context_get_source_info_list(c, source_cb, this))) {
0405             qCWarning(PLASMAPA) << "pa_context_get_source_info_list() failed";
0406             return;
0407         }
0408 
0409         if (!PAOperation(pa_context_get_client_info_list(c, client_cb, this))) {
0410             qCWarning(PLASMAPA) << "pa_context_client_info_list() failed";
0411             return;
0412         }
0413 
0414         if (!PAOperation(pa_context_get_card_info_list(c, card_cb, this))) {
0415             qCWarning(PLASMAPA) << "pa_context_get_card_info_list() failed";
0416             return;
0417         }
0418 
0419         if (!PAOperation(pa_context_get_sink_input_info_list(c, sink_input_callback, this))) {
0420             qCWarning(PLASMAPA) << "pa_context_get_sink_input_info_list() failed";
0421             return;
0422         }
0423 
0424         if (!PAOperation(pa_context_get_source_output_info_list(c, source_output_cb, this))) {
0425             qCWarning(PLASMAPA) << "pa_context_get_source_output_info_list() failed";
0426             return;
0427         }
0428 
0429         if (!PAOperation(pa_context_get_module_info_list(c, module_info_list_cb, this))) {
0430             qCWarning(PLASMAPA) << "pa_context_get_module_info_list() failed";
0431             return;
0432         }
0433 
0434         if (!PAOperation(pa_context_get_server_info(c, server_cb, this))) {
0435             qCWarning(PLASMAPA) << "pa_context_get_server_info() failed";
0436             return;
0437         }
0438 
0439         if (PAOperation(pa_ext_stream_restore_read(c, ext_stream_restore_read_cb, this))) {
0440             pa_ext_stream_restore_set_subscribe_cb(c, ext_stream_restore_subscribe_cb, this);
0441             PAOperation(pa_ext_stream_restore_subscribe(c, 1, nullptr, this));
0442         } else {
0443             qCWarning(PLASMAPA) << "Failed to initialize stream_restore extension";
0444         }
0445     } else if (!PA_CONTEXT_IS_GOOD(state)) {
0446         qCWarning(PLASMAPA) << "context kaput";
0447         if (m_context) {
0448             pa_context_unref(m_context);
0449             m_context = nullptr;
0450         }
0451         reset();
0452         QTimer::singleShot(1000, this, &Context::connectToDaemon);
0453     }
0454 }
0455 
0456 void Context::sinkCallback(const pa_sink_info *info)
0457 {
0458     // This parenting here is a bit weird
0459     m_sinks.updateEntry(info, this);
0460 }
0461 
0462 void Context::sinkInputCallback(const pa_sink_input_info *info)
0463 {
0464     m_sinkInputs.updateEntry(info, this);
0465 }
0466 
0467 void Context::sourceCallback(const pa_source_info *info)
0468 {
0469     m_sources.updateEntry(info, this);
0470 }
0471 
0472 void Context::sourceOutputCallback(const pa_source_output_info *info)
0473 {
0474     m_sourceOutputs.updateEntry(info, this);
0475 }
0476 
0477 void Context::clientCallback(const pa_client_info *info)
0478 {
0479     m_clients.updateEntry(info, this);
0480 }
0481 
0482 void Context::cardCallback(const pa_card_info *info)
0483 {
0484     m_cards.updateEntry(info, this);
0485 }
0486 
0487 void Context::moduleCallback(const pa_module_info *info)
0488 {
0489     m_modules.updateEntry(info, this);
0490 }
0491 
0492 void Context::streamRestoreCallback(const pa_ext_stream_restore_info *info)
0493 {
0494     if (qstrcmp(info->name, "sink-input-by-media-role:event") != 0) {
0495         return;
0496     }
0497 
0498     const int eventRoleIndex = 1;
0499     auto *obj = qobject_cast<StreamRestore *>(m_streamRestores.data().value(eventRoleIndex));
0500 
0501     if (!obj) {
0502         QVariantMap props;
0503         props.insert(QStringLiteral("application.icon_name"), QStringLiteral("preferences-desktop-notification"));
0504         obj = new StreamRestore(eventRoleIndex, props, this);
0505         obj->update(info);
0506         m_streamRestores.insert(obj);
0507     } else {
0508         obj->update(info);
0509     }
0510 }
0511 
0512 void Context::serverCallback(const pa_server_info *info)
0513 {
0514     m_server->update(info);
0515 }
0516 
0517 void Context::setCardProfile(quint32 index, const QString &profile)
0518 {
0519     if (!m_context) {
0520         return;
0521     }
0522     qCDebug(PLASMAPA) << index << profile;
0523     if (!PAOperation(pa_context_set_card_profile_by_index(m_context, index, profile.toUtf8().constData(), nullptr, nullptr))) {
0524         qCWarning(PLASMAPA) << "pa_context_set_card_profile_by_index failed";
0525         return;
0526     }
0527 }
0528 
0529 void Context::setDefaultSink(const QString &name)
0530 {
0531     if (!m_context) {
0532         return;
0533     }
0534     const QByteArray nameData = name.toUtf8();
0535     if (!PAOperation(pa_context_set_default_sink(m_context, nameData.constData(), nullptr, nullptr))) {
0536         qCWarning(PLASMAPA) << "pa_context_set_default_sink failed";
0537     }
0538 
0539     // Change device for all entries in stream-restore database
0540     m_newDefaultSink = name;
0541     if (!PAOperation(pa_ext_stream_restore_read(m_context, ext_stream_restore_change_sink_cb, this))) {
0542         qCWarning(PLASMAPA) << "pa_ext_stream_restore_read failed";
0543     }
0544 }
0545 
0546 void Context::setDefaultSource(const QString &name)
0547 {
0548     if (!m_context) {
0549         return;
0550     }
0551     const QByteArray nameData = name.toUtf8();
0552     if (!PAOperation(pa_context_set_default_source(m_context, nameData.constData(), nullptr, nullptr))) {
0553         qCWarning(PLASMAPA) << "pa_context_set_default_source failed";
0554     }
0555 
0556     // Change device for all entries in stream-restore database
0557     m_newDefaultSource = name;
0558     if (!PAOperation(pa_ext_stream_restore_read(m_context, ext_stream_restore_change_source_cb, this))) {
0559         qCWarning(PLASMAPA) << "pa_ext_stream_restore_read failed";
0560     }
0561 }
0562 
0563 void Context::streamRestoreWrite(const pa_ext_stream_restore_info *info)
0564 {
0565     if (!m_context) {
0566         return;
0567     }
0568     if (!PAOperation(pa_ext_stream_restore_write(m_context, PA_UPDATE_REPLACE, info, 1, true, nullptr, nullptr))) {
0569         qCWarning(PLASMAPA) << "pa_ext_stream_restore_write failed";
0570     }
0571 }
0572 
0573 void Context::connectToDaemon()
0574 {
0575     if (m_context) {
0576         return;
0577     }
0578 
0579     // We require a glib event loop
0580     if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("EventDispatcherGlib")
0581         && !QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("GlibEventDispatcher")) {
0582         qCWarning(PLASMAPA) << "Disabling PulseAudio integration for lack of GLib event loop";
0583         return;
0584     }
0585 
0586     qCDebug(PLASMAPA) << "Attempting connection to PulseAudio sound daemon";
0587     if (!m_mainloop) {
0588         m_mainloop = pa_glib_mainloop_new(nullptr);
0589         Q_ASSERT(m_mainloop);
0590     }
0591 
0592     pa_mainloop_api *api = pa_glib_mainloop_get_api(m_mainloop);
0593     Q_ASSERT(api);
0594 
0595     pa_proplist *proplist = pa_proplist_new();
0596     pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, i18nc("Name shown in debug pulseaudio tools", "Plasma PA").toUtf8().constData());
0597     if (!s_applicationId.isEmpty()) {
0598         pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, s_applicationId.toUtf8().constData());
0599     } else {
0600         pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, QGuiApplication::desktopFileName().toUtf8().constData());
0601     }
0602     pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card");
0603     m_context = pa_context_new_with_proplist(api, nullptr, proplist);
0604     pa_proplist_free(proplist);
0605     Q_ASSERT(m_context);
0606 
0607     if (pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) {
0608         pa_context_unref(m_context);
0609         pa_glib_mainloop_free(m_mainloop);
0610         m_context = nullptr;
0611         m_mainloop = nullptr;
0612         return;
0613     }
0614     pa_context_set_state_callback(m_context, &context_state_callback, this);
0615 }
0616 
0617 void Context::reset()
0618 {
0619     m_sinks.reset();
0620     m_sinkInputs.reset();
0621     m_sources.reset();
0622     m_sourceOutputs.reset();
0623     m_clients.reset();
0624     m_cards.reset();
0625     m_modules.reset();
0626     m_streamRestores.reset();
0627     m_server->reset();
0628 }
0629 
0630 void Context::setApplicationId(const QString &applicationId)
0631 {
0632     s_applicationId = applicationId;
0633 }
0634 
0635 } // QPulseAudio