- implementation of RtlReg* (read access), RtlEvent*, RtlSemaphore*,
[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  DWORD   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         WARN("Index %ld not handled.\n", idx);
153         return MIXERR_INVALLINE;
154     }
155     return MMSYSERR_NOERROR;
156 }
157
158 /**************************************************************************
159  *                              MIX_GetLineInfo                 [internal]
160  */
161 static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEA lpMl, DWORD fdwInfo)
162 {
163     int                 i, j;
164     BOOL                isDst = FALSE;
165     DWORD               ret = MMSYSERR_NOERROR;
166     
167     TRACE("(%04X, %p, %lu);\n", wDevID, lpMl, fdwInfo);
168     if (lpMl == NULL || lpMl->cbStruct != sizeof(*lpMl)) 
169         return MMSYSERR_INVALPARAM;
170     
171     /* FIXME: set all the variables correctly... the lines below
172      * are very wrong...
173      */
174     lpMl->fdwLine       = MIXERLINE_LINEF_ACTIVE;
175     lpMl->cChannels     = 1;
176     lpMl->dwUser        = 0;
177     lpMl->cControls     = 2;
178     
179     switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK) {
180     case MIXER_GETLINEINFOF_DESTINATION:
181         TRACE("DESTINATION (%08lx)\n", lpMl->dwDestination);
182         /* FIXME: Linux doesn't seem to support multiple outputs? 
183          * So we have only one output type: Speaker.
184          */
185         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
186         lpMl->dwSource = 0xFFFFFFFF;
187         lpMl->dwLineID = SOUND_MIXER_VOLUME;
188         lstrcpynA(lpMl->szShortName, MIX_Labels[SOUND_MIXER_VOLUME], MIXER_SHORT_NAME_CHARS);
189         lstrcpynA(lpMl->szName, MIX_Names[SOUND_MIXER_VOLUME], MIXER_LONG_NAME_CHARS);
190         
191         /* we have all connections found in the MIX_DevMask */
192         lpMl->cConnections = 0;
193         for (j = 1; j < SOUND_MIXER_NRDEVICES; j++)
194             if (WINE_CHN_SUPPORTS(MIX_DevMask, j))
195                 lpMl->cConnections++;
196         if (WINE_CHN_SUPPORTS(MIX_StereoMask, SOUND_MIXER_VOLUME))
197             lpMl->cChannels++;
198         break;
199     case MIXER_GETLINEINFOF_SOURCE:
200         TRACE("SOURCE (%08lx)\n", lpMl->dwSource);
201         i = lpMl->dwSource;
202         for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
203             if (WINE_CHN_SUPPORTS(MIX_DevMask, j) && (i-- == 0)) 
204                 break;
205         }
206         if (j >= SOUND_MIXER_NRDEVICES)
207             return MIXERR_INVALLINE;
208         if (WINE_CHN_SUPPORTS(MIX_StereoMask, j))
209             lpMl->cChannels++;
210         if ((ret = MIX_GetLineInfoFromIndex(lpMl, MIX_DevMask, j)) != MMSYSERR_NOERROR)
211             return ret;
212         break;
213     case MIXER_GETLINEINFOF_LINEID:
214         TRACE("LINEID (%08lx)\n", lpMl->dwLineID);
215         if (lpMl->dwLineID >= SOUND_MIXER_NRDEVICES)
216             return MIXERR_INVALLINE;
217         if (WINE_CHN_SUPPORTS(MIX_StereoMask, lpMl->dwLineID))
218             lpMl->cChannels++;
219         if ((ret = MIX_GetLineInfoFromIndex(lpMl, MIX_DevMask, lpMl->dwLineID)) != MMSYSERR_NOERROR)
220             return ret;
221         break;
222     case MIXER_GETLINEINFOF_COMPONENTTYPE:
223         TRACE("COMPONENT TYPE (%08lx)\n", lpMl->dwComponentType);
224         
225         switch (lpMl->dwComponentType) {
226         case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
227             i = SOUND_MIXER_VOLUME;
228             lpMl->dwDestination = 0;
229             lpMl->dwSource = 0xFFFFFFFF;
230             isDst = TRUE;
231             break;
232         case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
233             i = SOUND_MIXER_SYNTH;
234             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
235             break;
236         case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC:
237             i = SOUND_MIXER_CD;
238             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
239             break;
240         case MIXERLINE_COMPONENTTYPE_SRC_LINE:
241             i = SOUND_MIXER_LINE;
242             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
243             break;
244         case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE:
245             i = SOUND_MIXER_MIC;
246             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
247             break;
248         case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
249             i = SOUND_MIXER_PCM;
250             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
251             break;
252         default:
253             FIXME("Unhandled component type (%08lx)\n", lpMl->dwComponentType);
254             return MMSYSERR_INVALPARAM;
255         }
256         
257         if (WINE_CHN_SUPPORTS(MIX_DevMask, i)) {
258             strcpy(lpMl->szShortName, MIX_Labels[i]);
259             strcpy(lpMl->szName, MIX_Names[i]);
260             lpMl->dwLineID = i;
261         }
262         if (WINE_CHN_SUPPORTS(MIX_StereoMask, i))
263             lpMl->cChannels++;
264         lpMl->cConnections = 0;
265         if (isDst) {
266             for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
267                 if (WINE_CHN_SUPPORTS(MIX_DevMask, j)) {
268                     lpMl->cConnections++;
269                 }
270             }
271         }
272         break;
273     case MIXER_GETLINEINFOF_TARGETTYPE:
274         FIXME("_TARGETTYPE not implemented yet.\n");
275         break;
276     default:
277         WARN("Unknown flag (%08lx)\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
278         break;
279     }
280     
281     lpMl->Target.dwType = MIXERLINE_TARGETTYPE_AUX;
282     lpMl->Target.dwDeviceID = 0xFFFFFFFF;
283     lpMl->Target.wMid = WINE_MIXER_MANUF_ID;
284     lpMl->Target.wPid = WINE_MIXER_PRODUCT_ID;
285     lpMl->Target.vDriverVersion = WINE_MIXER_VERSION;
286     strcpy(lpMl->Target.szPname, WINE_MIXER_NAME);
287     
288     return ret;
289 }
290
291 /**************************************************************************
292  *                              MIX_GetLineInfo                 [internal]
293  */
294 static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD flags)
295 {
296     TRACE("(%04X, %p, %lu);\n", wDevID, lpMod, flags);
297     if (lpMod == NULL) return MMSYSERR_INVALPARAM;
298     
299     return (MIX_DevMask == 0) ? MMSYSERR_NODRIVER : MMSYSERR_NOERROR;
300 }
301
302 /**************************************************************************
303  *                              MIX_MakeControlID               [internal]
304  */
305 static DWORD MIX_MakeControlID(DWORD lineID, DWORD controlType)
306 {
307     switch (controlType) {
308     case MIXERCONTROL_CONTROLTYPE_VOLUME:
309         return 2 * lineID + 0;
310     case MIXERCONTROL_CONTROLTYPE_MUTE:
311         return 2 * lineID + 1;
312     }
313     FIXME("Internal error");
314     return 0x00FADE00;
315 }
316
317 /**************************************************************************
318  *                              MIX_SplitControlID              [internal]
319  */
320 static BOOL MIX_SplitControlID(DWORD controlID, LPDWORD lineID, LPDWORD controlType)
321 {
322     *lineID = controlID / 2;
323     *controlType = (controlID & 1) ? 
324         MIXERCONTROL_CONTROLTYPE_MUTE : MIXERCONTROL_CONTROLTYPE_VOLUME;
325
326     return *lineID < SOUND_MIXER_NRDEVICES && WINE_CHN_SUPPORTS(MIX_DevMask, *lineID);
327 }
328
329 /**************************************************************************
330  *                              MIX_DoGetLineControls           [internal]
331  */
332 static void MIX_DoGetLineControls(LPMIXERCONTROLA mc, DWORD lineID, DWORD dwType)
333 {
334     mc->cbStruct = sizeof(MIXERCONTROLA);
335     
336     switch (dwType) {
337     case MIXERCONTROL_CONTROLTYPE_VOLUME:
338         TRACE("Returning volume control\n");
339         mc->dwControlID = MIX_MakeControlID(lineID, dwType);
340         mc->dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
341         mc->fdwControl = 0;
342         mc->cMultipleItems = 0;
343         lstrcpynA(mc->szShortName, "Vol", MIXER_SHORT_NAME_CHARS);
344         lstrcpynA(mc->szName, "Volume", MIXER_LONG_NAME_CHARS);
345         memset(&mc->Bounds, 0, sizeof(mc->Bounds));
346         /* CONTROLTYPE_VOLUME uses the MIXER_CONTROLDETAILS_UNSIGNED struct, 
347          * [0, 100] is the range supported by OSS
348          * FIXME: sounds like MIXERCONTROL_CONTROLTYPE_VOLUME is always between 0 and 65536
349          * whatever the min and max values are...
350          * look at conversions done in (Get|Set)ControlDetails to stay in [0, 100] range
351          */
352         mc->Bounds.s1.dwMinimum = 0;
353         mc->Bounds.s1.dwMaximum = 100;
354         memset(&mc->Metrics, 0, sizeof(mc->Metrics));
355         break;
356     case MIXERCONTROL_CONTROLTYPE_MUTE:
357         TRACE("Returning mute control\n");
358         mc->dwControlID = MIX_MakeControlID(lineID, dwType);
359         mc->dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
360         mc->fdwControl = 0;
361         mc->cMultipleItems = 0;
362         lstrcpynA(mc->szShortName, "Mute", MIXER_SHORT_NAME_CHARS);
363         lstrcpynA(mc->szName, "Mute", MIXER_LONG_NAME_CHARS);
364         memset(&mc->Bounds, 0, sizeof(mc->Bounds));
365         memset(&mc->Metrics, 0, sizeof(mc->Metrics));
366         break;
367     default:
368         FIXME("Internal error: unknown type: %08lx\n", dwType);
369     }
370 }
371
372 /**************************************************************************
373  *                              MIX_GetLineControls             [internal]
374  */
375 static  DWORD   MIX_GetLineControls(WORD wDevID, LPMIXERLINECONTROLSA lpMlc, DWORD flags)
376 {
377     DWORD               dwRet = MMSYSERR_NOERROR;
378     DWORD               lineID, controlType;
379
380     TRACE("(%04X, %p, %lu);\n", wDevID, lpMlc, flags);
381     
382     if (lpMlc == NULL) return MMSYSERR_INVALPARAM;
383     if (lpMlc->cbStruct < sizeof(*lpMlc) ||
384         lpMlc->cbmxctrl < sizeof(MIXERCONTROLA))
385         return MMSYSERR_INVALPARAM;
386
387     switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK) {
388     case MIXER_GETLINECONTROLSF_ALL:
389         TRACE("line=%08lx GLCF_ALL (%ld)\n", lpMlc->dwLineID, lpMlc->cControls);
390         if (lpMlc->cControls != 2) {
391             dwRet = MMSYSERR_INVALPARAM;
392         } else {
393             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME);
394             MIX_DoGetLineControls(&lpMlc->pamxctrl[1], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_MUTE);
395         }
396         break;
397     case MIXER_GETLINECONTROLSF_ONEBYID:
398         TRACE("line=%08lx GLCF_ONEBYID (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlID);
399         if (MIX_SplitControlID(lpMlc->u.dwControlID, &lineID, &controlType))
400             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lineID, controlType);
401         else 
402             dwRet = MMSYSERR_INVALPARAM;
403         break;
404     case MIXER_GETLINECONTROLSF_ONEBYTYPE:
405         TRACE("line=%08lx GLCF_ONEBYTYPE (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlType);
406         switch (lpMlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK) {
407         case MIXERCONTROL_CT_CLASS_FADER:
408             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME);
409             break;
410         case MIXERCONTROL_CT_CLASS_SWITCH:
411             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_MUTE);
412             break;
413         default:
414             dwRet = MMSYSERR_INVALPARAM;
415         }
416         break;
417     default:
418         ERR("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
419         dwRet = MMSYSERR_INVALPARAM;
420     }
421
422     return dwRet;
423 }
424
425 /**************************************************************************
426  *                              MIX_GetControlDetails           [internal]
427  */
428 static  DWORD   MIX_GetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
429 {
430     DWORD       ret = MMSYSERR_NOTSUPPORTED;
431     DWORD       lineID, controlType;
432
433     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
434     
435     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
436
437     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
438     case MIXER_GETCONTROLDETAILSF_VALUE:
439         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
440         if (MIX_SplitControlID(lpmcd->dwControlID, &lineID, &controlType)) {
441             switch (controlType) {
442             case MIXERCONTROL_CONTROLTYPE_VOLUME:
443                 {
444                     LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
445                     int                                 val;
446
447                     /* return value is 00RL (4 bytes)... */
448                     if ((val = MIX_Volume[lineID]) == -1 && !MIX_GetVal(lineID, &val))
449                         return MMSYSERR_INVALPARAM;
450             
451                     switch (lpmcd->cChannels) {
452                     case 1:
453                         /* mono... so R = L */
454                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
455                         mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
456                         break;
457                     case 2:
458                         /* stereo, left is paDetails[0] */
459                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
460                         mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
461                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
462                         mcdu->dwValue = (HIBYTE(LOWORD(val)) * 65536L) / 100;
463                         break;
464                     default:
465                         WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
466                         return MMSYSERR_INVALPARAM;
467                     }
468                     TRACE("=> %08lx\n", mcdu->dwValue);
469                 }
470                 break;
471             case MIXERCONTROL_CONTROLTYPE_MUTE:
472                 {
473                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
474                     
475                     /* we mute both channels at the same time */
476                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
477                     mcdb->fValue = (MIX_Volume[lineID] != -1);
478                 }
479                 break;
480             }
481             ret = MMSYSERR_NOERROR;
482         } else {
483             ret = MMSYSERR_INVALPARAM;
484         }
485         break;
486     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
487         FIXME("NIY\n");
488         break;
489     default:
490         WARN("Unknown flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
491     }
492     return ret;
493 }
494
495 /**************************************************************************
496  *                              MIX_SetControlDetails           [internal]
497  */
498 static  DWORD   MIX_SetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
499 {
500     DWORD       ret = MMSYSERR_NOTSUPPORTED;
501     DWORD       lineID, controlType;
502     int         val;
503     
504     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
505     
506     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
507     
508     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
509     case MIXER_GETCONTROLDETAILSF_VALUE:
510         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
511         if (MIX_SplitControlID(lpmcd->dwControlID, &lineID, &controlType)) {
512             switch (controlType) {
513             case MIXERCONTROL_CONTROLTYPE_VOLUME:
514                 {
515                     LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
516                     
517                     /* val should contain 00RL */
518                     switch (lpmcd->cChannels) {
519                     case 1:
520                         /* mono... so R = L */
521                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
522                         TRACE("Setting RL to %08ld\n", mcdu->dwValue);
523                         val = 0x101 * ((mcdu->dwValue * 100) >> 16);
524                         break;
525                     case 2:
526                         /* stereo, left is paDetails[0] */
527                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
528                         TRACE("Setting L to %08ld\n", mcdu->dwValue);
529                         val = ((mcdu->dwValue * 100) >> 16);
530                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
531                         TRACE("Setting R to %08ld\n", mcdu->dwValue);
532                         val += ((mcdu->dwValue * 100) >> 16) << 8;
533                         break;
534                     default:
535                         WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
536                         return MMSYSERR_INVALPARAM;
537                     }
538                     
539                     if (MIX_Volume[lineID] == -1) {
540                         if (!MIX_SetVal(lineID, val))
541                             return MMSYSERR_INVALPARAM;
542                     } else {
543                         MIX_Volume[lineID] = val;
544                     }
545                 }
546                 ret = MMSYSERR_NOERROR;
547                 break;
548             case MIXERCONTROL_CONTROLTYPE_MUTE:
549                 {
550                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
551                     
552                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
553                     if (mcdb->fValue) {
554                         if (!MIX_GetVal(lineID, &MIX_Volume[lineID]))
555                             return MMSYSERR_INVALPARAM;
556                         if (!MIX_SetVal(lineID, 0))
557                             return MMSYSERR_INVALPARAM;
558                     } else {
559                         if (!MIX_SetVal(lineID, MIX_Volume[lineID]))
560                             return MMSYSERR_INVALPARAM;
561                         MIX_Volume[lineID] = -1;
562                     }
563                 }
564                 ret = MMSYSERR_NOERROR;
565                 break;
566             }
567         }
568         break;
569     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
570         FIXME("NIY\n");
571         break;
572     default:
573         WARN("Unknown GetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
574     }
575     return MMSYSERR_NOTSUPPORTED;
576 }
577
578 /**************************************************************************
579  *                              MIX_GetNumDevs                  [internal]
580  */
581 static  DWORD   MIX_GetNumDevs(UINT wDevID)
582 {
583     return (MIX_DevMask != 0) ? 1 : 0;
584 }
585
586 /**************************************************************************
587  *                              MIX_Init                        [internal]
588  */
589 static  DWORD   MIX_Init(void)
590 {
591     int mixer, i;
592
593     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
594         if (errno == ENODEV || errno == ENXIO) {        
595             /* no driver present */
596             return MMSYSERR_NODRIVER;
597         }
598         return MMSYSERR_ERROR;
599     }
600     if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &MIX_DevMask) == -1) {
601         close(mixer);
602         perror("ioctl mixer SOUND_MIXER_DEVMASK");
603         return MMSYSERR_NOTENABLED;
604     }
605     MIX_DevMask &= WINE_MIXER_MASK;
606     if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &MIX_StereoMask) == -1) {
607         close(mixer);
608         perror("ioctl mixer SOUND_MIXER_STEREODEVS");
609         return MMSYSERR_NOTENABLED;
610     }
611     MIX_StereoMask &= WINE_MIXER_MASK;
612
613 #if 0
614     int                 recsrc, recmask;
615
616     if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) {
617         close(mixer);
618         perror("ioctl mixer SOUND_MIXER_RECSRC");
619         return MMSYSERR_NOTENABLED;
620     }
621
622     if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
623         close(mixer);
624         perror("ioctl mixer SOUND_MIXER_RECMASK");
625         return MMSYSERR_NOTENABLED;
626     }
627 #endif
628     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
629         MIX_Volume[i] = -1;
630     }
631     close(mixer);
632
633     return MMSYSERR_NOERROR;
634 }
635
636 #endif /* HAVE_OSS */
637
638 /**************************************************************************
639  *                              OSS_mixMessage          [sample driver]
640  */
641 DWORD WINAPI OSS_mixMessage(UINT wDevID, UINT wMsg, DWORD dwUser, 
642                             DWORD dwParam1, DWORD dwParam2)
643 {
644     TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", 
645           wDevID, wMsg, dwUser, dwParam1, dwParam2);
646     
647 #ifdef HAVE_OSS
648     switch(wMsg) {
649     case DRVM_INIT:
650         return MIX_Init();
651         break; 
652     case DRVM_EXIT:
653     case DRVM_ENABLE:
654     case DRVM_DISABLE:
655         /* FIXME: Pretend this is supported */
656         return 0;
657     case MXDM_GETDEVCAPS:       
658         return MIX_GetDevCaps(wDevID, (LPMIXERCAPSA)dwParam1, dwParam2);
659     case MXDM_GETLINEINFO:
660         return MIX_GetLineInfo(wDevID, (LPMIXERLINEA)dwParam1, dwParam2);
661     case MXDM_GETNUMDEVS:
662         return MIX_GetNumDevs(wDevID);
663     case MXDM_OPEN:
664         return MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2);
665     case MXDM_CLOSE:
666         return MMSYSERR_NOERROR;
667     case MXDM_GETLINECONTROLS:
668         return MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSA)dwParam1, dwParam2);
669     case MXDM_GETCONTROLDETAILS:
670         return MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
671     case MXDM_SETCONTROLDETAILS:
672         return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
673     default:
674         WARN("unknown message %d!\n", wMsg);
675     }
676     return MMSYSERR_NOTSUPPORTED;
677 #else
678     return MMSYSERR_NOTENABLED;
679 #endif
680 }
681
682