2
* KMix -- KDE's full featured mini mixer
5
* Copyright (C) 2004 Christian Esken <esken@kde.org>
7
* This program is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU Library General Public
9
* License as published by the Free Software Foundation; either
10
* version 2 of the License, or (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* Library General Public License for more details.
17
* You should have received a copy of the GNU Library General Public
18
* License along with this program; if not, write to the Free
19
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29
#include <kstandarddirs.h>
31
#include "guiprofile.h"
32
#include "kmixdevicemanager.h"
33
#include "mixdevice.h"
36
#include "mixertoolbox.h"
39
MixerToolBox* MixerToolBox::s_instance = 0;
40
QMap<Mixer*,GUIProfile*> MixerToolBox::s_fallbackProfiles;
41
QRegExp MixerToolBox::s_ignoreMixerExpression("Modem");
42
//KLocale* MixerToolBox::s_whatsthisLocale = 0;
44
/***********************************************************************************
46
This MixerToolBox is linked to the KMix Main Program, the KMix Applet and kmixctrl.
47
As we do not want to link in more than necessary to kmixctrl, you are asked
48
not to put any GUI classes in here.
49
In the case where it is unavoidable, please put them in KMixToolBox.
50
***********************************************************************************/
52
MixerToolBox* MixerToolBox::instance()
54
if ( s_instance == 0 ) {
55
s_instance = new MixerToolBox();
56
// if ( s_ignoreMixerExpression.isEmpty() )
57
// s_ignoreMixerExpression.setPattern("Modem");
64
* Scan for Mixers in the System. This is the method that implicitely fills the
65
* list of Mixer's, which is accessible via the static Mixer::mixer() method.
67
* This is run only once during the initialization phase of KMix. It has the following tasks:
68
* 1) Coldplug scan, to fill the initial mixer list
69
* 2) Rember UDI's, to match them when unplugging a device
70
* 3) Find out, which Backend to use (plugin events of other Backends are ignored).
72
* @par multiDriverMode Whether the Mixer scan should try more all backendends.
73
* 'true' means to scan all backends. 'false' means: After scanning the
74
* current backend the next backend is only scanned if no Mixers were found yet.
75
* @par ref_hwInfoString Here a descripitive text of the scan is returned (Hardware Information)
77
void MixerToolBox::initMixer(bool multiDriverMode, QString& ref_hwInfoString)
79
//kDebug(67100) << "IN MixerToolBox::initMixer()";
81
// Find all mixers and initialize them
82
int drvNum = Mixer::numDrivers();
84
int driverWithMixer = -1;
85
bool multipleDriversActive = false;
87
QString driverInfo = "";
88
QString driverInfoUsed = "";
90
for( int drv1=0; drv1<drvNum; drv1++ )
92
QString driverName = Mixer::driverName(drv1);
93
if ( driverInfo.length() > 0 ) {
96
driverInfo += driverName;
98
/* Run a loop over all drivers. The loop will terminate after the first driver which
99
has mixers. And here is the reason:
100
- If you run ALSA with ALSA-OSS-Emulation enabled, mixers will show up twice: once
101
as native ALSA mixer, once as OSS mixer (emulated by ALSA). This is bad and WILL
102
confuse users. So it is a design decision that we can compile in multiple drivers
103
but we can run only one driver.
104
- For special usage scenarios, people will still want to run both drivers at the
105
same time. We allow them to hack their Config-File, where they can enable a
107
- Another remark: For KMix3.0 or so, we should allow multiple-driver, for allowing
108
addition of special-use drivers, e.g. an Jack-Mixer-Backend, or a CD-Rom volume Backend.
111
bool autodetectionFinished = false;
112
for( int drv=0; drv<drvNum; drv++ )
114
QString driverName = Mixer::driverName(drv);
115
kDebug(67100) << "Looking for mixers with the : " << driverName << " driver";
117
if ( autodetectionFinished ) {
118
// inner loop indicates that we are finished => sane exit from outer loop
122
bool drvInfoAppended = false;
123
// The "19" below is just a "silly" number:
124
// (Old: The loop will break as soon as an error is detected - e.g. on 3rd loop when 2 soundcards are installed)
125
// New: We don't try be that clever anymore. We now blindly scan 20 cards, as the clever
126
// approach doesn't work for the one or other user (e.g. hotplugging might create holes in the list of soundcards).
128
for( int dev=0; dev<=devNumMax; dev++ )
130
Mixer *mixer = new Mixer( driverName, dev );
131
bool mixerAccepted = possiblyAddMixer(mixer);
133
/* Lets decide if the autoprobing shall end (BTW: In multiDriver mode we scan all devices, so no check is necessary) */
134
if ( ! multiDriverMode ) {
135
// In Single-Driver-mode we only need to check after we reached devNumMax
136
if ( dev == devNumMax && Mixer::mixers().count() != 0 )
137
autodetectionFinished = true; // highest device number of driver and a Mixer => finished
142
kDebug(67100) << "Success! Found a mixer with the : " << driverName << " driver";
143
// append driverName (used drivers)
144
if ( !drvInfoAppended )
146
drvInfoAppended = true;
147
if ( Mixer::mixers().count() > 1)
148
driverInfoUsed += " + ";
149
driverInfoUsed += driverName;
152
// Check whether there are mixers in different drivers, so that the user can be warned
153
if ( !multipleDriversActive )
155
if ( driverWithMixer == -1 )
157
// Aha, this is the very first detected device
158
driverWithMixer = drv;
160
else if ( driverWithMixer != drv )
162
// Got him: There are mixers in different drivers
163
multipleDriversActive = true;
165
} // !multipleDriversActive
168
} // loop over sound card devices of current driver
170
if (autodetectionFinished) {
173
} // loop over soundcard drivers
176
// Add a master device (if we haven't defined one yet)
177
if ( Mixer::getGlobalMasterMD(false) == 0 ) {
178
// We have no master card yet. This actually only happens when there was
179
// not one defined in the kmixrc.
180
// So lets just set the first card as master card.
181
if ( Mixer::mixers().count() > 0 ) {
182
QString controlId = Mixer::mixers().first()->getLocalMasterMD()->id();
183
Mixer::setGlobalMaster( Mixer::mixers().first()->id(), controlId);
187
// setGlobalMaster was already set after reading the configuration.
188
// So we must make the local master consistent
189
MixDevice* md = Mixer::getGlobalMasterMD();
190
QString mdID = md->id();
191
md->mixer()->setLocalMasterMD(mdID);
196
if ( Mixer::mixers().count() == 0 )
198
// If there was no mixer found, we assume, that hotplugging will take place
199
// on the preferred driver (this is always the first in the backend list).
200
driverInfoUsed = Mixer::driverName(0);
203
ref_hwInfoString = i18n("Sound drivers supported:");
204
ref_hwInfoString.append(" ").append( driverInfo ).append( "\n").append(i18n("Sound drivers used:")) .append(" ").append(driverInfoUsed);
206
if ( multipleDriversActive )
208
// this will only be possible by hacking the config-file, as it will not be officially supported
209
ref_hwInfoString += "\nExperimental multiple-Driver mode activated";
210
QString allDrivermatch("*");
211
KMixDeviceManager::instance()->setHotpluggingBackends(allDrivermatch);
214
KMixDeviceManager::instance()->setHotpluggingBackends(driverInfoUsed);
217
kDebug(67100) << ref_hwInfoString << endl << "Total number of detected Mixers: " << Mixer::mixers().count();
218
//kDebug(67100) << "OUT MixerToolBox::initMixer()";
222
bool MixerToolBox::possiblyAddMixer(Mixer *mixer)
224
if ( mixer->openIfValid() )
226
if ( (!s_ignoreMixerExpression.isEmpty()) && mixer->id().contains(s_ignoreMixerExpression) ) {
227
// This Mixer should be ignored (default expression is "Modem").
232
Mixer::mixers().append( mixer );
233
// Count mixer nums for every mixer name to identify mixers with equal names.
234
// This is for creating persistent (reusable) primary keys, which can safely
235
// be referenced (especially for config file access, so it is meant to be persistent!).
236
s_mixerNums[mixer->baseName()]++;
237
kDebug(67100) << "mixerNums entry of added mixer is now: " << s_mixerNums[mixer->baseName()];
238
// Create a useful PK
239
/* As we use "::" and ":" as separators, the parts %1,%2 and %3 may not
241
* %1, the driver name is from the KMix backends, it does not contain colons.
242
* %2, the mixer name, is typically coming from an OS driver. It could contain colons.
243
* %3, the mixer number, is a number: it does not contain colons.
245
QString mixerName = mixer->baseName();
246
mixerName.replace(":","_");
247
QString primaryKeyOfMixer = QString("%1::%2:%3")
248
.arg(mixer->getDriverName())
250
.arg(s_mixerNums[mixer->baseName()]);
251
// The following 3 replaces are for not messing up the config file
252
primaryKeyOfMixer.replace("]","_");
253
primaryKeyOfMixer.replace("[","_"); // not strictly necessary, but lets play safe
254
primaryKeyOfMixer.replace(" ","_");
255
primaryKeyOfMixer.replace("=","_");
257
mixer->setID(primaryKeyOfMixer);
258
emit mixerAdded(primaryKeyOfMixer);
269
/* This allows to set an expression form Mixers that should be ignored.
270
The default is "Modem", because most people don't want to control the modem volume. */
271
void MixerToolBox::setMixerIgnoreExpression(QString& ignoreExpr)
273
s_ignoreMixerExpression.setPattern(ignoreExpr);
276
QString MixerToolBox::mixerIgnoreExpression()
278
return s_ignoreMixerExpression.pattern( );
281
void MixerToolBox::removeMixer(Mixer *par_mixer)
283
for (int i=0; i<Mixer::mixers().count(); ++i) {
284
Mixer *mixer = (Mixer::mixers())[i];
285
if ( mixer == par_mixer ) {
286
s_mixerNums[mixer->baseName()]--;
287
kDebug(67100) << "mixerNums entry of removed mixer is now: " << s_mixerNums[mixer->baseName()];
288
Mixer::mixers().removeAt(i);
297
* Clean up and free all resources of all found Mixers, which were found in the initMixer() call
299
void MixerToolBox::deinitMixer()
301
//kDebug(67100) << "IN MixerToolBox::deinitMixer()";
303
int mixerCount = Mixer::mixers().count();
304
for ( int i=0; i<mixerCount; ++i)
306
Mixer* mixer = (Mixer::mixers())[i];
307
//kDebug(67100) << "MixerToolBox::deinitMixer() Remove Mixer";
311
Mixer::mixers().clear();
312
// kDebug(67100) << "OUT MixerToolBox::deinitMixer()";
316
* Find a Mixer. If there is no mixer with the given id, 0 is returned
318
Mixer* MixerToolBox::find( const QString& mixer_id)
320
//kDebug(67100) << "IN MixerToolBox::find()";
323
int mixerCount = Mixer::mixers().count();
324
for ( int i=0; i<mixerCount; ++i)
326
if ( ((Mixer::mixers())[i])->id() == mixer_id )
328
mixer = (Mixer::mixers())[i];
334
// kDebug(67100) << "OUT MixerToolBox::find()";
338
KLocale* MixerToolBox::whatsthisControlLocale()
340
if ( s_whatsthisLocale == 0 ) {
341
s_whatsthisLocale = new KLocale("kmix-controls");
343
return s_whatsthisLocale;
348
Returns a GUI Profile
350
GUIProfile* MixerToolBox::selectProfile(Mixer* mixer)
352
/** This is a two-step process *****************************************
353
* (1) Build a list of all files we want to check
354
* (2) Evaluate all files and keep the best
355
***********************************************************************/
357
GUIProfile* guiprofBest = 0;
358
unsigned long matchValueBest = 0;
359
unsigned long matchValueTemp = 0;
361
QString userProfileDir = KStandardDirs::locateLocal("appdata", "profiles/" );
363
QString mixerNameSpacesToUnderscores = mixer->baseName();
364
mixerNameSpacesToUnderscores.replace(" ","_");
366
// (1) User profile Directory
367
QDir dir(userProfileDir);
368
dir.setFilter(QDir::Files);
369
dir.setNameFilters(QStringList(mixer->getDriverName() + "." + mixerNameSpacesToUnderscores + "*.xml"));
370
QFileInfoList fileList = dir.entryInfoList();
372
QString fileNamePrefix = "profiles/" + mixer->getDriverName() + ".";
374
// (2) Default profile for Soundcard Driver (usually from system Directory)
375
QString fileName = fileNamePrefix + "default.xml";
377
fileNameFQ = KStandardDirs::locate("appdata", fileName );
378
kDebug() << fileName << "; fnfq1=" << fileNameFQ;
379
if (!fileNameFQ.isEmpty())
380
fileList.insert(0, QFileInfo(fileNameFQ));
382
// (3) Soundcard specific profile (usually from system Directory)
383
fileName = fileNamePrefix + mixerNameSpacesToUnderscores + ".xml";
384
fileNameFQ = KStandardDirs::locate("appdata", fileName );
385
kDebug() << fileName << "; fnfq2=" << fileNameFQ;
386
if (!fileNameFQ.isEmpty())
387
fileList.insert(0, QFileInfo(fileNameFQ));
391
for (int i = 0; i < fileList.size(); ++i) {
392
QFileInfo fileInfo = fileList.at(i);
393
QString fileNameAbs = fileInfo.absoluteFilePath();
394
QString fileNameRelToProfile = "profiles/" + fileInfo.fileName();
395
kDebug() << i << ": Try user profile " << fileNameAbs;
396
GUIProfile* guiprofTemp = new GUIProfile();
397
if ( guiprofTemp->readProfile(fileNameAbs, fileNameRelToProfile) ) {
398
matchValueTemp = guiprofTemp->match(mixer);
399
if ( matchValueTemp < matchValueBest ) {
405
guiprofBest = guiprofTemp;
406
matchValueBest = matchValueTemp;
412
if ( guiprofBest == 0 ) {
413
// Still no profile found. This should usually not happen. This means one of the following things:
414
// a) The KMix installation is not OK
415
// b) The user has a defective profile in ~/.kde/share/apps/kmix/profiles/
416
// c) It is a Backend that ships no default profile (currently this is only Mixer_SUN and Mixer_PULSE)
417
guiprofBest = fallbackProfile(mixer);
419
// kDebug(67100) << "New Best =" << matchValueBest << " pointer=" << guiprofBest << "\n";
425
GUIProfile* MixerToolBox::fallbackProfile(Mixer *mixer)
427
if ( ! s_fallbackProfiles.contains(mixer) ) {
428
GUIProfile *fallback = new GUIProfile();
430
ProfProduct* prd = new ProfProduct();
431
prd->vendor = mixer->getDriverName();
432
prd->productName = mixer->readableName();
433
prd->productRelease = "1.0";
434
fallback->_products.insert(prd);
436
ProfControl* ctl = new ProfControl();
438
ctl->regexp = ".*"; // make sure id matches the regexp
439
ctl->subcontrols = ".*";
440
ctl->show = "simple";
441
fallback->_controls.push_back(ctl);
443
fallback->_soundcardDriver = mixer->getDriverName();
444
fallback->_soundcardName = mixer->readableName();
446
fallback->finalizeProfile();
447
s_fallbackProfiles[mixer] = fallback;
449
return s_fallbackProfiles[mixer];
452
#include "mixertoolbox.moc"