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>
33 #include <../config-alsa.h>
35 #ifdef HAVE_LIBASOUND2
36 #include <alsa/asoundlib.h>
37 #endif // HAVE_LIBASOUND2
42 K_GLOBAL_STATIC(AudioDeviceEnumeratorPrivate
, audioDeviceEnumeratorPrivate
)
44 AudioDeviceEnumerator::AudioDeviceEnumerator(AudioDeviceEnumeratorPrivate
*dd
)
49 AudioDeviceEnumeratorPrivate::AudioDeviceEnumeratorPrivate()
52 config
= KSharedConfig::openConfig("phonondevicesrc", KConfig::NoGlobals
);
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
);
74 if (dev
.isCaptureDevice()) {
75 capturedevicelist
<< dev
;
76 if (dev
.isPlaybackDevice()) {
77 playbackdevicelist
<< dev
;
78 alreadyFoundCards
<< QLatin1String("AudioIODevice_") + dev
.d
->uniqueId
;
80 alreadyFoundCards
<< QLatin1String("AudioCaptureDevice_") + dev
.d
->uniqueId
;
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"))) {
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
120 if (dev
.isCaptureDevice()) {
121 capturedevicelist
<< dev
;
122 if (dev
.isPlaybackDevice()) {
123 playbackdevicelist
<< dev
;
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);
158 void AudioDeviceEnumeratorPrivate::findVirtualDevices()
160 #ifdef HAS_LIBASOUND_DEVICE_NAME_HINT
161 QList
<DeviceHint
> deviceHints
;
164 //snd_config_update();
165 if (snd_device_name_hint(-1, "pcm", &hints
) < 0) {
166 kDebug(603) << "snd_device_name_hint failed for 'pcm'";
170 for (void **cStrings
= hints
; *cStrings
; ++cStrings
) {
172 char *x
= snd_device_name_get_hint(*cStrings
, "NAME");
173 nextHint
.name
= QString::fromUtf8(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"
190 x
= snd_device_name_get_hint(*cStrings
, "DESC");
191 nextHint
.description
= QString::fromUtf8(x
);
194 deviceHints
<< nextHint
;
196 snd_device_name_free_hint(hints
);
198 snd_config_update_free_global();
200 Q_ASSERT(snd_config
);
201 // after recreating the global configuration we can go and install custom configuration
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())) {
211 snd_config_load(snd_config
, sndInput
);
212 snd_input_close(sndInput
);
216 // phonon_softvol: device
217 QFile
softvolDefinition(":/phonon/softvol.alsa");
218 softvolDefinition
.open(QIODevice::ReadOnly
);
219 const QByteArray softvolDefinitionData
= softvolDefinition
.readAll();
222 if (0 == snd_input_buffer_open(&sndInput
, softvolDefinitionData
.constData(), softvolDefinitionData
.size())) {
224 snd_config_load(snd_config
, sndInput
);
225 snd_input_close(sndInput
);
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
;
237 if (!dev
.isPlaybackDevice()) {
238 kDebug(603) << deviceHint
.name
<< " doesn't work.";
242 #elif defined(HAVE_LIBASOUND2)
244 #warning "please update your libasound! this code is not supported"
247 Q_ASSERT(snd_config
);
248 // after recreating the global configuration we can go and install custom configuration
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())) {
258 snd_config_load(snd_config
, sndInput
);
259 snd_input_close(sndInput
);
263 // phonon_softvol: device
264 QFile
softvolDefinition(":/phonon/softvol.alsa");
265 softvolDefinition
.open(QIODevice::ReadOnly
);
266 const QByteArray softvolDefinitionData
= softvolDefinition
.readAll();
269 if (0 == snd_input_buffer_open(&sndInput
, softvolDefinitionData
.constData(), softvolDefinitionData
.size())) {
271 snd_config_load(snd_config
, sndInput
);
272 snd_input_close(sndInput
);
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
)
300 // I was not able to reload the changed configuration yet, so disable the code
301 #if 0 && defined(HAVE_LIBASOUND2)
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
) {
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
);
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
);
334 snd_config_update_free_global();
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
)
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
);
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
);
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
);
391 playbackdevicelist
<< dev
;
393 kDebug(603) << "emit q.devicePlugged " << dev
.cardName();
394 emit q
.devicePlugged(dev
);
399 void AudioDeviceEnumeratorPrivate::_k_deviceRemoved(const QString
&udi
)
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();
408 capturedevicelist
.removeAll(listedDev
);
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();
417 playbackdevicelist
.removeAll(listedDev
);
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
)
435 case Solid::AudioInterface::Alsa
:
436 s
.nospace() << "ALSA";
438 case Solid::AudioInterface::OpenSoundSystem
:
439 s
.nospace() << "OSS";
441 case Solid::AudioInterface::UnknownAudioDriver
:
442 s
.nospace() << "unknown driver";
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