1389 lines
41 KiB
C
1389 lines
41 KiB
C
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// File: volume.c
|
||
|
//
|
||
|
// This file defines the functions that drive the volume
|
||
|
// tab of the Sounds & Multimedia control panel.
|
||
|
//
|
||
|
// History:
|
||
|
// 06 March 2000 RogerW
|
||
|
// Created.
|
||
|
//
|
||
|
// Copyright (C) 2000 Microsoft Corporation All Rights Reserved.
|
||
|
//
|
||
|
// Microsoft Confidential
|
||
|
//
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
//=============================================================================
|
||
|
// Include files
|
||
|
//=============================================================================
|
||
|
#include <windows.h>
|
||
|
#include <windowsx.h>
|
||
|
#include <tchar.h>
|
||
|
#include <regstr.h>
|
||
|
#include <dbt.h>
|
||
|
#include <mmsystem.h>
|
||
|
#include <mmddkp.h>
|
||
|
#include <shlwapi.h>
|
||
|
#include "volume.h"
|
||
|
#include "mmcpl.h"
|
||
|
#include "trayvol.h"
|
||
|
#include "advaudio.h"
|
||
|
#include "medhelp.h"
|
||
|
#include "multchan.h"
|
||
|
|
||
|
// Constants
|
||
|
const SIZE ksizeBrandMax = { 32, 32 }; // Max Branding Bitmap Size
|
||
|
static SZCODE aszSndVolOptionKey[] = REGSTR_PATH_SETUP TEXT("\\SETUP\\OptionalComponents\\Vol");
|
||
|
static SZCODE aszInstalled[] = TEXT("Installed");
|
||
|
static const char aszSndVol32[] = "sndvol32.exe";
|
||
|
#define VOLUME_TICS (500)
|
||
|
static INTCODE aKeyWordIds[] =
|
||
|
{
|
||
|
IDC_VOLUME_BRAND, IDH_VOLUME_BRAND,
|
||
|
IDC_VOLUME_MIXER, IDH_VOLUME_MIXER,
|
||
|
IDC_GROUPBOX, IDH_SOUNDS_SYS_VOL_CONTROL,
|
||
|
IDC_VOLUME_ICON, IDH_COMM_GROUPBOX,
|
||
|
IDC_VOLUME_LOW, IDH_SOUNDS_SYS_VOL_CONTROL,
|
||
|
IDC_MASTERVOLUME, IDH_SOUNDS_SYS_VOL_CONTROL,
|
||
|
IDC_VOLUME_HIGH, IDH_SOUNDS_SYS_VOL_CONTROL,
|
||
|
IDC_VOLUME_MUTE, IDH_SOUNDS_VOL_MUTE_BUTTON,
|
||
|
IDC_TASKBAR_VOLUME, IDH_AUDIO_SHOW_INDICATOR,
|
||
|
IDC_LAUNCH_SNDVOL, IDH_AUDIO_PLAY_VOL,
|
||
|
IDC_GROUPBOX_2, IDH_COMM_GROUPBOX,
|
||
|
IDC_VOLUME_SPEAKER_BITMAP, IDH_COMM_GROUPBOX,
|
||
|
IDC_LAUNCH_MULTICHANNEL, IDH_LAUNCH_MULTICHANNEL,
|
||
|
IDC_PLAYBACK_ADVSETUP, IDH_ADV_AUDIO_PLAY_PROP,
|
||
|
IDC_TEXT_31, NO_HELP,
|
||
|
0,0
|
||
|
};
|
||
|
|
||
|
// TODO: Move to "regstr.h"
|
||
|
#define REGSTR_KEY_BRANDING TEXT("Branding")
|
||
|
#define REGSTR_VAL_AUDIO_BITMAP TEXT("bitmap")
|
||
|
#define REGSTR_VAL_AUDIO_ICON TEXT("icon")
|
||
|
#define REGSTR_VAL_AUDIO_URL TEXT("url")
|
||
|
|
||
|
HBITMAP ghSpkBitmap;
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// %%Function: VolumeTabProc
|
||
|
//
|
||
|
// Parameters: hDlg = window handle of dialog window.
|
||
|
// uiMessage = message ID.
|
||
|
// wParam = message-dependent.
|
||
|
// lParam = message-dependent.
|
||
|
//
|
||
|
// Returns: TRUE if message has been processed, else FALSE
|
||
|
//
|
||
|
// Description: Dialog proc for volume control panel page device change
|
||
|
// message.
|
||
|
//
|
||
|
//
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
LRESULT CALLBACK VolumeTabProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
if (iMsg == g_uiVolDevChange)
|
||
|
{
|
||
|
InitVolume (g_hWnd);
|
||
|
}
|
||
|
|
||
|
return CallWindowProc (g_fnVolPSProc, hwnd, iMsg, wParam, lParam);
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// %%Function: VolumeDlg
|
||
|
//
|
||
|
// Parameters: hDlg = window handle of dialog window.
|
||
|
// uiMessage = message ID.
|
||
|
// wParam = message-dependent.
|
||
|
// lParam = message-dependent.
|
||
|
//
|
||
|
// Returns: TRUE if message has been processed, else FALSE
|
||
|
//
|
||
|
// Description: Dialog proc for volume control panel page.
|
||
|
//
|
||
|
//
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
BOOL CALLBACK VolumeDlg (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
|
||
|
switch (uMsg)
|
||
|
{
|
||
|
case WM_NOTIFY:
|
||
|
{
|
||
|
OnNotify (hDlg, (LPNMHDR) lParam);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_INITDIALOG:
|
||
|
{
|
||
|
HANDLE_WM_INITDIALOG (hDlg, wParam, lParam, OnInitDialog);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_DESTROY:
|
||
|
{
|
||
|
HANDLE_WM_DESTROY (hDlg, wParam, lParam, OnDestroy);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_COMMAND:
|
||
|
{
|
||
|
HANDLE_WM_COMMAND (hDlg, wParam, lParam, OnCommand);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_POWERBROADCAST:
|
||
|
{
|
||
|
HandlePowerBroadcast (hDlg, wParam, lParam);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case MM_MIXM_LINE_CHANGE:
|
||
|
case MM_MIXM_CONTROL_CHANGE:
|
||
|
{
|
||
|
if (!g_fInternalGenerated)
|
||
|
{
|
||
|
RefreshMixCache ();
|
||
|
DisplayVolumeControl(hDlg);
|
||
|
}
|
||
|
|
||
|
g_fInternalGenerated = FALSE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_HSCROLL:
|
||
|
{
|
||
|
HANDLE_WM_HSCROLL (hDlg, wParam, lParam, MasterVolumeScroll);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_CONTEXTMENU:
|
||
|
{
|
||
|
WinHelp ((HWND)wParam, NULL, HELP_CONTEXTMENU,
|
||
|
(UINT_PTR)(LPTSTR)aKeyWordIds);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_HELP:
|
||
|
{
|
||
|
WinHelp (((LPHELPINFO)lParam)->hItemHandle, NULL, HELP_WM_HELP
|
||
|
, (UINT_PTR)(LPTSTR)aKeyWordIds);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_DEVICECHANGE:
|
||
|
{
|
||
|
DeviceChange_Change (hDlg, wParam, lParam);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_SYSCOLORCHANGE:
|
||
|
{
|
||
|
if (ghSpkBitmap)
|
||
|
{
|
||
|
DeleteObject(ghSpkBitmap);
|
||
|
ghSpkBitmap = NULL;
|
||
|
}
|
||
|
|
||
|
ghSpkBitmap = (HBITMAP) LoadImage(ghInstance,MAKEINTATOM(IDB_MULTICHANNEL_SPKR), IMAGE_BITMAP,
|
||
|
0, 0, LR_LOADMAP3DCOLORS);
|
||
|
|
||
|
SendDlgItemMessage(hDlg, IDC_VOLUME_SPEAKER_BITMAP, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) ghSpkBitmap);
|
||
|
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_WININICHANGE:
|
||
|
case WM_DISPLAYCHANGE :
|
||
|
{
|
||
|
SendDlgItemMessage (hDlg, IDC_MASTERVOLUME, uMsg, wParam, lParam);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void OnNotify (HWND hDlg, LPNMHDR pnmh)
|
||
|
{
|
||
|
if (!pnmh)
|
||
|
{
|
||
|
DPF ("bad WM_NOTIFY pointer\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch (pnmh->code)
|
||
|
{
|
||
|
case PSN_KILLACTIVE:
|
||
|
FORWARD_WM_COMMAND (hDlg, IDOK, 0, 0, SendMessage);
|
||
|
break;
|
||
|
|
||
|
case PSN_APPLY:
|
||
|
FORWARD_WM_COMMAND (hDlg, ID_APPLY, 0, 0, SendMessage);
|
||
|
break;
|
||
|
|
||
|
case PSN_RESET:
|
||
|
FORWARD_WM_COMMAND (hDlg, IDCANCEL, 0, 0, SendMessage);
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void OnDestroy (HWND hDlg)
|
||
|
{
|
||
|
// Unregister from notifications
|
||
|
DeviceChange_Cleanup();
|
||
|
SetWindowLongPtr (GetParent (hDlg), GWLP_WNDPROC, (LONG_PTR) g_fnVolPSProc);
|
||
|
// Free any mixer we have
|
||
|
FreeMixer ();
|
||
|
// Free any branding bitmap
|
||
|
FreeBrandBmp ();
|
||
|
// Free detail memory
|
||
|
|
||
|
DeleteObject( ghSpkBitmap );
|
||
|
|
||
|
if (g_mcd.paDetails)
|
||
|
{
|
||
|
LocalFree (g_mcd.paDetails);
|
||
|
g_mcd.paDetails = NULL;
|
||
|
}
|
||
|
if (g_pvPrevious)
|
||
|
{
|
||
|
LocalFree (g_pvPrevious);
|
||
|
g_pvPrevious = NULL;
|
||
|
}
|
||
|
if (g_pdblCacheMix)
|
||
|
{
|
||
|
LocalFree (g_pdblCacheMix);
|
||
|
g_pdblCacheMix = NULL;
|
||
|
}
|
||
|
// Free URL memory
|
||
|
if( g_szHotLinkURL )
|
||
|
{
|
||
|
LocalFree( g_szHotLinkURL );
|
||
|
g_szHotLinkURL = NULL;
|
||
|
}
|
||
|
|
||
|
ZeroMemory (&g_mcd, sizeof (g_mcd));
|
||
|
ZeroMemory (&g_mlDst, sizeof (g_mlDst));
|
||
|
}
|
||
|
|
||
|
void CreateHotLink (BOOL fHotLink)
|
||
|
{
|
||
|
WCHAR szMixerName[MAXMIXERLEN];
|
||
|
WCHAR* szLinkName;
|
||
|
UINT uiLinkSize = 0;
|
||
|
|
||
|
// Underline the mixer name to appear like a browser hot-link.
|
||
|
HWND hWndMixerName = GetDlgItem (g_hWnd, IDC_VOLUME_MIXER);
|
||
|
DWORD dwStyle = GetWindowLong (hWndMixerName, GWL_STYLE);
|
||
|
|
||
|
if (fHotLink)
|
||
|
{
|
||
|
GetDlgItemText( g_hWnd, IDC_VOLUME_MIXER, szMixerName, MAXMIXERLEN);
|
||
|
|
||
|
uiLinkSize = ((lstrlen(g_szHotLinkURL) * sizeof(WCHAR)) + (lstrlen(szMixerName) * sizeof(WCHAR))
|
||
|
+ (17 * sizeof(WCHAR))); // The 17 is for extra characters plus a NULL
|
||
|
|
||
|
szLinkName = (WCHAR *)LocalAlloc (LPTR, uiLinkSize);
|
||
|
if (szLinkName)
|
||
|
{
|
||
|
wsprintf(szLinkName, TEXT("<A HREF=\"%s\">%s</A>"), g_szHotLinkURL, szMixerName);
|
||
|
SetDlgItemText( g_hWnd, IDC_VOLUME_MIXER, szLinkName);
|
||
|
|
||
|
LocalFree(szLinkName);
|
||
|
}
|
||
|
|
||
|
EnableWindow(hWndMixerName, TRUE);
|
||
|
dwStyle |= WS_TABSTOP;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
EnableWindow(hWndMixerName, FALSE);
|
||
|
dwStyle &= ~WS_TABSTOP;
|
||
|
}
|
||
|
|
||
|
// Apply new style (remove/add tab stop)
|
||
|
SetWindowLong (hWndMixerName, GWL_STYLE, dwStyle);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL OnInitDialog (HWND hDlg, HWND hwndFocus, LPARAM lParam)
|
||
|
{
|
||
|
|
||
|
// Init Globals
|
||
|
g_hWnd = hDlg;
|
||
|
g_fChanged = FALSE;
|
||
|
g_fInternalGenerated = FALSE;
|
||
|
// Set Device Change Notification
|
||
|
g_fnVolPSProc = (WNDPROC) SetWindowLongPtr (GetParent (hDlg), GWLP_WNDPROC, (LONG_PTR) VolumeTabProc);
|
||
|
g_uiVolDevChange = RegisterWindowMessage (_T("winmm_devicechange"));
|
||
|
// Save the default "No Audio Device" string
|
||
|
GetDlgItemText (hDlg, IDC_VOLUME_MIXER, g_szNoAudioDevice, sizeof(g_szNoAudioDevice)/sizeof(g_szNoAudioDevice[0]));
|
||
|
|
||
|
// Init Volume
|
||
|
InitVolume (hDlg);
|
||
|
|
||
|
if (ghSpkBitmap)
|
||
|
{
|
||
|
DeleteObject(ghSpkBitmap);
|
||
|
ghSpkBitmap = NULL;
|
||
|
}
|
||
|
|
||
|
ghSpkBitmap = (HBITMAP) LoadImage(ghInstance,MAKEINTATOM(IDB_MULTICHANNEL_SPKR), IMAGE_BITMAP,
|
||
|
0, 0, LR_LOADMAP3DCOLORS);
|
||
|
SendDlgItemMessage(hDlg, IDC_VOLUME_SPEAKER_BITMAP, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) ghSpkBitmap);
|
||
|
|
||
|
|
||
|
return FALSE;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL PASCAL OnCommand (HWND hDlg, int id, HWND hwndCtl, UINT codeNotify)
|
||
|
{
|
||
|
switch (id)
|
||
|
{
|
||
|
case IDC_TASKBAR_VOLUME:
|
||
|
{
|
||
|
if (Button_GetCheck (GetDlgItem (hDlg, IDC_TASKBAR_VOLUME)) && (!SndVolPresent ()))
|
||
|
{
|
||
|
CheckDlgButton (hDlg, IDC_TASKBAR_VOLUME, FALSE);
|
||
|
ErrorBox (hDlg, IDS_NOSNDVOL, NULL);
|
||
|
g_sndvolPresent = sndvolNotChecked; // Reset
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_fTrayIcon = Button_GetCheck (GetDlgItem (hDlg, IDC_TASKBAR_VOLUME));
|
||
|
|
||
|
PropSheet_Changed(GetParent(hDlg),hDlg);
|
||
|
g_fChanged = TRUE;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IDC_VOLUME_MUTE:
|
||
|
{
|
||
|
BOOL fMute = !GetMute ();
|
||
|
SetMute(fMute);
|
||
|
|
||
|
if ((g_fPreviousMute != fMute) && !g_fChanged)
|
||
|
{
|
||
|
g_fChanged = TRUE;
|
||
|
PropSheet_Changed(GetParent(hDlg),hDlg);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ID_APPLY:
|
||
|
{
|
||
|
// Update Tray Icon
|
||
|
BOOL fTrayIcon = Button_GetCheck (GetDlgItem (hDlg, IDC_TASKBAR_VOLUME));
|
||
|
if (fTrayIcon != GetTrayVolumeEnabled ())
|
||
|
{
|
||
|
g_fTrayIcon = fTrayIcon;
|
||
|
SetTrayVolumeEnabled(g_fTrayIcon);
|
||
|
}
|
||
|
|
||
|
if (SUCCEEDED (GetVolume ()) && g_pvPrevious && g_mcd.paDetails)
|
||
|
{
|
||
|
// Copy data so can undo volume changes
|
||
|
memcpy (g_pvPrevious, g_mcd.paDetails, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlDst.cChannels);
|
||
|
DisplayVolumeControl (hDlg);
|
||
|
}
|
||
|
g_fPreviousMute = GetMute ();
|
||
|
|
||
|
g_fChanged = FALSE;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IDOK:
|
||
|
{
|
||
|
// OK processing handled in ID_APPLY because it is always
|
||
|
// called from the property sheet's IDOK processing.
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IDCANCEL:
|
||
|
{
|
||
|
if (g_hMixer)
|
||
|
{
|
||
|
SetMute (g_fPreviousMute);
|
||
|
if (g_pvPrevious && g_mcd.paDetails)
|
||
|
{
|
||
|
// Undo volume changes
|
||
|
memcpy (g_mcd.paDetails, g_pvPrevious, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlDst.cChannels);
|
||
|
g_fInternalGenerated = TRUE;
|
||
|
mixerSetControlDetails ((HMIXEROBJ) g_hMixer, &g_mcd, MIXER_SETCONTROLDETAILSF_VALUE);
|
||
|
g_fInternalGenerated = FALSE;
|
||
|
}
|
||
|
}
|
||
|
WinHelp (hDlg, gszWindowsHlp, HELP_QUIT, 0L);
|
||
|
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IDC_LAUNCH_SNDVOL:
|
||
|
{
|
||
|
TCHAR szCmd[MAXSTR];
|
||
|
STARTUPINFO si;
|
||
|
PROCESS_INFORMATION pi;
|
||
|
|
||
|
memset(&si, 0, sizeof(si));
|
||
|
si.cb = sizeof(si);
|
||
|
si.wShowWindow = SW_SHOW;
|
||
|
si.dwFlags = STARTF_USESHOWWINDOW;
|
||
|
wsprintf(szCmd,TEXT("sndvol32.exe -D %d"), g_uiMixID);
|
||
|
|
||
|
if (!CreateProcess(NULL,szCmd,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi))
|
||
|
{
|
||
|
ErrorBox (hDlg, IDS_NOSNDVOL, NULL);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IDC_LAUNCH_MULTICHANNEL:
|
||
|
{
|
||
|
Multichannel (hDlg, g_uiMixID, g_dwDest, g_dwVolID);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case IDC_PLAYBACK_ADVSETUP:
|
||
|
{
|
||
|
MIXERCAPS mc;
|
||
|
DWORD dwDeviceID = g_uiMixID;
|
||
|
|
||
|
|
||
|
if (MMSYSERR_NOERROR == mixerGetDevCaps (g_uiMixID, &mc, sizeof (mc)))
|
||
|
{
|
||
|
AdvancedAudio (hDlg, ghInstance, gszWindowsHlp, dwDeviceID, mc.szPname, FALSE);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
return FALSE;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void InitVolume (HWND hDlg)
|
||
|
{
|
||
|
|
||
|
FreeMixer ();
|
||
|
|
||
|
// Get the master volume & display
|
||
|
MasterVolumeConfig (hDlg, &g_uiMixID);
|
||
|
|
||
|
if (SUCCEEDED (GetVolume ()) && g_pvPrevious && g_mcd.paDetails)
|
||
|
{
|
||
|
RefreshMixCache ();
|
||
|
// Copy data so can undo volume changes
|
||
|
memcpy (g_pvPrevious, g_mcd.paDetails, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlDst.cChannels);
|
||
|
|
||
|
g_fPreviousMute = GetMute ();
|
||
|
}
|
||
|
DisplayVolumeControl (hDlg);
|
||
|
|
||
|
DeviceChange_Init (hDlg, g_uiMixID);
|
||
|
|
||
|
}
|
||
|
|
||
|
// returns current mute state
|
||
|
BOOL GetMute ()
|
||
|
{
|
||
|
BOOL fMute = FALSE;
|
||
|
|
||
|
if (g_hMixer && (g_dwMuteID != (DWORD) -1))
|
||
|
{
|
||
|
MIXERCONTROLDETAILS_UNSIGNED mcuMute;
|
||
|
MIXERCONTROLDETAILS mcd = g_mcd;
|
||
|
|
||
|
// Modify local copy for mute ...
|
||
|
mcd.dwControlID = g_dwMuteID;
|
||
|
mcd.cChannels = 1;
|
||
|
mcd.paDetails = &mcuMute;
|
||
|
mixerGetControlDetails ((HMIXEROBJ) g_hMixer, &mcd, MIXER_GETCONTROLDETAILSF_VALUE);
|
||
|
|
||
|
fMute = (BOOL) mcuMute.dwValue;
|
||
|
}
|
||
|
|
||
|
return fMute;
|
||
|
}
|
||
|
|
||
|
BOOL SndVolPresent ()
|
||
|
{
|
||
|
|
||
|
if (g_sndvolPresent == sndvolNotChecked)
|
||
|
{
|
||
|
OFSTRUCT of;
|
||
|
if (HFILE_ERROR != OpenFile (aszSndVol32, &of, OF_EXIST | OF_SHARE_DENY_NONE))
|
||
|
{
|
||
|
g_sndvolPresent = sndvolPresent;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
HKEY hkSndVol;
|
||
|
g_sndvolPresent = sndvolNotPresent;
|
||
|
|
||
|
if (!RegOpenKey (HKEY_LOCAL_MACHINE, aszSndVolOptionKey, &hkSndVol))
|
||
|
{
|
||
|
RegSetValueEx (hkSndVol, (LPTSTR) aszInstalled, 0L, REG_SZ, (LPBYTE)(TEXT("0")), 4);
|
||
|
RegCloseKey (hkSndVol);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (sndvolPresent == g_sndvolPresent);
|
||
|
|
||
|
}
|
||
|
|
||
|
void FreeMixer ()
|
||
|
{
|
||
|
if (g_hMixer)
|
||
|
{
|
||
|
mixerClose (g_hMixer);
|
||
|
g_hMixer = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Gets the primary audio device ID and find the mixer line for it
|
||
|
// It leaves it open so the slider can respond to other changes outside this app
|
||
|
//
|
||
|
void MasterVolumeConfig (HWND hWnd, UINT* puiMixID)
|
||
|
{
|
||
|
|
||
|
UINT uiWaveID;
|
||
|
|
||
|
// Init
|
||
|
g_fMasterVolume = g_fTrayIcon = g_fMasterMute = FALSE;
|
||
|
g_dwDest = g_dwVolID = g_dwMuteID = 0;
|
||
|
|
||
|
ResetBranding (hWnd);
|
||
|
|
||
|
if (puiMixID && GetWaveID (&uiWaveID) == MMSYSERR_NOERROR)
|
||
|
{
|
||
|
if (MMSYSERR_NOERROR == mixerGetID (HMIXEROBJ_INDEX(uiWaveID), puiMixID, MIXER_OBJECTF_WAVEOUT))
|
||
|
{
|
||
|
SetBranding (hWnd, *puiMixID);
|
||
|
|
||
|
if (SearchDevice (*puiMixID, &g_dwDest, &g_dwVolID, &g_dwMuteID))
|
||
|
{
|
||
|
FreeMixer ();
|
||
|
|
||
|
if (MMSYSERR_NOERROR == mixerOpen (&g_hMixer, *puiMixID, (DWORD_PTR) hWnd, 0L, CALLBACK_WINDOW))
|
||
|
{
|
||
|
|
||
|
ZeroMemory (&g_mlDst, sizeof (g_mlDst));
|
||
|
g_mlDst.cbStruct = sizeof (g_mlDst);
|
||
|
g_mlDst.dwDestination = g_dwDest;
|
||
|
|
||
|
if (MMSYSERR_NOERROR == mixerGetLineInfo ((HMIXEROBJ)g_hMixer, &g_mlDst, MIXER_GETLINEINFOF_DESTINATION))
|
||
|
{
|
||
|
g_mcd.cbStruct = sizeof (g_mcd);
|
||
|
g_mcd.dwControlID = g_dwVolID;
|
||
|
g_mcd.cChannels = g_mlDst.cChannels;
|
||
|
g_mcd.hwndOwner = 0;
|
||
|
g_mcd.cMultipleItems = 0;
|
||
|
g_mcd.cbDetails = sizeof (DWORD); // seems like it would be sizeof(g_mcd),
|
||
|
// but actually, it is the size of a single value
|
||
|
// and is multiplied by channel in the driver.
|
||
|
// TODO: Should Return Error on failure!
|
||
|
g_mcd.paDetails = (MIXERCONTROLDETAILS_UNSIGNED*) LocalAlloc (LPTR, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlDst.cChannels);
|
||
|
g_pvPrevious = (MIXERCONTROLDETAILS_UNSIGNED*) LocalAlloc (LPTR, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlDst.cChannels);
|
||
|
g_pdblCacheMix = (double*) LocalAlloc (LPTR, sizeof (double) * g_mlDst.cChannels);
|
||
|
|
||
|
g_fMasterVolume = TRUE;
|
||
|
g_fMasterMute = (g_dwMuteID != (DWORD) -1);
|
||
|
g_fTrayIcon = GetTrayVolumeEnabled ();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Locates the master volume and mute controls for this mixer line
|
||
|
//
|
||
|
void SearchControls(int mxid, LPMIXERLINE pml, LPDWORD pdwVolID, LPDWORD pdwMuteID, BOOL *pfFound)
|
||
|
{
|
||
|
MIXERLINECONTROLS mlc;
|
||
|
DWORD dwControl;
|
||
|
|
||
|
memset(&mlc, 0, sizeof(mlc));
|
||
|
mlc.cbStruct = sizeof(mlc);
|
||
|
mlc.dwLineID = pml->dwLineID;
|
||
|
mlc.cControls = pml->cControls;
|
||
|
mlc.cbmxctrl = sizeof(MIXERCONTROL);
|
||
|
mlc.pamxctrl = (LPMIXERCONTROL) GlobalAlloc(GMEM_FIXED, sizeof(MIXERCONTROL) * pml->cControls);
|
||
|
|
||
|
if (mlc.pamxctrl)
|
||
|
{
|
||
|
if (mixerGetLineControls(HMIXEROBJ_INDEX(mxid), &mlc, MIXER_GETLINECONTROLSF_ALL) == MMSYSERR_NOERROR)
|
||
|
{
|
||
|
for (dwControl = 0; dwControl < pml->cControls && !(*pfFound); dwControl++)
|
||
|
{
|
||
|
if (mlc.pamxctrl[dwControl].dwControlType == (DWORD)MIXERCONTROL_CONTROLTYPE_VOLUME)
|
||
|
{
|
||
|
DWORD dwIndex;
|
||
|
DWORD dwVolID = (DWORD) -1;
|
||
|
DWORD dwMuteID = (DWORD) -1;
|
||
|
|
||
|
dwVolID = mlc.pamxctrl[dwControl].dwControlID;
|
||
|
|
||
|
for (dwIndex = 0; dwIndex < pml->cControls; dwIndex++)
|
||
|
{
|
||
|
if (mlc.pamxctrl[dwIndex].dwControlType == (DWORD)MIXERCONTROL_CONTROLTYPE_MUTE)
|
||
|
{
|
||
|
dwMuteID = mlc.pamxctrl[dwIndex].dwControlID;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*pfFound = TRUE;
|
||
|
*pdwVolID = dwVolID;
|
||
|
*pdwMuteID = dwMuteID;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
GlobalFree((HGLOBAL) mlc.pamxctrl);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Locates the volume slider control for this mixer device
|
||
|
//
|
||
|
BOOL SearchDevice (DWORD dwMixID, LPDWORD pdwDest, LPDWORD pdwVolID, LPDWORD pdwMuteID)
|
||
|
{
|
||
|
MIXERCAPS mc;
|
||
|
MMRESULT mmr;
|
||
|
BOOL fFound = FALSE;
|
||
|
|
||
|
mmr = mixerGetDevCaps(dwMixID, &mc, sizeof(mc));
|
||
|
|
||
|
if (mmr == MMSYSERR_NOERROR)
|
||
|
{
|
||
|
MIXERLINE mlDst;
|
||
|
DWORD dwDestination;
|
||
|
|
||
|
for (dwDestination = 0; dwDestination < mc.cDestinations && !fFound; dwDestination++)
|
||
|
{
|
||
|
mlDst.cbStruct = sizeof ( mlDst );
|
||
|
mlDst.dwDestination = dwDestination;
|
||
|
|
||
|
if (mixerGetLineInfo(HMIXEROBJ_INDEX(dwMixID), &mlDst, MIXER_GETLINEINFOF_DESTINATION ) == MMSYSERR_NOERROR)
|
||
|
{
|
||
|
if (mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_DST_SPEAKERS || // needs to be a likely output destination
|
||
|
mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_DST_HEADPHONES ||
|
||
|
mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT)
|
||
|
{
|
||
|
if (!fFound && mlDst.cControls) // If there are controls, we'll take the master
|
||
|
{
|
||
|
SearchControls(dwMixID, &mlDst, pdwVolID, pdwMuteID, &fFound);
|
||
|
*pdwDest = dwDestination;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return(fFound);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Call this function to configure to the current preferred device and reflect master volume
|
||
|
// settings on the slider
|
||
|
//
|
||
|
void DisplayVolumeControl (HWND hDlg)
|
||
|
{
|
||
|
HWND hwndVol = GetDlgItem(hDlg, IDC_MASTERVOLUME);
|
||
|
BOOL fMute = g_fMasterMute && GetMute ();
|
||
|
|
||
|
SendMessage(hwndVol, TBM_SETTICFREQ, VOLUME_TICS / 10, 0);
|
||
|
SendMessage(hwndVol, TBM_SETRANGE, FALSE, MAKELONG(0,VOLUME_TICS));
|
||
|
|
||
|
EnableWindow(GetDlgItem(hDlg, IDC_MASTERVOLUME) , g_fMasterVolume);
|
||
|
EnableWindow(GetDlgItem(hDlg, IDC_VOLUME_LOW) , g_fMasterVolume);
|
||
|
EnableWindow(GetDlgItem(hDlg, IDC_VOLUME_HIGH) , g_fMasterVolume);
|
||
|
EnableWindow(GetDlgItem(hDlg, IDC_TASKBAR_VOLUME),g_fMasterVolume);
|
||
|
EnableWindow(GetDlgItem(hDlg, IDC_VOLUME_MUTE), g_fMasterMute);
|
||
|
EnableWindow(GetDlgItem(hDlg, IDC_LAUNCH_SNDVOL), g_fMasterVolume && SndVolPresent ());
|
||
|
EnableWindow(GetDlgItem(hDlg, IDC_LAUNCH_MULTICHANNEL), g_fMasterVolume);
|
||
|
EnableWindow(GetDlgItem(hDlg, IDC_PLAYBACK_ADVSETUP), g_fMasterVolume);
|
||
|
|
||
|
if (g_fMasterVolume)
|
||
|
{
|
||
|
UpdateVolumeSlider (hDlg, g_dwVolID);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SendMessage(GetDlgItem(hDlg, IDC_MASTERVOLUME), TBM_SETPOS, TRUE, 0 );
|
||
|
}
|
||
|
|
||
|
// Show if we are muted
|
||
|
Button_SetCheck (GetDlgItem (hDlg, IDC_VOLUME_MUTE), fMute);
|
||
|
|
||
|
// This displays the appropriate volume icon for the master mute state
|
||
|
// This looks like a memory leak, but it's not. LoadIcon just gets a handle to the already-loaded
|
||
|
// icon if it was previously loaded. See the docs for LoadIcon and DestroyIcon (which specifically
|
||
|
// should not be called with a handle to an icon loaded via LoadIcon).
|
||
|
if( fMute )
|
||
|
{
|
||
|
SendMessage (GetDlgItem (hDlg, IDC_VOLUME_ICON), STM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIcon(ghInstance, MAKEINTRESOURCE (IDI_MUTESPEAKERICON)) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SendMessage (GetDlgItem (hDlg, IDC_VOLUME_ICON), STM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIcon(ghInstance, MAKEINTRESOURCE (IDI_SPEAKERICON)) );
|
||
|
}
|
||
|
CheckDlgButton (hDlg, IDC_TASKBAR_VOLUME, g_fTrayIcon);
|
||
|
|
||
|
}
|
||
|
|
||
|
// Called to update the slider when the volume is changed externally
|
||
|
//
|
||
|
void UpdateVolumeSlider(HWND hWnd, DWORD dwLine)
|
||
|
{
|
||
|
if ((g_hMixer != NULL) && (g_dwVolID != (DWORD) -1) && (dwLine == g_dwVolID))
|
||
|
{
|
||
|
double volume = ((double) GetMaxVolume () / (double) 0xFFFF) * ((double) VOLUME_TICS);
|
||
|
// The 0.5f forces rounding (instead of truncation)
|
||
|
SendMessage(GetDlgItem(hWnd, IDC_MASTERVOLUME), TBM_SETPOS, TRUE, (DWORD) (volume+0.5f) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// returns current volume level
|
||
|
//
|
||
|
DWORD GetMaxVolume ()
|
||
|
{
|
||
|
DWORD dwVol = 0;
|
||
|
|
||
|
if (SUCCEEDED (GetVolume ()))
|
||
|
{
|
||
|
UINT uiIndx;
|
||
|
MIXERCONTROLDETAILS_UNSIGNED* pmcuVolume;
|
||
|
|
||
|
for (uiIndx = 0; uiIndx < g_mlDst.cChannels; uiIndx++)
|
||
|
{
|
||
|
pmcuVolume = ((MIXERCONTROLDETAILS_UNSIGNED*)g_mcd.paDetails + uiIndx);
|
||
|
dwVol = max (dwVol, pmcuVolume -> dwValue);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return dwVol;
|
||
|
}
|
||
|
|
||
|
HRESULT GetVolume ()
|
||
|
{
|
||
|
HRESULT hr = E_FAIL;
|
||
|
if (g_hMixer && g_mcd.paDetails)
|
||
|
{
|
||
|
g_mcd.dwControlID = g_dwVolID;
|
||
|
ZeroMemory (g_mcd.paDetails, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlDst.cChannels);
|
||
|
hr = mixerGetControlDetails ((HMIXEROBJ)g_hMixer, &g_mcd, MIXER_GETCONTROLDETAILSF_VALUE);
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
void DeviceChange_Cleanup ()
|
||
|
{
|
||
|
if (g_hDeviceEventContext)
|
||
|
{
|
||
|
UnregisterDeviceNotification (g_hDeviceEventContext);
|
||
|
g_hDeviceEventContext = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL DeviceChange_GetHandle(DWORD dwMixerID, HANDLE *phDevice)
|
||
|
{
|
||
|
MMRESULT mmr;
|
||
|
ULONG cbSize=0;
|
||
|
TCHAR *szInterfaceName=NULL;
|
||
|
|
||
|
//Query for the Device interface name
|
||
|
mmr = mixerMessage(HMIXER_INDEX(dwMixerID), DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR)&cbSize, 0L);
|
||
|
if(MMSYSERR_NOERROR == mmr)
|
||
|
{
|
||
|
szInterfaceName = (TCHAR *)GlobalAllocPtr(GHND, (cbSize+1)*sizeof(TCHAR));
|
||
|
if(!szInterfaceName)
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
mmr = mixerMessage(HMIXER_INDEX(dwMixerID), DRV_QUERYDEVICEINTERFACE, (DWORD_PTR)szInterfaceName, cbSize);
|
||
|
if(MMSYSERR_NOERROR != mmr)
|
||
|
{
|
||
|
GlobalFreePtr(szInterfaceName);
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
//Get an handle on the device interface name.
|
||
|
*phDevice = CreateFile(szInterfaceName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||
|
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||
|
|
||
|
GlobalFreePtr(szInterfaceName);
|
||
|
if(INVALID_HANDLE_VALUE == *phDevice)
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
void DeviceChange_Init(HWND hWnd, DWORD dwMixerID)
|
||
|
{
|
||
|
DEV_BROADCAST_HANDLE DevBrodHandle;
|
||
|
HANDLE hMixerDevice=NULL;
|
||
|
|
||
|
//If we had registered already for device notifications, unregister ourselves.
|
||
|
DeviceChange_Cleanup();
|
||
|
|
||
|
//If we get the device handle register for device notifications on it.
|
||
|
if(DeviceChange_GetHandle(dwMixerID, &hMixerDevice))
|
||
|
{
|
||
|
memset(&DevBrodHandle, 0, sizeof(DEV_BROADCAST_HANDLE));
|
||
|
|
||
|
DevBrodHandle.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
|
||
|
DevBrodHandle.dbch_devicetype = DBT_DEVTYP_HANDLE;
|
||
|
DevBrodHandle.dbch_handle = hMixerDevice;
|
||
|
|
||
|
g_hDeviceEventContext = RegisterDeviceNotification(hWnd, &DevBrodHandle, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||
|
|
||
|
if(hMixerDevice)
|
||
|
{
|
||
|
CloseHandle(hMixerDevice);
|
||
|
hMixerDevice = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Handle the case where we need to dump mixer handle so PnP can get rid of a device
|
||
|
// We assume we will get the WINMM_DEVICECHANGE handle when the dust settles after a remove or add
|
||
|
// except for DEVICEQUERYREMOVEFAILED which will not generate that message.
|
||
|
//
|
||
|
void DeviceChange_Change(HWND hDlg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
PDEV_BROADCAST_HANDLE bh = (PDEV_BROADCAST_HANDLE)lParam;
|
||
|
|
||
|
if(!g_hDeviceEventContext || !bh || bh->dbch_devicetype != DBT_DEVTYP_HANDLE)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch (wParam)
|
||
|
{
|
||
|
case DBT_DEVICEQUERYREMOVE: // Must free up Mixer if they are trying to remove the device
|
||
|
{
|
||
|
FreeMixer ();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case DBT_DEVICEQUERYREMOVEFAILED: // Didn't happen, need to re-acquire mixer
|
||
|
{
|
||
|
InitVolume (hDlg);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Sets the mute state
|
||
|
void SetMute(BOOL fMute)
|
||
|
{
|
||
|
if (g_hMixer)
|
||
|
{
|
||
|
MIXERCONTROLDETAILS_UNSIGNED mcuMute;
|
||
|
MIXERCONTROLDETAILS mcd = g_mcd;
|
||
|
|
||
|
// Modify local copy for mute ...
|
||
|
mcuMute.dwValue = (DWORD) fMute;
|
||
|
mcd.dwControlID = g_dwMuteID;
|
||
|
mcd.cChannels = 1;
|
||
|
mcd.paDetails = &mcuMute;
|
||
|
|
||
|
mixerSetControlDetails ((HMIXEROBJ)g_hMixer, &mcd, MIXER_SETCONTROLDETAILSF_VALUE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Called in response to slider movement, computes new volume level and sets it
|
||
|
// it also controls the apply state (changed or not)
|
||
|
//
|
||
|
void MasterVolumeScroll (HWND hwnd, HWND hwndCtl, UINT code, int pos)
|
||
|
{
|
||
|
DWORD dwVol = (DWORD) SendMessage(GetDlgItem(hwnd, IDC_MASTERVOLUME), TBM_GETPOS, 0, 0);
|
||
|
|
||
|
dwVol = (DWORD) (((double) dwVol / (double) VOLUME_TICS) * (double) 0xFFFF);
|
||
|
SetVolume(dwVol);
|
||
|
|
||
|
if (!g_fChanged && (memcmp (g_pvPrevious, g_mcd.paDetails,
|
||
|
sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlDst.cChannels)))
|
||
|
{
|
||
|
g_fChanged = TRUE;
|
||
|
PropSheet_Changed(GetParent(hwnd),hwnd);
|
||
|
}
|
||
|
|
||
|
// Play a sound on for the master volume slider when the
|
||
|
// user ends the scroll and we are still in focus and the topmost app.
|
||
|
if (code == SB_ENDSCROLL && hwndCtl == GetFocus() && GetParent (hwnd) == GetForegroundWindow ())
|
||
|
{
|
||
|
static const TCHAR cszDefSnd[] = TEXT(".Default");
|
||
|
PlaySound(cszDefSnd, NULL, SND_ASYNC | SND_ALIAS);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Sets the volume level
|
||
|
//
|
||
|
void SetVolume (DWORD dwVol)
|
||
|
{
|
||
|
|
||
|
if (g_hMixer && g_pdblCacheMix && g_mcd.paDetails)
|
||
|
{
|
||
|
UINT uiIndx;
|
||
|
MIXERCONTROLDETAILS_UNSIGNED* pmcuVolume;
|
||
|
|
||
|
// Caculate the new volume level for each of the channels. For volume levels
|
||
|
// at the current max, we simply set the newly requested level (in this case
|
||
|
// the cache value is 1.0). For those less than the max, we set a value that
|
||
|
// is a percentage of the max. This maintains the relative distance of the
|
||
|
// channel levels from each other.
|
||
|
for (uiIndx = 0; uiIndx < g_mlDst.cChannels; uiIndx++)
|
||
|
{
|
||
|
pmcuVolume = ((MIXERCONTROLDETAILS_UNSIGNED*)g_mcd.paDetails + uiIndx);
|
||
|
// The 0.5f forces rounding (instead of truncation)
|
||
|
pmcuVolume -> dwValue = (DWORD)(*(g_pdblCacheMix + uiIndx) * (double) dwVol + 0.5f);
|
||
|
}
|
||
|
|
||
|
g_fInternalGenerated = TRUE;
|
||
|
mixerSetControlDetails ((HMIXEROBJ)g_hMixer, &g_mcd, MIXER_SETCONTROLDETAILSF_VALUE);
|
||
|
g_fInternalGenerated = FALSE;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void HandlePowerBroadcast (HWND hWnd, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
switch (wParam)
|
||
|
{
|
||
|
case PBT_APMQUERYSUSPEND:
|
||
|
{
|
||
|
FreeMixer ();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case PBT_APMQUERYSUSPENDFAILED:
|
||
|
case PBT_APMRESUMESUSPEND:
|
||
|
{
|
||
|
InitVolume (hWnd);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
BOOL ChannelsAllMinimum()
|
||
|
{
|
||
|
MIXERCONTROLDETAILS_UNSIGNED* pmcuVolume;
|
||
|
|
||
|
if (g_hMixer && g_mcd.paDetails)
|
||
|
{
|
||
|
UINT uiIndx;
|
||
|
for (uiIndx = 0; uiIndx < g_mlDst.cChannels; uiIndx++)
|
||
|
{
|
||
|
pmcuVolume = (MIXERCONTROLDETAILS_UNSIGNED*)g_mcd.paDetails + uiIndx;
|
||
|
if ( pmcuVolume->dwValue != 0)
|
||
|
{
|
||
|
return (FALSE);
|
||
|
}
|
||
|
}
|
||
|
return (TRUE); // Volume of all channels equals zero since we haven't returned yet.
|
||
|
}
|
||
|
else return (FALSE);
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void RefreshMixCache ()
|
||
|
{
|
||
|
if (g_fCacheCreated && ChannelsAllMinimum())
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (g_pdblCacheMix && g_hMixer && g_mcd.paDetails)
|
||
|
{
|
||
|
|
||
|
UINT uiIndx;
|
||
|
double* pdblMixPercent;
|
||
|
MIXERCONTROLDETAILS_UNSIGNED* pmcuVolume;
|
||
|
// Note: This call does a GetVolume(), so no need to call it again...
|
||
|
DWORD dwMaxVol = GetMaxVolume ();
|
||
|
|
||
|
// Caculate the percentage distance each channel is away from the max
|
||
|
// value. Creating this cache allows us to maintain the relative distance
|
||
|
// of the channel levels from each other as the user adjusts the master
|
||
|
// volume level.
|
||
|
for (uiIndx = 0; uiIndx < g_mlDst.cChannels; uiIndx++)
|
||
|
{
|
||
|
pmcuVolume = ((MIXERCONTROLDETAILS_UNSIGNED*)g_mcd.paDetails + uiIndx);
|
||
|
pdblMixPercent = (g_pdblCacheMix + uiIndx);
|
||
|
|
||
|
// Caculate the percentage this value is from the max ...
|
||
|
if (dwMaxVol == pmcuVolume -> dwValue)
|
||
|
{
|
||
|
*pdblMixPercent = 1.0F;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*pdblMixPercent = ((double) pmcuVolume -> dwValue / (double) dwMaxVol);
|
||
|
}
|
||
|
}
|
||
|
g_fCacheCreated = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void FreeBrandBmp ()
|
||
|
{
|
||
|
if (g_hbmBrand)
|
||
|
{
|
||
|
DeleteObject (g_hbmBrand);
|
||
|
g_hbmBrand = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ResetBranding (HWND hwnd)
|
||
|
{
|
||
|
FreeBrandBmp ();
|
||
|
if( g_szHotLinkURL )
|
||
|
{
|
||
|
LocalFree( g_szHotLinkURL );
|
||
|
g_szHotLinkURL = NULL;
|
||
|
}
|
||
|
|
||
|
// Initialize the Device Name Text
|
||
|
SetDlgItemText (hwnd, IDC_VOLUME_MIXER, g_szNoAudioDevice);
|
||
|
EnableWindow (GetDlgItem (hwnd, IDC_VOLUME_MIXER), FALSE);
|
||
|
|
||
|
// Show the default icon window, and hide the custom bitmap window
|
||
|
ShowWindow (GetDlgItem (hwnd, IDC_VOLUME_ICON_BRAND), SW_SHOW);
|
||
|
ShowWindow (GetDlgItem (hwnd, IDC_VOLUME_BRAND), SW_HIDE);
|
||
|
}
|
||
|
|
||
|
void SetBranding (HWND hwnd, UINT uiMixID)
|
||
|
{
|
||
|
|
||
|
HKEY hkeyBrand = NULL;
|
||
|
MIXERCAPS mc;
|
||
|
|
||
|
if (MMSYSERR_NOERROR != mixerGetDevCaps (uiMixID, &mc, sizeof (mc)))
|
||
|
return; // bail
|
||
|
|
||
|
ResetBranding (hwnd);
|
||
|
|
||
|
// Device Name Text
|
||
|
SetDlgItemText(hwnd, IDC_VOLUME_MIXER, mc.szPname);
|
||
|
|
||
|
|
||
|
// Get Device Bitmap, if any.
|
||
|
hkeyBrand = OpenDeviceBrandRegKey (uiMixID);
|
||
|
if (hkeyBrand)
|
||
|
{
|
||
|
WCHAR szBuffer[MAX_PATH];
|
||
|
DWORD dwType = REG_SZ;
|
||
|
DWORD cb = sizeof (szBuffer);
|
||
|
|
||
|
// Get Any Branding Bitmap
|
||
|
if (ERROR_SUCCESS == RegQueryValueEx (hkeyBrand, REGSTR_VAL_AUDIO_BITMAP, NULL, &dwType, (LPBYTE)szBuffer, &cb))
|
||
|
{
|
||
|
BITMAP bm;
|
||
|
WCHAR* pszComma = wcschr (szBuffer, L',');
|
||
|
if (pszComma)
|
||
|
{
|
||
|
WCHAR* pszResourceID = pszComma + 1;
|
||
|
HANDLE hResource;
|
||
|
|
||
|
// Remove comma delimeter
|
||
|
*pszComma = L'\0';
|
||
|
|
||
|
// Should be a resource module and a resource ID
|
||
|
hResource = LoadLibrary (szBuffer);
|
||
|
if (!hResource)
|
||
|
{
|
||
|
WCHAR szDriversPath[MAX_PATH+1];
|
||
|
szDriversPath[MAX_PATH] = 0;
|
||
|
|
||
|
// If we didn't find it on the normal search path, try looking
|
||
|
// in the "drivers" directory.
|
||
|
if (GetSystemDirectory (szDriversPath, MAX_PATH))
|
||
|
{
|
||
|
wcsncat (szDriversPath, TEXT("\\drivers\\"), MAX_PATH - wcslen(szDriversPath));
|
||
|
wcsncat (szDriversPath, szBuffer, MAX_PATH - wcslen(szDriversPath));
|
||
|
hResource = LoadLibrary (szDriversPath);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
if (hResource)
|
||
|
{
|
||
|
g_hbmBrand = LoadImage (hResource, MAKEINTRESOURCE(_wtoi (pszResourceID)), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE);
|
||
|
FreeLibrary (hResource);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
// Should be an *.bmp file
|
||
|
g_hbmBrand = LoadImage (NULL, szBuffer, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
|
||
|
|
||
|
// Verify that this bitmap is not larger than our defined maximum. Do NOT
|
||
|
// use GetBitmapDimensionEx() here as it not set or used by the system.
|
||
|
if (g_hbmBrand && GetObject (g_hbmBrand, sizeof (BITMAP), &bm))
|
||
|
{
|
||
|
if (bm.bmWidth > ksizeBrandMax.cx ||
|
||
|
bm.bmHeight > ksizeBrandMax.cy)
|
||
|
{
|
||
|
// Too big, we will just show the standard one below
|
||
|
FreeBrandBmp ();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get Any Branding URL
|
||
|
|
||
|
// Get the size of the URL
|
||
|
if (ERROR_SUCCESS == RegQueryValueEx (hkeyBrand, REGSTR_VAL_AUDIO_URL, NULL, &dwType, NULL, &cb))
|
||
|
{
|
||
|
// Allocate a buffer to store the URL in, ensuring it is an integer number of WCHARs
|
||
|
g_szHotLinkURL = (WCHAR *)LocalAlloc (LPTR, sizeof(WCHAR) * (cb + (sizeof(WCHAR)-1) / sizeof(WCHAR)));
|
||
|
|
||
|
// Now, get the branding URL
|
||
|
if (ERROR_SUCCESS != RegQueryValueEx (hkeyBrand, REGSTR_VAL_AUDIO_URL, NULL, &dwType, (LPBYTE)g_szHotLinkURL, &cb))
|
||
|
{
|
||
|
// If we failed, free up g_szHotLinkURL
|
||
|
LocalFree( g_szHotLinkURL );
|
||
|
g_szHotLinkURL = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Close the Branding key
|
||
|
RegCloseKey (hkeyBrand);
|
||
|
}
|
||
|
|
||
|
// Apply any bitmap we have now.
|
||
|
if (g_hbmBrand)
|
||
|
{
|
||
|
// Show the custom bitmap window, and hide the default icon window
|
||
|
ShowWindow (GetDlgItem (hwnd, IDC_VOLUME_BRAND), SW_SHOW);
|
||
|
ShowWindow (GetDlgItem (hwnd, IDC_VOLUME_ICON_BRAND), SW_HIDE);
|
||
|
|
||
|
SendMessage (GetDlgItem (hwnd, IDC_VOLUME_BRAND), STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)g_hbmBrand);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Show the default icon window, and hide the custom bitmap window
|
||
|
ShowWindow (GetDlgItem (hwnd, IDC_VOLUME_ICON_BRAND), SW_SHOW);
|
||
|
ShowWindow (GetDlgItem (hwnd, IDC_VOLUME_BRAND), SW_HIDE);
|
||
|
}
|
||
|
|
||
|
// Create HotLink text if we have a valid internet address
|
||
|
CreateHotLink (ValidateURL ());
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL ValidateURL ()
|
||
|
{
|
||
|
|
||
|
BOOL fValid = FALSE;
|
||
|
|
||
|
// Test basic validity
|
||
|
if (g_szHotLinkURL && (0 < lstrlen (g_szHotLinkURL)))
|
||
|
{
|
||
|
// Test URL validity
|
||
|
if (UrlIsW (g_szHotLinkURL, URLIS_URL))
|
||
|
{
|
||
|
|
||
|
WIN32_FIND_DATA fd;
|
||
|
HANDLE hFile;
|
||
|
|
||
|
// Make certain that the URL is not a local file!
|
||
|
hFile = FindFirstFileW (g_szHotLinkURL, &fd);
|
||
|
if (INVALID_HANDLE_VALUE == hFile)
|
||
|
fValid = TRUE;
|
||
|
else
|
||
|
FindClose (hFile);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Clear any bogus info...
|
||
|
if (!fValid && g_szHotLinkURL)
|
||
|
{
|
||
|
LocalFree (g_szHotLinkURL);
|
||
|
g_szHotLinkURL = NULL;
|
||
|
}
|
||
|
|
||
|
return fValid;
|
||
|
|
||
|
}
|
||
|
|
||
|
STDAPI_(void) Multichannel (HWND hwnd, UINT uiMixID, DWORD dwDest, DWORD dwVolID)
|
||
|
{
|
||
|
|
||
|
PROPSHEETHEADER psh;
|
||
|
PROPSHEETPAGE psp;
|
||
|
TCHAR szWindowTitle[255];
|
||
|
TCHAR szPageTitle[255];
|
||
|
UINT uiTitle;
|
||
|
|
||
|
// Save multichannel parameters for the multichannel page
|
||
|
if (SUCCEEDED (SetDevice (uiMixID, dwDest, dwVolID)))
|
||
|
{
|
||
|
// Load Page Title
|
||
|
LoadString (ghInstance, GetPageStringID (), szPageTitle, sizeof (szPageTitle)/sizeof (TCHAR));
|
||
|
|
||
|
ZeroMemory (&psp, sizeof (PROPSHEETPAGE));
|
||
|
psp.dwSize = sizeof (PROPSHEETPAGE);
|
||
|
psp.dwFlags = PSP_DEFAULT | PSP_USETITLE | PSP_USECALLBACK;
|
||
|
psp.hInstance = ghInstance;
|
||
|
psp.pszTemplate = MAKEINTRESOURCE (IDD_MULTICHANNEL);
|
||
|
psp.pszTitle = szPageTitle;
|
||
|
psp.pfnDlgProc = MultichannelDlg;
|
||
|
|
||
|
// Load Window Title (Same as page name now!)
|
||
|
LoadString (ghInstance, GetPageStringID (), szWindowTitle, sizeof (szWindowTitle)/sizeof (TCHAR));
|
||
|
|
||
|
ZeroMemory (&psh, sizeof (psh));
|
||
|
psh.dwSize = sizeof (psh);
|
||
|
psh.dwFlags = PSH_DEFAULT | PSH_PROPSHEETPAGE;
|
||
|
psh.hwndParent = hwnd;
|
||
|
psh.hInstance = ghInstance;
|
||
|
psh.pszCaption = szWindowTitle;
|
||
|
psh.nPages = 1;
|
||
|
psh.nStartPage = 0;
|
||
|
psh.ppsp = &psp;
|
||
|
|
||
|
PropertySheet (&psh);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
HKEY OpenDeviceBrandRegKey (UINT uiMixID)
|
||
|
{
|
||
|
|
||
|
HKEY hkeyBrand = NULL;
|
||
|
HKEY hkeyDevice = OpenDeviceRegKey (uiMixID, KEY_READ);
|
||
|
|
||
|
if (hkeyDevice)
|
||
|
{
|
||
|
if (ERROR_SUCCESS != RegOpenKey (hkeyDevice, REGSTR_KEY_BRANDING, &hkeyBrand))
|
||
|
hkeyBrand = NULL; // Make sure NULL on failure
|
||
|
|
||
|
// Close the Device key
|
||
|
RegCloseKey (hkeyDevice);
|
||
|
}
|
||
|
|
||
|
return hkeyBrand;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// Microsoft Confidential - DO NOT COPY THIS METHOD INTO ANY APPLICATION, THIS MEANS YOU!!!
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||
|
PTCHAR GetInterfaceName (DWORD dwMixerID)
|
||
|
{
|
||
|
MMRESULT mmr;
|
||
|
ULONG cbSize=0;
|
||
|
TCHAR *szInterfaceName=NULL;
|
||
|
|
||
|
//Query for the Device interface name
|
||
|
mmr = mixerMessage(HMIXER_INDEX(dwMixerID), DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR)&cbSize, 0L);
|
||
|
if(MMSYSERR_NOERROR == mmr)
|
||
|
{
|
||
|
szInterfaceName = (TCHAR *)GlobalAllocPtr(GHND, (cbSize+1)*sizeof(TCHAR));
|
||
|
if(!szInterfaceName)
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
mmr = mixerMessage(HMIXER_INDEX(dwMixerID), DRV_QUERYDEVICEINTERFACE, (DWORD_PTR)szInterfaceName, cbSize);
|
||
|
if(MMSYSERR_NOERROR != mmr)
|
||
|
{
|
||
|
GlobalFreePtr(szInterfaceName);
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return szInterfaceName;
|
||
|
}
|
||
|
|
||
|
|
||
|
HKEY OpenDeviceRegKey (UINT uiMixID, REGSAM sam)
|
||
|
{
|
||
|
|
||
|
HKEY hkeyDevice = NULL;
|
||
|
PTCHAR szInterfaceName = GetInterfaceName (uiMixID);
|
||
|
|
||
|
if (szInterfaceName)
|
||
|
{
|
||
|
HDEVINFO DeviceInfoSet = SetupDiCreateDeviceInfoList (NULL, NULL);
|
||
|
|
||
|
if (INVALID_HANDLE_VALUE != DeviceInfoSet)
|
||
|
{
|
||
|
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
|
||
|
DeviceInterfaceData.cbSize = sizeof (SP_DEVICE_INTERFACE_DATA);
|
||
|
|
||
|
if (SetupDiOpenDeviceInterface (DeviceInfoSet, szInterfaceName,
|
||
|
0, &DeviceInterfaceData))
|
||
|
{
|
||
|
DWORD dwRequiredSize;
|
||
|
SP_DEVINFO_DATA DeviceInfoData;
|
||
|
DeviceInfoData.cbSize = sizeof (SP_DEVINFO_DATA);
|
||
|
|
||
|
// Ignore error, it always returns "ERROR_INSUFFICIENT_BUFFER" even though
|
||
|
// the "SP_DEVICE_INTERFACE_DETAIL_DATA" parameter is supposed to be optional.
|
||
|
(void) SetupDiGetDeviceInterfaceDetail (DeviceInfoSet, &DeviceInterfaceData,
|
||
|
NULL, 0, &dwRequiredSize, &DeviceInfoData);
|
||
|
// Open device reg key
|
||
|
hkeyDevice = SetupDiOpenDevRegKey (DeviceInfoSet, &DeviceInfoData,
|
||
|
DICS_FLAG_GLOBAL, 0,
|
||
|
DIREG_DRV, sam);
|
||
|
|
||
|
}
|
||
|
SetupDiDestroyDeviceInfoList (DeviceInfoSet);
|
||
|
}
|
||
|
GlobalFreePtr (szInterfaceName);
|
||
|
}
|
||
|
|
||
|
return hkeyDevice;
|
||
|
|
||
|
}
|