23#include "pulsesupport.h"
25#include <QAbstractEventDispatcher>
26#include <QApplication>
34#include "pulsestream_p.h"
35#include <pulse/pulseaudio.h>
36#include <pulse/xmalloc.h>
37#include <pulse/glib-mainloop.h>
39#define HAVE_PULSEAUDIO_DEVICE_MANAGER PA_CHECK_VERSION(0,9,21)
40#if HAVE_PULSEAUDIO_DEVICE_MANAGER
41# include <pulse/ext-device-manager.h>
45#include "phononnamespace_p.h"
46#include "platform_p.h"
48#define PA_PROP_PHONON_STREAMID "phonon.streamid"
54static PulseSupport *s_instance =
nullptr;
55static bool s_wasShutDown =
false;
56static bool s_pulseActive =
false;
66static int debugLevel() {
67 static int level = -1;
70 QByteArray pulseenv = qgetenv(
"PHONON_PULSEAUDIO_DEBUG");
71 int l = pulseenv.
toInt();
73 level = (l > 2 ? 2 : l);
78static void logMessage(
const QString &message,
int priority = 2,
QObject *obj=
nullptr);
79static void logMessage(
const QString &message,
int priority,
QObject *obj)
81 if (debugLevel() > 0) {
86 int nameLength = className.length() - className.lastIndexOf(
':') - 1;
87 className = className.right(nameLength);
90 className.constData(), obj);
95 if (priority <= debugLevel()) {
107 : pulseName(
name), pulseIndex(index)
112 properties[
"available"] = (index != PA_INVALID_INDEX);
115 DeviceAccessList dal;
116 dal.append(DeviceAccess(
"pulse", desc));
121 inline AudioDevice() {}
127bool operator!=(
const AudioDevice &a,
const AudioDevice &b)
129 return !(a.pulseName == b.pulseName && a.properties == b.properties);
147static pa_glib_mainloop *s_mainloop =
nullptr;
148static pa_context *s_context =
nullptr;
152static int s_deviceIndexCounter = 0;
159static const Phonon::CaptureCategory s_audioCapCategories[] = {
160 Phonon::NoCaptureCategory,
161 Phonon::CommunicationCaptureCategory,
162 Phonon::RecordingCaptureCategory,
163 Phonon::ControlCaptureCategory
166static const int s_audioCapCategoriesCount =
sizeof(s_audioCapCategories) /
sizeof(Phonon::CaptureCategory);
176 for (it =
map.begin(); it !=
map.end(); ++it)
177 if ((*it)->index() == index)
182static Phonon::Category pulseRoleToPhononCategory(
const char *role,
bool *success)
189 return Phonon::NoCategory;
191 return Phonon::VideoCategory;
193 return Phonon::MusicCategory;
195 return Phonon::GameCategory;
197 return Phonon::NotificationCategory;
199 return Phonon::CommunicationCategory;
201 return Phonon::AccessibilityCategory;
206 return Phonon::NoCategory;
209static Phonon::CaptureCategory pulseRoleToPhononCaptureCategory(
const char *role,
bool *success)
216 return Phonon::NoCaptureCategory;
218 return Phonon::CommunicationCaptureCategory;
219 if (r ==
"production")
220 return Phonon::RecordingCaptureCategory;
222 return Phonon::ControlCaptureCategory;
225 return Phonon::NoCaptureCategory;
228static const QByteArray phononCategoryToPulseRole(Phonon::Category category)
231 case Phonon::NoCategory:
233 case Phonon::VideoCategory:
235 case Phonon::MusicCategory:
237 case Phonon::GameCategory:
239 case Phonon::NotificationCategory:
241 case Phonon::CommunicationCategory:
243 case Phonon::AccessibilityCategory:
250static const QByteArray phononCaptureCategoryToPulseRole(Phonon::CaptureCategory category)
253 case Phonon::NoCaptureCategory:
255 case Phonon::CommunicationCaptureCategory:
257 case Phonon::RecordingCaptureCategory:
259 case Phonon::ControlCaptureCategory:
266static void createGenericDevices()
270 s_outputDeviceIndexes.
clear();
271 s_outputDevices.
clear();
272 s_outputDevicePriorities.
clear();
273 index = s_deviceIndexCounter++;
276 for (
int i = Phonon::NoCategory; i <= Phonon::LastCategory; ++i) {
277 Phonon::Category cat =
static_cast<Phonon::Category
>(i);
278 s_outputDevicePriorities[cat].
insert(0, index);
281 s_captureDeviceIndexes.
clear();
282 s_captureDevices.
clear();
283 s_captureDevicePriorities.
clear();
284 index = s_deviceIndexCounter++;
287 for (
int i = 0; i < s_audioCapCategoriesCount; ++i) {
288 Phonon::CaptureCategory cat = s_audioCapCategories[i];
289 s_captureDevicePriorities[cat].
insert(0, index);
293#if HAVE_PULSEAUDIO_DEVICE_MANAGER
294static void ext_device_manager_read_cb(pa_context *c,
const pa_ext_device_manager_info *info,
int eol,
void *userdata) {
298 PulseUserData *u =
reinterpret_cast<PulseUserData*
>(userdata);
301 logMessage(
QString::fromLatin1(
"Failed to initialize device manager extension: %1").arg(pa_strerror(pa_context_errno(c))));
302 if (s_context != c) {
303 logMessage(
QLatin1String(
"Falling back to single device mode"));
305 createGenericDevices();
307 pa_context_disconnect(c);
321 bool output_changed =
false;
322 for (newdev_it = u->newOutputDevices.begin(); newdev_it != u->newOutputDevices.end(); ++newdev_it) {
326 Q_ASSERT(s_outputDeviceIndexes.
contains(name));
328 int index = s_outputDeviceIndexes[
name];
329 if (!s_outputDevices.
contains(index)) {
331 output_changed =
true;
332 logMessage(
QString(
"Brand New Output Device Found."));
333 s_outputDevices.
insert(index, *newdev_it);
334 }
else if (s_outputDevices[index] != *newdev_it) {
336 output_changed =
true;
337 logMessage(
QString(
"Change to Existing Output Device (may be Added/Removed or something else)"));
338 s_outputDevices.
remove(index);
339 s_outputDevices.
insert(index, *newdev_it);
344 while (output_existing_it.hasNext()) {
345 output_existing_it.next();
346 if (!u->newOutputDevices.contains(output_existing_it.key())) {
347 output_changed =
true;
348 logMessage(
QString(
"Output Device Completely Removed"));
349 s_outputDevices.
remove(output_existing_it.value());
350 output_existing_it.remove();
355 bool capture_changed =
false;
356 for (newdev_it = u->newCaptureDevices.begin(); newdev_it != u->newCaptureDevices.end(); ++newdev_it) {
360 Q_ASSERT(s_captureDeviceIndexes.
contains(name));
362 int index = s_captureDeviceIndexes[
name];
363 if (!s_captureDevices.
contains(index)) {
365 capture_changed =
true;
366 logMessage(
QString(
"Brand New Capture Device Found."));
367 s_captureDevices.
insert(index, *newdev_it);
368 }
else if (s_captureDevices[index] != *newdev_it) {
370 capture_changed =
true;
371 logMessage(
QString(
"Change to Existing Capture Device (may be Added/Removed or something else)"));
372 s_captureDevices.
remove(index);
373 s_captureDevices.
insert(index, *newdev_it);
378 while (capture_existing_it.hasNext()) {
379 capture_existing_it.next();
380 if (!u->newCaptureDevices.contains(capture_existing_it.key())) {
381 capture_changed =
true;
382 logMessage(
QString(
"Capture Device Completely Removed"));
383 s_captureDevices.
remove(capture_existing_it.value());
384 capture_existing_it.remove();
389 if (s_outputDevicePriorities != u->newOutputDevicePriorities) {
390 output_changed =
true;
391 s_outputDevicePriorities = u->newOutputDevicePriorities;
393 if (s_captureDevicePriorities != u->newCaptureDevicePriorities) {
394 capture_changed =
true;
395 s_captureDevicePriorities = u->newCaptureDevicePriorities;
402 s_instance->emitObjectDescriptionChanged(AudioOutputDeviceType);
404 s_instance->emitObjectDescriptionChanged(AudioCaptureDeviceType);
411 logMessage(
QString(
"Output Device Priority List:"));
412 for (
int i = Phonon::NoCategory; i <= Phonon::LastCategory; ++i) {
413 Phonon::Category cat =
static_cast<Phonon::Category
>(i);
414 if (s_outputDevicePriorities.
contains(cat)) {
415 logMessage(
QString(
" Phonon Category %1").arg(cat));
417 foreach (
int j, s_outputDevicePriorities[cat]) {
419 logMessage(
QString(
" %1. %2 (Available: %3)").arg(++count).arg(props[
"name"].
toString()).arg(props[
"available"].toBool()));
423 logMessage(
QString(
"Capture Device Priority List:"));
424 for (
int i = 0; i < s_audioCapCategoriesCount; ++i) {
425 Phonon::CaptureCategory cat = s_audioCapCategories[i];
426 if (s_captureDevicePriorities.
contains(cat)) {
427 logMessage(
QString(
" Phonon Category %1").arg(cat));
429 foreach (
int j, s_captureDevicePriorities[cat]) {
431 logMessage(
QString(
" %1. %2 (Available: %3)").arg(++count).arg(props[
"name"].
toString()).arg(props[
"available"].toBool()));
439 pa_context_disconnect(c);
446 Q_ASSERT(info->name);
447 Q_ASSERT(info->description);
448 Q_ASSERT(info->icon);
458 bool isSource =
false;
463 new_devices = &u->newOutputDevices;
464 new_prio_map_cats = &u->newOutputDevicePriorities;
466 if (s_outputDeviceIndexes.
contains(name))
467 index = s_outputDeviceIndexes[
name];
469 index = s_outputDeviceIndexes[
name] = s_deviceIndexCounter++;
473 new_devices = &u->newCaptureDevices;
474 new_prio_map_capcats = &u->newCaptureDevicePriorities;
476 if (s_captureDeviceIndexes.
contains(name))
477 index = s_captureDeviceIndexes[
name];
479 index = s_captureDeviceIndexes[
name] = s_deviceIndexCounter++;
485 Q_ASSERT(new_devices);
486 Q_ASSERT(!isSink || new_prio_map_cats);
487 Q_ASSERT(!isSource || new_prio_map_capcats);
493 for (uint32_t i = 0; i < info->n_role_priorities; ++i) {
494 pa_ext_device_manager_role_priority_info* role_prio = &info->role_priorities[i];
495 Q_ASSERT(role_prio->role);
497 bool conversionSuccess;
500 Phonon::Category cat = pulseRoleToPhononCategory(role_prio->role, &conversionSuccess);
501 if (conversionSuccess) {
502 (*new_prio_map_cats)[cat].insert(role_prio->priority, index);
507 Phonon::CaptureCategory capcat = pulseRoleToPhononCaptureCategory(role_prio->role, &conversionSuccess);
508 if (conversionSuccess) {
509 (*new_prio_map_capcats)[capcat].insert(role_prio->priority, index);
515static void ext_device_manager_subscribe_cb(pa_context *c,
void *) {
519 PulseUserData *u =
new PulseUserData;
520 if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) {
525 pa_operation_unref(o);
529static void sink_input_cb(pa_context *c,
const pa_sink_input_info *i,
int eol,
void *userdata) {
534 if (pa_context_errno(c) == PA_ERR_NOENTITY)
548 if ((t = pa_proplist_gets(i->proplist, PA_PROP_PHONON_STREAMID))) {
553 PulseStream *stream = s_outputStreams[
QString(t)];
554 stream->setIndex(i->index);
555 stream->setVolume(&i->volume);
556 stream->setMute(!!i->mute);
559 if (PA_INVALID_INDEX != i->sink) {
561 for (it = s_outputDevices.
begin(); it != s_outputDevices.
end(); ++it) {
562 if ((*it).pulseIndex == i->sink) {
563 stream->setDevice(it.key());
572static void source_output_cb(pa_context *c,
const pa_source_output_info *i,
int eol,
void *userdata) {
577 if (pa_context_errno(c) == PA_ERR_NOENTITY)
591 if ((t = pa_proplist_gets(i->proplist, PA_PROP_PHONON_STREAMID))) {
596 PulseStream *stream = s_captureStreams[
QString(t)];
597 stream->setIndex(i->index);
602 if (PA_INVALID_INDEX != i->source) {
604 for (it = s_captureDevices.
begin(); it != s_captureDevices.
end(); ++it) {
605 if ((*it).pulseIndex == i->source) {
606 stream->setDevice(it.key());
615static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index,
void *userdata) {
618 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
619 case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
620 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
621 PulseStream *stream = findStreamByPulseIndex(s_outputStreams, index);
623 logMessage(
QString::fromLatin1(
"Phonon Output Stream %1 is gone at the PA end. Marking it as invalid in our cache as we may reuse it.").arg(stream->uuid()));
624 stream->setIndex(PA_INVALID_INDEX);
628 if (!(o = pa_context_get_sink_input_info(c, index, sink_input_cb,
nullptr))) {
632 pa_operation_unref(o);
636 case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
637 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
638 PulseStream *stream = findStreamByPulseIndex(s_captureStreams, index);
640 logMessage(
QString::fromLatin1(
"Phonon Capture Stream %1 is gone at the PA end. Marking it as invalid in our cache as we may reuse it.").arg(stream->uuid()));
641 stream->setIndex(PA_INVALID_INDEX);
645 if (!(o = pa_context_get_source_output_info(c, index, source_output_cb,
nullptr))) {
649 pa_operation_unref(o);
656static QString statename(pa_context_state_t state)
660 case PA_CONTEXT_UNCONNECTED:
return QLatin1String(
"Unconnected");
661 case PA_CONTEXT_CONNECTING:
return QLatin1String(
"Connecting");
662 case PA_CONTEXT_AUTHORIZING:
return QLatin1String(
"Authorizing");
663 case PA_CONTEXT_SETTING_NAME:
return QLatin1String(
"Setting Name");
666 case PA_CONTEXT_TERMINATED:
return QLatin1String(
"Terminated");
672static void context_state_callback(pa_context *c,
void *)
676 logMessage(
QString::fromLatin1(
"context_state_callback %1").arg(statename(pa_context_get_state(c))));
677 pa_context_state_t state = pa_context_get_state(c);
678 if (state == PA_CONTEXT_READY) {
680 s_pulseActive =
true;
686 if (s_context == c) {
687 pa_context_set_subscribe_callback(c, subscribe_cb,
nullptr);
689 if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
690 (PA_SUBSCRIPTION_MASK_SINK_INPUT|
691 PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT),
nullptr,
nullptr))) {
695 pa_operation_unref(o);
701 PulseStream *stream = *it;
702 logMessage(
QString::fromLatin1(
"Phonon Output Stream %1 is gone at the PA end. Marking it as invalid in our cache as we may reuse it.").arg(stream->uuid()));
703 stream->setIndex(PA_INVALID_INDEX);
705 if (!(o = pa_context_get_sink_input_info_list(c, sink_input_cb,
nullptr))) {
709 pa_operation_unref(o);
712 PulseStream *stream = *it;
713 logMessage(
QString::fromLatin1(
"Phonon Capture Stream %1 is gone at the PA end. Marking it as invalid in our cache as we may reuse it.").arg(stream->uuid()));
714 stream->setIndex(PA_INVALID_INDEX);
716 if (!(o = pa_context_get_source_output_info_list(c, source_output_cb,
nullptr))) {
720 pa_operation_unref(o);
723#if HAVE_PULSEAUDIO_DEVICE_MANAGER
725 if (s_context == c) {
726 pa_ext_device_manager_set_subscribe_cb(c, ext_device_manager_subscribe_cb,
nullptr);
727 if (!(o = pa_ext_device_manager_subscribe(c, 1,
nullptr,
nullptr))) {
731 pa_operation_unref(o);
735 PulseUserData *u =
new PulseUserData;
736 if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) {
737 if (s_context != c) {
738 logMessage(
QString::fromLatin1(
"pa_ext_device_manager_read() failed. Attempting to continue without device manager support"));
740 createGenericDevices();
742 pa_context_disconnect(c);
748 pa_operation_unref(o);
752 if (s_context != c) {
754 createGenericDevices();
756 pa_context_disconnect(c);
760 }
else if (!PA_CONTEXT_IS_GOOD(state)) {
766 pa_context_disconnect(c);
768 pa_context_unref(s_context);
776PulseSupport *PulseSupport::getInstanceOrNull(
bool allowNull)
778 if (s_wasShutDown && allowNull) {
782 if (
nullptr == s_instance) {
790 if (
nullptr == s_instance)
791 s_instance =
new PulseSupport();
797PulseSupport *PulseSupport::getInstance()
799 return getInstanceOrNull(
false);
802void PulseSupport::shutdown()
804 if (
nullptr != s_instance) {
806 s_instance =
nullptr;
807 s_wasShutDown =
true;
811void PulseSupport::debug()
813#ifdef HAVE_PULSEAUDIO
814 logMessage(
QString::fromLatin1(
"Have we been initialised yet? %1").arg(s_instance ?
"Yes" :
"No"));
816 logMessage(
QString::fromLatin1(
"Connected to PulseAudio? %1").arg(s_pulseActive ?
"Yes" :
"No"));
817 logMessage(
QString::fromLatin1(
"PulseAudio support 'Active'? %1").arg(s_instance->isActive() ?
"Yes" :
"No"));
822PulseSupport::PulseSupport()
827#ifdef HAVE_PULSEAUDIO
830 QByteArray pulseenv = qgetenv(
"PHONON_PULSEAUDIO_DISABLE");
831 if (pulseenv.
toInt()) {
832 logMessage(
QLatin1String(
"PulseAudio support disabled: PHONON_PULSEAUDIO_DISABLE is set"));
837 qWarning(
"WARNING: Cannot construct PulseSupport because there is no Eventloop."
838 " May be because of application shutdown.");
845 qWarning(
"WARNING: Disabling PulseAudio integration for lack of GLib event loop.");
851 pa_mainloop *p_test_mainloop;
852 if (!(p_test_mainloop = pa_mainloop_new())) {
853 logMessage(
QLatin1String(
"PulseAudio support disabled: Unable to create mainloop"));
857 pa_context *p_test_context;
858 if (!(p_test_context = pa_context_new(pa_mainloop_get_api(p_test_mainloop),
"libphonon-probe"))) {
859 logMessage(
QLatin1String(
"PulseAudio support disabled: Unable to create context"));
860 pa_mainloop_free(p_test_mainloop);
866 if (pa_context_connect(p_test_context,
nullptr,
static_cast<pa_context_flags_t
>(0),
nullptr) < 0) {
868 pa_context_disconnect(p_test_context);
869 pa_context_unref(p_test_context);
870 pa_mainloop_free(p_test_mainloop);
874 pa_context_set_state_callback(p_test_context, &context_state_callback,
nullptr);
876 pa_mainloop_iterate(p_test_mainloop, 1,
nullptr);
878 if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) {
883 pa_context_disconnect(p_test_context);
884 pa_context_unref(p_test_context);
885 pa_mainloop_free(p_test_mainloop);
887 if (!s_pulseActive) {
888 logMessage(
QLatin1String(
"PulseAudio support is not available."));
897 s_mainloop = pa_glib_mainloop_new(
nullptr);
898 Q_ASSERT(s_mainloop);
904PulseSupport::~PulseSupport()
906#ifdef HAVE_PULSEAUDIO
908 pa_context_disconnect(s_context);
913 pa_glib_mainloop_free(s_mainloop);
914 s_mainloop =
nullptr;
920void PulseSupport::connectToDaemon()
922#ifdef HAVE_PULSEAUDIO
923 pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop);
925 s_context = pa_context_new(api,
"libphonon");
926 if (pa_context_connect(s_context,
nullptr, PA_CONTEXT_NOFAIL,
nullptr) >= 0)
927 pa_context_set_state_callback(s_context, &context_state_callback,
nullptr);
931bool PulseSupport::isActive()
933#ifdef HAVE_PULSEAUDIO
934 return mEnabled && isUsed();
940bool PulseSupport::isUsed()
942 return isRequested() && isUsable();
945bool PulseSupport::isUsable()
const
947 return s_pulseActive;
950bool PulseSupport::isRequested()
const
955void PulseSupport::request(
bool requested)
957 m_requested = requested;
960void PulseSupport::enable(
bool enabled)
964#ifdef HAVE_PULSEAUDIO
965 logMessage(
QString::fromLocal8Bit(
"Enabled Breakdown: mEnabled: %1, s_pulseActive %2").arg(mEnabled ?
"Yes" :
"No" ).arg(s_pulseActive ?
"Yes" :
"No"));
969QList<int> PulseSupport::objectDescriptionIndexes(ObjectDescriptionType type)
const
973 if (type != AudioOutputDeviceType && type != AudioCaptureDeviceType)
976#ifdef HAVE_PULSEAUDIO
980 case AudioOutputDeviceType: {
982 for (it = s_outputDeviceIndexes.
begin(); it != s_outputDeviceIndexes.
end(); ++it) {
987 case AudioCaptureDeviceType: {
989 for (it = s_captureDeviceIndexes.
begin(); it != s_captureDeviceIndexes.
end(); ++it) {
1007 if (type != AudioOutputDeviceType && type != AudioCaptureDeviceType)
1010#ifndef HAVE_PULSEAUDIO
1013 if (s_pulseActive) {
1016 case AudioOutputDeviceType:
1017 Q_ASSERT(s_outputDevices.
contains(index));
1018 ret = s_outputDevices[index].properties;
1021 case AudioCaptureDeviceType:
1022 Q_ASSERT(s_captureDevices.
contains(index));
1023 ret = s_captureDevices[index].properties;
1035QList<int> PulseSupport::objectIndexesByCategory(ObjectDescriptionType type, Category category)
const
1039 if (type != AudioOutputDeviceType)
1042#ifndef HAVE_PULSEAUDIO
1045 if (s_pulseActive) {
1046 if (s_outputDevicePriorities.
contains(category))
1054QList<int> PulseSupport::objectIndexesByCategory(ObjectDescriptionType type, CaptureCategory category)
const
1058 if (type != AudioCaptureDeviceType)
1061#ifndef HAVE_PULSEAUDIO
1064 if (s_pulseActive) {
1065 if (s_captureDevicePriorities.
contains(category))
1073#ifdef HAVE_PULSEAUDIO
1079 devices = pa_xnew(
char *,
list.
size()+1);
1081 foreach (
const QString &str, list) {
1086#if HAVE_PULSEAUDIO_DEVICE_MANAGER
1088 if (!(o = pa_ext_device_manager_reorder_devices_for_role(s_context, role.
toUtf8().
constData(), (
const char**)devices,
nullptr,
nullptr)))
1089 logMessage(
QString::fromLatin1(
"pa_ext_device_manager_reorder_devices_for_role() failed"));
1091 pa_operation_unref(o);
1095 pa_xfree(devices[i]);
1099static void setDevicePriority(Category category,
QStringList list)
1101 QString role = phononCategoryToPulseRole(category);
1105 setDevicePriority(role, list);
1108static void setDevicePriority(CaptureCategory category,
QStringList list)
1110 QString role = phononCaptureCategoryToPulseRole(category);
1114 setDevicePriority(role, list);
1118void PulseSupport::setOutputDevicePriorityForCategory(Category category,
QList<int> order)
1120#ifndef HAVE_PULSEAUDIO
1127 for (it = order.
begin(); it != order.
end(); ++it) {
1128 if (s_outputDevices.
contains(*it)) {
1129 list << s_outputDeviceIndexes.
key(*it);
1132 setDevicePriority(category, list);
1136void PulseSupport::setCaptureDevicePriorityForCategory(CaptureCategory category,
QList<int> order)
1138#ifndef HAVE_PULSEAUDIO
1145 for (it = order.
begin(); it != order.
end(); ++it) {
1146 if (s_captureDevices.
contains(*it)) {
1147 list << s_captureDeviceIndexes.
key(*it);
1150 setDevicePriority(category, list);
1154void PulseSupport::setCaptureDevicePriorityForCategory(Category category,
QList<int> order)
1156 CaptureCategory cat = categoryToCaptureCategory(category);
1157 setCaptureDevicePriorityForCategory(cat, order);
1160#ifdef HAVE_PULSEAUDIO
1165 PulseStream *stream =
new PulseStream(streamUuid, role);
1166 map[streamUuid] = stream;
1170 if (!Platform::applicationName().isEmpty())
1171 qputenv(
QString(
"PULSE_PROP_OVERRIDE_%1").arg(PA_PROP_APPLICATION_NAME).toUtf8(),
1172 Platform::applicationName().toUtf8());
1173 if (!qApp->applicationVersion().isEmpty())
1174 qputenv(
QString(
"PULSE_PROP_OVERRIDE_%1").arg(PA_PROP_APPLICATION_VERSION).toUtf8(),
1175 qApp->applicationVersion().
toUtf8());
1176 if (!qApp->applicationName().isEmpty()) {
1178 if (!qApp->windowIcon().isNull()){
1180 icon = qApp->windowIcon().name();
1184 icon = qApp->applicationName().
toLower();
1186 qputenv(
QString(
"PULSE_PROP_OVERRIDE_%1").arg(PA_PROP_APPLICATION_ICON_NAME).toUtf8(),
1195 QString role = phononCategoryToPulseRole(category);
1196 return register_stream(map, streamUuid, role);
1201 QString role = phononCaptureCategoryToPulseRole(category);
1202 return register_stream(map, streamUuid, role);
1207PulseStream *PulseSupport::registerOutputStream(
QString streamUuid, Category category)
1209#ifndef HAVE_PULSEAUDIO
1210 Q_UNUSED(streamUuid);
1214 return register_stream(s_outputStreams, streamUuid, category);
1218PulseStream *PulseSupport::registerCaptureStream(
QString streamUuid, CaptureCategory category)
1220#ifndef HAVE_PULSEAUDIO
1221 Q_UNUSED(streamUuid);
1225 return register_stream(s_captureStreams, streamUuid, category);
1229PulseStream *PulseSupport::registerCaptureStream(
QString streamUuid, Category category)
1231#ifndef HAVE_PULSEAUDIO
1232 Q_UNUSED(streamUuid);
1236 return register_stream(s_captureStreams, streamUuid, category);
1244#ifdef HAVE_PULSEAUDIO
1245 PulseStream *stream =
nullptr;
1249 stream = s_outputStreams.
value(streamUuid);
1253 stream = s_captureStreams.
value(streamUuid);
1257 qWarning() << Q_FUNC_INFO <<
"Requested UUID Could not be found. Returning with empty properties.";
1267 while (it.hasNext()) {
1269 unsetenv(
QString(
"PULSE_PROP_OVERRIDE_%1").arg(it.key()).
toUtf8());
1276void PulseSupport::setupStreamEnvironment(
QString streamUuid)
1278 pDebug() <<
"Please note that your current Phonon backend is trying to force"
1279 " stream dependent PulseAudio properties through environment variables."
1280 " Slightly imprecise timing in doing so will cause the first"
1281 " of two subsequently started AudioOutputs to have disfunct volume"
1282 " control. Also see https://bugs.kde.org/show_bug.cgi?id=321288";
1287 while (it.hasNext()) {
1289 pDebug() <<
"PULSE_PROP_OVERRIDE_" << it.key() <<
" = " << it.value();
1290 qputenv(
QString(
"PULSE_PROP_OVERRIDE_%1").arg(it.key()).
toUtf8(), it.value().toUtf8());
1294void PulseSupport::emitObjectDescriptionChanged(ObjectDescriptionType type)
1297 emit objectDescriptionChanged(type);
1300bool PulseSupport::setOutputName(
QString streamUuid,
QString name) {
1301#ifndef HAVE_PULSEAUDIO
1302 Q_UNUSED(streamUuid);
1306 logMessage(
QString::fromLatin1(
"Unimplemented: Need to find a way to set either application.name or media.name in SI proplist"));
1307 Q_UNUSED(streamUuid);
1313bool PulseSupport::setOutputDevice(
QString streamUuid,
int device) {
1314#ifndef HAVE_PULSEAUDIO
1315 Q_UNUSED(streamUuid);
1319 if (s_outputDevices.
size() < 2)
1322 if (!s_outputDevices.
contains(device)) {
1323 logMessage(
QString::fromLatin1(
"Attempting to set Output Device for invalid device id %1.").arg(device));
1326 const QVariant var = s_outputDevices[device].properties[
"name"];
1330 if (s_outputStreams.
contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) {
1333 uint32_t pulse_device_index = s_outputDevices[device].pulseIndex;
1334 uint32_t pulse_stream_index = s_outputStreams[streamUuid]->index();
1336 logMessage(
QString::fromLatin1(
"Moving Pulse Sink Input %1 to '%2' (Pulse Sink %3)").arg(pulse_stream_index).arg(var.
toString()).
arg(pulse_device_index));
1340 if (!(o = pa_context_move_sink_input_by_index(s_context, pulse_stream_index, pulse_device_index,
nullptr,
nullptr))) {
1344 pa_operation_unref(o);
1346 logMessage(
QString::fromLatin1(
"... Not found in map. We will be notified of the device when the stream appears and we can process any moves needed then"));
1352bool PulseSupport::setOutputVolume(
QString streamUuid, qreal volume) {
1353#ifndef HAVE_PULSEAUDIO
1354 Q_UNUSED(streamUuid);
1358 logMessage(
QString::fromLatin1(
"Attempting to set volume to %1 for Output Stream %2").arg(volume).arg(streamUuid));
1361 if (s_outputStreams.
contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) {
1362 PulseStream *stream = s_outputStreams[streamUuid];
1364 uint8_t channels = stream->channels();
1371 pa_cvolume_set(&vol, channels, (volume * PA_VOLUME_NORM));
1373 logMessage(
QString::fromLatin1(
"Found PA index %1. Calling pa_context_set_sink_input_volume()").arg(stream->index()));
1375 if (!(o = pa_context_set_sink_input_volume(s_context, stream->index(), &vol,
nullptr,
nullptr))) {
1379 pa_operation_unref(o);
1380 }
else if (s_outputStreams.
contains(streamUuid) && s_outputStreams[streamUuid]->index() == PA_INVALID_INDEX) {
1381 logMessage(
QString::fromLatin1(
"Setting volume on an invalid stream ..... this better be intended"));
1382 PulseStream *stream = s_outputStreams[streamUuid];
1383 stream->setCachedVolume(volume);
1389bool PulseSupport::setOutputMute(
QString streamUuid,
bool mute) {
1390#ifndef HAVE_PULSEAUDIO
1391 Q_UNUSED(streamUuid);
1395 logMessage(
QString::fromLatin1(
"Attempting to %1 mute for Output Stream %2").arg(mute ?
"set" :
"unset").arg(streamUuid));
1398 if (s_outputStreams.
contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) {
1399 PulseStream *stream = s_outputStreams[streamUuid];
1401 logMessage(
QString::fromLatin1(
"Found PA index %1. Calling pa_context_set_sink_input_mute()").arg(stream->index()));
1403 if (!(o = pa_context_set_sink_input_mute(s_context, stream->index(), (mute ? 1 : 0),
nullptr,
nullptr))) {
1407 pa_operation_unref(o);
1413bool PulseSupport::setCaptureDevice(
QString streamUuid,
int device) {
1414#ifndef HAVE_PULSEAUDIO
1415 Q_UNUSED(streamUuid);
1419 if (s_captureDevices.
size() < 2)
1422 if (!s_captureDevices.
contains(device)) {
1423 logMessage(
QString::fromLatin1(
"Attempting to set Capture Device for invalid device id %1.").arg(device));
1426 const QVariant var = s_captureDevices[device].properties[
"name"];
1430 if (s_captureStreams.
contains(streamUuid) && s_captureStreams[streamUuid]->index() == PA_INVALID_INDEX) {
1433 uint32_t pulse_device_index = s_captureDevices[device].pulseIndex;
1434 uint32_t pulse_stream_index = s_captureStreams[streamUuid]->index();
1436 logMessage(
QString::fromLatin1(
"Moving Pulse Source Output %1 to '%2' (Pulse Sink %3)").arg(pulse_stream_index).arg(var.
toString()).
arg(pulse_device_index));
1440 if (!(o = pa_context_move_source_output_by_index(s_context, pulse_stream_index, pulse_device_index,
nullptr,
nullptr))) {
1444 pa_operation_unref(o);
1446 logMessage(
QString::fromLatin1(
"... Not found in map. We will be notified of the device when the stream appears and we can process any moves needed then"));
1452void PulseSupport::clearStreamCache(
QString streamUuid) {
1453#ifndef HAVE_PULSEAUDIO
1454 Q_UNUSED(streamUuid);
1458 if (s_outputStreams.
contains(streamUuid)) {
1459 PulseStream *stream = s_outputStreams[streamUuid];
1460 s_outputStreams.
remove(streamUuid);
1462 }
else if (s_captureStreams.
contains(streamUuid)) {
1463 PulseStream *stream = s_captureStreams[streamUuid];
1464 s_captureStreams.
remove(streamUuid);
1472#include "moc_pulsesupport.cpp"
char * toString(const EngineQuery &query)
KIOCORE_EXPORT bool operator!=(const UDSEntry &entry, const UDSEntry &other)
QStringView level(QStringView ifopt)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QString name(StandardAction id)
Category category(StandardShortcut id)
QAbstractEventDispatcher * instance(QThread *thread)
const char * constData() const const
int toInt(bool *ok, int base) const const
void append(QList< T > &&value)
qsizetype size() const const
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
Key key(const T &value, const Key &defaultKey) const const
size_type remove(const Key &key)
size_type size() const const
T value(const Key &key, const T &defaultValue) const const
QList< T > values() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
QString arg(Args &&... args) const const
QString asprintf(const char *cformat,...)
QString fromLatin1(QByteArrayView str)
QString fromLocal8Bit(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QString toLower() const const
QByteArray toUtf8() const const
QString join(QChar separator) const const
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QVariant fromValue(T &&value)
QString toString() const const