| /* |
| * 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 "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); |
| |
| #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; 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 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, *lineelem = 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; |
| else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Line") && !lineelem) |
| lineelem = 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 (lineelem && !mastelem) |
| { |
| /* Use 'Line' as master device */ |
| mastelem = lineelem; |
| 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; 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); |
| |
| 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; 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 >= 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; 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 = 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; 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", 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; 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\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;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\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; |
| } |
| |
| /************************************************************************** |
| * mxdMessage (WINEALSA.3) |
| */ |
| DWORD WINAPI ALSA_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser, |
| DWORD_PTR dwParam1, DWORD_PTR dwParam2) |
| { |
| 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; |
| } |