delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / runtime / phonon / libkaudiodevicelist / audiodevice.cpp
blob6843f4dfcdb1aef1f3ca58fcc0afd1e2ae69af30
1 /* This file is part of the KDE project
2 Copyright (C) 2006-2007 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 "audiodevice.h"
22 #include "audiodevice_p.h"
23 #include "audiodeviceenumerator.h"
24 #include "hardwaredatabase_p.h"
26 #include <solid/device.h>
27 #include <solid/audiointerface.h>
28 #include <solid/genericinterface.h>
29 #include <kconfiggroup.h>
30 #include <klocale.h>
31 #include <kdebug.h>
33 #ifdef HAVE_LIBASOUND2
34 #include <alsa/asoundlib.h>
35 #endif // HAVE_LIBASOUND2
37 namespace Phonon
39 #if 0
40 QStringList AudioDevice::addSoftVolumeMixerControl(const AudioDevice &device, const QStringList &mixerControlNames)
42 if (device.driver() != Solid::AudioInterface::Alsa) {
43 return QStringList();
46 QStringList ids = device.deviceIds();
47 foreach (const QString &mixerControlName, mixerControlNames) {
48 QStringList tmp;
49 foreach (QString id, ids) {
50 id.replace(QLatin1Char('"'), QLatin1String("\\\""));
51 tmp << QString("phonon_softvol:CARD=\"%1\",NAME=\"%2\",SLAVE=\"%3\"")
52 .arg(0)
53 .arg(mixerControlName)
54 .arg(id);
56 ids = tmp;
58 return ids;
60 #endif
62 AudioDevice::AudioDevice()
63 : d(new AudioDevicePrivate)
67 KConfigGroup AudioDevicePrivate::configGroup(KSharedConfig::Ptr config)
69 QString groupName;
70 if (captureDevice) {
71 if (playbackDevice) {
72 groupName = QLatin1String("AudioIODevice_");
73 } else {
74 groupName = QLatin1String("AudioCaptureDevice_");
76 } else {
77 Q_ASSERT(playbackDevice);
78 groupName = QLatin1String("AudioOutputDevice_");
80 groupName += uniqueId;
81 return KConfigGroup(config, groupName);
84 QString AudioDevicePrivate::uniqueIdentifierFromDevice(const Solid::Device &device)
86 const Solid::GenericInterface *genericIface = device.as<Solid::GenericInterface>();
87 Q_ASSERT(genericIface);
88 const QString subsystem = genericIface->propertyExists(QLatin1String("info.subsystem")) ?
89 genericIface->property(QLatin1String("info.subsystem")).toString() :
90 genericIface->property(QLatin1String("linux.subsystem")).toString();
91 if (subsystem == "pci") {
92 const QVariant vendor_id = genericIface->property("pci.vendor_id");
93 if (vendor_id.isValid()) {
94 const QVariant product_id = genericIface->property("pci.product_id");
95 if (product_id.isValid()) {
96 const QVariant subsys_vendor_id = genericIface->property("pci.subsys_vendor_id");
97 if (subsys_vendor_id.isValid()) {
98 const QVariant subsys_product_id = genericIface->property("pci.subsys_product_id");
99 if (subsys_product_id.isValid()) {
100 return QString("pci:%1:%2:%3:%4")
101 .arg(vendor_id.toInt(), 4, 16, QLatin1Char('0'))
102 .arg(product_id.toInt(), 4, 16, QLatin1Char('0'))
103 .arg(subsys_vendor_id.toInt(), 4, 16, QLatin1Char('0'))
104 .arg(subsys_product_id.toInt(), 4, 16, QLatin1Char('0'));
109 } else if (subsystem == "usb" || subsystem == "usb_device") {
110 const QVariant vendor_id = genericIface->property("usb.vendor_id");
111 if (vendor_id.isValid()) {
112 const QVariant product_id = genericIface->property("usb.product_id");
113 if (product_id.isValid()) {
114 return QString("usb:%1:%2")
115 .arg(vendor_id.toInt(), 4, 16, QLatin1Char('0'))
116 .arg(product_id.toInt(), 4, 16, QLatin1Char('0'));
119 /*} else if (subsystem == "platform") {
120 } else if (subsystem == "pnp") {
121 } else if (subsystem == "serio") {
122 } else if (subsystem == "scsi") {*/
124 return QString();
127 AudioDevice::AudioDevice(Solid::Device audioDevice, KSharedConfig::Ptr config)
128 : d(new AudioDevicePrivate)
130 Solid::AudioInterface *audioHw = audioDevice.as<Solid::AudioInterface>();
131 //kDebug(603) << audioHw->driverHandle();
132 d->uniqueId = d->uniqueIdentifierFromDevice(audioDevice);
133 d->udi = audioDevice.udi();
134 d->driver = audioHw->driver();
135 const QVariant handle = audioHw->driverHandle();
136 if (d->uniqueId.isEmpty()) {
137 Solid::Device parent = audioDevice.parent();
138 if (parent.isValid()) {
139 d->uniqueId = d->uniqueIdentifierFromDevice(parent);
140 // newer HAL versions add one more parent in between to find the actual hardware info
141 if (d->uniqueId.isEmpty() && parent.parent().isValid()) {
142 parent = parent.parent();
143 d->uniqueId = d->uniqueIdentifierFromDevice(parent);
145 if (!d->uniqueId.isEmpty()) {
146 switch (audioHw->deviceType()) {
147 case Solid::AudioInterface::AudioInput:
148 d->uniqueId += QLatin1String(":capture");
149 break;
150 case Solid::AudioInterface::AudioOutput:
151 d->uniqueId += QLatin1String(":playback");
152 break;
153 case 6: //Solid::AudioInterface::AudioInput | Solid::AudioInterface::AudioOutput:
154 d->uniqueId += QLatin1String(":both");
155 break;
156 default:
157 break;
159 switch (d->driver) {
160 case Solid::AudioInterface::Alsa:
161 d->uniqueId += QLatin1String(":alsa");
162 if (handle.type() == QVariant::List) {
163 const QList<QVariant> handles = handle.toList();
164 if (handles.size() > 1 && handles.at(1).isValid()) {
165 d->uniqueId += QLatin1Char(':') + handles.at(1).toString();
168 break;
169 case Solid::AudioInterface::OpenSoundSystem:
170 d->uniqueId += QLatin1String(":oss");
171 break;
172 default:
173 break;
177 if (d->uniqueId.isEmpty()) {
178 d->uniqueId = audioHw->name();
179 d->uniqueId += audioDevice.vendor();
180 d->uniqueId += audioDevice.product();
181 if (parent.isValid()) {
182 d->uniqueId += parent.vendor();
183 d->uniqueId += parent.product();
185 d->uniqueId += QString::number(audioHw->driver());
186 d->uniqueId += QLatin1Char('_');
187 d->uniqueId += QString::number(audioHw->deviceType());
190 d->cardName = audioHw->name();
192 // prefer devices Solid tells us about
193 d->initialPreference += 5;
195 switch (d->driver) {
196 case Solid::AudioInterface::UnknownAudioDriver:
197 d->valid = false;
198 return;
199 case Solid::AudioInterface::OpenSoundSystem:
200 if (handle.type() != QVariant::String) {
201 d->valid = false;
202 return;
204 d->deviceIds << handle.toString();
205 break;
206 case Solid::AudioInterface::Alsa:
207 if (handle.type() != QVariant::List) {
208 d->valid = false;
209 return;
211 const QList<QVariant> handles = handle.toList();
212 if (handles.size() < 1) {
213 d->valid = false;
214 return;
216 QString x_phononId = QLatin1String("x-phonon:CARD=") + handles.first().toString(); // the first is either an int (card number) or a QString (card id)
217 QString fallbackId = QLatin1String("plughw:CARD=") + handles.first().toString(); // the first is either an int (card number) or a QString (card id)
218 if (handles.size() > 1 && handles.at(1).isValid()) {
219 d->deviceNumber = handles.at(1).toInt();
220 if (d->deviceNumber == 0) {
221 // prefer DEV=0 devices over DEV>0
222 d->initialPreference += 1;
223 } else {
224 d->isAdvanced = true;
226 x_phononId += ",DEV=" + handles.at(1).toString();
227 fallbackId += ",DEV=" + handles.at(1).toString();
228 if (handles.size() > 2 && handles.at(2).isValid()) {
229 x_phononId += ",SUBDEV=" + handles.at(2).toString();
230 fallbackId += ",SUBDEV=" + handles.at(2).toString();
233 d->deviceIds << x_phononId << fallbackId;
234 break;
236 d->icon = audioDevice.icon();
237 if (d->icon.isEmpty()) {
238 switch (audioHw->soundcardType()) {
239 case Solid::AudioInterface::InternalSoundcard:
240 d->icon = QLatin1String("audio-card");
241 break;
242 case Solid::AudioInterface::UsbSoundcard:
243 d->icon = QLatin1String("audio-card-usb");
244 d->initialPreference -= 10;
245 break;
246 case Solid::AudioInterface::FirewireSoundcard:
247 d->icon = QLatin1String("audio-card-firewire");
248 d->initialPreference -= 10;
249 break;
250 case Solid::AudioInterface::Headset:
251 if (audioDevice.udi().contains("usb", Qt::CaseInsensitive) ||
252 d->cardName.contains("usb", Qt::CaseInsensitive)) {
253 d->icon = QLatin1String("audio-headset-usb");
254 } else {
255 d->icon = QLatin1String("audio-headset");
257 d->initialPreference -= 10;
258 break;
259 case Solid::AudioInterface::Modem:
260 d->icon = QLatin1String("modem");
261 // should a modem be a valid device so that it's shown to the user?
262 d->valid = false;
263 return;
266 d->available = true;
267 d->valid = true;
269 Solid::AudioInterface::AudioInterfaceTypes deviceType = audioHw->deviceType();
270 if (deviceType == Solid::AudioInterface::AudioInput) {
271 d->captureDevice = true;
272 } else {
273 if (deviceType == Solid::AudioInterface::AudioOutput) {
274 d->playbackDevice = true;
275 } else {
276 Q_ASSERT(deviceType == (Solid::AudioInterface::AudioOutput | Solid::AudioInterface::AudioInput));
277 d->captureDevice = true;
278 d->playbackDevice = true;
282 KConfigGroup deviceGroup = d->configGroup(config);
283 if (deviceGroup.exists()) {
284 d->index = deviceGroup.readEntry("index", -1);
286 if (d->index == -1) {
287 KConfigGroup globalGroup(config, "Globals");
288 int nextIndex = globalGroup.readEntry("nextIndex", 0);
289 d->index = nextIndex++;
290 globalGroup.writeEntry("nextIndex", nextIndex);
292 deviceGroup.writeEntry("index", d->index);
293 deviceGroup.writeEntry("cardName", d->cardName);
294 deviceGroup.writeEntry("icon", d->icon);
295 deviceGroup.writeEntry("driver", static_cast<int>(d->driver));
296 deviceGroup.writeEntry("captureDevice", d->captureDevice);
297 deviceGroup.writeEntry("playbackDevice", d->playbackDevice);
298 deviceGroup.writeEntry("udi", d->udi);
299 deviceGroup.writeEntry("initialPreference", d->initialPreference);
300 deviceGroup.writeEntry("isAdvanced", d->isAdvanced);
301 } else {
302 deviceGroup.writeEntry("udi", d->udi);
303 deviceGroup.writeEntry("initialPreference", d->initialPreference);
304 deviceGroup.writeEntry("icon", d->icon);
305 deviceGroup.writeEntry("isAdvanced", d->isAdvanced);
307 //kDebug(603) << deviceGroup.readEntry("uniqueId", d->uniqueId) << " == " << d->uniqueId;
309 d->applyHardwareDatabaseOverrides();
312 void AudioDevicePrivate::changeIndex(int newIndex, KSharedConfig::Ptr config)
314 index = newIndex;
315 KConfigGroup deviceGroup = configGroup(config);
316 deviceGroup.writeEntry("index", index);
319 AudioDevice::AudioDevice(KConfigGroup &deviceGroup)
320 : d(new AudioDevicePrivate)
322 d->index = deviceGroup.readEntry("index", d->index);
323 const QString groupName = deviceGroup.name();
324 d->uniqueId = groupName.mid(groupName.indexOf(QLatin1Char('_')) + 1);
325 d->udi = deviceGroup.readEntry("udi", d->udi);
326 kDebug(603) << groupName << d->uniqueId;
327 if (d->uniqueId.startsWith("/org/freedesktop/Hal/devices/")) {
328 // old invalid group
329 d->valid = false;
330 return;
332 d->cardName = deviceGroup.readEntry("cardName", d->cardName);
333 d->icon = deviceGroup.readEntry("icon", d->icon);
334 d->driver = static_cast<Solid::AudioInterface::AudioDriver>(deviceGroup.readEntry("driver", static_cast<int>(d->driver)));
335 d->captureDevice = deviceGroup.readEntry("captureDevice", d->captureDevice);
336 d->playbackDevice = deviceGroup.readEntry("playbackDevice", d->playbackDevice);
337 d->valid = true;
338 d->available = false;
339 d->initialPreference = deviceGroup.readEntry("initialPreference", 0);
340 d->isAdvanced = deviceGroup.readEntry("isAdvanced", false);
341 // deviceIds stays empty because it's not available
343 d->applyHardwareDatabaseOverrides();
346 AudioDevice::AudioDevice(const QString &alsaDeviceName, const QString &description, KSharedConfig::Ptr config)
347 : d(new AudioDevicePrivate)
349 #ifdef HAVE_LIBASOUND2
350 d->uniqueId = alsaDeviceName;
351 d->udi = alsaDeviceName;
352 d->driver = Solid::AudioInterface::Alsa;
353 d->deviceIds << alsaDeviceName;
354 QStringList lines = description.split("\n");
355 d->cardName = lines.first();
356 if (lines.size() > 1) {
357 d->cardName = i18n("%1 (%2)", d->cardName, lines[1]);
359 if (alsaDeviceName.startsWith("front:") ||
360 alsaDeviceName.startsWith("rear:") ||
361 alsaDeviceName.startsWith("center_lfe:") ||
362 alsaDeviceName.startsWith("surround40:") ||
363 alsaDeviceName.startsWith("surround41:") ||
364 alsaDeviceName.startsWith("surround50:") ||
365 alsaDeviceName.startsWith("surround51:") ||
366 alsaDeviceName.startsWith("surround71:") ||
367 alsaDeviceName.startsWith("iec958:")) {
368 d->isAdvanced = true;
371 snd_pcm_t *pcm;
372 const QByteArray deviceNameEnc = alsaDeviceName.toUtf8();
373 if (0 == snd_pcm_open(&pcm, deviceNameEnc.constData(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK /*open mode: non-blocking, sync */)) {
374 d->available = true;
375 d->playbackDevice = true;
376 d->valid = true;
377 snd_pcm_close(pcm);
379 if (0 == snd_pcm_open(&pcm, deviceNameEnc.constData(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK /*open mode: non-blocking, sync */)) {
380 d->available = true;
381 d->captureDevice = true;
382 d->valid = true;
383 snd_pcm_close(pcm);
386 if (description.contains("headset", Qt::CaseInsensitive) ||
387 description.contains("headphone", Qt::CaseInsensitive)) {
388 // it's a headset
389 if (description.contains("usb", Qt::CaseInsensitive)) {
390 d->icon = QLatin1String("audio-headset-usb");
391 d->initialPreference -= 10;
392 } else {
393 d->icon = QLatin1String("audio-headset");
394 d->initialPreference -= 10;
396 } else {
397 //Get card driver name from a CTL card info.
398 if (description.contains("usb", Qt::CaseInsensitive)) {
399 // it's an external USB device
400 d->icon = QLatin1String("audio-card-usb");
401 d->initialPreference -= 10;
402 } else {
403 d->icon = QLatin1String("audio-card");
407 KConfigGroup deviceGroup(config, alsaDeviceName);
408 if (config->hasGroup(alsaDeviceName)) {
409 d->index = deviceGroup.readEntry("index", -1);
411 if (d->index == -1) {
412 KConfigGroup globalGroup(config, "Globals");
413 int nextIndex = globalGroup.readEntry("nextIndex", 0);
414 d->index = nextIndex++;
415 globalGroup.writeEntry("nextIndex", nextIndex);
417 deviceGroup.writeEntry("index", d->index);
418 deviceGroup.writeEntry("cardName", d->cardName);
419 deviceGroup.writeEntry("icon", d->icon);
420 deviceGroup.writeEntry("driver", static_cast<int>(d->driver));
421 deviceGroup.writeEntry("captureDevice", d->captureDevice);
422 deviceGroup.writeEntry("playbackDevice", d->playbackDevice);
423 deviceGroup.writeEntry("initialPreference", d->initialPreference);
424 deviceGroup.writeEntry("isAdvanced", d->isAdvanced);
425 } else {
426 if (!deviceGroup.hasKey("initialPreference")) {
427 deviceGroup.writeEntry("initialPreference", d->initialPreference);
429 if (d->captureDevice) { // only "promote" devices
430 deviceGroup.writeEntry("captureDevice", d->captureDevice);
432 if (d->playbackDevice) { // only "promote" devices
433 deviceGroup.writeEntry("playbackDevice", d->playbackDevice);
435 deviceGroup.writeEntry("icon", d->icon);
436 deviceGroup.writeEntry("isAdvanced", d->isAdvanced);
439 //d->applyHardwareDatabaseOverrides();
440 #endif // HAVE_LIBASOUND2
443 int AudioDevice::index() const
445 return d->index;
448 int AudioDevice::initialPreference() const
450 return d->initialPreference;
453 bool AudioDevice::isAvailable() const
455 return d->available;
458 bool AudioDevice::ceaseToExist()
460 if (d->available) {
461 return false; // you cannot remove devices that are plugged in
463 d->valid = false;
464 KSharedConfig::Ptr config = KSharedConfig::openConfig("phonondevicesrc", KConfig::NoGlobals);
465 QString groupName;
466 if (d->captureDevice) {
467 if (d->playbackDevice) {
468 groupName = QLatin1String("AudioIODevice_");
469 } else {
470 groupName = QLatin1String("AudioCaptureDevice_");
472 } else {
473 groupName = QLatin1String("AudioOutputDevice_");
475 groupName += d->uniqueId;
476 config->deleteGroup(groupName);
477 config->sync();
478 return true;
481 bool AudioDevice::isValid() const
483 return d->valid;
486 bool AudioDevice::isCaptureDevice() const
488 return d->captureDevice;
491 bool AudioDevice::isPlaybackDevice() const
493 return d->playbackDevice;
496 bool AudioDevice::isAdvancedDevice() const
498 return d->isAdvanced;
501 AudioDevice::AudioDevice(const AudioDevice &rhs)
502 : d(rhs.d)
506 AudioDevice::~AudioDevice()
510 AudioDevice &AudioDevice::operator=(const AudioDevice &rhs)
512 d = rhs.d;
513 return *this;
516 bool AudioDevice::operator==(const AudioDevice &rhs) const
518 return d->uniqueId == rhs.d->uniqueId;
521 QString AudioDevice::cardName() const
523 return d->cardName;
526 QStringList AudioDevice::deviceIds() const
528 return d->deviceIds;
531 QString AudioDevice::iconName() const
533 return d->icon;
536 Solid::AudioInterface::AudioDriver AudioDevice::driver() const
538 return d->driver;
541 void AudioDevicePrivate::applyHardwareDatabaseOverrides()
543 // now let's take a look at the hardware database whether we have to override something
544 if (HardwareDatabase::contains(uniqueId)) {
545 HardwareDatabase::Entry e = HardwareDatabase::entryFor(uniqueId);
546 if (!e.name.isEmpty()) {
547 cardName = e.name;
549 if (!e.iconName.isEmpty()) {
550 icon = e.iconName;
552 if (e.isAdvanced != 2) {
553 isAdvanced = e.isAdvanced;
555 initialPreference = e.initialPreference;
559 } // namespace Phonon
561 // vim: sw=4 sts=4 et tw=100