[winealsa] add support for mixer devices
Maarten Lankhorst
m.b.lankhorst at gmail.com
Wed Apr 11 13:20:25 CDT 2007
Add support for mixer devices in alsa. If this patch is dropped please
elaborate why. It depends on the CALLBACK_WINDOW patch.
-------------- next part --------------
>From 02932b87f9da036503b090631cc54f8f3676e989 Mon Sep 17 00:00:00 2001
From: maarten lankhorst <m.b.lankhorst at gmail.com>
Date: Wed, 11 Apr 2007 20:16:30 +0200
Subject: [PATCH] [winealsa] add support for mixer devices
---
dlls/winealsa.drv/Makefile.in | 1 +
dlls/winealsa.drv/alsa.c | 4 +-
dlls/winealsa.drv/alsa.h | 4 +
dlls/winealsa.drv/mixer.c | 1460 +++++++++++++++++++++++++++++++++++
dlls/winealsa.drv/winealsa.drv.spec | 1 +
5 files changed, 1469 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..731135c 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_MixerClose(void);
+
#endif /* __ALSA_H */
diff --git a/dlls/winealsa.drv/mixer.c b/dlls/winealsa.drv/mixer.c
new file mode 100644
index 0000000..57bb98d
--- /dev/null
+++ b/dlls/winealsa.drv/mixer.c
@@ -0,0 +1,1460 @@
+/*
+ * 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
+ */
+
+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;
+
+typedef void CALLBACK (*CB)(HMIXEROBJ, UINT, DWORD, DWORD);
+
+/* Mixer device */
+typedef struct mixer
+{
+ snd_mixer_t *mix;
+ WCHAR mixername[MAXPNAMELEN];
+ char cardname[10];
+
+ int chans, dests;
+ CB callback;
+ DWORD_PTR callbackpriv;
+ HMIXEROBJ 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_MixerClose(void)
+{
+ int x;
+ TRACE("\n");
+ EnterCriticalSection(&crst);
+ crst.DebugInfo->Spare[0] = 0;
+ 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);
+ mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs);
+
+ if (mmixer->controls[ofs+OFS_MUTE].enabled)
+ mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs+OFS_MUTE);
+ }
+ if (captchanged)
+ mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, CONTROLSPERLINE+OFS_MUX);
+
+ return 0;
+}
+
+static DWORD WINAPI callback_thread(LPVOID lParam)
+{
+ struct pollfd *pfds = NULL;
+ int x, i, y, err, count = 0;
+ TRACE("%p\n", lParam);
+
+ for (x = 0; x < cards; ++x)
+ count += snd_mixer_poll_descriptors_count(mixdev[x].mix);
+
+ TRACE("Counted %d descriptors\n", count);
+ pfds = calloc(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;
+ }
+
+ /* Ignore EINTR, happens when wine is suspended then resumed */
+ while ((err = poll(pfds, (unsigned int) count, 10)) >= 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);
+ }
+ free(pfds);
+ TRACE("Thread exited\n");
+
+ ExitThread(0);
+}
+
+static DWORD MIX_Open(UINT wDevID, LPMIXEROPENDESC desc, DWORD 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 = (CB)desc->dwCallback;
+ mmixer->callbackpriv = desc->dwInstance;
+ mmixer->hmx = 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 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 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 (%08x)\n", flags);
+ return MMSYSERR_INVALPARAM;
+ }
+}
+
+/* Set volume/capture channel/muted for control */
+static DWORD MIX_SetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD 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 (%08x)\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 flags)
+{
+ DWORD 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: %08x\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 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 dwUser,
+ DWORD dwParam1, DWORD dwParam2)
+{
+#ifdef HAVE_ALSA
+ DWORD ret;
+ TRACE("(%04X, %s, %08X, %08X, %08X);\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