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>
33 #ifdef HAVE_LIBASOUND2
34 #include <alsa/asoundlib.h>
35 #endif // HAVE_LIBASOUND2
40 QStringList
AudioDevice::addSoftVolumeMixerControl(const AudioDevice
&device
, const QStringList
&mixerControlNames
)
42 if (device
.driver() != Solid::AudioInterface::Alsa
) {
46 QStringList ids
= device
.deviceIds();
47 foreach (const QString
&mixerControlName
, mixerControlNames
) {
49 foreach (QString id
, ids
) {
50 id
.replace(QLatin1Char('"'), QLatin1String("\\\""));
51 tmp
<< QString("phonon_softvol:CARD=\"%1\",NAME=\"%2\",SLAVE=\"%3\"")
53 .arg(mixerControlName
)
62 AudioDevice::AudioDevice()
63 : d(new AudioDevicePrivate
)
67 KConfigGroup
AudioDevicePrivate::configGroup(KSharedConfig::Ptr config
)
72 groupName
= QLatin1String("AudioIODevice_");
74 groupName
= QLatin1String("AudioCaptureDevice_");
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") {*/
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");
150 case Solid::AudioInterface::AudioOutput
:
151 d
->uniqueId
+= QLatin1String(":playback");
153 case 6: //Solid::AudioInterface::AudioInput | Solid::AudioInterface::AudioOutput:
154 d
->uniqueId
+= QLatin1String(":both");
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();
169 case Solid::AudioInterface::OpenSoundSystem
:
170 d
->uniqueId
+= QLatin1String(":oss");
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;
196 case Solid::AudioInterface::UnknownAudioDriver
:
199 case Solid::AudioInterface::OpenSoundSystem
:
200 if (handle
.type() != QVariant::String
) {
204 d
->deviceIds
<< handle
.toString();
206 case Solid::AudioInterface::Alsa
:
207 if (handle
.type() != QVariant::List
) {
211 const QList
<QVariant
> handles
= handle
.toList();
212 if (handles
.size() < 1) {
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;
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
;
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");
242 case Solid::AudioInterface::UsbSoundcard
:
243 d
->icon
= QLatin1String("audio-card-usb");
244 d
->initialPreference
-= 10;
246 case Solid::AudioInterface::FirewireSoundcard
:
247 d
->icon
= QLatin1String("audio-card-firewire");
248 d
->initialPreference
-= 10;
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");
255 d
->icon
= QLatin1String("audio-headset");
257 d
->initialPreference
-= 10;
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?
269 Solid::AudioInterface::AudioInterfaceTypes deviceType
= audioHw
->deviceType();
270 if (deviceType
== Solid::AudioInterface::AudioInput
) {
271 d
->captureDevice
= true;
273 if (deviceType
== Solid::AudioInterface::AudioOutput
) {
274 d
->playbackDevice
= true;
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
);
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
)
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/")) {
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
);
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;
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 */)) {
375 d
->playbackDevice
= true;
379 if (0 == snd_pcm_open(&pcm
, deviceNameEnc
.constData(), SND_PCM_STREAM_CAPTURE
, SND_PCM_NONBLOCK
/*open mode: non-blocking, sync */)) {
381 d
->captureDevice
= true;
386 if (description
.contains("headset", Qt::CaseInsensitive
) ||
387 description
.contains("headphone", Qt::CaseInsensitive
)) {
389 if (description
.contains("usb", Qt::CaseInsensitive
)) {
390 d
->icon
= QLatin1String("audio-headset-usb");
391 d
->initialPreference
-= 10;
393 d
->icon
= QLatin1String("audio-headset");
394 d
->initialPreference
-= 10;
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;
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
);
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
448 int AudioDevice::initialPreference() const
450 return d
->initialPreference
;
453 bool AudioDevice::isAvailable() const
458 bool AudioDevice::ceaseToExist()
461 return false; // you cannot remove devices that are plugged in
464 KSharedConfig::Ptr config
= KSharedConfig::openConfig("phonondevicesrc", KConfig::NoGlobals
);
466 if (d
->captureDevice
) {
467 if (d
->playbackDevice
) {
468 groupName
= QLatin1String("AudioIODevice_");
470 groupName
= QLatin1String("AudioCaptureDevice_");
473 groupName
= QLatin1String("AudioOutputDevice_");
475 groupName
+= d
->uniqueId
;
476 config
->deleteGroup(groupName
);
481 bool AudioDevice::isValid() const
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
)
506 AudioDevice::~AudioDevice()
510 AudioDevice
&AudioDevice::operator=(const AudioDevice
&rhs
)
516 bool AudioDevice::operator==(const AudioDevice
&rhs
) const
518 return d
->uniqueId
== rhs
.d
->uniqueId
;
521 QString
AudioDevice::cardName() const
526 QStringList
AudioDevice::deviceIds() const
531 QString
AudioDevice::iconName() const
536 Solid::AudioInterface::AudioDriver
AudioDevice::driver() const
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()) {
549 if (!e
.iconName
.isEmpty()) {
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