winealsa: add support for mixer devices, try 3

Maarten Lankhorst m.b.lankhorst at gmail.com
Thu Apr 12 01:44:04 CDT 2007


Fixed compiler warnings, and made code 64 bits safe.
-------------- next part --------------
>From a3afecf5fcb5f0b969441838767d68b4eeaf8584 Mon Sep 17 00:00:00 2001
From: maarten lankhorst <m.b.lankhorst at gmail.com>
Date: Thu, 12 Apr 2007 08:39:16 +0200
Subject: [PATCH] winealsa: add support for mixer devices, try 3

---
 dlls/winealsa.drv/Makefile.in       |    1 +
 dlls/winealsa.drv/alsa.c            |    4 +-
 dlls/winealsa.drv/alsa.h            |    4 +
 dlls/winealsa.drv/mixer.c           | 1466 +++++++++++++++++++++++++++++++++++
 dlls/winealsa.drv/winealsa.drv.spec |    1 +
 5 files changed, 1475 insertions(+), 1 deletions(-)

diff --git a/dlls/winealsa.drv/Makefile.in b/dlls/winealsa.drv/Makefile.in
index ff6e60c..58c733f 100644
--- a/dlls/winealsa.drv/Makefile.in
+++ b/dlls/winealsa.drv/Makefile.in
@@ -10,6 +10,7 @@ C_SRCS = \
 	alsa.c \
 	dsoutput.c \
 	midi.c \
+	mixer.c \
 	waveinit.c \
 	wavein.c \
 	waveout.c
diff --git a/dlls/winealsa.drv/alsa.c b/dlls/winealsa.drv/alsa.c
index b1945b2..fdccdc5 100644
--- a/dlls/winealsa.drv/alsa.c
+++ b/dlls/winealsa.drv/alsa.c
@@ -754,8 +754,10 @@ LRESULT CALLBACK ALSA_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg,
 #ifdef HAVE_ALSA
     case DRV_LOAD:		ALSA_WaveInit();
 				ALSA_MidiInit();
+				ALSA_MixerInit();
+				return 1;
+    case DRV_FREE:		ALSA_MixerExit();
 				return 1;
-    case DRV_FREE:		return 1;
     case DRV_OPEN:		return 1;
     case DRV_CLOSE:		return 1;
     case DRV_ENABLE:		return 1;
diff --git a/dlls/winealsa.drv/alsa.h b/dlls/winealsa.drv/alsa.h
index 26e379a..35e84bc 100644
--- a/dlls/winealsa.drv/alsa.h
+++ b/dlls/winealsa.drv/alsa.h
@@ -203,4 +203,8 @@ extern LONG ALSA_MidiInit(void);
 /* waveinit.c */
 extern LONG ALSA_WaveInit(void);
 
+/* mixer.c */
+extern void ALSA_MixerInit(void);
+extern void ALSA_MixerExit(void);
+
 #endif /* __ALSA_H */
diff --git a/dlls/winealsa.drv/mixer.c b/dlls/winealsa.drv/mixer.c
new file mode 100644
index 0000000..463fd45
--- /dev/null
+++ b/dlls/winealsa.drv/mixer.c
@@ -0,0 +1,1466 @@
+/*
+ * Alsa MIXER Wine Driver for Linux
+ * Very loosely based on wineoss mixer driver
+ *
+ * Copyright 2007 Maarten Lankhorst
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include "config.h"
+#include "wine/port.h"
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <errno.h>
+#include <assert.h>
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+
+#define NONAMELESSUNION
+#define NONAMELESSSTRUCT
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winerror.h"
+#include "winuser.h"
+#include "winnls.h"
+#include "mmddk.h"
+#include "mmsystem.h"
+#include "alsa.h"
+#include "wine/unicode.h"
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(mixer);
+
+#ifdef HAVE_ALSA
+
+#define	WINE_MIXER_MANUF_ID      0xAA
+#define	WINE_MIXER_PRODUCT_ID    0x55
+#define	WINE_MIXER_VERSION       0x0100
+
+/* Generic notes:
+ * In windows it seems to be required for all controls to have a volume switch
+ * In alsa that's optional
+ *
+ * I assume for playback controls, that there is always a playback volume switch available
+ * Mute is optional
+ *
+ * For capture controls, it is needed that there is a capture switch and a volume switch,
+ * It doesn't matter wether it is a playback volume switch or a capture volume switch.
+ * The code will first try to get/adjust capture volume, if that fails it tries playback volume
+ * It is not pretty, but under my 3 test cards it seems that there is no other choice:
+ * Most capture controls don't have a capture volume setting
+ *
+ * MUX means that only capture source can be exclusively selected,
+ * MIXER means that multiple sources can be selected simultaneously.
+ */
+
+static const char * getMessage(UINT uMsg)
+{
+    static char str[64];
+#define MSG_TO_STR(x) case x: return #x;
+    switch (uMsg){
+    MSG_TO_STR(DRVM_INIT);
+    MSG_TO_STR(DRVM_EXIT);
+    MSG_TO_STR(DRVM_ENABLE);
+    MSG_TO_STR(DRVM_DISABLE);
+    MSG_TO_STR(MXDM_GETDEVCAPS);
+    MSG_TO_STR(MXDM_GETLINEINFO);
+    MSG_TO_STR(MXDM_GETNUMDEVS);
+    MSG_TO_STR(MXDM_OPEN);
+    MSG_TO_STR(MXDM_CLOSE);
+    MSG_TO_STR(MXDM_GETLINECONTROLS);
+    MSG_TO_STR(MXDM_GETCONTROLDETAILS);
+    MSG_TO_STR(MXDM_SETCONTROLDETAILS);
+    default: break;
+    }
+#undef MSG_TO_STR
+    sprintf(str, "UNKNOWN(%08x)", uMsg);
+    return str;
+}
+
+static const char * getControlType(DWORD dwControlType)
+{
+    static char str[64];
+#define TYPE_TO_STR(x) case x: return #x;
+    switch (dwControlType) {
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
+    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
+    }
+#undef TYPE_TO_STR
+    sprintf(str, "UNKNOWN(%08x)", dwControlType);
+    return str;
+}
+
+/* A simple declaration of a line control
+ * These are each of the channels that show up
+ */
+typedef struct line {
+    /* Name we present to outside world */
+    WCHAR name[MAXPNAMELEN];
+
+    DWORD component;
+    DWORD dst;
+    DWORD capt;
+    DWORD chans;
+    snd_mixer_elem_t *elem;
+} line;
+
+/* A control structure, with toggle enabled switch
+ * Control structures control volume, muted, which capture source
+ */
+typedef struct control {
+    BOOL enabled;
+    MIXERCONTROLW c;
+} control;
+
+/* Mixer device */
+typedef struct mixer
+{
+    snd_mixer_t *mix;
+    WCHAR mixername[MAXPNAMELEN];
+    char cardname[10];
+
+    int chans, dests;
+    LPDRVCALLBACK callback;
+    DWORD_PTR callbackpriv;
+    HDRVR hmx;
+
+    line *lines;
+    control *controls;
+} mixer;
+
+#define MAX_MIXERS 32
+#define CONTROLSPERLINE 3
+#define OFS_MUTE 2
+#define OFS_MUX 1
+
+static int cards = 0;
+static mixer mixdev[MAX_MIXERS];
+static HANDLE thread;
+static CRITICAL_SECTION crst;
+
+/* found channel names in alsa lib, alsa api doesn't have another way for this
+ * map name -> componenttype, worst case we get a wrong componenttype which is
+ * mostly harmless
+ */
+
+static const struct mixerlinetype {
+    const char *name;  DWORD cmpt;
+} converttable[] = {
+    { "Master",     MIXERLINE_COMPONENTTYPE_DST_SPEAKERS,    },
+    { "Capture",    MIXERLINE_COMPONENTTYPE_DST_WAVEIN,      },
+    { "PCM",        MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT,     },
+    { "PC Speaker", MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER,   },
+    { "Synth",      MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER, },
+    { "Headphone",  MIXERLINE_COMPONENTTYPE_DST_HEADPHONES,  },
+    { "Mic",        MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE,  },
+    { "Aux",        MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED,   },
+    { "CD",         MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC, },
+    { "Line",       MIXERLINE_COMPONENTTYPE_SRC_LINE,        },
+    { "Phone",      MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE,   },
+};
+
+/* Map name to MIXERLINE_COMPONENTTYPE_XXX */
+static int getcomponenttype(const char *name)
+{
+    int x;
+    for (x=0; x< sizeof(converttable)/sizeof(converttable[0]); ++x)
+        if (!strcasecmp(name, converttable[x].name))
+        {
+            TRACE("%d -> %s\n", x, name);
+            return converttable[x].cmpt;
+        }
+    WARN("Unknown mixer name %s, probably harmless\n", name);
+    return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
+}
+
+/* Is this control suited for showing up? */
+static int blacklisted(snd_mixer_elem_t *elem)
+{
+    const char *name = snd_mixer_selem_get_name(elem);
+    BOOL blisted = 0;
+
+    if (!snd_mixer_selem_has_playback_volume(elem) &&
+        (!snd_mixer_selem_has_capture_volume(elem) ||
+         !snd_mixer_selem_has_capture_switch(elem)))
+        blisted = 1;
+
+    TRACE("%s: %x\n", name, blisted);
+    return blisted;
+}
+
+static void fillcontrols(mixer *mmixer)
+{
+    int id;
+    for (id = 0; id < mmixer->chans; ++id)
+    {
+        line *mline = &mmixer->lines[id];
+        int ofs = CONTROLSPERLINE * id;
+        int x;
+        long min, max;
+
+        if (mline->capt && snd_mixer_selem_has_capture_volume(mline->elem))
+            snd_mixer_selem_get_capture_volume_range(mline->elem, &min, &max);
+        else
+            snd_mixer_selem_get_playback_volume_range(mline->elem, &min, &max);
+
+        /* (!snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) */
+        /* Volume, always enabled by definition of blacklisted channels */
+        mmixer->controls[ofs].enabled = 1;
+        mmixer->controls[ofs].c.cbStruct = sizeof(mmixer->controls[ofs].c);
+        mmixer->controls[ofs].c.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
+        mmixer->controls[ofs].c.dwControlID = ofs;
+        mmixer->controls[ofs].c.Bounds.s1.dwMinimum = 0;
+        mmixer->controls[ofs].c.Bounds.s1.dwMaximum = 65535;
+        mmixer->controls[ofs].c.Metrics.cSteps = 65536/(max-min);
+
+        if ((id == 1 && snd_mixer_selem_has_capture_switch(mline->elem)) ||
+            (!mline->capt && snd_mixer_selem_has_playback_switch(mline->elem)))
+        { /* MUTE button optional, main capture channel should have one too */
+            mmixer->controls[ofs+OFS_MUTE].enabled = 1;
+            mmixer->controls[ofs+OFS_MUTE].c.cbStruct = sizeof(mmixer->controls[ofs].c);
+            mmixer->controls[ofs+OFS_MUTE].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
+            mmixer->controls[ofs+OFS_MUTE].c.dwControlID = ofs+OFS_MUTE;
+            mmixer->controls[ofs+OFS_MUTE].c.Bounds.s1.dwMaximum = 1;
+        }
+
+        if (mline->capt && snd_mixer_selem_has_capture_switch_exclusive(mline->elem))
+            mmixer->controls[CONTROLSPERLINE+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUX;
+
+        if (id == 1)
+        { /* Capture select, in case cMultipleItems is 0, it means capture is disabled anyway */
+            mmixer->controls[ofs+OFS_MUX].enabled = 1;
+            mmixer->controls[ofs+OFS_MUX].c.cbStruct = sizeof(mmixer->controls[ofs].c);
+            mmixer->controls[ofs+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MIXER;
+            mmixer->controls[ofs+OFS_MUX].c.dwControlID = ofs+OFS_MUX;
+            mmixer->controls[ofs+OFS_MUX].c.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE;
+
+            for (x = 0; x<mmixer->chans; ++x)
+                if (x != id && mmixer->lines[x].dst == id)
+                    ++(mmixer->controls[ofs+OFS_MUX].c.cMultipleItems);
+            if (!mmixer->controls[ofs+OFS_MUX].c.cMultipleItems)
+                mmixer->controls[ofs+OFS_MUX].enabled = 0;
+
+            mmixer->controls[ofs+OFS_MUX].c.Bounds.s1.dwMaximum = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems - 1;
+            mmixer->controls[ofs+OFS_MUX].c.Metrics.cSteps = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems;
+        }
+        for (x=0; x<CONTROLSPERLINE; ++x)
+        {
+            lstrcpynW(mmixer->controls[ofs+x].c.szShortName, mline->name, sizeof(mmixer->controls[ofs+x].c.szShortName)/sizeof(WCHAR));
+            lstrcpynW(mmixer->controls[ofs+x].c.szName, mline->name, sizeof(mmixer->controls[ofs+x].c.szName)/sizeof(WCHAR));
+        }
+    }
+}
+
+/* get amount of channels for elem */
+/* Officially we should keep capture/playback seperated,
+ * but that's not going to work in the alsa api */
+static int chans(mixer *mmixer, snd_mixer_elem_t * elem, DWORD capt)
+{
+    int ret=0, chn;
+
+    if (capt && snd_mixer_selem_has_capture_volume(elem)) {
+        for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+            if (snd_mixer_selem_has_capture_channel(elem, chn))
+                ++ret;
+    } else {
+        for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+            if (snd_mixer_selem_has_playback_channel(elem, chn))
+                ++ret;
+    }
+    if (!ret)
+        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"));
+    return ret;
+}
+
+static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask);
+static DWORD WINAPI callback_thread(LPVOID lParam);
+
+void ALSA_MixerInit(void)
+{
+    int x, mixnum = 0;
+
+    for (x = 0; x < MAX_MIXERS; ++x)
+    {
+        int card, err, y;
+        char cardind[6];
+        BOOL hascapt=0, hasmast=0;
+        line *mline;
+
+        snd_ctl_t *ctl;
+        snd_mixer_elem_t *elem, *mastelem = NULL, *captelem = NULL;
+        snd_ctl_card_info_t *info = NULL;
+        snd_ctl_card_info_alloca(&info);
+
+        mixdev[mixnum].lines = NULL;
+        mixdev[mixnum].callback = 0;
+        mixdev[mixnum].controls = NULL;
+        sprintf(cardind, "%d", x);
+        card = snd_card_get_index(cardind);
+        if (card < 0 || card > MAX_MIXERS - 1)
+            continue;
+        sprintf(mixdev[mixnum].cardname, "hw:%d", card);
+
+        err = snd_ctl_open(&ctl, mixdev[mixnum].cardname, 0);
+        if (err < 0)
+        {
+            WARN("Cannot open card: %s\n", snd_strerror(err));
+            continue;
+        }
+
+        err = snd_ctl_card_info(ctl, info);
+        if (err < 0)
+        {
+            WARN("Cannot get card info: %s\n", snd_strerror(err));
+            snd_ctl_close(ctl);
+            continue;
+        }
+
+        MultiByteToWideChar(CP_UNIXCP, 0, snd_ctl_card_info_get_name(info), -1, mixdev[mixnum].mixername, sizeof(mixdev[mixnum].mixername)/sizeof(WCHAR));
+        snd_ctl_close(ctl);
+
+        err = snd_mixer_open(&mixdev[mixnum].mix,0);
+        if (err < 0)
+        {
+            WARN("Error occured opening mixer: %s\n", snd_strerror(err));
+            continue;
+        }
+
+        err = snd_mixer_attach(mixdev[mixnum].mix, mixdev[mixnum].cardname);
+        if (err < 0)
+            goto eclose;
+
+        err = snd_mixer_selem_register(mixdev[mixnum].mix, NULL, NULL);
+        if (err < 0)
+            goto eclose;
+
+        err = snd_mixer_load(mixdev[mixnum].mix);
+        if (err < 0)
+            goto eclose;
+
+        mixdev[mixnum].chans = 0;
+        mixdev[mixnum].dests = 1; /* Master, Capture will be enabled if needed */
+
+        for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
+            if (!strcasecmp(snd_mixer_selem_get_name(elem), "Master"))
+            {
+                mastelem = elem;
+                ++hasmast;
+            }
+            else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture"))
+            {
+                captelem = elem;
+                ++hascapt;
+            }
+            else if (!blacklisted(elem))
+            {
+                if (snd_mixer_selem_has_capture_switch(elem))
+                    ++mixdev[mixnum].chans;
+                if (snd_mixer_selem_has_playback_volume(elem))
+                    ++mixdev[mixnum].chans;
+            }
+
+        /* If there is only 'Capture' and 'Master', this device is not worth it */
+        if (!mixdev[mixnum].chans)
+        {
+            WARN("No channels found, skipping device!\n");
+            snd_mixer_close(mixdev[mixnum].mix);
+            continue;
+        }
+
+        /* If there are no 'Capture' and 'Master', something is wrong */
+        if (hasmast != 1 || hascapt != 1)
+        {
+            if (hasmast != 1)
+                FIXME("Should have found 1 channel for 'Master', but instead found %d\n", hasmast);
+            if (hascapt != 1)
+                FIXME("Should have found 1 channel for 'Capture', but instead found %d\n", hascapt);
+            goto eclose;
+        }
+
+        mixdev[mixnum].chans += 2; /* Capture/Master */
+        mixdev[mixnum].lines = calloc(sizeof(MIXERLINEW), mixdev[mixnum].chans);
+
+        /* Master control */
+        mline = &mixdev[mixnum].lines[0];
+        MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
+        mline->component = getcomponenttype("Master");
+        mline->dst = 0;
+        mline->capt = 0;
+        mline->elem = mastelem;
+        mline->chans = chans(&mixdev[mixnum], mastelem, 0);
+
+        /* Capture control
+         * Note: since mmixer->dests = 1, it means only playback control is visible
+         * This makes sense, because if there are no capture sources capture control
+         * can't do anything and should be invisible */
+
+        mline++;
+        MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
+        mline->component = getcomponenttype("Capture");
+        mline->dst = 1;
+        mline->capt = 1;
+        mline->elem = captelem;
+        mline->chans = chans(&mixdev[mixnum], captelem, 1);
+
+        mixdev[mixnum].controls = calloc(sizeof(control), CONTROLSPERLINE*mixdev[mixnum].chans);
+        err = -ENOMEM;
+        if (!mixdev[mixnum].lines || !mixdev[mixnum].controls)
+            goto eclose;
+
+        snd_mixer_elem_set_callback(mastelem, &elem_callback);
+        snd_mixer_elem_set_callback_private(mastelem, &mixdev[mixnum]);
+
+        y=2;
+        for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
+            if (elem != mastelem && elem != captelem && !blacklisted(elem))
+            {
+                const char * name = snd_mixer_selem_get_name(elem);
+                DWORD comp = getcomponenttype(name);
+                snd_mixer_elem_set_callback(elem, &elem_callback);
+                snd_mixer_elem_set_callback_private(elem, &mixdev[mixnum]);
+
+                if (snd_mixer_selem_has_playback_volume(elem))
+                {
+                    mline = &mixdev[mixnum].lines[y++];
+                    mline->component = comp;
+                    MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
+                    mline->capt = mline->dst = 0;
+                    mline->elem = elem;
+                    mline->chans = chans(&mixdev[mixnum], elem, 0);
+                }
+                if (snd_mixer_selem_has_capture_switch(elem))
+                {
+                    mline = &mixdev[mixnum].lines[y++];
+                    mline->component = comp;
+                    MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
+                    mline->capt = mline->dst = 1;
+                    mline->elem = elem;
+                    mline->chans = chans(&mixdev[mixnum], elem, 1);
+                    /* Enable capture */
+                    mixdev[mixnum].dests = 2;
+                }
+            }
+
+        if (mixdev[mixnum].dests == 2)
+        {
+            snd_mixer_elem_set_callback(captelem, &elem_callback);
+            snd_mixer_elem_set_callback_private(captelem, &mixdev[mixnum]);
+        }
+
+        fillcontrols(&mixdev[mixnum]);
+
+        TRACE("%s: Amount of controls: %i/%i, name: %s\n", mixdev[mixnum].cardname, mixdev[mixnum].dests, mixdev[mixnum].chans, debugstr_w(mixdev[mixnum].mixername));
+        mixnum++;
+        continue;
+
+        eclose:
+        WARN("Error occured initialising mixer: %s\n", snd_strerror(err));
+        if (mixdev[mixnum].lines)
+            free(mixdev[mixnum].lines);
+        if (mixdev[mixnum].controls)
+            free(mixdev[mixnum].controls);
+        snd_mixer_close(mixdev[mixnum].mix);
+    }
+    cards = mixnum;
+    InitializeCriticalSection(&crst);
+    crst.DebugInfo->Spare[0] = (DWORD_PTR)"WINEALSA_mixer_crst";
+
+    thread = CreateThread(NULL, 0, callback_thread, NULL, 0, NULL);
+    TRACE("\n");
+}
+
+void ALSA_MixerExit(void)
+{
+    int x;
+    TRACE("\n");
+    EnterCriticalSection(&crst);
+    crst.DebugInfo->Spare[0] = 0;
+    TerminateThread(thread, 1);
+    DeleteCriticalSection(&crst);
+
+    for (x = 0; x < cards; ++x)
+    {
+        snd_mixer_close(mixdev[x].mix);
+        free(mixdev[x].lines);
+        free(mixdev[x].controls);
+    }
+    cards = 0;
+}
+
+static mixer* MIX_GetMix(UINT wDevID)
+{
+    mixer *mmixer;
+
+    if (wDevID < 0 || wDevID >= cards)
+    {
+        WARN("Invalid mixer id: %d\n", wDevID);
+        return NULL;
+    }
+
+    mmixer = &mixdev[wDevID];
+    return mmixer;
+}
+
+/* Since alsa doesn't tell what exactly changed, just assume all affected controls changed */
+static int elem_callback(snd_mixer_elem_t *elem, unsigned int type)
+{
+    mixer *mmixer = snd_mixer_elem_get_callback_private(elem);
+    int x;
+    BOOL captchanged=0;
+
+    if (type != SND_CTL_EVENT_MASK_VALUE)
+        return 0;
+
+    assert(mmixer);
+    if (!mmixer->callback)
+        return 0;
+
+    for (x=0; x<mmixer->chans; ++x)
+    {
+        const int ofs = CONTROLSPERLINE*x;
+        if (elem != mmixer->lines[x].elem)
+            continue;
+
+        if (mmixer->lines[x].capt)
+            ++captchanged;
+
+        TRACE("Found changed control %s\n", debugstr_w(mmixer->lines[x].name));
+        mmixer->callback(mmixer->hmx, MM_MIXM_LINE_CHANGE, mmixer->callbackpriv, x, 0);
+        mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs, 0);
+
+        if (mmixer->controls[ofs+OFS_MUTE].enabled)
+            mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs+OFS_MUTE, 0);
+    }
+    if (captchanged)
+        mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, CONTROLSPERLINE+OFS_MUX, 0);
+
+    return 0;
+}
+
+/* Note: this thread can be TerminateThread'd, when not in critical section
+ * Hence I use alloca here, and a critical section to protect when it is not
+ * desired to be TerminateThread'd, assume when the critical section is taken
+ * we won't ever get it back and we're signalled to terminate
+ */
+static DWORD WINAPI callback_thread(LPVOID lParam)
+{
+    struct pollfd *pfds = NULL;
+    int x, i, y, err, count = 0;
+    TRACE("%p\n", lParam);
+
+    if (!TryEnterCriticalSection(&crst))
+        ExitThread(1);
+
+    for (x = 0; x < cards; ++x)
+        count += snd_mixer_poll_descriptors_count(mixdev[x].mix);
+
+    TRACE("Counted %d descriptors\n", count);
+    pfds = alloca(count * sizeof(struct pollfd));
+
+    if (!pfds)
+    {
+        WARN("Out of memory\n");
+        ExitThread(1);
+    }
+
+    for (x = y = 0; x < cards; ++x)
+    {
+        i = snd_mixer_poll_descriptors(mixdev[x].mix, &pfds[y], snd_mixer_poll_descriptors_count(mixdev[x].mix));
+        y += i;
+    }
+    LeaveCriticalSection(&crst);
+
+    /* Ignore EINTR, happens when wine is suspended then resumed */
+    while ((err = poll(pfds, (unsigned int) count, -1)) >= 0 || errno == EINTR)
+    {
+        if (!TryEnterCriticalSection(&crst))
+            break;
+        for (x = y = 0; x < cards; ++x)
+            y += snd_mixer_handle_events(mixdev[x].mix);
+        LeaveCriticalSection(&crst);
+        if (y)
+            TRACE("Handled %d events\n", y);
+    }
+    TRACE("Thread exited\n");
+
+    ExitThread(0);
+}
+
+static DWORD MIX_Open(UINT wDevID, LPMIXEROPENDESC desc, DWORD_PTR flags)
+{
+    mixer *mmixer = MIX_GetMix(wDevID);
+    if (!mmixer)
+        return MMSYSERR_BADDEVICEID;
+
+    flags &= CALLBACK_TYPEMASK;
+    switch (flags)
+    {
+    case CALLBACK_NULL:
+        return MMSYSERR_NOERROR;
+
+    case CALLBACK_FUNCTION:
+        break;
+
+    default:
+        FIXME("Unhandled callback type: %08lx\n", flags & CALLBACK_TYPEMASK);
+        return MIXERR_INVALVALUE;
+    }
+
+    mmixer->callback = (LPDRVCALLBACK)desc->dwCallback;
+    mmixer->callbackpriv = desc->dwInstance;
+    mmixer->hmx = (HDRVR)desc->hmx;
+    return MMSYSERR_NOERROR;
+}
+
+static DWORD MIX_Close(UINT wDevID)
+{
+    mixer *mmixer = MIX_GetMix(wDevID);
+    if (!mmixer)
+        return MMSYSERR_BADDEVICEID;
+
+    mmixer->callback = 0;
+    return MMSYSERR_NOERROR;
+}
+
+static DWORD MIX_GetDevCaps(UINT wDevID, LPMIXERCAPS2W caps, DWORD_PTR parm2)
+{
+    mixer *mmixer = MIX_GetMix(wDevID);
+    MIXERCAPS2W capsW;
+
+    if (!caps)
+        return MMSYSERR_INVALPARAM;
+
+    if (!mmixer)
+        return MMSYSERR_BADDEVICEID;
+
+    memset(&capsW, 0, sizeof(MIXERCAPS2W));
+
+    capsW.wMid = WINE_MIXER_MANUF_ID;
+    capsW.wPid = WINE_MIXER_PRODUCT_ID;
+    capsW.vDriverVersion = WINE_MIXER_VERSION;
+
+    lstrcpynW(capsW.szPname, mmixer->mixername, sizeof(capsW.szPname)/sizeof(WCHAR));
+    capsW.cDestinations = mmixer->dests;
+    memcpy(caps, &capsW, min(parm2, sizeof(capsW)));
+    return MMSYSERR_NOERROR;
+}
+
+/* convert win32 volume to alsa volume, and vice versa */
+static DWORD normalized(long value, long prevmax, long nextmax)
+{
+    double ret = (double)value;
+    ret *= (double)nextmax;
+    ret /= (double)prevmax;
+    ret += .5;
+    TRACE("%ld/%ld -> %ld/%ld\n", value, prevmax, (long)ret, nextmax);
+
+    if (ret > nextmax)
+        ret = nextmax;
+    else if (ret < 0)
+        ret = 0;
+
+    return (DWORD)ret;
+}
+
+/* get amount of sources for dest */
+static int getsrccntfromchan(mixer *mmixer, int dad)
+{
+    int i, j=0;
+
+    for (i=0; i<mmixer->chans; ++i)
+        if (i != dad && mmixer->lines[i].dst == dad)
+        {
+            ++j;
+        }
+    if (!j)
+        FIXME("No src found for %i (%s)?\n", dad, debugstr_w(mmixer->lines[dad].name));
+    return j;
+}
+
+/* find lineid for source 'num' with dest 'dad' */
+static int getsrclinefromchan(mixer *mmixer, int dad, int num)
+{
+    int i, j=0;
+    for (i=0; i<mmixer->chans; ++i)
+        if (i != dad && mmixer->lines[i].dst == dad)
+        {
+            if (num == j)
+                return i;
+            ++j;
+        }
+    WARN("No src found for src %i from dest %i\n", num, dad);
+    return 0;
+}
+
+/* get the source number belonging to line */
+static int getsrcfromline(mixer *mmixer, int line)
+{
+    int i, j=0, dad = mmixer->lines[line].dst;
+
+    for (i=0; i<mmixer->chans; ++i)
+        if (i != dad && mmixer->lines[i].dst == dad)
+        {
+            if (line == i)
+                return j;
+            ++j;
+        }
+    WARN("No src found for line %i with dad %i\n", line, dad);
+    return 0;
+}
+
+/* Get volume/muted/capture channel */
+static DWORD MIX_GetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
+{
+    mixer *mmixer = MIX_GetMix(wDevID);
+    DWORD ctrl;
+    DWORD line;
+    control *ct;
+
+    if (!mctrld)
+        return MMSYSERR_INVALPARAM;
+
+    ctrl = mctrld->dwControlID;
+    line = ctrl/CONTROLSPERLINE;
+
+    if (mctrld->cbStruct != sizeof(*mctrld))
+        return MMSYSERR_INVALPARAM;
+
+    if (!mmixer)
+        return MMSYSERR_BADDEVICEID;
+
+    if (line < 0 || line >= mmixer->chans || !mmixer->controls[ctrl].enabled)
+        return MIXERR_INVALCONTROL;
+
+    ct = &mmixer->controls[ctrl];
+
+    flags &= MIXER_GETCONTROLDETAILSF_QUERYMASK;
+
+    switch (flags) {
+    case MIXER_GETCONTROLDETAILSF_VALUE:
+        TRACE("MIXER_GETCONTROLDETAILSF_VALUE (%d/%d)\n", ctrl, line);
+        switch (ct->c.dwControlType)
+        {
+        case MIXERCONTROL_CONTROLTYPE_VOLUME:
+        {
+            long min = 0, max = 0, vol = 0;
+            int chn;
+            LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
+            snd_mixer_elem_t * elem = mmixer->lines[line].elem;
+
+            if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
+            {
+                WARN("invalid parameter: cbDetails != %d\n", sizeof(MIXERCONTROLDETAILS_UNSIGNED));
+                return MMSYSERR_INVALPARAM;
+            }
+
+            TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
+
+            mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)mctrld->paDetails;
+
+            if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
+            {
+                WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
+                return MMSYSERR_INVALPARAM;
+            }
+
+            if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem)) {
+                snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
+                for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+                    if (snd_mixer_selem_has_capture_channel(elem, chn))
+                    {
+                        snd_mixer_selem_get_capture_volume(elem, chn, &vol);
+                        mcdu->dwValue = normalized(vol - min, max, 65535);
+                        if (mctrld->cChannels == 1)
+                            break;
+                        ++mcdu;
+                    }
+            } else {
+                snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
+
+                for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+                    if (snd_mixer_selem_has_playback_channel(elem, chn))
+                    {
+                        snd_mixer_selem_get_playback_volume(elem, chn, &vol);
+                        mcdu->dwValue = normalized(vol - min, max, 65535);
+                        if (mctrld->cChannels == 1)
+                            break;
+                        ++mcdu;
+                    }
+            }
+
+            return MMSYSERR_NOERROR;
+        }
+
+        case MIXERCONTROL_CONTROLTYPE_ONOFF:
+        case MIXERCONTROL_CONTROLTYPE_MUTE:
+        {
+            LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
+            int chn, ival;
+            snd_mixer_elem_t * elem = mmixer->lines[line].elem;
+
+            if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
+            {
+                WARN("invalid parameter: cbDetails != %d\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN));
+                return MMSYSERR_INVALPARAM;
+            }
+
+            TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
+
+            mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)mctrld->paDetails;
+
+            if (line == 1)
+                for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+                {
+                    if (!snd_mixer_selem_has_capture_channel(elem, chn))
+                        continue;
+                    snd_mixer_selem_get_capture_switch(elem, chn, &ival);
+                    break;
+                }
+            else
+                for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+                {
+                    if (!snd_mixer_selem_has_playback_channel(elem, chn))
+                        continue;
+                    snd_mixer_selem_get_playback_switch(elem, chn, &ival);
+                    break;
+                }
+
+            mcdb->fValue = !ival;
+            TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
+            return MMSYSERR_NOERROR;
+        }
+        case MIXERCONTROL_CONTROLTYPE_MIXER:
+        case MIXERCONTROL_CONTROLTYPE_MUX:
+        {
+            LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
+            int x, i=0, ival = 0, chn;
+
+            if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
+            {
+                WARN("invalid parameter: cbDetails != %d\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN));
+                return MMSYSERR_INVALPARAM;
+            }
+
+            TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
+
+            mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)mctrld->paDetails;
+
+            for (x = 0; x<mmixer->chans; ++x)
+                if (line != x && mmixer->lines[x].dst == line)
+                {
+                    ival = 0;
+                    for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+                    {
+                        if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
+                            continue;
+                        snd_mixer_selem_get_capture_switch(mmixer->lines[x].elem, chn, &ival);
+                        if (ival)
+                            break;
+                    }
+                    if (i >= mctrld->u.cMultipleItems)
+                    {
+                        TRACE("overflow\n");
+                        return MMSYSERR_INVALPARAM;
+                    }
+                    TRACE("fVal[%i] = %sselected\n", i, (!ival ? "un" : ""));
+                    mcdb[i++].fValue = ival;
+                }
+            break;
+        }
+        default:
+
+            FIXME("Unhandled controltype %s\n", getControlType(ct->c.dwControlType));
+            return MMSYSERR_INVALPARAM;
+        }
+        return MMSYSERR_NOERROR;
+
+    case MIXER_GETCONTROLDETAILSF_LISTTEXT:
+        TRACE("MIXER_GETCONTROLDETAILSF_LISTTEXT (%d)\n", ctrl);
+
+        if (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX || ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER)
+        {
+            LPMIXERCONTROLDETAILS_LISTTEXTW mcdlt = (LPMIXERCONTROLDETAILS_LISTTEXTW)mctrld->paDetails;
+            int i, j;
+
+            for (i = j = 0; j < mmixer->chans; ++j)
+                if (j != line && mmixer->lines[j].dst == line)
+                {
+                    if (i > mctrld->u.cMultipleItems)
+                        return MMSYSERR_INVALPARAM;
+                    mcdlt->dwParam1 = j;
+                    mcdlt->dwParam2 = mmixer->lines[j].component;
+                    lstrcpynW(mcdlt->szName, mmixer->lines[j].name, sizeof(mcdlt->szName) / sizeof(WCHAR));
+                    TRACE("Adding %i as %s\n", j, debugstr_w(mcdlt->szName));
+                    ++i; ++mcdlt;
+                }
+            if (i < mctrld->u.cMultipleItems)
+                return MMSYSERR_INVALPARAM;
+            return MMSYSERR_NOERROR;
+        }
+        FIXME ("Imagine this code being horribly broken and incomplete, introducing: reality\n");
+        return MMSYSERR_INVALPARAM;
+
+    default:
+        WARN("Unknown flag (%08lx)\n", flags);
+        return MMSYSERR_INVALPARAM;
+    }
+}
+
+/* Set volume/capture channel/muted for control */
+static DWORD MIX_SetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
+{
+    mixer *mmixer = MIX_GetMix(wDevID);
+    DWORD ctrl, line, i;
+    control *ct;
+    snd_mixer_elem_t * elem;
+
+    if (!mctrld)
+        return MMSYSERR_INVALPARAM;
+
+    ctrl = mctrld->dwControlID;
+    line = ctrl/CONTROLSPERLINE;
+
+    if (mctrld->cbStruct != sizeof(*mctrld))
+    {
+        WARN("Invalid size of mctrld %d instead of %d\n", mctrld->cbStruct, sizeof(*mctrld));
+        return MMSYSERR_INVALPARAM;
+    }
+
+    if (!mmixer)
+        return MMSYSERR_BADDEVICEID;
+
+    if (line < 0 || line >= mmixer->chans)
+    {
+        WARN("Invalid line id: %d not in range of 0-%d\n", line, mmixer->chans-1);
+        return MMSYSERR_INVALPARAM;
+    }
+
+    if (!mmixer->controls[ctrl].enabled)
+    {
+        WARN("Control %d not enabled\n", ctrl);
+        return MIXERR_INVALCONTROL;
+    }
+
+    ct = &mmixer->controls[ctrl];
+    elem = mmixer->lines[line].elem;
+    flags &= MIXER_SETCONTROLDETAILSF_QUERYMASK;
+
+    switch (flags) {
+    case MIXER_SETCONTROLDETAILSF_VALUE:
+        TRACE("MIXER_SETCONTROLDETAILSF_VALUE (%d)\n", ctrl);
+        break;
+
+    default:
+        WARN("Unknown flag (%08lx)\n", flags);
+        return MMSYSERR_INVALPARAM;
+    }
+
+    switch (ct->c.dwControlType)
+    {
+    case MIXERCONTROL_CONTROLTYPE_VOLUME:
+    {
+        long min = 0, max = 0;
+        int chn;
+        LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
+        snd_mixer_elem_t * elem = mmixer->lines[line].elem;
+
+        if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
+        {
+            WARN("invalid parameter: cbDetails != %d\n", sizeof(MIXERCONTROLDETAILS_UNSIGNED));
+            return MMSYSERR_INVALPARAM;
+        }
+
+        if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
+        {
+            WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
+            return MMSYSERR_INVALPARAM;
+        }
+
+        TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
+        mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)mctrld->paDetails;
+
+        for (chn=0; chn<mctrld->cChannels;++chn)
+        {
+            TRACE("Chan %d value %d\n", chn, mcdu[chn].dwValue);
+        }
+
+        /* There isn't always a capture volume, so in that case change playback volume */
+        if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem))
+        {
+            snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
+
+            for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+                if (snd_mixer_selem_has_capture_channel(elem, chn))
+                {
+                    snd_mixer_selem_set_capture_volume(elem, chn, min+normalized(mcdu->dwValue, 65535, max));
+                    if (mctrld->cChannels != 1)
+                        mcdu++;
+                }
+        }
+        else
+        {
+            snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
+
+            for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+                if (snd_mixer_selem_has_playback_channel(elem, chn))
+                {
+                    snd_mixer_selem_set_playback_volume(elem, chn, min+normalized(mcdu->dwValue, 65535, max));
+                    if (mctrld->cChannels != 1)
+                        mcdu++;
+                }
+        }
+
+        break;
+    }
+    case MIXERCONTROL_CONTROLTYPE_MUTE:
+    case MIXERCONTROL_CONTROLTYPE_ONOFF:
+    {
+        LPMIXERCONTROLDETAILS_BOOLEAN	mcdb;
+
+        if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
+        {
+            WARN("invalid parameter: cbDetails != %d\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN));
+            return MMSYSERR_INVALPARAM;
+        }
+
+        TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
+
+        mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)mctrld->paDetails;
+        if (line == 1) /* Mute/unmute capturing */
+            for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
+            {
+                if (snd_mixer_selem_has_capture_channel(elem, i))
+                    snd_mixer_selem_set_capture_switch(elem, i, !mcdb->fValue);
+            }
+        else
+            for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
+                if (snd_mixer_selem_has_playback_channel(elem, i))
+                    snd_mixer_selem_set_playback_switch(elem, i, !mcdb->fValue);
+        break;
+    }
+
+    case MIXERCONTROL_CONTROLTYPE_MIXER:
+    case MIXERCONTROL_CONTROLTYPE_MUX:
+    {
+        LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
+        int x, i=0, chn;
+        int didone = 0, canone = (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX);
+
+        if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
+        {
+            WARN("invalid parameter: cbDetails != %d\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN));
+            return MMSYSERR_INVALPARAM;
+        }
+
+        TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
+        mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)mctrld->paDetails;
+
+        for (x=i=0; x < mmixer->chans; ++x)
+            if (line != x && mmixer->lines[x].dst == line)
+            {
+                TRACE("fVal[%i] (%s) = %i\n", i, debugstr_w(mmixer->lines[x].name), mcdb[i].fValue);
+                if (i >= mctrld->u.cMultipleItems)
+                {
+                    TRACE("Too many items to fit, overflowing\n");
+                    return MIXERR_INVALVALUE;
+                }
+                if (mcdb[i].fValue && canone && didone)
+                {
+                    TRACE("Nice try, but it's not going to work\n");
+                    elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
+                    return MIXERR_INVALVALUE;
+                }
+                if (mcdb[i].fValue)
+                    didone = 1;
+                ++i;
+            }
+
+        if (canone && !didone)
+        {
+            TRACE("Nice try, this is not going to work either\n");
+            elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
+            return MIXERR_INVALVALUE;
+        }
+
+        for (x = i = 0; x<mmixer->chans; ++x)
+            if (line != x && mmixer->lines[x].dst == line)
+            {
+                if (mcdb[i].fValue)
+                    for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+                    {
+                        if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
+                            continue;
+                        snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
+                    }
+                ++i;
+            }
+
+        /* If it's a MUX, it means that only 1 channel can be selected
+         * and the other channels are unselected
+         *
+         * For MIXER multiple sources are allowed, so unselect here
+         */
+        if (canone)
+            break;
+
+        for (x = i = 0; x<mmixer->chans; ++x)
+            if (line != x && mmixer->lines[x].dst == line)
+            {
+                if (!mcdb[i].fValue)
+                    for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
+                    {
+                        if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
+                            continue;
+                        snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
+                    }
+                ++i;
+            }
+        break;
+    }
+    default:
+        FIXME("Unhandled type %s\n", getControlType(ct->c.dwControlType));
+        return MMSYSERR_INVALPARAM;
+    }
+    return MMSYSERR_NOERROR;
+}
+
+/* Here we give info over the source/dest line given by dwSource+dwDest or dwDest, respectively
+ * It is also possible that a line is found by componenttype or target type, latter is not implemented yet
+ * Most important values returned in struct:
+ * dwLineID
+ * sz(Short)Name
+ * line control count
+ * amount of channels
+ */
+static DWORD MIX_GetLineInfo(UINT wDevID, LPMIXERLINEW Ml, DWORD_PTR flags)
+{
+    DWORD_PTR qf = flags & MIXER_GETLINEINFOF_QUERYMASK;
+    mixer *mmixer = MIX_GetMix(wDevID);
+    line *mline;
+    int idx, i;
+
+    if (!Ml)
+    {
+        WARN("No Ml\n");
+        return MMSYSERR_INVALPARAM;
+    }
+
+    if (!mmixer)
+    {
+        WARN("Device %u not found\n", wDevID);
+        return MMSYSERR_BADDEVICEID;
+    }
+
+    if (Ml->cbStruct != sizeof(*Ml))
+    {
+        WARN("invalid parameter: Ml->cbStruct = %d != %d\n", Ml->cbStruct, sizeof(*Ml));
+        return MMSYSERR_INVALPARAM;
+    }
+
+    Ml->fdwLine = MIXERLINE_LINEF_ACTIVE;
+    Ml->dwUser  = 0;
+
+    switch (qf)
+    {
+    case MIXER_GETLINEINFOF_COMPONENTTYPE:
+    {
+        Ml->dwLineID = 0xFFFF;
+        for (idx = 0; idx < mmixer->chans; ++idx)
+            if (mmixer->lines[idx].component == Ml->dwComponentType)
+            {
+                Ml->dwLineID = idx;
+                break;
+            }
+        if (Ml->dwLineID == 0xFFFF)
+            return MMSYSERR_KEYNOTFOUND;
+        /* Now that we have lineid, fallback to lineid*/
+    }
+
+    case MIXER_GETLINEINFOF_LINEID:
+        if (Ml->dwLineID < 0 || Ml->dwLineID >= mmixer->chans)
+            return MIXERR_INVALLINE;
+
+        TRACE("MIXER_GETLINEINFOF_LINEID %d\n", Ml->dwLineID);
+        Ml->dwDestination = mmixer->lines[Ml->dwLineID].dst;
+
+        if (Ml->dwDestination != Ml->dwLineID)
+        {
+            Ml->dwSource = getsrcfromline(mmixer, Ml->dwLineID);
+            Ml->cConnections = 1;
+        }
+        else
+        {
+            Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
+            Ml->dwSource = 0xFFFFFFFF;
+        }
+        TRACE("Connections %d, source %d\n", Ml->cConnections, Ml->dwSource);
+        break;
+
+    case MIXER_GETLINEINFOF_DESTINATION:
+        if (Ml->dwDestination < 0 || Ml->dwDestination >= mmixer->dests)
+        {
+            WARN("dest %d out of bounds\n", Ml->dwDestination);
+            return MIXERR_INVALLINE;
+        }
+
+        Ml->dwLineID = Ml->dwDestination;
+        Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
+        Ml->dwSource = 0xFFFFFFFF;
+        break;
+
+    case MIXER_GETLINEINFOF_SOURCE:
+        if (Ml->dwDestination < 0 || Ml->dwDestination >= mmixer->dests)
+        {
+            WARN("dest %d for source out of bounds\n", Ml->dwDestination);
+            return MIXERR_INVALLINE;
+        }
+
+        if (Ml->dwSource < 0 || Ml->dwSource >= getsrccntfromchan(mmixer, Ml->dwDestination))
+        {
+            WARN("src %d out of bounds\n", Ml->dwSource);
+            return MIXERR_INVALLINE;
+        }
+
+        Ml->dwLineID = getsrclinefromchan(mmixer, Ml->dwDestination, Ml->dwSource);
+        Ml->cConnections = 1;
+        break;
+
+    case MIXER_GETLINEINFOF_TARGETTYPE:
+        FIXME("TODO: TARGETTYPE, stub\n");
+        return MMSYSERR_INVALPARAM;
+
+    default:
+        FIXME("Unknown query flag: %08lx\n", qf);
+        return MMSYSERR_INVALPARAM;
+    }
+
+    if (Ml->dwLineID >= mmixer->dests)
+        Ml->fdwLine |= MIXERLINE_LINEF_SOURCE;
+
+    mline = &mmixer->lines[Ml->dwLineID];
+    Ml->dwComponentType = mline->component;
+    Ml->cChannels = mmixer->lines[Ml->dwLineID].chans;
+    Ml->cControls = 0;
+
+    for (i=CONTROLSPERLINE*Ml->dwLineID;i<CONTROLSPERLINE*(Ml->dwLineID+1); ++i)
+        if (mmixer->controls[i].enabled)
+            ++(Ml->cControls);
+
+    lstrcpynW(Ml->szShortName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szShortName)/sizeof(WCHAR));
+    lstrcpynW(Ml->szName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szName)/sizeof(WCHAR));
+    if (mline->capt)
+        Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
+    else
+        Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
+    Ml->Target.dwDeviceID = 0xFFFFFFFF;
+    Ml->Target.wMid = WINE_MIXER_MANUF_ID;
+    Ml->Target.wPid = WINE_MIXER_PRODUCT_ID;
+    Ml->Target.vDriverVersion = WINE_MIXER_VERSION;
+    lstrcpynW(Ml->Target.szPname, mmixer->mixername, sizeof(Ml->Target.szPname)/sizeof(WCHAR));
+    return MMSYSERR_NOERROR;
+}
+
+/* Get the controls that belong to a certain line, either all or 1 */
+static DWORD MIX_GetLineControls(UINT wDevID, LPMIXERLINECONTROLSW mlc, DWORD_PTR flags)
+{
+    mixer *mmixer = MIX_GetMix(wDevID);
+    int i,j = 0;
+    DWORD ct;
+
+    if (!mlc || mlc->cbStruct != sizeof(*mlc))
+    {
+        WARN("Invalid mlc %p, cbStruct: %d instead of %d\n", mlc, (!mlc ? -1 : mlc->cbStruct), sizeof(*mlc));
+        return MMSYSERR_INVALPARAM;
+    }
+
+    if (mlc->cbmxctrl != sizeof(MIXERCONTROLW))
+    {
+        WARN("cbmxctrl %d instead of %d\n", mlc->cbmxctrl, sizeof(MIXERCONTROLW));
+        return MMSYSERR_INVALPARAM;
+    }
+
+    if (!mmixer)
+        return MMSYSERR_BADDEVICEID;
+
+    flags &= MIXER_GETLINECONTROLSF_QUERYMASK;
+
+    if (flags == MIXER_GETLINECONTROLSF_ONEBYID)
+        mlc->dwLineID = mlc->u.dwControlID / CONTROLSPERLINE;
+
+    if (mlc->dwLineID < 0 || mlc->dwLineID >= mmixer->chans)
+    {
+        TRACE("Invalid dwLineID %d\n", mlc->dwLineID);
+        return MIXERR_INVALLINE;
+    }
+
+    switch (flags)
+    {
+    case MIXER_GETLINECONTROLSF_ALL:
+       TRACE("line=%08x MIXER_GETLINECONTROLSF_ALL (%d)\n", mlc->dwLineID, mlc->cControls);
+       for (i = 0; i < CONTROLSPERLINE; ++i)
+           if (mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].enabled)
+           {
+               memcpy(&mlc->pamxctrl[j], &mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].c, sizeof(MIXERCONTROLW));
+               TRACE("Added %s (%s)\n", debugstr_w(mlc->pamxctrl[j].szShortName), debugstr_w(mlc->pamxctrl[j].szName));
+               ++j;
+               if (j > mlc->cControls)
+               {
+                   WARN("invalid parameter\n");
+                   return MMSYSERR_INVALPARAM;
+               }
+           }
+
+        if (!j || mlc->cControls > j)
+        {
+            WARN("invalid parameter\n");
+            return MMSYSERR_INVALPARAM;
+        }
+        break;
+    case MIXER_GETLINECONTROLSF_ONEBYID:
+        TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYID (%x)\n", mlc->dwLineID, mlc->u.dwControlID);
+
+        if (!mmixer->controls[mlc->u.dwControlID].enabled)
+           return MIXERR_INVALCONTROL;
+
+        mlc->pamxctrl[0] = mmixer->controls[mlc->u.dwControlID].c;
+        break;
+    case MIXER_GETLINECONTROLSF_ONEBYTYPE:
+        TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", mlc->dwLineID, getControlType(mlc->u.dwControlType));
+
+        ct = mlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK;
+        for (i = 0; i <= CONTROLSPERLINE; ++i)
+        {
+            const int ofs = i+mlc->dwLineID*CONTROLSPERLINE;
+            if (i == CONTROLSPERLINE)
+            {
+                WARN("invalid parameter: control %s not found\n", getControlType(mlc->u.dwControlType));
+                return MIXERR_INVALCONTROL;
+            }
+            if (mmixer->controls[ofs].enabled && (mmixer->controls[ofs].c.dwControlType & MIXERCONTROL_CT_CLASS_MASK) == ct)
+            {
+                mlc->pamxctrl[0] = mmixer->controls[ofs].c;
+                break;
+            }
+        }
+    break;
+    default:
+        FIXME("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
+        return MMSYSERR_INVALPARAM;
+    }
+
+    return MMSYSERR_NOERROR;
+}
+
+#endif
+
+/**************************************************************************
+ *                        mxdMessage (WINEALSA.3)
+ */
+DWORD WINAPI ALSA_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
+                             DWORD_PTR dwParam1, DWORD_PTR dwParam2)
+{
+#ifdef HAVE_ALSA
+    DWORD ret;
+    TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
+          dwUser, dwParam1, dwParam2);
+
+    switch (wMsg)
+    {
+    /* All taken care of by driver initialisation */
+    /* Unimplemented, and not needed */
+    case DRVM_INIT:
+    case DRVM_EXIT:
+    case DRVM_ENABLE:
+    case DRVM_DISABLE:
+        ret = MMSYSERR_NOERROR; break;
+
+    case MXDM_OPEN:
+        ret = MIX_Open(wDevID, (LPMIXEROPENDESC) dwParam1, dwParam2); break;
+
+    case MXDM_CLOSE:
+        ret = MIX_Close(wDevID); break;
+
+    case MXDM_GETDEVCAPS:
+        ret = MIX_GetDevCaps(wDevID, (LPMIXERCAPS2W)dwParam1, dwParam2); break;
+
+    case MXDM_GETNUMDEVS:
+        ret = cards; break;
+
+    case MXDM_GETLINEINFO:
+        ret = MIX_GetLineInfo(wDevID, (LPMIXERLINEW)dwParam1, dwParam2); break;
+
+    case MXDM_GETLINECONTROLS:
+        ret = MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSW)dwParam1, dwParam2); break;
+
+    case MXDM_GETCONTROLDETAILS:
+        ret = MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
+
+    case MXDM_SETCONTROLDETAILS:
+        ret = MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
+
+    default:
+        WARN("unknown message %s!\n", getMessage(wMsg));
+        return MMSYSERR_NOTSUPPORTED;
+    }
+
+    TRACE("Returning %08X\n", ret);
+    return ret;
+#else /*HAVE_ALSA*/
+    TRACE("(%04X, %04X, %08X, %08X, %08X);\n", wDevID, wMsg,
+          dwUser, dwParam1, dwParam2);
+
+    return MMSYSERR_NOTENABLED;
+#endif /*HAVE_ALSA*/
+}
diff --git a/dlls/winealsa.drv/winealsa.drv.spec b/dlls/winealsa.drv/winealsa.drv.spec
index 766c2fc..bf883b4 100644
--- a/dlls/winealsa.drv/winealsa.drv.spec
+++ b/dlls/winealsa.drv/winealsa.drv.spec
@@ -1,5 +1,6 @@
 @ stdcall -private DriverProc(long long long long long) ALSA_DriverProc
 @ stdcall -private midMessage(long long long long long) ALSA_midMessage
 @ stdcall -private modMessage(long long long long long) ALSA_modMessage
+@ stdcall -private mxdMessage(long long long long long) ALSA_mxdMessage
 @ stdcall -private widMessage(long long long long long) ALSA_widMessage
 @ stdcall -private wodMessage(long long long long long) ALSA_wodMessage
-- 
1.4.4.2



More information about the wine-patches mailing list