/* * 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 #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include #include #ifdef HAVE_SYS_IOCTL_H # include #endif #define NONAMELESSUNION #define NONAMELESSSTRUCT #include "windef.h" #include "winbase.h" #include "wingdi.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 whether 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) { #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 return wine_dbg_sprintf("UNKNOWN(%08x)", uMsg); } static const char * getControlType(DWORD dwControlType) { #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 return wine_dbg_sprintf("UNKNOWN(%08x)", dwControlType); } /* 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]; 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 int elem_callback(snd_mixer_elem_t *elem, unsigned int mask); static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam); static CRITICAL_SECTION elem_crst; static int msg_pipe[2]; static LONG refcnt; /* 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, }, { "Digital", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, }, { "Front Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, }, }; /* 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)) 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; TRACE("Filling control %d\n", id); if (!mline->elem) break; if (id == 1 && !mline->elem) continue; 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; xchans; ++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; xcontrols[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 separated, * 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 void filllines(mixer *mmixer, snd_mixer_elem_t *mastelem, snd_mixer_elem_t *captelem, int capt) { snd_mixer_elem_t *elem; line *mline = mmixer->lines; if (mastelem) { /* Master control */ MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR)); mline->component = getcomponenttype(snd_mixer_selem_get_name(mastelem)); mline->dst = 0; mline->capt = 0; mline->elem = mastelem; mline->chans = chans(mmixer, mastelem, 0); snd_mixer_elem_set_callback(mastelem, &elem_callback); snd_mixer_elem_set_callback_private(mastelem, mmixer); } else { MultiByteToWideChar(CP_UNIXCP, 0, "Empty Master Element", -1, mline->name, sizeof(mline->name)/sizeof(WCHAR)); } /* 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 */ /* Control 1 is reserved for capture even when not enabled */ ++mline; if (capt) { MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR)); mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem)); mline->dst = 1; mline->capt = 1; mline->elem = captelem; mline->chans = chans(mmixer, captelem, 1); snd_mixer_elem_set_callback(captelem, &elem_callback); snd_mixer_elem_set_callback_private(captelem, mmixer); } for (elem = snd_mixer_first_elem(mmixer->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); if (snd_mixer_selem_has_playback_volume(elem) && (snd_mixer_selem_has_capture_volume(elem) || comp != MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)) { (++mline)->component = comp; MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN); mline->capt = mline->dst = 0; mline->elem = elem; mline->chans = chans(mmixer, elem, 0); } else if (!capt) continue; if (capt && (snd_mixer_selem_has_capture_volume(elem) || comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)) { (++mline)->component = comp; MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN); mline->capt = mline->dst = 1; mline->elem = elem; mline->chans = chans(mmixer, elem, 1); } snd_mixer_elem_set_callback(elem, &elem_callback); snd_mixer_elem_set_callback_private(elem, mmixer); } } static void filllines_no_master(mixer *mmixer, snd_mixer_elem_t *captelem, int capt) { line *mline = mmixer->lines; MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR)); mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem)); mline->dst = 0; mline->capt = 1; mline->elem = captelem; mline->chans = chans(mmixer, captelem, 1); snd_mixer_elem_set_callback(captelem, &elem_callback); snd_mixer_elem_set_callback_private(captelem, mmixer); } /* Windows api wants to have a 'master' device to which all slaves are attached * There are 2 ones in this code: * - 'Master', fall back to 'Headphone' if unavailable, and if that's not available 'PCM' * - 'Capture' * Capture might not always be available, so should be prepared to be without if needed */ static void ALSA_MixerInit(void) { int x, mixnum = 0; snd_ctl_card_info_t *info; info = HeapAlloc( GetProcessHeap(), 0, snd_ctl_card_info_sizeof()); for (x = 0; x < MAX_MIXERS; ++x) { int card, err, capcontrols = 0, total_elems = 0; char cardind[6], cardname[10]; snd_ctl_t *ctl; snd_mixer_elem_t *elem, *mastelem = NULL, *headelem = NULL, *captelem = NULL, *pcmelem = NULL, *micelem = NULL; memset(info, 0, snd_ctl_card_info_sizeof()); memset(&mixdev[mixnum], 0, sizeof(*mixdev)); snprintf(cardind, sizeof(cardind), "%d", x); card = snd_card_get_index(cardind); if (card < 0) continue; snprintf(cardname, sizeof(cardname), "hw:%d", card); err = snd_ctl_open(&ctl, 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 occurred opening mixer: %s\n", snd_strerror(err)); continue; } err = snd_mixer_attach(mixdev[mixnum].mix, 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; /* First, lets see what's available.. * If there are multiple Master or Captures, all except 1 will be added as slaves */ total_elems = snd_mixer_get_count(mixdev[mixnum].mix); TRACE("Total elems: %d\n", total_elems); 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) { mastelem = elem; ++(mixdev[mixnum].chans); } else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture") && !captelem) captelem = elem; else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Mic") && !micelem && !mastelem && total_elems == 1) /* this is what snd-usb-audio mics look like; just a Mic control and that's it.*/ micelem = elem; else if (!blacklisted(elem)) { DWORD comp = getcomponenttype(snd_mixer_selem_get_name(elem)); DWORD skip = 0; /* Work around buggy drivers: Make this a capture control if the name is recognised as a microphone */ if (snd_mixer_selem_has_capture_volume(elem)) ++capcontrols; else if (comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE) { ++capcontrols; skip = 1; } if (!skip && snd_mixer_selem_has_playback_volume(elem)) { if (!strcasecmp(snd_mixer_selem_get_name(elem), "Headphone") && !headelem) headelem = elem; else if (!strcasecmp(snd_mixer_selem_get_name(elem), "PCM") && !pcmelem) pcmelem = elem; ++(mixdev[mixnum].chans); } } /* Add dummy capture channel, wanted by Windows */ mixdev[mixnum].chans += 1; /* If there is only 'Capture' and 'Master', this device is not worth it */ if (mixdev[mixnum].chans == 2) { WARN("No channels found, skipping device!\n"); goto close; } /* Master element can't have a capture control in this code, so * if Headphone or PCM is promoted to master, unset its capture control */ if (headelem && !mastelem) { /* Using 'Headphone' as master device */ mastelem = headelem; capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem); } else if (pcmelem && !mastelem) { /* Use 'PCM' as master device */ mastelem = pcmelem; capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem); } else if (!mastelem && !captelem && !micelem) { /* If there is nothing sensible that can act as 'Master' control, something is wrong */ FIXME("No master control found on %s, disabling mixer\n", snd_ctl_card_info_get_name(info)); goto close; } if (!captelem || !capcontrols) { /* Can't enable capture, so disabling it * Note: capture control will still exist because * dwLineID 0 and 1 are reserved for Master and Capture */ WARN("No use enabling capture part of mixer, capture control found: %s, amount of capture controls: %d\n", (!captelem ? "no" : "yes"), capcontrols); capcontrols = 0; mixdev[mixnum].dests = 1; } else { mixdev[mixnum].chans += capcontrols; mixdev[mixnum].dests = 2; } mixdev[mixnum].lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(line) * mixdev[mixnum].chans); mixdev[mixnum].controls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(control) * CONTROLSPERLINE*mixdev[mixnum].chans); err = -ENOMEM; if (!mixdev[mixnum].lines || !mixdev[mixnum].controls) goto close; if (mastelem) filllines(&mixdev[mixnum], mastelem, captelem, capcontrols); else if (micelem) filllines_no_master(&mixdev[mixnum], micelem, 1); fillcontrols(&mixdev[mixnum]); TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname, mixdev[mixnum].dests, mixdev[mixnum].chans, debugstr_w(mixdev[mixnum].mixername)); mixnum++; continue; eclose: WARN("Error occurred initialising mixer: %s\n", snd_strerror(err)); close: HeapFree(GetProcessHeap(), 0, mixdev[mixnum].lines); HeapFree(GetProcessHeap(), 0, mixdev[mixnum].controls); snd_mixer_close(mixdev[mixnum].mix); } cards = mixnum; HeapFree( GetProcessHeap(), 0, info ); /* There is no trouble with already assigning callbacks without initialising critsect: * Callbacks only occur when snd_mixer_handle_events is called (only happens in thread) */ InitializeCriticalSection(&elem_crst); elem_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_MIXER.elem_crst"); TRACE("\n"); } static void ALSA_MixerExit(void) { int x; if (refcnt) { WARN("Callback thread still alive, terminating uncleanly, refcnt: %d\n", refcnt); /* Least we can do is making sure we're not in 'foreign' code */ EnterCriticalSection(&elem_crst); TerminateThread(thread, 1); refcnt = 0; LeaveCriticalSection(&elem_crst); } TRACE("Cleaning up\n"); elem_crst.DebugInfo->Spare[0] = 0; DeleteCriticalSection(&elem_crst); for (x = 0; x < cards; ++x) { snd_mixer_close(mixdev[x].mix); HeapFree(GetProcessHeap(), 0, mixdev[x].lines); HeapFree(GetProcessHeap(), 0, mixdev[x].controls); } cards = 0; } static mixer* MIX_GetMix(UINT wDevID) { mixer *mmixer; if (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); EnterCriticalSection(&elem_crst); if (!mmixer->callback) goto out; for (x=0; xchans; ++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); out: LeaveCriticalSection(&elem_crst); return 0; } static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam) { struct pollfd *pfds = NULL; int x, y, err, mcnt, count = 1; 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 = HeapAlloc(GetProcessHeap(), 0, count * sizeof(struct pollfd)); if (!pfds) { WARN("Out of memory\n"); goto die; } pfds[0].fd = msg_pipe[0]; pfds[0].events = POLLIN; y = 1; for (x = 0; x < cards; ++x) y += snd_mixer_poll_descriptors(mixdev[x].mix, &pfds[y], count - y); while ((err = poll(pfds, (unsigned int) count, -1)) >= 0 || errno == EINTR || errno == EAGAIN) { if (pfds[0].revents & POLLIN) break; mcnt = 1; for (x = y = 0; x < cards; ++x) { int j, max = snd_mixer_poll_descriptors_count(mixdev[x].mix); for (j = 0; j < max; ++j) if (pfds[mcnt+j].revents) { y += snd_mixer_handle_events(mixdev[x].mix); break; } mcnt += max; } if (y) TRACE("Handled %d events\n", y); } die: TRACE("Shutting down\n"); HeapFree(GetProcessHeap(), 0, pfds); y = read(msg_pipe[0], &x, sizeof(x)); close(msg_pipe[1]); close(msg_pipe[0]); return 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: goto done; 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; done: if (InterlockedIncrement(&refcnt) == 1) { if (pipe(msg_pipe) >= 0) { thread = CreateThread(NULL, 0, ALSA_MixerPollThread, NULL, 0, NULL); if (!thread) { close(msg_pipe[0]); close(msg_pipe[1]); msg_pipe[0] = msg_pipe[1] = -1; } } else msg_pipe[0] = msg_pipe[1] = -1; } return MMSYSERR_NOERROR; } static DWORD MIX_Close(UINT wDevID) { int x = 0; mixer *mmixer = MIX_GetMix(wDevID); if (!mmixer) return MMSYSERR_BADDEVICEID; EnterCriticalSection(&elem_crst); mmixer->callback = 0; LeaveCriticalSection(&elem_crst); if (!InterlockedDecrement(&refcnt)) { if (write(msg_pipe[1], &x, sizeof(x)) > 0) { TRACE("Shutting down thread...\n"); WaitForSingleObject(thread, INFINITE); TRACE("Done\n"); } } 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 INT normalized(INT value, INT prevmax, INT nextmax) { int ret = MulDiv(value, nextmax, prevmax); /* Have to stay in range */ TRACE("%d/%d -> %d/%d\n", value, prevmax, ret, nextmax); if (ret > nextmax) ret = nextmax; else if (ret < 0) ret = 0; return ret; } /* get amount of sources for dest */ static int getsrccntfromchan(mixer *mmixer, int dad) { int i, j=0; for (i=0; ichans; ++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; ichans; ++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; ichans; ++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 >= 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", mctrld->cbDetails); return MMSYSERR_INVALPARAM; } TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels); mcdu = 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", mctrld->cbDetails); return MMSYSERR_INVALPARAM; } TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels); mcdb = 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; } if (chn > SND_MIXER_SCHN_LAST) { TRACE("can't find active channel\n"); return MMSYSERR_INVALPARAM; /* fixme: what's right error? */ } 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", mctrld->cbDetails); return MMSYSERR_INVALPARAM; } TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels); mcdb = mctrld->paDetails; for (x = 0; xchans; ++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 = 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\n", mctrld->cbStruct); return MMSYSERR_INVALPARAM; } if (!mmixer) return MMSYSERR_BADDEVICEID; if (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", mctrld->cbDetails); 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 = mctrld->paDetails; for (chn=0; chncChannels;++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", mctrld->cbDetails); return MMSYSERR_INVALPARAM; } TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels); mcdb = 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", mctrld->cbDetails); return MMSYSERR_INVALPARAM; } TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels); mcdb = 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; xchans; ++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; xchans; ++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\n", Ml->cbStruct); return MMSYSERR_INVALPARAM; } Ml->dwUser = 0; Ml->fdwLine = MIXERLINE_LINEF_DISCONNECTED; switch (qf) { case MIXER_GETLINEINFOF_COMPONENTTYPE: { Ml->dwLineID = 0xFFFF; TRACE("Looking for componenttype %d/%x\n", Ml->dwComponentType, Ml->dwComponentType); 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 >= 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 >= 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 >= mmixer->dests) { WARN("dest %d for source out of bounds\n", Ml->dwDestination); return MIXERR_INVALLINE; } if (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; } Ml->fdwLine &= ~MIXERLINE_LINEF_DISCONNECTED; Ml->fdwLine |= MIXERLINE_LINEF_ACTIVE; 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;idwLineID+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\n", mlc, (!mlc ? -1 : mlc->cbStruct)); return MMSYSERR_INVALPARAM; } if (mlc->cbmxctrl != sizeof(MIXERCONTROLW)) { WARN("cbmxctrl %d\n", mlc->cbmxctrl); 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 >= 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 /*HAVE_ALSA*/ /************************************************************************** * 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) { case DRVM_INIT: ALSA_MixerInit(); ret = MMSYSERR_NOERROR; break; case DRVM_EXIT: ALSA_MixerExit(); ret = MMSYSERR_NOERROR; break; /* All taken care of by driver initialisation */ /* Unimplemented, and not needed */ 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_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; case MXDM_GETNUMDEVS: ret = cards; 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, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2); return MMSYSERR_NOTENABLED; #endif /*HAVE_ALSA*/ }