File indexing completed on 2024-09-15 04:15:52
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