windows-nt/Source/XPSP1/NT/shell/osshell/control/mmsys/volume.c

1389 lines
41 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
///////////////////////////////////////////////////////////////////////////////
//
// 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;
}