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