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 
597 static VOID
598 DeleteMixerWindowControls(PMIXER_WINDOW MixerWindow)
599 {
600     DWORD Index;
601 
602     for(Index = 0; Index < MixerWindow->WindowCount; Index++)
603     {
604         /* destroys the window */
605         DestroyWindow(MixerWindow->Window[Index]);
606     }
607 
608     /* free memory */
609     HeapFree(GetProcessHeap(), 0, MixerWindow->Window);
610 
611     /* set to null */
612     MixerWindow->Window = NULL;
613     MixerWindow->WindowCount = 0;
614 }
615 
616 static BOOL
617 RebuildMixerWindowControls(PPREFERENCES_CONTEXT PrefContext)
618 {
619     /* delete existing mixer controls */
620     DeleteMixerWindowControls(PrefContext->MixerWindow);
621 
622     /* load new mixer controls */
623     LoadDialogCtrls(PrefContext);
624 
625     return TRUE;
626 }
627 
628 static
629 BOOL
630 CALLBACK
631 SetVolumeCallback(PSND_MIXER Mixer, DWORD LineID, LPMIXERLINE Line, PVOID Ctx)
632 {
633     UINT ControlCount = 0, Index;
634     LPMIXERCONTROL Control = NULL;
635     MIXERCONTROLDETAILS_UNSIGNED uDetails;
636     MIXERCONTROLDETAILS_BOOLEAN bDetails;
637     PSET_VOLUME_CONTEXT Context = (PSET_VOLUME_CONTEXT)Ctx;
638 
639     /* check if the line name is equal */
640     if (wcsicmp(Line->szName, Context->LineName))
641     {
642         /* it is not */
643         return TRUE;
644     }
645 
646     /* query controls */
647     if (SndMixerQueryControls(Mixer, &ControlCount, Line, &Control) == FALSE)
648     {
649         /* failed to query for controls */
650         return FALSE;
651     }
652 
653     /* now go through all controls and compare control ids */
654     for (Index = 0; Index < ControlCount; Index++)
655     {
656         if (Context->bVertical)
657         {
658             if ((Control[Index].dwControlType & MIXERCONTROL_CT_CLASS_MASK) == MIXERCONTROL_CT_CLASS_FADER)
659             {
660                 /* FIXME: give me granularity */
661                 DWORD Step = 0x10000 / VOLUME_STEPS;
662 
663                 /* set up details */
664                 uDetails.dwValue = 0x10000 - Step * Context->SliderPos;
665 
666                 /* set volume */
667                 SndMixerSetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, sizeof(MIXERCONTROLDETAILS_UNSIGNED), (LPVOID)&uDetails);
668 
669                 /* done */
670                 break;
671             }
672         }
673         else if (Context->bSwitch)
674         {
675             if ((Control[Index].dwControlType & MIXERCONTROL_CT_CLASS_MASK) == MIXERCONTROL_CT_CLASS_SWITCH)
676             {
677                 /* set up details */
678                 bDetails.fValue = Context->SliderPos;
679 
680                 /* set volume */
681                 SndMixerSetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, sizeof(MIXERCONTROLDETAILS_BOOLEAN), (LPVOID)&bDetails);
682 
683                 /* done */
684                 break;
685             }
686         }
687         else
688         {
689             /* FIXME: implement left - right channel switch support */
690             assert(0);
691         }
692     }
693 
694     /* free controls */
695     HeapFree(GetProcessHeap(), 0, Control);
696 
697 
698     /* done */
699     return TRUE;
700 }
701 
702 static
703 BOOL
704 CALLBACK
705 MixerControlChangeCallback(PSND_MIXER Mixer, DWORD LineID, LPMIXERLINE Line, PVOID Context)
706 {
707     UINT ControlCount = 0, Index;
708     LPMIXERCONTROL Control = NULL;
709 
710     /* check if the line has controls */
711     if (Line->cControls == 0)
712     {
713         /* no controls */
714         return TRUE;
715     }
716 
717     /* query controls */
718     if (SndMixerQueryControls(Mixer, &ControlCount, Line, &Control) == FALSE)
719     {
720         /* failed to query for controls */
721         return FALSE;
722     }
723 
724     /* now go through all controls and compare control ids */
725     for (Index = 0; Index < ControlCount; Index++)
726     {
727         if (Control[Index].dwControlID == PtrToUlong(Context))
728         {
729             if ((Control[Index].dwControlType & MIXERCONTROL_CT_CLASS_MASK) == MIXERCONTROL_CT_CLASS_SWITCH)
730             {
731                 MIXERCONTROLDETAILS_BOOLEAN Details;
732 
733                 /* get volume control details */
734                 if (SndMixerGetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, sizeof(MIXERCONTROLDETAILS_BOOLEAN), (LPVOID)&Details) != -1)
735                 {
736                     /* update dialog control */
737                     UpdateDialogLineSwitchControl(&Preferences, Line, Details.fValue);
738                 }
739             }
740             else if ((Control[Index].dwControlType & MIXERCONTROL_CT_CLASS_MASK) == MIXERCONTROL_CT_CLASS_FADER)
741             {
742                 MIXERCONTROLDETAILS_UNSIGNED Details;
743 
744                 /* get volume control details */
745                 if (SndMixerGetVolumeControlDetails(Preferences.MixerWindow->Mixer, Control[Index].dwControlID, sizeof(MIXERCONTROLDETAILS_UNSIGNED), (LPVOID)&Details) != -1)
746                 {
747                     /* update dialog control */
748                     DWORD Position;
749                     DWORD Step = 0x10000 / VOLUME_STEPS;
750 
751                     /* FIXME: give me granularity */
752                     Position = VOLUME_STEPS - (Details.dwValue / Step);
753 
754                     /* update volume control slider */
755                     UpdateDialogLineSliderControl(&Preferences, Line, Control[Index].dwControlID, IDC_LINE_SLIDER_VERT, Position);
756                 }
757             }
758             break;
759         }
760     }
761 
762     /* free controls */
763     HeapFree(GetProcessHeap(), 0, Control);
764 
765     /* done */
766     return TRUE;
767 }
768 
769 
770 static LRESULT CALLBACK
771 MainWindowProc(HWND hwnd,
772                UINT uMsg,
773                WPARAM wParam,
774                LPARAM lParam)
775 {
776     PMIXER_WINDOW MixerWindow;
777     DWORD CtrlID, LineOffset;
778     LRESULT Result = 0;
779     SET_VOLUME_CONTEXT Context;
780 
781     switch (uMsg)
782     {
783         case WM_COMMAND:
784         {
785             MixerWindow = GetWindowData(hwnd,
786                                         MIXER_WINDOW);
787 
788             switch (LOWORD(wParam))
789             {
790                 case IDC_PROPERTIES:
791                 {
792                     PREFERENCES_CONTEXT Pref;
793 
794                     Pref.MixerWindow = MixerWindow;
795                     Pref.Mixer = NULL;
796                     Pref.SelectedLine = Preferences.SelectedLine;
797 
798                     if (DialogBoxParam(hAppInstance,
799                                        MAKEINTRESOURCE(IDD_PREFERENCES),
800                                        hwnd,
801                                        DlgPreferencesProc,
802                                        (LPARAM)&Pref) == IDOK)
803                     {
804                         /* update window */
805                         TCHAR szProduct[MAXPNAMELEN];
806 
807                         /* get mixer product name */
808                         if (SndMixerGetProductName(Pref.Mixer,
809                                                    szProduct,
810                                                    sizeof(szProduct) / sizeof(szProduct[0])) == -1)
811                         {
812                             /* failed to get name */
813                             szProduct[0] = L'\0';
814                         }
815                         else
816                         {
817                             /* copy product */
818                             wcscpy(Preferences.DeviceName, szProduct);
819                         }
820 
821                         /* destroy old status bar */
822                         if (MixerWindow->Mode == NORMAL_MODE)
823                             DestroyWindow(MixerWindow->hStatusBar);
824 
825                         /* update details */
826                         Preferences.SelectedLine = Pref.SelectedLine;
827 
828                         /* destroy old mixer */
829                         SndMixerDestroy(Preferences.MixerWindow->Mixer);
830 
831                         /* use new selected mixer */
832                         Preferences.MixerWindow->Mixer = Pref.Mixer;
833 
834                         /* rebuild dialog controls */
835                         RebuildMixerWindowControls(&Preferences);
836 
837                         /* create status window */
838                         if (MixerWindow->Mode == NORMAL_MODE)
839                         {
840                             MixerWindow->hStatusBar = CreateStatusWindow(WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS,
841                                                                          NULL,
842                                                                          hwnd,
843                                                                          0);
844                             if (MixerWindow->hStatusBar)
845                             {
846                                 /* Set status bar */
847                                 SendMessage(MixerWindow->hStatusBar,
848                                     WM_SETTEXT,
849                                     0,
850                                     (LPARAM)szProduct);
851                             }
852                         }
853                     }
854                     break;
855                 }
856 
857                 case IDC_EXIT:
858                 {
859                     PostQuitMessage(0);
860                     break;
861                 }
862 
863                 case IDC_ABOUT:
864                 {
865                     HICON hAppIcon = (HICON)GetClassLongPtrW(hwnd,
866                                                              GCLP_HICON);
867                     ShellAbout(hwnd,
868                                lpAppTitle,
869                                NULL,
870                                hAppIcon);
871                     break;
872                 }
873 
874                 default:
875                 {
876                     /* get button id */
877                     CtrlID = LOWORD(wParam);
878 
879                     /* check if the message is from the line switch */
880                     if (HIWORD(wParam) == BN_CLICKED && (CtrlID % IDC_LINE_SWITCH == 0))
881                     {
882                          /* compute line offset */
883                          LineOffset = CtrlID / IDC_LINE_SWITCH;
884 
885                         /* compute window id of line name static control */
886                         CtrlID = LineOffset * IDC_LINE_NAME;
887 
888                        /* get line name */
889                        if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
890                        {
891                            /* setup context */
892                            Context.SliderPos = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0);
893                            Context.bVertical = FALSE;
894                            Context.bSwitch = TRUE;
895 
896                            /* set volume */
897                            SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
898                        }
899                     }
900                 }
901 
902             }
903             break;
904         }
905 
906         case MM_MIXM_LINE_CHANGE:
907         {
908             DPRINT("MM_MIXM_LINE_CHANGE\n");
909             break;
910         }
911 
912         case MM_MIXM_CONTROL_CHANGE:
913         {
914             DPRINT("MM_MIXM_CONTROL_CHANGE\n");
915 
916             /* get mixer window */
917             MixerWindow = GetWindowData(hwnd,
918                                         MIXER_WINDOW);
919 
920             /* sanity checks */
921             assert(MixerWindow);
922             assert(MixerWindow->Mixer->hmx == (HMIXER)wParam);
923 
924             SndMixerEnumConnections(MixerWindow->Mixer, Preferences.SelectedLine, MixerControlChangeCallback, (PVOID)lParam);
925             break;
926         }
927 
928         case WM_VSCROLL:
929         {
930             if (LOWORD(wParam) == TB_THUMBTRACK)
931             {
932                 /* get dialog item ctrl */
933                 CtrlID = GetDlgCtrlID((HWND)lParam);
934 
935                 /* get line index */
936                 LineOffset = CtrlID / IDC_LINE_SLIDER_VERT;
937 
938                 /* compute window id of line name static control */
939                 CtrlID = LineOffset * IDC_LINE_NAME;
940 
941                 /* get line name */
942                 if (GetDlgItemTextW(hwnd, CtrlID, Context.LineName, MIXER_LONG_NAME_CHARS) != 0)
943                 {
944                     /* setup context */
945                     Context.SliderPos = HIWORD(wParam);
946                     Context.bVertical = TRUE;
947                     Context.bSwitch = FALSE;
948 
949                     /* set volume */
950                     SndMixerEnumConnections(Preferences.MixerWindow->Mixer, Preferences.SelectedLine, SetVolumeCallback, (LPVOID)&Context);
951                 }
952             }
953 
954             break;
955         }
956 
957 
958         case WM_CREATE:
959         {
960             MixerWindow = ((LPCREATESTRUCT)lParam)->lpCreateParams;
961             SetWindowLongPtr(hwnd,
962                              GWL_USERDATA,
963                              (LONG_PTR)MixerWindow);
964             MixerWindow->hWnd = hwnd;
965             MixerWindow->Mixer = SndMixerCreate(MixerWindow->hWnd, MixerWindow->MixerId);
966             if (MixerWindow->Mixer != NULL)
967             {
968                 TCHAR szProduct[MAXPNAMELEN];
969 
970                 /* get mixer product name */
971                 if (SndMixerGetProductName(MixerWindow->Mixer,
972                                            szProduct,
973                                            sizeof(szProduct) / sizeof(szProduct[0])) == -1)
974                 {
975                     /* failed to get name */
976                     szProduct[0] = L'\0';
977                 }
978 
979 
980                 /* initialize preferences */
981                 ZeroMemory(&Preferences, sizeof(Preferences));
982 
983                 /* store mixer */
984                 Preferences.Mixer = MixerWindow->Mixer;
985 
986                 /* store mixer window */
987                 Preferences.MixerWindow = MixerWindow;
988 
989                 /* first destination line id */
990                 Preferences.SelectedLine = 0xFFFF0000;
991 
992                 /* copy product */
993                 wcscpy(Preferences.DeviceName, szProduct);
994 
995                 if (!RebuildMixerWindowControls(&Preferences))
996                 {
997                     DPRINT("Rebuilding mixer window controls failed!\n");
998                     SndMixerDestroy(MixerWindow->Mixer);
999                     MixerWindow->Mixer = NULL;
1000                     Result = -1;
1001                 }
1002 
1003                 /* create status window */
1004                 if (MixerWindow->Mode == NORMAL_MODE)
1005                 {
1006                     MixerWindow->hStatusBar = CreateStatusWindow(WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS,
1007                                                                  NULL,
1008                                                                  hwnd,
1009                                                                  0);
1010                     if (MixerWindow->hStatusBar)
1011                     {
1012                         SendMessage(MixerWindow->hStatusBar,
1013                                     WM_SETTEXT,
1014                                     0,
1015                                    (LPARAM)szProduct);
1016                     }
1017                 }
1018             }
1019             break;
1020         }
1021 
1022         case WM_DESTROY:
1023         {
1024             MixerWindow = GetWindowData(hwnd,
1025                                         MIXER_WINDOW);
1026             if (MixerWindow != NULL)
1027             {
1028                 if (MixerWindow->Mixer != NULL)
1029                 {
1030                     SndMixerDestroy(MixerWindow->Mixer);
1031                 }
1032                 if (MixerWindow->hFont)
1033                     DeleteObject(MixerWindow->hFont);
1034                 HeapFree(hAppHeap, 0, MixerWindow);
1035             }
1036             break;
1037         }
1038 
1039         case WM_CLOSE:
1040         {
1041             PostQuitMessage(0);
1042             break;
1043         }
1044 
1045         default:
1046         {
1047             Result = DefWindowProc(hwnd,
1048                                    uMsg,
1049                                    wParam,
1050                                    lParam);
1051             break;
1052         }
1053     }
1054 
1055     return Result;
1056 }
1057 
1058 static BOOL
1059 RegisterApplicationClasses(VOID)
1060 {
1061     WNDCLASSEX wc;
1062 
1063     wc.cbSize = sizeof(WNDCLASSEX);
1064     wc.style = CS_HREDRAW | CS_VREDRAW;
1065     wc.lpfnWndProc = MainWindowProc;
1066     wc.cbClsExtra = 0;
1067     wc.cbWndExtra = sizeof(PMIXER_WINDOW);
1068     wc.hInstance = hAppInstance;
1069     wc.hIcon = LoadIcon(hAppInstance,
1070                         MAKEINTRESOURCE(IDI_MAINAPP));
1071     wc.hCursor = LoadCursor(NULL,
1072                             IDC_ARROW);
1073     wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
1074     wc.lpszMenuName = NULL;
1075     wc.lpszClassName = SZ_APP_CLASS;
1076     wc.hIconSm = NULL;
1077     MainWindowClass = RegisterClassEx(&wc);
1078 
1079     return MainWindowClass != 0;
1080 }
1081 
1082 static VOID
1083 UnregisterApplicationClasses(VOID)
1084 {
1085     UnregisterClass(SZ_APP_CLASS,
1086                     hAppInstance);
1087 }
1088 
1089 static HWND
1090 CreateApplicationWindow(
1091     WINDOW_MODE WindowMode,
1092     UINT MixerId)
1093 {
1094     HWND hWnd;
1095 
1096     PMIXER_WINDOW MixerWindow = HeapAlloc(hAppHeap,
1097                                           HEAP_ZERO_MEMORY,
1098                                           sizeof(MIXER_WINDOW));
1099     if (MixerWindow == NULL)
1100     {
1101         return NULL;
1102     }
1103 
1104     MixerWindow->Mode = WindowMode;
1105     MixerWindow->MixerId = MixerId;
1106 
1107     if (mixerGetNumDevs() > 0)
1108     {
1109         hWnd = CreateWindowEx(WS_EX_WINDOWEDGE | WS_EX_CONTROLPARENT,
1110                               SZ_APP_CLASS,
1111                               lpAppTitle,
1112                               WS_DLGFRAME | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE,
1113                               0, 0, 300, 315,
1114                               NULL,
1115                               LoadMenu(hAppInstance,
1116                                        MAKEINTRESOURCE(IDM_MAINMENU)),
1117                               hAppInstance,
1118                               MixerWindow);
1119     }
1120     else
1121     {
1122         LPTSTR lpErrMessage;
1123 
1124         /*
1125          * no mixer devices are available!
1126          */
1127 
1128         hWnd = NULL;
1129         if (AllocAndLoadString(&lpErrMessage,
1130                                hAppInstance,
1131                                IDS_NOMIXERDEVICES))
1132         {
1133             MessageBox(NULL,
1134                        lpErrMessage,
1135                        lpAppTitle,
1136                        MB_ICONINFORMATION);
1137             LocalFree(lpErrMessage);
1138         }
1139     }
1140 
1141     if (hWnd == NULL)
1142     {
1143         HeapFree(hAppHeap,
1144                  0,
1145                  MixerWindow);
1146     }
1147 
1148     return hWnd;
1149 }
1150 
1151 static
1152 BOOL
1153 HandleCommandLine(LPTSTR cmdline,
1154                   PWINDOW_MODE pMode,
1155                   PUINT pMixerId)
1156 {
1157     TCHAR option;
1158 
1159     *pMixerId = 0;
1160     *pMode = SMALL_MODE;
1161 
1162     while (*cmdline == _T(' ') || *cmdline == _T('-') || *cmdline == _T('/'))
1163     {
1164         if (*cmdline++ == _T(' '))
1165             continue;
1166 
1167         option = *cmdline;
1168         if (option)
1169             cmdline++;
1170         while (*cmdline == _T(' '))
1171             cmdline++;
1172 
1173         switch (option)
1174         {
1175             case 'd': /* Device */
1176             case 'D':
1177                 break;
1178 
1179             case 'n': /* Normal size */
1180             case 'N':
1181                 *pMode = NORMAL_MODE;
1182                 break;
1183 
1184             case 's': /* Small size */
1185             case 'S':
1186                 *pMode = SMALL_MODE;
1187                 break;
1188 
1189             case 't': /* Tray size */
1190             case 'T':
1191                 *pMode = TRAY_MODE;
1192                 break;
1193 
1194             case 'p': /* Play mode */
1195             case 'P':
1196                 *pMixerId = 0;
1197                 break;
1198 
1199             case 'r': /* Record mode */
1200             case 'R':
1201                 *pMixerId = 1;
1202                 break;
1203 
1204             default:
1205                 return FALSE;
1206         }
1207     }
1208 
1209     return TRUE;
1210 }
1211 
1212 int WINAPI
1213 _tWinMain(HINSTANCE hInstance,
1214           HINSTANCE hPrevInstance,
1215           LPTSTR lpszCmdLine,
1216           int nCmdShow)
1217 {
1218     MSG Msg;
1219     int Ret = 1;
1220     INITCOMMONCONTROLSEX Controls;
1221     WINDOW_MODE WindowMode = SMALL_MODE;
1222     UINT MixerId = 0;
1223 
1224     UNREFERENCED_PARAMETER(hPrevInstance);
1225     UNREFERENCED_PARAMETER(nCmdShow);
1226 
1227     hAppInstance = hInstance;
1228     hAppHeap = GetProcessHeap();
1229 
1230     HandleCommandLine(lpszCmdLine, &WindowMode, &MixerId);
1231 
1232     if (InitAppConfig())
1233     {
1234         /* load the application title */
1235         if (!AllocAndLoadString(&lpAppTitle,
1236                                 hAppInstance,
1237                                 IDS_SNDVOL32))
1238         {
1239             lpAppTitle = NULL;
1240         }
1241 
1242         Controls.dwSize = sizeof(INITCOMMONCONTROLSEX);
1243         Controls.dwICC = ICC_BAR_CLASSES | ICC_STANDARD_CLASSES;
1244 
1245         InitCommonControlsEx(&Controls);
1246 
1247         if (WindowMode == TRAY_MODE)
1248         {
1249             DialogBoxParam(hAppInstance,
1250                            MAKEINTRESOURCE(IDD_TRAY_MASTER),
1251                            NULL,
1252                            TrayDlgProc,
1253                            0);
1254         }
1255         else
1256         {
1257             if (RegisterApplicationClasses())
1258             {
1259                 hMainWnd = CreateApplicationWindow(WindowMode, MixerId);
1260                 if (hMainWnd != NULL)
1261                 {
1262                     BOOL bRet;
1263                     while ((bRet =GetMessage(&Msg,
1264                                              NULL,
1265                                              0,
1266                                              0)) != 0)
1267                     {
1268                         if (bRet != -1)
1269                         {
1270                             TranslateMessage(&Msg);
1271                             DispatchMessage(&Msg);
1272                         }
1273                     }
1274 
1275                     DestroyWindow(hMainWnd);
1276                     Ret = 0;
1277                 }
1278                 else
1279                 {
1280                     DPRINT("Failed to create application window (LastError: %d)!\n", GetLastError());
1281                 }
1282 
1283                 UnregisterApplicationClasses();
1284             }
1285             else
1286             {
1287                 DPRINT("Failed to register application classes (LastError: %d)!\n", GetLastError());
1288             }
1289         }
1290 
1291         if (lpAppTitle != NULL)
1292         {
1293             LocalFree(lpAppTitle);
1294         }
1295 
1296         CloseAppConfig();
1297     }
1298     else
1299     {
1300         DPRINT("Unable to open the Volume Control registry key!\n");
1301     }
1302 
1303     return Ret;
1304 }
1305