diff --git a/phonon/libkaudiodevicelist/audiodeviceenumerator.cpp b/phonon/libkaudiodevicelist/audiodeviceenumerator.cpp index 98af70b1d2..ad7ddb907c 100644 --- a/phonon/libkaudiodevicelist/audiodeviceenumerator.cpp +++ b/phonon/libkaudiodevicelist/audiodeviceenumerator.cpp @@ -1,462 +1,464 @@ /* This file is part of the KDE project Copyright (C) 2006 Matthias Kretz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "audiodeviceenumerator.h" #include "audiodeviceenumerator_p.h" #include "audiodevice_p.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBASOUND2 #include #endif // HAVE_LIBASOUND2 namespace Phonon { K_GLOBAL_STATIC(AudioDeviceEnumeratorPrivate, audioDeviceEnumeratorPrivate) AudioDeviceEnumerator::AudioDeviceEnumerator(AudioDeviceEnumeratorPrivate *dd) : d(dd) { } AudioDeviceEnumeratorPrivate::AudioDeviceEnumeratorPrivate() : q(this) { config = KSharedConfig::openConfig("phonondevicesrc", KConfig::NoGlobals); findDevices(); QObject::connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(const QString &)), &q, SLOT(_k_deviceAdded(const QString &))); QObject::connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(const QString &)), &q, SLOT(_k_deviceRemoved(const QString &))); } AudioDeviceEnumerator *AudioDeviceEnumerator::self() { return &audioDeviceEnumeratorPrivate->q; } void AudioDeviceEnumeratorPrivate::findDevices() { KConfigGroup globalConfigGroup(config, "Globals"); // for future use: const int cacheVersion = globalConfigGroup.readEntry("CacheVersion", 0); QSet alreadyFoundCards; // ask Solid for the available audio hardware const QList devices = Solid::Device::listFromQuery("AudioInterface.deviceType & 'AudioInput|AudioOutput'"); foreach (const Solid::Device &device, devices) { AudioDevice dev(device, config); if (dev.isValid()) { if (dev.isCaptureDevice()) { capturedevicelist << dev; if (dev.isPlaybackDevice()) { playbackdevicelist << dev; alreadyFoundCards << QLatin1String("AudioIODevice_") + dev.d->uniqueId; } else { alreadyFoundCards << QLatin1String("AudioCaptureDevice_") + dev.d->uniqueId; } } else { playbackdevicelist << dev; alreadyFoundCards << QLatin1String("AudioOutputDevice_") + dev.d->uniqueId; } } } // now look in the config file for disconnected devices QStringList groupList = config->groupList(); foreach (const QString &groupName, groupList) { if (alreadyFoundCards.contains(groupName) || !groupName.startsWith(QLatin1String("Audio"))) { continue; } KConfigGroup configGroup(config, groupName); AudioDevice dev(configGroup); if (!dev.isValid()) { // invalid only if the storage format changed // try to find the new device Solid::Device device(dev.d->udi); AudioDevice newDevice(device, config); if (newDevice.isValid()) { // found it, now give it the old index newDevice.d->changeIndex(dev.index(), config); if (newDevice.isCaptureDevice()) { const int i = capturedevicelist.indexOf(newDevice); capturedevicelist.replace(i, newDevice); } if (newDevice.isPlaybackDevice()) { const int i = playbackdevicelist.indexOf(newDevice); playbackdevicelist.replace(i, newDevice); } } configGroup.deleteGroup(); // don't need it anymore } else { if (dev.isCaptureDevice()) { capturedevicelist << dev; if (dev.isPlaybackDevice()) { playbackdevicelist << dev; } } else { playbackdevicelist << dev; } alreadyFoundCards << groupName; } } // now that we know about the hardware let's see what virtual devices we can find in // ~/.asoundrc and /etc/asound.conf findVirtualDevices(); renameDevices(&playbackdevicelist); renameDevices(&capturedevicelist); //X QFileSystemWatcher *watcher = new QFileSystemWatcher(QCoreApplication::instance()); //X watcher->addPath(QDir::homePath() + QLatin1String("/.asoundrc")); //X watcher->addPath(QLatin1String("/etc/asound.conf")); //X q.connect(watcher, SIGNAL(fileChanged(const QString &)), &q, SLOT(_k_asoundrcChanged(const QString &))); //X KDirWatch *dirWatch = KDirWatch::self(); //X dirWatch->addFile(QDir::homePath() + QLatin1String("/.asoundrc")); //X dirWatch->addFile(QLatin1String("/etc/asound.conf")); //X q.connect(dirWatch, SIGNAL(dirty(const QString &)), &q, SLOT(_k_asoundrcChanged(const QString &))); globalConfigGroup.writeEntry("CacheVersion", 1); config->sync(); } struct DeviceHint { QString name; QString description; }; void AudioDeviceEnumeratorPrivate::findVirtualDevices() { #ifdef HAS_LIBASOUND_DEVICE_NAME_HINT QList deviceHints; void **hints; //snd_config_update(); if (snd_device_name_hint(-1, "pcm", &hints) < 0) { kDebug(603) << "snd_device_name_hint failed for 'pcm'"; return; } for (void **cStrings = hints; *cStrings; ++cStrings) { DeviceHint nextHint; char *x = snd_device_name_get_hint(*cStrings, "NAME"); nextHint.name = QString::fromUtf8(x); free(x); if (/*nextHint.name.startsWith("front:") || nextHint.name.startsWith("rear:") || nextHint.name.startsWith("center_lfe:") ||*/ nextHint.name.startsWith("surround40:") || nextHint.name.startsWith("surround41:") || nextHint.name.startsWith("surround50:") || nextHint.name.startsWith("surround51:") || nextHint.name.startsWith("surround71:") || nextHint.name.startsWith("default:") || nextHint.name == "null" ) { continue; } x = snd_device_name_get_hint(*cStrings, "DESC"); nextHint.description = QString::fromUtf8(x); free(x); deviceHints << nextHint; } snd_device_name_free_hint(hints); snd_config_update_free_global(); snd_config_update(); Q_ASSERT(snd_config); // after recreating the global configuration we can go and install custom configuration { // x-phonon: device QFile phononDefinition(":/phonon/phonondevice.alsa"); phononDefinition.open(QIODevice::ReadOnly); const QByteArray phononDefinitionData = phononDefinition.readAll(); snd_input_t *sndInput = 0; if (0 == snd_input_buffer_open(&sndInput, phononDefinitionData.constData(), phononDefinitionData.size())) { Q_ASSERT(sndInput); snd_config_load(snd_config, sndInput); snd_input_close(sndInput); } #if 0 // phonon_softvol: device QFile softvolDefinition(":/phonon/softvol.alsa"); softvolDefinition.open(QIODevice::ReadOnly); const QByteArray softvolDefinitionData = softvolDefinition.readAll(); sndInput = 0; if (0 == snd_input_buffer_open(&sndInput, softvolDefinitionData.constData(), softvolDefinitionData.size())) { Q_ASSERT(sndInput); snd_config_load(snd_config, sndInput); snd_input_close(sndInput); } #endif } foreach (const DeviceHint &deviceHint, deviceHints) { AudioDevice dev(deviceHint.name, deviceHint.description, config); if (dev.isPlaybackDevice()) { playbackdevicelist << dev; } if (dev.isCaptureDevice()) { capturedevicelist << dev; } else { if (!dev.isPlaybackDevice()) { kDebug(603) << deviceHint.name << " doesn't work."; } } } #elif defined(HAVE_LIBASOUND2) +#ifdef __GNUC__ #warning "please update your libasound! this code is not supported" +#endif snd_config_update(); Q_ASSERT(snd_config); // after recreating the global configuration we can go and install custom configuration { // x-phonon: device QFile phononDefinition(":/phonon/phonondevice.alsa"); phononDefinition.open(QIODevice::ReadOnly); const QByteArray phononDefinitionData = phononDefinition.readAll(); snd_input_t *sndInput = 0; if (0 == snd_input_buffer_open(&sndInput, phononDefinitionData.constData(), phononDefinitionData.size())) { Q_ASSERT(sndInput); snd_config_load(snd_config, sndInput); snd_input_close(sndInput); } #if 0 // phonon_softvol: device QFile softvolDefinition(":/phonon/softvol.alsa"); softvolDefinition.open(QIODevice::ReadOnly); const QByteArray softvolDefinitionData = softvolDefinition.readAll(); sndInput = 0; if (0 == snd_input_buffer_open(&sndInput, softvolDefinitionData.constData(), softvolDefinitionData.size())) { Q_ASSERT(sndInput); snd_config_load(snd_config, sndInput); snd_input_close(sndInput); } #endif } #endif //HAS_LIBASOUND_DEVICE_NAME_HINT / HAVE_LIBASOUND2 } void AudioDeviceEnumeratorPrivate::renameDevices(QList *devicelist) { // This looks horrible but allows us to quickly find out which // AudioDevices have duplicate names with the scope of an AudioDriver QHash > cardNames; foreach (AudioDevice dev, *devicelist) { cardNames[dev.d->driver][dev.d->cardName]++; } // We then go through and rename devices by appending the device number as appropriate QList::Iterator device; for (device = devicelist->begin(); device != devicelist->end(); ++device) { if (device->d->deviceNumber > 0 && cardNames[device->d->driver][device->d->cardName] > 1) { device->d->cardName += QLatin1String(" #") + QString::number(device->d->deviceNumber); } } } void AudioDeviceEnumeratorPrivate::_k_asoundrcChanged(const QString &file) { Q_UNUSED(file); // I was not able to reload the changed configuration yet, so disable the code #if 0 && defined(HAVE_LIBASOUND2) kDebug(603) << file; QFileInfo changedFile(file); QFileInfo asoundrc(QDir::homePath() + QLatin1String("/.asoundrc")); if (changedFile != asoundrc) { asoundrc.setFile("/etc/asound.conf"); if (changedFile != asoundrc) { return; } } QList oldPlaybackdevicelist = playbackdevicelist; QList oldCapturedevicelist = capturedevicelist; QList::Iterator it = playbackdevicelist.begin(); while (it != playbackdevicelist.end()) { if (it->d->driver == Solid::AudioInterface::Alsa && it->d->deviceIds.size() == 1 && it->d->deviceIds.first() == it->cardName()) { it = playbackdevicelist.erase(it); } else { ++it; } } it = capturedevicelist.begin(); while (it != capturedevicelist.end()) { if (it->d->driver == Solid::AudioInterface::Alsa && it->d->deviceIds.size() == 1 && it->d->deviceIds.first() == it->cardName()) { it = capturedevicelist.erase(it); } else { ++it; } } snd_config_update_free_global(); snd_config_update(); findVirtualDevices(); foreach (const AudioDevice &dev, oldCapturedevicelist) { if (!capturedevicelist.contains(dev)) { emit q.deviceUnplugged(dev); } } foreach (const AudioDevice &dev, oldPlaybackdevicelist) { if (!playbackdevicelist.contains(dev)) { emit q.deviceUnplugged(dev); } } foreach (const AudioDevice &dev, playbackdevicelist) { if (!oldPlaybackdevicelist.contains(dev)) { emit q.devicePlugged(dev); } } foreach (const AudioDevice &dev, capturedevicelist) { if (!oldCapturedevicelist.contains(dev)) { emit q.devicePlugged(dev); } } #endif // HAVE_LIBASOUND2 } void AudioDeviceEnumeratorPrivate::_k_deviceAdded(const QString &udi) { kDebug(603) << udi; Solid::Device _device(udi); Solid::AudioInterface *audiohw = _device.as(); if (audiohw && (audiohw->deviceType() & (Solid::AudioInterface::AudioInput | Solid::AudioInterface::AudioOutput))) { // an audio i/o device was plugged in AudioDevice dev(_device, config); if (dev.isValid()) { if (dev.isCaptureDevice()) { foreach (const AudioDevice &listedDev, capturedevicelist) { if (listedDev == dev && !listedDev.isAvailable()) { // listedDev is the same devices as dev but shown as unavailable kDebug(603) << "removing from capturedevicelist: " << listedDev.cardName(); capturedevicelist.removeAll(listedDev); break; } } capturedevicelist << dev; } if (dev.isPlaybackDevice()) { foreach (const AudioDevice &listedDev, playbackdevicelist) { if (listedDev == dev && !listedDev.isAvailable()) { // listedDev is the same devices as dev but shown as unavailable kDebug(603) << "removing from playbackdevicelist: " << listedDev.cardName(); playbackdevicelist.removeAll(listedDev); break; } } playbackdevicelist << dev; } kDebug(603) << "emit q.devicePlugged " << dev.cardName(); emit q.devicePlugged(dev); } } } void AudioDeviceEnumeratorPrivate::_k_deviceRemoved(const QString &udi) { kDebug(603) << udi; AudioDevice dev; foreach (const AudioDevice &listedDev, capturedevicelist) { if (listedDev.d->udi == udi && listedDev.isAvailable()) { // listedDev is the same devices as was removed kDebug(603) << "removing from capturedevicelist: " << listedDev.cardName(); dev = listedDev; capturedevicelist.removeAll(listedDev); break; } } foreach (const AudioDevice &listedDev, playbackdevicelist) { if (listedDev.d->udi == udi && listedDev.isAvailable()) { // listedDev is the same devices as was removed kDebug(603) << "removing from playbackdevicelist: " << listedDev.cardName(); dev = listedDev; playbackdevicelist.removeAll(listedDev); break; } } if (dev.isValid()) { kDebug(603) << "emit q.deviceUnplugged " << dev.cardName(); emit q.deviceUnplugged(dev); } } AudioDeviceEnumerator::~AudioDeviceEnumerator() { } QDebug operator<<(QDebug &s, const Solid::AudioInterface::AudioDriver &driver) { switch (driver) { case Solid::AudioInterface::Alsa: s.nospace() << "ALSA"; break; case Solid::AudioInterface::OpenSoundSystem: s.nospace() << "OSS"; break; case Solid::AudioInterface::UnknownAudioDriver: s.nospace() << "unknown driver"; break; } return s.space(); } QDebug operator<<(QDebug &s, const AudioDevice &dev) { s.space() << "\n-" << dev.cardName() << dev.driver() << dev.deviceIds() << "index:" << dev.index() << "preference:" << dev.initialPreference() << "avail:" << dev.isAvailable() << "advanced:" << dev.isAdvancedDevice(); return s.space(); } QList AudioDeviceEnumerator::availablePlaybackDevices() { kDebug(603) << audioDeviceEnumeratorPrivate->playbackdevicelist; return audioDeviceEnumeratorPrivate->playbackdevicelist; } QList AudioDeviceEnumerator::availableCaptureDevices() { return audioDeviceEnumeratorPrivate->capturedevicelist; } } // namespace Phonon #include "audiodeviceenumerator.moc" // vim: sw=4 sts=4 et tw=100