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     BOOL bRet;
884     LRESULT Result = 0;
885     SET_VOLUME_CONTEXT Context;
886 
887     switch (uMsg)
888     {
889         case WM_COMMAND:
890         {
891             MixerWindow = GetWindowData(hwnd,
892                                         MIXER_WINDOW);
893 
894             switch (LOWORD(wParam))
895             {
896                 case IDM_PROPERTIES:
897                 {
898                     PREFERENCES_CONTEXT Pref;
899 
900                     Pref.MixerWindow = MixerWindow;
901                     Pref.Mixer = NULL;
902                     Pref.SelectedLine = Preferences.SelectedLine;
903 
904                     if (DialogBoxParam(hAppInstance,
905                                        MAKEINTRESOURCE(IDD_PREFERENCES),
906                                        hwnd,
907                                        DlgPreferencesProc,
908                                        (LPARAM)&Pref) == IDOK)
909                     {
910                         /* update window */
911                         TCHAR szProduct[MAXPNAMELEN];
912 
913                         /* get mixer product name */
914                         if (SndMixerGetProductName(Pref.Mixer,
915                                                    szProduct,
916                                                    sizeof(szProduct) / sizeof(szProduct[0])) == -1)
917                         {
918                             /* failed to get name */
919                             szProduct[0] = L'\0';
920                         }
921                         else
922                         {
923                             /* copy product */
924                             wcscpy(Preferences.DeviceName, szProduct);
925                         }
926 
927                         /* destroy old status bar */
928                         if (MixerWindow->Mode == NORMAL_MODE)
929                             DestroyWindow(MixerWindow->hStatusBar);
930 
931                         /* update details */
932                         Preferences.SelectedLine = Pref.SelectedLine;
933 
934                         /* destroy old mixer */
935                         SndMixerDestroy(Preferences.MixerWindow->Mixer);
936 
937                         /* use new selected mixer */
938                         Preferences.MixerWindow->Mixer = Pref.Mixer;
939 
940                         /* create status window */
941                         if (MixerWindow->Mode == NORMAL_MODE)
942                         {
943                             MixerWindow->hStatusBar = CreateStatusWindow(WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS,
944                                                                          NULL,
945                                                                          hwnd,
946                                                                          0);
947                             if (MixerWindow->hStatusBar)
948                             {
949                                 /* Set status bar */
950                                 SendMessage(MixerWindow->hStatusBar,
951                                     WM_SETTEXT,
952                                     0,
953                                     (LPARAM)szProduct);
954                             }
955                         }
956 
957                         /* rebuild dialog controls */
958                         RebuildMixerWindowControls(&Preferences);
959                     }
960                     break;
961                 }
962 
963                 case IDM_ADVANCED_CONTROLS:
964                     MixerWindow->bShowExtendedControls = !MixerWindow->bShowExtendedControls;
965                     CheckMenuItem(GetMenu(hwnd),
966                                   IDM_ADVANCED_CONTROLS,
967                                   MF_BYCOMMAND | (MixerWindow->bShowExtendedControls ? MF_CHECKED : MF_UNCHECKED));
968                     RebuildMixerWindowControls(&Preferences);
969                     break;
970 
971                 case IDM_EXIT:
972                 {
973                     PostQuitMessage(0);
974                     break;
975                 }
976 
977                 case IDM_ABOUT:
978                 {
979                     HICON hAppIcon = (HICON)GetClassLongPtrW(hwnd,
980                                                              GCLP_HICON);
981                     ShellAbout(hwnd,
982                                lpAppTitle,
983                                NULL,
984                                hAppIcon);
985                     break;
986                 }
987 
988                 default:
989                 {
990                     /* get button id */
991                     CtrlID = LOWORD(wParam);
992 
993                     /* check if the message is from the line switch */
994                     if (HIWORD(wParam) == BN_CLICKED)
995                     {
996                         if (CtrlID % IDC_LINE_SWITCH == 0)
997                         {
998                             /* compute line offset */
999                             LineOffset = CtrlID / IDC_LINE_SWITCH;
1000 
1001                             /* compute window id of line name static control */
1002                             CtrlID = LineOffset * IDC_LINE_NAME;
1003 
1004                             if (Preferences.MixerWindow->Mixer->MixerId == PLAY_MIXER)
1005                             {
1006                                 /* get line name */
1007                                 if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
1008                                 {
1009                                     /* setup context */
1010                                     Context.SliderPos = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0);
1011                                     Context.bVertical = FALSE;
1012                                     Context.bSwitch = TRUE;
1013 
1014                                     /* set volume */
1015                                     SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
1016                                 }
1017                             }
1018                             else if (Preferences.MixerWindow->Mixer->MixerId == RECORD_MIXER)
1019                             {
1020                                 UINT i;
1021 
1022                                 for (i = 0; i < Preferences.MixerWindow->DialogCount; i++)
1023                                 {
1024                                     SendDlgItemMessageW(hwnd, (i + 1) * IDC_LINE_SWITCH, BM_SETCHECK, (WPARAM)((i + 1) == LineOffset), 0);
1025                                 }
1026                             }
1027                         }
1028                         else if (CtrlID % IDC_LINE_ADVANCED == 0)
1029                         {
1030                             ADVANCED_CONTEXT AdvancedContext;
1031 
1032                             /* compute line offset */
1033                             LineOffset = CtrlID / IDC_LINE_ADVANCED;
1034 
1035                             /* compute window id of line name static control */
1036                             CtrlID = LineOffset * IDC_LINE_NAME;
1037 
1038                             /* get line name */
1039                             if (GetDlgItemTextW(hwnd, CtrlID, AdvancedContext.LineName, MIXER_LONG_NAME_CHARS) != 0)
1040                             {
1041                                 AdvancedContext.MixerWindow = Preferences.MixerWindow;
1042                                 AdvancedContext.Mixer = Preferences.MixerWindow->Mixer;
1043                                 AdvancedContext.Line = SndMixerGetLineByName(Preferences.MixerWindow->Mixer,
1044                                                                              Preferences.SelectedLine,
1045                                                                              AdvancedContext.LineName);
1046                                 if (AdvancedContext.Line)
1047                                 {
1048                                     DialogBoxParam(hAppInstance,
1049                                                    MAKEINTRESOURCE(IDD_ADVANCED),
1050                                                    hwnd,
1051                                                    AdvancedDlgProc,
1052                                                    (LPARAM)&AdvancedContext);
1053                                 }
1054                             }
1055                         }
1056                     }
1057                 }
1058             }
1059             break;
1060         }
1061 
1062         case MM_MIXM_LINE_CHANGE:
1063         {
1064             DPRINT("MM_MIXM_LINE_CHANGE\n");
1065             break;
1066         }
1067 
1068         case MM_MIXM_CONTROL_CHANGE:
1069         {
1070             DPRINT("MM_MIXM_CONTROL_CHANGE\n");
1071 
1072             /* get mixer window */
1073             MixerWindow = GetWindowData(hwnd,
1074                                         MIXER_WINDOW);
1075 
1076             /* sanity checks */
1077             assert(MixerWindow);
1078             assert(MixerWindow->Mixer->hmx == (HMIXER)wParam);
1079 
1080             SndMixerEnumConnections(MixerWindow->Mixer, Preferences.SelectedLine, MixerControlChangeCallback, (PVOID)lParam);
1081             break;
1082         }
1083 
1084         case WM_VSCROLL:
1085             switch (LOWORD(wParam))
1086             {
1087                 case TB_THUMBTRACK:
1088                     /* get dialog item ctrl */
1089                     CtrlID = GetDlgCtrlID((HWND)lParam);
1090 
1091                     /* get line index */
1092                     LineOffset = CtrlID / IDC_LINE_SLIDER_VERT;
1093 
1094                     /* compute window id of line name static control */
1095                     CtrlID = LineOffset * IDC_LINE_NAME;
1096 
1097                     /* get line name */
1098                     if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
1099                     {
1100                         /* setup context */
1101                         Context.SliderPos = LineOffset;
1102                         Context.bVertical = TRUE;
1103                         Context.bSwitch = FALSE;
1104 
1105                         /* set volume */
1106                         SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
1107                     }
1108                     break;
1109 
1110                 case TB_ENDTRACK:
1111                     MixerWindow = GetWindowData(hwnd,
1112                                                 MIXER_WINDOW);
1113 
1114                     /* get dialog item ctrl */
1115                     CtrlID = GetDlgCtrlID((HWND)lParam);
1116 
1117                     /* get line index */
1118                     LineOffset = CtrlID / IDC_LINE_SLIDER_VERT;
1119 
1120                     if (LineOffset == 1 && MixerWindow->Mixer->MixerId == 0)
1121                         PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, NULL, SND_ASYNC | SND_ALIAS_ID);
1122                     break;
1123 
1124                 default:
1125                     break;
1126             }
1127             break;
1128 
1129         case WM_HSCROLL:
1130             switch (LOWORD(wParam))
1131             {
1132                 case TB_THUMBTRACK:
1133                     /* get dialog item ctrl */
1134                     CtrlID = GetDlgCtrlID((HWND)lParam);
1135 
1136                     /* get line index */
1137                     LineOffset = CtrlID / IDC_LINE_SLIDER_HORZ;
1138 
1139                     /* compute window id of line name static control */
1140                     CtrlID = LineOffset * IDC_LINE_NAME;
1141 
1142                     /* get line name */
1143                     if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
1144                     {
1145                         /* setup context */
1146                         Context.SliderPos = LineOffset;
1147                         Context.bVertical = TRUE;
1148                         Context.bSwitch = FALSE;
1149 
1150                         /* set volume */
1151                         SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
1152                     }
1153                     break;
1154 
1155                 case TB_ENDTRACK:
1156                     MixerWindow = GetWindowData(hwnd,
1157                                                 MIXER_WINDOW);
1158 
1159                     /* get dialog item ctrl */
1160                     CtrlID = GetDlgCtrlID((HWND)lParam);
1161 
1162                     /* get line index */
1163                     LineOffset = CtrlID / IDC_LINE_SLIDER_HORZ;
1164 
1165                     if (LineOffset == 1 && MixerWindow->Mixer->MixerId == 0)
1166                         PlaySound((LPCTSTR)SND_ALIAS_SYSTEMDEFAULT, NULL, SND_ASYNC | SND_ALIAS_ID);
1167                     break;
1168 
1169                 default:
1170                     break;
1171             }
1172             break;
1173 
1174 
1175         case WM_CREATE:
1176         {
1177             MixerWindow = ((LPCREATESTRUCT)lParam)->lpCreateParams;
1178             SetWindowLongPtr(hwnd,
1179                              GWL_USERDATA,
1180                              (LONG_PTR)MixerWindow);
1181             MixerWindow->hWnd = hwnd;
1182             MixerWindow->Mixer = SndMixerCreate(MixerWindow->hWnd, MixerWindow->MixerId);
1183             if (MixerWindow->Mixer != NULL)
1184             {
1185                 TCHAR szProduct[MAXPNAMELEN];
1186 
1187                 /* get mixer product name */
1188                 if (SndMixerGetProductName(MixerWindow->Mixer,
1189                                            szProduct,
1190                                            sizeof(szProduct) / sizeof(szProduct[0])) == -1)
1191                 {
1192                     /* failed to get name */
1193                     szProduct[0] = L'\0';
1194                 }
1195 
1196 
1197                 /* initialize preferences */
1198                 ZeroMemory(&Preferences, sizeof(Preferences));
1199 
1200                 /* store mixer */
1201                 Preferences.Mixer = MixerWindow->Mixer;
1202 
1203                 /* store mixer window */
1204                 Preferences.MixerWindow = MixerWindow;
1205 
1206                 /* first destination line id */
1207                 Preferences.SelectedLine = 0xFFFF0000;
1208 
1209                 /* copy product */
1210                 wcscpy(Preferences.DeviceName, szProduct);
1211 
1212                 /* Disable the 'Advanced Controls' menu item */
1213                 EnableMenuItem(GetMenu(hwnd), IDM_ADVANCED_CONTROLS, MF_BYCOMMAND | MF_GRAYED);
1214 
1215                 /* Load the placement coordinate data of the window */
1216                 bRet = LoadXYCoordWnd(&Preferences);
1217                 if (bRet)
1218                 {
1219                     /*
1220                      * LoadXYCoordWnd() might fail for the first time of opening the application which is normal as
1221                      * the Sound Control's applet settings haven't been saved yet to the Registry. At this point SetWindowPos()
1222                      * call is skipped.
1223                      */
1224                     SetWindowPos(hwnd, NULL, MixerWindow->WndPosX, MixerWindow->WndPosY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
1225                 }
1226 
1227                 /* create status window */
1228                 if (MixerWindow->Mode == NORMAL_MODE)
1229                 {
1230                     MixerWindow->hStatusBar = CreateStatusWindow(WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS,
1231                                                                  NULL,
1232                                                                  hwnd,
1233                                                                  0);
1234                     if (MixerWindow->hStatusBar)
1235                     {
1236                         SendMessage(MixerWindow->hStatusBar,
1237                                     WM_SETTEXT,
1238                                     0,
1239                                    (LPARAM)szProduct);
1240                     }
1241                 }
1242 
1243                 if (!RebuildMixerWindowControls(&Preferences))
1244                 {
1245                     DPRINT("Rebuilding mixer window controls failed!\n");
1246                     SndMixerDestroy(MixerWindow->Mixer);
1247                     MixerWindow->Mixer = NULL;
1248                     Result = -1;
1249                 }
1250             }
1251             break;
1252         }
1253 
1254         case WM_DESTROY:
1255         {
1256             MixerWindow = GetWindowData(hwnd,
1257                                         MIXER_WINDOW);
1258             if (MixerWindow != NULL)
1259             {
1260                 if (MixerWindow->Mixer != NULL)
1261                 {
1262                     SndMixerDestroy(MixerWindow->Mixer);
1263                 }
1264                 if (MixerWindow->hFont)
1265                     DeleteObject(MixerWindow->hFont);
1266                 HeapFree(hAppHeap, 0, MixerWindow);
1267             }
1268             break;
1269         }
1270 
1271         case WM_CLOSE:
1272         {
1273             SaveXYCoordWnd(hwnd, &Preferences);
1274             PostQuitMessage(0);
1275             break;
1276         }
1277 
1278         default:
1279         {
1280             Result = DefWindowProc(hwnd,
1281                                    uMsg,
1282                                    wParam,
1283                                    lParam);
1284             break;
1285         }
1286     }
1287 
1288     return Result;
1289 }
1290 
1291 static BOOL
1292 RegisterApplicationClasses(VOID)
1293 {
1294     WNDCLASSEX wc;
1295 
1296     wc.cbSize = sizeof(WNDCLASSEX);
1297     wc.style = CS_HREDRAW | CS_VREDRAW;
1298     wc.lpfnWndProc = MainWindowProc;
1299     wc.cbClsExtra = 0;
1300     wc.cbWndExtra = sizeof(PMIXER_WINDOW);
1301     wc.hInstance = hAppInstance;
1302     wc.hIcon = LoadIcon(hAppInstance,
1303                         MAKEINTRESOURCE(IDI_MAINAPP));
1304     wc.hCursor = LoadCursor(NULL,
1305                             IDC_ARROW);
1306     wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
1307     wc.lpszMenuName = NULL;
1308     wc.lpszClassName = SZ_APP_CLASS;
1309     wc.hIconSm = NULL;
1310     MainWindowClass = RegisterClassEx(&wc);
1311 
1312     return MainWindowClass != 0;
1313 }
1314 
1315 static VOID
1316 UnregisterApplicationClasses(VOID)
1317 {
1318     UnregisterClass(SZ_APP_CLASS,
1319                     hAppInstance);
1320 }
1321 
1322 static HWND
1323 CreateApplicationWindow(
1324     WINDOW_MODE WindowMode,
1325     UINT MixerId)
1326 {
1327     HWND hWnd;
1328 
1329     PMIXER_WINDOW MixerWindow = HeapAlloc(hAppHeap,
1330                                           HEAP_ZERO_MEMORY,
1331                                           sizeof(MIXER_WINDOW));
1332     if (MixerWindow == NULL)
1333     {
1334         return NULL;
1335     }
1336 
1337     MixerWindow->Mode = WindowMode;
1338     MixerWindow->MixerId = MixerId;
1339 
1340     if (mixerGetNumDevs() > 0)
1341     {
1342         hWnd = CreateWindowEx(WS_EX_WINDOWEDGE | WS_EX_CONTROLPARENT,
1343                               SZ_APP_CLASS,
1344                               lpAppTitle,
1345                               WS_DLGFRAME | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE,
1346                               0, 0, 300, 315,
1347                               NULL,
1348                               LoadMenu(hAppInstance,
1349                                        MAKEINTRESOURCE(IDM_MAINMENU)),
1350                               hAppInstance,
1351                               MixerWindow);
1352     }
1353     else
1354     {
1355         LPTSTR lpErrMessage;
1356 
1357         /*
1358          * no mixer devices are available!
1359          */
1360 
1361         hWnd = NULL;
1362         if (AllocAndLoadString(&lpErrMessage,
1363                                hAppInstance,
1364                                IDS_NOMIXERDEVICES))
1365         {
1366             MessageBox(NULL,
1367                        lpErrMessage,
1368                        lpAppTitle,
1369                        MB_ICONINFORMATION);
1370             LocalFree(lpErrMessage);
1371         }
1372     }
1373 
1374     if (hWnd == NULL)
1375     {
1376         HeapFree(hAppHeap,
1377                  0,
1378                  MixerWindow);
1379     }
1380 
1381     return hWnd;
1382 }
1383 
1384 static
1385 BOOL
1386 HandleCommandLine(LPTSTR cmdline,
1387                   DWORD dwStyle,
1388                   PWINDOW_MODE pMode,
1389                   PUINT pMixerId)
1390 {
1391     TCHAR option;
1392 
1393     *pMixerId = PLAY_MIXER;
1394     *pMode = (dwStyle & 0x20) ? SMALL_MODE : NORMAL_MODE;
1395 
1396     while (*cmdline == _T(' ') || *cmdline == _T('-') || *cmdline == _T('/'))
1397     {
1398         if (*cmdline++ == _T(' '))
1399             continue;
1400 
1401         option = *cmdline;
1402         if (option)
1403             cmdline++;
1404         while (*cmdline == _T(' '))
1405             cmdline++;
1406 
1407         switch (option)
1408         {
1409             case 'd': /* Device */
1410             case 'D':
1411                 break;
1412 
1413             case 'n': /* Small size */
1414             case 'N':
1415                 *pMode = NORMAL_MODE;
1416                 break;
1417 
1418             case 's': /* Small size */
1419             case 'S':
1420                 *pMode = SMALL_MODE;
1421                 break;
1422 
1423             case 't': /* Tray size */
1424             case 'T':
1425                 *pMode = TRAY_MODE;
1426                 break;
1427 
1428             case 'p': /* Play mode */
1429             case 'P':
1430                 *pMixerId = PLAY_MIXER;
1431                 break;
1432 
1433             case 'r': /* Record mode */
1434             case 'R':
1435                 *pMixerId = RECORD_MIXER;
1436                 break;
1437 
1438             default:
1439                 return FALSE;
1440         }
1441     }
1442 
1443     return TRUE;
1444 }
1445 
1446 int WINAPI
1447 _tWinMain(HINSTANCE hInstance,
1448           HINSTANCE hPrevInstance,
1449           LPTSTR lpszCmdLine,
1450           int nCmdShow)
1451 {
1452     MSG Msg;
1453     int Ret = 1;
1454     INITCOMMONCONTROLSEX Controls;
1455     WINDOW_MODE WindowMode = SMALL_MODE;
1456     UINT MixerId = 0;
1457     DWORD dwStyle;
1458 
1459     UNREFERENCED_PARAMETER(hPrevInstance);
1460     UNREFERENCED_PARAMETER(nCmdShow);
1461 
1462     hAppInstance = hInstance;
1463     hAppHeap = GetProcessHeap();
1464 
1465     if (InitAppConfig())
1466     {
1467         dwStyle = GetStyleValue();
1468         HandleCommandLine(lpszCmdLine, dwStyle, &WindowMode, &MixerId);
1469 
1470         /* load the application title */
1471         if (!AllocAndLoadString(&lpAppTitle,
1472                                 hAppInstance,
1473                                 IDS_SNDVOL32))
1474         {
1475             lpAppTitle = NULL;
1476         }
1477 
1478         Controls.dwSize = sizeof(INITCOMMONCONTROLSEX);
1479         Controls.dwICC = ICC_BAR_CLASSES | ICC_STANDARD_CLASSES;
1480 
1481         InitCommonControlsEx(&Controls);
1482 
1483         if (WindowMode == TRAY_MODE)
1484         {
1485             DialogBoxParam(hAppInstance,
1486                            MAKEINTRESOURCE(IDD_TRAY_MASTER),
1487                            NULL,
1488                            TrayDlgProc,
1489                            0);
1490         }
1491         else
1492         {
1493             if (RegisterApplicationClasses())
1494             {
1495                 hMainWnd = CreateApplicationWindow(WindowMode, MixerId);
1496                 if (hMainWnd != NULL)
1497                 {
1498                     BOOL bRet;
1499                     while ((bRet =GetMessage(&Msg,
1500                                              NULL,
1501                                              0,
1502                                              0)) != 0)
1503                     {
1504                         if (bRet != -1)
1505                         {
1506                             TranslateMessage(&Msg);
1507                             DispatchMessage(&Msg);
1508                         }
1509                     }
1510 
1511                     DestroyWindow(hMainWnd);
1512                     Ret = 0;
1513                 }
1514                 else
1515                 {
1516                     DPRINT("Failed to create application window (LastError: %d)!\n", GetLastError());
1517                 }
1518 
1519                 UnregisterApplicationClasses();
1520             }
1521             else
1522             {
1523                 DPRINT("Failed to register application classes (LastError: %d)!\n", GetLastError());
1524             }
1525         }
1526 
1527         if (lpAppTitle != NULL)
1528         {
1529             LocalFree(lpAppTitle);
1530         }
1531 
1532         CloseAppConfig();
1533     }
1534     else
1535     {
1536         DPRINT("Unable to open the Volume Control registry key!\n");
1537     }
1538 
1539     return Ret;
1540 }
1541