summaryrefslogtreecommitdiffstats
path: root/kmix/mixertoolbox.cpp
blob: 1021fd8c02118cc1cb719a23b08190a4aaa1858a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/*
 * KMix -- KDE's full featured mini mixer
 *
 *
 * Copyright (C) 2004 Christian Esken <esken@kde.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program 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 program; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */


#include "tqwidget.h"
#include "tqstring.h"

//#include <kdebug.h>
#include <tdelocale.h>

#include "mixdevice.h"
#include "mixer.h"
#include "mixer_backend.h"

#include "mixertoolbox.h"

#ifndef KMIX_PLATFORMS_CPP
typedef Mixer_Backend *getMixerFunc( int device );
typedef TQString getDriverNameFunc( );
typedef DevIterator* getDevIteratorFunc( );

struct MixerFactory {
    getMixerFunc *getMixer;
    getDriverNameFunc *getDriverName;
    getDevIteratorFunc *getDevIterator;
};

extern MixerFactory g_mixerFactories[];
#endif

/***********************************************************************************
 Attention:
 This MixerToolBox is linked to the KMix Main Program, the KMix Applet and kmixctrl.
 As we do not want to link in more than neccesary to kmixctrl, you are asked
 not to put any GUI classes in here.
 In the case where it is unavoidable, please put them in KMixToolBox.
 ***********************************************************************************/

/**
 * Scan for Mixers in the System and fills the given list.
 * @par mixers The list where to add the found Mixers. This parameter is superfluous
 *             nowadays, as it is now really trivial to get it - just call the static
 *             Mixer::mixer() method.
 * @par multiDriverMode Whether the Mixer scan should try more all backendends.
 *          'true' means to scan all backends. 'false' means: After scanning the
 *          current backend the next backend is only scanned if no Mixers were found yet.
 */
void MixerToolBox::initMixer(TQPtrList<Mixer> &mixers, bool multiDriverMode, TQString& ref_hwInfoString)
{
   //kdDebug(67100) << "IN MixerToolBox::initMixer()"<<endl;

    // Find all mixers and initalize them
    TQMap<TQString,int> mixerNums;
    int drvNum = Mixer::numDrivers();

    int driverWithMixer = -1;
    bool multipleDriversActive = false;

    TQString driverInfo = "";
    TQString driverInfoUsed = "";

    for( int drv1=0; drv1<drvNum; drv1++ )
    {
      TQString driverName = Mixer::driverName(drv1);
      if ( driverInfo.length() > 0 )
	driverInfo += " + ";
      driverInfo += driverName;
    }
	/* Run a loop over all drivers. The loop will terminate after the first driver which
	   has mixers. And here is the reason:
	   - If you run ALSA with ALSA-OSS-Emulation enabled, mixers will show up twice: once
	     as native ALSA mixer, once as OSS mixer (emulated by ALSA). This is bad and WILL
	     confuse users. So it is a design decision that we can compile in multiple drivers
	     but we can run only one driver.
	   - For special usage scenarios, people will still want to run both drivers at the
	     same time. We allow them to hack their Config-File, where they can enable a
	     multi-driver mode.
	   - Another remark: For KMix3.0 or so, we should allow multiple-driver, for allowing
	     addition of special-use drivers, e.g. an Jack-Mixer-Backend, or a CD-Rom volume Backend.
	 */

    bool autodetectionFinished = false;
    for( int drv=0; drv<drvNum; drv++ )
    {
	  TQString driverName = Mixer::driverName(drv);

	  if ( autodetectionFinished ) {
		// sane exit from loop
		break;
	    }

	    bool drvInfoAppended = false;
	    // The "19" below is just a "silly" number:
	    // (Old: The loop will break as soon as an error is detected - e.g. on 3rd loop when 2 soundcards are installed)
	    // New: We don't try be that clever anymore. We now blindly scan 20 cards, as the clever
	    // approach doesn't work for the one or other user.
	    int devNumMax = 19;
	    getDevIteratorFunc *f = g_mixerFactories[drv].getDevIterator;
	    DevIterator *devIt = f ? f() : new DevIterator();
	    for (; !devIt->end(); devIt->next())
	    {
		int dev = devIt->getdev();
		// Check with backend if mixer is invalid
		if (!Mixer::isValid(drv, dev))
		{
		  continue;
		}
		// Check if mixer already exists
		Mixer *mixer = new Mixer(drv, dev);
		Mixer *m;
		for (m = mixers.first(); m; m = mixers.next())
		{
		  if (mixer->devnum() == m->devnum())
		  {
		    break;
		  }
		}
		if (m)
		{
		  delete mixer;
		  mixer = 0;
		  continue;
		}
		// New mixer found
		mixer->open();
		mixers.append( mixer );
		// Count mixer nums for every mixer name to identify mixers with equal names.
		// This is for creating persistent (reusable) primary keys, which can safely
		// be referenced (especially for config file access, so it is meant to be persistent!).
		mixerNums[mixer->mixerName()]++;
		// Create a useful PK
		/* As we use "::" and ":" as separators, the parts %1,%2 and %3 may not
		 * contain it.
		 * %1, the driver name is from the KMix backends, it does not contain colons.
		 * %2, the mixer name, is typically coming from an OS driver. It could contain colons.
		 * %3, the mixer number, is a number: it does not contain colons.
		 */
		TQString mixerName = mixer->mixerName();
		mixerName.replace(":","_");
		TQString primaryKeyOfMixer = TQString("%1::%2:%3")
		    .arg(driverName)
		    .arg(mixerName)
		    .arg(mixerNums[mixer->mixerName()]);
		// The following 3 replaces are for not messing up the config file
		primaryKeyOfMixer.replace("]","_");
		primaryKeyOfMixer.replace("[","_"); // not strictly necessary, but let's play safe
		primaryKeyOfMixer.replace(" ","_");
		primaryKeyOfMixer.replace("=","_");
		mixer->setID(primaryKeyOfMixer);

		// Lets decide if we the autoprobing shall continue
		if ( multiDriverMode ) {
			// trivial case: In multiDriverMode, we scan ALL 20 devs of ALL drivers
			// so we have to do "nothing" in this case
		} // multiDriver
		else {
			// In No-multiDriver-mode we only need to check after we reached devNumMax
			if ( dev == devNumMax ) {
			   if ( mixers.count() != 0 ) {
				// highest device number of driver and a Mixer => finished
				autodetectionFinished = true;	
			   }
			}
		} // !multiDriver

		// append driverName (used drivers)
		if ( !drvInfoAppended )
                {
		    drvInfoAppended = true;
		    TQString driverName = Mixer::driverName(drv);
		    if ( drv!= 0 && mixers.count() > 0) {
			driverInfoUsed += " + ";
		    }
		    driverInfoUsed += driverName;
		}

		// Check whether there are mixers in different drivers, so that the user can be warned
		if (!multipleDriversActive)
		{
		    if ( driverWithMixer == -1 )
		    {
			// Aha, this is the very first detected device
			driverWithMixer = drv;
		    }
		    else
		    {
			if ( driverWithMixer != drv )
			{
			    // Got him: There are mixers in different drivers
			    multipleDriversActive = true;
			}
		    }
		} //  !multipleDriversActive
		
	    } // loop over sound card devices of current driver
	    delete devIt;
	} // loop over soundcard drivers

        if (Mixer::masterCard() == 0)
        {
           // We have no master card yet. This actually only happens when there was
           // not one defined in the kmixrc.
           // So lets just set the first card as master card.
           if (mixers.count() > 0)
           {
              Mixer *mixer = mixers.first();
              Mixer::setMasterCard(mixer->id());
              MixSet ms = mixer->getMixSet();
              for (MixDevice *md = ms.first(); md != 0; md = ms.next())
              {
		 if (!md->isRecordable() && !md->isSwitch() && !md->isEnum())
		 {
		    Mixer::setMasterCardDevice(md->getPK());
		    break;
		 }
              }
           }
        }

	ref_hwInfoString = i18n("Sound drivers supported:");
	ref_hwInfoString.append(" ").append( driverInfo ).append(	"\n").append(i18n("Sound drivers used:")) .append(" ").append(driverInfoUsed);

	if ( multipleDriversActive )
	{
	    // this will only be possible by hacking the config-file, as it will not be officially supported
	    ref_hwInfoString += "\nExperimental multiple-Driver mode activated";
	}

	kdDebug(67100) << ref_hwInfoString << endl << "Total number of detected Mixers: " << mixers.count() << endl;
   //kdDebug(67100) << "OUT MixerToolBox::initMixer()"<<endl;

}


/*
 * Clean up and free all resources of all found Mixers, which were found in the initMixer() call
 */
void MixerToolBox::deinitMixer()
{
   // kdDebug(67100) << "IN MixerToolBox::deinitMixer()" << endl;
   deinitMixer(Mixer::mixers());
   // kdDebug(67100) << "OUT MixerToolBox::deinitMixer()" << endl;
}

/**
 * Clean up and free all resources of the given mixers
 * @par mixers The list of mixers to deinitialize.
 */
void MixerToolBox::deinitMixer(TQPtrList<Mixer> &mixers)
{
   // kdDebug(67100) << "IN MixerToolBox::deinitMixer(TQPtrList<Mixer> &mixers)" << endl;
   while (!mixers.isEmpty())
   {
      Mixer *mixer=mixers.first();
      //kdDebug(67100) << "MixerToolBox::deinitMixer(TQPtrList<Mixer> &mixers) remove mixer " << mixer->mixerName() << endl;
      mixer->close();
      mixers.remove(mixer);
      delete mixer;
   }
   // kdDebug(67100) << "OUT MixerToolBox::deinitMixer(TQPtrList<Mixer> &mixers)" << endl;
}

/**
 * Clean up and free all resources of the given mixers
 * @par mixers The list of mixers to deinitialize.
 */
/**
 * Scan for Mixers in the System and update the given list. No longer existing mixers
 * will be deinitialized.
 * @par mixers The list where to add the found Mixers. This parameter is superfluous
 *             nowadays, as it is now really trivial to get it - just call the static
 *             Mixer::mixer() method.
 * @par multiDriverMode Whether the Mixer scan should try more all backendends.
 *          'true' means to scan all backends. 'false' means: After scanning the
 *          current backend the next backend is only scanned if no Mixers were found yet.
 */
void MixerToolBox::updateMixer(TQPtrList<Mixer> &mixers, bool multiDriverMode, TQString& ref_hwInfoString)
{
  TQPtrList<Mixer> newMixers;
  // Scan for new mixers
  initMixer(newMixers, multiDriverMode, ref_hwInfoString);
  // Remove no longer existing mixers
  TQPtrList<Mixer> mixersToDeinit;
  Mixer *searchMixer = NULL;
  Mixer *currMixer = mixers.first();
  while (currMixer)
  {
    Mixer *searchMixer = newMixers.first();
    while (searchMixer)
    {
      if (currMixer->id() == searchMixer->id())
	{
	  break;
	}
	searchMixer = newMixers.next();
    }
    if (!searchMixer)
    {
	mixers.take();
	mixersToDeinit.append(currMixer);
    }
    currMixer = mixers.next();
  }
  deinitMixer(mixersToDeinit);
  // Add newly found mixers
  searchMixer = newMixers.first();
  while (searchMixer)
  {
    currMixer = mixers.first();
    while (currMixer)
    {
	if (searchMixer->id() == currMixer->id())
	{
	  break;
	}
	currMixer = mixers.next();
    }
    if (!currMixer)
    {
	// New mixer, append to existing list
	newMixers.take();
	mixers.append(searchMixer);
    }
    searchMixer = newMixers.next();
  }
  // Deallocate duplicated mixers
  deinitMixer(newMixers);
}