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
51 #include "wine/unicode.h"
52 #include "wine/debug.h"
54 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 wether 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
)
82 #define MSG_TO_STR(x) case x: return #x;
84 MSG_TO_STR(DRVM_INIT
);
85 MSG_TO_STR(DRVM_EXIT
);
86 MSG_TO_STR(DRVM_ENABLE
);
87 MSG_TO_STR(DRVM_DISABLE
);
88 MSG_TO_STR(MXDM_GETDEVCAPS
);
89 MSG_TO_STR(MXDM_GETLINEINFO
);
90 MSG_TO_STR(MXDM_GETNUMDEVS
);
91 MSG_TO_STR(MXDM_OPEN
);
92 MSG_TO_STR(MXDM_CLOSE
);
93 MSG_TO_STR(MXDM_GETLINECONTROLS
);
94 MSG_TO_STR(MXDM_GETCONTROLDETAILS
);
95 MSG_TO_STR(MXDM_SETCONTROLDETAILS
);
99 sprintf(str
, "UNKNOWN(%08x)", uMsg
);
103 /* A simple declaration of a line control
104 * These are each of the channels that show up
106 typedef struct line
{
107 /* Name we present to outside world */
108 WCHAR name
[MAXPNAMELEN
];
114 snd_mixer_elem_t
*elem
;
117 /* A control structure, with toggle enabled switch
118 * Control structures control volume, muted, which capture source
120 typedef struct control
{
129 WCHAR mixername
[MAXPNAMELEN
];
132 LPDRVCALLBACK callback
;
133 DWORD_PTR callbackpriv
;
140 #define MAX_MIXERS 32
141 #define CONTROLSPERLINE 3
145 static int cards
= 0;
146 static mixer mixdev
[MAX_MIXERS
];
147 static HANDLE thread
;
148 static int elem_callback(snd_mixer_elem_t
*elem
, unsigned int mask
);
149 static DWORD WINAPI
ALSA_MixerPollThread(LPVOID lParam
);
150 static CRITICAL_SECTION elem_crst
;
151 static int msg_pipe
[2];
154 /* found channel names in alsa lib, alsa api doesn't have another way for this
155 * map name -> componenttype, worst case we get a wrong componenttype which is
159 static const struct mixerlinetype
{
160 const char *name
; DWORD cmpt
;
162 { "Master", MIXERLINE_COMPONENTTYPE_DST_SPEAKERS
, },
163 { "Capture", MIXERLINE_COMPONENTTYPE_DST_WAVEIN
, },
164 { "PCM", MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT
, },
165 { "PC Speaker", MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER
, },
166 { "Synth", MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER
, },
167 { "Headphone", MIXERLINE_COMPONENTTYPE_DST_HEADPHONES
, },
168 { "Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
, },
169 { "Aux", MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED
, },
170 { "CD", MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC
, },
171 { "Line", MIXERLINE_COMPONENTTYPE_SRC_LINE
, },
172 { "Phone", MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE
, },
175 /* Map name to MIXERLINE_COMPONENTTYPE_XXX */
176 static int getcomponenttype(const char *name
)
179 for (x
=0; x
< sizeof(converttable
)/sizeof(converttable
[0]); ++x
)
180 if (!strcasecmp(name
, converttable
[x
].name
))
182 TRACE("%d -> %s\n", x
, name
);
183 return converttable
[x
].cmpt
;
185 WARN("Unknown mixer name %s, probably harmless\n", name
);
186 return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED
;
189 /* Is this control suited for showing up? */
190 static int blacklisted(snd_mixer_elem_t
*elem
)
192 const char *name
= snd_mixer_selem_get_name(elem
);
195 if (!snd_mixer_selem_has_playback_volume(elem
) &&
196 (!snd_mixer_selem_has_capture_volume(elem
) ||
197 !snd_mixer_selem_has_capture_switch(elem
)))
200 TRACE("%s: %x\n", name
, blisted
);
204 static void fillcontrols(mixer
*mmixer
)
207 for (id
= 0; id
< mmixer
->chans
; ++id
)
209 line
*mline
= &mmixer
->lines
[id
];
210 int ofs
= CONTROLSPERLINE
* id
;
214 if (mline
->capt
&& snd_mixer_selem_has_capture_volume(mline
->elem
))
215 snd_mixer_selem_get_capture_volume_range(mline
->elem
, &min
, &max
);
217 snd_mixer_selem_get_playback_volume_range(mline
->elem
, &min
, &max
);
219 /* (!snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) */
220 /* Volume, always enabled by definition of blacklisted channels */
221 mmixer
->controls
[ofs
].enabled
= 1;
222 mmixer
->controls
[ofs
].c
.cbStruct
= sizeof(mmixer
->controls
[ofs
].c
);
223 mmixer
->controls
[ofs
].c
.dwControlType
= MIXERCONTROL_CONTROLTYPE_VOLUME
;
224 mmixer
->controls
[ofs
].c
.dwControlID
= ofs
;
225 mmixer
->controls
[ofs
].c
.Bounds
.s1
.dwMinimum
= 0;
226 mmixer
->controls
[ofs
].c
.Bounds
.s1
.dwMaximum
= 65535;
227 mmixer
->controls
[ofs
].c
.Metrics
.cSteps
= 65536/(max
-min
);
229 if ((id
== 1 && snd_mixer_selem_has_capture_switch(mline
->elem
)) ||
230 (!mline
->capt
&& snd_mixer_selem_has_playback_switch(mline
->elem
)))
231 { /* MUTE button optional, main capture channel should have one too */
232 mmixer
->controls
[ofs
+OFS_MUTE
].enabled
= 1;
233 mmixer
->controls
[ofs
+OFS_MUTE
].c
.cbStruct
= sizeof(mmixer
->controls
[ofs
].c
);
234 mmixer
->controls
[ofs
+OFS_MUTE
].c
.dwControlType
= MIXERCONTROL_CONTROLTYPE_MUTE
;
235 mmixer
->controls
[ofs
+OFS_MUTE
].c
.dwControlID
= ofs
+OFS_MUTE
;
236 mmixer
->controls
[ofs
+OFS_MUTE
].c
.Bounds
.s1
.dwMaximum
= 1;
239 if (mline
->capt
&& snd_mixer_selem_has_capture_switch_exclusive(mline
->elem
))
240 mmixer
->controls
[CONTROLSPERLINE
+OFS_MUX
].c
.dwControlType
= MIXERCONTROL_CONTROLTYPE_MUX
;
243 { /* Capture select, in case cMultipleItems is 0, it means capture is disabled anyway */
244 mmixer
->controls
[ofs
+OFS_MUX
].enabled
= 1;
245 mmixer
->controls
[ofs
+OFS_MUX
].c
.cbStruct
= sizeof(mmixer
->controls
[ofs
].c
);
246 mmixer
->controls
[ofs
+OFS_MUX
].c
.dwControlType
= MIXERCONTROL_CONTROLTYPE_MIXER
;
247 mmixer
->controls
[ofs
+OFS_MUX
].c
.dwControlID
= ofs
+OFS_MUX
;
248 mmixer
->controls
[ofs
+OFS_MUX
].c
.fdwControl
= MIXERCONTROL_CONTROLF_MULTIPLE
;
250 for (x
= 0; x
<mmixer
->chans
; ++x
)
251 if (x
!= id
&& mmixer
->lines
[x
].dst
== id
)
252 ++(mmixer
->controls
[ofs
+OFS_MUX
].c
.cMultipleItems
);
253 if (!mmixer
->controls
[ofs
+OFS_MUX
].c
.cMultipleItems
)
254 mmixer
->controls
[ofs
+OFS_MUX
].enabled
= 0;
256 mmixer
->controls
[ofs
+OFS_MUX
].c
.Bounds
.s1
.dwMaximum
= mmixer
->controls
[ofs
+OFS_MUX
].c
.cMultipleItems
- 1;
257 mmixer
->controls
[ofs
+OFS_MUX
].c
.Metrics
.cSteps
= mmixer
->controls
[ofs
+OFS_MUX
].c
.cMultipleItems
;
259 for (x
=0; x
<CONTROLSPERLINE
; ++x
)
261 lstrcpynW(mmixer
->controls
[ofs
+x
].c
.szShortName
, mline
->name
, sizeof(mmixer
->controls
[ofs
+x
].c
.szShortName
)/sizeof(WCHAR
));
262 lstrcpynW(mmixer
->controls
[ofs
+x
].c
.szName
, mline
->name
, sizeof(mmixer
->controls
[ofs
+x
].c
.szName
)/sizeof(WCHAR
));
267 /* get amount of channels for elem */
268 /* Officially we should keep capture/playback seperated,
269 * but that's not going to work in the alsa api */
270 static int chans(mixer
*mmixer
, snd_mixer_elem_t
* elem
, DWORD capt
)
274 if (capt
&& snd_mixer_selem_has_capture_volume(elem
)) {
275 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
276 if (snd_mixer_selem_has_capture_channel(elem
, chn
))
279 for (chn
= 0; chn
<= SND_MIXER_SCHN_LAST
; ++chn
)
280 if (snd_mixer_selem_has_playback_channel(elem
, chn
))
284 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"));
288 static void ALSA_MixerInit(void)
292 for (x
= 0; x
< MAX_MIXERS
; ++x
)
295 char cardind
[6], cardname
[10];
296 BOOL hascapt
=0, hasmast
=0;
300 snd_mixer_elem_t
*elem
, *mastelem
= NULL
, *captelem
= NULL
;
301 snd_ctl_card_info_t
*info
= NULL
;
302 snd_ctl_card_info_alloca(&info
);
303 mixdev
[mixnum
].lines
= NULL
;
304 mixdev
[mixnum
].callback
= 0;
306 snprintf(cardind
, sizeof(cardind
), "%d", x
);
307 card
= snd_card_get_index(cardind
);
310 snprintf(cardname
, sizeof(cardname
), "hw:%d", card
);
312 err
= snd_ctl_open(&ctl
, cardname
, 0);
315 WARN("Cannot open card: %s\n", snd_strerror(err
));
319 err
= snd_ctl_card_info(ctl
, info
);
322 WARN("Cannot get card info: %s\n", snd_strerror(err
));
327 MultiByteToWideChar(CP_UNIXCP
, 0, snd_ctl_card_info_get_name(info
), -1, mixdev
[mixnum
].mixername
, sizeof(mixdev
[mixnum
].mixername
)/sizeof(WCHAR
));
330 err
= snd_mixer_open(&mixdev
[mixnum
].mix
,0);
333 WARN("Error occured opening mixer: %s\n", snd_strerror(err
));
337 err
= snd_mixer_attach(mixdev
[mixnum
].mix
, cardname
);
341 err
= snd_mixer_selem_register(mixdev
[mixnum
].mix
, NULL
, NULL
);
345 err
= snd_mixer_load(mixdev
[mixnum
].mix
);
349 mixdev
[mixnum
].chans
= 0;
350 mixdev
[mixnum
].dests
= 1; /* Master, Capture will be enabled if needed */
352 for (elem
= snd_mixer_first_elem(mixdev
[mixnum
].mix
); elem
; elem
= snd_mixer_elem_next(elem
))
353 if (!strcasecmp(snd_mixer_selem_get_name(elem
), "Master"))
358 else if (!strcasecmp(snd_mixer_selem_get_name(elem
), "Capture"))
363 else if (!blacklisted(elem
))
365 if (snd_mixer_selem_has_capture_switch(elem
))
367 ++mixdev
[mixnum
].chans
;
368 mixdev
[mixnum
].dests
= 2;
370 if (snd_mixer_selem_has_playback_volume(elem
))
371 ++mixdev
[mixnum
].chans
;
374 /* If there is only 'Capture' and 'Master', this device is not worth it */
375 if (!mixdev
[mixnum
].chans
)
377 WARN("No channels found, skipping device!\n");
378 snd_mixer_close(mixdev
[mixnum
].mix
);
382 /* If there are no 'Capture' and 'Master', something is wrong */
383 if (hasmast
!= 1 || hascapt
!= 1)
386 FIXME("Should have found 1 channel for 'Master', but instead found %d\n", hasmast
);
388 FIXME("Should have found 1 channel for 'Capture', but instead found %d\n", hascapt
);
392 mixdev
[mixnum
].chans
+= 2; /* Capture/Master */
393 mixdev
[mixnum
].lines
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(line
) * mixdev
[mixnum
].chans
);
394 mixdev
[mixnum
].controls
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(control
) * CONTROLSPERLINE
*mixdev
[mixnum
].chans
);
396 if (!mixdev
[mixnum
].lines
|| !mixdev
[mixnum
].controls
)
400 mline
= &mixdev
[mixnum
].lines
[0];
401 MultiByteToWideChar(CP_UNIXCP
, 0, snd_mixer_selem_get_name(mastelem
), -1, mline
->name
, sizeof(mline
->name
)/sizeof(WCHAR
));
402 mline
->component
= getcomponenttype("Master");
405 mline
->elem
= mastelem
;
406 mline
->chans
= chans(&mixdev
[mixnum
], mastelem
, 0);
409 * Note: since mmixer->dests = 1, it means only playback control is visible
410 * This makes sense, because if there are no capture sources capture control
411 * can't do anything and should be invisible */
414 MultiByteToWideChar(CP_UNIXCP
, 0, snd_mixer_selem_get_name(captelem
), -1, mline
->name
, sizeof(mline
->name
)/sizeof(WCHAR
));
415 mline
->component
= getcomponenttype("Capture");
418 mline
->elem
= captelem
;
419 mline
->chans
= chans(&mixdev
[mixnum
], captelem
, 1);
421 snd_mixer_elem_set_callback(mastelem
, &elem_callback
);
422 snd_mixer_elem_set_callback_private(mastelem
, &mixdev
[mixnum
]);
424 if (mixdev
[mixnum
].dests
== 2)
426 snd_mixer_elem_set_callback(captelem
, &elem_callback
);
427 snd_mixer_elem_set_callback_private(captelem
, &mixdev
[mixnum
]);
431 for (elem
= snd_mixer_first_elem(mixdev
[mixnum
].mix
); elem
; elem
= snd_mixer_elem_next(elem
))
432 if (elem
!= mastelem
&& elem
!= captelem
&& !blacklisted(elem
))
434 const char * name
= snd_mixer_selem_get_name(elem
);
435 DWORD comp
= getcomponenttype(name
);
436 snd_mixer_elem_set_callback(elem
, &elem_callback
);
437 snd_mixer_elem_set_callback_private(elem
, &mixdev
[mixnum
]);
439 if (snd_mixer_selem_has_playback_volume(elem
))
441 mline
= &mixdev
[mixnum
].lines
[y
++];
442 mline
->component
= comp
;
443 MultiByteToWideChar(CP_UNIXCP
, 0, name
, -1, mline
->name
, MAXPNAMELEN
);
444 mline
->capt
= mline
->dst
= 0;
446 mline
->chans
= chans(&mixdev
[mixnum
], elem
, 0);
448 if (snd_mixer_selem_has_capture_switch(elem
))
450 mline
= &mixdev
[mixnum
].lines
[y
++];
451 mline
->component
= comp
;
452 MultiByteToWideChar(CP_UNIXCP
, 0, name
, -1, mline
->name
, MAXPNAMELEN
);
453 mline
->capt
= mline
->dst
= 1;
455 mline
->chans
= chans(&mixdev
[mixnum
], elem
, 1);
459 fillcontrols(&mixdev
[mixnum
]);
461 TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname
, mixdev
[mixnum
].dests
, mixdev
[mixnum
].chans
, debugstr_w(mixdev
[mixnum
].mixername
));
466 WARN("Error occured initialising mixer: %s\n", snd_strerror(err
));
467 if (mixdev
[mixnum
].lines
)
468 HeapFree(GetProcessHeap(), 0, mixdev
[mixnum
].lines
);
469 if (mixdev
[mixnum
].controls
)
470 HeapFree(GetProcessHeap(), 0, mixdev
[mixnum
].controls
);
471 snd_mixer_close(mixdev
[mixnum
].mix
);
475 InitializeCriticalSection(&elem_crst
);
476 elem_crst
.DebugInfo
->Spare
[0] = (DWORD_PTR
)(__FILE__
": ALSA_MIXER.elem_crst");
480 static void ALSA_MixerExit(void)
486 WARN("Callback thread still alive, terminating uncleanly, refcnt: %d\n", refcnt
);
487 /* Least we can do is making sure we're not in 'foreign' code */
488 EnterCriticalSection(&elem_crst
);
489 TerminateThread(thread
, 1);
493 TRACE("Cleaning up\n");
495 elem_crst
.DebugInfo
->Spare
[0] = 0;
496 DeleteCriticalSection(&elem_crst
);
497 for (x
= 0; x
< cards
; ++x
)
499 snd_mixer_close(mixdev
[x
].mix
);
500 HeapFree(GetProcessHeap(), 0, mixdev
[x
].lines
);
501 HeapFree(GetProcessHeap(), 0, mixdev
[x
].controls
);
506 static mixer
* MIX_GetMix(UINT wDevID
)
510 if (wDevID
< 0 || wDevID
>= cards
)
512 WARN("Invalid mixer id: %d\n", wDevID
);
516 mmixer
= &mixdev
[wDevID
];
520 /* Since alsa doesn't tell what exactly changed, just assume all affected controls changed */
521 static int elem_callback(snd_mixer_elem_t
*elem
, unsigned int type
)
523 mixer
*mmixer
= snd_mixer_elem_get_callback_private(elem
);
525 BOOL captchanged
= 0;
527 if (type
!= SND_CTL_EVENT_MASK_VALUE
)
532 EnterCriticalSection(&elem_crst
);
534 if (!mmixer
->callback
)
537 for (x
=0; x
<mmixer
->chans
; ++x
)
539 const int ofs
= CONTROLSPERLINE
*x
;
540 if (elem
!= mmixer
->lines
[x
].elem
)
543 if (mmixer
->lines
[x
].capt
)
546 TRACE("Found changed control %s\n", debugstr_w(mmixer
->lines
[x
].name
));
547 mmixer
->callback(mmixer
->hmx
, MM_MIXM_LINE_CHANGE
, mmixer
->callbackpriv
, x
, 0);
548 mmixer
->callback(mmixer
->hmx
, MM_MIXM_CONTROL_CHANGE
, mmixer
->callbackpriv
, ofs
, 0);
550 if (mmixer
->controls
[ofs
+OFS_MUTE
].enabled
)
551 mmixer
->callback(mmixer
->hmx
, MM_MIXM_CONTROL_CHANGE
, mmixer
->callbackpriv
, ofs
+OFS_MUTE
, 0);
554 mmixer
->callback(mmixer
->hmx
, MM_MIXM_CONTROL_CHANGE
, mmixer
->callbackpriv
, CONTROLSPERLINE
+OFS_MUX
, 0);
557 LeaveCriticalSection(&elem_crst
);
562 static DWORD WINAPI
ALSA_MixerPollThread(LPVOID lParam
)
564 struct pollfd
*pfds
= NULL
;
565 int x
, y
, err
, mcnt
, count
= 1;
567 TRACE("%p\n", lParam
);
569 for (x
= 0; x
< cards
; ++x
)
570 count
+= snd_mixer_poll_descriptors_count(mixdev
[x
].mix
);
572 TRACE("Counted %d descriptors\n", count
);
573 pfds
= HeapAlloc(GetProcessHeap(), 0, count
* sizeof(struct pollfd
));
577 WARN("Out of memory\n");
581 pfds
[0].fd
= msg_pipe
[0];
582 pfds
[0].events
= POLLIN
;
585 for (x
= 0; x
< cards
; ++x
)
586 y
+= snd_mixer_poll_descriptors(mixdev
[x
].mix
, &pfds
[y
], count
- y
);
588 while ((err
= poll(pfds
, (unsigned int) count
, -1)) >= 0 || errno
== EINTR
|| errno
== EAGAIN
)
590 if (pfds
[0].revents
& POLLIN
)
594 for (x
= y
= 0; x
< cards
; ++x
)
596 int j
, max
= snd_mixer_poll_descriptors_count(mixdev
[x
].mix
);
597 for (j
= 0; j
< max
; ++j
)
598 if (pfds
[mcnt
+j
].revents
)
600 y
+= snd_mixer_handle_events(mixdev
[x
].mix
);
606 TRACE("Handled %d events\n", y
);
610 TRACE("Shutting down\n");
612 HeapFree(GetProcessHeap(), 0, pfds
);
614 y
= read(msg_pipe
[0], &x
, sizeof(x
));
620 static DWORD
MIX_Open(UINT wDevID
, LPMIXEROPENDESC desc
, DWORD_PTR flags
)
622 mixer
*mmixer
= MIX_GetMix(wDevID
);
624 return MMSYSERR_BADDEVICEID
;
626 flags
&= CALLBACK_TYPEMASK
;
632 case CALLBACK_FUNCTION
:
636 FIXME("Unhandled callback type: %08lx\n", flags
& CALLBACK_TYPEMASK
);
637 return MIXERR_INVALVALUE
;
640 mmixer
->callback
= (LPDRVCALLBACK
)desc
->dwCallback
;
641 mmixer
->callbackpriv
= desc
->dwInstance
;
642 mmixer
->hmx
= (HDRVR
)desc
->hmx
;
645 if (InterlockedIncrement(&refcnt
) == 1)
647 if (pipe(msg_pipe
) >= 0)
649 thread
= CreateThread(NULL
, 0, ALSA_MixerPollThread
, NULL
, 0, NULL
);
654 msg_pipe
[0] = msg_pipe
[1] = -1;
658 msg_pipe
[0] = msg_pipe
[1] = -1;
661 return MMSYSERR_NOERROR
;
664 static DWORD
MIX_Close(UINT wDevID
)
667 mixer
*mmixer
= MIX_GetMix(wDevID
);
669 return MMSYSERR_BADDEVICEID
;
671 EnterCriticalSection(&elem_crst
);
672 mmixer
->callback
= 0;
673 LeaveCriticalSection(&elem_crst
);
675 if (!InterlockedDecrement(&refcnt
))
677 if (write(msg_pipe
[1], &x
, sizeof(x
)) > 0)
679 TRACE("Shutting down thread...\n");
680 WaitForSingleObject(thread
, INFINITE
);
685 return MMSYSERR_NOERROR
;
688 static DWORD
MIX_GetDevCaps(UINT wDevID
, LPMIXERCAPS2W caps
, DWORD_PTR parm2
)
690 mixer
*mmixer
= MIX_GetMix(wDevID
);
694 return MMSYSERR_INVALPARAM
;
697 return MMSYSERR_BADDEVICEID
;
699 memset(&capsW
, 0, sizeof(MIXERCAPS2W
));
701 capsW
.wMid
= WINE_MIXER_MANUF_ID
;
702 capsW
.wPid
= WINE_MIXER_PRODUCT_ID
;
703 capsW
.vDriverVersion
= WINE_MIXER_VERSION
;
705 lstrcpynW(capsW
.szPname
, mmixer
->mixername
, sizeof(capsW
.szPname
)/sizeof(WCHAR
));
706 capsW
.cDestinations
= mmixer
->dests
;
707 memcpy(caps
, &capsW
, min(parm2
, sizeof(capsW
)));
708 return MMSYSERR_NOERROR
;
712 /* get amount of sources for dest */
713 static int getsrccntfromchan(mixer
*mmixer
, int dad
)
717 for (i
=0; i
<mmixer
->chans
; ++i
)
718 if (i
!= dad
&& mmixer
->lines
[i
].dst
== dad
)
723 FIXME("No src found for %i (%s)?\n", dad
, debugstr_w(mmixer
->lines
[dad
].name
));
727 /* find lineid for source 'num' with dest 'dad' */
728 static int getsrclinefromchan(mixer
*mmixer
, int dad
, int num
)
731 for (i
=0; i
<mmixer
->chans
; ++i
)
732 if (i
!= dad
&& mmixer
->lines
[i
].dst
== dad
)
738 WARN("No src found for src %i from dest %i\n", num
, dad
);
742 /* get the source number belonging to line */
743 static int getsrcfromline(mixer
*mmixer
, int line
)
745 int i
, j
=0, dad
= mmixer
->lines
[line
].dst
;
747 for (i
=0; i
<mmixer
->chans
; ++i
)
748 if (i
!= dad
&& mmixer
->lines
[i
].dst
== dad
)
754 WARN("No src found for line %i with dad %i\n", line
, dad
);
758 /* Here we give info over the source/dest line given by dwSource+dwDest or dwDest, respectively
759 * It is also possible that a line is found by componenttype or target type, latter is not implemented yet
760 * Most important values returned in struct:
766 static DWORD
MIX_GetLineInfo(UINT wDevID
, LPMIXERLINEW Ml
, DWORD_PTR flags
)
768 DWORD_PTR qf
= flags
& MIXER_GETLINEINFOF_QUERYMASK
;
769 mixer
*mmixer
= MIX_GetMix(wDevID
);
776 return MMSYSERR_INVALPARAM
;
781 WARN("Device %u not found\n", wDevID
);
782 return MMSYSERR_BADDEVICEID
;
785 if (Ml
->cbStruct
!= sizeof(*Ml
))
787 WARN("invalid parameter: Ml->cbStruct = %d != %d\n", Ml
->cbStruct
, sizeof(*Ml
));
788 return MMSYSERR_INVALPARAM
;
791 Ml
->fdwLine
= MIXERLINE_LINEF_ACTIVE
;
796 case MIXER_GETLINEINFOF_COMPONENTTYPE
:
798 Ml
->dwLineID
= 0xFFFF;
799 for (idx
= 0; idx
< mmixer
->chans
; ++idx
)
800 if (mmixer
->lines
[idx
].component
== Ml
->dwComponentType
)
805 if (Ml
->dwLineID
== 0xFFFF)
806 return MMSYSERR_KEYNOTFOUND
;
807 /* Now that we have lineid, fallback to lineid*/
810 case MIXER_GETLINEINFOF_LINEID
:
811 if (Ml
->dwLineID
< 0 || Ml
->dwLineID
>= mmixer
->chans
)
812 return MIXERR_INVALLINE
;
814 TRACE("MIXER_GETLINEINFOF_LINEID %d\n", Ml
->dwLineID
);
815 Ml
->dwDestination
= mmixer
->lines
[Ml
->dwLineID
].dst
;
817 if (Ml
->dwDestination
!= Ml
->dwLineID
)
819 Ml
->dwSource
= getsrcfromline(mmixer
, Ml
->dwLineID
);
820 Ml
->cConnections
= 1;
824 Ml
->cConnections
= getsrccntfromchan(mmixer
, Ml
->dwLineID
);
825 Ml
->dwSource
= 0xFFFFFFFF;
827 TRACE("Connections %d, source %d\n", Ml
->cConnections
, Ml
->dwSource
);
830 case MIXER_GETLINEINFOF_DESTINATION
:
831 if (Ml
->dwDestination
< 0 || Ml
->dwDestination
>= mmixer
->dests
)
833 WARN("dest %d out of bounds\n", Ml
->dwDestination
);
834 return MIXERR_INVALLINE
;
837 Ml
->dwLineID
= Ml
->dwDestination
;
838 Ml
->cConnections
= getsrccntfromchan(mmixer
, Ml
->dwLineID
);
839 Ml
->dwSource
= 0xFFFFFFFF;
842 case MIXER_GETLINEINFOF_SOURCE
:
843 if (Ml
->dwDestination
< 0 || Ml
->dwDestination
>= mmixer
->dests
)
845 WARN("dest %d for source out of bounds\n", Ml
->dwDestination
);
846 return MIXERR_INVALLINE
;
849 if (Ml
->dwSource
< 0 || Ml
->dwSource
>= getsrccntfromchan(mmixer
, Ml
->dwDestination
))
851 WARN("src %d out of bounds\n", Ml
->dwSource
);
852 return MIXERR_INVALLINE
;
855 Ml
->dwLineID
= getsrclinefromchan(mmixer
, Ml
->dwDestination
, Ml
->dwSource
);
856 Ml
->cConnections
= 1;
859 case MIXER_GETLINEINFOF_TARGETTYPE
:
860 FIXME("TODO: TARGETTYPE, stub\n");
861 return MMSYSERR_INVALPARAM
;
864 FIXME("Unknown query flag: %08lx\n", qf
);
865 return MMSYSERR_INVALPARAM
;
868 if (Ml
->dwLineID
>= mmixer
->dests
)
869 Ml
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
871 mline
= &mmixer
->lines
[Ml
->dwLineID
];
872 Ml
->dwComponentType
= mline
->component
;
873 Ml
->cChannels
= mmixer
->lines
[Ml
->dwLineID
].chans
;
876 for (i
=CONTROLSPERLINE
*Ml
->dwLineID
;i
<CONTROLSPERLINE
*(Ml
->dwLineID
+1); ++i
)
877 if (mmixer
->controls
[i
].enabled
)
880 lstrcpynW(Ml
->szShortName
, mmixer
->lines
[Ml
->dwLineID
].name
, sizeof(Ml
->szShortName
)/sizeof(WCHAR
));
881 lstrcpynW(Ml
->szName
, mmixer
->lines
[Ml
->dwLineID
].name
, sizeof(Ml
->szName
)/sizeof(WCHAR
));
883 Ml
->Target
.dwType
= MIXERLINE_TARGETTYPE_WAVEIN
;
885 Ml
->Target
.dwType
= MIXERLINE_TARGETTYPE_WAVEOUT
;
886 Ml
->Target
.dwDeviceID
= 0xFFFFFFFF;
887 Ml
->Target
.wMid
= WINE_MIXER_MANUF_ID
;
888 Ml
->Target
.wPid
= WINE_MIXER_PRODUCT_ID
;
889 Ml
->Target
.vDriverVersion
= WINE_MIXER_VERSION
;
890 lstrcpynW(Ml
->Target
.szPname
, mmixer
->mixername
, sizeof(Ml
->Target
.szPname
)/sizeof(WCHAR
));
891 return MMSYSERR_NOERROR
;
896 /**************************************************************************
897 * mxdMessage (WINEALSA.3)
899 DWORD WINAPI
ALSA_mxdMessage(UINT wDevID
, UINT wMsg
, DWORD_PTR dwUser
,
900 DWORD_PTR dwParam1
, DWORD_PTR dwParam2
)
904 TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID
, getMessage(wMsg
),
905 dwUser
, dwParam1
, dwParam2
);
909 case DRVM_INIT
: ALSA_MixerInit(); ret
= MMSYSERR_NOERROR
; break;
910 case DRVM_EXIT
: ALSA_MixerExit(); ret
= MMSYSERR_NOERROR
; break;
911 /* All taken care of by driver initialisation */
912 /* Unimplemented, and not needed */
915 ret
= MMSYSERR_NOERROR
; break;
918 ret
= MIX_Open(wDevID
, (LPMIXEROPENDESC
) dwParam1
, dwParam2
); break;
921 ret
= MIX_Close(wDevID
); break;
923 case MXDM_GETDEVCAPS
:
924 ret
= MIX_GetDevCaps(wDevID
, (LPMIXERCAPS2W
)dwParam1
, dwParam2
); break;
926 case MXDM_GETLINEINFO
:
927 ret
= MIX_GetLineInfo(wDevID
, (LPMIXERLINEW
)dwParam1
, dwParam2
); break;
929 case MXDM_GETNUMDEVS
:
933 WARN("unknown message %s!\n", getMessage(wMsg
));
934 return MMSYSERR_NOTSUPPORTED
;
937 TRACE("Returning %08X\n", ret
);
940 TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", wDevID
, wMsg
, dwUser
, dwParam1
, dwParam2
);
942 return MMSYSERR_NOTENABLED
;