1 /*
2  * ReactOS Sound Volume Control
3  * Copyright (C) 2004-2005 Thomas Weidenmueller
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 /*
20  * COPYRIGHT:   See COPYING in the top level directory
21  * PROJECT:     ReactOS Sound Volume Control
22  * FILE:        base/applications/sndvol32/sndvol32.c
23  * PROGRAMMERS: Thomas Weidenmueller <w3seek@reactos.com>
24  */
25 
26 #include "sndvol32.h"
27 
28 #include <shellapi.h>
29 
30 HINSTANCE hAppInstance;
31 ATOM MainWindowClass;
32 HWND hMainWnd;
33 HANDLE hAppHeap;
34 LPTSTR lpAppTitle;
35 PREFERENCES_CONTEXT Preferences;
36 
37 #define GetDialogData(hwndDlg, type) \
38     ( P##type )GetWindowLongPtr((hwndDlg), DWLP_USER)
39 #define GetWindowData(hwnd, type) \
40     ( P##type )GetWindowLongPtr((hwnd), GWL_USERDATA)
41 
42 /******************************************************************************/
43 
44 
45 
46 typedef struct _PREFERENCES_FILL_DEVICES
47 {
48     PPREFERENCES_CONTEXT PrefContext;
49     HWND hComboBox;
50     UINT Selected;
51 } PREFERENCES_FILL_DEVICES, *PPREFERENCES_FILL_DEVICES;
52 
53 static BOOL CALLBACK
54 FillDeviceComboBox(PSND_MIXER Mixer,
55                    UINT Id,
56                    LPCTSTR ProductName,
57                    PVOID Context)
58 {
59     LRESULT lres;
60     PPREFERENCES_FILL_DEVICES FillContext = (PPREFERENCES_FILL_DEVICES)Context;
61 
62     UNREFERENCED_PARAMETER(Mixer);
63 
64     lres = SendMessage(FillContext->hComboBox,
65                        CB_ADDSTRING,
66                        0,
67                        (LPARAM)ProductName);
68     if (lres != CB_ERR)
69     {
70         /* save the index so we don't screw stuff when the combobox is sorted... */
71         SendMessage(FillContext->hComboBox,
72                     CB_SETITEMDATA,
73                     (WPARAM)lres,
74                     Id);
75 
76         if (Id == FillContext->Selected)
77         {
78             SendMessage(FillContext->hComboBox,
79                         CB_SETCURSEL,
80                         (WPARAM)lres,
81                         0);
82         }
83     }
84 
85     return TRUE;
86 }
87 
88 static BOOL CALLBACK
89 PrefDlgAddLine(PSND_MIXER Mixer,
90                LPMIXERLINE Line,
91                UINT DisplayControls,
92                PVOID Context)
93 {
94     PPREFERENCES_CONTEXT PrefContext = (PPREFERENCES_CONTEXT)Context;
95 
96     UNREFERENCED_PARAMETER(Mixer);
97     UNREFERENCED_PARAMETER(DisplayControls);
98 
99     switch (Line->dwComponentType)
100     {
101         case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
102             if (PrefContext->PlaybackID == (DWORD)-1)
103             {
104                 PrefContext->PlaybackID = Line->dwLineID;
105 
106                 if (PrefContext->SelectedLine == (DWORD)-1)
107                 {
108                     PrefContext->SelectedLine = Line->dwLineID;
109                 }
110             }
111             else
112                 goto AddToOthersLines;
113 
114             break;
115 
116         case MIXERLINE_COMPONENTTYPE_DST_WAVEIN:
117             if (PrefContext->RecordingID == (DWORD)-1)
118             {
119                 PrefContext->RecordingID = Line->dwLineID;
120 
121                 if (PrefContext->SelectedLine == (DWORD)-1)
122                 {
123                     PrefContext->SelectedLine = Line->dwLineID;
124                 }
125             }
126             else
127                 goto AddToOthersLines;
128 
129             break;
130 
131         default:
132         {
133             LRESULT lres;
134             HWND hwndCbOthers;
135 
136             if (PrefContext->SelectedLine == (DWORD)-1)
137             {
138                 PrefContext->SelectedLine = Line->dwLineID;
139             }
140 
141 AddToOthersLines:
142             hwndCbOthers = GetDlgItem(PrefContext->hwndDlg,
143                                       IDC_LINE);
144 
145             lres = SendMessage(hwndCbOthers,
146                                CB_ADDSTRING,
147                                0,
148                                (LPARAM)Line->szName);
149             if (lres != CB_ERR)
150             {
151                 SendMessage(hwndCbOthers,
152                             CB_SETITEMDATA,
153                             (WPARAM)lres,
154                             Line->dwLineID);
155 
156                 PrefContext->OtherLines++;
157             }
158             break;
159         }
160     }
161 
162     return TRUE;
163 }
164 
165 static BOOL CALLBACK
166 PrefDlgAddConnection(PSND_MIXER Mixer,
167                      DWORD LineID,
168                      LPMIXERLINE Line,
169                      PVOID Context)
170 {
171     PPREFERENCES_CONTEXT PrefContext = (PPREFERENCES_CONTEXT)Context;
172     HWND hwndControls;
173     LVITEM lvi;
174     UINT i;
175 
176     UNREFERENCED_PARAMETER(Mixer);
177     UNREFERENCED_PARAMETER(LineID);
178 
179     if (Line->cControls != 0)
180     {
181         hwndControls = GetDlgItem(PrefContext->hwndDlg,
182                                   IDC_CONTROLS);
183 
184         lvi.mask = LVIF_TEXT | LVIF_PARAM;
185         lvi.iItem = PrefContext->tmp++;
186         lvi.iSubItem = 0;
187         lvi.pszText = Line->szName;
188         lvi.lParam = (LPARAM)Line->dwSource;
189 
190         i = SendMessage(hwndControls,
191                         LVM_INSERTITEM,
192                         0,
193                         (LPARAM)&lvi);
194         if (i != (UINT)-1)
195         {
196             TCHAR LineName[MIXER_LONG_NAME_CHARS];
197             DWORD Flags;
198             BOOL SelLine = FALSE;
199 
200             if (SndMixerGetLineName(PrefContext->Mixer,
201                                     PrefContext->SelectedLine,
202                                     LineName,
203                                     MIXER_LONG_NAME_CHARS,
204                                     TRUE) == -1)
205             {
206                 LineName[0] = TEXT('\0');
207             }
208 
209             if (ReadLineConfig(PrefContext->DeviceName,
210                                LineName,
211                                Line->szName,
212                                &Flags))
213             {
214                 if (Flags != 0x4)
215                 {
216                     SelLine = TRUE;
217                 }
218             }
219 
220             ListView_SetCheckState(hwndControls,
221                                    i,
222                                    SelLine);
223         }
224     }
225 
226     return TRUE;
227 }
228 
229 static VOID
230 UpdatePrefDlgControls(PPREFERENCES_CONTEXT Context,
231                       DWORD LineID)
232 {
233     INT OldID, MixerID = 0;
234     INT DeviceCbIndex;
235 
236     /* select the mixer */
237     DeviceCbIndex = SendDlgItemMessage(Context->hwndDlg,
238                                        IDC_MIXERDEVICE,
239                                        CB_GETCURSEL,
240                                        0,
241                                        0);
242     if (DeviceCbIndex != CB_ERR)
243     {
244         MixerID = SendDlgItemMessage(Context->hwndDlg,
245                                      IDC_MIXERDEVICE,
246                                      CB_GETITEMDATA,
247                                      DeviceCbIndex,
248                                      0);
249         if (MixerID == CB_ERR)
250         {
251             MixerID = 0;
252         }
253     }
254 
255     OldID = Context->Selected;
256     if (MixerID != OldID &&
257         SndMixerSelect(Context->Mixer,
258                        MixerID))
259     {
260         Context->Selected = SndMixerGetSelection(Context->Mixer);
261 
262         /* update the controls */
263         Context->PlaybackID = (DWORD)-1;
264         Context->RecordingID = (DWORD)-1;
265         Context->OtherLines = 0;
266         Context->SelectedLine = (DWORD)-1;
267 
268         SndMixerGetProductName(Context->Mixer,
269                                Context->DeviceName,
270                                sizeof(Context->DeviceName) / sizeof(Context->DeviceName[0]));
271 
272         if (SndMixerEnumLines(Context->Mixer,
273                               PrefDlgAddLine,
274                               Context))
275         {
276             UINT SelBox = 0;
277 
278             /* enable/disable controls and make default selection */
279             EnableWindow(GetDlgItem(Context->hwndDlg,
280                                     IDC_PLAYBACK),
281                          Context->PlaybackID != (DWORD)-1);
282             CheckDlgButton(Context->hwndDlg,
283                            IDC_PLAYBACK,
284                            (Context->PlaybackID != (DWORD)-1 && SelBox++ == 0) ?
285                                BST_CHECKED : BST_UNCHECKED);
286 
287             EnableWindow(GetDlgItem(Context->hwndDlg,
288                                     IDC_RECORDING),
289                          Context->RecordingID != (DWORD)-1);
290             CheckDlgButton(Context->hwndDlg,
291                            IDC_RECORDING,
292                            (Context->RecordingID != (DWORD)-1 && SelBox++ == 0) ?
293                                BST_CHECKED : BST_UNCHECKED);
294 
295             if (Context->OtherLines != 0)
296             {
297                 /* select the first item in the other lines combo box by default */
298                 SendDlgItemMessage(Context->hwndDlg,
299                                    IDC_LINE,
300                                    CB_SETCURSEL,
301                                    0,
302                                    0);
303             }
304             EnableWindow(GetDlgItem(Context->hwndDlg,
305                                     IDC_LINE),
306                          FALSE);
307             EnableWindow(GetDlgItem(Context->hwndDlg,
308                                     IDC_OTHER),
309                          Context->OtherLines != 0);
310             CheckDlgButton(Context->hwndDlg,
311                            IDC_LINE,
312                            (Context->OtherLines != 0 && SelBox++ == 0) ?
313                                BST_CHECKED : BST_UNCHECKED);
314 
315             /* disable the OK button if the device doesn't have any lines */
316             EnableWindow(GetDlgItem(Context->hwndDlg,
317                                     IDOK),
318                          Context->PlaybackID != (DWORD)-1 ||
319                          Context->RecordingID != (DWORD)-1 ||
320                          Context->OtherLines != 0);
321 
322             LineID = Context->SelectedLine;
323         }
324     }
325 
326     /* update the line sources list */
327     if ((MixerID != OldID && Context->SelectedLine != (DWORD)-1) ||
328         (Context->SelectedLine != LineID && LineID != (DWORD)-1))
329     {
330         Context->SelectedLine = LineID;
331 
332         (void)ListView_DeleteAllItems(GetDlgItem(Context->hwndDlg,
333                                       IDC_CONTROLS));
334 
335         Context->tmp = 0;
336         SndMixerEnumConnections(Context->Mixer,
337                                 LineID,
338                                 PrefDlgAddConnection,
339                                 Context);
340     }
341 }
342 
343 static
344 VOID
345 WriteLineSettings(PPREFERENCES_CONTEXT Context, HWND hwndDlg)
346 {
347     HWND hwndControls;
348     INT Count, Index;
349     WCHAR LineName[MIXER_LONG_NAME_CHARS];
350     WCHAR DestinationName[MIXER_LONG_NAME_CHARS];
351     DWORD Flags;
352     PSNDVOL_REG_LINESTATE LineStates;
353 
354     /* get list view */
355     hwndControls = GetDlgItem(hwndDlg, IDC_CONTROLS);
356 
357     /* get list item count */
358     Count = ListView_GetItemCount(hwndControls);
359 
360     /* sanity check */
361     assert(Count);
362 
363     if (SndMixerGetLineName(Context->Mixer, Context->SelectedLine, DestinationName, MIXER_LONG_NAME_CHARS, TRUE) == -1)
364     {
365         /* failed to get destination line name */
366         return;
367     }
368 
369     /* allocate line states array */
370     LineStates = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SNDVOL_REG_LINESTATE) * Count);
371     if (LineStates == NULL)
372     {
373         /* failed to allocate line states array */
374         return;
375     }
376 
377 
378     for(Index = 0; Index < Count; Index++)
379     {
380         /* set to empty */
381         LineName[0] = L'\0';
382 
383         /* get item text */
384         ListView_GetItemText(hwndControls, Index, 0, LineName, MIXER_LONG_NAME_CHARS);
385 
386         /* make sure it is null terminated */
387         LineName[MIXER_LONG_NAME_CHARS-1] = L'\0';
388 
389         /* get check state */
390         Flags = (ListView_GetCheckState(hwndControls, Index) == 0 ? 0x4 : 0);
391 
392         /* copy line name */
393         wcscpy(LineStates[Index].LineName, LineName);
394 
395         /* store flags */
396         LineStates[Index].Flags = Flags;
397     }
398 
399     /* now write the line config */
400     WriteLineConfig(Context->DeviceName, DestinationName, LineStates, sizeof(SNDVOL_REG_LINESTATE) * Count);
401 
402     /* free line states */
403     HeapFree(GetProcessHeap(), 0, LineStates);
404 }
405 
406 static INT_PTR CALLBACK
407 DlgPreferencesProc(HWND hwndDlg,
408                    UINT uMsg,
409                    WPARAM wParam,
410                    LPARAM lParam)
411 {
412     PPREFERENCES_CONTEXT Context;
413 
414     switch (uMsg)
415     {
416         case WM_COMMAND:
417         {
418             Context = GetDialogData(hwndDlg,
419                                     PREFERENCES_CONTEXT);
420             switch (LOWORD(wParam))
421             {
422                 case IDC_MIXERDEVICE:
423                 {
424                     if (HIWORD(wParam) == CBN_SELCHANGE)
425                     {
426                         UpdatePrefDlgControls(Context,
427                                               (DWORD)-1);
428                     }
429                     break;
430                 }
431 
432                 case IDC_LINE:
433                 {
434                     if (HIWORD(wParam) == CBN_SELCHANGE)
435                     {
436                         INT LineID;
437                         INT Index;
438 
439                         Index = SendDlgItemMessage(hwndDlg,
440                                                    IDC_LINE,
441                                                    CB_GETCURSEL,
442                                                    0,
443                                                    0);
444                         if (Index != CB_ERR)
445                         {
446                             LineID = SendDlgItemMessage(hwndDlg,
447                                                         IDC_LINE,
448                                                         CB_GETITEMDATA,
449                                                         Index,
450                                                         0);
451                             if (LineID != CB_ERR)
452                             {
453                                 UpdatePrefDlgControls(Context,
454                                                       LineID);
455                             }
456                         }
457                     }
458                     break;
459                 }
460 
461                 case IDC_PLAYBACK:
462                 {
463                     UpdatePrefDlgControls(Context,
464                                           Context->PlaybackID);
465                     EnableWindow(GetDlgItem(hwndDlg,
466                                             IDC_LINE),
467                                  FALSE);
468                     break;
469                 }
470 
471                 case IDC_RECORDING:
472                 {
473                     UpdatePrefDlgControls(Context,
474                                           Context->RecordingID);
475                     EnableWindow(GetDlgItem(hwndDlg,
476                                             IDC_LINE),
477                                  FALSE);
478                     break;
479                 }
480 
481                 case IDC_OTHER:
482                 {
483                     INT LineCbIndex;
484                     INT LineID;
485 
486                     EnableWindow(GetDlgItem(hwndDlg,
487                                             IDC_LINE),
488                                  TRUE);
489 
490                     LineCbIndex = SendDlgItemMessage(hwndDlg,
491                                                      IDC_LINE,
492                                                      CB_GETCURSEL,
493                                                      0,
494                                                      0);
495                     if (LineCbIndex != CB_ERR)
496                     {
497                         LineID = SendDlgItemMessage(hwndDlg,
498                                                     IDC_LINE,
499                                                     CB_GETITEMDATA,
500                                                     LineCbIndex,
501                                                     0);
502                         if (LineID != CB_ERR)
503                         {
504                             UpdatePrefDlgControls(Context,
505                                                   LineID);
506                         }
507                     }
508                     break;
509                 }
510 
511                 case IDOK:
512                 {
513                     /* write line settings */
514                     WriteLineSettings(Context, hwndDlg);
515 
516                     /* fall through */
517                 }
518                 case IDCANCEL:
519                 {
520                     EndDialog(hwndDlg,
521                               LOWORD(wParam));
522                     break;
523                 }
524             }
525             break;
526         }
527 
528         case WM_INITDIALOG:
529         {
530             PREFERENCES_FILL_DEVICES FillDevContext;
531             LVCOLUMN lvc;
532             RECT rcClient;
533             HWND hwndControls;
534 
535             SetWindowLongPtr(hwndDlg,
536                              DWLP_USER,
537                              (LONG_PTR)lParam);
538             Context = (PPREFERENCES_CONTEXT)((LONG_PTR)lParam);
539             Context->hwndDlg = hwndDlg;
540             Context->Mixer = SndMixerCreate(hwndDlg, Context->MixerWindow->MixerId);
541             Context->Selected = (UINT)-1;
542 
543             FillDevContext.PrefContext = Context;
544             FillDevContext.hComboBox = GetDlgItem(hwndDlg,
545                                                   IDC_MIXERDEVICE);
546             FillDevContext.Selected = SndMixerGetSelection(Context->Mixer);
547             SndMixerEnumProducts(Context->Mixer,
548                                  FillDeviceComboBox,
549                                  &FillDevContext);
550 
551             /* initialize the list view control */
552             hwndControls = GetDlgItem(hwndDlg,
553                                       IDC_CONTROLS);
554             (void)ListView_SetExtendedListViewStyle(hwndControls,
555                                                     LVS_EX_CHECKBOXES);
556 
557             GetClientRect(hwndControls,
558                           &rcClient);
559             lvc.mask = LVCF_TEXT | LVCF_WIDTH;
560             lvc.pszText = TEXT("");
561             lvc.cx = rcClient.right;
562             SendMessage(hwndControls,
563                         LVM_INSERTCOLUMN,
564                         0,
565                         (LPARAM)&lvc);
566 
567             /* update all controls */
568             UpdatePrefDlgControls(Context,
569                                   (DWORD)Context->SelectedLine);
570             return TRUE;
571         }
572 
573         case WM_CLOSE:
574         {
575             EndDialog(hwndDlg,
576                       IDCANCEL);
577             break;
578         }
579 
580         case WM_SYSCOLORCHANGE:
581         {
582             HWND hwndControls;
583 
584             /* Forward WM_SYSCOLORCHANGE */
585             hwndControls = GetDlgItem(hwndDlg, IDC_CONTROLS);
586             SendMessage(hwndControls, WM_SYSCOLORCHANGE, 0, 0);
587             break;
588         }
589     }
590 
591     return 0;
592 }
593 
594 /******************************************************************************/
595 
596 static VOID
597 DeleteMixerWindowControls(PMIXER_WINDOW MixerWindow)
598 {
599     DWORD Index;
600 
601     for(Index = 0; Index < MixerWindow->WindowCount; Index++)
602     {
603         /* destroys the window */
604         DestroyWindow(MixerWindow->Window[Index]);
605     }
606 
607     /* free memory */
608     HeapFree(GetProcessHeap(), 0, MixerWindow->Window);
609 
610     /* set to null */
611     MixerWindow->Window = NULL;
612     MixerWindow->WindowCount = 0;
613 }
614 
615 static BOOL
616 RebuildMixerWindowControls(PPREFERENCES_CONTEXT PrefContext)
617 {
618     /* delete existing mixer controls */
619     DeleteMixerWindowControls(PrefContext->MixerWindow);
620 
621     /* load new mixer controls */
622     LoadDialogCtrls(PrefContext);
623 
624     return TRUE;
625 }
626 
627 static
628 BOOL
629 CALLBACK
630 SetVolumeCallback(PSND_MIXER Mixer, DWORD LineID, LPMIXERLINE Line, PVOID Ctx)
631 {
632     UINT ControlCount = 0, Index;
633     LPMIXERCONTROL Control = NULL;
634     PMIXERCONTROLDETAILS_UNSIGNED puDetails = NULL;
635     MIXERCONTROLDETAILS_BOOLEAN bDetails;
636     PSET_VOLUME_CONTEXT Context = (PSET_VOLUME_CONTEXT)Ctx;
637 
638     /* check if the line name is equal */
639     if (wcsicmp(Line->szName, Context->LineName))
640     {
641         /* it is not */
642         return TRUE;
643     }
644 
645     /* query controls */
646     if (SndMixerQueryControls(Mixer, &ControlCount, Line, &Control) == FALSE)
647     {
648         /* failed to query for controls */
649         return FALSE;
650     }
651 
652     puDetails = HeapAlloc(GetProcessHeap(), 0, Line->cChannels * sizeof(MIXERCONTROLDETAILS_UNSIGNED));
653     if (puDetails == NULL)
654         return FALSE;
655 
656     /* now go through all controls and compare control ids */
657     for (Index = 0; Index < ControlCount; Index++)
658     {
659         if (Context->bVertical)
660         {
661             if (Control[Index].dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)
662             {
663                 DWORD LineOffset, volumePosition, balancePosition;
664                 DWORD volumeStep, balanceStep;
665 
666                 LineOffset = Context->SliderPos;
667 
668                 volumePosition = (DWORD)SendDlgItemMessage(Preferences.MixerWindow->hWnd, LineOffset * IDC_LINE_SLIDER_VERT, TBM_GETPOS, 0, 0);
669                 volumeStep = (Control[Index].Bounds.dwMaximum - Control[Index].Bounds.dwMinimum) / (VOLUME_MAX - VOLUME_MIN);
670 
671                 if (Line->cChannels == 1)
672                 {
673                     /* set up details */
674                     puDetails[0].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
675                 }
676                 else if (Line->cChannels == 2)
677                 {
678                     balancePosition = (DWORD)SendDlgItemMessage(Preferences.MixerWindow->hWnd, LineOffset * IDC_LINE_SLIDER_HORZ, TBM_GETPOS, 0, 0);
679                     if (balancePosition == BALANCE_CENTER)
680                     {
681                         puDetails[0].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
682                         puDetails[1].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
683                     }
684                     else if (balancePosition == BALANCE_LEFT)
685                     {
686                         puDetails[0].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
687                         puDetails[1].dwValue = Control[Index].Bounds.dwMinimum;
688                     }
689                     else if (balancePosition == BALANCE_RIGHT)
690                     {
691                         puDetails[0].dwValue = Control[Index].Bounds.dwMinimum;
692                         puDetails[1].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
693                     }
694                     else if (balancePosition < BALANCE_CENTER) // Left
695                     {
696                         puDetails[0].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
697 
698                         balanceStep = (puDetails[0].dwValue - Control[Index].Bounds.dwMinimum) / (BALANCE_STEPS / 2);
699 
700                         puDetails[1].dwValue = (balancePosition * balanceStep) + Control[Index].Bounds.dwMinimum;
701                     }
702                     else if (balancePosition > BALANCE_CENTER) // Right
703                     {
704                         puDetails[1].dwValue = ((VOLUME_MAX - volumePosition) * volumeStep) + Control[Index].Bounds.dwMinimum;
705 
706                         balanceStep = (puDetails[1].dwValue - Control[Index].Bounds.dwMinimum) / (BALANCE_STEPS / 2);
707 
708                         puDetails[0].dwValue = ((BALANCE_RIGHT - balancePosition) * balanceStep) + Control[Index].Bounds.dwMinimum;
709                     }
710                 }
711                 else
712                 {
713                     SndMixerGetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, Line->cChannels, sizeof(MIXERCONTROLDETAILS_UNSIGNED), (LPVOID)puDetails);
714 
715                     /* FIXME */
716                 }
717 
718                 /* set volume */
719                 SndMixerSetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, Line->cChannels, sizeof(MIXERCONTROLDETAILS_UNSIGNED), (LPVOID)puDetails);
720 
721                 /* done */
722                 break;
723             }
724         }
725         else if (Context->bSwitch)
726         {
727             if (Control[Index].dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE)
728             {
729                 /* set up details */
730                 bDetails.fValue = Context->SliderPos;
731 
732                 /* set volume */
733                 SndMixerSetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, 1, sizeof(MIXERCONTROLDETAILS_BOOLEAN), (LPVOID)&bDetails);
734 
735                 /* done */
736                 break;
737             }
738         }
739     }
740 
741     if (puDetails != NULL)
742         HeapFree(GetProcessHeap(), 0, puDetails);
743 
744     /* free controls */
745     HeapFree(GetProcessHeap(), 0, Control);
746 
747 
748     /* done */
749     return TRUE;
750 }
751 
752 static
753 BOOL
754 CALLBACK
755 MixerControlChangeCallback(PSND_MIXER Mixer, DWORD LineID, LPMIXERLINE Line, PVOID Context)
756 {
757     PMIXERCONTROLDETAILS_UNSIGNED pVolumeDetails = NULL;
758     UINT ControlCount = 0, Index;
759     LPMIXERCONTROL Control = NULL;
760 
761     /* check if the line has controls */
762     if (Line->cControls == 0)
763     {
764         /* no controls */
765         return TRUE;
766     }
767 
768     /* query controls */
769     if (SndMixerQueryControls(Mixer, &ControlCount, Line, &Control) == FALSE)
770     {
771         /* failed to query for controls */
772         return FALSE;
773     }
774 
775     pVolumeDetails = HeapAlloc(GetProcessHeap(),
776                                0,
777                                Line->cChannels * sizeof(MIXERCONTROLDETAILS_UNSIGNED));
778     if (pVolumeDetails == NULL)
779         goto done;
780 
781     /* now go through all controls and compare control ids */
782     for (Index = 0; Index < ControlCount; Index++)
783     {
784         if (Control[Index].dwControlID == PtrToUlong(Context))
785         {
786             if (Control[Index].dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE)
787             {
788                 MIXERCONTROLDETAILS_BOOLEAN Details;
789 
790                 /* get volume control details */
791                 if (SndMixerGetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, 1, sizeof(MIXERCONTROLDETAILS_BOOLEAN), (LPVOID)&Details) != -1)
792                 {
793                     /* update dialog control */
794                     UpdateDialogLineSwitchControl(&Preferences, Line, Details.fValue);
795                 }
796             }
797             else if (Control[Index].dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)
798             {
799                 /* get volume control details */
800                 if (SndMixerGetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, Line->cChannels, sizeof(MIXERCONTROLDETAILS_UNSIGNED), (LPVOID)pVolumeDetails) != -1)
801                 {
802                     /* update dialog control */
803                     DWORD volumePosition, volumeStep, maxVolume, i;
804                     DWORD balancePosition, balanceStep;
805 
806                     volumeStep = (Control[Index].Bounds.dwMaximum - Control[Index].Bounds.dwMinimum) / (VOLUME_MAX - VOLUME_MIN);
807 
808                     maxVolume = 0;
809                     for (i = 0; i < Line->cChannels; i++)
810                     {
811                         if (pVolumeDetails[i].dwValue > maxVolume)
812                             maxVolume = pVolumeDetails[i].dwValue;
813                     }
814 
815                     volumePosition = (maxVolume - Control[Index].Bounds.dwMinimum) / volumeStep;
816 
817                     if (Line->cChannels == 1)
818                     {
819                         balancePosition = BALANCE_CENTER;
820                     }
821                     else if (Line->cChannels == 2)
822                     {
823                         if (pVolumeDetails[0].dwValue == pVolumeDetails[1].dwValue)
824                         {
825                             balancePosition = BALANCE_CENTER;
826                         }
827                         else if (pVolumeDetails[0].dwValue == Control[Index].Bounds.dwMinimum)
828                         {
829                             balancePosition = BALANCE_RIGHT;
830                         }
831                         else if (pVolumeDetails[1].dwValue == Control[Index].Bounds.dwMinimum)
832                         {
833                             balancePosition = BALANCE_LEFT;
834                         }
835                         else
836                         {
837                             balanceStep = (maxVolume - Control[Index].Bounds.dwMinimum) / (BALANCE_STEPS / 2);
838 
839                             if (pVolumeDetails[0].dwValue < pVolumeDetails[1].dwValue)
840                             {
841                                 balancePosition = (pVolumeDetails[0].dwValue - Control[Index].Bounds.dwMinimum) / balanceStep;
842                                 balancePosition = BALANCE_RIGHT - balancePosition;
843                             }
844                             else if (pVolumeDetails[1].dwValue < pVolumeDetails[0].dwValue)
845                             {
846                                 balancePosition = (pVolumeDetails[1].dwValue - Control[Index].Bounds.dwMinimum) / balanceStep;
847                                 balancePosition = BALANCE_LEFT + balancePosition;
848                             }
849                         }
850                     }
851 
852                     /* Update the volume control slider */
853                     UpdateDialogLineSliderControl(&Preferences, Line, IDC_LINE_SLIDER_VERT, VOLUME_MAX - volumePosition);
854 
855                     /* Update the balance control slider */
856                     UpdateDialogLineSliderControl(&Preferences, Line, IDC_LINE_SLIDER_HORZ, balancePosition);
857                 }
858             }
859             break;
860         }
861     }
862 
863 done:
864     /* Free the volume details */
865     if (pVolumeDetails)
866         HeapFree(GetProcessHeap(), 0, pVolumeDetails);
867 
868     /* free controls */
869     HeapFree(GetProcessHeap(), 0, Control);
870 
871     /* done */
872     return TRUE;
873 }
874 
875 static LRESULT CALLBACK
876 MainWindowProc(HWND hwnd,
877                UINT uMsg,
878                WPARAM wParam,
879                LPARAM lParam)
880 {
881     PMIXER_WINDOW MixerWindow;
882     DWORD CtrlID, LineOffset;
883     LRESULT Result = 0;
884     SET_VOLUME_CONTEXT Context;
885 
886     switch (uMsg)
887     {
888         case WM_COMMAND:
889         {
890             MixerWindow = GetWindowData(hwnd,
891                                         MIXER_WINDOW);
892 
893             switch (LOWORD(wParam))
894             {
895                 case IDM_PROPERTIES:
896                 {
897                     PREFERENCES_CONTEXT Pref;
898 
899                     Pref.MixerWindow = MixerWindow;
900                     Pref.Mixer = NULL;
901                     Pref.SelectedLine = Preferences.SelectedLine;
902 
903                     if (DialogBoxParam(hAppInstance,
904                                        MAKEINTRESOURCE(IDD_PREFERENCES),
905                                        hwnd,
906                                        DlgPreferencesProc,
907                                        (LPARAM)&Pref) == IDOK)
908                     {
909                         /* update window */
910                         TCHAR szProduct[MAXPNAMELEN];
911 
912                         /* get mixer product name */
913                         if (SndMixerGetProductName(Pref.Mixer,
914                                                    szProduct,
915                                                    sizeof(szProduct) / sizeof(szProduct[0])) == -1)
916                         {
917                             /* failed to get name */
918                             szProduct[0] = L'\0';
919                         }
920                         else
921                         {
922                             /* copy product */
923                             wcscpy(Preferences.DeviceName, szProduct);
924                         }
925 
926                         /* destroy old status bar */
927                         if (MixerWindow->Mode == NORMAL_MODE)
928                             DestroyWindow(MixerWindow->hStatusBar);
929 
930                         /* update details */
931                         Preferences.SelectedLine = Pref.SelectedLine;
932 
933                         /* destroy old mixer */
934                         SndMixerDestroy(Preferences.MixerWindow->Mixer);
935 
936                         /* use new selected mixer */
937                         Preferences.MixerWindow->Mixer = Pref.Mixer;
938 
939                         /* create status window */
940                         if (MixerWindow->Mode == NORMAL_MODE)
941                         {
942                             MixerWindow->hStatusBar = CreateStatusWindow(WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS,
943                                                                          NULL,
944                                                                          hwnd,
945                                                                          0);
946                             if (MixerWindow->hStatusBar)
947                             {
948                                 /* Set status bar */
949                                 SendMessage(MixerWindow->hStatusBar,
950                                     WM_SETTEXT,
951                                     0,
952                                     (LPARAM)szProduct);
953                             }
954                         }
955 
956                         /* rebuild dialog controls */
957                         RebuildMixerWindowControls(&Preferences);
958                     }
959                     break;
960                 }
961 
962                 case IDM_ADVANCED_CONTROLS:
963                     MixerWindow->bShowExtendedControls = !MixerWindow->bShowExtendedControls;
964                     CheckMenuItem(GetMenu(hwnd),
965                                   IDM_ADVANCED_CONTROLS,
966                                   MF_BYCOMMAND | (MixerWindow->bShowExtendedControls ? MF_CHECKED : MF_UNCHECKED));
967                     RebuildMixerWindowControls(&Preferences);
968                     break;
969 
970                 case IDM_EXIT:
971                 {
972                     PostQuitMessage(0);
973                     break;
974                 }
975 
976                 case IDM_ABOUT:
977                 {
978                     HICON hAppIcon = (HICON)GetClassLongPtrW(hwnd,
979                                                              GCLP_HICON);
980                     ShellAbout(hwnd,
981                                lpAppTitle,
982                                NULL,
983                                hAppIcon);
984                     break;
985                 }
986 
987                 default:
988                 {
989                     /* get button id */
990                     CtrlID = LOWORD(wParam);
991 
992                     /* check if the message is from the line switch */
993                     if (HIWORD(wParam) == BN_CLICKED)
994                     {
995                         if (CtrlID % IDC_LINE_SWITCH == 0)
996                         {
997                             /* compute line offset */
998                             LineOffset = CtrlID / IDC_LINE_SWITCH;
999 
1000                             /* compute window id of line name static control */
1001                             CtrlID = LineOffset * IDC_LINE_NAME;
1002 
1003                             if (Preferences.MixerWindow->Mixer->MixerId == PLAY_MIXER)
1004                             {
1005                                 /* get line name */
1006                                 if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
1007                                 {
1008                                     /* setup context */
1009                                     Context.SliderPos = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0);
1010                                     Context.bVertical = FALSE;
1011                                     Context.bSwitch = TRUE;
1012 
1013                                     /* set volume */
1014                                     SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
1015                                 }
1016                             }
1017                             else if (Preferences.MixerWindow->Mixer->MixerId == RECORD_MIXER)
1018                             {
1019                                 UINT i;
1020 
1021                                 for (i = 0; i < Preferences.MixerWindow->DialogCount; i++)
1022                                 {
1023                                     SendDlgItemMessageW(hwnd, (i + 1) * IDC_LINE_SWITCH, BM_SETCHECK, (WPARAM)((i + 1) == LineOffset), 0);
1024                                 }
1025                             }
1026                         }
1027                         else if (CtrlID % IDC_LINE_ADVANCED == 0)
1028                         {
1029                             ADVANCED_CONTEXT AdvancedContext;
1030 
1031                             /* compute line offset */
1032                             LineOffset = CtrlID / IDC_LINE_ADVANCED;
1033 
1034                             /* compute window id of line name static control */
1035                             CtrlID = LineOffset * IDC_LINE_NAME;
1036 
1037                             /* get line name */
1038                             if (GetDlgItemTextW(hwnd, CtrlID, AdvancedContext.LineName, MIXER_LONG_NAME_CHARS) != 0)
1039                             {
1040                                 AdvancedContext.MixerWindow = Preferences.MixerWindow;
1041                                 AdvancedContext.Mixer = Preferences.MixerWindow->Mixer;
1042                                 AdvancedContext.Line = SndMixerGetLineByName(Preferences.MixerWindow->Mixer,
1043                                                                              Preferences.SelectedLine,
1044                                                                              AdvancedContext.LineName);
1045                                 if (AdvancedContext.Line)
1046                                 {
1047                                     DialogBoxParam(hAppInstance,
1048                                                    MAKEINTRESOURCE(IDD_ADVANCED),
1049                                                    hwnd,
1050                                                    AdvancedDlgProc,
1051                                                    (LPARAM)&AdvancedContext);
1052                                 }
1053                             }
1054                         }
1055                     }
1056                 }
1057             }
1058             break;
1059         }
1060 
1061         case MM_MIXM_LINE_CHANGE:
1062         {
1063             DPRINT("MM_MIXM_LINE_CHANGE\n");
1064             break;
1065         }
1066 
1067         case MM_MIXM_CONTROL_CHANGE:
1068         {
1069             DPRINT("MM_MIXM_CONTROL_CHANGE\n");
1070 
1071             /* get mixer window */
1072             MixerWindow = GetWindowData(hwnd,
1073                                         MIXER_WINDOW);
1074 
1075             /* sanity checks */
1076             assert(MixerWindow);
1077             assert(MixerWindow->Mixer->hmx == (HMIXER)wParam);
1078 
1079             SndMixerEnumConnections(MixerWindow->Mixer, Preferences.SelectedLine, MixerControlChangeCallback, (PVOID)lParam);
1080             break;
1081         }
1082 
1083         case WM_VSCROLL:
1084             switch (LOWORD(wParam))
1085             {
1086                 case TB_THUMBTRACK:
1087                     /* get dialog item ctrl */
1088                     CtrlID = GetDlgCtrlID((HWND)lParam);
1089 
1090                     /* get line index */
1091                     LineOffset = CtrlID / IDC_LINE_SLIDER_VERT;
1092 
1093                     /* compute window id of line name static control */
1094                     CtrlID = LineOffset * IDC_LINE_NAME;
1095 
1096                     /* get line name */
1097                     if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
1098                     {
1099                         /* setup context */
1100                         Context.SliderPos = LineOffset;
1101                         Context.bVertical = TRUE;
1102                         Context.bSwitch = FALSE;
1103 
1104                         /* set volume */
1105                         SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
1106                     }
1107                     break;
1108 
1109                 case TB_ENDTRACK:
1110                     MixerWindow = GetWindowData(hwnd,
1111                                                 MIXER_WINDOW);
1112 
1113                     /* get dialog item ctrl */
1114                     CtrlID = GetDlgCtrlID((HWND)lParam);
1115 
1116                     /* get line index */
1117                     LineOffset = CtrlID / IDC_LINE_SLIDER_VERT;
1118 
1119                     if (LineOffset == 1 && MixerWindow->Mixer->MixerId == 0)
1120                         PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, NULL, SND_ASYNC | SND_ALIAS_ID);
1121                     break;
1122 
1123                 default:
1124                     break;
1125             }
1126             break;
1127 
1128         case WM_HSCROLL:
1129             switch (LOWORD(wParam))
1130             {
1131                 case TB_THUMBTRACK:
1132                     /* get dialog item ctrl */
1133                     CtrlID = GetDlgCtrlID((HWND)lParam);
1134 
1135                     /* get line index */
1136                     LineOffset = CtrlID / IDC_LINE_SLIDER_HORZ;
1137 
1138                     /* compute window id of line name static control */
1139                     CtrlID = LineOffset * IDC_LINE_NAME;
1140 
1141                     /* get line name */
1142                     if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
1143                     {
1144                         /* setup context */
1145                         Context.SliderPos = LineOffset;
1146                         Context.bVertical = TRUE;
1147                         Context.bSwitch = FALSE;
1148 
1149                         /* set volume */
1150                         SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
1151                     }
1152                     break;
1153 
1154                 case TB_ENDTRACK:
1155                     MixerWindow = GetWindowData(hwnd,
1156                                                 MIXER_WINDOW);
1157 
1158                     /* get dialog item ctrl */
1159                     CtrlID = GetDlgCtrlID((HWND)lParam);
1160 
1161                     /* get line index */
1162                     LineOffset = CtrlID / IDC_LINE_SLIDER_HORZ;
1163 
1164                     if (LineOffset == 1 && MixerWindow->Mixer->MixerId == 0)
1165                         PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, NULL, SND_ASYNC | SND_ALIAS_ID);
1166                     break;
1167 
1168                 default:
1169                     break;
1170             }
1171             break;
1172 
1173 
1174         case WM_CREATE:
1175         {
1176             MixerWindow = ((LPCREATESTRUCT)lParam)->lpCreateParams;
1177             SetWindowLongPtr(hwnd,
1178                              GWL_USERDATA,
1179                              (LONG_PTR)MixerWindow);
1180             MixerWindow->hWnd = hwnd;
1181             MixerWindow->Mixer = SndMixerCreate(MixerWindow->hWnd, MixerWindow->MixerId);
1182             if (MixerWindow->Mixer != NULL)
1183             {
1184                 TCHAR szProduct[MAXPNAMELEN];
1185 
1186                 /* get mixer product name */
1187                 if (SndMixerGetProductName(MixerWindow->Mixer,
1188                                            szProduct,
1189                                            sizeof(szProduct) / sizeof(szProduct[0])) == -1)
1190                 {
1191                     /* failed to get name */
1192                     szProduct[0] = L'\0';
1193                 }
1194 
1195 
1196                 /* initialize preferences */
1197                 ZeroMemory(&Preferences, sizeof(Preferences));
1198 
1199                 /* store mixer */
1200                 Preferences.Mixer = MixerWindow->Mixer;
1201 
1202                 /* store mixer window */
1203                 Preferences.MixerWindow = MixerWindow;
1204 
1205                 /* first destination line id */
1206                 Preferences.SelectedLine = 0xFFFF0000;
1207 
1208                 /* copy product */
1209                 wcscpy(Preferences.DeviceName, szProduct);
1210 
1211                 /* Disable the 'Advanced Controls' menu item */
1212                 EnableMenuItem(GetMenu(hwnd), IDM_ADVANCED_CONTROLS, MF_BYCOMMAND | MF_GRAYED);
1213 
1214                 /* create status window */
1215                 if (MixerWindow->Mode == NORMAL_MODE)
1216                 {
1217                     MixerWindow->hStatusBar = CreateStatusWindow(WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS,
1218                                                                  NULL,
1219                                                                  hwnd,
1220                                                                  0);
1221                     if (MixerWindow->hStatusBar)
1222                     {
1223                         SendMessage(MixerWindow->hStatusBar,
1224                                     WM_SETTEXT,
1225                                     0,
1226                                    (LPARAM)szProduct);
1227                     }
1228                 }
1229 
1230                 if (!RebuildMixerWindowControls(&Preferences))
1231                 {
1232                     DPRINT("Rebuilding mixer window controls failed!\n");
1233                     SndMixerDestroy(MixerWindow->Mixer);
1234                     MixerWindow->Mixer = NULL;
1235                     Result = -1;
1236                 }
1237             }
1238             break;
1239         }
1240 
1241         case WM_DESTROY:
1242         {
1243             MixerWindow = GetWindowData(hwnd,
1244                                         MIXER_WINDOW);
1245             if (MixerWindow != NULL)
1246             {
1247                 if (MixerWindow->Mixer != NULL)
1248                 {
1249                     SndMixerDestroy(MixerWindow->Mixer);
1250                 }
1251                 if (MixerWindow->hFont)
1252                     DeleteObject(MixerWindow->hFont);
1253                 HeapFree(hAppHeap, 0, MixerWindow);
1254             }
1255             break;
1256         }
1257 
1258         case WM_CLOSE:
1259         {
1260             PostQuitMessage(0);
1261             break;
1262         }
1263 
1264         default:
1265         {
1266             Result = DefWindowProc(hwnd,
1267                                    uMsg,
1268                                    wParam,
1269                                    lParam);
1270             break;
1271         }
1272     }
1273 
1274     return Result;
1275 }
1276 
1277 static BOOL
1278 RegisterApplicationClasses(VOID)
1279 {
1280     WNDCLASSEX wc;
1281 
1282     wc.cbSize = sizeof(WNDCLASSEX);
1283     wc.style = CS_HREDRAW | CS_VREDRAW;
1284     wc.lpfnWndProc = MainWindowProc;
1285     wc.cbClsExtra = 0;
1286     wc.cbWndExtra = sizeof(PMIXER_WINDOW);
1287     wc.hInstance = hAppInstance;
1288     wc.hIcon = LoadIcon(hAppInstance,
1289                         MAKEINTRESOURCE(IDI_MAINAPP));
1290     wc.hCursor = LoadCursor(NULL,
1291                             IDC_ARROW);
1292     wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
1293     wc.lpszMenuName = NULL;
1294     wc.lpszClassName = SZ_APP_CLASS;
1295     wc.hIconSm = NULL;
1296     MainWindowClass = RegisterClassEx(&wc);
1297 
1298     return MainWindowClass != 0;
1299 }
1300 
1301 static VOID
1302 UnregisterApplicationClasses(VOID)
1303 {
1304     UnregisterClass(SZ_APP_CLASS,
1305                     hAppInstance);
1306 }
1307 
1308 static HWND
1309 CreateApplicationWindow(
1310     WINDOW_MODE WindowMode,
1311     UINT MixerId)
1312 {
1313     HWND hWnd;
1314 
1315     PMIXER_WINDOW MixerWindow = HeapAlloc(hAppHeap,
1316                                           HEAP_ZERO_MEMORY,
1317                                           sizeof(MIXER_WINDOW));
1318     if (MixerWindow == NULL)
1319     {
1320         return NULL;
1321     }
1322 
1323     MixerWindow->Mode = WindowMode;
1324     MixerWindow->MixerId = MixerId;
1325 
1326     if (mixerGetNumDevs() > 0)
1327     {
1328         hWnd = CreateWindowEx(WS_EX_WINDOWEDGE | WS_EX_CONTROLPARENT,
1329                               SZ_APP_CLASS,
1330                               lpAppTitle,
1331                               WS_DLGFRAME | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE,
1332                               0, 0, 300, 315,
1333                               NULL,
1334                               LoadMenu(hAppInstance,
1335                                        MAKEINTRESOURCE(IDM_MAINMENU)),
1336                               hAppInstance,
1337                               MixerWindow);
1338     }
1339     else
1340     {
1341         LPTSTR lpErrMessage;
1342 
1343         /*
1344          * no mixer devices are available!
1345          */
1346 
1347         hWnd = NULL;
1348         if (AllocAndLoadString(&lpErrMessage,
1349                                hAppInstance,
1350                                IDS_NOMIXERDEVICES))
1351         {
1352             MessageBox(NULL,
1353                        lpErrMessage,
1354                        lpAppTitle,
1355                        MB_ICONINFORMATION);
1356             LocalFree(lpErrMessage);
1357         }
1358     }
1359 
1360     if (hWnd == NULL)
1361     {
1362         HeapFree(hAppHeap,
1363                  0,
1364                  MixerWindow);
1365     }
1366 
1367     return hWnd;
1368 }
1369 
1370 static
1371 BOOL
1372 HandleCommandLine(LPTSTR cmdline,
1373                   DWORD dwStyle,
1374                   PWINDOW_MODE pMode,
1375                   PUINT pMixerId)
1376 {
1377     TCHAR option;
1378 
1379     *pMixerId = PLAY_MIXER;
1380     *pMode = (dwStyle & 0x20) ? SMALL_MODE : NORMAL_MODE;
1381 
1382     while (*cmdline == _T(' ') || *cmdline == _T('-') || *cmdline == _T('/'))
1383     {
1384         if (*cmdline++ == _T(' '))
1385             continue;
1386 
1387         option = *cmdline;
1388         if (option)
1389             cmdline++;
1390         while (*cmdline == _T(' '))
1391             cmdline++;
1392 
1393         switch (option)
1394         {
1395             case 'd': /* Device */
1396             case 'D':
1397                 break;
1398 
1399             case 'n': /* Small size */
1400             case 'N':
1401                 *pMode = NORMAL_MODE;
1402                 break;
1403 
1404             case 's': /* Small size */
1405             case 'S':
1406                 *pMode = SMALL_MODE;
1407                 break;
1408 
1409             case 't': /* Tray size */
1410             case 'T':
1411                 *pMode = TRAY_MODE;
1412                 break;
1413 
1414             case 'p': /* Play mode */
1415             case 'P':
1416                 *pMixerId = PLAY_MIXER;
1417                 break;
1418 
1419             case 'r': /* Record mode */
1420             case 'R':
1421                 *pMixerId = RECORD_MIXER;
1422                 break;
1423 
1424             default:
1425                 return FALSE;
1426         }
1427     }
1428 
1429     return TRUE;
1430 }
1431 
1432 int WINAPI
1433 _tWinMain(HINSTANCE hInstance,
1434           HINSTANCE hPrevInstance,
1435           LPTSTR lpszCmdLine,
1436           int nCmdShow)
1437 {
1438     MSG Msg;
1439     int Ret = 1;
1440     INITCOMMONCONTROLSEX Controls;
1441     WINDOW_MODE WindowMode = SMALL_MODE;
1442     UINT MixerId = 0;
1443     DWORD dwStyle;
1444 
1445     UNREFERENCED_PARAMETER(hPrevInstance);
1446     UNREFERENCED_PARAMETER(nCmdShow);
1447 
1448     hAppInstance = hInstance;
1449     hAppHeap = GetProcessHeap();
1450 
1451     if (InitAppConfig())
1452     {
1453         dwStyle = GetStyleValue();
1454         HandleCommandLine(lpszCmdLine, dwStyle, &WindowMode, &MixerId);
1455 
1456         /* load the application title */
1457         if (!AllocAndLoadString(&lpAppTitle,
1458                                 hAppInstance,
1459                                 IDS_SNDVOL32))
1460         {
1461             lpAppTitle = NULL;
1462         }
1463 
1464         Controls.dwSize = sizeof(INITCOMMONCONTROLSEX);
1465         Controls.dwICC = ICC_BAR_CLASSES | ICC_STANDARD_CLASSES;
1466 
1467         InitCommonControlsEx(&Controls);
1468 
1469         if (WindowMode == TRAY_MODE)
1470         {
1471             DialogBoxParam(hAppInstance,
1472                            MAKEINTRESOURCE(IDD_TRAY_MASTER),
1473                            NULL,
1474                            TrayDlgProc,
1475                            0);
1476         }
1477         else
1478         {
1479             if (RegisterApplicationClasses())
1480             {
1481                 hMainWnd = CreateApplicationWindow(WindowMode, MixerId);
1482                 if (hMainWnd != NULL)
1483                 {
1484                     BOOL bRet;
1485                     while ((bRet =GetMessage(&Msg,
1486                                              NULL,
1487                                              0,
1488                                              0)) != 0)
1489                     {
1490                         if (bRet != -1)
1491                         {
1492                             TranslateMessage(&Msg);
1493                             DispatchMessage(&Msg);
1494                         }
1495                     }
1496 
1497                     DestroyWindow(hMainWnd);
1498                     Ret = 0;
1499                 }
1500                 else
1501                 {
1502                     DPRINT("Failed to create application window (LastError: %d)!\n", GetLastError());
1503                 }
1504 
1505                 UnregisterApplicationClasses();
1506             }
1507             else
1508             {
1509                 DPRINT("Failed to register application classes (LastError: %d)!\n", GetLastError());
1510             }
1511         }
1512 
1513         if (lpAppTitle != NULL)
1514         {
1515             LocalFree(lpAppTitle);
1516         }
1517 
1518         CloseAppConfig();
1519     }
1520     else
1521     {
1522         DPRINT("Unable to open the Volume Control registry key!\n");
1523     }
1524 
1525     return Ret;
1526 }
1527