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