File indexing completed on 2024-04-14 04:38:28

0001 /*
0002     Copyright (C) 2010 Colin Guthrie <cguthrie@mandriva.org>
0003     Copyright (C) 2013 Harald Sitter <sitter@kde.org>
0004 
0005     This library is free software; you can redistribute it and/or
0006     modify it under the terms of the GNU Lesser General Public
0007     License as published by the Free Software Foundation; either
0008     version 2.1 of the License, or (at your option) version 3, or any
0009     later version accepted by the membership of KDE e.V. (or its
0010     successor approved by the membership of KDE e.V.), Nokia Corporation
0011     (or its successors, if any) and the KDE Free Qt Foundation, which shall
0012     act as a proxy defined in Section 6 of version 3 of the license.
0013 
0014     This library is distributed in the hope that it will be useful,
0015     but WITHOUT ANY WARRANTY; without even the implied warranty of
0016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0017     Lesser General Public License for more details.
0018 
0019     You should have received a copy of the GNU Lesser General Public
0020     License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include "pulsesupport.h"
0024 
0025 #include <QAbstractEventDispatcher>
0026 #include <QApplication>
0027 #include <QDebug>
0028 #include <QIcon>
0029 #include <QMutex>
0030 #include <QStringList>
0031 #include <QTimer>
0032 
0033 #ifdef HAVE_PULSEAUDIO
0034 #include "pulsestream_p.h"
0035 #include <pulse/pulseaudio.h>
0036 #include <pulse/xmalloc.h>
0037 #include <pulse/glib-mainloop.h>
0038 
0039 #define HAVE_PULSEAUDIO_DEVICE_MANAGER PA_CHECK_VERSION(0,9,21)
0040 #if HAVE_PULSEAUDIO_DEVICE_MANAGER
0041 #  include <pulse/ext-device-manager.h>
0042 #endif
0043 #endif // HAVE_PULSEAUDIO
0044 
0045 #include "phononnamespace_p.h"
0046 #include "platform_p.h"
0047 
0048 #define PA_PROP_PHONON_STREAMID "phonon.streamid"
0049 
0050 namespace Phonon
0051 {
0052 
0053 QMutex probeMutex;
0054 static PulseSupport *s_instance = nullptr;
0055 static bool s_wasShutDown = false;
0056 static bool s_pulseActive = false;
0057 
0058 #ifdef HAVE_PULSEAUDIO
0059 /***
0060 * Prints a conditional debug message based on the current debug level
0061 * If obj is provided, classname and objectname will be printed as well
0062 *
0063 * see debugLevel()
0064 */
0065 
0066 static int debugLevel() {
0067     static int level = -1;
0068     if (level < 1) {
0069         level = 0;
0070         QByteArray pulseenv = qgetenv("PHONON_PULSEAUDIO_DEBUG");
0071         int l = pulseenv.toInt();
0072         if (l > 0)
0073             level = (l > 2 ? 2 : l);
0074     }
0075     return level;
0076 }
0077 
0078 static void logMessage(const QString &message, int priority = 2, QObject *obj=nullptr);
0079 static void logMessage(const QString &message, int priority, QObject *obj)
0080 {
0081     if (debugLevel() > 0) {
0082         QString output;
0083         if (obj) {
0084             // Strip away namespace from className
0085             QByteArray className(obj->metaObject()->className());
0086             int nameLength = className.length() - className.lastIndexOf(':') - 1;
0087             className = className.right(nameLength);
0088             output.asprintf("%s %s (%s %p)", message.toLatin1().constData(),
0089                            obj->objectName().toLatin1().constData(),
0090                            className.constData(), obj);
0091         }
0092         else {
0093             output = message;
0094         }
0095         if (priority <= debugLevel()) {
0096             qDebug() << QString::fromLatin1("PulseSupport(%1): %2").arg(priority).arg(output);
0097         }
0098     }
0099 }
0100 
0101 
0102 class AudioDevice
0103 {
0104     public:
0105         inline
0106         AudioDevice(QString name, QString desc, QString icon, uint32_t index)
0107         : pulseName(name), pulseIndex(index)
0108         {
0109             properties["name"] = desc;
0110             properties["description"] = QLatin1String(""); // We don't have descriptions (well we do, but we use them as the name!)
0111             properties["icon"] = icon;
0112             properties["available"] = (index != PA_INVALID_INDEX);
0113             properties["isAdvanced"] = false; // Nothing is advanced!
0114 
0115             DeviceAccessList dal;
0116             dal.append(DeviceAccess("pulse", desc));
0117             properties["deviceAccessList"] = QVariant::fromValue<DeviceAccessList>(dal);
0118         }
0119 
0120         // Needed for QMap
0121         inline AudioDevice() {}
0122 
0123         QString pulseName;
0124         uint32_t pulseIndex;
0125         QHash<QByteArray, QVariant> properties;
0126 };
0127 bool operator!=(const AudioDevice &a, const AudioDevice &b)
0128 {
0129     return !(a.pulseName == b.pulseName && a.properties == b.properties);
0130 }
0131 
0132 class PulseUserData
0133 {
0134     public:
0135         inline
0136         PulseUserData()
0137         {
0138         }
0139 
0140         QMap<QString, AudioDevice> newOutputDevices;
0141         QMap<Phonon::Category, QMap<int, int> > newOutputDevicePriorities; // prio, device
0142 
0143         QMap<QString, AudioDevice> newCaptureDevices;
0144         QMap<Phonon::CaptureCategory, QMap<int, int> > newCaptureDevicePriorities; // prio, device
0145 };
0146 
0147 static pa_glib_mainloop *s_mainloop = nullptr;
0148 static pa_context *s_context = nullptr;
0149 
0150 
0151 
0152 static int s_deviceIndexCounter = 0;
0153 
0154 static QMap<QString, int> s_outputDeviceIndexes;
0155 static QMap<int, AudioDevice> s_outputDevices;
0156 static QMap<Phonon::Category, QMap<int, int> > s_outputDevicePriorities; // prio, device
0157 static QMap<QString, PulseStream*> s_outputStreams;
0158 
0159 static const Phonon::CaptureCategory s_audioCapCategories[] = {
0160     Phonon::NoCaptureCategory,
0161     Phonon::CommunicationCaptureCategory,
0162     Phonon::RecordingCaptureCategory,
0163     Phonon::ControlCaptureCategory
0164 };
0165 
0166 static const int s_audioCapCategoriesCount = sizeof(s_audioCapCategories) / sizeof(Phonon::CaptureCategory);
0167 
0168 static QMap<QString, int> s_captureDeviceIndexes;
0169 static QMap<int, AudioDevice> s_captureDevices;
0170 static QMap<Phonon::CaptureCategory, QMap<int, int> > s_captureDevicePriorities; // prio, device
0171 static QMap<QString, PulseStream*> s_captureStreams;
0172 
0173 static PulseStream* findStreamByPulseIndex(QMap<QString, PulseStream*> map, uint32_t index)
0174 {
0175   QMap<QString, PulseStream*>::iterator it;
0176   for (it = map.begin(); it != map.end(); ++it)
0177     if ((*it)->index() == index)
0178       return *it;
0179   return nullptr;
0180 }
0181 
0182 static Phonon::Category pulseRoleToPhononCategory(const char *role, bool *success)
0183 {
0184     Q_ASSERT(role);
0185     Q_ASSERT(success);
0186     *success = true;
0187     QByteArray r(role);
0188     if (r == "none")
0189         return Phonon::NoCategory;
0190     if (r == "video")
0191         return Phonon::VideoCategory;
0192     if (r == "music")
0193         return Phonon::MusicCategory;
0194     if (r == "game")
0195         return Phonon::GameCategory;
0196     if (r == "event")
0197         return Phonon::NotificationCategory;
0198     if (r == "phone")
0199         return Phonon::CommunicationCategory;
0200     if (r == "a11y")
0201         return Phonon::AccessibilityCategory;
0202 
0203     // ^^ "animation" and "production" have no mapping
0204 
0205     *success = false;
0206     return Phonon::NoCategory;
0207 }
0208 
0209 static Phonon::CaptureCategory pulseRoleToPhononCaptureCategory(const char *role, bool *success)
0210 {
0211     Q_ASSERT(role);
0212     Q_ASSERT(success);
0213     *success = true;
0214     QByteArray r(role);
0215     if (r == "none")
0216         return Phonon::NoCaptureCategory;
0217     if (r == "phone")
0218         return Phonon::CommunicationCaptureCategory;
0219     if (r == "production")
0220         return Phonon::RecordingCaptureCategory;
0221     if (r == "a11y")
0222         return Phonon::ControlCaptureCategory;
0223 
0224     *success = false;
0225     return Phonon::NoCaptureCategory;
0226 }
0227 
0228 static const QByteArray phononCategoryToPulseRole(Phonon::Category category)
0229 {
0230     switch (category) {
0231     case Phonon::NoCategory:
0232         return QByteArray("none");
0233     case Phonon::VideoCategory:
0234         return QByteArray("video");
0235     case Phonon::MusicCategory:
0236         return QByteArray("music");
0237     case Phonon::GameCategory:
0238         return QByteArray("game");
0239     case Phonon::NotificationCategory:
0240         return QByteArray("event");
0241     case Phonon::CommunicationCategory:
0242         return QByteArray("phone");
0243     case Phonon::AccessibilityCategory:
0244         return QByteArray("a11y");
0245     default:
0246         return QByteArray();
0247     }
0248 }
0249 
0250 static const QByteArray phononCaptureCategoryToPulseRole(Phonon::CaptureCategory category)
0251 {
0252     switch (category) {
0253     case Phonon::NoCaptureCategory:
0254         return QByteArray("none");
0255     case Phonon::CommunicationCaptureCategory:
0256         return QByteArray("phone");
0257     case Phonon::RecordingCaptureCategory:
0258         return QByteArray("production");
0259     case Phonon::ControlCaptureCategory:
0260         return QByteArray("a11y");
0261     default:
0262         return QByteArray();
0263     }
0264 }
0265 
0266 static void createGenericDevices()
0267 {
0268     // OK so we don't have the device manager extension, but we can show a single device and fake it.
0269     int index;
0270     s_outputDeviceIndexes.clear();
0271     s_outputDevices.clear();
0272     s_outputDevicePriorities.clear();
0273     index = s_deviceIndexCounter++;
0274     s_outputDeviceIndexes.insert(QLatin1String("sink:default"), index);
0275     s_outputDevices.insert(index, AudioDevice(QLatin1String("sink:default"), QObject::tr("PulseAudio Sound Server"), QLatin1String("audio-backend-pulseaudio"), 0));
0276     for (int i = Phonon::NoCategory; i <= Phonon::LastCategory; ++i) {
0277         Phonon::Category cat = static_cast<Phonon::Category>(i);
0278         s_outputDevicePriorities[cat].insert(0, index);
0279     }
0280 
0281     s_captureDeviceIndexes.clear();
0282     s_captureDevices.clear();
0283     s_captureDevicePriorities.clear();
0284     index = s_deviceIndexCounter++;
0285     s_captureDeviceIndexes.insert(QLatin1String("source:default"), index);
0286     s_captureDevices.insert(index, AudioDevice(QLatin1String("source:default"), QObject::tr("PulseAudio Sound Server"), QLatin1String("audio-backend-pulseaudio"), 0));
0287     for (int i = 0; i < s_audioCapCategoriesCount; ++i) {
0288         Phonon::CaptureCategory cat = s_audioCapCategories[i];
0289         s_captureDevicePriorities[cat].insert(0, index);
0290     }
0291 }
0292 
0293 #if HAVE_PULSEAUDIO_DEVICE_MANAGER
0294 static void ext_device_manager_read_cb(pa_context *c, const pa_ext_device_manager_info *info, int eol, void *userdata) {
0295     Q_ASSERT(c);
0296     Q_ASSERT(userdata);
0297 
0298     PulseUserData *u = reinterpret_cast<PulseUserData*>(userdata);
0299 
0300     if (eol < 0) {
0301         logMessage(QString::fromLatin1("Failed to initialize device manager extension: %1").arg(pa_strerror(pa_context_errno(c))));
0302         if (s_context != c) {
0303             logMessage(QLatin1String("Falling back to single device mode"));
0304             // Only create our generic devices during the probe phase.
0305             createGenericDevices();
0306             // As this is our probe phase, exit immediately
0307             pa_context_disconnect(c);
0308         }
0309         delete u;
0310 
0311         return;
0312     }
0313 
0314     if (eol) {
0315         // We're done reading the data, so order it by priority and copy it into the
0316         // static variables where it can then be accessed by those classes that need it.
0317 
0318         QMap<QString, AudioDevice>::iterator newdev_it;
0319 
0320         // Check for new output devices or things changing about known output devices.
0321         bool output_changed = false;
0322         for (newdev_it = u->newOutputDevices.begin(); newdev_it != u->newOutputDevices.end(); ++newdev_it) {
0323             QString name = newdev_it.key();
0324 
0325             // The name + index map is always written when a new device is added.
0326             Q_ASSERT(s_outputDeviceIndexes.contains(name));
0327 
0328             int index = s_outputDeviceIndexes[name];
0329             if (!s_outputDevices.contains(index)) {
0330                 // This is a totally new device
0331                 output_changed = true;
0332                 logMessage(QString("Brand New Output Device Found."));
0333                 s_outputDevices.insert(index, *newdev_it);
0334             } else  if (s_outputDevices[index] != *newdev_it) {
0335                 // We have this device already, but is it different?
0336                 output_changed = true;
0337                 logMessage(QString("Change to Existing Output Device (may be Added/Removed or something else)"));
0338                 s_outputDevices.remove(index);
0339                 s_outputDevices.insert(index, *newdev_it);
0340             }
0341         }
0342         // Go through the output devices we know about and see if any are no longer mentioned in the list.
0343         QMutableMapIterator<QString, int> output_existing_it(s_outputDeviceIndexes);
0344         while (output_existing_it.hasNext()) {
0345             output_existing_it.next();
0346             if (!u->newOutputDevices.contains(output_existing_it.key())) {
0347                 output_changed = true;
0348                 logMessage(QString("Output Device Completely Removed"));
0349                 s_outputDevices.remove(output_existing_it.value());
0350                 output_existing_it.remove();
0351             }
0352         }
0353 
0354         // Check for new capture devices or things changing about known capture devices.
0355         bool capture_changed = false;
0356         for (newdev_it = u->newCaptureDevices.begin(); newdev_it != u->newCaptureDevices.end(); ++newdev_it) {
0357             QString name = newdev_it.key();
0358 
0359             // The name + index map is always written when a new device is added.
0360             Q_ASSERT(s_captureDeviceIndexes.contains(name));
0361 
0362             int index = s_captureDeviceIndexes[name];
0363             if (!s_captureDevices.contains(index)) {
0364                 // This is a totally new device
0365                 capture_changed = true;
0366                 logMessage(QString("Brand New Capture Device Found."));
0367                 s_captureDevices.insert(index, *newdev_it);
0368             } else  if (s_captureDevices[index] != *newdev_it) {
0369                 // We have this device already, but is it different?
0370                 capture_changed = true;
0371                 logMessage(QString("Change to Existing Capture Device (may be Added/Removed or something else)"));
0372                 s_captureDevices.remove(index);
0373                 s_captureDevices.insert(index, *newdev_it);
0374             }
0375         }
0376         // Go through the capture devices we know about and see if any are no longer mentioned in the list.
0377         QMutableMapIterator<QString, int> capture_existing_it(s_captureDeviceIndexes);
0378         while (capture_existing_it.hasNext()) {
0379             capture_existing_it.next();
0380             if (!u->newCaptureDevices.contains(capture_existing_it.key())) {
0381                 capture_changed = true;
0382                 logMessage(QString("Capture Device Completely Removed"));
0383                 s_captureDevices.remove(capture_existing_it.value());
0384                 capture_existing_it.remove();
0385             }
0386         }
0387 
0388         // Just copy across the new priority lists as we know they are valid
0389         if (s_outputDevicePriorities != u->newOutputDevicePriorities) {
0390             output_changed = true;
0391             s_outputDevicePriorities = u->newOutputDevicePriorities;
0392         }
0393         if (s_captureDevicePriorities != u->newCaptureDevicePriorities) {
0394             capture_changed = true;
0395             s_captureDevicePriorities = u->newCaptureDevicePriorities;
0396         }
0397 
0398         if (s_instance) {
0399             // This won't be emitted during the connection probe phase
0400             // which is intentional
0401             if (output_changed)
0402                 s_instance->emitObjectDescriptionChanged(AudioOutputDeviceType);
0403             if (capture_changed)
0404                 s_instance->emitObjectDescriptionChanged(AudioCaptureDeviceType);
0405         }
0406 
0407         // We can free the user data as we will not be called again.
0408         delete u;
0409 
0410         // Some debug
0411         logMessage(QString("Output Device Priority List:"));
0412         for (int i = Phonon::NoCategory; i <= Phonon::LastCategory; ++i) {
0413             Phonon::Category cat = static_cast<Phonon::Category>(i);
0414             if (s_outputDevicePriorities.contains(cat)) {
0415                 logMessage(QString("  Phonon Category %1").arg(cat));
0416                 int count = 0;
0417                 foreach (int j, s_outputDevicePriorities[cat]) {
0418                     QHash<QByteArray, QVariant> &props = s_outputDevices[j].properties;
0419                     logMessage(QString("    %1. %2 (Available: %3)").arg(++count).arg(props["name"].toString()).arg(props["available"].toBool()));
0420                 }
0421             }
0422         }
0423         logMessage(QString("Capture Device Priority List:"));
0424         for (int i = 0; i < s_audioCapCategoriesCount; ++i) {
0425             Phonon::CaptureCategory cat = s_audioCapCategories[i];
0426             if (s_captureDevicePriorities.contains(cat)) {
0427                 logMessage(QString("  Phonon Category %1").arg(cat));
0428                 int count = 0;
0429                 foreach (int j, s_captureDevicePriorities[cat]) {
0430                     QHash<QByteArray, QVariant> &props = s_captureDevices[j].properties;
0431                     logMessage(QString("    %1. %2 (Available: %3)").arg(++count).arg(props["name"].toString()).arg(props["available"].toBool()));
0432                 }
0433             }
0434         }
0435 
0436         // If this is our probe phase, exit now as we're finished reading
0437         // our device info and can exit and reconnect
0438         if (s_context != c)
0439             pa_context_disconnect(c);
0440 
0441         return; // eol
0442     }
0443 
0444     // If we aren't at eol we expect info to be valid!
0445     Q_ASSERT(info);
0446     Q_ASSERT(info->name);
0447     Q_ASSERT(info->description);
0448     Q_ASSERT(info->icon);
0449 
0450     // QString wrapper
0451     QString name(info->name);
0452     int index;
0453     QMap<Phonon::Category, QMap<int, int> > *new_prio_map_cats = nullptr; // prio, device
0454     QMap<Phonon::CaptureCategory, QMap<int, int> > *new_prio_map_capcats = nullptr; // prio, device
0455     QMap<QString, AudioDevice> *new_devices = nullptr;
0456 
0457     bool isSink = false;
0458     bool isSource = false;
0459 
0460     if (name.startsWith(QLatin1String("sink:"))) {
0461         isSink = true;
0462 
0463         new_devices = &u->newOutputDevices;
0464         new_prio_map_cats = &u->newOutputDevicePriorities;
0465 
0466         if (s_outputDeviceIndexes.contains(name))
0467             index = s_outputDeviceIndexes[name];
0468         else
0469             index = s_outputDeviceIndexes[name] = s_deviceIndexCounter++;
0470     } else if (name.startsWith(QLatin1String("source:"))) {
0471         isSource = true;
0472 
0473         new_devices = &u->newCaptureDevices;
0474         new_prio_map_capcats = &u->newCaptureDevicePriorities;
0475 
0476         if (s_captureDeviceIndexes.contains(name))
0477             index = s_captureDeviceIndexes[name];
0478         else
0479             index = s_captureDeviceIndexes[name] = s_deviceIndexCounter++;
0480     } else {
0481         // This indicates a bug in pulseaudio.
0482         return;
0483     }
0484 
0485     Q_ASSERT(new_devices);
0486     Q_ASSERT(!isSink || new_prio_map_cats);
0487     Q_ASSERT(!isSource || new_prio_map_capcats);
0488 
0489     // Add the new device itself.
0490     new_devices->insert(name, AudioDevice(name, QString::fromUtf8(info->description), QString::fromUtf8(info->icon), info->index));
0491 
0492     // For each role in the priority, map it to a phonon category and store the order.
0493     for (uint32_t i = 0; i < info->n_role_priorities; ++i) {
0494         pa_ext_device_manager_role_priority_info* role_prio = &info->role_priorities[i];
0495         Q_ASSERT(role_prio->role);
0496 
0497         bool conversionSuccess;
0498 
0499         if (isSink) {
0500             Phonon::Category cat = pulseRoleToPhononCategory(role_prio->role, &conversionSuccess);
0501             if (conversionSuccess) {
0502                 (*new_prio_map_cats)[cat].insert(role_prio->priority, index);
0503             }
0504         }
0505 
0506         if (isSource) {
0507             Phonon::CaptureCategory capcat = pulseRoleToPhononCaptureCategory(role_prio->role, &conversionSuccess);
0508             if (conversionSuccess) {
0509                 (*new_prio_map_capcats)[capcat].insert(role_prio->priority, index);
0510             }
0511         }
0512     }
0513 }
0514 
0515 static void ext_device_manager_subscribe_cb(pa_context *c, void *) {
0516     Q_ASSERT(c);
0517 
0518     pa_operation *o;
0519     PulseUserData *u = new PulseUserData;
0520     if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) {
0521         logMessage(QString::fromLatin1("pa_ext_device_manager_read() failed."));
0522         delete u;
0523         return;
0524     }
0525     pa_operation_unref(o);
0526 }
0527 #endif
0528 
0529 static void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
0530     Q_UNUSED(userdata);
0531     Q_ASSERT(c);
0532 
0533     if (eol < 0) {
0534         if (pa_context_errno(c) == PA_ERR_NOENTITY)
0535             return;
0536 
0537         logMessage(QLatin1String("Sink input callback failure"));
0538         return;
0539     }
0540 
0541     if (eol > 0)
0542         return;
0543 
0544     Q_ASSERT(i);
0545 
0546     // loop through (*i) and extract phonon->streamindex...
0547     const char *t;
0548     if ((t = pa_proplist_gets(i->proplist, PA_PROP_PHONON_STREAMID))) {
0549         logMessage(QString::fromLatin1("Found PulseAudio stream index %1 for Phonon Output Stream %2").arg(i->index).arg(QLatin1String(t)));
0550 
0551         // We only care about our own streams (other phonon processes are irrelevant)
0552         if (s_outputStreams.contains(QLatin1String(t))) {
0553             PulseStream *stream = s_outputStreams[QString(t)];
0554             stream->setIndex(i->index);
0555             stream->setVolume(&i->volume);
0556             stream->setMute(!!i->mute);
0557 
0558             // Find the sink's phonon index and notify whoever cares...
0559             if (PA_INVALID_INDEX != i->sink) {
0560                 QMap<int, AudioDevice>::iterator it;
0561                 for (it = s_outputDevices.begin(); it != s_outputDevices.end(); ++it) {
0562                     if ((*it).pulseIndex == i->sink) {
0563                         stream->setDevice(it.key());
0564                         break;
0565                     }
0566                 }
0567             }
0568         }
0569     }
0570 }
0571 
0572 static void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, void *userdata) {
0573     Q_UNUSED(userdata);
0574     Q_ASSERT(c);
0575 
0576     if (eol < 0) {
0577         if (pa_context_errno(c) == PA_ERR_NOENTITY)
0578             return;
0579 
0580         logMessage(QLatin1String("Source output callback failure"));
0581         return;
0582     }
0583 
0584     if (eol > 0)
0585         return;
0586 
0587     Q_ASSERT(i);
0588 
0589     // loop through (*i) and extract phonon->streamindex...
0590     const char *t;
0591     if ((t = pa_proplist_gets(i->proplist, PA_PROP_PHONON_STREAMID))) {
0592         logMessage(QString::fromLatin1("Found PulseAudio stream index %1 for Phonon Capture Stream %2").arg(i->index).arg(QLatin1String(t)));
0593 
0594         // We only care about our own streams (other phonon processes are irrelevant)
0595         if (s_captureStreams.contains(QLatin1String(t))) {
0596             PulseStream *stream = s_captureStreams[QString(t)];
0597             stream->setIndex(i->index);
0598             //stream->setVolume(&i->volume);
0599             //stream->setMute(!!i->mute);
0600 
0601             // Find the source's phonon index and notify whoever cares...
0602             if (PA_INVALID_INDEX != i->source) {
0603                 QMap<int, AudioDevice>::iterator it;
0604                 for (it = s_captureDevices.begin(); it != s_captureDevices.end(); ++it) {
0605                     if ((*it).pulseIndex == i->source) {
0606                         stream->setDevice(it.key());
0607                         break;
0608                     }
0609                 }
0610             }
0611         }
0612     }
0613 }
0614 
0615 static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) {
0616     Q_UNUSED(userdata);
0617 
0618     switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
0619         case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
0620             if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0621                 PulseStream *stream = findStreamByPulseIndex(s_outputStreams, index);
0622                 if (stream) {
0623                     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()));
0624                     stream->setIndex(PA_INVALID_INDEX);
0625                 }
0626             } else {
0627                 pa_operation *o;
0628                 if (!(o = pa_context_get_sink_input_info(c, index, sink_input_cb, nullptr))) {
0629                     logMessage(QString::fromLatin1("pa_context_get_sink_input_info() failed"));
0630                     return;
0631                 }
0632                 pa_operation_unref(o);
0633             }
0634             break;
0635 
0636         case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
0637             if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
0638               PulseStream *stream = findStreamByPulseIndex(s_captureStreams, index);
0639               if (stream) {
0640                     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()));
0641                     stream->setIndex(PA_INVALID_INDEX);
0642                 }
0643             } else {
0644                 pa_operation *o;
0645                 if (!(o = pa_context_get_source_output_info(c, index, source_output_cb, nullptr))) {
0646                     logMessage(QString::fromLatin1("pa_context_get_sink_input_info() failed"));
0647                     return;
0648                 }
0649                 pa_operation_unref(o);
0650             }
0651             break;
0652     }
0653 }
0654 
0655 
0656 static QString statename(pa_context_state_t state)
0657 {
0658     switch (state)
0659     {
0660         case PA_CONTEXT_UNCONNECTED:  return QLatin1String("Unconnected");
0661         case PA_CONTEXT_CONNECTING:   return QLatin1String("Connecting");
0662         case PA_CONTEXT_AUTHORIZING:  return QLatin1String("Authorizing");
0663         case PA_CONTEXT_SETTING_NAME: return QLatin1String("Setting Name");
0664         case PA_CONTEXT_READY:        return QLatin1String("Ready");
0665         case PA_CONTEXT_FAILED:       return QLatin1String("Failed");
0666         case PA_CONTEXT_TERMINATED:   return QLatin1String("Terminated");
0667     }
0668 
0669     return QString::fromLatin1("Unknown state: %0").arg(state);
0670 }
0671 
0672 static void context_state_callback(pa_context *c, void *)
0673 {
0674     Q_ASSERT(c);
0675 
0676     logMessage(QString::fromLatin1("context_state_callback %1").arg(statename(pa_context_get_state(c))));
0677     pa_context_state_t state = pa_context_get_state(c);
0678     if (state == PA_CONTEXT_READY) {
0679         // We've connected to PA, so it is active
0680         s_pulseActive = true;
0681 
0682         // Attempt to load things up
0683         pa_operation *o;
0684 
0685         // 1. Register for the stream changes (except during probe)
0686         if (s_context == c) {
0687             pa_context_set_subscribe_callback(c, subscribe_cb, nullptr);
0688 
0689             if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
0690                                               (PA_SUBSCRIPTION_MASK_SINK_INPUT|
0691                                                PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), nullptr, nullptr))) {
0692                 logMessage(QLatin1String("pa_context_subscribe() failed"));
0693                 return;
0694             }
0695             pa_operation_unref(o);
0696 
0697             // In the case of reconnection or simply lagging behind the stream object creation
0698             // on startup (due to the probe+reconnect system), we invalidate all loaded streams
0699             // and then load up info about all streams.
0700             for (QMap<QString, PulseStream*>::iterator it = s_outputStreams.begin(); it != s_outputStreams.end(); ++it) {
0701               PulseStream *stream = *it;
0702               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()));
0703               stream->setIndex(PA_INVALID_INDEX);
0704             }
0705             if (!(o = pa_context_get_sink_input_info_list(c, sink_input_cb, nullptr))) {
0706               logMessage(QString::fromLatin1("pa_context_get_sink_input_info_list() failed"));
0707               return;
0708             }
0709             pa_operation_unref(o);
0710 
0711             for (QMap<QString, PulseStream*>::iterator it = s_captureStreams.begin(); it != s_captureStreams.end(); ++it) {
0712               PulseStream *stream = *it;
0713               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()));
0714               stream->setIndex(PA_INVALID_INDEX);
0715             }
0716             if (!(o = pa_context_get_source_output_info_list(c, source_output_cb, nullptr))) {
0717               logMessage(QString::fromLatin1("pa_context_get_source_output_info_list() failed"));
0718               return;
0719             }
0720             pa_operation_unref(o);
0721         }
0722 
0723 #if HAVE_PULSEAUDIO_DEVICE_MANAGER
0724         // 2a. Attempt to initialise Device Manager info (except during probe)
0725         if (s_context == c) {
0726             pa_ext_device_manager_set_subscribe_cb(c, ext_device_manager_subscribe_cb, nullptr);
0727             if (!(o = pa_ext_device_manager_subscribe(c, 1, nullptr, nullptr))) {
0728                 logMessage(QString::fromLatin1("pa_ext_device_manager_subscribe() failed"));
0729                 return;
0730             }
0731             pa_operation_unref(o);
0732         }
0733 
0734         // 3. Attempt to read info from Device Manager
0735         PulseUserData *u = new PulseUserData;
0736         if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) {
0737             if (s_context != c) {
0738                 logMessage(QString::fromLatin1("pa_ext_device_manager_read() failed. Attempting to continue without device manager support"));
0739                 // Only create our generic devices during the probe phase.
0740                 createGenericDevices();
0741                 // As this is our probe phase, exit immediately
0742                 pa_context_disconnect(c);
0743             }
0744             delete u;
0745 
0746             return;
0747         }
0748         pa_operation_unref(o);
0749 
0750 #else
0751         // If we know do not have Device Manager support, we just create our dummy devices now
0752         if (s_context != c) {
0753             // Only create our generic devices during the probe phase.
0754             createGenericDevices();
0755             // As this is our probe phase, exit immediately
0756             pa_context_disconnect(c);
0757         }
0758 
0759 #endif
0760     } else if (!PA_CONTEXT_IS_GOOD(state)) {
0761         /// @todo Deal with reconnection...
0762         //logMessage(QString("Connection to PulseAudio lost: %1").arg(pa_strerror(pa_context_errno(c))));
0763 
0764         // If this is our probe phase, exit our context immediately
0765         if (s_context != c)
0766             pa_context_disconnect(c);
0767         else {
0768             pa_context_unref(s_context);
0769             s_context = nullptr;
0770             QTimer::singleShot(50, PulseSupport::getInstance(), SLOT(connectToDaemon()));
0771         }
0772     }
0773 }
0774 #endif // HAVE_PULSEAUDIO
0775 
0776 PulseSupport *PulseSupport::getInstanceOrNull(bool allowNull)
0777 {
0778     if (s_wasShutDown && allowNull) {
0779         return nullptr;
0780     }
0781 
0782     if (nullptr == s_instance) {
0783         /*
0784          * In order to prevent the instance being used from multiple threads
0785          * prior to it being constructed fully, we need to ensure we obtain a
0786          * lock prior to creating it. After we acquire the lock, check to see
0787          * if the object is created again before proceeding.
0788          */
0789         probeMutex.lock();
0790         if (nullptr == s_instance)
0791             s_instance = new PulseSupport();
0792         probeMutex.unlock();
0793     }
0794     return s_instance;
0795 }
0796 
0797 PulseSupport *PulseSupport::getInstance()
0798 {
0799     return getInstanceOrNull(false);
0800 }
0801 
0802 void PulseSupport::shutdown()
0803 {
0804     if (nullptr != s_instance) {
0805         delete s_instance;
0806         s_instance = nullptr;
0807         s_wasShutDown = true;
0808     }
0809 }
0810 
0811 void PulseSupport::debug()
0812 {
0813 #ifdef HAVE_PULSEAUDIO
0814     logMessage(QString::fromLatin1("Have we been initialised yet? %1").arg(s_instance ? "Yes" : "No"));
0815     if (s_instance) {
0816         logMessage(QString::fromLatin1("Connected to PulseAudio? %1").arg(s_pulseActive ? "Yes" : "No"));
0817         logMessage(QString::fromLatin1("PulseAudio support 'Active'? %1").arg(s_instance->isActive() ? "Yes" : "No"));
0818     }
0819 #endif
0820 }
0821 
0822 PulseSupport::PulseSupport()
0823     : QObject()
0824     , mEnabled(false)
0825     , m_requested(false)
0826 {
0827 #ifdef HAVE_PULSEAUDIO
0828 
0829     // To allow for easy debugging, give an easy way to disable this pulseaudio check
0830     QByteArray pulseenv = qgetenv("PHONON_PULSEAUDIO_DISABLE");
0831     if (pulseenv.toInt()) {
0832         logMessage(QLatin1String("PulseAudio support disabled: PHONON_PULSEAUDIO_DISABLE is set"));
0833         return;
0834     }
0835 
0836     if (!QAbstractEventDispatcher::instance() || !QAbstractEventDispatcher::instance()->metaObject()) {
0837         qWarning("WARNING: Cannot construct PulseSupport because there is no Eventloop."
0838                  " May be because of application shutdown.");
0839         return;
0840     }
0841 
0842     // We require a glib event loop
0843     if (!QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("EventDispatcherGlib") &&
0844         !QByteArray(QAbstractEventDispatcher::instance()->metaObject()->className()).contains("GlibEventDispatcher")) {
0845         qWarning("WARNING: Disabling PulseAudio integration for lack of GLib event loop.");
0846         return;
0847     }
0848 
0849     // First of all connect to PA via simple/blocking means and if that succeeds,
0850     // use a fully async integrated mainloop method to connect and get proper support.
0851     pa_mainloop *p_test_mainloop;
0852     if (!(p_test_mainloop = pa_mainloop_new())) {
0853         logMessage(QLatin1String("PulseAudio support disabled: Unable to create mainloop"));
0854         return;
0855     }
0856 
0857     pa_context *p_test_context;
0858     if (!(p_test_context = pa_context_new(pa_mainloop_get_api(p_test_mainloop), "libphonon-probe"))) {
0859         logMessage(QLatin1String("PulseAudio support disabled: Unable to create context"));
0860         pa_mainloop_free(p_test_mainloop);
0861         return;
0862     }
0863 
0864     logMessage(QLatin1String("Probing for PulseAudio..."));
0865     // (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required
0866     if (pa_context_connect(p_test_context, nullptr, static_cast<pa_context_flags_t>(0), nullptr) < 0) {
0867         logMessage(QString::fromLatin1("PulseAudio support disabled: %1").arg(QString::fromLocal8Bit(pa_strerror(pa_context_errno(p_test_context)))));
0868         pa_context_disconnect(p_test_context);
0869         pa_context_unref(p_test_context);
0870         pa_mainloop_free(p_test_mainloop);
0871         return;
0872     }
0873 
0874     pa_context_set_state_callback(p_test_context, &context_state_callback, nullptr);
0875     for (;;) {
0876         pa_mainloop_iterate(p_test_mainloop, 1, nullptr);
0877 
0878         if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) {
0879             logMessage(QLatin1String("PulseAudio probe complete."));
0880             break;
0881         }
0882     }
0883     pa_context_disconnect(p_test_context);
0884     pa_context_unref(p_test_context);
0885     pa_mainloop_free(p_test_mainloop);
0886 
0887     if (!s_pulseActive) {
0888         logMessage(QLatin1String("PulseAudio support is not available."));
0889         return;
0890     }
0891 
0892     // If we're still here, PA is available.
0893     logMessage(QLatin1String("PulseAudio support enabled"));
0894 
0895     // Now we connect for real using a proper main loop that we can forget
0896     // all about processing.
0897     s_mainloop = pa_glib_mainloop_new(nullptr);
0898     Q_ASSERT(s_mainloop);
0899 
0900     connectToDaemon();
0901 #endif
0902 }
0903 
0904 PulseSupport::~PulseSupport()
0905 {
0906 #ifdef HAVE_PULSEAUDIO
0907     if (s_context) {
0908         pa_context_disconnect(s_context);
0909         s_context = nullptr;
0910     }
0911 
0912     if (s_mainloop) {
0913         pa_glib_mainloop_free(s_mainloop);
0914         s_mainloop = nullptr;
0915     }
0916 #endif
0917 }
0918 
0919 
0920 void PulseSupport::connectToDaemon()
0921 {
0922 #ifdef HAVE_PULSEAUDIO
0923     pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop);
0924 
0925     s_context = pa_context_new(api, "libphonon");
0926     if (pa_context_connect(s_context, nullptr, PA_CONTEXT_NOFAIL, nullptr) >= 0)
0927         pa_context_set_state_callback(s_context, &context_state_callback, nullptr);
0928 #endif
0929 }
0930 
0931 bool PulseSupport::isActive()
0932 {
0933 #ifdef HAVE_PULSEAUDIO
0934     return mEnabled && isUsed();
0935 #else
0936     return false;
0937 #endif
0938 }
0939 
0940 bool PulseSupport::isUsed()
0941 {
0942    return isRequested() && isUsable();
0943 }
0944 
0945 bool PulseSupport::isUsable() const
0946 {
0947     return s_pulseActive;
0948 }
0949 
0950 bool PulseSupport::isRequested() const
0951 {
0952     return m_requested;
0953 }
0954 
0955 void PulseSupport::request(bool requested)
0956 {
0957     m_requested = requested;
0958 }
0959 
0960 void PulseSupport::enable(bool enabled)
0961 {
0962     mEnabled = enabled;
0963     request(enabled); // compat, enable needs to imply request.
0964 #ifdef HAVE_PULSEAUDIO
0965     logMessage(QString::fromLocal8Bit("Enabled Breakdown: mEnabled: %1, s_pulseActive %2").arg(mEnabled ? "Yes" : "No" ).arg(s_pulseActive ? "Yes" : "No"));
0966 #endif
0967 }
0968 
0969 QList<int> PulseSupport::objectDescriptionIndexes(ObjectDescriptionType type) const
0970 {
0971     QList<int> list;
0972 
0973     if (type != AudioOutputDeviceType && type != AudioCaptureDeviceType)
0974         return list;
0975 
0976 #ifdef HAVE_PULSEAUDIO
0977     if (s_pulseActive) {
0978         switch (type) {
0979 
0980             case AudioOutputDeviceType: {
0981                 QMap<QString, int>::iterator it;
0982                 for (it = s_outputDeviceIndexes.begin(); it != s_outputDeviceIndexes.end(); ++it) {
0983                     list.append(*it);
0984                 }
0985                 break;
0986             }
0987             case AudioCaptureDeviceType: {
0988                 QMap<QString, int>::iterator it;
0989                 for (it = s_captureDeviceIndexes.begin(); it != s_captureDeviceIndexes.end(); ++it) {
0990                     list.append(*it);
0991                 }
0992                 break;
0993             }
0994             default:
0995                 break;
0996         }
0997     }
0998 #endif
0999 
1000     return list;
1001 }
1002 
1003 QHash<QByteArray, QVariant> PulseSupport::objectDescriptionProperties(ObjectDescriptionType type, int index) const
1004 {
1005     QHash<QByteArray, QVariant> ret;
1006 
1007     if (type != AudioOutputDeviceType && type != AudioCaptureDeviceType)
1008         return ret;
1009 
1010 #ifndef HAVE_PULSEAUDIO
1011     Q_UNUSED(index);
1012 #else
1013     if (s_pulseActive) {
1014         switch (type) {
1015 
1016             case AudioOutputDeviceType:
1017                 Q_ASSERT(s_outputDevices.contains(index));
1018                 ret = s_outputDevices[index].properties;
1019                 break;
1020 
1021             case AudioCaptureDeviceType:
1022                 Q_ASSERT(s_captureDevices.contains(index));
1023                 ret = s_captureDevices[index].properties;
1024                 break;
1025 
1026             default:
1027                 break;
1028         }
1029     }
1030 #endif
1031 
1032     return ret;
1033 }
1034 
1035 QList<int> PulseSupport::objectIndexesByCategory(ObjectDescriptionType type, Category category) const
1036 {
1037     QList<int> ret;
1038 
1039     if (type != AudioOutputDeviceType)
1040         return ret;
1041 
1042 #ifndef HAVE_PULSEAUDIO
1043     Q_UNUSED(category);
1044 #else
1045     if (s_pulseActive) {
1046         if (s_outputDevicePriorities.contains(category))
1047             ret = s_outputDevicePriorities[category].values();
1048     }
1049 #endif
1050 
1051     return ret;
1052 }
1053 
1054 QList<int> PulseSupport::objectIndexesByCategory(ObjectDescriptionType type, CaptureCategory category) const
1055 {
1056     QList<int> ret;
1057 
1058     if (type != AudioCaptureDeviceType)
1059         return ret;
1060 
1061 #ifndef HAVE_PULSEAUDIO
1062     Q_UNUSED(category);
1063 #else
1064     if (s_pulseActive) {
1065         if (s_captureDevicePriorities.contains(category))
1066             ret = s_captureDevicePriorities[category].values();
1067     }
1068 #endif
1069 
1070     return ret;
1071 }
1072 
1073 #ifdef HAVE_PULSEAUDIO
1074 static void setDevicePriority(QString role, QStringList list)
1075 {
1076     logMessage(QString::fromLatin1("Reindexing %1: %2").arg(role).arg(list.join(QLatin1String(", "))));
1077 
1078     char **devices;
1079     devices = pa_xnew(char *, list.size()+1);
1080     int i = 0;
1081     foreach (const QString &str, list) {
1082         devices[i++] = pa_xstrdup(str.toUtf8().constData());
1083     }
1084     devices[list.size()] = nullptr;
1085 
1086 #if HAVE_PULSEAUDIO_DEVICE_MANAGER
1087     pa_operation *o;
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"));
1090     else
1091         pa_operation_unref(o);
1092 #endif
1093 
1094     for (i = 0; i < list.size(); ++i)
1095         pa_xfree(devices[i]);
1096     pa_xfree(devices);
1097 }
1098 
1099 static void setDevicePriority(Category category, QStringList list)
1100 {
1101     QString role = phononCategoryToPulseRole(category);
1102     if (role.isEmpty())
1103         return;
1104 
1105     setDevicePriority(role, list);
1106 }
1107 
1108 static void setDevicePriority(CaptureCategory category, QStringList list)
1109 {
1110     QString role = phononCaptureCategoryToPulseRole(category);
1111     if (role.isEmpty())
1112         return;
1113 
1114     setDevicePriority(role, list);
1115 }
1116 #endif // HAVE_PULSEAUDIO
1117 
1118 void PulseSupport::setOutputDevicePriorityForCategory(Category category, QList<int> order)
1119 {
1120 #ifndef HAVE_PULSEAUDIO
1121     Q_UNUSED(category);
1122     Q_UNUSED(order);
1123 #else
1124     QStringList list;
1125     QList<int>::iterator it;
1126 
1127     for (it = order.begin(); it != order.end(); ++it) {
1128         if (s_outputDevices.contains(*it)) {
1129             list << s_outputDeviceIndexes.key(*it);
1130         }
1131     }
1132     setDevicePriority(category, list);
1133 #endif
1134 }
1135 
1136 void PulseSupport::setCaptureDevicePriorityForCategory(CaptureCategory category, QList<int> order)
1137 {
1138 #ifndef HAVE_PULSEAUDIO
1139     Q_UNUSED(category);
1140     Q_UNUSED(order);
1141 #else
1142     QStringList list;
1143     QList<int>::iterator it;
1144 
1145     for (it = order.begin(); it != order.end(); ++it) {
1146         if (s_captureDevices.contains(*it)) {
1147             list << s_captureDeviceIndexes.key(*it);
1148         }
1149     }
1150     setDevicePriority(category, list);
1151 #endif
1152 }
1153 
1154 void PulseSupport::setCaptureDevicePriorityForCategory(Category category, QList<int> order)
1155 {
1156     CaptureCategory cat = categoryToCaptureCategory(category);
1157     setCaptureDevicePriorityForCategory(cat, order);
1158 }
1159 
1160 #ifdef HAVE_PULSEAUDIO
1161 static PulseStream* register_stream(QMap<QString,PulseStream*> &map, QString streamUuid, QString role)
1162 {
1163     logMessage(QString::fromLatin1("Initialising streamindex %1").arg(streamUuid));
1164 
1165     PulseStream *stream = new PulseStream(streamUuid, role);
1166     map[streamUuid] = stream;
1167 
1168     // Setup environment...
1169     // These values are considered static, so we force property overrides for them.
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()) {
1177         QString icon;
1178         if (!qApp->windowIcon().isNull()){
1179             // Try to get the fromTheme() name of the QIcon.
1180             icon = qApp->windowIcon().name();
1181         }
1182         if (icon.isEmpty()) {
1183             // If we failed to get a proper icon name, use the appname instead.
1184             icon = qApp->applicationName().toLower();
1185         }
1186         qputenv(QString("PULSE_PROP_OVERRIDE_%1").arg(PA_PROP_APPLICATION_ICON_NAME).toUtf8(),
1187                 icon.toUtf8());
1188     }
1189 
1190     return stream;
1191 }
1192 
1193 static PulseStream* register_stream(QMap<QString,PulseStream*> &map, QString streamUuid, Category category)
1194 {
1195     QString role = phononCategoryToPulseRole(category);
1196     return register_stream(map, streamUuid, role);
1197 }
1198 
1199 static PulseStream* register_stream(QMap<QString,PulseStream*> &map, QString streamUuid, CaptureCategory category)
1200 {
1201     QString role = phononCaptureCategoryToPulseRole(category);
1202     return register_stream(map, streamUuid, role);
1203 }
1204 
1205 #endif
1206 
1207 PulseStream *PulseSupport::registerOutputStream(QString streamUuid, Category category)
1208 {
1209 #ifndef HAVE_PULSEAUDIO
1210     Q_UNUSED(streamUuid);
1211     Q_UNUSED(category);
1212     return NULL;
1213 #else
1214     return register_stream(s_outputStreams, streamUuid, category);
1215 #endif
1216 }
1217 
1218 PulseStream *PulseSupport::registerCaptureStream(QString streamUuid, CaptureCategory category)
1219 {
1220 #ifndef HAVE_PULSEAUDIO
1221     Q_UNUSED(streamUuid);
1222     Q_UNUSED(category);
1223     return NULL;
1224 #else
1225     return register_stream(s_captureStreams, streamUuid, category);
1226 #endif
1227 }
1228 
1229 PulseStream *PulseSupport::registerCaptureStream(QString streamUuid, Category category)
1230 {
1231 #ifndef HAVE_PULSEAUDIO
1232     Q_UNUSED(streamUuid);
1233     Q_UNUSED(category);
1234     return NULL;
1235 #else
1236     return register_stream(s_captureStreams, streamUuid, category);
1237 #endif
1238 }
1239 
1240 QHash<QString, QString> PulseSupport::streamProperties(QString streamUuid) const
1241 {
1242     QHash<QString, QString> properties;
1243 
1244 #ifdef HAVE_PULSEAUDIO
1245     PulseStream *stream = nullptr;
1246 
1247     // Try to find the stream among the known output streams.
1248     if (!stream)
1249         stream = s_outputStreams.value(streamUuid);
1250 
1251     // Not an output stream, try capture streams.
1252     if (!stream)
1253         stream = s_captureStreams.value(streamUuid);
1254 
1255     // Also no capture stream, start crying and return an empty hash.
1256     if (!stream) {
1257         qWarning() << Q_FUNC_INFO << "Requested UUID Could not be found. Returning with empty properties.";
1258         return properties;
1259     }
1260 
1261     properties[QLatin1String(PA_PROP_PHONON_STREAMID)] = stream->uuid();
1262     properties[QLatin1String(PA_PROP_MEDIA_ROLE)] = stream->role();
1263 
1264     // Tear down environment before returning. This is to prevent backends from
1265     // being overridden by the environment if present.
1266     QHashIterator<QString, QString> it(properties);
1267     while (it.hasNext()) {
1268         it.next();
1269         unsetenv(QString("PULSE_PROP_OVERRIDE_%1").arg(it.key()).toUtf8());
1270     }
1271 #endif // HAVE_PULSEAUDIO
1272 
1273     return properties;
1274 }
1275 
1276 void PulseSupport::setupStreamEnvironment(QString streamUuid)
1277 {
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";
1283 
1284     const QHash<QString, QString> properties = streamProperties(streamUuid);
1285 
1286     QHashIterator<QString, QString> it(properties);
1287     while (it.hasNext()) {
1288         it.next();
1289         pDebug() << "PULSE_PROP_OVERRIDE_" <<  it.key() << " = " << it.value();
1290         qputenv(QString("PULSE_PROP_OVERRIDE_%1").arg(it.key()).toUtf8(), it.value().toUtf8());
1291     }
1292 }
1293 
1294 void PulseSupport::emitObjectDescriptionChanged(ObjectDescriptionType type)
1295 {
1296     if (isUsed())
1297         emit objectDescriptionChanged(type);
1298 }
1299 
1300 bool PulseSupport::setOutputName(QString streamUuid, QString name) {
1301 #ifndef HAVE_PULSEAUDIO
1302     Q_UNUSED(streamUuid);
1303     Q_UNUSED(name);
1304     return false;
1305 #else
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);
1308     Q_UNUSED(name);
1309     return true;
1310 #endif
1311 }
1312 
1313 bool PulseSupport::setOutputDevice(QString streamUuid, int device) {
1314 #ifndef HAVE_PULSEAUDIO
1315     Q_UNUSED(streamUuid);
1316     Q_UNUSED(device);
1317     return false;
1318 #else
1319     if (s_outputDevices.size() < 2)
1320         return true;
1321 
1322     if (!s_outputDevices.contains(device)) {
1323         logMessage(QString::fromLatin1("Attempting to set Output Device for invalid device id %1.").arg(device));
1324         return false;
1325     }
1326     const QVariant var = s_outputDevices[device].properties["name"];
1327     logMessage(QString::fromLatin1("Attempting to set Output Device to '%1' for Output Stream %2").arg(var.toString()).arg(streamUuid));
1328 
1329     // Attempt to look up the pulse stream index.
1330     if (s_outputStreams.contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) {
1331         logMessage(QString::fromLatin1("... Found in map. Moving now"));
1332 
1333         uint32_t pulse_device_index = s_outputDevices[device].pulseIndex;
1334         uint32_t pulse_stream_index = s_outputStreams[streamUuid]->index();
1335 
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));
1337 
1338         /// @todo Find a way to move the stream without saving it... We don't want to pollute the stream restore db.
1339         pa_operation* o;
1340         if (!(o = pa_context_move_sink_input_by_index(s_context, pulse_stream_index, pulse_device_index, nullptr, nullptr))) {
1341             logMessage(QString::fromLatin1("pa_context_move_sink_input_by_index() failed"));
1342             return false;
1343         }
1344         pa_operation_unref(o);
1345     } else {
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"));
1347     }
1348     return true;
1349 #endif
1350 }
1351 
1352 bool PulseSupport::setOutputVolume(QString streamUuid, qreal volume) {
1353 #ifndef HAVE_PULSEAUDIO
1354     Q_UNUSED(streamUuid);
1355     Q_UNUSED(volume);
1356     return false;
1357 #else
1358     logMessage(QString::fromLatin1("Attempting to set volume to %1 for Output Stream %2").arg(volume).arg(streamUuid));
1359 
1360     // Attempt to look up the pulse stream index.
1361     if (s_outputStreams.contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) {
1362         PulseStream *stream = s_outputStreams[streamUuid];
1363 
1364         uint8_t channels = stream->channels();
1365         if (channels < 1) {
1366             logMessage(QString::fromLatin1("Channel count is less than 1. Cannot set volume."));
1367             return false;
1368         }
1369 
1370         pa_cvolume vol;
1371         pa_cvolume_set(&vol, channels, (volume * PA_VOLUME_NORM));
1372 
1373         logMessage(QString::fromLatin1("Found PA index %1. Calling pa_context_set_sink_input_volume()").arg(stream->index()));
1374         pa_operation* o;
1375         if (!(o = pa_context_set_sink_input_volume(s_context, stream->index(), &vol, nullptr, nullptr))) {
1376             logMessage(QString::fromLatin1("pa_context_set_sink_input_volume() failed"));
1377             return false;
1378         }
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);
1384     }
1385     return true;
1386 #endif
1387 }
1388 
1389 bool PulseSupport::setOutputMute(QString streamUuid, bool mute) {
1390 #ifndef HAVE_PULSEAUDIO
1391     Q_UNUSED(streamUuid);
1392     Q_UNUSED(mute);
1393     return false;
1394 #else
1395     logMessage(QString::fromLatin1("Attempting to %1 mute for Output Stream %2").arg(mute ? "set" : "unset").arg(streamUuid));
1396 
1397     // Attempt to look up the pulse stream index.
1398     if (s_outputStreams.contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) {
1399         PulseStream *stream = s_outputStreams[streamUuid];
1400 
1401         logMessage(QString::fromLatin1("Found PA index %1. Calling pa_context_set_sink_input_mute()").arg(stream->index()));
1402         pa_operation* o;
1403         if (!(o = pa_context_set_sink_input_mute(s_context, stream->index(), (mute ? 1 : 0), nullptr, nullptr))) {
1404             logMessage(QString::fromLatin1("pa_context_set_sink_input_mute() failed"));
1405             return false;
1406         }
1407         pa_operation_unref(o);
1408     }
1409     return true;
1410 #endif
1411 }
1412 
1413 bool PulseSupport::setCaptureDevice(QString streamUuid, int device) {
1414 #ifndef HAVE_PULSEAUDIO
1415     Q_UNUSED(streamUuid);
1416     Q_UNUSED(device);
1417     return false;
1418 #else
1419     if (s_captureDevices.size() < 2)
1420         return true;
1421 
1422     if (!s_captureDevices.contains(device)) {
1423         logMessage(QString::fromLatin1("Attempting to set Capture Device for invalid device id %1.").arg(device));
1424         return false;
1425     }
1426     const QVariant var = s_captureDevices[device].properties["name"];
1427     logMessage(QString::fromLatin1("Attempting to set Capture Device to '%1' for Capture Stream %2").arg(var.toString()).arg(streamUuid));
1428 
1429     // Attempt to look up the pulse stream index.
1430     if (s_captureStreams.contains(streamUuid) && s_captureStreams[streamUuid]->index() == PA_INVALID_INDEX) {
1431         logMessage(QString::fromLatin1("... Found in map. Moving now"));
1432 
1433         uint32_t pulse_device_index = s_captureDevices[device].pulseIndex;
1434         uint32_t pulse_stream_index = s_captureStreams[streamUuid]->index();
1435 
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));
1437 
1438         /// @todo Find a way to move the stream without saving it... We don't want to pollute the stream restore db.
1439         pa_operation* o;
1440         if (!(o = pa_context_move_source_output_by_index(s_context, pulse_stream_index, pulse_device_index, nullptr, nullptr))) {
1441             logMessage(QString::fromLatin1("pa_context_move_source_output_by_index() failed"));
1442             return false;
1443         }
1444         pa_operation_unref(o);
1445     } else {
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"));
1447     }
1448     return true;
1449 #endif
1450 }
1451 
1452 void PulseSupport::clearStreamCache(QString streamUuid) {
1453 #ifndef HAVE_PULSEAUDIO
1454     Q_UNUSED(streamUuid);
1455     return;
1456 #else
1457     logMessage(QString::fromLatin1("Clearing stream cache for stream %1").arg(streamUuid));
1458     if (s_outputStreams.contains(streamUuid)) {
1459         PulseStream *stream = s_outputStreams[streamUuid];
1460         s_outputStreams.remove(streamUuid);
1461         delete stream;
1462     } else if (s_captureStreams.contains(streamUuid)) {
1463         PulseStream *stream = s_captureStreams[streamUuid];
1464         s_captureStreams.remove(streamUuid);
1465         delete stream;
1466     }
1467 #endif
1468 }
1469 
1470 } // namespace Phonon
1471 
1472 #include "moc_pulsesupport.cpp"