Fixed infinite loop for 0-length wavehdrs.
[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 Eric Pouech
8  */
9
10 #include "config.h"
11
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <fcntl.h>
16 #include <sys/ioctl.h>
17 #include "windef.h"
18 #include "user.h"
19 #include "driver.h"
20 #include "mmddk.h"
21 #include "oss.h"
22 #include "debugtools.h"
23
24 DEFAULT_DEBUG_CHANNEL(mmaux)
25
26 #ifdef HAVE_OSS
27
28 #define MIXER_DEV "/dev/mixer"
29
30 #define WINE_MIXER_MANUF_ID             0xAA
31 #define WINE_MIXER_PRODUCT_ID           0x55
32 #define WINE_MIXER_VERSION              0x0100
33 #define WINE_MIXER_NAME                 "WINE OSS Mixer"
34
35 #define WINE_CHN_MASK(_x)               (1L << (_x))
36 #define WINE_CHN_SUPPORTS(_c, _x)       ((_c) & WINE_CHN_MASK(_x))
37 #define WINE_MIXER_MASK                 (WINE_CHN_MASK(SOUND_MIXER_VOLUME) | \
38                                          WINE_CHN_MASK(SOUND_MIXER_BASS)   | \
39                                          WINE_CHN_MASK(SOUND_MIXER_TREBLE) | \
40                                          WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
41                                          WINE_CHN_MASK(SOUND_MIXER_PCM)    | \
42                                          WINE_CHN_MASK(SOUND_MIXER_LINE)   | \
43                                          WINE_CHN_MASK(SOUND_MIXER_MIC)    | \
44                                          WINE_CHN_MASK(SOUND_MIXER_CD))
45
46 /* FIXME: the two following string arrays should be moved to a resource file in a string table */
47 /* if it's done, better use a struct to hold labels, name, and muted channel volume cache */
48 static char*    MIX_Labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
49 static char*    MIX_Names [SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
50 static int      MIX_Volume[SOUND_MIXER_NRDEVICES];
51 static int      MIX_DevMask = 0;
52 static int      MIX_StereoMask = 0;
53
54 /**************************************************************************
55  *                              MIX_GetVal                      [internal]
56  */
57 static  BOOL    MIX_GetVal(int chn, int* val)
58 {
59     int         mixer;
60     BOOL        ret = FALSE;
61
62     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
63         /* FIXME: ENXIO => no mixer installed */
64         WARN("mixer device not available !\n");
65     } else {
66         if (ioctl(mixer, MIXER_READ(chn), val) >= 0) {
67             TRACE("Reading %x on %d\n", *val, chn);
68             ret = TRUE;
69         }
70         close(mixer);
71     }
72     return ret;
73 }
74
75 /**************************************************************************
76  *                              MIX_SetVal                      [internal]
77  */
78 static  BOOL    MIX_SetVal(int chn, int val)
79 {
80     int         mixer;
81     BOOL        ret = FALSE;
82
83     TRACE("Writing %x on %d\n", val, chn);
84
85     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
86         /* FIXME: ENXIO => no mixer installed */
87         WARN("mixer device not available !\n");
88     } else {
89         if (ioctl(mixer, MIXER_WRITE(chn), &val) >= 0) {
90             ret = TRUE;
91         }
92         close(mixer);
93     }
94     return ret;
95 }
96
97 /**************************************************************************
98  *                              MIX_GetDevCaps                  [internal]
99  */
100 static DWORD MIX_GetDevCaps(WORD wDevID, LPMIXERCAPSA lpCaps, DWORD dwSize)
101 {
102     TRACE("(%04X, %p, %lu);\n", wDevID, lpCaps, dwSize);
103     
104     if (wDevID != 0) return MMSYSERR_BADDEVICEID;
105     if (lpCaps == NULL) return MMSYSERR_INVALPARAM;
106     if (!MIX_DevMask) return MMSYSERR_NOTENABLED;
107
108     lpCaps->wMid = WINE_MIXER_MANUF_ID;
109     lpCaps->wPid = WINE_MIXER_PRODUCT_ID;
110     lpCaps->vDriverVersion = WINE_MIXER_VERSION;
111     strcpy(lpCaps->szPname, WINE_MIXER_NAME);
112
113     lpCaps->cDestinations = 1;
114     lpCaps->fdwSupport = 0; /* No bits defined yet */
115     
116     return MMSYSERR_NOERROR;
117 }
118
119 /**************************************************************************
120  *                              MIX_GetLineInfoFromIndex        [internal]
121  */
122 static  void    MIX_GetLineInfoFromIndex(LPMIXERLINEA lpMl, int devmask, DWORD idx)
123 {
124     strcpy(lpMl->szShortName, MIX_Labels[idx]);
125     strcpy(lpMl->szName, MIX_Names[idx]);
126     lpMl->dwLineID = idx;
127     lpMl->dwDestination = 0; /* index for speakers */
128     lpMl->cConnections = 1;
129     lpMl->cControls = 2;
130     switch (idx) {
131     case SOUND_MIXER_SYNTH:
132         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;
133         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
134         break;
135     case SOUND_MIXER_CD:
136         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
137         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
138         break;
139     case SOUND_MIXER_LINE:
140         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_LINE;
141         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
142         break;
143     case SOUND_MIXER_MIC:
144         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
145         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
146         break;
147     case SOUND_MIXER_PCM:
148         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
149         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
150         break;
151     default:
152         ERR("Index %ld not handled.\n", idx);
153         break;
154     }
155 }
156
157 /**************************************************************************
158  *                              MIX_GetLineInfo                 [internal]
159  */
160 static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEA lpMl, DWORD fdwInfo)
161 {
162     int                 i, j;
163     BOOL                isDst = FALSE;
164     DWORD               ret = MMSYSERR_NOERROR;
165     
166     TRACE("(%04X, %p, %lu);\n", wDevID, lpMl, fdwInfo);
167     if (lpMl == NULL || lpMl->cbStruct != sizeof(*lpMl)) 
168         return MMSYSERR_INVALPARAM;
169     
170     /* FIXME: set all the variables correctly... the lines below
171      * are very wrong...
172      */
173     lpMl->fdwLine       = MIXERLINE_LINEF_ACTIVE;
174     lpMl->cChannels     = 1;
175     lpMl->dwUser        = 0;
176     lpMl->cControls     = 2;
177     
178     switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK) {
179     case MIXER_GETLINEINFOF_DESTINATION:
180         TRACE("DESTINATION (%08lx)\n", lpMl->dwDestination);
181         /* FIXME: Linux doesn't seem to support multiple outputs? 
182          * So we have only one output type: Speaker.
183          */
184         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
185         lpMl->dwSource = 0xFFFFFFFF;
186         lpMl->dwLineID = SOUND_MIXER_VOLUME;
187         strncpy(lpMl->szShortName, MIX_Labels[SOUND_MIXER_VOLUME], MIXER_SHORT_NAME_CHARS);
188         strncpy(lpMl->szName, MIX_Names[SOUND_MIXER_VOLUME], MIXER_LONG_NAME_CHARS);
189         
190         /* we have all connections found in the MIX_DevMask */
191         lpMl->cConnections = 0;
192         for (j = 1; j < SOUND_MIXER_NRDEVICES; j++)
193             if (WINE_CHN_SUPPORTS(MIX_DevMask, j))
194                 lpMl->cConnections++;
195         if (WINE_CHN_SUPPORTS(MIX_StereoMask, SOUND_MIXER_VOLUME))
196             lpMl->cChannels++;
197         break;
198     case MIXER_GETLINEINFOF_SOURCE:
199         TRACE("SOURCE (%08lx)\n", lpMl->dwSource);
200         i = lpMl->dwSource;
201         for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
202             if (WINE_CHN_SUPPORTS(MIX_DevMask, j) && (i-- == 0)) 
203                 break;
204         }
205         if (j >= SOUND_MIXER_NRDEVICES)
206             return MIXERR_INVALLINE;
207         if (WINE_CHN_SUPPORTS(MIX_StereoMask, j))
208             lpMl->cChannels++;
209         MIX_GetLineInfoFromIndex(lpMl, MIX_DevMask, j);
210         break;
211     case MIXER_GETLINEINFOF_LINEID:
212         TRACE("LINEID (%08lx)\n", lpMl->dwLineID);
213         if (lpMl->dwLineID >= SOUND_MIXER_NRDEVICES)
214             return MIXERR_INVALLINE;
215         if (WINE_CHN_SUPPORTS(MIX_StereoMask, lpMl->dwLineID))
216             lpMl->cChannels++;
217         MIX_GetLineInfoFromIndex(lpMl, MIX_DevMask, lpMl->dwLineID);
218         break;
219     case MIXER_GETLINEINFOF_COMPONENTTYPE:
220         TRACE("COMPONENT TYPE (%08lx)\n", lpMl->dwComponentType);
221         
222         switch (lpMl->dwComponentType) {
223         case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
224             i = SOUND_MIXER_VOLUME;
225             lpMl->dwDestination = 0;
226             lpMl->dwSource = 0xFFFFFFFF;
227             isDst = TRUE;
228             break;
229         case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
230             i = SOUND_MIXER_SYNTH;
231             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
232             break;
233         case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC:
234             i = SOUND_MIXER_CD;
235             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
236             break;
237         case MIXERLINE_COMPONENTTYPE_SRC_LINE:
238             i = SOUND_MIXER_LINE;
239             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
240             break;
241         case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE:
242             i = SOUND_MIXER_MIC;
243             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
244             break;
245         case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
246             i = SOUND_MIXER_PCM;
247             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
248             break;
249         default:
250             FIXME("Unhandled component type (%08lx)\n", lpMl->dwComponentType);
251             return MMSYSERR_INVALPARAM;
252         }
253         
254         if (WINE_CHN_SUPPORTS(MIX_DevMask, i)) {
255             strcpy(lpMl->szShortName, MIX_Labels[i]);
256             strcpy(lpMl->szName, MIX_Names[i]);
257             lpMl->dwLineID = i;
258         }
259         if (WINE_CHN_SUPPORTS(MIX_StereoMask, i))
260             lpMl->cChannels++;
261         lpMl->cConnections = 0;
262         if (isDst) {
263             for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
264                 if (WINE_CHN_SUPPORTS(MIX_DevMask, j)) {
265                     lpMl->cConnections++;
266                 }
267             }
268         }
269         break;
270     case MIXER_GETLINEINFOF_TARGETTYPE:
271         FIXME("_TARGETTYPE not implemented yet.\n");
272         break;
273     default:
274         WARN("Unknown flag (%08lx)\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
275         break;
276     }
277     
278     lpMl->Target.dwType = MIXERLINE_TARGETTYPE_AUX;
279     lpMl->Target.dwDeviceID = 0xFFFFFFFF;
280     lpMl->Target.wMid = WINE_MIXER_MANUF_ID;
281     lpMl->Target.wPid = WINE_MIXER_PRODUCT_ID;
282     lpMl->Target.vDriverVersion = WINE_MIXER_VERSION;
283     strcpy(lpMl->Target.szPname, WINE_MIXER_NAME);
284     
285     return ret;
286 }
287
288 /**************************************************************************
289  *                              MIX_GetLineInfo                 [internal]
290  */
291 static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD flags)
292 {
293     TRACE("(%04X, %p, %lu);\n", wDevID, lpMod, flags);
294     if (lpMod == NULL) return MMSYSERR_INVALPARAM;
295     
296     return (MIX_DevMask == 0) ? MMSYSERR_NODRIVER : MMSYSERR_NOERROR;
297 }
298
299 /**************************************************************************
300  *                              MIX_MakeControlID               [internal]
301  */
302 static DWORD MIX_MakeControlID(DWORD lineID, DWORD controlType)
303 {
304     switch (controlType) {
305     case MIXERCONTROL_CONTROLTYPE_VOLUME:
306         return 2 * lineID + 0;
307     case MIXERCONTROL_CONTROLTYPE_MUTE:
308         return 2 * lineID + 1;
309     }
310     FIXME("Internal error");
311     return 0x00FADE00;
312 }
313
314 /**************************************************************************
315  *                              MIX_SplitControlID              [internal]
316  */
317 static BOOL MIX_SplitControlID(DWORD controlID, LPDWORD lineID, LPDWORD controlType)
318 {
319     *lineID = controlID / 2;
320     *controlType = (controlID & 1) ? 
321         MIXERCONTROL_CONTROLTYPE_MUTE : MIXERCONTROL_CONTROLTYPE_VOLUME;
322
323     return *lineID < SOUND_MIXER_NRDEVICES && WINE_CHN_SUPPORTS(MIX_DevMask, *lineID);
324 }
325
326 /**************************************************************************
327  *                              MIX_DoGetLineControls           [internal]
328  */
329 static void MIX_DoGetLineControls(LPMIXERCONTROLA mc, DWORD lineID, DWORD dwType)
330 {
331     mc->cbStruct = sizeof(MIXERCONTROLA);
332     
333     switch (dwType) {
334     case MIXERCONTROL_CONTROLTYPE_VOLUME:
335         TRACE("Returning volume control\n");
336         mc->dwControlID = MIX_MakeControlID(lineID, dwType);
337         mc->dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
338         mc->fdwControl = 0;
339         mc->cMultipleItems = 0;
340         strncpy(mc->szShortName, "Vol", MIXER_SHORT_NAME_CHARS);
341         strncpy(mc->szName, "Volume", MIXER_LONG_NAME_CHARS);
342         memset(&mc->Bounds, 0, sizeof(mc->Bounds));
343         /* CONTROLTYPE_VOLUME uses the MIXER_CONTROLDETAILS_UNSIGNED struct, 
344          * [0, 100] is the range supported by OSS
345          * FIXME: sounds like MIXERCONTROL_CONTROLTYPE_VOLUME is always between 0 and 65536
346          * whatever the min and max values are...
347          * look at conversions done in (Get|Set)ControlDetails to stay in [0, 100] range
348          */
349         mc->Bounds.s1.dwMinimum = 0;
350         mc->Bounds.s1.dwMaximum = 100;
351         memset(&mc->Metrics, 0, sizeof(mc->Metrics));
352         break;
353     case MIXERCONTROL_CONTROLTYPE_MUTE:
354         TRACE("Returning mute control\n");
355         mc->dwControlID = MIX_MakeControlID(lineID, dwType);
356         mc->dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
357         mc->fdwControl = 0;
358         mc->cMultipleItems = 0;
359         strncpy(mc->szShortName, "Mute", MIXER_SHORT_NAME_CHARS);
360         strncpy(mc->szName, "Mute", MIXER_LONG_NAME_CHARS);
361         memset(&mc->Bounds, 0, sizeof(mc->Bounds));
362         memset(&mc->Metrics, 0, sizeof(mc->Metrics));
363         break;
364     default:
365         FIXME("Internal error: unknown type: %08lx\n", dwType);
366     }
367 }
368
369 /**************************************************************************
370  *                              MIX_GetLineControls             [internal]
371  */
372 static  DWORD   MIX_GetLineControls(WORD wDevID, LPMIXERLINECONTROLSA lpMlc, DWORD flags)
373 {
374     DWORD               dwRet = MMSYSERR_NOERROR;
375     DWORD               lineID, controlType;
376
377     TRACE("(%04X, %p, %lu);\n", wDevID, lpMlc, flags);
378     
379     if (lpMlc == NULL) return MMSYSERR_INVALPARAM;
380     if (lpMlc->cbStruct < sizeof(*lpMlc) ||
381         lpMlc->cbmxctrl < sizeof(MIXERCONTROLA))
382         return MMSYSERR_INVALPARAM;
383
384     switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK) {
385     case MIXER_GETLINECONTROLSF_ALL:
386         TRACE("line=%08lx GLCF_ALL (%ld)\n", lpMlc->dwLineID, lpMlc->cControls);
387         if (lpMlc->cControls != 2) {
388             dwRet = MMSYSERR_INVALPARAM;
389         } else {
390             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME);
391             MIX_DoGetLineControls(&lpMlc->pamxctrl[1], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_MUTE);
392         }
393         break;
394     case MIXER_GETLINECONTROLSF_ONEBYID:
395         TRACE("line=%08lx GLCF_ONEBYID (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlID);
396         if (MIX_SplitControlID(lpMlc->u.dwControlID, &lineID, &controlType))
397             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lineID, controlType);
398         else 
399             dwRet = MMSYSERR_INVALPARAM;
400         break;
401     case MIXER_GETLINECONTROLSF_ONEBYTYPE:
402         TRACE("line=%08lx GLCF_ONEBYTYPE (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlType);
403         switch (lpMlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK) {
404         case MIXERCONTROL_CT_CLASS_FADER:
405             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME);
406             break;
407         case MIXERCONTROL_CT_CLASS_SWITCH:
408             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_MUTE);
409             break;
410         default:
411             dwRet = MMSYSERR_INVALPARAM;
412         }
413         break;
414     default:
415         ERR("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
416         dwRet = MMSYSERR_INVALPARAM;
417     }
418
419     return dwRet;
420 }
421
422 /**************************************************************************
423  *                              MIX_GetControlDetails           [internal]
424  */
425 static  DWORD   MIX_GetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
426 {
427     DWORD       ret = MMSYSERR_NOTSUPPORTED;
428     DWORD       lineID, controlType;
429
430     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
431     
432     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
433
434     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
435     case MIXER_GETCONTROLDETAILSF_VALUE:
436         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
437         if (MIX_SplitControlID(lpmcd->dwControlID, &lineID, &controlType)) {
438             switch (controlType) {
439             case MIXERCONTROL_CONTROLTYPE_VOLUME:
440                 {
441                     LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
442                     int                                 val;
443
444                     /* return value is 00RL (4 bytes)... */
445                     if ((val = MIX_Volume[lineID]) == -1 && !MIX_GetVal(lineID, &val))
446                         return MMSYSERR_INVALPARAM;
447             
448                     switch (lpmcd->cChannels) {
449                     case 1:
450                         /* mono... so R = L */
451                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
452                         mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
453                         break;
454                     case 2:
455                         /* stereo, left is paDetails[0] */
456                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
457                         mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
458                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
459                         mcdu->dwValue = (HIBYTE(LOWORD(val)) * 65536L) / 100;
460                         break;
461                     default:
462                         WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
463                         return MMSYSERR_INVALPARAM;
464                     }
465                     TRACE("=> %08lx\n", mcdu->dwValue);
466                 }
467                 break;
468             case MIXERCONTROL_CONTROLTYPE_MUTE:
469                 {
470                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
471                     
472                     /* we mute both channels at the same time */
473                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
474                     mcdb->fValue = (MIX_Volume[lineID] != -1);
475                 }
476                 break;
477             }
478             ret = MMSYSERR_NOERROR;
479         } else {
480             ret = MMSYSERR_INVALPARAM;
481         }
482         break;
483     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
484         FIXME("NIY\n");
485         break;
486     default:
487         WARN("Unknown flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
488     }
489     return ret;
490 }
491
492 /**************************************************************************
493  *                              MIX_SetControlDetails           [internal]
494  */
495 static  DWORD   MIX_SetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
496 {
497     DWORD       ret = MMSYSERR_NOTSUPPORTED;
498     DWORD       lineID, controlType;
499     int         val;
500     
501     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
502     
503     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
504     
505     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
506     case MIXER_GETCONTROLDETAILSF_VALUE:
507         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
508         if (MIX_SplitControlID(lpmcd->dwControlID, &lineID, &controlType)) {
509             switch (controlType) {
510             case MIXERCONTROL_CONTROLTYPE_VOLUME:
511                 {
512                     LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
513                     
514                     /* val should contain 00RL */
515                     switch (lpmcd->cChannels) {
516                     case 1:
517                         /* mono... so R = L */
518                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
519                         TRACE("Setting RL to %08ld\n", mcdu->dwValue);
520                         val = 0x101 * ((mcdu->dwValue * 100) >> 16);
521                         break;
522                     case 2:
523                         /* stereo, left is paDetails[0] */
524                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
525                         TRACE("Setting L to %08ld\n", mcdu->dwValue);
526                         val = ((mcdu->dwValue * 100) >> 16);
527                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
528                         TRACE("Setting R to %08ld\n", mcdu->dwValue);
529                         val += ((mcdu->dwValue * 100) >> 16) << 8;
530                         break;
531                     default:
532                         WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
533                         return MMSYSERR_INVALPARAM;
534                     }
535                     
536                     if (MIX_Volume[lineID] == -1) {
537                         if (!MIX_SetVal(lineID, val))
538                             return MMSYSERR_INVALPARAM;
539                     } else {
540                         MIX_Volume[lineID] = val;
541                     }
542                 }
543                 ret = MMSYSERR_NOERROR;
544                 break;
545             case MIXERCONTROL_CONTROLTYPE_MUTE:
546                 {
547                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
548                     
549                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
550                     if (mcdb->fValue) {
551                         if (!MIX_GetVal(lineID, &MIX_Volume[lineID]))
552                             return MMSYSERR_INVALPARAM;
553                         if (!MIX_SetVal(lineID, 0))
554                             return MMSYSERR_INVALPARAM;
555                     } else {
556                         if (!MIX_SetVal(lineID, MIX_Volume[lineID]))
557                             return MMSYSERR_INVALPARAM;
558                         MIX_Volume[lineID] = -1;
559                     }
560                 }
561                 ret = MMSYSERR_NOERROR;
562                 break;
563             }
564         }
565         break;
566     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
567         FIXME("NIY\n");
568         break;
569     default:
570         WARN("Unknown GetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
571     }
572     return MMSYSERR_NOTSUPPORTED;
573 }
574
575 /**************************************************************************
576  *                              MIX_GetNumDevs                  [internal]
577  */
578 static  DWORD   MIX_GetNumDevs(UINT wDevID)
579 {
580     return (MIX_DevMask != 0) ? 1 : 0;
581 }
582
583 /**************************************************************************
584  *                              MIX_Init                        [internal]
585  */
586 static  DWORD   MIX_Init(void)
587 {
588     int mixer, i;
589
590     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
591         if (errno == ENODEV || errno == ENXIO) {        
592             /* no driver present */
593             return MMSYSERR_NODRIVER;
594         }
595         return MMSYSERR_ERROR;
596     }
597     if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &MIX_DevMask) == -1) {
598         close(mixer);
599         perror("ioctl mixer SOUND_MIXER_DEVMASK");
600         return MMSYSERR_NOTENABLED;
601     }
602     MIX_DevMask &= WINE_MIXER_MASK;
603     if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &MIX_StereoMask) == -1) {
604         close(mixer);
605         perror("ioctl mixer SOUND_MIXER_STEREODEVS");
606         return MMSYSERR_NOTENABLED;
607     }
608     MIX_StereoMask &= WINE_MIXER_MASK;
609
610 #if 0
611     int                 recsrc, recmask;
612
613     if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) {
614         close(mixer);
615         perror("ioctl mixer SOUND_MIXER_RECSRC");
616         return MMSYSERR_NOTENABLED;
617     }
618
619     if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
620         close(mixer);
621         perror("ioctl mixer SOUND_MIXER_RECMASK");
622         return MMSYSERR_NOTENABLED;
623     }
624 #endif
625     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
626         MIX_Volume[i] = -1;
627     }
628     close(mixer);
629
630     return MMSYSERR_NOERROR;
631 }
632
633 #endif /* HAVE_OSS */
634
635 /**************************************************************************
636  *                              OSS_mixMessage          [sample driver]
637  */
638 DWORD WINAPI OSS_mixMessage(UINT wDevID, UINT wMsg, DWORD dwUser, 
639                             DWORD dwParam1, DWORD dwParam2)
640 {
641     TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", 
642           wDevID, wMsg, dwUser, dwParam1, dwParam2);
643     
644 #ifdef HAVE_OSS
645     switch(wMsg) {
646     case DRVM_INIT:
647         return MIX_Init();
648         break; 
649     case DRVM_EXIT:
650     case DRVM_ENABLE:
651     case DRVM_DISABLE:
652         /* FIXME: Pretend this is supported */
653         return 0;
654     case MXDM_GETDEVCAPS:       
655         return MIX_GetDevCaps(wDevID, (LPMIXERCAPSA)dwParam1, dwParam2);
656     case MXDM_GETLINEINFO:
657         return MIX_GetLineInfo(wDevID, (LPMIXERLINEA)dwParam1, dwParam2);
658     case MXDM_GETNUMDEVS:
659         return MIX_GetNumDevs(wDevID);
660     case MXDM_OPEN:
661         return MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2);
662     case MXDM_CLOSE:
663         return MMSYSERR_NOERROR;
664     case MXDM_GETLINECONTROLS:
665         return MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSA)dwParam1, dwParam2);
666     case MXDM_GETCONTROLDETAILS:
667         return MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
668     case MXDM_SETCONTROLDETAILS:
669         return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
670     default:
671         WARN("unknown message %d!\n", wMsg);
672     }
673     return MMSYSERR_NOTSUPPORTED;
674 #else
675     return MMSYSERR_NOTENABLED;
676 #endif
677 }
678
679