2 * Alsa MIXER Wine Driver for Linux
3 * Very loosely based on wineoss mixer driver
5 * Copyright 2007 Maarten Lankhorst
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.
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.
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
23 #include "wine/port.h"
35 #ifdef HAVE_SYS_IOCTL_H
36 # include <sys/ioctl.h>
39 #define NONAMELESSUNION
40 #define NONAMELESSSTRUCT
53 #include "wine/unicode.h"
54 #include "wine/debug.h"
56 WINE_DEFAULT_DEBUG_CHANNEL(mixer
);
58 #define WINE_MIXER_MANUF_ID 0xAA
59 #define WINE_MIXER_PRODUCT_ID 0x55
60 #define WINE_MIXER_VERSION 0x0100
63 * In windows it seems to be required for all controls to have a volume switch
64 * In alsa that's optional
66 * I assume for playback controls, that there is always a playback volume switch available
69 * For capture controls, it is needed that there is a capture switch and a volume switch,
70 * It doesn't matter whether it is a playback volume switch or a capture volume switch.
71 * The code will first try to get/adjust capture volume, if that fails it tries playback volume
72 * It is not pretty, but under my 3 test cards it seems that there is no other choice:
73 * Most capture controls don't have a capture volume setting
75 * MUX means that only capture source can be exclusively selected,
76 * MIXER means that multiple sources can be selected simultaneously.
79 static const char * getMessage(UINT uMsg
)
81 #define MSG_TO_STR(x) case x: return #x;
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
);
98 return wine_dbg_sprintf("UNKNOWN(%08x)", uMsg
);
101 static const char * getControlType(DWORD dwControlType
)
103 #define TYPE_TO_STR(x) case x: return #x;
104 switch (dwControlType
) {
105 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM
);
106 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER
);
107 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER
);
108 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER
);
109 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER
);
110 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN
);
111 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF
);
112 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE
);
113 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO
);
114 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS
);
115 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH
);
116 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST
);
117 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON
);
118 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS
);
119 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED
);
120 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED
);
121 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT
);
122 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER
);
123 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN
);
124 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN
);
125 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER
);
126 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME
);
127 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS
);
128 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE
);
129 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER
);
130 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT
);
131 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX
);
132 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT
);
133 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER
);
134 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME
);
135 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME
);
138 return wine_dbg_sprintf("UNKNOWN(%08x)", dwControlType
);
141 /* A simple declaration of a line control
142 * These are each of the channels that show up
144 typedef struct line
{
145 /* Name we present to outside world */
146 WCHAR name
[MAXPNAMELEN
];
152 snd_mixer_elem_t
*elem
;
155 /* A control structure, with toggle enabled switch
156 * Control structures control volume, muted, which capture source
158 typedef struct control
{
167 WCHAR mixername
[MAXPNAMELEN
];
170 LPDRVCALLBACK callback
;
171 DWORD_PTR callbackpriv
;
178 #define MAX_MIXERS 32
179 #define CONTROLSPERLINE 3
183 static int cards
= 0;
184 static mixer mixdev
[MAX_MIXERS
];
185 static HANDLE thread
;
186 static int elem_callback(snd_mixer_elem_t
*elem
, unsigned int mask
);
187 static DWORD WINAPI
ALSA_MixerPollThread(LPVOID lParam
);
188 static CRITICAL_SECTION elem_crst
;
189 static int msg_pipe
[2];
192 /* found channel names in alsa lib, alsa api doesn't have another way for this
193 * map name -> componenttype, worst case we get a wrong componenttype which is
197 static const struct mixerlinetype
{
198 const char *name
; DWORD cmpt
;
200 { "Master", MIXERLINE_COMPONENTTYPE_DST_SPEAKERS
, },
201 { "Capture", MIXERLINE_COMPONENTTYPE_DST_WAVEIN
, },
202 { "PCM", MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT
, },
203 { "PC Speaker", MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER
, },
204 { "Synth", MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER
, },
205 { "Headphone", MIXERLINE_COMPONENTTYPE_DST_HEADPHONES
, },
206 { "Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
, },
207 { "Aux", MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED
, },
208 { "CD", MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC
, },
209 { "Line", MIXERLINE_COMPONENTTYPE_SRC_LINE
, },
210 { "Phone", MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE
, },
211 { "Digital", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
, },
212 { "Front Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
, },
215 /* Map name to MIXERLINE_COMPONENTTYPE_XXX */
216 static int getcomponenttype(const char *name
)
219 for (x
=0; x
< sizeof(converttable
)/sizeof(converttable
[0]); ++x
)
220 if (!strcasecmp(name
, converttable
[x
].name
))
222 TRACE("%d -> %s\n", x
, name
);
223 return converttable
[x
].cmpt
;
225 WARN("Unknown mixer name %s, probably harmless\n", name
);
226 return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED
;
229 /* Is this control suited for showing up? */
230 static int blacklisted(snd_mixer_elem_t
*elem
)
232 const char *name
= snd_mixer_selem_get_name(elem
);
235 if (!snd_mixer_selem_has_playback_volume(elem
) &&
236 !snd_mixer_selem_has_capture_volume(elem
))
239 TRACE("%s: %x\n", name
, blisted
);
243 static void fillcontrols(mixer
*mmixer
)
246 for (id
= 0; id
< mmixer
->chans
; ++id
)
248 line
*mline
= &mmixer
->lines
[id
];
249 int ofs
= CONTROLSPERLINE
* id
;
253 TRACE("Filling control %d\n", id
);
256 if (id
== 1 && !mline
->elem
)
259 if (mline
->capt
&& snd_mixer_selem_has_capture_volume(mline
->elem
))
260 snd_mixer_selem_get_capture_volume_range(mline
->elem
, &min
, &max
);
262 snd_mixer_selem_get_playback_volume_range(mline
->elem
, &min
, &max
);
264 /* (!snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) */
265 /* Volume, always enabled by definition of blacklisted channels */
266 mmixer
->controls
[ofs
].enabled
= 1;
267 mmixer
->controls
[ofs
].c
.cbStruct
= sizeof(mmixer
->controls
[ofs
].c
);
268 mmixer
->controls
[ofs
].c
.dwControlType
= MIXERCONTROL_CONTROLTYPE_VOLUME
;
269 mmixer
->controls
[ofs
].c
.dwControlID
= ofs
;
270 mmixer
->controls
[ofs
].c
.Bounds
.s1
.dwMinimum
= 0;
271 mmixer
->controls
[ofs
].c
.Bounds
.s1
.dwMaximum
= 65535;
272 mmixer
->controls
[ofs
].c
.Metrics
.cSteps
= 65536/(max
-min
);
274 if ((id
== 1 && snd_mixer_selem_has_capture_switch(mline
->elem
)) ||
275 (!mline
->capt
&& snd_mixer_selem_has_playback_switch(mline
->elem
)))
276 { /* MUTE button optional, main capture channel should have one too */
277 mmixer
->controls
[ofs
+OFS_MUTE
].enabled
= 1;
278 mmixer
->controls
[ofs
+OFS_MUTE
].c
.cbStruct
= sizeof(mmixer
->controls
[ofs
].c
);
279 mmixer
->controls
[ofs
+OFS_MUTE
].c
.dwControlType
= MIXERCONTROL_CONTROLTYPE_MUTE
;
280 mmixer
->controls
[ofs
+OFS_MUTE
].c
.dwControlID
= ofs
+OFS_MUTE
;
281 mmixer
->controls
[ofs
+OFS_MUTE
].c
.Bounds
.s1
.dwMaximum
= 1;
284 if (mline
->capt
&& snd_mixer_selem_has_capture_switch_exclusive(mline
->elem
))
285 mmixer
->controls
[CONTROLSPERLINE
+OFS_MUX
].c
.dwControlType
= MIXERCONTROL_CONTROLTYPE_MUX
;
288 { /* Capture select, in case cMultipleItems is 0, it means capture is disabled anyway */
289 mmixer
->controls
[ofs
+OFS_MUX
].enabled
= 1;
290 mmixer
->controls
[ofs
+OFS_MUX
].c
.cbStruct
= sizeof(mmixer
->controls
[ofs
].c
);
291 mmixer
->controls
[ofs
+OFS_MUX
].c
.dwControlType
= MIXERCONTROL_CONTROLTYPE_MIXER
;
292 mmixer
->controls
[ofs
+OFS_MUX
].c
.dwControlID
= ofs
+OFS_MUX
;
293 mmixer
->controls
[ofs
+OFS_MUX
].c
.fdwControl
= MIXERCONTROL_CONTROLF_MULTIPLE
;
295 for (x
= 0; x
<mmixer
->chans
; ++x
)
296 if (x
!= id
&& mmixer
->lines
[x
].dst
== id
)
297 ++(mmixer
->controls
[ofs
+OFS_MUX
].c
.cMultipleItems
);
298 if (!mmixer
->controls
[ofs
+OFS_MUX
].c
.cMultipleItems
)
299 mmixer
->controls
[ofs
+OFS_MUX
].enabled
= 0;
301 mmixer
->controls
[ofs
+OFS_MUX
].c
.Bounds
.s1
.dwMaximum
= mmixer
->controls
[ofs
+OFS_MUX
].c
.cMultipleItems
- 1;
302 mmixer
->controls
[ofs
+OFS_MUX
].c
.Metrics
.cSteps
= mmixer
->controls
[ofs
+OFS_MUX
].c
.cMultipleItems
;
304 for (x
=0; x
<CONTROLSPERLINE
; ++x
)
306 lstrcpynW(mmixer
->controls
[ofs
+x
].c
.szShortName
, mline
->name
, sizeof(mmixer
->controls
[ofs
+x
].c
.szShortName
)/sizeof(WCHAR
));
307 lstrcpynW(mmixer
->controls
[ofs
+x
].c
.szName
, mline
->name
, sizeof(mmixer
->controls
[ofs
+x
].c
.szName
)/sizeof(WCHAR
));
312 /* get amount of channels for elem */
313 /* Officially we should keep capture/playback separated,
314 * but that's not going to work in the alsa api */
315 static int chans(mixer
*mmixer
, snd_mixer_elem_t
* elem
, DWORD capt
)
319 if (capt
&& snd_mixer_selem_has_capture_volume(elem
)) {
320 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
321 if (snd_mixer_selem_has_capture_channel(elem
, chn
))
324 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
325 if (snd_mixer_selem_has_playback_channel(elem
, chn
))
329 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"));
333 static void filllines(mixer
*mmixer
, snd_mixer_elem_t
*mastelem
, snd_mixer_elem_t
*captelem
, int capt
)
335 snd_mixer_elem_t
*elem
;
336 line
*mline
= mmixer
->lines
;
340 MultiByteToWideChar(CP_UNIXCP
, 0, snd_mixer_selem_get_name(mastelem
), -1, mline
->name
, sizeof(mline
->name
)/sizeof(WCHAR
));
341 mline
->component
= getcomponenttype(snd_mixer_selem_get_name(mastelem
));
344 mline
->elem
= mastelem
;
345 mline
->chans
= chans(mmixer
, mastelem
, 0);
347 snd_mixer_elem_set_callback(mastelem
, &elem_callback
);
348 snd_mixer_elem_set_callback_private(mastelem
, mmixer
);
350 MultiByteToWideChar(CP_UNIXCP
, 0, "Empty Master Element", -1, mline
->name
, sizeof(mline
->name
)/sizeof(WCHAR
));
354 * Note: since mmixer->dests = 1, it means only playback control is visible
355 * This makes sense, because if there are no capture sources capture control
356 * can't do anything and should be invisible */
358 /* Control 1 is reserved for capture even when not enabled */
362 MultiByteToWideChar(CP_UNIXCP
, 0, snd_mixer_selem_get_name(captelem
), -1, mline
->name
, sizeof(mline
->name
)/sizeof(WCHAR
));
363 mline
->component
= getcomponenttype(snd_mixer_selem_get_name(captelem
));
366 mline
->elem
= captelem
;
367 mline
->chans
= chans(mmixer
, captelem
, 1);
369 snd_mixer_elem_set_callback(captelem
, &elem_callback
);
370 snd_mixer_elem_set_callback_private(captelem
, mmixer
);
373 for (elem
= snd_mixer_first_elem(mmixer
->mix
); elem
; elem
= snd_mixer_elem_next(elem
))
374 if (elem
!= mastelem
&& elem
!= captelem
&& !blacklisted(elem
))
376 const char * name
= snd_mixer_selem_get_name(elem
);
377 DWORD comp
= getcomponenttype(name
);
379 if (snd_mixer_selem_has_playback_volume(elem
) &&
380 (snd_mixer_selem_has_capture_volume(elem
) || comp
!= MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
))
382 (++mline
)->component
= comp
;
383 MultiByteToWideChar(CP_UNIXCP
, 0, name
, -1, mline
->name
, MAXPNAMELEN
);
384 mline
->capt
= mline
->dst
= 0;
386 mline
->chans
= chans(mmixer
, elem
, 0);
391 if (capt
&& (snd_mixer_selem_has_capture_volume(elem
) || comp
== MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
))
393 (++mline
)->component
= comp
;
394 MultiByteToWideChar(CP_UNIXCP
, 0, name
, -1, mline
->name
, MAXPNAMELEN
);
395 mline
->capt
= mline
->dst
= 1;
397 mline
->chans
= chans(mmixer
, elem
, 1);
400 snd_mixer_elem_set_callback(elem
, &elem_callback
);
401 snd_mixer_elem_set_callback_private(elem
, mmixer
);
405 static void filllines_no_master(mixer
*mmixer
, snd_mixer_elem_t
*captelem
, int capt
)
407 line
*mline
= mmixer
->lines
;
409 MultiByteToWideChar(CP_UNIXCP
, 0, snd_mixer_selem_get_name(captelem
), -1, mline
->name
, sizeof(mline
->name
)/sizeof(WCHAR
));
410 mline
->component
= getcomponenttype(snd_mixer_selem_get_name(captelem
));
413 mline
->elem
= captelem
;
414 mline
->chans
= chans(mmixer
, captelem
, 1);
416 snd_mixer_elem_set_callback(captelem
, &elem_callback
);
417 snd_mixer_elem_set_callback_private(captelem
, mmixer
);
420 /* Windows api wants to have a 'master' device to which all slaves are attached
421 * There are 2 ones in this code:
422 * - 'Master', fall back to 'Headphone' if unavailable, and if that's not available 'PCM'
424 * Capture might not always be available, so should be prepared to be without if needed
427 static void ALSA_MixerInit(void)
430 snd_ctl_card_info_t
*info
;
432 info
= HeapAlloc( GetProcessHeap(), 0, snd_ctl_card_info_sizeof());
433 for (x
= 0; x
< MAX_MIXERS
; ++x
)
435 int card
, err
, capcontrols
= 0, total_elems
= 0;
436 char cardind
[6], cardname
[10];
439 snd_mixer_elem_t
*elem
, *mastelem
= NULL
, *headelem
= NULL
, *captelem
= NULL
, *pcmelem
= NULL
, *lineelem
= NULL
, *micelem
= NULL
;
441 memset(info
, 0, snd_ctl_card_info_sizeof());
442 memset(&mixdev
[mixnum
], 0, sizeof(*mixdev
));
443 snprintf(cardind
, sizeof(cardind
), "%d", x
);
444 card
= snd_card_get_index(cardind
);
448 snprintf(cardname
, sizeof(cardname
), "hw:%d", card
);
450 err
= snd_ctl_open(&ctl
, cardname
, 0);
453 WARN("Cannot open card: %s\n", snd_strerror(err
));
457 err
= snd_ctl_card_info(ctl
, info
);
460 WARN("Cannot get card info: %s\n", snd_strerror(err
));
465 MultiByteToWideChar(CP_UNIXCP
, 0, snd_ctl_card_info_get_name(info
), -1, mixdev
[mixnum
].mixername
, sizeof(mixdev
[mixnum
].mixername
)/sizeof(WCHAR
));
468 err
= snd_mixer_open(&mixdev
[mixnum
].mix
, 0);
471 WARN("Error occurred opening mixer: %s\n", snd_strerror(err
));
475 err
= snd_mixer_attach(mixdev
[mixnum
].mix
, cardname
);
479 err
= snd_mixer_selem_register(mixdev
[mixnum
].mix
, NULL
, NULL
);
483 err
= snd_mixer_load(mixdev
[mixnum
].mix
);
487 /* First, lets see what's available..
488 * If there are multiple Master or Captures, all except 1 will be added as slaves
490 total_elems
= snd_mixer_get_count(mixdev
[mixnum
].mix
);
491 TRACE("Total elems: %d\n", total_elems
);
493 for (elem
= snd_mixer_first_elem(mixdev
[mixnum
].mix
); elem
; elem
= snd_mixer_elem_next(elem
))
494 if (!strcasecmp(snd_mixer_selem_get_name(elem
), "Master") && !mastelem
)
497 ++(mixdev
[mixnum
].chans
);
499 else if (!strcasecmp(snd_mixer_selem_get_name(elem
), "Capture") && !captelem
)
501 else if (!strcasecmp(snd_mixer_selem_get_name(elem
), "Mic") && !micelem
&& !mastelem
&& total_elems
== 1)
502 /* this is what snd-usb-audio mics look like; just a Mic control and that's it.*/
504 else if (!blacklisted(elem
))
506 DWORD comp
= getcomponenttype(snd_mixer_selem_get_name(elem
));
509 /* Work around buggy drivers: Make this a capture control if the name is recognised as a microphone */
510 if (snd_mixer_selem_has_capture_volume(elem
))
512 else if (comp
== MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
)
518 if (!skip
&& snd_mixer_selem_has_playback_volume(elem
))
520 if (!strcasecmp(snd_mixer_selem_get_name(elem
), "Headphone") && !headelem
)
522 else if (!strcasecmp(snd_mixer_selem_get_name(elem
), "PCM") && !pcmelem
)
524 else if (!strcasecmp(snd_mixer_selem_get_name(elem
), "Line") && !lineelem
)
526 ++(mixdev
[mixnum
].chans
);
530 /* Add dummy capture channel, wanted by Windows */
531 mixdev
[mixnum
].chans
+= 1;
533 /* If there is only 'Capture' and 'Master', this device is not worth it */
534 if (mixdev
[mixnum
].chans
== 2)
536 WARN("No channels found, skipping device!\n");
540 /* Master element can't have a capture control in this code, so
541 * if Headphone or PCM is promoted to master, unset its capture control */
542 if (headelem
&& !mastelem
)
544 /* Using 'Headphone' as master device */
546 capcontrols
-= !!snd_mixer_selem_has_capture_switch(mastelem
);
548 else if (pcmelem
&& !mastelem
)
550 /* Use 'PCM' as master device */
552 capcontrols
-= !!snd_mixer_selem_has_capture_switch(mastelem
);
554 else if (lineelem
&& !mastelem
)
556 /* Use 'Line' as master device */
558 capcontrols
-= !!snd_mixer_selem_has_capture_switch(mastelem
);
560 else if (!mastelem
&& !captelem
&& !micelem
)
562 /* If there is nothing sensible that can act as 'Master' control, something is wrong */
563 FIXME("No master control found on %s, disabling mixer\n", snd_ctl_card_info_get_name(info
));
567 if (!captelem
|| !capcontrols
)
569 /* Can't enable capture, so disabling it
570 * Note: capture control will still exist because
571 * dwLineID 0 and 1 are reserved for Master and Capture
573 WARN("No use enabling capture part of mixer, capture control found: %s, amount of capture controls: %d\n",
574 (!captelem
? "no" : "yes"), capcontrols
);
576 mixdev
[mixnum
].dests
= 1;
580 mixdev
[mixnum
].chans
+= capcontrols
;
581 mixdev
[mixnum
].dests
= 2;
584 mixdev
[mixnum
].lines
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(line
) * mixdev
[mixnum
].chans
);
585 mixdev
[mixnum
].controls
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(control
) * CONTROLSPERLINE
*mixdev
[mixnum
].chans
);
587 if (!mixdev
[mixnum
].lines
|| !mixdev
[mixnum
].controls
)
591 filllines(&mixdev
[mixnum
], mastelem
, captelem
, capcontrols
);
593 filllines_no_master(&mixdev
[mixnum
], micelem
, 1);
594 fillcontrols(&mixdev
[mixnum
]);
596 TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname
, mixdev
[mixnum
].dests
, mixdev
[mixnum
].chans
, debugstr_w(mixdev
[mixnum
].mixername
));
601 WARN("Error occurred initialising mixer: %s\n", snd_strerror(err
));
603 HeapFree(GetProcessHeap(), 0, mixdev
[mixnum
].lines
);
604 HeapFree(GetProcessHeap(), 0, mixdev
[mixnum
].controls
);
605 snd_mixer_close(mixdev
[mixnum
].mix
);
608 HeapFree( GetProcessHeap(), 0, info
);
610 /* There is no trouble with already assigning callbacks without initialising critsect:
611 * Callbacks only occur when snd_mixer_handle_events is called (only happens in thread)
613 InitializeCriticalSection(&elem_crst
);
614 elem_crst
.DebugInfo
->Spare
[0] = (DWORD_PTR
)(__FILE__
": ALSA_MIXER.elem_crst");
618 static void ALSA_MixerExit(void)
624 WARN("Callback thread still alive, terminating uncleanly, refcnt: %d\n", refcnt
);
625 /* Least we can do is making sure we're not in 'foreign' code */
626 EnterCriticalSection(&elem_crst
);
627 TerminateThread(thread
, 1);
629 LeaveCriticalSection(&elem_crst
);
632 TRACE("Cleaning up\n");
634 elem_crst
.DebugInfo
->Spare
[0] = 0;
635 DeleteCriticalSection(&elem_crst
);
636 for (x
= 0; x
< cards
; ++x
)
638 snd_mixer_close(mixdev
[x
].mix
);
639 HeapFree(GetProcessHeap(), 0, mixdev
[x
].lines
);
640 HeapFree(GetProcessHeap(), 0, mixdev
[x
].controls
);
645 static mixer
* MIX_GetMix(UINT wDevID
)
651 WARN("Invalid mixer id: %d\n", wDevID
);
655 mmixer
= &mixdev
[wDevID
];
659 /* Since alsa doesn't tell what exactly changed, just assume all affected controls changed */
660 static int elem_callback(snd_mixer_elem_t
*elem
, unsigned int type
)
662 mixer
*mmixer
= snd_mixer_elem_get_callback_private(elem
);
664 BOOL captchanged
= 0;
666 if (type
!= SND_CTL_EVENT_MASK_VALUE
)
671 EnterCriticalSection(&elem_crst
);
673 if (!mmixer
->callback
)
676 for (x
=0; x
<mmixer
->chans
; ++x
)
678 const int ofs
= CONTROLSPERLINE
*x
;
679 if (elem
!= mmixer
->lines
[x
].elem
)
682 if (mmixer
->lines
[x
].capt
)
685 TRACE("Found changed control %s\n", debugstr_w(mmixer
->lines
[x
].name
));
686 mmixer
->callback(mmixer
->hmx
, MM_MIXM_LINE_CHANGE
, mmixer
->callbackpriv
, x
, 0);
687 mmixer
->callback(mmixer
->hmx
, MM_MIXM_CONTROL_CHANGE
, mmixer
->callbackpriv
, ofs
, 0);
689 if (mmixer
->controls
[ofs
+OFS_MUTE
].enabled
)
690 mmixer
->callback(mmixer
->hmx
, MM_MIXM_CONTROL_CHANGE
, mmixer
->callbackpriv
, ofs
+OFS_MUTE
, 0);
693 mmixer
->callback(mmixer
->hmx
, MM_MIXM_CONTROL_CHANGE
, mmixer
->callbackpriv
, CONTROLSPERLINE
+OFS_MUX
, 0);
696 LeaveCriticalSection(&elem_crst
);
701 static DWORD WINAPI
ALSA_MixerPollThread(LPVOID lParam
)
703 struct pollfd
*pfds
= NULL
;
704 int x
, y
, err
, mcnt
, count
= 1;
706 TRACE("%p\n", lParam
);
708 for (x
= 0; x
< cards
; ++x
)
709 count
+= snd_mixer_poll_descriptors_count(mixdev
[x
].mix
);
711 TRACE("Counted %d descriptors\n", count
);
712 pfds
= HeapAlloc(GetProcessHeap(), 0, count
* sizeof(struct pollfd
));
716 WARN("Out of memory\n");
720 pfds
[0].fd
= msg_pipe
[0];
721 pfds
[0].events
= POLLIN
;
724 for (x
= 0; x
< cards
; ++x
)
725 y
+= snd_mixer_poll_descriptors(mixdev
[x
].mix
, &pfds
[y
], count
- y
);
727 while ((err
= poll(pfds
, (unsigned int) count
, -1)) >= 0 || errno
== EINTR
|| errno
== EAGAIN
)
729 if (pfds
[0].revents
& POLLIN
)
733 for (x
= y
= 0; x
< cards
; ++x
)
735 int j
, max
= snd_mixer_poll_descriptors_count(mixdev
[x
].mix
);
736 for (j
= 0; j
< max
; ++j
)
737 if (pfds
[mcnt
+j
].revents
)
739 y
+= snd_mixer_handle_events(mixdev
[x
].mix
);
745 TRACE("Handled %d events\n", y
);
749 TRACE("Shutting down\n");
750 HeapFree(GetProcessHeap(), 0, pfds
);
752 y
= read(msg_pipe
[0], &x
, sizeof(x
));
758 static DWORD
MIX_Open(UINT wDevID
, LPMIXEROPENDESC desc
, DWORD_PTR flags
)
760 mixer
*mmixer
= MIX_GetMix(wDevID
);
762 return MMSYSERR_BADDEVICEID
;
764 flags
&= CALLBACK_TYPEMASK
;
770 case CALLBACK_FUNCTION
:
774 FIXME("Unhandled callback type: %08lx\n", flags
& CALLBACK_TYPEMASK
);
775 return MIXERR_INVALVALUE
;
778 mmixer
->callback
= (LPDRVCALLBACK
)desc
->dwCallback
;
779 mmixer
->callbackpriv
= desc
->dwInstance
;
780 mmixer
->hmx
= (HDRVR
)desc
->hmx
;
783 if (InterlockedIncrement(&refcnt
) == 1)
785 if (pipe(msg_pipe
) >= 0)
787 thread
= CreateThread(NULL
, 0, ALSA_MixerPollThread
, NULL
, 0, NULL
);
792 msg_pipe
[0] = msg_pipe
[1] = -1;
796 msg_pipe
[0] = msg_pipe
[1] = -1;
799 return MMSYSERR_NOERROR
;
802 static DWORD
MIX_Close(UINT wDevID
)
805 mixer
*mmixer
= MIX_GetMix(wDevID
);
807 return MMSYSERR_BADDEVICEID
;
809 EnterCriticalSection(&elem_crst
);
810 mmixer
->callback
= 0;
811 LeaveCriticalSection(&elem_crst
);
813 if (!InterlockedDecrement(&refcnt
))
815 if (write(msg_pipe
[1], &x
, sizeof(x
)) > 0)
817 TRACE("Shutting down thread...\n");
818 WaitForSingleObject(thread
, INFINITE
);
823 return MMSYSERR_NOERROR
;
826 static DWORD
MIX_GetDevCaps(UINT wDevID
, LPMIXERCAPS2W caps
, DWORD_PTR parm2
)
828 mixer
*mmixer
= MIX_GetMix(wDevID
);
832 return MMSYSERR_INVALPARAM
;
835 return MMSYSERR_BADDEVICEID
;
837 memset(&capsW
, 0, sizeof(MIXERCAPS2W
));
839 capsW
.wMid
= WINE_MIXER_MANUF_ID
;
840 capsW
.wPid
= WINE_MIXER_PRODUCT_ID
;
841 capsW
.vDriverVersion
= WINE_MIXER_VERSION
;
843 lstrcpynW(capsW
.szPname
, mmixer
->mixername
, sizeof(capsW
.szPname
)/sizeof(WCHAR
));
844 capsW
.cDestinations
= mmixer
->dests
;
845 memcpy(caps
, &capsW
, min(parm2
, sizeof(capsW
)));
846 return MMSYSERR_NOERROR
;
849 /* convert win32 volume to alsa volume, and vice versa */
850 static INT
normalized(INT value
, INT prevmax
, INT nextmax
)
852 int ret
= MulDiv(value
, nextmax
, prevmax
);
854 /* Have to stay in range */
855 TRACE("%d/%d -> %d/%d\n", value
, prevmax
, ret
, nextmax
);
864 /* get amount of sources for dest */
865 static int getsrccntfromchan(mixer
*mmixer
, int dad
)
869 for (i
=0; i
<mmixer
->chans
; ++i
)
870 if (i
!= dad
&& mmixer
->lines
[i
].dst
== dad
)
875 FIXME("No src found for %i (%s)?\n", dad
, debugstr_w(mmixer
->lines
[dad
].name
));
879 /* find lineid for source 'num' with dest 'dad' */
880 static int getsrclinefromchan(mixer
*mmixer
, int dad
, int num
)
883 for (i
=0; i
<mmixer
->chans
; ++i
)
884 if (i
!= dad
&& mmixer
->lines
[i
].dst
== dad
)
890 WARN("No src found for src %i from dest %i\n", num
, dad
);
894 /* get the source number belonging to line */
895 static int getsrcfromline(mixer
*mmixer
, int line
)
897 int i
, j
=0, dad
= mmixer
->lines
[line
].dst
;
899 for (i
=0; i
<mmixer
->chans
; ++i
)
900 if (i
!= dad
&& mmixer
->lines
[i
].dst
== dad
)
906 WARN("No src found for line %i with dad %i\n", line
, dad
);
910 /* Get volume/muted/capture channel */
911 static DWORD
MIX_GetControlDetails(UINT wDevID
, LPMIXERCONTROLDETAILS mctrld
, DWORD_PTR flags
)
913 mixer
*mmixer
= MIX_GetMix(wDevID
);
919 return MMSYSERR_INVALPARAM
;
921 ctrl
= mctrld
->dwControlID
;
922 line
= ctrl
/CONTROLSPERLINE
;
924 if (mctrld
->cbStruct
!= sizeof(*mctrld
))
925 return MMSYSERR_INVALPARAM
;
928 return MMSYSERR_BADDEVICEID
;
930 if (line
>= mmixer
->chans
|| !mmixer
->controls
[ctrl
].enabled
)
931 return MIXERR_INVALCONTROL
;
933 ct
= &mmixer
->controls
[ctrl
];
935 flags
&= MIXER_GETCONTROLDETAILSF_QUERYMASK
;
938 case MIXER_GETCONTROLDETAILSF_VALUE
:
939 TRACE("MIXER_GETCONTROLDETAILSF_VALUE (%d/%d)\n", ctrl
, line
);
940 switch (ct
->c
.dwControlType
)
942 case MIXERCONTROL_CONTROLTYPE_VOLUME
:
944 long min
= 0, max
= 0, vol
= 0;
946 LPMIXERCONTROLDETAILS_UNSIGNED mcdu
;
947 snd_mixer_elem_t
* elem
= mmixer
->lines
[line
].elem
;
949 if (mctrld
->cbDetails
!= sizeof(MIXERCONTROLDETAILS_UNSIGNED
))
951 WARN("invalid parameter: cbDetails %d\n", mctrld
->cbDetails
);
952 return MMSYSERR_INVALPARAM
;
955 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct
->c
.dwControlType
), mctrld
->cChannels
);
957 mcdu
= mctrld
->paDetails
;
959 if (mctrld
->cChannels
!= 1 && mmixer
->lines
[line
].chans
!= mctrld
->cChannels
)
961 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld
->cChannels
, mmixer
->lines
[line
].chans
);
962 return MMSYSERR_INVALPARAM
;
965 if (mmixer
->lines
[line
].capt
&& snd_mixer_selem_has_capture_volume(elem
)) {
966 snd_mixer_selem_get_capture_volume_range(elem
, &min
, &max
);
967 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
968 if (snd_mixer_selem_has_capture_channel(elem
, chn
))
970 snd_mixer_selem_get_capture_volume(elem
, chn
, &vol
);
971 mcdu
->dwValue
= normalized(vol
- min
, max
, 65535);
972 if (mctrld
->cChannels
== 1)
977 snd_mixer_selem_get_playback_volume_range(elem
, &min
, &max
);
979 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
980 if (snd_mixer_selem_has_playback_channel(elem
, chn
))
982 snd_mixer_selem_get_playback_volume(elem
, chn
, &vol
);
983 mcdu
->dwValue
= normalized(vol
- min
, max
, 65535);
984 if (mctrld
->cChannels
== 1)
990 return MMSYSERR_NOERROR
;
993 case MIXERCONTROL_CONTROLTYPE_ONOFF
:
994 case MIXERCONTROL_CONTROLTYPE_MUTE
:
996 LPMIXERCONTROLDETAILS_BOOLEAN mcdb
;
998 snd_mixer_elem_t
* elem
= mmixer
->lines
[line
].elem
;
1000 if (mctrld
->cbDetails
!= sizeof(MIXERCONTROLDETAILS_BOOLEAN
))
1002 WARN("invalid parameter: cbDetails %d\n", mctrld
->cbDetails
);
1003 return MMSYSERR_INVALPARAM
;
1006 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct
->c
.dwControlType
), mctrld
->cChannels
);
1008 mcdb
= mctrld
->paDetails
;
1011 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
1013 if (!snd_mixer_selem_has_capture_channel(elem
, chn
))
1015 snd_mixer_selem_get_capture_switch(elem
, chn
, &ival
);
1019 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
1021 if (!snd_mixer_selem_has_playback_channel(elem
, chn
))
1023 snd_mixer_selem_get_playback_switch(elem
, chn
, &ival
);
1027 if (chn
> SND_MIXER_SCHN_LAST
)
1029 TRACE("can't find active channel\n");
1030 return MMSYSERR_INVALPARAM
; /* fixme: what's right error? */
1033 mcdb
->fValue
= !ival
;
1034 TRACE("=> %s\n", mcdb
->fValue
? "on" : "off");
1035 return MMSYSERR_NOERROR
;
1037 case MIXERCONTROL_CONTROLTYPE_MIXER
:
1038 case MIXERCONTROL_CONTROLTYPE_MUX
:
1040 LPMIXERCONTROLDETAILS_BOOLEAN mcdb
;
1041 int x
, i
=0, ival
= 0, chn
;
1043 if (mctrld
->cbDetails
!= sizeof(MIXERCONTROLDETAILS_BOOLEAN
))
1045 WARN("invalid parameter: cbDetails %d\n", mctrld
->cbDetails
);
1046 return MMSYSERR_INVALPARAM
;
1049 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct
->c
.dwControlType
), mctrld
->cChannels
);
1051 mcdb
= mctrld
->paDetails
;
1053 for (x
= 0; x
<mmixer
->chans
; ++x
)
1054 if (line
!= x
&& mmixer
->lines
[x
].dst
== line
)
1057 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
1059 if (!snd_mixer_selem_has_capture_channel(mmixer
->lines
[x
].elem
, chn
))
1061 snd_mixer_selem_get_capture_switch(mmixer
->lines
[x
].elem
, chn
, &ival
);
1065 if (i
>= mctrld
->u
.cMultipleItems
)
1067 TRACE("overflow\n");
1068 return MMSYSERR_INVALPARAM
;
1070 TRACE("fVal[%i] = %sselected\n", i
, (!ival
? "un" : ""));
1071 mcdb
[i
++].fValue
= ival
;
1077 FIXME("Unhandled controltype %s\n", getControlType(ct
->c
.dwControlType
));
1078 return MMSYSERR_INVALPARAM
;
1080 return MMSYSERR_NOERROR
;
1082 case MIXER_GETCONTROLDETAILSF_LISTTEXT
:
1083 TRACE("MIXER_GETCONTROLDETAILSF_LISTTEXT (%d)\n", ctrl
);
1085 if (ct
->c
.dwControlType
== MIXERCONTROL_CONTROLTYPE_MUX
|| ct
->c
.dwControlType
== MIXERCONTROL_CONTROLTYPE_MIXER
)
1087 LPMIXERCONTROLDETAILS_LISTTEXTW mcdlt
= mctrld
->paDetails
;
1090 for (i
= j
= 0; j
< mmixer
->chans
; ++j
)
1091 if (j
!= line
&& mmixer
->lines
[j
].dst
== line
)
1093 if (i
> mctrld
->u
.cMultipleItems
)
1094 return MMSYSERR_INVALPARAM
;
1095 mcdlt
->dwParam1
= j
;
1096 mcdlt
->dwParam2
= mmixer
->lines
[j
].component
;
1097 lstrcpynW(mcdlt
->szName
, mmixer
->lines
[j
].name
, sizeof(mcdlt
->szName
) / sizeof(WCHAR
));
1098 TRACE("Adding %i as %s\n", j
, debugstr_w(mcdlt
->szName
));
1101 if (i
< mctrld
->u
.cMultipleItems
)
1102 return MMSYSERR_INVALPARAM
;
1103 return MMSYSERR_NOERROR
;
1105 FIXME ("Imagine this code being horribly broken and incomplete, introducing: reality\n");
1106 return MMSYSERR_INVALPARAM
;
1109 WARN("Unknown flag (%08lx)\n", flags
);
1110 return MMSYSERR_INVALPARAM
;
1114 /* Set volume/capture channel/muted for control */
1115 static DWORD
MIX_SetControlDetails(UINT wDevID
, LPMIXERCONTROLDETAILS mctrld
, DWORD_PTR flags
)
1117 mixer
*mmixer
= MIX_GetMix(wDevID
);
1118 DWORD ctrl
, line
, i
;
1120 snd_mixer_elem_t
* elem
;
1123 return MMSYSERR_INVALPARAM
;
1125 ctrl
= mctrld
->dwControlID
;
1126 line
= ctrl
/CONTROLSPERLINE
;
1128 if (mctrld
->cbStruct
!= sizeof(*mctrld
))
1130 WARN("Invalid size of mctrld %d\n", mctrld
->cbStruct
);
1131 return MMSYSERR_INVALPARAM
;
1135 return MMSYSERR_BADDEVICEID
;
1137 if (line
>= mmixer
->chans
)
1139 WARN("Invalid line id: %d not in range of 0-%d\n", line
, mmixer
->chans
-1);
1140 return MMSYSERR_INVALPARAM
;
1143 if (!mmixer
->controls
[ctrl
].enabled
)
1145 WARN("Control %d not enabled\n", ctrl
);
1146 return MIXERR_INVALCONTROL
;
1149 ct
= &mmixer
->controls
[ctrl
];
1150 elem
= mmixer
->lines
[line
].elem
;
1151 flags
&= MIXER_SETCONTROLDETAILSF_QUERYMASK
;
1154 case MIXER_SETCONTROLDETAILSF_VALUE
:
1155 TRACE("MIXER_SETCONTROLDETAILSF_VALUE (%d)\n", ctrl
);
1159 WARN("Unknown flag (%08lx)\n", flags
);
1160 return MMSYSERR_INVALPARAM
;
1163 switch (ct
->c
.dwControlType
)
1165 case MIXERCONTROL_CONTROLTYPE_VOLUME
:
1167 long min
= 0, max
= 0;
1169 LPMIXERCONTROLDETAILS_UNSIGNED mcdu
;
1170 snd_mixer_elem_t
* elem
= mmixer
->lines
[line
].elem
;
1172 if (mctrld
->cbDetails
!= sizeof(MIXERCONTROLDETAILS_UNSIGNED
))
1174 WARN("invalid parameter: cbDetails %d\n", mctrld
->cbDetails
);
1175 return MMSYSERR_INVALPARAM
;
1178 if (mctrld
->cChannels
!= 1 && mmixer
->lines
[line
].chans
!= mctrld
->cChannels
)
1180 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld
->cChannels
, mmixer
->lines
[line
].chans
);
1181 return MMSYSERR_INVALPARAM
;
1184 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct
->c
.dwControlType
), mctrld
->cChannels
);
1185 mcdu
= mctrld
->paDetails
;
1187 for (chn
=0; chn
<mctrld
->cChannels
;++chn
)
1189 TRACE("Chan %d value %d\n", chn
, mcdu
[chn
].dwValue
);
1192 /* There isn't always a capture volume, so in that case change playback volume */
1193 if (mmixer
->lines
[line
].capt
&& snd_mixer_selem_has_capture_volume(elem
))
1195 snd_mixer_selem_get_capture_volume_range(elem
, &min
, &max
);
1197 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
1198 if (snd_mixer_selem_has_capture_channel(elem
, chn
))
1200 snd_mixer_selem_set_capture_volume(elem
, chn
, min
+ normalized(mcdu
->dwValue
, 65535, max
));
1201 if (mctrld
->cChannels
!= 1)
1207 snd_mixer_selem_get_playback_volume_range(elem
, &min
, &max
);
1209 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
1210 if (snd_mixer_selem_has_playback_channel(elem
, chn
))
1212 snd_mixer_selem_set_playback_volume(elem
, chn
, min
+ normalized(mcdu
->dwValue
, 65535, max
));
1213 if (mctrld
->cChannels
!= 1)
1220 case MIXERCONTROL_CONTROLTYPE_MUTE
:
1221 case MIXERCONTROL_CONTROLTYPE_ONOFF
:
1223 LPMIXERCONTROLDETAILS_BOOLEAN mcdb
;
1225 if (mctrld
->cbDetails
!= sizeof(MIXERCONTROLDETAILS_BOOLEAN
))
1227 WARN("invalid parameter: cbDetails %d\n", mctrld
->cbDetails
);
1228 return MMSYSERR_INVALPARAM
;
1231 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct
->c
.dwControlType
), mctrld
->cChannels
);
1233 mcdb
= mctrld
->paDetails
;
1234 if (line
== 1) /* Mute/unmute capturing */
1235 for (i
= 0; i
<= SND_MIXER_SCHN_LAST
; ++i
)
1237 if (snd_mixer_selem_has_capture_channel(elem
, i
))
1238 snd_mixer_selem_set_capture_switch(elem
, i
, !mcdb
->fValue
);
1241 for (i
= 0; i
<= SND_MIXER_SCHN_LAST
; ++i
)
1242 if (snd_mixer_selem_has_playback_channel(elem
, i
))
1243 snd_mixer_selem_set_playback_switch(elem
, i
, !mcdb
->fValue
);
1247 case MIXERCONTROL_CONTROLTYPE_MIXER
:
1248 case MIXERCONTROL_CONTROLTYPE_MUX
:
1250 LPMIXERCONTROLDETAILS_BOOLEAN mcdb
;
1252 int didone
= 0, canone
= (ct
->c
.dwControlType
== MIXERCONTROL_CONTROLTYPE_MUX
);
1254 if (mctrld
->cbDetails
!= sizeof(MIXERCONTROLDETAILS_BOOLEAN
))
1256 WARN("invalid parameter: cbDetails %d\n", mctrld
->cbDetails
);
1257 return MMSYSERR_INVALPARAM
;
1260 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct
->c
.dwControlType
), mctrld
->cChannels
);
1261 mcdb
= mctrld
->paDetails
;
1263 for (x
=i
=0; x
< mmixer
->chans
; ++x
)
1264 if (line
!= x
&& mmixer
->lines
[x
].dst
== line
)
1266 TRACE("fVal[%i] (%s) = %i\n", i
, debugstr_w(mmixer
->lines
[x
].name
), mcdb
[i
].fValue
);
1267 if (i
>= mctrld
->u
.cMultipleItems
)
1269 TRACE("Too many items to fit, overflowing\n");
1270 return MIXERR_INVALVALUE
;
1272 if (mcdb
[i
].fValue
&& canone
&& didone
)
1274 TRACE("Nice try, but it's not going to work\n");
1275 elem_callback(mmixer
->lines
[1].elem
, SND_CTL_EVENT_MASK_VALUE
);
1276 return MIXERR_INVALVALUE
;
1283 if (canone
&& !didone
)
1285 TRACE("Nice try, this is not going to work either\n");
1286 elem_callback(mmixer
->lines
[1].elem
, SND_CTL_EVENT_MASK_VALUE
);
1287 return MIXERR_INVALVALUE
;
1290 for (x
= i
= 0; x
<mmixer
->chans
; ++x
)
1291 if (line
!= x
&& mmixer
->lines
[x
].dst
== line
)
1294 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
1296 if (!snd_mixer_selem_has_capture_channel(mmixer
->lines
[x
].elem
, chn
))
1298 snd_mixer_selem_set_capture_switch(mmixer
->lines
[x
].elem
, chn
, mcdb
[i
].fValue
);
1303 /* If it's a MUX, it means that only 1 channel can be selected
1304 * and the other channels are unselected
1306 * For MIXER multiple sources are allowed, so unselect here
1311 for (x
= i
= 0; x
<mmixer
->chans
; ++x
)
1312 if (line
!= x
&& mmixer
->lines
[x
].dst
== line
)
1314 if (!mcdb
[i
].fValue
)
1315 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
1317 if (!snd_mixer_selem_has_capture_channel(mmixer
->lines
[x
].elem
, chn
))
1319 snd_mixer_selem_set_capture_switch(mmixer
->lines
[x
].elem
, chn
, mcdb
[i
].fValue
);
1326 FIXME("Unhandled type %s\n", getControlType(ct
->c
.dwControlType
));
1327 return MMSYSERR_INVALPARAM
;
1329 return MMSYSERR_NOERROR
;
1332 /* Here we give info over the source/dest line given by dwSource+dwDest or dwDest, respectively
1333 * It is also possible that a line is found by componenttype or target type, latter is not implemented yet
1334 * Most important values returned in struct:
1337 * line control count
1338 * amount of channels
1340 static DWORD
MIX_GetLineInfo(UINT wDevID
, LPMIXERLINEW Ml
, DWORD_PTR flags
)
1342 DWORD_PTR qf
= flags
& MIXER_GETLINEINFOF_QUERYMASK
;
1343 mixer
*mmixer
= MIX_GetMix(wDevID
);
1350 return MMSYSERR_INVALPARAM
;
1355 WARN("Device %u not found\n", wDevID
);
1356 return MMSYSERR_BADDEVICEID
;
1359 if (Ml
->cbStruct
!= sizeof(*Ml
))
1361 WARN("invalid parameter: Ml->cbStruct = %d\n", Ml
->cbStruct
);
1362 return MMSYSERR_INVALPARAM
;
1366 Ml
->fdwLine
= MIXERLINE_LINEF_DISCONNECTED
;
1369 case MIXER_GETLINEINFOF_COMPONENTTYPE
:
1371 Ml
->dwLineID
= 0xFFFF;
1372 TRACE("Looking for componenttype %d/%x\n", Ml
->dwComponentType
, Ml
->dwComponentType
);
1373 for (idx
= 0; idx
< mmixer
->chans
; ++idx
)
1374 if (mmixer
->lines
[idx
].component
== Ml
->dwComponentType
)
1379 if (Ml
->dwLineID
== 0xFFFF)
1380 return MMSYSERR_KEYNOTFOUND
;
1381 /* Now that we have lineid, fallback to lineid*/
1384 case MIXER_GETLINEINFOF_LINEID
:
1385 if (Ml
->dwLineID
>= mmixer
->chans
)
1386 return MIXERR_INVALLINE
;
1388 TRACE("MIXER_GETLINEINFOF_LINEID %d\n", Ml
->dwLineID
);
1389 Ml
->dwDestination
= mmixer
->lines
[Ml
->dwLineID
].dst
;
1391 if (Ml
->dwDestination
!= Ml
->dwLineID
)
1393 Ml
->dwSource
= getsrcfromline(mmixer
, Ml
->dwLineID
);
1394 Ml
->cConnections
= 1;
1398 Ml
->cConnections
= getsrccntfromchan(mmixer
, Ml
->dwLineID
);
1399 Ml
->dwSource
= 0xFFFFFFFF;
1401 TRACE("Connections %d, source %d\n", Ml
->cConnections
, Ml
->dwSource
);
1404 case MIXER_GETLINEINFOF_DESTINATION
:
1405 if (Ml
->dwDestination
>= mmixer
->dests
)
1407 WARN("dest %d out of bounds\n", Ml
->dwDestination
);
1408 return MIXERR_INVALLINE
;
1411 Ml
->dwLineID
= Ml
->dwDestination
;
1412 Ml
->cConnections
= getsrccntfromchan(mmixer
, Ml
->dwLineID
);
1413 Ml
->dwSource
= 0xFFFFFFFF;
1416 case MIXER_GETLINEINFOF_SOURCE
:
1417 if (Ml
->dwDestination
>= mmixer
->dests
)
1419 WARN("dest %d for source out of bounds\n", Ml
->dwDestination
);
1420 return MIXERR_INVALLINE
;
1423 if (Ml
->dwSource
>= getsrccntfromchan(mmixer
, Ml
->dwDestination
))
1425 WARN("src %d out of bounds\n", Ml
->dwSource
);
1426 return MIXERR_INVALLINE
;
1429 Ml
->dwLineID
= getsrclinefromchan(mmixer
, Ml
->dwDestination
, Ml
->dwSource
);
1430 Ml
->cConnections
= 1;
1433 case MIXER_GETLINEINFOF_TARGETTYPE
:
1434 FIXME("TODO: TARGETTYPE, stub\n");
1435 return MMSYSERR_INVALPARAM
;
1438 FIXME("Unknown query flag: %08lx\n", qf
);
1439 return MMSYSERR_INVALPARAM
;
1442 Ml
->fdwLine
&= ~MIXERLINE_LINEF_DISCONNECTED
;
1443 Ml
->fdwLine
|= MIXERLINE_LINEF_ACTIVE
;
1444 if (Ml
->dwLineID
>= mmixer
->dests
)
1445 Ml
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
1447 mline
= &mmixer
->lines
[Ml
->dwLineID
];
1448 Ml
->dwComponentType
= mline
->component
;
1449 Ml
->cChannels
= mmixer
->lines
[Ml
->dwLineID
].chans
;
1452 for (i
=CONTROLSPERLINE
*Ml
->dwLineID
;i
<CONTROLSPERLINE
*(Ml
->dwLineID
+1); ++i
)
1453 if (mmixer
->controls
[i
].enabled
)
1456 lstrcpynW(Ml
->szShortName
, mmixer
->lines
[Ml
->dwLineID
].name
, sizeof(Ml
->szShortName
)/sizeof(WCHAR
));
1457 lstrcpynW(Ml
->szName
, mmixer
->lines
[Ml
->dwLineID
].name
, sizeof(Ml
->szName
)/sizeof(WCHAR
));
1459 Ml
->Target
.dwType
= MIXERLINE_TARGETTYPE_WAVEIN
;
1461 Ml
->Target
.dwType
= MIXERLINE_TARGETTYPE_WAVEOUT
;
1462 Ml
->Target
.dwDeviceID
= 0xFFFFFFFF;
1463 Ml
->Target
.wMid
= WINE_MIXER_MANUF_ID
;
1464 Ml
->Target
.wPid
= WINE_MIXER_PRODUCT_ID
;
1465 Ml
->Target
.vDriverVersion
= WINE_MIXER_VERSION
;
1466 lstrcpynW(Ml
->Target
.szPname
, mmixer
->mixername
, sizeof(Ml
->Target
.szPname
)/sizeof(WCHAR
));
1467 return MMSYSERR_NOERROR
;
1470 /* Get the controls that belong to a certain line, either all or 1 */
1471 static DWORD
MIX_GetLineControls(UINT wDevID
, LPMIXERLINECONTROLSW mlc
, DWORD_PTR flags
)
1473 mixer
*mmixer
= MIX_GetMix(wDevID
);
1477 if (!mlc
|| mlc
->cbStruct
!= sizeof(*mlc
))
1479 WARN("Invalid mlc %p, cbStruct: %d\n", mlc
, (!mlc
? -1 : mlc
->cbStruct
));
1480 return MMSYSERR_INVALPARAM
;
1483 if (mlc
->cbmxctrl
!= sizeof(MIXERCONTROLW
))
1485 WARN("cbmxctrl %d\n", mlc
->cbmxctrl
);
1486 return MMSYSERR_INVALPARAM
;
1490 return MMSYSERR_BADDEVICEID
;
1492 flags
&= MIXER_GETLINECONTROLSF_QUERYMASK
;
1494 if (flags
== MIXER_GETLINECONTROLSF_ONEBYID
)
1495 mlc
->dwLineID
= mlc
->u
.dwControlID
/ CONTROLSPERLINE
;
1497 if (mlc
->dwLineID
>= mmixer
->chans
)
1499 TRACE("Invalid dwLineID %d\n", mlc
->dwLineID
);
1500 return MIXERR_INVALLINE
;
1505 case MIXER_GETLINECONTROLSF_ALL
:
1506 TRACE("line=%08x MIXER_GETLINECONTROLSF_ALL (%d)\n", mlc
->dwLineID
, mlc
->cControls
);
1507 for (i
= 0; i
< CONTROLSPERLINE
; ++i
)
1508 if (mmixer
->controls
[i
+mlc
->dwLineID
* CONTROLSPERLINE
].enabled
)
1510 memcpy(&mlc
->pamxctrl
[j
], &mmixer
->controls
[i
+mlc
->dwLineID
* CONTROLSPERLINE
].c
, sizeof(MIXERCONTROLW
));
1511 TRACE("Added %s (%s)\n", debugstr_w(mlc
->pamxctrl
[j
].szShortName
), debugstr_w(mlc
->pamxctrl
[j
].szName
));
1513 if (j
> mlc
->cControls
)
1515 WARN("invalid parameter\n");
1516 return MMSYSERR_INVALPARAM
;
1520 if (!j
|| mlc
->cControls
> j
)
1522 WARN("invalid parameter\n");
1523 return MMSYSERR_INVALPARAM
;
1526 case MIXER_GETLINECONTROLSF_ONEBYID
:
1527 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYID (%x)\n", mlc
->dwLineID
, mlc
->u
.dwControlID
);
1529 if (!mmixer
->controls
[mlc
->u
.dwControlID
].enabled
)
1530 return MIXERR_INVALCONTROL
;
1532 mlc
->pamxctrl
[0] = mmixer
->controls
[mlc
->u
.dwControlID
].c
;
1534 case MIXER_GETLINECONTROLSF_ONEBYTYPE
:
1535 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", mlc
->dwLineID
, getControlType(mlc
->u
.dwControlType
));
1537 ct
= mlc
->u
.dwControlType
& MIXERCONTROL_CT_CLASS_MASK
;
1538 for (i
= 0; i
<= CONTROLSPERLINE
; ++i
)
1540 const int ofs
= i
+mlc
->dwLineID
*CONTROLSPERLINE
;
1541 if (i
== CONTROLSPERLINE
)
1543 WARN("invalid parameter: control %s not found\n", getControlType(mlc
->u
.dwControlType
));
1544 return MIXERR_INVALCONTROL
;
1546 if (mmixer
->controls
[ofs
].enabled
&& (mmixer
->controls
[ofs
].c
.dwControlType
& MIXERCONTROL_CT_CLASS_MASK
) == ct
)
1548 mlc
->pamxctrl
[0] = mmixer
->controls
[ofs
].c
;
1554 FIXME("Unknown flag %08lx\n", flags
& MIXER_GETLINECONTROLSF_QUERYMASK
);
1555 return MMSYSERR_INVALPARAM
;
1558 return MMSYSERR_NOERROR
;
1561 /**************************************************************************
1562 * mxdMessage (WINEALSA.3)
1564 DWORD WINAPI
ALSA_mxdMessage(UINT wDevID
, UINT wMsg
, DWORD_PTR dwUser
,
1565 DWORD_PTR dwParam1
, DWORD_PTR dwParam2
)
1568 TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID
, getMessage(wMsg
),
1569 dwUser
, dwParam1
, dwParam2
);
1573 case DRVM_INIT
: ALSA_MixerInit(); ret
= MMSYSERR_NOERROR
; break;
1574 case DRVM_EXIT
: ALSA_MixerExit(); ret
= MMSYSERR_NOERROR
; break;
1575 /* All taken care of by driver initialisation */
1576 /* Unimplemented, and not needed */
1579 ret
= MMSYSERR_NOERROR
; break;
1582 ret
= MIX_Open(wDevID
, (LPMIXEROPENDESC
) dwParam1
, dwParam2
); break;
1585 ret
= MIX_Close(wDevID
); break;
1587 case MXDM_GETDEVCAPS
:
1588 ret
= MIX_GetDevCaps(wDevID
, (LPMIXERCAPS2W
)dwParam1
, dwParam2
); break;
1590 case MXDM_GETLINEINFO
:
1591 ret
= MIX_GetLineInfo(wDevID
, (LPMIXERLINEW
)dwParam1
, dwParam2
); break;
1593 case MXDM_GETLINECONTROLS
:
1594 ret
= MIX_GetLineControls(wDevID
, (LPMIXERLINECONTROLSW
)dwParam1
, dwParam2
); break;
1596 case MXDM_GETCONTROLDETAILS
:
1597 ret
= MIX_GetControlDetails(wDevID
, (LPMIXERCONTROLDETAILS
)dwParam1
, dwParam2
); break;
1599 case MXDM_SETCONTROLDETAILS
:
1600 ret
= MIX_SetControlDetails(wDevID
, (LPMIXERCONTROLDETAILS
)dwParam1
, dwParam2
); break;
1602 case MXDM_GETNUMDEVS
:
1606 WARN("unknown message %s!\n", getMessage(wMsg
));
1607 return MMSYSERR_NOTSUPPORTED
;
1610 TRACE("Returning %08X\n", ret
);