delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / runtime / phonon / libkaudiodevicelist / audiodeviceenumerator.cpp
blob05ff4f5490cc2518251d4c3deb6daa041147302a
1 /* This file is part of the KDE project
2 Copyright (C) 2006 Matthias Kretz <kretz@kde.org>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
20 #include "audiodeviceenumerator.h"
21 #include "audiodeviceenumerator_p.h"
22 #include "audiodevice_p.h"
23 #include <QtCore/QFileSystemWatcher>
24 #include <QtCore/QCoreApplication>
25 #include <QtCore/QDir>
26 #include <QtCore/QSet>
27 #include <solid/devicenotifier.h>
28 #include <solid/device.h>
29 #include <solid/audiointerface.h>
30 #include <kconfiggroup.h>
31 #include <kglobal.h>
32 #include <kdebug.h>
33 #include <../config-alsa.h>
35 #ifdef HAVE_LIBASOUND2
36 #include <alsa/asoundlib.h>
37 #endif // HAVE_LIBASOUND2
39 namespace Phonon
42 K_GLOBAL_STATIC(AudioDeviceEnumeratorPrivate, audioDeviceEnumeratorPrivate)
44 AudioDeviceEnumerator::AudioDeviceEnumerator(AudioDeviceEnumeratorPrivate *dd)
45 : d(dd)
49 AudioDeviceEnumeratorPrivate::AudioDeviceEnumeratorPrivate()
50 : q(this)
52 config = KSharedConfig::openConfig("phonondevicesrc", KConfig::NoGlobals);
53 findDevices();
54 QObject::connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(const QString &)), &q, SLOT(_k_deviceAdded(const QString &)));
55 QObject::connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(const QString &)), &q, SLOT(_k_deviceRemoved(const QString &)));
58 AudioDeviceEnumerator *AudioDeviceEnumerator::self()
60 return &audioDeviceEnumeratorPrivate->q;
63 void AudioDeviceEnumeratorPrivate::findDevices()
65 KConfigGroup globalConfigGroup(config, "Globals");
66 // for future use: const int cacheVersion = globalConfigGroup.readEntry("CacheVersion", 0);
67 QSet<QString> alreadyFoundCards;
69 // ask Solid for the available audio hardware
70 const QList<Solid::Device> devices = Solid::Device::listFromQuery("AudioInterface.deviceType & 'AudioInput|AudioOutput'");
71 foreach (const Solid::Device &device, devices) {
72 AudioDevice dev(device, config);
73 if (dev.isValid()) {
74 if (dev.isCaptureDevice()) {
75 capturedevicelist << dev;
76 if (dev.isPlaybackDevice()) {
77 playbackdevicelist << dev;
78 alreadyFoundCards << QLatin1String("AudioIODevice_") + dev.d->uniqueId;
79 } else {
80 alreadyFoundCards << QLatin1String("AudioCaptureDevice_") + dev.d->uniqueId;
82 } else {
83 playbackdevicelist << dev;
84 alreadyFoundCards << QLatin1String("AudioOutputDevice_") + dev.d->uniqueId;
89 // now look in the config file for disconnected devices
90 QStringList groupList = config->groupList();
91 foreach (const QString &groupName, groupList) {
92 if (alreadyFoundCards.contains(groupName) || !groupName.startsWith(QLatin1String("Audio"))) {
93 continue;
96 KConfigGroup configGroup(config, groupName);
97 AudioDevice dev(configGroup);
98 if (!dev.isValid()) { // invalid only if the storage format changed
99 Q_ASSERT(!dev.d->udi.isEmpty());
100 // try to find the new device
101 Solid::Device device(dev.d->udi);
102 if (device.isValid()) {
103 AudioDevice newDevice(device, config);
104 if (newDevice.isValid()) {
105 // found it, now give it the old index
106 newDevice.d->changeIndex(dev.index(), config);
108 if (newDevice.isCaptureDevice()) {
109 const int i = capturedevicelist.indexOf(newDevice);
110 capturedevicelist.replace(i, newDevice);
112 if (newDevice.isPlaybackDevice()) {
113 const int i = playbackdevicelist.indexOf(newDevice);
114 playbackdevicelist.replace(i, newDevice);
118 configGroup.deleteGroup(); // don't need it anymore
119 } else {
120 if (dev.isCaptureDevice()) {
121 capturedevicelist << dev;
122 if (dev.isPlaybackDevice()) {
123 playbackdevicelist << dev;
125 } else {
126 playbackdevicelist << dev;
128 alreadyFoundCards << groupName;
132 // now that we know about the hardware let's see what virtual devices we can find in
133 // ~/.asoundrc and /etc/asound.conf
134 findVirtualDevices();
136 renameDevices(&playbackdevicelist);
137 renameDevices(&capturedevicelist);
139 //X QFileSystemWatcher *watcher = new QFileSystemWatcher(QCoreApplication::instance());
140 //X watcher->addPath(QDir::homePath() + QLatin1String("/.asoundrc"));
141 //X watcher->addPath(QLatin1String("/etc/asound.conf"));
142 //X q.connect(watcher, SIGNAL(fileChanged(const QString &)), &q, SLOT(_k_asoundrcChanged(const QString &)));
143 //X KDirWatch *dirWatch = KDirWatch::self();
144 //X dirWatch->addFile(QDir::homePath() + QLatin1String("/.asoundrc"));
145 //X dirWatch->addFile(QLatin1String("/etc/asound.conf"));
146 //X q.connect(dirWatch, SIGNAL(dirty(const QString &)), &q, SLOT(_k_asoundrcChanged(const QString &)));
148 globalConfigGroup.writeEntry("CacheVersion", 1);
149 config->sync();
152 struct DeviceHint
154 QString name;
155 QString description;
158 void AudioDeviceEnumeratorPrivate::findVirtualDevices()
160 #ifdef HAS_LIBASOUND_DEVICE_NAME_HINT
161 QList<DeviceHint> deviceHints;
163 void **hints;
164 //snd_config_update();
165 if (snd_device_name_hint(-1, "pcm", &hints) < 0) {
166 kDebug(603) << "snd_device_name_hint failed for 'pcm'";
167 return;
170 for (void **cStrings = hints; *cStrings; ++cStrings) {
171 DeviceHint nextHint;
172 char *x = snd_device_name_get_hint(*cStrings, "NAME");
173 nextHint.name = QString::fromUtf8(x);
174 free(x);
176 if (/*nextHint.name.startsWith("front:") ||
177 nextHint.name.startsWith("rear:") ||
178 nextHint.name.startsWith("center_lfe:") ||*/
179 nextHint.name.startsWith("surround40:") ||
180 nextHint.name.startsWith("surround41:") ||
181 nextHint.name.startsWith("surround50:") ||
182 nextHint.name.startsWith("surround51:") ||
183 nextHint.name.startsWith("surround71:") ||
184 nextHint.name.startsWith("default:") ||
185 nextHint.name == "null"
187 continue;
190 x = snd_device_name_get_hint(*cStrings, "DESC");
191 nextHint.description = QString::fromUtf8(x);
192 free(x);
194 deviceHints << nextHint;
196 snd_device_name_free_hint(hints);
198 snd_config_update_free_global();
199 snd_config_update();
200 Q_ASSERT(snd_config);
201 // after recreating the global configuration we can go and install custom configuration
203 // x-phonon: device
204 QFile phononDefinition(":/phonon/phonondevice.alsa");
205 phononDefinition.open(QIODevice::ReadOnly);
206 const QByteArray phononDefinitionData = phononDefinition.readAll();
208 snd_input_t *sndInput = 0;
209 if (0 == snd_input_buffer_open(&sndInput, phononDefinitionData.constData(), phononDefinitionData.size())) {
210 Q_ASSERT(sndInput);
211 snd_config_load(snd_config, sndInput);
212 snd_input_close(sndInput);
215 #if 0
216 // phonon_softvol: device
217 QFile softvolDefinition(":/phonon/softvol.alsa");
218 softvolDefinition.open(QIODevice::ReadOnly);
219 const QByteArray softvolDefinitionData = softvolDefinition.readAll();
221 sndInput = 0;
222 if (0 == snd_input_buffer_open(&sndInput, softvolDefinitionData.constData(), softvolDefinitionData.size())) {
223 Q_ASSERT(sndInput);
224 snd_config_load(snd_config, sndInput);
225 snd_input_close(sndInput);
227 #endif
229 foreach (const DeviceHint &deviceHint, deviceHints) {
230 AudioDevice dev(deviceHint.name, deviceHint.description, config);
231 if (dev.isPlaybackDevice()) {
232 playbackdevicelist << dev;
234 if (dev.isCaptureDevice()) {
235 capturedevicelist << dev;
236 } else {
237 if (!dev.isPlaybackDevice()) {
238 kDebug(603) << deviceHint.name << " doesn't work.";
242 #elif defined(HAVE_LIBASOUND2)
243 #ifdef __GNUC__
244 #warning "please update your libasound! this code is not supported"
245 #endif
246 snd_config_update();
247 Q_ASSERT(snd_config);
248 // after recreating the global configuration we can go and install custom configuration
250 // x-phonon: device
251 QFile phononDefinition(":/phonon/phonondevice.alsa");
252 phononDefinition.open(QIODevice::ReadOnly);
253 const QByteArray phononDefinitionData = phononDefinition.readAll();
255 snd_input_t *sndInput = 0;
256 if (0 == snd_input_buffer_open(&sndInput, phononDefinitionData.constData(), phononDefinitionData.size())) {
257 Q_ASSERT(sndInput);
258 snd_config_load(snd_config, sndInput);
259 snd_input_close(sndInput);
262 #if 0
263 // phonon_softvol: device
264 QFile softvolDefinition(":/phonon/softvol.alsa");
265 softvolDefinition.open(QIODevice::ReadOnly);
266 const QByteArray softvolDefinitionData = softvolDefinition.readAll();
268 sndInput = 0;
269 if (0 == snd_input_buffer_open(&sndInput, softvolDefinitionData.constData(), softvolDefinitionData.size())) {
270 Q_ASSERT(sndInput);
271 snd_config_load(snd_config, sndInput);
272 snd_input_close(sndInput);
274 #endif
276 #endif //HAS_LIBASOUND_DEVICE_NAME_HINT / HAVE_LIBASOUND2
279 void AudioDeviceEnumeratorPrivate::renameDevices(QList<AudioDevice> *devicelist)
281 // This looks horrible but allows us to quickly find out which
282 // AudioDevices have duplicate names with the scope of an AudioDriver
283 QHash<Solid::AudioInterface::AudioDriver, QHash<QString, int> > cardNames;
284 foreach (const AudioDevice &dev, *devicelist) {
285 cardNames[dev.d->driver][dev.d->cardName]++;
288 // We then go through and rename devices by appending the device number as appropriate
289 QList<AudioDevice>::Iterator device;
290 for (device = devicelist->begin(); device != devicelist->end(); ++device) {
291 if (device->d->deviceNumber > 0 && cardNames[device->d->driver][device->d->cardName] > 1) {
292 device->d->cardName += QLatin1String(" #") + QString::number(device->d->deviceNumber);
297 void AudioDeviceEnumeratorPrivate::_k_asoundrcChanged(const QString &file)
299 Q_UNUSED(file);
300 // I was not able to reload the changed configuration yet, so disable the code
301 #if 0 && defined(HAVE_LIBASOUND2)
302 kDebug(603) << file;
303 QFileInfo changedFile(file);
304 QFileInfo asoundrc(QDir::homePath() + QLatin1String("/.asoundrc"));
305 if (changedFile != asoundrc) {
306 asoundrc.setFile("/etc/asound.conf");
307 if (changedFile != asoundrc) {
308 return;
311 QList<AudioDevice> oldPlaybackdevicelist = playbackdevicelist;
312 QList<AudioDevice> oldCapturedevicelist = capturedevicelist;
314 QList<AudioDevice>::Iterator it = playbackdevicelist.begin();
315 while (it != playbackdevicelist.end()) {
316 if (it->d->driver == Solid::AudioInterface::Alsa &&
317 it->d->deviceIds.size() == 1 &&
318 it->d->deviceIds.first() == it->cardName()) {
319 it = playbackdevicelist.erase(it);
320 } else {
321 ++it;
324 it = capturedevicelist.begin();
325 while (it != capturedevicelist.end()) {
326 if (it->d->driver == Solid::AudioInterface::Alsa &&
327 it->d->deviceIds.size() == 1 &&
328 it->d->deviceIds.first() == it->cardName()) {
329 it = capturedevicelist.erase(it);
330 } else {
331 ++it;
334 snd_config_update_free_global();
335 snd_config_update();
336 findVirtualDevices();
338 foreach (const AudioDevice &dev, oldCapturedevicelist) {
339 if (!capturedevicelist.contains(dev)) {
340 emit q.deviceUnplugged(dev);
343 foreach (const AudioDevice &dev, oldPlaybackdevicelist) {
344 if (!playbackdevicelist.contains(dev)) {
345 emit q.deviceUnplugged(dev);
348 foreach (const AudioDevice &dev, playbackdevicelist) {
349 if (!oldPlaybackdevicelist.contains(dev)) {
350 emit q.devicePlugged(dev);
353 foreach (const AudioDevice &dev, capturedevicelist) {
354 if (!oldCapturedevicelist.contains(dev)) {
355 emit q.devicePlugged(dev);
358 #endif // HAVE_LIBASOUND2
361 void AudioDeviceEnumeratorPrivate::_k_deviceAdded(const QString &udi)
363 kDebug(603) << udi;
364 Solid::Device _device(udi);
365 Solid::AudioInterface *audiohw = _device.as<Solid::AudioInterface>();
366 if (audiohw && (audiohw->deviceType() & (Solid::AudioInterface::AudioInput |
367 Solid::AudioInterface::AudioOutput))) {
368 // an audio i/o device was plugged in
369 AudioDevice dev(_device, config);
370 if (dev.isValid()) {
371 if (dev.isCaptureDevice()) {
372 foreach (const AudioDevice &listedDev, capturedevicelist) {
373 if (listedDev == dev && !listedDev.isAvailable()) {
374 // listedDev is the same devices as dev but shown as unavailable
375 kDebug(603) << "removing from capturedevicelist: " << listedDev.cardName();
376 capturedevicelist.removeAll(listedDev);
377 break;
380 capturedevicelist << dev;
382 if (dev.isPlaybackDevice()) {
383 foreach (const AudioDevice &listedDev, playbackdevicelist) {
384 if (listedDev == dev && !listedDev.isAvailable()) {
385 // listedDev is the same devices as dev but shown as unavailable
386 kDebug(603) << "removing from playbackdevicelist: " << listedDev.cardName();
387 playbackdevicelist.removeAll(listedDev);
388 break;
391 playbackdevicelist << dev;
393 kDebug(603) << "emit q.devicePlugged " << dev.cardName();
394 emit q.devicePlugged(dev);
399 void AudioDeviceEnumeratorPrivate::_k_deviceRemoved(const QString &udi)
401 kDebug(603) << udi;
402 AudioDevice dev;
403 foreach (const AudioDevice &listedDev, capturedevicelist) {
404 if (listedDev.d->udi == udi && listedDev.isAvailable()) {
405 // listedDev is the same devices as was removed
406 kDebug(603) << "removing from capturedevicelist: " << listedDev.cardName();
407 dev = listedDev;
408 capturedevicelist.removeAll(listedDev);
409 break;
412 foreach (const AudioDevice &listedDev, playbackdevicelist) {
413 if (listedDev.d->udi == udi && listedDev.isAvailable()) {
414 // listedDev is the same devices as was removed
415 kDebug(603) << "removing from playbackdevicelist: " << listedDev.cardName();
416 dev = listedDev;
417 playbackdevicelist.removeAll(listedDev);
418 break;
422 if (dev.isValid()) {
423 kDebug(603) << "emit q.deviceUnplugged " << dev.cardName();
424 emit q.deviceUnplugged(dev);
428 AudioDeviceEnumerator::~AudioDeviceEnumerator()
432 QDebug operator<<(QDebug &s, const Solid::AudioInterface::AudioDriver &driver)
434 switch (driver) {
435 case Solid::AudioInterface::Alsa:
436 s.nospace() << "ALSA";
437 break;
438 case Solid::AudioInterface::OpenSoundSystem:
439 s.nospace() << "OSS";
440 break;
441 case Solid::AudioInterface::UnknownAudioDriver:
442 s.nospace() << "unknown driver";
443 break;
445 return s.space();
448 QList<AudioDevice> AudioDeviceEnumerator::availablePlaybackDevices()
450 kDebug(603) << audioDeviceEnumeratorPrivate->playbackdevicelist;
451 return audioDeviceEnumeratorPrivate->playbackdevicelist;
454 QList<AudioDevice> AudioDeviceEnumerator::availableCaptureDevices()
456 return audioDeviceEnumeratorPrivate->capturedevicelist;
459 } // namespace Phonon
460 #include "audiodeviceenumerator.moc"
462 // vim: sw=4 sts=4 et tw=100