1 /*
2 * Alsa MIXER Wine Driver for Linux
3 * Very loosely based on wineoss mixer driver
4 *
5 * Copyright 2007 Maarten Lankhorst
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library 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 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 */
21
22 #include "config.h"
23 #include "wine/port.h"
24
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <string.h>
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32 #include <fcntl.h>
33 #include <errno.h>
34 #include <assert.h>
35 #ifdef HAVE_SYS_IOCTL_H
36 # include <sys/ioctl.h>
37 #endif
38
39 #define NONAMELESSUNION
40 #define NONAMELESSSTRUCT
41
42 #include "windef.h"
43 #include "winbase.h"
44 #include "wingdi.h"
45 #include "winuser.h"
46 #include "winnls.h"
47 #include "mmddk.h"
48 #include "mmsystem.h"
49 #include "alsa.h"
50 #include "wine/unicode.h"
51 #include "wine/debug.h"
52
53 WINE_DEFAULT_DEBUG_CHANNEL(mixer);
54
55 #ifdef HAVE_ALSA
56
57 #define WINE_MIXER_MANUF_ID 0xAA
58 #define WINE_MIXER_PRODUCT_ID 0x55
59 #define WINE_MIXER_VERSION 0x0100
60
61 /* Generic notes:
62 * In windows it seems to be required for all controls to have a volume switch
63 * In alsa that's optional
64 *
65 * I assume for playback controls, that there is always a playback volume switch available
66 * Mute is optional
67 *
68 * For capture controls, it is needed that there is a capture switch and a volume switch,
69 * It doesn't matter whether it is a playback volume switch or a capture volume switch.
70 * The code will first try to get/adjust capture volume, if that fails it tries playback volume
71 * It is not pretty, but under my 3 test cards it seems that there is no other choice:
72 * Most capture controls don't have a capture volume setting
73 *
74 * MUX means that only capture source can be exclusively selected,
75 * MIXER means that multiple sources can be selected simultaneously.
76 */
77
78 static const char * getMessage(UINT uMsg)
79 {
80 static char str[64];
81 #define MSG_TO_STR(x) case x: return #x;
82 switch (uMsg){
83 MSG_TO_STR(DRVM_INIT);
84 MSG_TO_STR(DRVM_EXIT);
85 MSG_TO_STR(DRVM_ENABLE);
86 MSG_TO_STR(DRVM_DISABLE);
87 MSG_TO_STR(MXDM_GETDEVCAPS);
88 MSG_TO_STR(MXDM_GETLINEINFO);
89 MSG_TO_STR(MXDM_GETNUMDEVS);
90 MSG_TO_STR(MXDM_OPEN);
91 MSG_TO_STR(MXDM_CLOSE);
92 MSG_TO_STR(MXDM_GETLINECONTROLS);
93 MSG_TO_STR(MXDM_GETCONTROLDETAILS);
94 MSG_TO_STR(MXDM_SETCONTROLDETAILS);
95 default: break;
96 }
97 #undef MSG_TO_STR
98 sprintf(str, "UNKNOWN(%08x)", uMsg);
99 return str;
100 }
101
102 static const char * getControlType(DWORD dwControlType)
103 {
104 #define TYPE_TO_STR(x) case x: return #x;
105 switch (dwControlType) {
106 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
107 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
108 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
109 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
110 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
111 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
112 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
113 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
114 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
115 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
116 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
117 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
118 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
119 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
120 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
121 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
122 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
123 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
124 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
125 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
126 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
127 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
128 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
129 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
130 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
131 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
132 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
133 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
134 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
135 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
136 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
137 }
138 #undef TYPE_TO_STR
139 return wine_dbg_sprintf("UNKNOWN(%08x)", dwControlType);
140 }
141
142 /* A simple declaration of a line control
143 * These are each of the channels that show up
144 */
145 typedef struct line {
146 /* Name we present to outside world */
147 WCHAR name[MAXPNAMELEN];
148
149 DWORD component;
150 DWORD dst;
151 DWORD capt;
152 DWORD chans;
153 snd_mixer_elem_t *elem;
154 } line;
155
156 /* A control structure, with toggle enabled switch
157 * Control structures control volume, muted, which capture source
158 */
159 typedef struct control {
160 BOOL enabled;
161 MIXERCONTROLW c;
162 } control;
163
164 /* Mixer device */
165 typedef struct mixer
166 {
167 snd_mixer_t *mix;
168 WCHAR mixername[MAXPNAMELEN];
169
170 int chans, dests;
171 LPDRVCALLBACK callback;
172 DWORD_PTR callbackpriv;
173 HDRVR hmx;
174
175 line *lines;
176 control *controls;
177 } mixer;
178
179 #define MAX_MIXERS 32
180 #define CONTROLSPERLINE 3
181 #define OFS_MUTE 2
182 #define OFS_MUX 1
183
184 static int cards = 0;
185 static mixer mixdev[MAX_MIXERS];
186 static HANDLE thread;
187 static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask);
188 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam);
189 static CRITICAL_SECTION elem_crst;
190 static int msg_pipe[2];
191 static LONG refcnt;
192
193 /* found channel names in alsa lib, alsa api doesn't have another way for this
194 * map name -> componenttype, worst case we get a wrong componenttype which is
195 * mostly harmless
196 */
197
198 static const struct mixerlinetype {
199 const char *name; DWORD cmpt;
200 } converttable[] = {
201 { "Master", MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, },
202 { "Capture", MIXERLINE_COMPONENTTYPE_DST_WAVEIN, },
203 { "PCM", MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT, },
204 { "PC Speaker", MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER, },
205 { "Synth", MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER, },
206 { "Headphone", MIXERLINE_COMPONENTTYPE_DST_HEADPHONES, },
207 { "Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
208 { "Aux", MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED, },
209 { "CD", MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC, },
210 { "Line", MIXERLINE_COMPONENTTYPE_SRC_LINE, },
211 { "Phone", MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE, },
212 { "Digital", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
213 { "Front Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
214 };
215
216 /* Map name to MIXERLINE_COMPONENTTYPE_XXX */
217 static int getcomponenttype(const char *name)
218 {
219 int x;
220 for (x=0; x< sizeof(converttable)/sizeof(converttable[0]); ++x)
221 if (!strcasecmp(name, converttable[x].name))
222 {
223 TRACE("%d -> %s\n", x, name);
224 return converttable[x].cmpt;
225 }
226 WARN("Unknown mixer name %s, probably harmless\n", name);
227 return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
228 }
229
230 /* Is this control suited for showing up? */
231 static int blacklisted(snd_mixer_elem_t *elem)
232 {
233 const char *name = snd_mixer_selem_get_name(elem);
234 BOOL blisted = 0;
235
236 if (!snd_mixer_selem_has_playback_volume(elem) &&
237 !snd_mixer_selem_has_capture_volume(elem))
238 blisted = 1;
239
240 TRACE("%s: %x\n", name, blisted);
241 return blisted;
242 }
243
244 static void fillcontrols(mixer *mmixer)
245 {
246 int id;
247 for (id = 0; id < mmixer->chans; ++id)
248 {
249 line *mline = &mmixer->lines[id];
250 int ofs = CONTROLSPERLINE * id;
251 int x;
252 long min, max;
253
254 TRACE("Filling control %d\n", id);
255 if (id == 1 && !mline->elem)
256 continue;
257
258 if (mline->capt && snd_mixer_selem_has_capture_volume(mline->elem))
259 snd_mixer_selem_get_capture_volume_range(mline->elem, &min, &max);
260 else
261 snd_mixer_selem_get_playback_volume_range(mline->elem, &min, &max);
262
263 /* (!snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) */
264 /* Volume, always enabled by definition of blacklisted channels */
265 mmixer->controls[ofs].enabled = 1;
266 mmixer->controls[ofs].c.cbStruct = sizeof(mmixer->controls[ofs].c);
267 mmixer->controls[ofs].c.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
268 mmixer->controls[ofs].c.dwControlID = ofs;
269 mmixer->controls[ofs].c.Bounds.s1.dwMinimum = 0;
270 mmixer->controls[ofs].c.Bounds.s1.dwMaximum = 65535;
271 mmixer->controls[ofs].c.Metrics.cSteps = 65536/(max-min);
272
273 if ((id == 1 && snd_mixer_selem_has_capture_switch(mline->elem)) ||
274 (!mline->capt && snd_mixer_selem_has_playback_switch(mline->elem)))
275 { /* MUTE button optional, main capture channel should have one too */
276 mmixer->controls[ofs+OFS_MUTE].enabled = 1;
277 mmixer->controls[ofs+OFS_MUTE].c.cbStruct = sizeof(mmixer->controls[ofs].c);
278 mmixer->controls[ofs+OFS_MUTE].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
279 mmixer->controls[ofs+OFS_MUTE].c.dwControlID = ofs+OFS_MUTE;
280 mmixer->controls[ofs+OFS_MUTE].c.Bounds.s1.dwMaximum = 1;
281 }
282
283 if (mline->capt && snd_mixer_selem_has_capture_switch_exclusive(mline->elem))
284 mmixer->controls[CONTROLSPERLINE+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUX;
285
286 if (id == 1)
287 { /* Capture select, in case cMultipleItems is 0, it means capture is disabled anyway */
288 mmixer->controls[ofs+OFS_MUX].enabled = 1;
289 mmixer->controls[ofs+OFS_MUX].c.cbStruct = sizeof(mmixer->controls[ofs].c);
290 mmixer->controls[ofs+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MIXER;
291 mmixer->controls[ofs+OFS_MUX].c.dwControlID = ofs+OFS_MUX;
292 mmixer->controls[ofs+OFS_MUX].c.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE;
293
294 for (x = 0; x<mmixer->chans; ++x)
295 if (x != id && mmixer->lines[x].dst == id)
296 ++(mmixer->controls[ofs+OFS_MUX].c.cMultipleItems);
297 if (!mmixer->controls[ofs+OFS_MUX].c.cMultipleItems)
298 mmixer->controls[ofs+OFS_MUX].enabled = 0;
299
300 mmixer->controls[ofs+OFS_MUX].c.Bounds.s1.dwMaximum = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems - 1;
301 mmixer->controls[ofs+OFS_MUX].c.Metrics.cSteps = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems;
302 }
303 for (x=0; x<CONTROLSPERLINE; ++x)
304 {
305 lstrcpynW(mmixer->controls[ofs+x].c.szShortName, mline->name, sizeof(mmixer->controls[ofs+x].c.szShortName)/sizeof(WCHAR));
306 lstrcpynW(mmixer->controls[ofs+x].c.szName, mline->name, sizeof(mmixer->controls[ofs+x].c.szName)/sizeof(WCHAR));
307 }
308 }
309 }
310
311 /* get amount of channels for elem */
312 /* Officially we should keep capture/playback separated,
313 * but that's not going to work in the alsa api */
314 static int chans(mixer *mmixer, snd_mixer_elem_t * elem, DWORD capt)
315 {
316 int ret=0, chn;
317
318 if (capt && snd_mixer_selem_has_capture_volume(elem)) {
319 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
320 if (snd_mixer_selem_has_capture_channel(elem, chn))
321 ++ret;
322 } else {
323 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
324 if (snd_mixer_selem_has_playback_channel(elem, chn))
325 ++ret;
326 }
327 if (!ret)
328 FIXME("Mixer channel %s was found for %s, but no channels were found? Wrong selection!\n", snd_mixer_selem_get_name(elem), (snd_mixer_selem_has_playback_volume(elem) ? "playback" : "capture"));
329 return ret;
330 }
331
332 static void filllines(mixer *mmixer, snd_mixer_elem_t *mastelem, snd_mixer_elem_t *captelem, int capt)
333 {
334 snd_mixer_elem_t *elem;
335 line *mline = mmixer->lines;
336
337 /* Master control */
338 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
339 mline->component = getcomponenttype(snd_mixer_selem_get_name(mastelem));
340 mline->dst = 0;
341 mline->capt = 0;
342 mline->elem = mastelem;
343 mline->chans = chans(mmixer, mastelem, 0);
344
345 snd_mixer_elem_set_callback(mastelem, &elem_callback);
346 snd_mixer_elem_set_callback_private(mastelem, mmixer);
347
348 /* Capture control
349 * Note: since mmixer->dests = 1, it means only playback control is visible
350 * This makes sense, because if there are no capture sources capture control
351 * can't do anything and should be invisible */
352
353 /* Control 1 is reserved for capture even when not enabled */
354 ++mline;
355 if (capt)
356 {
357 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
358 mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
359 mline->dst = 1;
360 mline->capt = 1;
361 mline->elem = captelem;
362 mline->chans = chans(mmixer, captelem, 1);
363
364 snd_mixer_elem_set_callback(captelem, &elem_callback);
365 snd_mixer_elem_set_callback_private(captelem, mmixer);
366 }
367
368 for (elem = snd_mixer_first_elem(mmixer->mix); elem; elem = snd_mixer_elem_next(elem))
369 if (elem != mastelem && elem != captelem && !blacklisted(elem))
370 {
371 const char * name = snd_mixer_selem_get_name(elem);
372 DWORD comp = getcomponenttype(name);
373
374 if (snd_mixer_selem_has_playback_volume(elem) &&
375 (snd_mixer_selem_has_capture_volume(elem) || comp != MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE))
376 {
377 (++mline)->component = comp;
378 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
379 mline->capt = mline->dst = 0;
380 mline->elem = elem;
381 mline->chans = chans(mmixer, elem, 0);
382 }
383 else if (!capt)
384 continue;
385
386 if (capt && (snd_mixer_selem_has_capture_volume(elem) || comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE))
387 {
388 (++mline)->component = comp;
389 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
390 mline->capt = mline->dst = 1;
391 mline->elem = elem;
392 mline->chans = chans(mmixer, elem, 1);
393 }
394
395 snd_mixer_elem_set_callback(elem, &elem_callback);
396 snd_mixer_elem_set_callback_private(elem, mmixer);
397 }
398 }
399
400 /* Windows api wants to have a 'master' device to which all slaves are attached
401 * There are 2 ones in this code:
402 * - 'Master', fall back to 'Headphone' if unavailable, and if that's not available 'PCM'
403 * - 'Capture'
404 * Capture might not always be available, so should be prepared to be without if needed
405 */
406
407 static void ALSA_MixerInit(void)
408 {
409 int x, mixnum = 0;
410 snd_ctl_card_info_t *info;
411
412 info = HeapAlloc( GetProcessHeap(), 0, snd_ctl_card_info_sizeof());
413 for (x = 0; x < MAX_MIXERS; ++x)
414 {
415 int card, err, capcontrols = 0;
416 char cardind[6], cardname[10];
417
418 snd_ctl_t *ctl;
419 snd_mixer_elem_t *elem, *mastelem = NULL, *headelem = NULL, *captelem = NULL, *pcmelem = NULL;
420
421 memset(info, 0, snd_ctl_card_info_sizeof());
422 memset(&mixdev[mixnum], 0, sizeof(*mixdev));
423 snprintf(cardind, sizeof(cardind), "%d", x);
424 card = snd_card_get_index(cardind);
425 if (card < 0)
426 continue;
427
428 snprintf(cardname, sizeof(cardname), "hw:%d", card);
429
430 err = snd_ctl_open(&ctl, cardname, 0);
431 if (err < 0)
432 {
433 WARN("Cannot open card: %s\n", snd_strerror(err));
434 continue;
435 }
436
437 err = snd_ctl_card_info(ctl, info);
438 if (err < 0)
439 {
440 WARN("Cannot get card info: %s\n", snd_strerror(err));
441 snd_ctl_close(ctl);
442 continue;
443 }
444
445 MultiByteToWideChar(CP_UNIXCP, 0, snd_ctl_card_info_get_name(info), -1, mixdev[mixnum].mixername, sizeof(mixdev[mixnum].mixername)/sizeof(WCHAR));
446 snd_ctl_close(ctl);
447
448 err = snd_mixer_open(&mixdev[mixnum].mix, 0);
449 if (err < 0)
450 {
451 WARN("Error occurred opening mixer: %s\n", snd_strerror(err));
452 continue;
453 }
454
455 err = snd_mixer_attach(mixdev[mixnum].mix, cardname);
456 if (err < 0)
457 goto eclose;
458
459 err = snd_mixer_selem_register(mixdev[mixnum].mix, NULL, NULL);
460 if (err < 0)
461 goto eclose;
462
463 err = snd_mixer_load(mixdev[mixnum].mix);
464 if (err < 0)
465 goto eclose;
466
467 /* First, lets see what's available..
468 * If there are multiple Master or Captures, all except 1 will be added as slaves
469 */
470 for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
471 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Master") && !mastelem)
472 mastelem = elem;
473 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture") && !captelem)
474 captelem = elem;
475 else if (!blacklisted(elem))
476 {
477 DWORD comp = getcomponenttype(snd_mixer_selem_get_name(elem));
478 DWORD skip = 0;
479
480 /* Work around buggy drivers: Make this a capture control if the name is recognised as a microphone */
481 if (snd_mixer_selem_has_capture_volume(elem))
482 ++capcontrols;
483 else if (comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
484 {
485 ++capcontrols;
486 skip = 1;
487 }
488
489 if (!skip && snd_mixer_selem_has_playback_volume(elem))
490 {
491 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Headphone") && !headelem)
492 headelem = elem;
493 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "PCM") && !pcmelem)
494 pcmelem = elem;
495 else
496 ++(mixdev[mixnum].chans);
497 }
498 }
499
500 /* Add master channel, uncounted channels and an extra for capture */
501 mixdev[mixnum].chans += !!mastelem + !!headelem + !!pcmelem + 1;
502
503 /* If there is only 'Capture' and 'Master', this device is not worth it */
504 if (mixdev[mixnum].chans == 2)
505 {
506 WARN("No channels found, skipping device!\n");
507 goto close;
508 }
509
510 /* Master element can't have a capture control in this code, so
511 * if Headphone or PCM is promoted to master, unset its capture control */
512 if (headelem && !mastelem)
513 {
514 /* Using 'Headphone' as master device */
515 mastelem = headelem;
516 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
517 }
518 else if (pcmelem && !mastelem)
519 {
520 /* Use 'PCM' as master device */
521 mastelem = pcmelem;
522 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
523 }
524 else if (!mastelem)
525 {
526 /* If there is nothing sensible that can act as 'Master' control, something is wrong */
527 FIXME("No master control found on %s, disabling mixer\n", snd_ctl_card_info_get_name(info));
528 goto close;
529 }
530
531 if (!captelem || !capcontrols)
532 {
533 /* Can't enable capture, so disabling it
534 * Note: capture control will still exist because
535 * dwLineID 0 and 1 are reserved for Master and Capture
536 */
537 WARN("No use enabling capture part of mixer, capture control found: %s, amount of capture controls: %d\n",
538 (!captelem ? "no" : "yes"), capcontrols);
539 capcontrols = 0;
540 mixdev[mixnum].dests = 1;
541 }
542 else
543 {
544 mixdev[mixnum].chans += capcontrols;
545 mixdev[mixnum].dests = 2;
546 }
547
548 mixdev[mixnum].lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(line) * mixdev[mixnum].chans);
549 mixdev[mixnum].controls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(control) * CONTROLSPERLINE*mixdev[mixnum].chans);
550 err = -ENOMEM;
551 if (!mixdev[mixnum].lines || !mixdev[mixnum].controls)
552 goto close;
553
554 filllines(&mixdev[mixnum], mastelem, captelem, capcontrols);
555 fillcontrols(&mixdev[mixnum]);
556
557 TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname, mixdev[mixnum].dests, mixdev[mixnum].chans, debugstr_w(mixdev[mixnum].mixername));
558 mixnum++;
559 continue;
560
561 eclose:
562 WARN("Error occurred initialising mixer: %s\n", snd_strerror(err));
563 close:
564 HeapFree(GetProcessHeap(), 0, mixdev[mixnum].lines);
565 HeapFree(GetProcessHeap(), 0, mixdev[mixnum].controls);
566 snd_mixer_close(mixdev[mixnum].mix);
567 }
568 cards = mixnum;
569 HeapFree( GetProcessHeap(), 0, info );
570
571 /* There is no trouble with already assigning callbacks without initialising critsect:
572 * Callbacks only occur when snd_mixer_handle_events is called (only happens in thread)
573 */
574 InitializeCriticalSection(&elem_crst);
575 elem_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_MIXER.elem_crst");
576 TRACE("\n");
577 }
578
579 static void ALSA_MixerExit(void)
580 {
581 int x;
582
583 if (refcnt)
584 {
585 WARN("Callback thread still alive, terminating uncleanly, refcnt: %d\n", refcnt);
586 /* Least we can do is making sure we're not in 'foreign' code */
587 EnterCriticalSection(&elem_crst);
588 TerminateThread(thread, 1);
589 refcnt = 0;
590 LeaveCriticalSection(&elem_crst);
591 }
592
593 TRACE("Cleaning up\n");
594
595 elem_crst.DebugInfo->Spare[0] = 0;
596 DeleteCriticalSection(&elem_crst);
597 for (x = 0; x < cards; ++x)
598 {
599 snd_mixer_close(mixdev[x].mix);
600 HeapFree(GetProcessHeap(), 0, mixdev[x].lines);
601 HeapFree(GetProcessHeap(), 0, mixdev[x].controls);
602 }
603 cards = 0;
604 }
605
606 static mixer* MIX_GetMix(UINT wDevID)
607 {
608 mixer *mmixer;
609
610 if (wDevID >= cards)
611 {
612 WARN("Invalid mixer id: %d\n", wDevID);
613 return NULL;
614 }
615
616 mmixer = &mixdev[wDevID];
617 return mmixer;
618 }
619
620 /* Since alsa doesn't tell what exactly changed, just assume all affected controls changed */
621 static int elem_callback(snd_mixer_elem_t *elem, unsigned int type)
622 {
623 mixer *mmixer = snd_mixer_elem_get_callback_private(elem);
624 int x;
625 BOOL captchanged = 0;
626
627 if (type != SND_CTL_EVENT_MASK_VALUE)
628 return 0;
629
630 assert(mmixer);
631
632 EnterCriticalSection(&elem_crst);
633
634 if (!mmixer->callback)
635 goto out;
636
637 for (x=0; x<mmixer->chans; ++x)
638 {
639 const int ofs = CONTROLSPERLINE*x;
640 if (elem != mmixer->lines[x].elem)
641 continue;
642
643 if (mmixer->lines[x].capt)
644 ++captchanged;
645
646 TRACE("Found changed control %s\n", debugstr_w(mmixer->lines[x].name));
647 mmixer->callback(mmixer->hmx, MM_MIXM_LINE_CHANGE, mmixer->callbackpriv, x, 0);
648 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs, 0);
649
650 if (mmixer->controls[ofs+OFS_MUTE].enabled)
651 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs+OFS_MUTE, 0);
652 }
653 if (captchanged)
654 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, CONTROLSPERLINE+OFS_MUX, 0);
655
656 out:
657 LeaveCriticalSection(&elem_crst);
658
659 return 0;
660 }
661
662 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam)
663 {
664 struct pollfd *pfds = NULL;
665 int x, y, err, mcnt, count = 1;
666
667 TRACE("%p\n", lParam);
668
669 for (x = 0; x < cards; ++x)
670 count += snd_mixer_poll_descriptors_count(mixdev[x].mix);
671
672 TRACE("Counted %d descriptors\n", count);
673 pfds = HeapAlloc(GetProcessHeap(), 0, count * sizeof(struct pollfd));
674
675 if (!pfds)
676 {
677 WARN("Out of memory\n");
678 goto die;
679 }
680
681 pfds[0].fd = msg_pipe[0];
682 pfds[0].events = POLLIN;
683
684 y = 1;
685 for (x = 0; x < cards; ++x)
686 y += snd_mixer_poll_descriptors(mixdev[x].mix, &pfds[y], count - y);
687
688 while ((err = poll(pfds, (unsigned int) count, -1)) >= 0 || errno == EINTR || errno == EAGAIN)
689 {
690 if (pfds[0].revents & POLLIN)
691 break;
692
693 mcnt = 1;
694 for (x = y = 0; x < cards; ++x)
695 {
696 int j, max = snd_mixer_poll_descriptors_count(mixdev[x].mix);
697 for (j = 0; j < max; ++j)
698 if (pfds[mcnt+j].revents)
699 {
700 y += snd_mixer_handle_events(mixdev[x].mix);
701 break;
702 }
703 mcnt += max;
704 }
705 if (y)
706 TRACE("Handled %d events\n", y);
707 }
708
709 die:
710 TRACE("Shutting down\n");
711 HeapFree(GetProcessHeap(), 0, pfds);
712
713 y = read(msg_pipe[0], &x, sizeof(x));
714 close(msg_pipe[1]);
715 close(msg_pipe[0]);
716 return 0;
717 }
718
719 static DWORD MIX_Open(UINT wDevID, LPMIXEROPENDESC desc, DWORD_PTR flags)
720 {
721 mixer *mmixer = MIX_GetMix(wDevID);
722 if (!mmixer)
723 return MMSYSERR_BADDEVICEID;
724
725 flags &= CALLBACK_TYPEMASK;
726 switch (flags)
727 {
728 case CALLBACK_NULL:
729 goto done;
730
731 case CALLBACK_FUNCTION:
732 break;
733
734 default:
735 FIXME("Unhandled callback type: %08lx\n", flags & CALLBACK_TYPEMASK);
736 return MIXERR_INVALVALUE;
737 }
738
739 mmixer->callback = (LPDRVCALLBACK)desc->dwCallback;
740 mmixer->callbackpriv = desc->dwInstance;
741 mmixer->hmx = (HDRVR)desc->hmx;
742
743 done:
744 if (InterlockedIncrement(&refcnt) == 1)
745 {
746 if (pipe(msg_pipe) >= 0)
747 {
748 thread = CreateThread(NULL, 0, ALSA_MixerPollThread, NULL, 0, NULL);
749 if (!thread)
750 {
751 close(msg_pipe[0]);
752 close(msg_pipe[1]);
753 msg_pipe[0] = msg_pipe[1] = -1;
754 }
755 }
756 else
757 msg_pipe[0] = msg_pipe[1] = -1;
758 }
759
760 return MMSYSERR_NOERROR;
761 }
762
763 static DWORD MIX_Close(UINT wDevID)
764 {
765 int x = 0;
766 mixer *mmixer = MIX_GetMix(wDevID);
767 if (!mmixer)
768 return MMSYSERR_BADDEVICEID;
769
770 EnterCriticalSection(&elem_crst);
771 mmixer->callback = 0;
772 LeaveCriticalSection(&elem_crst);
773
774 if (!InterlockedDecrement(&refcnt))
775 {
776 if (write(msg_pipe[1], &x, sizeof(x)) > 0)
777 {
778 TRACE("Shutting down thread...\n");
779 WaitForSingleObject(thread, INFINITE);
780 TRACE("Done\n");
781 }
782 }
783
784 return MMSYSERR_NOERROR;
785 }
786
787 static DWORD MIX_GetDevCaps(UINT wDevID, LPMIXERCAPS2W caps, DWORD_PTR parm2)
788 {
789 mixer *mmixer = MIX_GetMix(wDevID);
790 MIXERCAPS2W capsW;
791
792 if (!caps)
793 return MMSYSERR_INVALPARAM;
794
795 if (!mmixer)
796 return MMSYSERR_BADDEVICEID;
797
798 memset(&capsW, 0, sizeof(MIXERCAPS2W));
799
800 capsW.wMid = WINE_MIXER_MANUF_ID;
801 capsW.wPid = WINE_MIXER_PRODUCT_ID;
802 capsW.vDriverVersion = WINE_MIXER_VERSION;
803
804 lstrcpynW(capsW.szPname, mmixer->mixername, sizeof(capsW.szPname)/sizeof(WCHAR));
805 capsW.cDestinations = mmixer->dests;
806 memcpy(caps, &capsW, min(parm2, sizeof(capsW)));
807 return MMSYSERR_NOERROR;
808 }
809
810 /* convert win32 volume to alsa volume, and vice versa */
811 static INT normalized(INT value, INT prevmax, INT nextmax)
812 {
813 int ret = MulDiv(value, nextmax, prevmax);
814
815 /* Have to stay in range */
816 TRACE("%d/%d -> %d/%d\n", value, prevmax, ret, nextmax);
817 if (ret > nextmax)
818 ret = nextmax;
819 else if (ret < 0)
820 ret = 0;
821
822 return ret;
823 }
824
825 /* get amount of sources for dest */
826 static int getsrccntfromchan(mixer *mmixer, int dad)
827 {
828 int i, j=0;
829
830 for (i=0; i<mmixer->chans; ++i)
831 if (i != dad && mmixer->lines[i].dst == dad)
832 {
833 ++j;
834 }
835 if (!j)
836 FIXME("No src found for %i (%s)?\n", dad, debugstr_w(mmixer->lines[dad].name));
837 return j;
838 }
839
840 /* find lineid for source 'num' with dest 'dad' */
841 static int getsrclinefromchan(mixer *mmixer, int dad, int num)
842 {
843 int i, j=0;
844 for (i=0; i<mmixer->chans; ++i)
845 if (i != dad && mmixer->lines[i].dst == dad)
846 {
847 if (num == j)
848 return i;
849 ++j;
850 }
851 WARN("No src found for src %i from dest %i\n", num, dad);
852 return 0;
853 }
854
855 /* get the source number belonging to line */
856 static int getsrcfromline(mixer *mmixer, int line)
857 {
858 int i, j=0, dad = mmixer->lines[line].dst;
859
860 for (i=0; i<mmixer->chans; ++i)
861 if (i != dad && mmixer->lines[i].dst == dad)
862 {
863 if (line == i)
864 return j;
865 ++j;
866 }
867 WARN("No src found for line %i with dad %i\n", line, dad);
868 return 0;
869 }
870
871 /* Get volume/muted/capture channel */
872 static DWORD MIX_GetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
873 {
874 mixer *mmixer = MIX_GetMix(wDevID);
875 DWORD ctrl;
876 DWORD line;
877 control *ct;
878
879 if (!mctrld)
880 return MMSYSERR_INVALPARAM;
881
882 ctrl = mctrld->dwControlID;
883 line = ctrl/CONTROLSPERLINE;
884
885 if (mctrld->cbStruct != sizeof(*mctrld))
886 return MMSYSERR_INVALPARAM;
887
888 if (!mmixer)
889 return MMSYSERR_BADDEVICEID;
890
891 if (line >= mmixer->chans || !mmixer->controls[ctrl].enabled)
892 return MIXERR_INVALCONTROL;
893
894 ct = &mmixer->controls[ctrl];
895
896 flags &= MIXER_GETCONTROLDETAILSF_QUERYMASK;
897
898 switch (flags) {
899 case MIXER_GETCONTROLDETAILSF_VALUE:
900 TRACE("MIXER_GETCONTROLDETAILSF_VALUE (%d/%d)\n", ctrl, line);
901 switch (ct->c.dwControlType)
902 {
903 case MIXERCONTROL_CONTROLTYPE_VOLUME:
904 {
905 long min = 0, max = 0, vol = 0;
906 int chn;
907 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
908 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
909
910 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
911 {
912 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
913 return MMSYSERR_INVALPARAM;
914 }
915
916 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
917
918 mcdu = mctrld->paDetails;
919
920 if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
921 {
922 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
923 return MMSYSERR_INVALPARAM;
924 }
925
926 if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem)) {
927 snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
928 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
929 if (snd_mixer_selem_has_capture_channel(elem, chn))
930 {
931 snd_mixer_selem_get_capture_volume(elem, chn, &vol);
932 mcdu->dwValue = normalized(vol - min, max, 65535);
933 if (mctrld->cChannels == 1)
934 break;
935 ++mcdu;
936 }
937 } else {
938 snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
939
940 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
941 if (snd_mixer_selem_has_playback_channel(elem, chn))
942 {
943 snd_mixer_selem_get_playback_volume(elem, chn, &vol);
944 mcdu->dwValue = normalized(vol - min, max, 65535);
945 if (mctrld->cChannels == 1)
946 break;
947 ++mcdu;
948 }
949 }
950
951 return MMSYSERR_NOERROR;
952 }
953
954 case MIXERCONTROL_CONTROLTYPE_ONOFF:
955 case MIXERCONTROL_CONTROLTYPE_MUTE:
956 {
957 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
958 int chn, ival;
959 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
960
961 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
962 {
963 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
964 return MMSYSERR_INVALPARAM;
965 }
966
967 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
968
969 mcdb = mctrld->paDetails;
970
971 if (line == 1)
972 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
973 {
974 if (!snd_mixer_selem_has_capture_channel(elem, chn))
975 continue;
976 snd_mixer_selem_get_capture_switch(elem, chn, &ival);
977 break;
978 }
979 else
980 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
981 {
982 if (!snd_mixer_selem_has_playback_channel(elem, chn))
983 continue;
984 snd_mixer_selem_get_playback_switch(elem, chn, &ival);
985 break;
986 }
987
988 if (chn > SND_MIXER_SCHN_LAST)
989 {
990 TRACE("can't find active channel\n");
991 return MMSYSERR_INVALPARAM; /* fixme: what's right error? */
992 }
993
994 mcdb->fValue = !ival;
995 TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
996 return MMSYSERR_NOERROR;
997 }
998 case MIXERCONTROL_CONTROLTYPE_MIXER:
999 case MIXERCONTROL_CONTROLTYPE_MUX:
1000 {
1001 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1002 int x, i=0, ival = 0, chn;
1003
1004 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1005 {
1006 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1007 return MMSYSERR_INVALPARAM;
1008 }
1009
1010 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1011
1012 mcdb = mctrld->paDetails;
1013
1014 for (x = 0; x<mmixer->chans; ++x)
1015 if (line != x && mmixer->lines[x].dst == line)
1016 {
1017 ival = 0;
1018 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1019 {
1020 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1021 continue;
1022 snd_mixer_selem_get_capture_switch(mmixer->lines[x].elem, chn, &ival);
1023 if (ival)
1024 break;
1025 }
1026 if (i >= mctrld->u.cMultipleItems)
1027 {
1028 TRACE("overflow\n");
1029 return MMSYSERR_INVALPARAM;
1030 }
1031 TRACE("fVal[%i] = %sselected\n", i, (!ival ? "un" : ""));
1032 mcdb[i++].fValue = ival;
1033 }
1034 break;
1035 }
1036 default:
1037
1038 FIXME("Unhandled controltype %s\n", getControlType(ct->c.dwControlType));
1039 return MMSYSERR_INVALPARAM;
1040 }
1041 return MMSYSERR_NOERROR;
1042
1043 case MIXER_GETCONTROLDETAILSF_LISTTEXT:
1044 TRACE("MIXER_GETCONTROLDETAILSF_LISTTEXT (%d)\n", ctrl);
1045
1046 if (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX || ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER)
1047 {
1048 LPMIXERCONTROLDETAILS_LISTTEXTW mcdlt = mctrld->paDetails;
1049 int i, j;
1050
1051 for (i = j = 0; j < mmixer->chans; ++j)
1052 if (j != line && mmixer->lines[j].dst == line)
1053 {
1054 if (i > mctrld->u.cMultipleItems)
1055 return MMSYSERR_INVALPARAM;
1056 mcdlt->dwParam1 = j;
1057 mcdlt->dwParam2 = mmixer->lines[j].component;
1058 lstrcpynW(mcdlt->szName, mmixer->lines[j].name, sizeof(mcdlt->szName) / sizeof(WCHAR));
1059 TRACE("Adding %i as %s\n", j, debugstr_w(mcdlt->szName));
1060 ++i; ++mcdlt;
1061 }
1062 if (i < mctrld->u.cMultipleItems)
1063 return MMSYSERR_INVALPARAM;
1064 return MMSYSERR_NOERROR;
1065 }
1066 FIXME ("Imagine this code being horribly broken and incomplete, introducing: reality\n");
1067 return MMSYSERR_INVALPARAM;
1068
1069 default:
1070 WARN("Unknown flag (%08lx)\n", flags);
1071 return MMSYSERR_INVALPARAM;
1072 }
1073 }
1074
1075 /* Set volume/capture channel/muted for control */
1076 static DWORD MIX_SetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
1077 {
1078 mixer *mmixer = MIX_GetMix(wDevID);
1079 DWORD ctrl, line, i;
1080 control *ct;
1081 snd_mixer_elem_t * elem;
1082
1083 if (!mctrld)
1084 return MMSYSERR_INVALPARAM;
1085
1086 ctrl = mctrld->dwControlID;
1087 line = ctrl/CONTROLSPERLINE;
1088
1089 if (mctrld->cbStruct != sizeof(*mctrld))
1090 {
1091 WARN("Invalid size of mctrld %d\n", mctrld->cbStruct);
1092 return MMSYSERR_INVALPARAM;
1093 }
1094
1095 if (!mmixer)
1096 return MMSYSERR_BADDEVICEID;
1097
1098 if (line >= mmixer->chans)
1099 {
1100 WARN("Invalid line id: %d not in range of 0-%d\n", line, mmixer->chans-1);
1101 return MMSYSERR_INVALPARAM;
1102 }
1103
1104 if (!mmixer->controls[ctrl].enabled)
1105 {
1106 WARN("Control %d not enabled\n", ctrl);
1107 return MIXERR_INVALCONTROL;
1108 }
1109
1110 ct = &mmixer->controls[ctrl];
1111 elem = mmixer->lines[line].elem;
1112 flags &= MIXER_SETCONTROLDETAILSF_QUERYMASK;
1113
1114 switch (flags) {
1115 case MIXER_SETCONTROLDETAILSF_VALUE:
1116 TRACE("MIXER_SETCONTROLDETAILSF_VALUE (%d)\n", ctrl);
1117 break;
1118
1119 default:
1120 WARN("Unknown flag (%08lx)\n", flags);
1121 return MMSYSERR_INVALPARAM;
1122 }
1123
1124 switch (ct->c.dwControlType)
1125 {
1126 case MIXERCONTROL_CONTROLTYPE_VOLUME:
1127 {
1128 long min = 0, max = 0;
1129 int chn;
1130 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
1131 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
1132
1133 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
1134 {
1135 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1136 return MMSYSERR_INVALPARAM;
1137 }
1138
1139 if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
1140 {
1141 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
1142 return MMSYSERR_INVALPARAM;
1143 }
1144
1145 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1146 mcdu = mctrld->paDetails;
1147
1148 for (chn=0; chn<mctrld->cChannels;++chn)
1149 {
1150 TRACE("Chan %d value %d\n", chn, mcdu[chn].dwValue);
1151 }
1152
1153 /* There isn't always a capture volume, so in that case change playback volume */
1154 if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem))
1155 {
1156 snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
1157
1158 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1159 if (snd_mixer_selem_has_capture_channel(elem, chn))
1160 {
1161 snd_mixer_selem_set_capture_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
1162 if (mctrld->cChannels != 1)
1163 mcdu++;
1164 }
1165 }
1166 else
1167 {
1168 snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
1169
1170 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1171 if (snd_mixer_selem_has_playback_channel(elem, chn))
1172 {
1173 snd_mixer_selem_set_playback_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
1174 if (mctrld->cChannels != 1)
1175 mcdu++;
1176 }
1177 }
1178
1179 break;
1180 }
1181 case MIXERCONTROL_CONTROLTYPE_MUTE:
1182 case MIXERCONTROL_CONTROLTYPE_ONOFF:
1183 {
1184 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1185
1186 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1187 {
1188 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1189 return MMSYSERR_INVALPARAM;
1190 }
1191
1192 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1193
1194 mcdb = mctrld->paDetails;
1195 if (line == 1) /* Mute/unmute capturing */
1196 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
1197 {
1198 if (snd_mixer_selem_has_capture_channel(elem, i))
1199 snd_mixer_selem_set_capture_switch(elem, i, !mcdb->fValue);
1200 }
1201 else
1202 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
1203 if (snd_mixer_selem_has_playback_channel(elem, i))
1204 snd_mixer_selem_set_playback_switch(elem, i, !mcdb->fValue);
1205 break;
1206 }
1207
1208 case MIXERCONTROL_CONTROLTYPE_MIXER:
1209 case MIXERCONTROL_CONTROLTYPE_MUX:
1210 {
1211 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1212 int x, i=0, chn;
1213 int didone = 0, canone = (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX);
1214
1215 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1216 {
1217 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1218 return MMSYSERR_INVALPARAM;
1219 }
1220
1221 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1222 mcdb = mctrld->paDetails;
1223
1224 for (x=i=0; x < mmixer->chans; ++x)
1225 if (line != x && mmixer->lines[x].dst == line)
1226 {
1227 TRACE("fVal[%i] (%s) = %i\n", i, debugstr_w(mmixer->lines[x].name), mcdb[i].fValue);
1228 if (i >= mctrld->u.cMultipleItems)
1229 {
1230 TRACE("Too many items to fit, overflowing\n");
1231 return MIXERR_INVALVALUE;
1232 }
1233 if (mcdb[i].fValue && canone && didone)
1234 {
1235 TRACE("Nice try, but it's not going to work\n");
1236 elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
1237 return MIXERR_INVALVALUE;
1238 }
1239 if (mcdb[i].fValue)
1240 didone = 1;
1241 ++i;
1242 }
1243
1244 if (canone && !didone)
1245 {
1246 TRACE("Nice try, this is not going to work either\n");
1247 elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
1248 return MIXERR_INVALVALUE;
1249 }
1250
1251 for (x = i = 0; x<mmixer->chans; ++x)
1252 if (line != x && mmixer->lines[x].dst == line)
1253 {
1254 if (mcdb[i].fValue)
1255 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1256 {
1257 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1258 continue;
1259 snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
1260 }
1261 ++i;
1262 }
1263
1264 /* If it's a MUX, it means that only 1 channel can be selected
1265 * and the other channels are unselected
1266 *
1267 * For MIXER multiple sources are allowed, so unselect here
1268 */
1269 if (canone)
1270 break;
1271
1272 for (x = i = 0; x<mmixer->chans; ++x)
1273 if (line != x && mmixer->lines[x].dst == line)
1274 {
1275 if (!mcdb[i].fValue)
1276 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1277 {
1278 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1279 continue;
1280 snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
1281 }
1282 ++i;
1283 }
1284 break;
1285 }
1286 default:
1287 FIXME("Unhandled type %s\n", getControlType(ct->c.dwControlType));
1288 return MMSYSERR_INVALPARAM;
1289 }
1290 return MMSYSERR_NOERROR;
1291 }
1292
1293 /* Here we give info over the source/dest line given by dwSource+dwDest or dwDest, respectively
1294 * It is also possible that a line is found by componenttype or target type, latter is not implemented yet
1295 * Most important values returned in struct:
1296 * dwLineID
1297 * sz(Short)Name
1298 * line control count
1299 * amount of channels
1300 */
1301 static DWORD MIX_GetLineInfo(UINT wDevID, LPMIXERLINEW Ml, DWORD_PTR flags)
1302 {
1303 DWORD_PTR qf = flags & MIXER_GETLINEINFOF_QUERYMASK;
1304 mixer *mmixer = MIX_GetMix(wDevID);
1305 line *mline;
1306 int idx, i;
1307
1308 if (!Ml)
1309 {
1310 WARN("No Ml\n");
1311 return MMSYSERR_INVALPARAM;
1312 }
1313
1314 if (!mmixer)
1315 {
1316 WARN("Device %u not found\n", wDevID);
1317 return MMSYSERR_BADDEVICEID;
1318 }
1319
1320 if (Ml->cbStruct != sizeof(*Ml))
1321 {
1322 WARN("invalid parameter: Ml->cbStruct = %d\n", Ml->cbStruct);
1323 return MMSYSERR_INVALPARAM;
1324 }
1325
1326 Ml->dwUser = 0;
1327 Ml->fdwLine = MIXERLINE_LINEF_DISCONNECTED;
1328 switch (qf)
1329 {
1330 case MIXER_GETLINEINFOF_COMPONENTTYPE:
1331 {
1332 Ml->dwLineID = 0xFFFF;
1333 TRACE("Looking for componenttype %d/%x\n", Ml->dwComponentType, Ml->dwComponentType);
1334 for (idx = 0; idx < mmixer->chans; ++idx)
1335 if (mmixer->lines[idx].component == Ml->dwComponentType)
1336 {
1337 Ml->dwLineID = idx;
1338 break;
1339 }
1340 if (Ml->dwLineID == 0xFFFF)
1341 return MMSYSERR_KEYNOTFOUND;
1342 /* Now that we have lineid, fallback to lineid*/
1343 }
1344
1345 case MIXER_GETLINEINFOF_LINEID:
1346 if (Ml->dwLineID >= mmixer->chans)
1347 return MIXERR_INVALLINE;
1348
1349 TRACE("MIXER_GETLINEINFOF_LINEID %d\n", Ml->dwLineID);
1350 Ml->dwDestination = mmixer->lines[Ml->dwLineID].dst;
1351
1352 if (Ml->dwDestination != Ml->dwLineID)
1353 {
1354 Ml->dwSource = getsrcfromline(mmixer, Ml->dwLineID);
1355 Ml->cConnections = 1;
1356 }
1357 else
1358 {
1359 Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
1360 Ml->dwSource = 0xFFFFFFFF;
1361 }
1362 TRACE("Connections %d, source %d\n", Ml->cConnections, Ml->dwSource);
1363 break;
1364
1365 case MIXER_GETLINEINFOF_DESTINATION:
1366 if (Ml->dwDestination >= mmixer->dests)
1367 {
1368 WARN("dest %d out of bounds\n", Ml->dwDestination);
1369 return MIXERR_INVALLINE;
1370 }
1371
1372 Ml->dwLineID = Ml->dwDestination;
1373 Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
1374 Ml->dwSource = 0xFFFFFFFF;
1375 break;
1376
1377 case MIXER_GETLINEINFOF_SOURCE:
1378 if (Ml->dwDestination >= mmixer->dests)
1379 {
1380 WARN("dest %d for source out of bounds\n", Ml->dwDestination);
1381 return MIXERR_INVALLINE;
1382 }
1383
1384 if (Ml->dwSource >= getsrccntfromchan(mmixer, Ml->dwDestination))
1385 {
1386 WARN("src %d out of bounds\n", Ml->dwSource);
1387 return MIXERR_INVALLINE;
1388 }
1389
1390 Ml->dwLineID = getsrclinefromchan(mmixer, Ml->dwDestination, Ml->dwSource);
1391 Ml->cConnections = 1;
1392 break;
1393
1394 case MIXER_GETLINEINFOF_TARGETTYPE:
1395 FIXME("TODO: TARGETTYPE, stub\n");
1396 return MMSYSERR_INVALPARAM;
1397
1398 default:
1399 FIXME("Unknown query flag: %08lx\n", qf);
1400 return MMSYSERR_INVALPARAM;
1401 }
1402
1403 Ml->fdwLine &= ~MIXERLINE_LINEF_DISCONNECTED;
1404 Ml->fdwLine |= MIXERLINE_LINEF_ACTIVE;
1405 if (Ml->dwLineID >= mmixer->dests)
1406 Ml->fdwLine |= MIXERLINE_LINEF_SOURCE;
1407
1408 mline = &mmixer->lines[Ml->dwLineID];
1409 Ml->dwComponentType = mline->component;
1410 Ml->cChannels = mmixer->lines[Ml->dwLineID].chans;
1411 Ml->cControls = 0;
1412
1413 for (i=CONTROLSPERLINE*Ml->dwLineID;i<CONTROLSPERLINE*(Ml->dwLineID+1); ++i)
1414 if (mmixer->controls[i].enabled)
1415 ++(Ml->cControls);
1416
1417 lstrcpynW(Ml->szShortName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szShortName)/sizeof(WCHAR));
1418 lstrcpynW(Ml->szName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szName)/sizeof(WCHAR));
1419 if (mline->capt)
1420 Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
1421 else
1422 Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
1423 Ml->Target.dwDeviceID = 0xFFFFFFFF;
1424 Ml->Target.wMid = WINE_MIXER_MANUF_ID;
1425 Ml->Target.wPid = WINE_MIXER_PRODUCT_ID;
1426 Ml->Target.vDriverVersion = WINE_MIXER_VERSION;
1427 lstrcpynW(Ml->Target.szPname, mmixer->mixername, sizeof(Ml->Target.szPname)/sizeof(WCHAR));
1428 return MMSYSERR_NOERROR;
1429 }
1430
1431 /* Get the controls that belong to a certain line, either all or 1 */
1432 static DWORD MIX_GetLineControls(UINT wDevID, LPMIXERLINECONTROLSW mlc, DWORD_PTR flags)
1433 {
1434 mixer *mmixer = MIX_GetMix(wDevID);
1435 int i,j = 0;
1436 DWORD ct;
1437
1438 if (!mlc || mlc->cbStruct != sizeof(*mlc))
1439 {
1440 WARN("Invalid mlc %p, cbStruct: %d\n", mlc, (!mlc ? -1 : mlc->cbStruct));
1441 return MMSYSERR_INVALPARAM;
1442 }
1443
1444 if (mlc->cbmxctrl != sizeof(MIXERCONTROLW))
1445 {
1446 WARN("cbmxctrl %d\n", mlc->cbmxctrl);
1447 return MMSYSERR_INVALPARAM;
1448 }
1449
1450 if (!mmixer)
1451 return MMSYSERR_BADDEVICEID;
1452
1453 flags &= MIXER_GETLINECONTROLSF_QUERYMASK;
1454
1455 if (flags == MIXER_GETLINECONTROLSF_ONEBYID)
1456 mlc->dwLineID = mlc->u.dwControlID / CONTROLSPERLINE;
1457
1458 if (mlc->dwLineID >= mmixer->chans)
1459 {
1460 TRACE("Invalid dwLineID %d\n", mlc->dwLineID);
1461 return MIXERR_INVALLINE;
1462 }
1463
1464 switch (flags)
1465 {
1466 case MIXER_GETLINECONTROLSF_ALL:
1467 TRACE("line=%08x MIXER_GETLINECONTROLSF_ALL (%d)\n", mlc->dwLineID, mlc->cControls);
1468 for (i = 0; i < CONTROLSPERLINE; ++i)
1469 if (mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].enabled)
1470 {
1471 memcpy(&mlc->pamxctrl[j], &mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].c, sizeof(MIXERCONTROLW));
1472 TRACE("Added %s (%s)\n", debugstr_w(mlc->pamxctrl[j].szShortName), debugstr_w(mlc->pamxctrl[j].szName));
1473 ++j;
1474 if (j > mlc->cControls)
1475 {
1476 WARN("invalid parameter\n");
1477 return MMSYSERR_INVALPARAM;
1478 }
1479 }
1480
1481 if (!j || mlc->cControls > j)
1482 {
1483 WARN("invalid parameter\n");
1484 return MMSYSERR_INVALPARAM;
1485 }
1486 break;
1487 case MIXER_GETLINECONTROLSF_ONEBYID:
1488 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYID (%x)\n", mlc->dwLineID, mlc->u.dwControlID);
1489
1490 if (!mmixer->controls[mlc->u.dwControlID].enabled)
1491 return MIXERR_INVALCONTROL;
1492
1493 mlc->pamxctrl[0] = mmixer->controls[mlc->u.dwControlID].c;
1494 break;
1495 case MIXER_GETLINECONTROLSF_ONEBYTYPE:
1496 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", mlc->dwLineID, getControlType(mlc->u.dwControlType));
1497
1498 ct = mlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK;
1499 for (i = 0; i <= CONTROLSPERLINE; ++i)
1500 {
1501 const int ofs = i+mlc->dwLineID*CONTROLSPERLINE;
1502 if (i == CONTROLSPERLINE)
1503 {
1504 WARN("invalid parameter: control %s not found\n", getControlType(mlc->u.dwControlType));
1505 return MIXERR_INVALCONTROL;
1506 }
1507 if (mmixer->controls[ofs].enabled && (mmixer->controls[ofs].c.dwControlType & MIXERCONTROL_CT_CLASS_MASK) == ct)
1508 {
1509 mlc->pamxctrl[0] = mmixer->controls[ofs].c;
1510 break;
1511 }
1512 }
1513 break;
1514 default:
1515 FIXME("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
1516 return MMSYSERR_INVALPARAM;
1517 }
1518
1519 return MMSYSERR_NOERROR;
1520 }
1521
1522 #endif /*HAVE_ALSA*/
1523
1524 /**************************************************************************
1525 * mxdMessage (WINEALSA.3)
1526 */
1527 DWORD WINAPI ALSA_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
1528 DWORD_PTR dwParam1, DWORD_PTR dwParam2)
1529 {
1530 #ifdef HAVE_ALSA
1531 DWORD ret;
1532 TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
1533 dwUser, dwParam1, dwParam2);
1534
1535 switch (wMsg)
1536 {
1537 case DRVM_INIT: ALSA_MixerInit(); ret = MMSYSERR_NOERROR; break;
1538 case DRVM_EXIT: ALSA_MixerExit(); ret = MMSYSERR_NOERROR; break;
1539 /* All taken care of by driver initialisation */
1540 /* Unimplemented, and not needed */
1541 case DRVM_ENABLE:
1542 case DRVM_DISABLE:
1543 ret = MMSYSERR_NOERROR; break;
1544
1545 case MXDM_OPEN:
1546 ret = MIX_Open(wDevID, (LPMIXEROPENDESC) dwParam1, dwParam2); break;
1547
1548 case MXDM_CLOSE:
1549 ret = MIX_Close(wDevID); break;
1550
1551 case MXDM_GETDEVCAPS:
1552 ret = MIX_GetDevCaps(wDevID, (LPMIXERCAPS2W)dwParam1, dwParam2); break;
1553
1554 case MXDM_GETLINEINFO:
1555 ret = MIX_GetLineInfo(wDevID, (LPMIXERLINEW)dwParam1, dwParam2); break;
1556
1557 case MXDM_GETLINECONTROLS:
1558 ret = MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSW)dwParam1, dwParam2); break;
1559
1560 case MXDM_GETCONTROLDETAILS:
1561 ret = MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
1562
1563 case MXDM_SETCONTROLDETAILS:
1564 ret = MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
1565
1566 case MXDM_GETNUMDEVS:
1567 ret = cards; break;
1568
1569 default:
1570 WARN("unknown message %s!\n", getMessage(wMsg));
1571 return MMSYSERR_NOTSUPPORTED;
1572 }
1573
1574 TRACE("Returning %08X\n", ret);
1575 return ret;
1576 #else /*HAVE_ALSA*/
1577 TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
1578
1579 return MMSYSERR_NOTENABLED;
1580 #endif /*HAVE_ALSA*/
1581 }
1582
This page was automatically generated by the
LXR engine.
Visit the LXR main site for more
information.