pulse: Fix the reading of stream information on startup.
[phonon:phonon.git] / phonon / pulsesupport.cpp
1 /*  This file is part of the KDE project
2     Copyright (C) 2009 Colin Guthrie <cguthrie@mandriva.org>
3
4     This library is free software; you can redistribute it and/or
5     modify it under the terms of the GNU Lesser General Public
6     License as published by the Free Software Foundation; either
7     version 2.1 of the License, or (at your option) version 3, or any
8     later version accepted by the membership of KDE e.V. (or its
9     successor approved by the membership of KDE e.V.), Nokia Corporation
10     (or its successors, if any) and the KDE Free Qt Foundation, which shall
11     act as a proxy defined in Section 6 of version 3 of the license.
12
13     This library is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16     Lesser General Public License for more details.
17
18     You should have received a copy of the GNU Lesser General Public
19     License along with this library.  If not, see <http://www.gnu.org/licenses/>.
20
21 */
22
23 #include <QtCore/QAbstractEventDispatcher>
24 #include <QtCore/QDebug>
25 #include <QtCore/QStringList>
26 #include <QTimer>
27
28 #ifdef HAVE_PULSEAUDIO
29 #include "pulsestream_p.h"
30 #include <glib.h>
31 #include <pulse/pulseaudio.h>
32 #include <pulse/xmalloc.h>
33 #include <pulse/glib-mainloop.h>
34 #ifdef HAVE_PULSEAUDIO_DEVICE_MANAGER
35 #  include <pulse/ext-device-manager.h>
36 #endif
37 #endif // HAVE_PULSEAUDIO
38
39 #include "pulsesupport.h"
40
41 QT_BEGIN_NAMESPACE
42
43 namespace Phonon
44 {
45
46 static PulseSupport* s_instance = NULL;
47
48 #ifdef HAVE_PULSEAUDIO
49 /***
50 * Prints a conditional debug message based on the current debug level
51 * If obj is provided, classname and objectname will be printed as well
52 *
53 * see debugLevel()
54 */
55
56 static int debugLevel() {
57     static int level = -1;
58     if (level < 1) {
59         level = 0;
60         QString pulseenv = qgetenv("PHONON_PULSEAUDIO_DEBUG");
61         int l = pulseenv.toInt();
62         if (l > 0)
63             level = (l > 2 ? 2 : l);
64     }
65     return level;
66 }
67
68 static void logMessage(const QString &message, int priority = 2, QObject *obj=0);
69 static void logMessage(const QString &message, int priority, QObject *obj)
70 {
71     if (debugLevel() > 0) {
72         QString output;
73         if (obj) {
74             // Strip away namespace from className
75             QString className(obj->metaObject()->className());
76             int nameLength = className.length() - className.lastIndexOf(':') - 1;
77             className = className.right(nameLength);
78             output.sprintf("%s %s (%s %p)", message.toLatin1().constData(), 
79                            obj->objectName().toLatin1().constData(), 
80                            className.toLatin1().constData(), obj);
81         }
82         else {
83             output = message;
84         }
85         if (priority <= debugLevel()) {
86             qDebug() << QString("PulseSupport(%1): %2").arg(priority).arg(output);
87         }
88     }
89 }
90
91
92 class AudioDevice
93 {
94     public:
95         inline
96         AudioDevice(QString name, QString desc, QString icon, uint32_t index)
97         : pulseName(name), pulseIndex(index)
98         {
99             properties["name"] = desc;
100             properties["description"] = ""; // We don't have descriptions (well we do, but we use them as the name!)
101             properties["icon"] = icon;
102             properties["available"] = (index != PA_INVALID_INDEX);
103             properties["isAdvanced"] = false; // Nothing is advanced!
104         }
105
106         // Needed for QMap
107         inline AudioDevice() {}
108
109         QString pulseName;
110         uint32_t pulseIndex;
111         QHash<QByteArray, QVariant> properties;
112 };
113 bool operator!=(const AudioDevice &a, const AudioDevice &b)
114 {
115     return !(a.pulseName == b.pulseName && a.properties == b.properties);
116 }
117
118 class PulseUserData
119 {
120     public:
121         inline 
122         PulseUserData()
123         {
124         }
125
126         QMap<QString, AudioDevice> newOutputDevices;
127         QMap<Phonon::Category, QMap<int, int> > newOutputDevicePriorities; // prio, device
128
129         QMap<QString, AudioDevice> newCaptureDevices;
130         QMap<Phonon::Category, QMap<int, int> > newCaptureDevicePriorities; // prio, device
131 };
132
133 static QMap<QString, Phonon::Category> s_roleCategoryMap;
134
135 static bool s_pulseActive = false;
136
137 static pa_glib_mainloop *s_mainloop = NULL;
138 static pa_context *s_context = NULL;
139
140
141
142 static int s_deviceIndexCounter = 0;
143
144 static QMap<QString, int> s_outputDeviceIndexes;
145 static QMap<int, AudioDevice> s_outputDevices;
146 static QMap<Phonon::Category, QMap<int, int> > s_outputDevicePriorities; // prio, device
147 static QMap<QString, PulseStream*> s_outputStreams;
148
149 static QMap<QString, int> s_captureDeviceIndexes;
150 static QMap<int, AudioDevice> s_captureDevices;
151 static QMap<Phonon::Category, QMap<int, int> > s_captureDevicePriorities; // prio, device
152 static QMap<QString, PulseStream*> s_captureStreams;
153
154 static PulseStream* findStreamByPulseIndex(QMap<QString, PulseStream*> map, uint32_t index)
155 {
156   QMap<QString, PulseStream*>::iterator it;
157   for (it = map.begin(); it != map.end(); ++it)
158     if ((*it)->index() == index)
159       return *it;
160   return NULL;
161 }
162
163 static void createGenericDevices()
164 {
165     // OK so we don't have the device manager extension, but we can show a single device and fake it.
166     int index;
167     s_outputDeviceIndexes.clear();
168     s_outputDevices.clear();
169     s_outputDevicePriorities.clear();
170     index = s_deviceIndexCounter++;
171     s_outputDeviceIndexes.insert("sink:default", index);
172     s_outputDevices.insert(index, AudioDevice("sink:default", QObject::tr("PulseAudio Sound Server"), "audio-backend-pulseaudio", 0));
173     for (int i = Phonon::NoCategory; i <= Phonon::LastCategory; ++i) {
174         Phonon::Category cat = static_cast<Phonon::Category>(i);
175         s_outputDevicePriorities[cat].insert(0, index);
176     }
177
178     s_captureDeviceIndexes.clear();
179     s_captureDevices.clear();
180     s_captureDevicePriorities.clear();
181     index = s_deviceIndexCounter++;
182     s_captureDeviceIndexes.insert("source:default", index);
183     s_captureDevices.insert(index, AudioDevice("source:default", QObject::tr("PulseAudio Sound Server"), "audio-backend-pulseaudio", 0));
184     for (int i = Phonon::NoCategory; i <= Phonon::LastCategory; ++i) {
185         Phonon::Category cat = static_cast<Phonon::Category>(i);
186         s_captureDevicePriorities[cat].insert(0, index);
187     }
188 }
189
190 #ifdef HAVE_PULSEAUDIO_DEVICE_MANAGER
191 static void ext_device_manager_read_cb(pa_context *c, const pa_ext_device_manager_info *info, int eol, void *userdata) {
192     Q_ASSERT(c);
193     Q_ASSERT(userdata);
194
195     PulseUserData *u = reinterpret_cast<PulseUserData*>(userdata);
196
197     if (eol < 0) {
198         logMessage(QString("Failed to initialize device manager extension: %1").arg(pa_strerror(pa_context_errno(c))));
199         if (s_context != c) {
200             logMessage("Falling back to single device mode");
201             // Only create our gerneric devices during the probe phase.
202             createGenericDevices();
203             // As this is our probe phase, exit immediately
204             pa_context_disconnect(c);
205         }
206         delete u;
207
208         return;
209     }
210
211     if (eol) {
212         // We're done reading the data, so order it by priority and copy it into the
213         // static variables where it can then be accessed by those classes that need it.
214
215         QMap<QString, AudioDevice>::iterator newdev_it;
216
217         // Check for new output devices or things changing about known output devices.
218         bool output_changed = false;
219         for (newdev_it = u->newOutputDevices.begin(); newdev_it != u->newOutputDevices.end(); ++newdev_it) {
220             QString name = newdev_it.key();
221
222             // The name + index map is always written when a new device is added.
223             Q_ASSERT(s_outputDeviceIndexes.contains(name));
224
225             int index = s_outputDeviceIndexes[name];
226             if (!s_outputDevices.contains(index)) {
227                 // This is a totally new device
228                 output_changed = true;
229                 logMessage(QString("Brand New Output Device Found."));
230                 s_outputDevices.insert(index, *newdev_it);
231             } else  if (s_outputDevices[index] != *newdev_it) {
232                 // We have this device already, but is it different?
233                 output_changed = true;
234                 logMessage(QString("Change to Existing Output Device (may be Added/Removed or something else)"));
235                 s_outputDevices.remove(index);
236                 s_outputDevices.insert(index, *newdev_it);
237             }
238         }
239         // Go through the output devices we know about and see if any are no longer mentioned in the list.
240         QMutableMapIterator<QString, int> output_existing_it(s_outputDeviceIndexes);
241         while (output_existing_it.hasNext()) {
242             output_existing_it.next();
243             if (!u->newOutputDevices.contains(output_existing_it.key())) {
244                 output_changed = true;
245                 logMessage(QString("Output Device Completely Removed"));
246                 s_outputDevices.remove(output_existing_it.value());
247                 output_existing_it.remove();
248             }
249         }
250
251         // Check for new capture devices or things changing about known capture devices.
252         bool capture_changed = false;
253         for (newdev_it = u->newCaptureDevices.begin(); newdev_it != u->newCaptureDevices.end(); ++newdev_it) {
254             QString name = newdev_it.key();
255
256             // The name + index map is always written when a new device is added.
257             Q_ASSERT(s_captureDeviceIndexes.contains(name));
258
259             int index = s_captureDeviceIndexes[name];
260             if (!s_captureDevices.contains(index)) {
261                 // This is a totally new device
262                 capture_changed = true;
263                 logMessage(QString("Brand New Capture Device Found."));
264                 s_captureDevices.insert(index, *newdev_it);
265             } else  if (s_captureDevices[index] != *newdev_it) {
266                 // We have this device already, but is it different?
267                 capture_changed = true;
268                 logMessage(QString("Change to Existing Capture Device (may be Added/Removed or something else)"));
269                 s_captureDevices.remove(index);
270                 s_captureDevices.insert(index, *newdev_it);
271             }
272         }
273         // Go through the capture devices we know about and see if any are no longer mentioned in the list.
274         QMutableMapIterator<QString, int> capture_existing_it(s_captureDeviceIndexes);
275         while (capture_existing_it.hasNext()) {
276             capture_existing_it.next();
277             if (!u->newCaptureDevices.contains(capture_existing_it.key())) {
278                 capture_changed = true;
279                 logMessage(QString("Capture Device Completely Removed"));
280                 s_captureDevices.remove(capture_existing_it.value());
281                 capture_existing_it.remove();
282             }
283         }
284
285         // Just copy accross the new priority lists as we know they are valid
286         if (s_outputDevicePriorities != u->newOutputDevicePriorities) {
287             output_changed = true;
288             s_outputDevicePriorities = u->newOutputDevicePriorities;
289         }
290         if (s_captureDevicePriorities != u->newCaptureDevicePriorities) {
291             capture_changed = true;
292             s_captureDevicePriorities = u->newCaptureDevicePriorities;
293         }
294
295         if (s_instance) {
296             // This wont be emitted durring the connection probe phase
297             // which is intensional
298             if (output_changed)
299                 s_instance->emitObjectDescriptionChanged(AudioOutputDeviceType);
300             if (capture_changed)
301                 s_instance->emitObjectDescriptionChanged(AudioCaptureDeviceType);
302         }
303
304         // We can free the user data as we will not be called again.
305         delete u;
306
307         // Some debug
308         logMessage(QString("Output Device Priority List:"));
309         for (int i = Phonon::NoCategory; i <= Phonon::LastCategory; ++i) {
310             Phonon::Category cat = static_cast<Phonon::Category>(i);
311             if (s_outputDevicePriorities.contains(cat)) {
312                 logMessage(QString("  Phonon Category %1").arg(cat));
313                 int count = 0;
314                 foreach (int j, s_outputDevicePriorities[cat]) {
315                     QHash<QByteArray, QVariant> &props = s_outputDevices[j].properties;
316                     logMessage(QString("    %1. %2 (Available: %3)").arg(++count).arg(props["name"].toString()).arg(props["available"].toBool()));
317                 }
318             }
319         }
320         logMessage(QString("Capture Device Priority List:"));
321         for (int i = Phonon::NoCategory; i <= Phonon::LastCategory; ++i) {
322             Phonon::Category cat = static_cast<Phonon::Category>(i);
323             if (s_captureDevicePriorities.contains(cat)) {
324                 logMessage(QString("  Phonon Category %1").arg(cat));
325                 int count = 0;
326                 foreach (int j, s_captureDevicePriorities[cat]) {
327                     QHash<QByteArray, QVariant> &props = s_captureDevices[j].properties;
328                     logMessage(QString("    %1. %2 (Available: %3)").arg(++count).arg(props["name"].toString()).arg(props["available"].toBool()));
329                 }
330             }
331         }
332
333         // If this is our probe phase, exit now as we're finished reading
334         // our device info and can exit and reconnect
335         if (s_context != c)
336             pa_context_disconnect(c);
337     }
338
339     if (!info)
340         return;
341
342     Q_ASSERT(info->name);
343     Q_ASSERT(info->description);
344     Q_ASSERT(info->icon);
345
346     // QString wrapper
347     QString name(info->name);
348     int index;
349     QMap<Phonon::Category, QMap<int, int> > *new_prio_map_cats; // prio, device
350     QMap<QString, AudioDevice> *new_devices;
351
352     if (name.startsWith("sink:")) {
353         new_devices = &u->newOutputDevices;
354         new_prio_map_cats = &u->newOutputDevicePriorities;
355
356         if (s_outputDeviceIndexes.contains(name))
357             index = s_outputDeviceIndexes[name];
358         else
359             index = s_outputDeviceIndexes[name] = s_deviceIndexCounter++;
360     } else if (name.startsWith("source:")) {
361         new_devices = &u->newCaptureDevices;
362         new_prio_map_cats = &u->newCaptureDevicePriorities;
363
364         if (s_captureDeviceIndexes.contains(name))
365             index = s_captureDeviceIndexes[name];
366         else
367             index = s_captureDeviceIndexes[name] = s_deviceIndexCounter++;
368     } else {
369         // This indicates a bug in pulseaudio.
370         return;
371     }
372
373     // Add the new device itself.
374     new_devices->insert(name, AudioDevice(name, QString::fromUtf8(info->description), QString::fromUtf8(info->icon), info->index));
375
376     // For each role in the priority, map it to a phonon category and store the order.
377     for (uint32_t i = 0; i < info->n_role_priorities; ++i) {
378         pa_ext_device_manager_role_priority_info* role_prio = &info->role_priorities[i];
379         Q_ASSERT(role_prio->role);
380
381         if (s_roleCategoryMap.contains(role_prio->role)) {
382             Phonon::Category cat = s_roleCategoryMap[role_prio->role];
383
384             (*new_prio_map_cats)[cat].insert(role_prio->priority, index);
385         }
386     }
387 }
388
389 static void ext_device_manager_subscribe_cb(pa_context *c, void *) {
390     Q_ASSERT(c);
391
392     pa_operation *o;
393     PulseUserData *u = new PulseUserData;
394     if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) {
395         logMessage(QString("pa_ext_device_manager_read() failed."));
396         delete u;
397         return;
398     }
399     pa_operation_unref(o);
400 }
401 #endif
402
403 void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
404     Q_UNUSED(userdata);
405     Q_ASSERT(c);
406
407     if (eol < 0) {
408         if (pa_context_errno(c) == PA_ERR_NOENTITY)
409             return;
410
411         logMessage(QString("Sink input callback failure"));
412         return;
413     }
414
415     if (eol > 0)
416         return;
417
418     Q_ASSERT(i);
419
420     // loop through (*i) and extract phonon->streamindex...
421     const char *t;
422     if ((t = pa_proplist_gets(i->proplist, "phonon.streamid"))) {
423         logMessage(QString("Found PulseAudio stream index %1 for Phonon Output Stream %2").arg(i->index).arg(t));
424
425         // We only care about our own streams (other phonon processes are irrelevent)
426         if (s_outputStreams.contains(QString(t))) {
427             PulseStream *stream = s_outputStreams[QString(t)];
428             stream->setIndex(i->index);
429             stream->setVolume(&i->volume);
430             stream->setMute(!!i->mute);
431
432             // Find the sink's phonon index and notify whoever cares...
433             if (PA_INVALID_INDEX != i->sink) {
434                 QMap<int, AudioDevice>::iterator it;
435                 for (it = s_outputDevices.begin(); it != s_outputDevices.end(); ++it) {
436                     if ((*it).pulseIndex == i->sink) {
437                         stream->setDevice(it.key());
438                         break;
439                     }
440                 }
441             }
442         }
443     }
444 }
445
446 void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, void *userdata) {
447     Q_UNUSED(userdata);
448     Q_ASSERT(c);
449
450     if (eol < 0) {
451         if (pa_context_errno(c) == PA_ERR_NOENTITY)
452             return;
453
454         logMessage(QString("Source output callback failure"));
455         return;
456     }
457
458     if (eol > 0)
459         return;
460
461     Q_ASSERT(i);
462
463     // loop through (*i) and extract phonon->streamindex...
464     const char *t;
465     if ((t = pa_proplist_gets(i->proplist, "phonon.streamid"))) {
466         logMessage(QString("Found PulseAudio stream index %1 for Phonon Capture Stream %2").arg(i->index).arg(t));
467
468         // We only care about our own streams (other phonon processes are irrelevent)
469         if (s_captureStreams.contains(QString(t))) {
470             PulseStream *stream = s_captureStreams[QString(t)];
471             stream->setIndex(i->index);
472             //stream->setVolume(&i->volume);
473             //stream->setMute(!!i->mute);
474
475             // Find the source's phonon index and notify whoever cares...
476             if (PA_INVALID_INDEX != i->source) {
477                 QMap<int, AudioDevice>::iterator it;
478                 for (it = s_captureDevices.begin(); it != s_captureDevices.end(); ++it) {
479                     if ((*it).pulseIndex == i->source) {
480                         stream->setDevice(it.key());
481                         break;
482                     }
483                 }
484             }
485         }
486     }
487 }
488
489 static void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) {
490     Q_UNUSED(userdata);
491
492     switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
493         case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
494             if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
495                 PulseStream *stream = findStreamByPulseIndex(s_outputStreams, index);
496                 if (stream) {
497                     logMessage(QString("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()));
498                     stream->setIndex(PA_INVALID_INDEX);
499                 }
500             } else {
501                 pa_operation *o;
502                 if (!(o = pa_context_get_sink_input_info(c, index, sink_input_cb, NULL))) {
503                     logMessage(QString("pa_context_get_sink_input_info() failed"));
504                     return;
505                 }
506                 pa_operation_unref(o);
507             }
508             break;
509
510         case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
511             if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
512               PulseStream *stream = findStreamByPulseIndex(s_captureStreams, index);
513               if (stream) {
514                     logMessage(QString("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()));
515                     stream->setIndex(PA_INVALID_INDEX);
516                 }
517             } else {
518                 pa_operation *o;
519                 if (!(o = pa_context_get_source_output_info(c, index, source_output_cb, NULL))) {
520                     logMessage(QString("pa_context_get_sink_input_info() failed"));
521                     return;
522                 }
523                 pa_operation_unref(o);
524             }
525             break;
526     }
527 }
528
529
530 static const char* statename(pa_context_state_t state)
531 {
532     switch (state)
533     {
534         case PA_CONTEXT_UNCONNECTED:  return "Unconnected";
535         case PA_CONTEXT_CONNECTING:   return "Connecting";
536         case PA_CONTEXT_AUTHORIZING:  return "Authorizing";
537         case PA_CONTEXT_SETTING_NAME: return "Setting Name";
538         case PA_CONTEXT_READY:        return "Ready";
539         case PA_CONTEXT_FAILED:       return "Failed";
540         case PA_CONTEXT_TERMINATED:   return "Terminated";
541     }
542
543     static QString unknown;
544     unknown = QString("Unknown state: %0").arg(state);
545     return unknown.toAscii().constData();
546 }
547
548 static void context_state_callback(pa_context *c, void *)
549 {
550     Q_ASSERT(c);
551
552     logMessage(QString("context_state_callback %1").arg(statename(pa_context_get_state(c))));
553     pa_context_state_t state = pa_context_get_state(c);
554     if (state == PA_CONTEXT_READY) {
555         // We've connected to PA, so it is active
556         s_pulseActive = true;
557
558         // Attempt to load things up
559         pa_operation *o;
560
561         // 1. Register for the stream changes (except during probe)
562         if (s_context == c) {
563             pa_context_set_subscribe_callback(c, subscribe_cb, NULL);
564
565             if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
566                                               (PA_SUBSCRIPTION_MASK_SINK_INPUT|
567                                                PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) {
568                 logMessage(QString("pa_context_subscribe() failed"));
569                 return;
570             }
571             pa_operation_unref(o);
572
573             // In the case of reconnection or simply lagging behind the stream object creation
574             // on startup (due to the probe+reconnect system), we invalidate all loaded streams
575             // and then load up info about all streams.
576             for (QMap<QString, PulseStream*>::iterator it = s_outputStreams.begin(); it != s_outputStreams.end(); ++it) {
577               PulseStream *stream = *it;
578               logMessage(QString("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()));
579               stream->setIndex(PA_INVALID_INDEX);
580             }
581             if (!(o = pa_context_get_sink_input_info_list(c, sink_input_cb, NULL))) {
582               logMessage(QString("pa_context_get_sink_input_info_list() failed"));
583               return;
584             }
585             pa_operation_unref(o);
586
587             for (QMap<QString, PulseStream*>::iterator it = s_captureStreams.begin(); it != s_captureStreams.end(); ++it) {
588               PulseStream *stream = *it;
589               logMessage(QString("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()));
590               stream->setIndex(PA_INVALID_INDEX);
591             }
592             if (!(o = pa_context_get_source_output_info_list(c, source_output_cb, NULL))) {
593               logMessage(QString("pa_context_get_source_output_info_list() failed"));
594               return;
595             }
596             pa_operation_unref(o);
597         }
598
599 #ifdef HAVE_PULSEAUDIO_DEVICE_MANAGER
600         // 2a. Attempt to initialise Device Manager info (except during probe)
601         if (s_context == c) {
602             pa_ext_device_manager_set_subscribe_cb(c, ext_device_manager_subscribe_cb, NULL);
603             if (!(o = pa_ext_device_manager_subscribe(c, 1, NULL, NULL))) {
604                 logMessage(QString("pa_ext_device_manager_subscribe() failed"));
605                 return;
606             }
607             pa_operation_unref(o);
608         }
609
610         // 3. Attempt to read info from Device Manager
611         PulseUserData *u = new PulseUserData;
612         if (!(o = pa_ext_device_manager_read(c, ext_device_manager_read_cb, u))) {
613             if (s_context != c) {
614                 logMessage(QString("pa_ext_device_manager_read() failed. Attempting to continue without device manager support"));
615                 // Only create our gerneric devices during the probe phase.
616                 createGenericDevices();
617                 // As this is our probe phase, exit immediately
618                 pa_context_disconnect(c);
619             }
620             delete u;
621
622             return;
623         }
624         pa_operation_unref(o);
625
626 #else
627         // If we know do not have Device Manager support, we just create our dummy devices now
628         if (s_context != c) {
629             // Only create our gerneric devices during the probe phase.
630             createGenericDevices();
631             // As this is our probe phase, exit immediately
632             pa_context_disconnect(c);
633         }
634
635 #endif
636     } else if (!PA_CONTEXT_IS_GOOD(state)) {
637         /// @todo Deal with reconnection...
638         //logMessage(QString("Connection to PulseAudio lost: %1").arg(pa_strerror(pa_context_errno(c))));
639
640         // If this is our probe phase, exit our context immediately
641         if (s_context != c)
642             pa_context_disconnect(c);
643         else {
644             pa_context_unref(s_context);
645             s_context = NULL;
646             QTimer::singleShot(50, PulseSupport::getInstance(), SLOT(connectToDaemon()));
647         }
648     }
649 }
650 #endif // HAVE_PULSEAUDIO
651
652
653 PulseSupport* PulseSupport::getInstance()
654 {
655     if (NULL == s_instance) {
656         s_instance = new PulseSupport();
657     }
658     return s_instance;
659 }
660
661 void PulseSupport::shutdown()
662 {
663     if (NULL != s_instance) {
664         delete s_instance;
665         s_instance = NULL;
666     }
667 }
668
669 PulseSupport::PulseSupport()
670  : QObject(), mEnabled(false)
671 {
672 #ifdef HAVE_PULSEAUDIO
673     // Initialise our map (is there a better way to do this?)
674     s_roleCategoryMap["none"] = Phonon::NoCategory;
675     s_roleCategoryMap["video"] = Phonon::VideoCategory;
676     s_roleCategoryMap["music"] = Phonon::MusicCategory;
677     s_roleCategoryMap["game"] = Phonon::GameCategory;
678     s_roleCategoryMap["event"] = Phonon::NotificationCategory;
679     s_roleCategoryMap["phone"] = Phonon::CommunicationCategory;
680     //s_roleCategoryMap["animation"]; // No Mapping
681     //s_roleCategoryMap["production"]; // No Mapping
682     s_roleCategoryMap["a11y"] = Phonon::AccessibilityCategory;
683
684     // To allow for easy debugging, give an easy way to disable this pulseaudio check
685     QString pulseenv = qgetenv("PHONON_PULSEAUDIO_DISABLE");
686     if (pulseenv.toInt()) {
687         logMessage("PulseAudio support disabled: PHONON_PULSEAUDIO_DISABLE is set");
688         return;
689     }
690
691     // We require a glib event loop
692     if (QLatin1String(QAbstractEventDispatcher::instance()->metaObject()->className())
693             != "QGuiEventDispatcherGlib") {
694         logMessage("Disabling PulseAudio integration for lack of GLib event loop.");
695         return;
696     }
697
698     // First of all conenct to PA via simple/blocking means and if that succeeds,
699     // use a fully async integrated mainloop method to connect and get proper support.
700     pa_mainloop *p_test_mainloop;
701     if (!(p_test_mainloop = pa_mainloop_new())) {
702         logMessage("PulseAudio support disabled: Unable to create mainloop");
703         return;
704     }
705
706     pa_context *p_test_context;
707     if (!(p_test_context = pa_context_new(pa_mainloop_get_api(p_test_mainloop), "libphonon-probe"))) {
708         logMessage("PulseAudio support disabled: Unable to create context");
709         pa_mainloop_free(p_test_mainloop);
710         return;
711     }
712
713     logMessage("Probing for PulseAudio...");
714     // (cg) Convert to PA_CONTEXT_NOFLAGS when PulseAudio 0.9.19 is required
715     if (pa_context_connect(p_test_context, NULL, static_cast<pa_context_flags_t>(0), NULL) < 0) {
716         logMessage(QString("PulseAudio support disabled: %1").arg(pa_strerror(pa_context_errno(p_test_context))));
717         pa_context_disconnect(p_test_context);
718         pa_context_unref(p_test_context);
719         pa_mainloop_free(p_test_mainloop);
720         return;
721     }
722
723     pa_context_set_state_callback(p_test_context, &context_state_callback, NULL);
724     for (;;) {
725         pa_mainloop_iterate(p_test_mainloop, 1, NULL);
726
727         if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(p_test_context))) {
728             logMessage("PulseAudio probe complete.");
729             break;
730         }
731     }
732     pa_context_disconnect(p_test_context);
733     pa_context_unref(p_test_context);
734     pa_mainloop_free(p_test_mainloop);
735
736     if (!s_pulseActive) {
737         logMessage("PulseAudio support is not available.");
738         return;
739     }
740
741     // If we're still here, PA is available.
742     logMessage("PulseAudio support enabled");
743
744     // Now we connect for real using a proper main loop that we can forget
745     // all about processing.
746     s_mainloop = pa_glib_mainloop_new(NULL);
747     Q_ASSERT(s_mainloop);
748
749     connectToDaemon();
750 #endif
751 }
752
753 PulseSupport::~PulseSupport()
754 {
755 #ifdef HAVE_PULSEAUDIO
756     if (s_context) {
757         pa_context_disconnect(s_context);
758         s_context = NULL;
759     }
760
761     if (s_mainloop) {
762         pa_glib_mainloop_free(s_mainloop);
763         s_mainloop = NULL;
764     }
765 #endif
766 }
767
768
769 void PulseSupport::connectToDaemon()
770 {
771 #ifdef HAVE_PULSEAUDIO
772     pa_mainloop_api *api = pa_glib_mainloop_get_api(s_mainloop);
773
774     s_context = pa_context_new(api, "libphonon");
775     if (pa_context_connect(s_context, NULL, PA_CONTEXT_NOFAIL, 0) >= 0)
776         pa_context_set_state_callback(s_context, &context_state_callback, NULL);
777 #endif
778 }
779
780 bool PulseSupport::isActive()
781 {
782 #ifdef HAVE_PULSEAUDIO
783     //logMessage(QString("Enabled Breakdown: mEnabled: %1, s_pulseActive %2").arg(mEnabled).arg(s_pulseActive));
784     return mEnabled && s_pulseActive;
785 #else
786     return false;
787 #endif
788 }
789
790 void PulseSupport::enable(bool enabled)
791 {
792     mEnabled = enabled;
793 }
794
795 QList<int> PulseSupport::objectDescriptionIndexes(ObjectDescriptionType type) const
796 {
797     QList<int> list;
798
799     if (type != AudioOutputDeviceType && type != AudioCaptureDeviceType)
800         return list;
801
802 #ifdef HAVE_PULSEAUDIO
803     if (s_pulseActive) {
804         switch (type) {
805
806             case AudioOutputDeviceType: {
807                 QMap<QString, int>::iterator it;
808                 for (it = s_outputDeviceIndexes.begin(); it != s_outputDeviceIndexes.end(); ++it) {
809                     list.append(*it);
810                 }
811                 break;
812             }
813             case AudioCaptureDeviceType: {
814                 QMap<QString, int>::iterator it;
815                 for (it = s_captureDeviceIndexes.begin(); it != s_captureDeviceIndexes.end(); ++it) {
816                     list.append(*it);
817                 }
818                 break;
819             }
820             default:
821                 break;
822         }
823     }
824 #endif
825
826     return list;
827 }
828
829 QHash<QByteArray, QVariant> PulseSupport::objectDescriptionProperties(ObjectDescriptionType type, int index) const
830 {
831     QHash<QByteArray, QVariant> ret;
832
833     if (type != AudioOutputDeviceType && type != AudioCaptureDeviceType)
834         return ret;
835
836 #ifndef HAVE_PULSEAUDIO
837     Q_UNUSED(index);
838 #else
839     if (s_pulseActive) {
840         switch (type) {
841
842             case AudioOutputDeviceType:
843                 Q_ASSERT(s_outputDevices.contains(index));
844                 ret = s_outputDevices[index].properties;
845                 break;
846
847             case AudioCaptureDeviceType:
848                 Q_ASSERT(s_captureDevices.contains(index));
849                 ret = s_captureDevices[index].properties;
850                 break;
851
852             default:
853                 break;
854         }
855     }
856 #endif
857
858     return ret;
859 }
860
861 QList<int> PulseSupport::objectIndexesByCategory(ObjectDescriptionType type, Category category) const
862 {
863     QList<int> ret;
864
865     if (type != AudioOutputDeviceType && type != AudioCaptureDeviceType)
866         return ret;
867
868 #ifndef HAVE_PULSEAUDIO
869     Q_UNUSED(category);
870 #else
871     if (s_pulseActive) {
872         switch (type) {
873
874             case AudioOutputDeviceType:
875                 if (s_outputDevicePriorities.contains(category))
876                     ret = s_outputDevicePriorities[category].values();
877                 break;
878
879             case AudioCaptureDeviceType:
880                 if (s_captureDevicePriorities.contains(category))
881                     ret = s_captureDevicePriorities[category].values();
882                 break;
883
884             default:
885                 break;
886         }
887     }
888 #endif
889
890     return ret;
891 }
892
893 #ifdef HAVE_PULSEAUDIO
894 static void setDevicePriority(Category category, QStringList list)
895 {
896     QString role = s_roleCategoryMap.key(category);
897     if (role.isEmpty())
898         return;
899
900     logMessage(QString("Reindexing %1: %2").arg(role).arg(list.join(", ")));
901
902     char **devices;
903     devices = pa_xnew(char *, list.size()+1);
904     int i = 0;
905     foreach (QString str, list) {
906         devices[i++] = pa_xstrdup(str.toUtf8().constData());
907     }
908     devices[list.size()] = NULL;
909
910 #ifdef HAVE_PULSEAUDIO_DEVICE_MANAGER 
911     pa_operation *o;
912     if (!(o = pa_ext_device_manager_reorder_devices_for_role(s_context, role.toUtf8().constData(), (const char**)devices, NULL, NULL)))
913         logMessage(QString("pa_ext_device_manager_reorder_devices_for_role() failed"));
914     else
915         pa_operation_unref(o);
916 #endif
917
918     for (i = 0; i < list.size(); ++i)
919         pa_xfree(devices[i]);
920     pa_xfree(devices);
921 }
922 #endif
923
924 void PulseSupport::setOutputDevicePriorityForCategory(Category category, QList<int> order)
925 {
926 #ifndef HAVE_PULSEAUDIO
927     Q_UNUSED(category);
928     Q_UNUSED(order);
929 #else
930     QStringList list;
931     QList<int>::iterator it;
932
933     for (it = order.begin(); it != order.end(); ++it) {
934         if (s_outputDevices.contains(*it)) {
935             list << s_outputDeviceIndexes.key(*it);
936         }
937     }
938     setDevicePriority(category, list);
939 #endif
940 }
941
942 void PulseSupport::setCaptureDevicePriorityForCategory(Category category, QList<int> order)
943 {
944 #ifndef HAVE_PULSEAUDIO
945     Q_UNUSED(category);
946     Q_UNUSED(order);
947 #else
948     QStringList list;
949     QList<int>::iterator it;
950
951     for (it = order.begin(); it != order.end(); ++it) {
952         if (s_captureDevices.contains(*it)) {
953             list << s_captureDeviceIndexes.key(*it);
954         }
955     }
956     setDevicePriority(category, list);
957 #endif
958 }
959
960 #ifdef HAVE_PULSEAUDIO
961 static PulseStream* register_stream(QMap<QString,PulseStream*> &map, QString streamUuid, Category category)
962 {
963     logMessage(QString("Initialising streamindex %1").arg(streamUuid));
964     QString role = s_roleCategoryMap.key(category);
965     if (!role.isEmpty()) {
966         logMessage(QString("Setting role to %1 for streamindex %2").arg(role).arg(streamUuid));
967         setenv("PULSE_PROP_media.role", role.toLatin1().constData(), 1);
968     }
969     setenv("PULSE_PROP_phonon.streamid", streamUuid.toLatin1().constData(), 1);
970     PulseStream *stream = new PulseStream(streamUuid);
971     map[streamUuid] = stream;
972     return stream;
973 }
974 #endif
975
976 PulseStream *PulseSupport::registerOutputStream(QString streamUuid, Category category)
977 {
978 #ifndef HAVE_PULSEAUDIO
979     Q_UNUSED(streamUuid);
980     Q_UNUSED(category);
981     return NULL;
982 #else
983     return register_stream(s_outputStreams, streamUuid, category);
984 #endif
985 }
986
987 PulseStream *PulseSupport::registerCaptureStream(QString streamUuid, Category category)
988 {
989 #ifndef HAVE_PULSEAUDIO
990     Q_UNUSED(streamUuid);
991     Q_UNUSED(category);
992     return NULL;
993 #else
994     return register_stream(s_captureStreams, streamUuid, category);
995 #endif
996 }
997
998 void PulseSupport::emitObjectDescriptionChanged(ObjectDescriptionType type)
999 {
1000     emit objectDescriptionChanged(type);
1001 }
1002
1003 bool PulseSupport::setOutputName(QString streamUuid, QString name) {
1004 #ifndef HAVE_PULSEAUDIO
1005     Q_UNUSED(streamUuid);
1006     Q_UNUSED(name);
1007     return false;
1008 #else
1009     logMessage(QString("Unimplemented: Need to find a way to set either application.name or media.name in SI proplist"));
1010     Q_UNUSED(streamUuid);
1011     Q_UNUSED(name);
1012     return true;
1013 #endif
1014 }
1015
1016 bool PulseSupport::setOutputDevice(QString streamUuid, int device) {
1017 #ifndef HAVE_PULSEAUDIO
1018     Q_UNUSED(streamUuid);
1019     Q_UNUSED(device);
1020     return false;
1021 #else
1022     if (s_outputDevices.size() < 2)
1023         return true;
1024
1025     if (!s_outputDevices.contains(device)) {
1026         logMessage(QString("Attempting to set Output Device for invalid device id %1.").arg(device));
1027         return false;
1028     }
1029     const QVariant var = s_outputDevices[device].properties["name"];
1030     logMessage(QString("Attempting to set Output Device to '%1' for Output Stream %2").arg(var.toString()).arg(streamUuid));
1031
1032     // Attempt to look up the pulse stream index.
1033     if (s_outputStreams.contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) {
1034         logMessage(QString("... Found in map. Moving now"));
1035
1036         uint32_t pulse_device_index = s_outputDevices[device].pulseIndex;
1037         uint32_t pulse_stream_index = s_outputStreams[streamUuid]->index();
1038
1039         logMessage(QString("Moving Pulse Sink Input %1 to '%2' (Pulse Sink %3)").arg(pulse_stream_index).arg(var.toString()).arg(pulse_device_index));
1040
1041         /// @todo Find a way to move the stream without saving it... We don't want to pollute the stream restore db.
1042         pa_operation* o;
1043         if (!(o = pa_context_move_sink_input_by_index(s_context, pulse_stream_index, pulse_device_index, NULL, NULL))) {
1044             logMessage(QString("pa_context_move_sink_input_by_index() failed"));
1045             return false;
1046         }
1047         pa_operation_unref(o);
1048     } else {
1049         logMessage(QString("... Not found in map. We will be notified of the device when the stream appears and we can process any moves needed then"));
1050     }
1051     return true;
1052 #endif
1053 }
1054
1055 bool PulseSupport::setOutputVolume(QString streamUuid, qreal volume) {
1056 #ifndef HAVE_PULSEAUDIO
1057     Q_UNUSED(streamUuid);
1058     Q_UNUSED(volume);
1059     return false;
1060 #else
1061     logMessage(QString("Attempting to set volume to %1 for Output Stream %2").arg(volume).arg(streamUuid));
1062
1063     // Attempt to look up the pulse stream index.
1064     if (s_outputStreams.contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) {
1065         PulseStream *stream = s_outputStreams[streamUuid];
1066
1067         uint8_t channels = stream->channels();
1068         if (channels < 1) {
1069             logMessage("Channel count is less than 1. Cannot set volume.");
1070             return false;
1071         }
1072
1073         pa_cvolume vol;
1074         pa_cvolume_set(&vol, channels, (volume * PA_VOLUME_NORM));
1075
1076         logMessage(QString("Found PA index %1. Calling pa_context_set_sink_input_volume()").arg(stream->index()));
1077         pa_operation* o;
1078         if (!(o = pa_context_set_sink_input_volume(s_context, stream->index(), &vol, NULL, NULL))) {
1079             logMessage(QString("pa_context_set_sink_input_volume() failed"));
1080             return false;
1081         }
1082         pa_operation_unref(o);
1083     }
1084     return true;
1085 #endif
1086 }
1087
1088 bool PulseSupport::setOutputMute(QString streamUuid, bool mute) {
1089 #ifndef HAVE_PULSEAUDIO
1090     Q_UNUSED(streamUuid);
1091     Q_UNUSED(mute);
1092     return false;
1093 #else
1094     logMessage(QString("Attempting to %1 mute for Output Stream %2").arg(mute ? "set" : "unset").arg(streamUuid));
1095
1096     // Attempt to look up the pulse stream index.
1097     if (s_outputStreams.contains(streamUuid) && s_outputStreams[streamUuid]->index() != PA_INVALID_INDEX) {
1098         PulseStream *stream = s_outputStreams[streamUuid];
1099
1100         logMessage(QString("Found PA index %1. Calling pa_context_set_sink_input_mute()").arg(stream->index()));
1101         pa_operation* o;
1102         if (!(o = pa_context_set_sink_input_mute(s_context, stream->index(), (mute ? 1 : 0), NULL, NULL))) {
1103             logMessage(QString("pa_context_set_sink_input_mute() failed"));
1104             return false;
1105         }
1106         pa_operation_unref(o);
1107     }
1108     return true;
1109 #endif
1110 }
1111
1112 bool PulseSupport::setCaptureDevice(QString streamUuid, int device) {
1113 #ifndef HAVE_PULSEAUDIO
1114     Q_UNUSED(streamUuid);
1115     Q_UNUSED(device);
1116     return false;
1117 #else
1118     if (s_captureDevices.size() < 2)
1119         return true;
1120
1121     if (!s_captureDevices.contains(device)) {
1122         logMessage(QString("Attempting to set Capture Device for invalid device id %1.").arg(device));
1123         return false;
1124     }
1125     const QVariant var = s_captureDevices[device].properties["name"];
1126     logMessage(QString("Attempting to set Capture Device to '%1' for Capture Stream %2").arg(var.toString()).arg(streamUuid));
1127
1128     // Attempt to look up the pulse stream index.
1129     if (s_captureStreams.contains(streamUuid) && s_captureStreams[streamUuid]->index() == PA_INVALID_INDEX) {
1130         logMessage(QString("... Found in map. Moving now"));
1131
1132         uint32_t pulse_device_index = s_captureDevices[device].pulseIndex;
1133         uint32_t pulse_stream_index = s_captureStreams[streamUuid]->index();
1134
1135         logMessage(QString("Moving Pulse Source Output %1 to '%2' (Pulse Sink %3)").arg(pulse_stream_index).arg(var.toString()).arg(pulse_device_index));
1136
1137         /// @todo Find a way to move the stream without saving it... We don't want to pollute the stream restore db.
1138         pa_operation* o;
1139         if (!(o = pa_context_move_source_output_by_index(s_context, pulse_stream_index, pulse_device_index, NULL, NULL))) {
1140             logMessage(QString("pa_context_move_source_output_by_index() failed"));
1141             return false;
1142         }
1143         pa_operation_unref(o);
1144     } else {
1145         logMessage(QString("... Not found in map. We will be notified of the device when the stream appears and we can process any moves needed then"));
1146     }
1147     return true;
1148 #endif
1149 }
1150
1151 void PulseSupport::clearStreamCache(QString streamUuid) {
1152 #ifndef HAVE_PULSEAUDIO
1153     Q_UNUSED(streamUuid);
1154     return;
1155 #else
1156     logMessage(QString("Clearing stream cache for stream %1").arg(streamUuid));
1157     if (s_outputStreams.contains(streamUuid)) {
1158         PulseStream *stream = s_outputStreams[streamUuid];
1159         s_outputStreams.remove(streamUuid);
1160         delete stream;
1161     } else if (s_captureStreams.contains(streamUuid)) {
1162         PulseStream *stream = s_captureStreams[streamUuid];
1163         s_captureStreams.remove(streamUuid);
1164         delete stream;
1165     }
1166 #endif
1167 }
1168
1169 } // namespace Phonon
1170
1171 QT_END_NAMESPACE
1172
1173 #include "moc_pulsesupport.cpp"
1174
1175 // vim: sw=4 ts=4