Implement A->W call for GetNamedSecurityInfo.
[wine] / dlls / winmm / wineoss / mixer.c
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
2
3 /*
4  * Sample MIXER Wine Driver for Linux
5  *
6  * Copyright    1997 Marcus Meissner
7  *              1999,2001 Eric Pouech
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23
24 #include "config.h"
25
26 #include <stdlib.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <string.h>
30 #ifdef HAVE_UNISTD_H
31 # include <unistd.h>
32 #endif
33 #include <fcntl.h>
34 #include <errno.h>
35 #include <assert.h>
36 #ifdef HAVE_SYS_IOCTL_H
37 # include <sys/ioctl.h>
38 #endif
39
40 #define NONAMELESSUNION
41 #define NONAMELESSSTRUCT
42 #include "windef.h"
43 #include "winbase.h"
44 #include "mmddk.h"
45 #include "oss.h"
46 #include "wine/debug.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(mmaux);
49
50 #ifdef HAVE_OSS
51
52 #define WINE_MIXER_MANUF_ID             0xAA
53 #define WINE_MIXER_PRODUCT_ID           0x55
54 #define WINE_MIXER_VERSION              0x0100
55 #define WINE_MIXER_NAME                 "WINE OSS Mixer"
56
57 #define WINE_CHN_MASK(_x)               (1L << (_x))
58 #define WINE_CHN_SUPPORTS(_c, _x)       ((_c) & WINE_CHN_MASK(_x))
59 /* Bass and Treble are no longer in the mask as Windows does not handle them */
60 #define WINE_MIXER_MASK_SPEAKER         (WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
61                                          WINE_CHN_MASK(SOUND_MIXER_PCM)    | \
62                                          WINE_CHN_MASK(SOUND_MIXER_LINE)   | \
63                                          WINE_CHN_MASK(SOUND_MIXER_MIC)    | \
64                                          WINE_CHN_MASK(SOUND_MIXER_CD)     )
65
66 #define WINE_MIXER_MASK_RECORD          (WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
67                                          WINE_CHN_MASK(SOUND_MIXER_LINE)   | \
68                                          WINE_CHN_MASK(SOUND_MIXER_MIC)    | \
69                                          WINE_CHN_MASK(SOUND_MIXER_IMIX)   )
70
71 /* FIXME: the two following string arrays should be moved to a resource file in a string table */
72 /* if it's done, better use a struct to hold labels, name, and muted channel volume cache */
73 static const char*      MIX_Labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
74 static const char*      MIX_Names [SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
75
76 struct mixerCtrl
77 {
78     DWORD               dwLineID;
79     MIXERCONTROLA       ctrl;
80 };
81
82 struct mixer
83 {
84     const char*         name;
85     int                 volume[SOUND_MIXER_NRDEVICES];
86     int                 devMask;
87     int                 stereoMask;
88     int                 recMask;
89     BOOL                singleRecChannel;
90     struct mixerCtrl*   ctrl;
91     int                 numCtrl;
92 };
93
94 #define LINEID_DST      0xFFFF
95 #define LINEID_SPEAKER  0x0000
96 #define LINEID_RECORD   0x0001
97
98 static int              MIX_NumMixers;
99 static struct mixer     MIX_Mixers[1];
100
101 /**************************************************************************
102  *                              MIX_FillLineControls            [internal]
103  */
104 static void MIX_FillLineControls(struct mixer* mix, int c, DWORD lineID, DWORD dwType)
105 {
106     struct mixerCtrl*   mc = &mix->ctrl[c];
107     int                 j;
108
109     mc->dwLineID = lineID;
110     mc->ctrl.cbStruct = sizeof(MIXERCONTROLA);
111     mc->ctrl.dwControlID = c + 1;
112     mc->ctrl.dwControlType = dwType;
113
114     switch (dwType)
115     {
116     case MIXERCONTROL_CONTROLTYPE_VOLUME:
117         mc->ctrl.fdwControl = 0;
118         mc->ctrl.cMultipleItems = 0;
119         lstrcpynA(mc->ctrl.szShortName, "Vol", MIXER_SHORT_NAME_CHARS);
120         lstrcpynA(mc->ctrl.szName, "Volume", MIXER_LONG_NAME_CHARS);
121         memset(&mc->ctrl.Bounds, 0, sizeof(mc->ctrl.Bounds));
122         /* CONTROLTYPE_VOLUME uses the MIXER_CONTROLDETAILS_UNSIGNED struct,
123          * [0, 100] is the range supported by OSS
124          * whatever the min and max values are they must match
125          * conversions done in (Get|Set)ControlDetails to stay in [0, 100] range
126          */
127         mc->ctrl.Bounds.s1.dwMinimum = 0;
128         mc->ctrl.Bounds.s1.dwMaximum = 65535;
129         memset(&mc->ctrl.Metrics, 0, sizeof(mc->ctrl.Metrics));
130         break;
131     case MIXERCONTROL_CONTROLTYPE_MUTE:
132     case MIXERCONTROL_CONTROLTYPE_ONOFF:
133         mc->ctrl.fdwControl = 0;
134         mc->ctrl.cMultipleItems = 0;
135         lstrcpynA(mc->ctrl.szShortName, "Mute", MIXER_SHORT_NAME_CHARS);
136         lstrcpynA(mc->ctrl.szName, "Mute", MIXER_LONG_NAME_CHARS);
137         memset(&mc->ctrl.Bounds, 0, sizeof(mc->ctrl.Bounds));
138         mc->ctrl.Bounds.s1.dwMinimum = 0;
139         mc->ctrl.Bounds.s1.dwMaximum = 1;
140         memset(&mc->ctrl.Metrics, 0, sizeof(mc->ctrl.Metrics));
141         break;
142     case MIXERCONTROL_CONTROLTYPE_MUX:
143     case MIXERCONTROL_CONTROLTYPE_MIXER:
144         mc->ctrl.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE;
145         mc->ctrl.cMultipleItems = 0;
146         for (j = 0; j < SOUND_MIXER_NRDEVICES; j++)
147             if (WINE_CHN_SUPPORTS(mix->recMask, j))
148                 mc->ctrl.cMultipleItems++;
149         lstrcpynA(mc->ctrl.szShortName, "Mixer", MIXER_SHORT_NAME_CHARS);
150         lstrcpynA(mc->ctrl.szName, "Mixer", MIXER_LONG_NAME_CHARS);
151         memset(&mc->ctrl.Bounds, 0, sizeof(mc->ctrl.Bounds));
152         memset(&mc->ctrl.Metrics, 0, sizeof(mc->ctrl.Metrics));
153         break;
154
155     default:
156         FIXME("Internal error: unknown type: %08lx\n", dwType);
157     }
158     TRACE("ctrl[%2d]: typ=%08lx lin=%08lx\n", c + 1, dwType, lineID);
159 }
160
161 /******************************************************************
162  *              MIX_GetMixer
163  *
164  *
165  */
166 static struct mixer*    MIX_Get(WORD wDevID)
167 {
168     if (wDevID >= MIX_NumMixers || MIX_Mixers[wDevID].name == NULL) return NULL;
169     return &MIX_Mixers[wDevID];
170 }
171
172 /**************************************************************************
173  *                              MIX_Open                        [internal]
174  */
175 static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD flags)
176 {
177     int                 mixer, i, j;
178     unsigned            caps;
179     struct mixer*       mix;
180     DWORD               ret = MMSYSERR_NOERROR;
181
182     TRACE("(%04X, %p, %lu);\n", wDevID, lpMod, flags);
183
184     /* as we partly init the mixer with MIX_Open, we can allow null open decs */
185     /* EPP     if (lpMod == NULL) return MMSYSERR_INVALPARAM; */
186     /* anyway, it seems that WINMM/MMSYSTEM doesn't always open the mixer device before sending
187      * messages to it... it seems to be linked to all the equivalent of mixer identification
188      * (with a reference to a wave, midi.. handle
189      */
190     if (!(mix = MIX_Get(wDevID))) return MMSYSERR_BADDEVICEID;
191
192     if ((mixer = open(mix->name, O_RDWR)) < 0)
193     {
194         if (errno == ENODEV || errno == ENXIO)
195         {
196             /* no driver present */
197             return MMSYSERR_NODRIVER;
198         }
199         return MMSYSERR_ERROR;
200     }
201
202     if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &mix->devMask) == -1)
203     {
204         perror("ioctl mixer SOUND_MIXER_DEVMASK");
205         ret = MMSYSERR_ERROR;
206         goto error;
207     }
208     mix->devMask &= WINE_MIXER_MASK_SPEAKER;
209     if (mix->devMask == 0)
210     {
211         ret = MMSYSERR_NODRIVER;
212         goto error;
213     }
214
215     if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &mix->stereoMask) == -1)
216     {
217         perror("ioctl mixer SOUND_MIXER_STEREODEVS");
218         ret = MMSYSERR_ERROR;
219         goto error;
220     }
221     mix->stereoMask &= WINE_MIXER_MASK_SPEAKER;
222
223     if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &mix->recMask) == -1)
224     {
225         perror("ioctl mixer SOUND_MIXER_RECMASK");
226         ret = MMSYSERR_ERROR;
227         goto error;
228     }
229     mix->recMask &= WINE_MIXER_MASK_RECORD;
230     /* FIXME: we may need to support both rec lev & igain */
231     if (!WINE_CHN_SUPPORTS(mix->recMask, SOUND_MIXER_RECLEV))
232     {
233         WARN("The sound card doesn't support rec level\n");
234         if (WINE_CHN_SUPPORTS(mix->recMask, SOUND_MIXER_IGAIN))
235             WARN("but it does support IGain, please report\n");
236     }
237     if (ioctl(mixer, SOUND_MIXER_READ_CAPS, &caps) == -1)
238     {
239         perror("ioctl mixer SOUND_MIXER_READ_CAPS");
240         ret = MMSYSERR_ERROR;
241         goto error;
242     }
243     mix->singleRecChannel = caps & SOUND_CAP_EXCL_INPUT;
244     TRACE("dev=%04x rec=%04x stereo=%04x %s\n",
245           mix->devMask, mix->recMask, mix->stereoMask,
246           mix->singleRecChannel ? "single" : "multiple");
247     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
248     {
249         mix->volume[i] = -1;
250     }
251     mix->numCtrl = 4; /* dst lines... vol&mute on speakers, vol&onoff on rec */
252     /* FIXME: do we always have RECLEV on all cards ??? */
253     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
254     {
255         if (WINE_CHN_SUPPORTS(mix->devMask, i))
256             mix->numCtrl += 2; /* volume & mute */
257         if (WINE_CHN_SUPPORTS(mix->recMask, i))
258             mix->numCtrl += 2; /* volume & onoff */
259
260     }
261     if (!(mix->ctrl = HeapAlloc(GetProcessHeap(), 0, sizeof(mix->ctrl[0]) * mix->numCtrl)))
262     {
263         ret = MMSYSERR_NOMEM;
264         goto error;
265     }
266
267     j = 0;
268     MIX_FillLineControls(mix, j++, MAKELONG(0, LINEID_DST), MIXERCONTROL_CONTROLTYPE_VOLUME);
269     MIX_FillLineControls(mix, j++, MAKELONG(0, LINEID_DST), MIXERCONTROL_CONTROLTYPE_MUTE);
270     MIX_FillLineControls(mix, j++, MAKELONG(1, LINEID_DST),
271                          mix->singleRecChannel ?
272                             MIXERCONTROL_CONTROLTYPE_MUX : MIXERCONTROL_CONTROLTYPE_MIXER);
273     MIX_FillLineControls(mix, j++, MAKELONG(1, LINEID_DST), MIXERCONTROL_CONTROLTYPE_MUTE/*EPP*/);
274     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
275     {
276         if (WINE_CHN_SUPPORTS(mix->devMask, i))
277         {
278             MIX_FillLineControls(mix, j++, MAKELONG(LINEID_SPEAKER, i),
279                                  MIXERCONTROL_CONTROLTYPE_VOLUME);
280             MIX_FillLineControls(mix, j++, MAKELONG(LINEID_SPEAKER, i),
281                                  MIXERCONTROL_CONTROLTYPE_MUTE);
282         }
283     }
284     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
285     {
286         if (WINE_CHN_SUPPORTS(mix->recMask, i))
287         {
288             MIX_FillLineControls(mix, j++, MAKELONG(LINEID_RECORD, i),
289                                  MIXERCONTROL_CONTROLTYPE_VOLUME);
290             MIX_FillLineControls(mix, j++, MAKELONG(LINEID_RECORD, i),
291                                  MIXERCONTROL_CONTROLTYPE_MUTE/*EPP*/);
292         }
293     }
294     assert(j == mix->numCtrl);
295  error:
296     close(mixer);
297     return ret;
298 }
299
300 /**************************************************************************
301  *                              MIX_GetVal                      [internal]
302  */
303 static  BOOL    MIX_GetVal(struct mixer* mix, int chn, int* val)
304 {
305     int         mixer;
306     BOOL        ret = FALSE;
307
308     if ((mixer = open(mix->name, O_RDWR)) < 0)
309     {
310         /* FIXME: ENXIO => no mixer installed */
311         WARN("mixer device not available !\n");
312     }
313     else
314     {
315         if (ioctl(mixer, MIXER_READ(chn), val) >= 0)
316         {
317             TRACE("Reading volume %x on %d\n", *val, chn);
318             ret = TRUE;
319         }
320         close(mixer);
321     }
322     return ret;
323 }
324
325 /**************************************************************************
326  *                              MIX_SetVal                      [internal]
327  */
328 static  BOOL    MIX_SetVal(struct mixer* mix, int chn, int val)
329 {
330     int         mixer;
331     BOOL        ret = FALSE;
332
333     TRACE("Writing volume %x on %d\n", val, chn);
334
335     if ((mixer = open(mix->name, O_RDWR)) < 0)
336     {
337         /* FIXME: ENXIO => no mixer installed */
338         WARN("mixer device not available !\n");
339     }
340     else
341     {
342         if (ioctl(mixer, MIXER_WRITE(chn), &val) >= 0)
343         {
344             ret = TRUE;
345         }
346         close(mixer);
347     }
348     return ret;
349 }
350
351 /******************************************************************
352  *              MIX_GetRecSrc
353  *
354  *
355  */
356 static BOOL     MIX_GetRecSrc(struct mixer* mix, unsigned* mask)
357 {
358     int         mixer;
359     BOOL        ret = FALSE;
360
361     if ((mixer = open(mix->name, O_RDWR)) >= 0)
362     {
363         if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &mask) >= 0) ret = TRUE;
364         close(mixer);
365     }
366     return ret;
367 }
368
369 /******************************************************************
370  *              MIX_SetRecSrc
371  *
372  *
373  */
374 static BOOL     MIX_SetRecSrc(struct mixer* mix, unsigned mask)
375 {
376     int         mixer;
377     BOOL        ret = FALSE;
378
379     if ((mixer = open(mix->name, O_RDWR)) >= 0)
380     {
381         if (ioctl(mixer, SOUND_MIXER_WRITE_RECSRC, &mask) < 0)
382         {
383             ERR("Can't write new mixer settings\n");
384         }
385         else
386             ret = TRUE;
387         close(mixer);
388     }
389     return ret;
390 }
391
392 /**************************************************************************
393  *                              MIX_GetDevCaps                  [internal]
394  */
395 static DWORD MIX_GetDevCaps(WORD wDevID, LPMIXERCAPSA lpCaps, DWORD dwSize)
396 {
397     struct mixer*       mix;
398
399     TRACE("(%04X, %p, %lu);\n", wDevID, lpCaps, dwSize);
400
401     if (lpCaps == NULL) return MMSYSERR_INVALPARAM;
402     if (!(mix = MIX_Get(wDevID))) return MMSYSERR_BADDEVICEID;
403
404     lpCaps->wMid = WINE_MIXER_MANUF_ID;
405     lpCaps->wPid = WINE_MIXER_PRODUCT_ID;
406     lpCaps->vDriverVersion = WINE_MIXER_VERSION;
407     strcpy(lpCaps->szPname, WINE_MIXER_NAME);
408
409     lpCaps->cDestinations = 2; /* speakers & record */
410     lpCaps->fdwSupport = 0; /* No bits defined yet */
411
412     return MMSYSERR_NOERROR;
413 }
414
415 /**************************************************************************
416  *                              MIX_GetLineInfoDst      [internal]
417  */
418 static  DWORD   MIX_GetLineInfoDst(struct mixer* mix, LPMIXERLINEA lpMl, DWORD dst)
419 {
420     unsigned mask;
421     int j;
422
423     lpMl->dwDestination = dst;
424     switch (dst)
425     {
426     case 0:
427         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
428         mask = mix->devMask;
429         j = SOUND_MIXER_VOLUME;
430         break;
431     case 1:
432         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
433         mask = mix->recMask;
434         j = SOUND_MIXER_RECLEV;
435         break;
436     default:
437         FIXME("shouldn't happen\n");
438         return MMSYSERR_ERROR;
439     }
440     lpMl->dwSource = 0xFFFFFFFF;
441     lstrcpynA(lpMl->szShortName, MIX_Labels[j], MIXER_SHORT_NAME_CHARS);
442     lstrcpynA(lpMl->szName, MIX_Names[j], MIXER_LONG_NAME_CHARS);
443
444     /* we have all connections found in the MIX_DevMask */
445     lpMl->cConnections = 0;
446     for (j = 0; j < SOUND_MIXER_NRDEVICES; j++)
447         if (WINE_CHN_SUPPORTS(mask, j))
448             lpMl->cConnections++;
449     lpMl->cChannels = 1;
450     if (WINE_CHN_SUPPORTS(mix->stereoMask, lpMl->dwLineID))
451         lpMl->cChannels++;
452     lpMl->dwLineID = MAKELONG(dst, LINEID_DST);
453     lpMl->cControls = 0;
454     for (j = 0; j < mix->numCtrl; j++)
455         if (mix->ctrl[j].dwLineID == lpMl->dwLineID)
456             lpMl->cControls++;
457
458     return MMSYSERR_NOERROR;
459 }
460
461 /**************************************************************************
462  *                              MIX_GetLineInfoSrc      [internal]
463  */
464 static  DWORD   MIX_GetLineInfoSrc(struct mixer* mix, LPMIXERLINEA lpMl, DWORD idx, DWORD dst)
465 {
466     int         i, j;
467     unsigned    mask = (dst) ? mix->recMask : mix->devMask;
468
469     strcpy(lpMl->szShortName, MIX_Labels[idx]);
470     strcpy(lpMl->szName, MIX_Names[idx]);
471     lpMl->dwLineID = MAKELONG(dst, idx);
472     lpMl->dwDestination = dst;
473     lpMl->cConnections = 1;
474     lpMl->cControls = 0;
475     for (i = 0; i < mix->numCtrl; i++)
476         if (mix->ctrl[i].dwLineID == lpMl->dwLineID)
477             lpMl->cControls++;
478
479     switch (idx)
480     {
481     case SOUND_MIXER_SYNTH:
482         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;
483         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
484         break;
485     case SOUND_MIXER_CD:
486         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
487         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
488         break;
489     case SOUND_MIXER_LINE:
490         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_LINE;
491         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
492         break;
493     case SOUND_MIXER_MIC:
494         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
495         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
496         break;
497     case SOUND_MIXER_PCM:
498         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
499         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
500         break;
501     case SOUND_MIXER_IMIX:
502         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
503         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
504         break;
505     default:
506         WARN("Index %ld not handled.\n", idx);
507         return MIXERR_INVALLINE;
508     }
509     lpMl->cChannels = 1;
510     if (dst == 0 && WINE_CHN_SUPPORTS(mix->stereoMask, idx))
511         lpMl->cChannels++;
512     for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
513     {
514         if (WINE_CHN_SUPPORTS(mask, j))
515         {
516             if (j == idx) break;
517             i++;
518         }
519     }
520     lpMl->dwSource = i;
521     return MMSYSERR_NOERROR;
522 }
523
524 /******************************************************************
525  *              MIX_CheckLine
526  */
527 static BOOL MIX_CheckLine(DWORD lineID)
528 {
529     return ((HIWORD(lineID) < SOUND_MIXER_NRDEVICES && LOWORD(lineID) < 2) ||
530             (HIWORD(lineID) == LINEID_DST && LOWORD(lineID) < SOUND_MIXER_NRDEVICES));
531 }
532
533 /**************************************************************************
534  *                              MIX_GetLineInfo                 [internal]
535  */
536 static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEA lpMl, DWORD fdwInfo)
537 {
538     int                 i, j;
539     DWORD               ret = MMSYSERR_NOERROR;
540     unsigned            mask;
541     struct mixer*       mix;
542
543     TRACE("(%04X, %p, %lu);\n", wDevID, lpMl, fdwInfo);
544
545     if (lpMl == NULL || lpMl->cbStruct != sizeof(*lpMl))
546         return MMSYSERR_INVALPARAM;
547     if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
548
549     /* FIXME: set all the variables correctly... the lines below
550      * are very wrong...
551      */
552     lpMl->fdwLine       = MIXERLINE_LINEF_ACTIVE;
553     lpMl->dwUser        = 0;
554
555     switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK)
556     {
557     case MIXER_GETLINEINFOF_DESTINATION:
558         TRACE("DESTINATION (%08lx)\n", lpMl->dwDestination);
559         if (lpMl->dwDestination >= 2)
560             return MMSYSERR_INVALPARAM;
561         if ((ret = MIX_GetLineInfoDst(mix, lpMl, lpMl->dwDestination)) != MMSYSERR_NOERROR)
562             return ret;
563         break;
564     case MIXER_GETLINEINFOF_SOURCE:
565         TRACE("SOURCE (%08lx), dst=%08lx\n", lpMl->dwSource, lpMl->dwDestination);
566         switch (lpMl->dwDestination)
567         {
568         case 0: mask = mix->devMask; break;
569         case 1: mask = mix->recMask; break;
570         default: return MMSYSERR_INVALPARAM;
571         }
572         i = lpMl->dwSource;
573         for (j = 0; j < SOUND_MIXER_NRDEVICES; j++)
574         {
575             if (WINE_CHN_SUPPORTS(mask, j) && (i-- == 0))
576                 break;
577         }
578         if (j >= SOUND_MIXER_NRDEVICES)
579             return MIXERR_INVALLINE;
580         if ((ret = MIX_GetLineInfoSrc(mix, lpMl, j, lpMl->dwDestination)) != MMSYSERR_NOERROR)
581             return ret;
582         break;
583     case MIXER_GETLINEINFOF_LINEID:
584         TRACE("LINEID (%08lx)\n", lpMl->dwLineID);
585
586         if (!MIX_CheckLine(lpMl->dwLineID))
587             return MIXERR_INVALLINE;
588         if (HIWORD(lpMl->dwLineID) == LINEID_DST)
589             ret = MIX_GetLineInfoDst(mix, lpMl, LOWORD(lpMl->dwLineID));
590         else
591             ret = MIX_GetLineInfoSrc(mix, lpMl, HIWORD(lpMl->dwLineID), LOWORD(lpMl->dwLineID));
592         if (ret != MMSYSERR_NOERROR)
593             return ret;
594         break;
595     case MIXER_GETLINEINFOF_COMPONENTTYPE:
596         TRACE("COMPONENT TYPE (%08lx)\n", lpMl->dwComponentType);
597         switch (lpMl->dwComponentType)
598         {
599         case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
600             ret = MIX_GetLineInfoDst(mix, lpMl, 0);
601             break;
602         case MIXERLINE_COMPONENTTYPE_DST_WAVEIN:
603             ret = MIX_GetLineInfoDst(mix, lpMl, 1);
604             break;
605         case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
606             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_SYNTH, 0);
607             break;
608         case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC:
609             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_CD, 0);
610             break;
611         case MIXERLINE_COMPONENTTYPE_SRC_LINE:
612             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_LINE, 0);
613             break;
614         case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE:
615             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_MIC, 1);
616             break;
617         case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
618             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_PCM, 0);
619             break;
620         case MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED:
621             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_IMIX, 1);
622             break;
623         default:
624             FIXME("Unhandled component type (%08lx)\n", lpMl->dwComponentType);
625             return MMSYSERR_INVALPARAM;
626         }
627         break;
628     case MIXER_GETLINEINFOF_TARGETTYPE:
629         FIXME("_TARGETTYPE not implemented yet.\n");
630         break;
631     default:
632         WARN("Unknown flag (%08lx)\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
633         break;
634     }
635
636     lpMl->Target.dwType = MIXERLINE_TARGETTYPE_AUX; /* FIXME */
637     lpMl->Target.dwDeviceID = 0xFFFFFFFF;
638     lpMl->Target.wMid = WINE_MIXER_MANUF_ID;
639     lpMl->Target.wPid = WINE_MIXER_PRODUCT_ID;
640     lpMl->Target.vDriverVersion = WINE_MIXER_VERSION;
641     strcpy(lpMl->Target.szPname, WINE_MIXER_NAME);
642
643     return ret;
644 }
645
646 /******************************************************************
647  *              MIX_CheckControl
648  *
649  */
650 static BOOL     MIX_CheckControl(struct mixer* mix, DWORD ctrlID)
651 {
652     return (ctrlID >= 1 && ctrlID <= mix->numCtrl);
653 }
654
655 /**************************************************************************
656  *                              MIX_GetLineControls             [internal]
657  */
658 static  DWORD   MIX_GetLineControls(WORD wDevID, LPMIXERLINECONTROLSA lpMlc, DWORD flags)
659 {
660     DWORD               dwRet = MMSYSERR_NOERROR;
661     struct mixer*       mix;
662
663     TRACE("(%04X, %p, %lu);\n", wDevID, lpMlc, flags);
664
665     if (lpMlc == NULL) return MMSYSERR_INVALPARAM;
666     if (lpMlc->cbStruct < sizeof(*lpMlc) ||
667         lpMlc->cbmxctrl < sizeof(MIXERCONTROLA))
668         return MMSYSERR_INVALPARAM;
669     if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
670
671     switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK)
672     {
673     case MIXER_GETLINECONTROLSF_ALL:
674         {
675             int         i, j;
676
677             TRACE("line=%08lx GLCF_ALL (%ld)\n", lpMlc->dwLineID, lpMlc->cControls);
678
679             for (i = j = 0; i < mix->numCtrl; i++)
680             {
681                 if (mix->ctrl[i].dwLineID == lpMlc->dwLineID)
682                     j++;
683             }
684             if (!j || lpMlc->cControls != j)            dwRet = MMSYSERR_INVALPARAM;
685             else if (!MIX_CheckLine(lpMlc->dwLineID))   dwRet = MIXERR_INVALLINE;
686             else
687             {
688                 for (i = j = 0; i < mix->numCtrl; i++)
689                 {
690                     if (mix->ctrl[i].dwLineID == lpMlc->dwLineID)
691                     {
692                         TRACE("[%d] => [%2d]: typ=%08lx\n", j, i + 1, mix->ctrl[i].ctrl.dwControlType);
693                         lpMlc->pamxctrl[j++] = mix->ctrl[i].ctrl;
694                     }
695                 }
696             }
697         }
698         break;
699     case MIXER_GETLINECONTROLSF_ONEBYID:
700         TRACE("line=%08lx GLCF_ONEBYID (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlID);
701
702         if (!MIX_CheckControl(mix, lpMlc->u.dwControlID) ||
703             mix->ctrl[lpMlc->u.dwControlID - 1].dwLineID != lpMlc->dwLineID)
704             dwRet = MMSYSERR_INVALPARAM;
705         else
706             lpMlc->pamxctrl[0] = mix->ctrl[lpMlc->u.dwControlID - 1].ctrl;
707         break;
708     case MIXER_GETLINECONTROLSF_ONEBYTYPE:
709         TRACE("line=%08lx GLCF_ONEBYTYPE (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlType);
710         if (!MIX_CheckLine(lpMlc->dwLineID))    dwRet = MIXERR_INVALLINE;
711         else
712         {
713             int         i;
714             DWORD       ct = lpMlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK;
715
716             for (i = 0; i < mix->numCtrl; i++)
717             {
718                 if (mix->ctrl[i].dwLineID == lpMlc->dwLineID &&
719                     ct == (mix->ctrl[i].ctrl.dwControlType & MIXERCONTROL_CT_CLASS_MASK))
720                 {
721                     lpMlc->pamxctrl[0] = mix->ctrl[i].ctrl;
722                     break;
723                 }
724             }
725             if (i == mix->numCtrl) dwRet = MMSYSERR_INVALPARAM;
726         }
727         break;
728     default:
729         ERR("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
730         dwRet = MMSYSERR_INVALPARAM;
731     }
732
733     return dwRet;
734 }
735
736 /**************************************************************************
737  *                              MIX_GetControlDetails           [internal]
738  */
739 static  DWORD   MIX_GetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
740 {
741     DWORD               ret = MMSYSERR_NOTSUPPORTED;
742     DWORD               c, chnl;
743     struct mixer*       mix;
744
745     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
746
747     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
748     if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
749
750     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK)
751     {
752     case MIXER_GETCONTROLDETAILSF_VALUE:
753         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
754         if (MIX_CheckControl(mix, lpmcd->dwControlID))
755         {
756             c = lpmcd->dwControlID - 1;
757             chnl = HIWORD(mix->ctrl[c].dwLineID);
758             if (chnl == LINEID_DST)
759                 chnl = LOWORD(mix->ctrl[c].dwLineID) ? SOUND_MIXER_RECLEV : SOUND_MIXER_VOLUME;
760             switch (mix->ctrl[c].ctrl.dwControlType)
761             {
762             case MIXERCONTROL_CONTROLTYPE_VOLUME:
763                 {
764                     LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
765                     int                                 val;
766
767                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_UNSIGNED), lpmcd->cbDetails);
768                     /* return value is 00RL (4 bytes)... */
769                     if ((val = mix->volume[chnl]) == -1 && !MIX_GetVal(mix, chnl, &val))
770                         return MMSYSERR_INVALPARAM;
771
772                     switch (lpmcd->cChannels)
773                     {
774                     case 1:
775                         /* mono... so R = L */
776                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
777                         mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
778                         break;
779                     case 2:
780                         /* stereo, left is paDetails[0] */
781                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
782                         mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
783                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
784                         mcdu->dwValue = (HIBYTE(LOWORD(val)) * 65536L) / 100;
785                         break;
786                     default:
787                         WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
788                         return MMSYSERR_INVALPARAM;
789                     }
790                     TRACE("=> %08lx\n", mcdu->dwValue);
791                 }
792                 break;
793             case MIXERCONTROL_CONTROLTYPE_MUTE:
794             case MIXERCONTROL_CONTROLTYPE_ONOFF:
795                 {
796                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
797
798                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
799                     /* we mute both channels at the same time */
800                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
801                     mcdb->fValue = (mix->volume[chnl] != -1);
802                     TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
803                 }
804                 break;
805             case MIXERCONTROL_CONTROLTYPE_MIXER:
806             case MIXERCONTROL_CONTROLTYPE_MUX:
807                 {
808                     unsigned                            mask;
809
810                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
811                     if (!MIX_GetRecSrc(mix, &mask))
812                     {
813                         /* FIXME: ENXIO => no mixer installed */
814                         WARN("mixer device not available !\n");
815                         ret = MMSYSERR_ERROR;
816                     }
817                     else
818                     {
819                         LPMIXERCONTROLDETAILS_BOOLEAN   mcdb;
820                         int                             i, j;
821
822                         /* we mute both channels at the same time */
823                         mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
824
825                         for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
826                         {
827                             if (WINE_CHN_SUPPORTS(mix->recMask, j))
828                             {
829                                 if (i >= lpmcd->u.cMultipleItems)
830                                     return MMSYSERR_INVALPARAM;
831                                 mcdb[i++].fValue = WINE_CHN_SUPPORTS(mask, j);
832                             }
833                         }
834                     }
835                 }
836                 break;
837             default:
838                 WARN("Unsupported\n");
839             }
840             ret = MMSYSERR_NOERROR;
841         }
842         else
843         {
844             ret = MMSYSERR_INVALPARAM;
845         }
846         break;
847     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
848         TRACE("LIST TEXT (%08lx)\n", lpmcd->dwControlID);
849
850         ret = MMSYSERR_INVALPARAM;
851         if (MIX_CheckControl(mix, lpmcd->dwControlID))
852         {
853             int c = lpmcd->dwControlID - 1;
854
855             if (mix->ctrl[c].ctrl.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX ||
856                 mix->ctrl[c].ctrl.dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER)
857             {
858                 LPMIXERCONTROLDETAILS_LISTTEXTA mcdlt;
859                 int i, j;
860
861                 mcdlt = (LPMIXERCONTROLDETAILS_LISTTEXTA)lpmcd->paDetails;
862                 for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
863                 {
864                     if (WINE_CHN_SUPPORTS(mix->recMask, j))
865                     {
866                         mcdlt[i].dwParam1 = MAKELONG(LINEID_RECORD, j);
867                         mcdlt[i].dwParam2 = 0;
868                         strcpy(mcdlt[i].szName, MIX_Names[j]);
869                         i++;
870                     }
871                 }
872                 if (i != lpmcd->u.cMultipleItems) FIXME("bad count\n");
873                 ret = MMSYSERR_NOERROR;
874             }
875         }
876         break;
877     default:
878         WARN("Unknown flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
879     }
880     return ret;
881 }
882
883 /**************************************************************************
884  *                              MIX_SetControlDetails           [internal]
885  */
886 static  DWORD   MIX_SetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
887 {
888     DWORD               ret = MMSYSERR_NOTSUPPORTED;
889     DWORD               c, chnl;
890     int                 val;
891     struct mixer*       mix;
892
893     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
894
895     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
896     if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
897
898     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK)
899     {
900     case MIXER_GETCONTROLDETAILSF_VALUE:
901         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
902         if (MIX_CheckControl(mix, lpmcd->dwControlID))
903         {
904             c = lpmcd->dwControlID - 1;
905             chnl = HIWORD(mix->ctrl[c].dwLineID);
906             if (chnl == LINEID_DST)
907                 chnl = LOWORD(mix->ctrl[c].dwLineID) ? SOUND_MIXER_RECLEV : SOUND_MIXER_VOLUME;
908
909             switch (mix->ctrl[c].ctrl.dwControlType)
910             {
911             case MIXERCONTROL_CONTROLTYPE_VOLUME:
912                 {
913                     LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
914
915                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_UNSIGNED), lpmcd->cbDetails);
916                     /* val should contain 00RL */
917                     switch (lpmcd->cChannels)
918                     {
919                     case 1:
920                         /* mono... so R = L */
921                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
922                         TRACE("Setting RL to %08ld\n", mcdu->dwValue);
923                         val = 0x101 * ((mcdu->dwValue * 100) >> 16);
924                         break;
925                     case 2:
926                         /* stereo, left is paDetails[0] */
927                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
928                         TRACE("Setting L to %08ld\n", mcdu->dwValue);
929                         val = ((mcdu->dwValue * 100) >> 16);
930                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
931                         TRACE("Setting R to %08ld\n", mcdu->dwValue);
932                         val += ((mcdu->dwValue * 100) >> 16) << 8;
933                         break;
934                     default:
935                         WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
936                         return MMSYSERR_INVALPARAM;
937                     }
938
939                     if (mix->volume[chnl] == -1)
940                     {
941                         if (!MIX_SetVal(mix, chnl, val))
942                             return MMSYSERR_INVALPARAM;
943                     }
944                     else
945                     {
946                         mix->volume[chnl] = val;
947                     }
948                 }
949                 ret = MMSYSERR_NOERROR;
950                 break;
951             case MIXERCONTROL_CONTROLTYPE_MUTE:
952             case MIXERCONTROL_CONTROLTYPE_ONOFF:
953                 {
954                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
955
956                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
957                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
958                     if (mcdb->fValue)
959                     {
960                         if (!MIX_GetVal(mix, chnl, &mix->volume[chnl]) || !MIX_SetVal(mix, chnl, 0))
961                             return MMSYSERR_INVALPARAM;
962                     }
963                     else
964                     {
965                         if (mix->volume[chnl] == -1)
966                         {
967                             ret = MMSYSERR_NOERROR;
968                             break;
969                         }
970                         if (!MIX_SetVal(mix, chnl, mix->volume[chnl]))
971                             return MMSYSERR_INVALPARAM;
972                         mix->volume[chnl] = -1;
973                     }
974                 }
975                 ret = MMSYSERR_NOERROR;
976                 break;
977             case MIXERCONTROL_CONTROLTYPE_MIXER:
978             case MIXERCONTROL_CONTROLTYPE_MUX:
979                 {
980                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
981                     unsigned                            mask;
982                     int                                 i, j;
983
984                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
985                     /* we mute both channels at the same time */
986                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
987                     mask = 0;
988                     for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
989                     {
990                         if (WINE_CHN_SUPPORTS(mix->recMask, j) && mcdb[i++].fValue)
991                         {
992                             /* a mux can only select one line at a time... */
993                             if (mix->singleRecChannel && mask != 0)
994                             {
995                                 FIXME("!!!\n");
996                                 return MMSYSERR_INVALPARAM;
997                             }
998                             mask |= WINE_CHN_MASK(j);
999                         }
1000                     }
1001                     if (i != lpmcd->u.cMultipleItems) FIXME("bad count\n");
1002                     TRACE("writing %04x as rec src\n", mask);
1003                     if (!MIX_SetRecSrc(mix, mask))
1004                         ERR("Can't write new mixer settings\n");
1005                     else
1006                         ret = MMSYSERR_NOERROR;
1007                 }
1008                 break;
1009             }
1010         }
1011         break;
1012     default:
1013         WARN("Unknown SetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
1014     }
1015     return ret;
1016 }
1017
1018 /**************************************************************************
1019  *                              MIX_Init                        [internal]
1020  */
1021 static  DWORD   MIX_Init(void)
1022 {
1023     int mixer;
1024
1025 #define MIXER_DEV "/dev/mixer"
1026     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0)
1027     {
1028         if (errno == ENODEV || errno == ENXIO)
1029         {
1030             /* no driver present */
1031             return MMSYSERR_NODRIVER;
1032         }
1033         MIX_NumMixers = 0;
1034         return MMSYSERR_ERROR;
1035     }
1036     close(mixer);
1037     MIX_NumMixers = 1;
1038     MIX_Mixers[0].name = MIXER_DEV;
1039     MIX_Open(0, NULL, 0); /* FIXME */
1040 #undef MIXER_DEV
1041     return MMSYSERR_NOERROR;
1042 }
1043
1044 /**************************************************************************
1045  *                              MIX_GetNumDevs                  [internal]
1046  */
1047 static  DWORD   MIX_GetNumDevs(void)
1048 {
1049     return MIX_NumMixers;
1050 }
1051
1052 #endif /* HAVE_OSS */
1053
1054 /**************************************************************************
1055  *                              mxdMessage (WINEOSS.3)
1056  */
1057 DWORD WINAPI OSS_mxdMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
1058                             DWORD dwParam1, DWORD dwParam2)
1059 {
1060 /* TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2); */
1061
1062 #ifdef HAVE_OSS
1063     switch (wMsg)
1064     {
1065     case DRVM_INIT:
1066         return MIX_Init();
1067     case DRVM_EXIT:
1068     case DRVM_ENABLE:
1069     case DRVM_DISABLE:
1070         /* FIXME: Pretend this is supported */
1071         return 0;
1072     case MXDM_GETDEVCAPS:
1073         return MIX_GetDevCaps(wDevID, (LPMIXERCAPSA)dwParam1, dwParam2);
1074     case MXDM_GETLINEINFO:
1075         return MIX_GetLineInfo(wDevID, (LPMIXERLINEA)dwParam1, dwParam2);
1076     case MXDM_GETNUMDEVS:
1077         return MIX_GetNumDevs();
1078     case MXDM_OPEN:
1079         return MMSYSERR_NOERROR;
1080         /* MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2); */
1081     case MXDM_CLOSE:
1082         return MMSYSERR_NOERROR;
1083     case MXDM_GETLINECONTROLS:
1084         return MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSA)dwParam1, dwParam2);
1085     case MXDM_GETCONTROLDETAILS:
1086         return MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
1087     case MXDM_SETCONTROLDETAILS:
1088         return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
1089     default:
1090         WARN("unknown message %d!\n", wMsg);
1091         return MMSYSERR_NOTSUPPORTED;
1092     }
1093 #else
1094     return MMSYSERR_NOTENABLED;
1095 #endif
1096 }