File indexing completed on 2024-10-06 10:58:34
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