Don't load user32 too early on for 16-bit apps, so that app-specific
[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 <stdio.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <sys/ioctl.h>
18 #include "windef.h"
19 #include "mmddk.h"
20 #include "oss.h"
21 #include "debugtools.h"
22
23 DEFAULT_DEBUG_CHANNEL(mmaux);
24
25 #ifdef HAVE_OSS
26
27 #define MIXER_DEV "/dev/mixer"
28
29 #define WINE_MIXER_MANUF_ID             0xAA
30 #define WINE_MIXER_PRODUCT_ID           0x55
31 #define WINE_MIXER_VERSION              0x0100
32 #define WINE_MIXER_NAME                 "WINE OSS Mixer"
33
34 #define WINE_CHN_MASK(_x)               (1L << (_x))
35 #define WINE_CHN_SUPPORTS(_c, _x)       ((_c) & WINE_CHN_MASK(_x))
36 /* Bass and Treble are no longer in the mask as Windows does not handle them */
37 #define WINE_MIXER_MASK                 (WINE_CHN_MASK(SOUND_MIXER_VOLUME) | \
38                                          WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
39                                          WINE_CHN_MASK(SOUND_MIXER_PCM)    | \
40                                          WINE_CHN_MASK(SOUND_MIXER_LINE)   | \
41                                          WINE_CHN_MASK(SOUND_MIXER_MIC)    | \
42                                          WINE_CHN_MASK(SOUND_MIXER_CD))
43
44 /* FIXME: the two following string arrays should be moved to a resource file in a string table */
45 /* if it's done, better use a struct to hold labels, name, and muted channel volume cache */
46 static char*    MIX_Labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
47 static char*    MIX_Names [SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
48 static int      MIX_Volume[SOUND_MIXER_NRDEVICES];
49 static int      MIX_DevMask = 0;
50 static int      MIX_StereoMask = 0;
51
52 /**************************************************************************
53  *                              MIX_GetVal                      [internal]
54  */
55 static  BOOL    MIX_GetVal(int chn, int* val)
56 {
57     int         mixer;
58     BOOL        ret = FALSE;
59
60     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
61         /* FIXME: ENXIO => no mixer installed */
62         WARN("mixer device not available !\n");
63     } else {
64         if (ioctl(mixer, MIXER_READ(chn), val) >= 0) {
65             TRACE("Reading volume %x on %d\n", *val, chn);
66             ret = TRUE;
67         }
68         close(mixer);
69     }
70     return ret;
71 }
72
73 /**************************************************************************
74  *                              MIX_SetVal                      [internal]
75  */
76 static  BOOL    MIX_SetVal(int chn, int val)
77 {
78     int         mixer;
79     BOOL        ret = FALSE;
80
81     TRACE("Writing volume %x on %d\n", val, chn);
82
83     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
84         /* FIXME: ENXIO => no mixer installed */
85         WARN("mixer device not available !\n");
86     } else {
87         if (ioctl(mixer, MIXER_WRITE(chn), &val) >= 0) {
88             ret = TRUE;
89         }
90         close(mixer);
91     }
92     return ret;
93 }
94
95 /**************************************************************************
96  *                              MIX_GetDevCaps                  [internal]
97  */
98 static DWORD MIX_GetDevCaps(WORD wDevID, LPMIXERCAPSA lpCaps, DWORD dwSize)
99 {
100     TRACE("(%04X, %p, %lu);\n", wDevID, lpCaps, dwSize);
101     
102     if (wDevID != 0) return MMSYSERR_BADDEVICEID;
103     if (lpCaps == NULL) return MMSYSERR_INVALPARAM;
104     if (!MIX_DevMask) return MMSYSERR_NOTENABLED;
105
106     lpCaps->wMid = WINE_MIXER_MANUF_ID;
107     lpCaps->wPid = WINE_MIXER_PRODUCT_ID;
108     lpCaps->vDriverVersion = WINE_MIXER_VERSION;
109     strcpy(lpCaps->szPname, WINE_MIXER_NAME);
110
111     lpCaps->cDestinations = 1;
112     lpCaps->fdwSupport = 0; /* No bits defined yet */
113     
114     return MMSYSERR_NOERROR;
115 }
116
117 /**************************************************************************
118  *                              MIX_GetLineInfoFromIndex        [internal]
119  */
120 static  DWORD   MIX_GetLineInfoFromIndex(LPMIXERLINEA lpMl, int devmask, DWORD idx)
121 {
122     strcpy(lpMl->szShortName, MIX_Labels[idx]);
123     strcpy(lpMl->szName, MIX_Names[idx]);
124     lpMl->dwLineID = idx;
125     lpMl->dwDestination = 0; /* index for speakers */
126     lpMl->cConnections = 1;
127     lpMl->cControls = 2;
128     switch (idx) {
129     case SOUND_MIXER_SYNTH:
130         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;
131         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
132         break;
133     case SOUND_MIXER_CD:
134         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
135         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
136         break;
137     case SOUND_MIXER_LINE:
138         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_LINE;
139         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
140         break;
141     case SOUND_MIXER_MIC:
142         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
143         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
144         break;
145     case SOUND_MIXER_PCM:
146         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
147         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
148         break;
149     default:
150         WARN("Index %ld not handled.\n", idx);
151         return MIXERR_INVALLINE;
152     }
153     return MMSYSERR_NOERROR;
154 }
155
156 /**************************************************************************
157  *                              MIX_GetLineInfo                 [internal]
158  */
159 static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEA lpMl, DWORD fdwInfo)
160 {
161     int                 i, j;
162     BOOL                isDst = FALSE;
163     DWORD               ret = MMSYSERR_NOERROR;
164     
165     TRACE("(%04X, %p, %lu);\n", wDevID, lpMl, fdwInfo);
166     if (lpMl == NULL || lpMl->cbStruct != sizeof(*lpMl)) 
167         return MMSYSERR_INVALPARAM;
168     
169     /* FIXME: set all the variables correctly... the lines below
170      * are very wrong...
171      */
172     lpMl->fdwLine       = MIXERLINE_LINEF_ACTIVE;
173     lpMl->cChannels     = 1;
174     lpMl->dwUser        = 0;
175     lpMl->cControls     = 2;
176     
177     switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK) {
178     case MIXER_GETLINEINFOF_DESTINATION:
179         TRACE("DESTINATION (%08lx)\n", lpMl->dwDestination);
180         /* FIXME: Linux doesn't seem to support multiple outputs? 
181          * So we have only one output type: Speaker.
182          */
183         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
184         lpMl->dwSource = 0xFFFFFFFF;
185         lpMl->dwLineID = SOUND_MIXER_VOLUME;
186         lstrcpynA(lpMl->szShortName, MIX_Labels[SOUND_MIXER_VOLUME], MIXER_SHORT_NAME_CHARS);
187         lstrcpynA(lpMl->szName, MIX_Names[SOUND_MIXER_VOLUME], MIXER_LONG_NAME_CHARS);
188         
189         /* we have all connections found in the MIX_DevMask */
190         lpMl->cConnections = 0;
191         for (j = 1; j < SOUND_MIXER_NRDEVICES; j++)
192             if (WINE_CHN_SUPPORTS(MIX_DevMask, j))
193                 lpMl->cConnections++;
194         if (WINE_CHN_SUPPORTS(MIX_StereoMask, SOUND_MIXER_VOLUME))
195             lpMl->cChannels++;
196         break;
197     case MIXER_GETLINEINFOF_SOURCE:
198         TRACE("SOURCE (%08lx)\n", lpMl->dwSource);
199         i = lpMl->dwSource;
200         for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
201             if (WINE_CHN_SUPPORTS(MIX_DevMask, j) && (i-- == 0))
202                 break;
203         }
204         if (j >= SOUND_MIXER_NRDEVICES)
205             return MIXERR_INVALLINE;
206         if (WINE_CHN_SUPPORTS(MIX_StereoMask, j))
207             lpMl->cChannels++;
208         if ((ret = MIX_GetLineInfoFromIndex(lpMl, MIX_DevMask, j)) != MMSYSERR_NOERROR)
209             return ret;
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         if ((ret = MIX_GetLineInfoFromIndex(lpMl, MIX_DevMask, lpMl->dwLineID)) != MMSYSERR_NOERROR)
218             return ret;
219         break;
220     case MIXER_GETLINEINFOF_COMPONENTTYPE:
221         TRACE("COMPONENT TYPE (%08lx)\n", lpMl->dwComponentType);
222         
223         switch (lpMl->dwComponentType) {
224         case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
225             i = SOUND_MIXER_VOLUME;
226             lpMl->dwDestination = 0;
227             lpMl->dwSource = 0xFFFFFFFF;
228             isDst = TRUE;
229             break;
230         case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
231             i = SOUND_MIXER_SYNTH;
232             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
233             break;
234         case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC:
235             i = SOUND_MIXER_CD;
236             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
237             break;
238         case MIXERLINE_COMPONENTTYPE_SRC_LINE:
239             i = SOUND_MIXER_LINE;
240             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
241             break;
242         case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE:
243             i = SOUND_MIXER_MIC;
244             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
245             break;
246         case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
247             i = SOUND_MIXER_PCM;
248             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
249             break;
250         default:
251             FIXME("Unhandled component type (%08lx)\n", lpMl->dwComponentType);
252             return MMSYSERR_INVALPARAM;
253         }
254         
255         if (WINE_CHN_SUPPORTS(MIX_DevMask, i)) {
256             strcpy(lpMl->szShortName, MIX_Labels[i]);
257             strcpy(lpMl->szName, MIX_Names[i]);
258             lpMl->dwLineID = i;
259         }
260         if (WINE_CHN_SUPPORTS(MIX_StereoMask, i))
261             lpMl->cChannels++;
262         lpMl->cConnections = 0;
263         if (isDst) {
264             for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
265                 if (WINE_CHN_SUPPORTS(MIX_DevMask, j)) {
266                     lpMl->cConnections++;
267                 }
268             }
269         }
270         break;
271     case MIXER_GETLINEINFOF_TARGETTYPE:
272         FIXME("_TARGETTYPE not implemented yet.\n");
273         break;
274     default:
275         WARN("Unknown flag (%08lx)\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
276         break;
277     }
278     
279     lpMl->Target.dwType = MIXERLINE_TARGETTYPE_AUX;
280     lpMl->Target.dwDeviceID = 0xFFFFFFFF;
281     lpMl->Target.wMid = WINE_MIXER_MANUF_ID;
282     lpMl->Target.wPid = WINE_MIXER_PRODUCT_ID;
283     lpMl->Target.vDriverVersion = WINE_MIXER_VERSION;
284     strcpy(lpMl->Target.szPname, WINE_MIXER_NAME);
285     
286     return ret;
287 }
288
289 /**************************************************************************
290  *                              MIX_GetLineInfo                 [internal]
291  */
292 static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD flags)
293 {
294     TRACE("(%04X, %p, %lu);\n", wDevID, lpMod, flags);
295     if (lpMod == NULL) return MMSYSERR_INVALPARAM;
296     
297     return (MIX_DevMask == 0) ? MMSYSERR_NODRIVER : MMSYSERR_NOERROR;
298 }
299
300 /**************************************************************************
301  *                              MIX_MakeControlID               [internal]
302  */
303 static DWORD MIX_MakeControlID(DWORD lineID, DWORD controlType)
304 {
305     switch (controlType) {
306     case MIXERCONTROL_CONTROLTYPE_VOLUME:
307         return 2 * lineID + 0;
308     case MIXERCONTROL_CONTROLTYPE_MUTE:
309         return 2 * lineID + 1;
310     }
311     FIXME("Internal error");
312     return 0x00FADE00;
313 }
314
315 /**************************************************************************
316  *                              MIX_SplitControlID              [internal]
317  */
318 static BOOL MIX_SplitControlID(DWORD controlID, LPDWORD lineID, LPDWORD controlType)
319 {
320     *lineID = controlID / 2;
321     *controlType = (controlID & 1) ? 
322         MIXERCONTROL_CONTROLTYPE_MUTE : MIXERCONTROL_CONTROLTYPE_VOLUME;
323
324     return *lineID < SOUND_MIXER_NRDEVICES && WINE_CHN_SUPPORTS(MIX_DevMask, *lineID);
325 }
326
327 /**************************************************************************
328  *                              MIX_DoGetLineControls           [internal]
329  */
330 static void MIX_DoGetLineControls(LPMIXERCONTROLA mc, DWORD lineID, DWORD dwType)
331 {
332     mc->cbStruct = sizeof(MIXERCONTROLA);
333     
334     switch (dwType) {
335     case MIXERCONTROL_CONTROLTYPE_VOLUME:
336         TRACE("Returning volume control\n");
337         mc->dwControlID = MIX_MakeControlID(lineID, dwType);
338         mc->dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
339         mc->fdwControl = 0;
340         mc->cMultipleItems = 0;
341         lstrcpynA(mc->szShortName, "Vol", MIXER_SHORT_NAME_CHARS);
342         lstrcpynA(mc->szName, "Volume", MIXER_LONG_NAME_CHARS);
343         memset(&mc->Bounds, 0, sizeof(mc->Bounds));
344         /* CONTROLTYPE_VOLUME uses the MIXER_CONTROLDETAILS_UNSIGNED struct, 
345          * [0, 100] is the range supported by OSS
346          * whatever the min and max values are they must match
347          * conversions done in (Get|Set)ControlDetails to stay in [0, 100] range
348          */
349         mc->Bounds.s1.dwMinimum = 0;
350         mc->Bounds.s1.dwMaximum = 65535;
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         lstrcpynA(mc->szShortName, "Mute", MIXER_SHORT_NAME_CHARS);
360         lstrcpynA(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_Volume[lineID] == -1) {
557                                 ret = MMSYSERR_NOERROR;
558                                 break;
559                         }
560                         if (!MIX_SetVal(lineID, MIX_Volume[lineID]))
561                             return MMSYSERR_INVALPARAM;
562                         MIX_Volume[lineID] = -1;
563                     }
564                 }
565                 ret = MMSYSERR_NOERROR;
566                 break;
567             }
568         }
569         break;
570     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
571         FIXME("NIY\n");
572         break;
573     default:
574         WARN("Unknown GetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
575     }
576     return MMSYSERR_NOTSUPPORTED;
577 }
578
579 /**************************************************************************
580  *                              MIX_GetNumDevs                  [internal]
581  */
582 static  DWORD   MIX_GetNumDevs(UINT wDevID)
583 {
584     return (MIX_DevMask != 0) ? 1 : 0;
585 }
586
587 /**************************************************************************
588  *                              MIX_Init                        [internal]
589  */
590 static  DWORD   MIX_Init(void)
591 {
592     int mixer, i;
593
594     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
595         if (errno == ENODEV || errno == ENXIO) {        
596             /* no driver present */
597             return MMSYSERR_NODRIVER;
598         }
599         return MMSYSERR_ERROR;
600     }
601     if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &MIX_DevMask) == -1) {
602         close(mixer);
603         perror("ioctl mixer SOUND_MIXER_DEVMASK");
604         return MMSYSERR_NOTENABLED;
605     }
606     MIX_DevMask &= WINE_MIXER_MASK;
607     if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &MIX_StereoMask) == -1) {
608         close(mixer);
609         perror("ioctl mixer SOUND_MIXER_STEREODEVS");
610         return MMSYSERR_NOTENABLED;
611     }
612     MIX_StereoMask &= WINE_MIXER_MASK;
613
614 #if 0
615     int                 recsrc, recmask;
616
617     if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) {
618         close(mixer);
619         perror("ioctl mixer SOUND_MIXER_RECSRC");
620         return MMSYSERR_NOTENABLED;
621     }
622
623     if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
624         close(mixer);
625         perror("ioctl mixer SOUND_MIXER_RECMASK");
626         return MMSYSERR_NOTENABLED;
627     }
628 #endif
629     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
630         MIX_Volume[i] = -1;
631     }
632     close(mixer);
633
634     return MMSYSERR_NOERROR;
635 }
636
637 #endif /* HAVE_OSS */
638
639 /**************************************************************************
640  *                              OSS_mixMessage          [sample driver]
641  */
642 DWORD WINAPI OSS_mixMessage(UINT wDevID, UINT wMsg, DWORD dwUser, 
643                             DWORD dwParam1, DWORD dwParam2)
644 {
645     TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", 
646           wDevID, wMsg, dwUser, dwParam1, dwParam2);
647     
648 #ifdef HAVE_OSS
649     switch(wMsg) {
650     case DRVM_INIT:
651         return MIX_Init();
652         break; 
653     case DRVM_EXIT:
654     case DRVM_ENABLE:
655     case DRVM_DISABLE:
656         /* FIXME: Pretend this is supported */
657         return 0;
658     case MXDM_GETDEVCAPS:       
659         return MIX_GetDevCaps(wDevID, (LPMIXERCAPSA)dwParam1, dwParam2);
660     case MXDM_GETLINEINFO:
661         return MIX_GetLineInfo(wDevID, (LPMIXERLINEA)dwParam1, dwParam2);
662     case MXDM_GETNUMDEVS:
663         return MIX_GetNumDevs(wDevID);
664     case MXDM_OPEN:
665         return MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2);
666     case MXDM_CLOSE:
667         return MMSYSERR_NOERROR;
668     case MXDM_GETLINECONTROLS:
669         return MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSA)dwParam1, dwParam2);
670     case MXDM_GETCONTROLDETAILS:
671         return MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
672     case MXDM_SETCONTROLDETAILS:
673         return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
674     default:
675         WARN("unknown message %d!\n", wMsg);
676     }
677     return MMSYSERR_NOTSUPPORTED;
678 #else
679     return MMSYSERR_NOTENABLED;
680 #endif
681 }
682
683