You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
927 lines
29 KiB
927 lines
29 KiB
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// File: multchan.c
|
|
//
|
|
// This file defines the functions that drive the multichannel
|
|
// volume tab of the Sounds & Multimedia control panel.
|
|
//
|
|
// History:
|
|
// 13 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 "medhelp.h"
|
|
#include "mmcpl.h"
|
|
#include "multchan.h"
|
|
#include "speakers.h"
|
|
#include "dslevel.h"
|
|
|
|
// Externals
|
|
extern BOOL DeviceChange_GetHandle(DWORD dwMixerID, HANDLE *phDevice);
|
|
extern HRESULT DSGetGuidFromName(LPTSTR szName, BOOL fRecord, LPGUID pGuid);
|
|
extern HRESULT DSGetCplValues(GUID guid, BOOL fRecord, LPCPLDATA pData);
|
|
|
|
// Globals
|
|
UINT g_uiMCMixID = 0;
|
|
HMIXER g_hMCMixer = NULL;
|
|
UINT g_uiMCPageStringID = 0;
|
|
UINT g_uiMCDescStringID = 0;
|
|
LPVOID g_paPrevious = NULL;
|
|
BOOL g_fInternalMCGenerated = FALSE;
|
|
BOOL g_fMCChanged = FALSE;
|
|
MIXERCONTROLDETAILS g_mcdMC;
|
|
MIXERLINE g_mlMCDst;
|
|
WNDPROC g_fnMCPSProc = NULL;
|
|
UINT g_uiMCDevChange = 0;
|
|
HWND g_hWndMC = NULL;
|
|
static HDEVNOTIFY g_hMCDeviceEventContext= NULL;
|
|
|
|
// Constants
|
|
#define VOLUME_TICS (500) // VOLUME_TICS * VOLUME_MAX must be less than 0xFFFFFFFF
|
|
#define VOLUME_MAX (0xFFFF)
|
|
#define VOLUME_MIN (0)
|
|
#define MC_SLIDER_COUNT (8) // Update Code & Dialog Template if change this value!
|
|
static INTCODE aKeyWordIds[] =
|
|
{
|
|
IDC_MC_DESCRIPTION, NO_HELP,
|
|
IDC_MC_ZERO_LOW, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_ZERO, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_ZERO_VOLUME, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_ZERO_HIGH, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_ONE_LOW, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_ONE, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_ONE_VOLUME, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_ONE_HIGH, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_TWO_LOW, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_TWO, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_TWO_VOLUME, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_TWO_HIGH, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_THREE_LOW, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_THREE, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_THREE_VOLUME, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_THREE_HIGH, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_FOUR_LOW, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_FOUR, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_FOUR_VOLUME, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_FOUR_HIGH, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_FIVE_LOW, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_FIVE, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_FIVE_VOLUME, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_FIVE_HIGH, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_SIX_LOW, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_SIX, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_SIX_VOLUME, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_SIX_HIGH, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_SEVEN_LOW, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_SEVEN, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_SEVEN_VOLUME, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_SEVEN_HIGH, IDH_MC_ALL_SLIDERS,
|
|
IDC_MC_MOVE_TOGETHER, IDH_MC_MOVE_TOGETHER,
|
|
IDC_MC_RESTORE, IDH_MC_RESTORE,
|
|
0,0
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// %%Function: MCTabProc
|
|
//
|
|
// 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 multichannel control panel page device change
|
|
// message.
|
|
//
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
LRESULT CALLBACK MCTabProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (iMsg == g_uiMCDevChange)
|
|
{
|
|
InitMCVolume (g_hWndMC);
|
|
}
|
|
|
|
return CallWindowProc (g_fnMCPSProc, hwnd, iMsg, wParam, lParam);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// %%Function: MultichannelDlg
|
|
//
|
|
// 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 multichannel volume control panel page.
|
|
//
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
INT_PTR CALLBACK MultichannelDlg (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_NOTIFY:
|
|
{
|
|
OnNotifyMC (hDlg, (LPNMHDR) lParam);
|
|
}
|
|
break;
|
|
|
|
case WM_INITDIALOG:
|
|
{
|
|
HANDLE_WM_INITDIALOG (hDlg, wParam, lParam, OnInitDialogMC);
|
|
}
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
{
|
|
HANDLE_WM_DESTROY (hDlg, wParam, lParam, OnDestroyMC);
|
|
}
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
{
|
|
HANDLE_WM_COMMAND (hDlg, wParam, lParam, OnCommandMC);
|
|
}
|
|
break;
|
|
|
|
case WM_HSCROLL:
|
|
{
|
|
HANDLE_WM_HSCROLL (hDlg, wParam, lParam, MCVolumeScroll);
|
|
}
|
|
break;
|
|
|
|
case WM_POWERBROADCAST:
|
|
{
|
|
HandleMCPowerBroadcast (hDlg, wParam, lParam);
|
|
}
|
|
break;
|
|
|
|
case MM_MIXM_LINE_CHANGE:
|
|
case MM_MIXM_CONTROL_CHANGE:
|
|
{
|
|
if (!g_fInternalMCGenerated)
|
|
{
|
|
DisplayMCVolumeControl (hDlg);
|
|
}
|
|
|
|
g_fInternalMCGenerated = FALSE;
|
|
}
|
|
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:
|
|
{
|
|
MCDeviceChange_Change (hDlg, wParam, lParam);
|
|
}
|
|
break;
|
|
|
|
case WM_WININICHANGE:
|
|
case WM_DISPLAYCHANGE :
|
|
{
|
|
int iLastSliderID = IDC_MC_ZERO_VOLUME + (MC_SLIDER_COUNT - 1) * 4;
|
|
int indx = IDC_MC_ZERO_VOLUME;
|
|
for (; indx <= iLastSliderID; indx += 4)
|
|
SendDlgItemMessage (hDlg, indx, uMsg, wParam, lParam);
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
void InitMCVolume (HWND hDlg)
|
|
{
|
|
FreeMCMixer ();
|
|
|
|
if (MMSYSERR_NOERROR == mixerOpen (&g_hMCMixer, g_uiMCMixID, (DWORD_PTR) hDlg, 0L, CALLBACK_WINDOW))
|
|
{
|
|
if (SUCCEEDED (GetMCVolume ()) && g_paPrevious && g_mcdMC.paDetails)
|
|
{
|
|
// Copy data so can undo volume changes
|
|
memcpy (g_paPrevious, g_mcdMC.paDetails, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
|
|
DisplayMCVolumeControl (hDlg);
|
|
}
|
|
MCDeviceChange_Init (hDlg, g_uiMCMixID);
|
|
}
|
|
}
|
|
|
|
|
|
BOOL OnInitDialogMC (HWND hDlg, HWND hwndFocus, LPARAM lParam)
|
|
{
|
|
|
|
TCHAR szDescription [255];
|
|
LoadString (ghInstance, g_uiMCDescStringID, szDescription, sizeof (szDescription)/sizeof (TCHAR));
|
|
SetWindowText (GetDlgItem (hDlg, IDC_MC_DESCRIPTION), szDescription);
|
|
|
|
// Init Globals
|
|
g_fInternalMCGenerated = FALSE;
|
|
g_fMCChanged = FALSE;
|
|
g_hWndMC = hDlg;
|
|
// Set Device Change Notification
|
|
g_fnMCPSProc = (WNDPROC) SetWindowLongPtr (GetParent (hDlg), GWLP_WNDPROC, (LONG_PTR) MCTabProc);
|
|
g_uiMCDevChange = RegisterWindowMessage (_T("winmm_devicechange"));
|
|
|
|
// Init Volume
|
|
InitMCVolume (hDlg);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void OnDestroyMC (HWND hDlg)
|
|
{
|
|
// Unregister from notifications
|
|
MCDeviceChange_Cleanup ();
|
|
SetWindowLongPtr (GetParent (hDlg), GWLP_WNDPROC, (LONG_PTR) g_fnMCPSProc);
|
|
|
|
FreeAll ();
|
|
}
|
|
|
|
|
|
void OnNotifyMC (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;
|
|
}
|
|
}
|
|
|
|
|
|
BOOL PASCAL OnCommandMC (HWND hDlg, int id, HWND hwndCtl, UINT codeNotify)
|
|
{
|
|
switch (id)
|
|
{
|
|
case IDC_MC_RESTORE:
|
|
{
|
|
// Move all sliders to center
|
|
UINT uiIndx;
|
|
for (uiIndx = 0; uiIndx < g_mlMCDst.cChannels; uiIndx++)
|
|
{
|
|
((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + uiIndx) -> dwValue = VOLUME_MAX/2;
|
|
}
|
|
g_fInternalMCGenerated = FALSE;
|
|
mixerSetControlDetails ((HMIXEROBJ) g_hMCMixer, &g_mcdMC, MIXER_SETCONTROLDETAILSF_VALUE);
|
|
|
|
if (!g_fMCChanged)
|
|
{
|
|
g_fMCChanged = TRUE;
|
|
PropSheet_Changed (GetParent (hDlg), hDlg);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ID_APPLY:
|
|
{
|
|
if (SUCCEEDED (GetMCVolume ()) && g_paPrevious && g_mcdMC.paDetails)
|
|
{
|
|
// Copy data so can undo volume changes
|
|
memcpy (g_paPrevious, g_mcdMC.paDetails, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
|
|
DisplayMCVolumeControl (hDlg);
|
|
}
|
|
|
|
g_fMCChanged = FALSE;
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case IDOK:
|
|
{
|
|
}
|
|
break;
|
|
|
|
case IDCANCEL:
|
|
{
|
|
if (g_paPrevious && g_mcdMC.paDetails)
|
|
{
|
|
// Undo volume changes
|
|
memcpy (g_mcdMC.paDetails, g_paPrevious, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
|
|
g_fInternalMCGenerated = TRUE;
|
|
mixerSetControlDetails ((HMIXEROBJ) g_hMCMixer, &g_mcdMC, MIXER_SETCONTROLDETAILSF_VALUE);
|
|
}
|
|
WinHelp (hDlg, gszWindowsHlp, HELP_QUIT, 0L);
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
void HandleMCPowerBroadcast (HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case PBT_APMQUERYSUSPEND:
|
|
{
|
|
FreeMCMixer ();
|
|
}
|
|
break;
|
|
|
|
case PBT_APMQUERYSUSPENDFAILED:
|
|
case PBT_APMRESUMESUSPEND:
|
|
{
|
|
InitMCVolume (hWnd);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
BOOL SliderIDtoChannel (UINT uiSliderID, DWORD* pdwChannel)
|
|
{
|
|
|
|
if (!pdwChannel)
|
|
return FALSE;
|
|
|
|
// Determine channel number (index)
|
|
switch (uiSliderID)
|
|
{
|
|
case IDC_MC_ZERO_VOLUME: // Left
|
|
*pdwChannel = 0;
|
|
break;
|
|
case IDC_MC_ONE_VOLUME: // Right
|
|
*pdwChannel = 1;
|
|
break;
|
|
case IDC_MC_TWO_VOLUME: // Center
|
|
*pdwChannel = 2;
|
|
break;
|
|
case IDC_MC_THREE_VOLUME: // Back Left
|
|
*pdwChannel = 3;
|
|
break;
|
|
case IDC_MC_FOUR_VOLUME: // Back Right
|
|
*pdwChannel = 4;
|
|
break;
|
|
case IDC_MC_FIVE_VOLUME: // Low Frequency
|
|
*pdwChannel = 5;
|
|
break;
|
|
case IDC_MC_SIX_VOLUME: // Left of Center
|
|
*pdwChannel = 6;
|
|
break;
|
|
case IDC_MC_SEVEN_VOLUME: // Right of Center
|
|
*pdwChannel = 7;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return ((*pdwChannel) < g_mlMCDst.cChannels);
|
|
|
|
}
|
|
|
|
|
|
// Called in response to slider movement, computes new volume level and sets it
|
|
// it also controls the apply state (changed or not)
|
|
void MCVolumeScroll (HWND hwnd, HWND hwndCtl, UINT code, int pos)
|
|
{
|
|
|
|
BOOL fSet;
|
|
BOOL fMoveTogether;
|
|
DWORD dwChannel;
|
|
DWORD dwSliderVol;
|
|
DWORD dwNewMixerVol;
|
|
DWORD dwOldMixerVol;
|
|
DWORD dwVolume;
|
|
|
|
fMoveTogether = IsDlgButtonChecked (hwnd, IDC_MC_MOVE_TOGETHER);
|
|
if (SliderIDtoChannel (GetDlgCtrlID (hwndCtl), &dwChannel))
|
|
{
|
|
// Set the new volume
|
|
dwSliderVol = (DWORD) SendMessage (hwndCtl, TBM_GETPOS, 0, 0);
|
|
dwNewMixerVol = (VOLUME_MAX * dwSliderVol + VOLUME_TICS / 2) / VOLUME_TICS;
|
|
dwOldMixerVol = (g_paPrevious ? ((MIXERCONTROLDETAILS_UNSIGNED*)g_paPrevious + dwChannel) -> dwValue : 0);
|
|
fSet = SetMCVolume (dwChannel, dwNewMixerVol, fMoveTogether);
|
|
|
|
if (!fSet)
|
|
{
|
|
// Restore the correct thumb position.
|
|
dwVolume = (VOLUME_TICS * ((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + dwChannel) -> dwValue + VOLUME_MAX / 2) / VOLUME_MAX;
|
|
SendMessage (hwndCtl, TBM_SETPOS, TRUE, dwVolume);
|
|
}
|
|
|
|
if ((fMoveTogether || dwOldMixerVol != dwNewMixerVol) && !g_fMCChanged)
|
|
{
|
|
g_fMCChanged = TRUE;
|
|
PropSheet_Changed (GetParent (hwnd), hwnd);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Sets the volume level
|
|
//
|
|
BOOL SetMCVolume (DWORD dwChannel, DWORD dwVol, BOOL fMoveTogether)
|
|
{
|
|
|
|
BOOL fReturn;
|
|
UINT uiIndx;
|
|
DWORD dwValue;
|
|
long lMoveValue;
|
|
long lMoveValueActual;
|
|
|
|
fReturn = TRUE;
|
|
if (dwChannel < g_mlMCDst.cChannels && g_mcdMC.paDetails && g_hMCMixer)
|
|
{
|
|
if (fMoveTogether)
|
|
{
|
|
// Note: Do not set g_fInternalMCGenerated = TRUE here because we are relying
|
|
// on the change notification to update the other sliders.
|
|
lMoveValue = (long)((double)dwVol - (double)(((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + dwChannel) -> dwValue));
|
|
lMoveValueActual = lMoveValue;
|
|
|
|
// Don't bother if no move requested.
|
|
if (lMoveValue == 0)
|
|
return TRUE; // Already Set
|
|
|
|
// Ensure that the new value is within the range of all the sliders that are
|
|
// being used. This will ensure that we maintain the distance between sliders
|
|
// as they are being moved.
|
|
for (uiIndx = 0; uiIndx < g_mlMCDst.cChannels; uiIndx++)
|
|
{
|
|
dwValue = ((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + uiIndx) -> dwValue;
|
|
if (VOLUME_MIN > ((long)dwValue+lMoveValueActual))
|
|
{
|
|
lMoveValueActual = VOLUME_MIN - dwValue;
|
|
}
|
|
else
|
|
{
|
|
if (VOLUME_MAX < ((long)dwValue+lMoveValueActual))
|
|
lMoveValueActual = VOLUME_MAX - dwValue;
|
|
}
|
|
}
|
|
|
|
if (lMoveValueActual != 0)
|
|
{
|
|
// Update the values
|
|
for (uiIndx = 0; uiIndx < g_mlMCDst.cChannels; uiIndx++)
|
|
{
|
|
dwValue = ((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + uiIndx) -> dwValue;
|
|
((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + uiIndx) -> dwValue = (DWORD)((long) dwValue + lMoveValueActual);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Let user know that they can move no farther in the current direction.
|
|
// Note: We use the PC Speaker instead of the mixer because this is an
|
|
// indicator that they are at either min or max volume for one of
|
|
// the sliders. Since these are channel volume sliders, if we used
|
|
// the mixer, the user would either not hear anything (min volume)
|
|
// or get blown away (max volume).
|
|
MessageBeep (-1 /*PC Speaker*/);
|
|
fReturn = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_fInternalMCGenerated = TRUE;
|
|
((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + dwChannel) -> dwValue = dwVol;
|
|
}
|
|
|
|
mixerSetControlDetails ((HMIXEROBJ) g_hMCMixer, &g_mcdMC, MIXER_SETCONTROLDETAILSF_VALUE);
|
|
}
|
|
|
|
return fReturn;
|
|
|
|
}
|
|
|
|
|
|
void ShowAndEnableWindow (HWND hwnd, BOOL fEnable)
|
|
{
|
|
EnableWindow (hwnd, fEnable);
|
|
ShowWindow (hwnd, fEnable ? SW_SHOW : SW_HIDE);
|
|
}
|
|
|
|
|
|
void DisplayMCVolumeControl (HWND hDlg)
|
|
{
|
|
|
|
HWND hwndVol = NULL;
|
|
HWND hwndLabel = NULL;
|
|
WCHAR szLabel[MAX_PATH];
|
|
BOOL fEnabled;
|
|
UINT uiIndx;
|
|
DWORD dwSpeakerType = TYPE_STEREODESKTOP;
|
|
BOOL fPlayback = (MIXERLINE_COMPONENTTYPE_DST_SPEAKERS == g_mlMCDst.dwComponentType);
|
|
ZeroMemory (szLabel, sizeof (szLabel));
|
|
|
|
// Get Speaker Configuration Type
|
|
if (fPlayback)
|
|
GetSpeakerType (&dwSpeakerType);
|
|
|
|
for (uiIndx = 0; uiIndx < MC_SLIDER_COUNT; uiIndx++)
|
|
{
|
|
|
|
fEnabled = (uiIndx < g_mlMCDst.cChannels);
|
|
|
|
// Set up the volume slider
|
|
hwndVol = GetDlgItem (hDlg, IDC_MC_ZERO_VOLUME + uiIndx * 4);
|
|
SendMessage (hwndVol, TBM_SETTICFREQ, VOLUME_TICS / 10, 0);
|
|
SendMessage (hwndVol, TBM_SETRANGE, FALSE, MAKELONG (0, VOLUME_TICS));
|
|
|
|
// Enable/Disable sliders
|
|
hwndLabel = GetDlgItem (hDlg, IDC_MC_ZERO + uiIndx * 4);
|
|
ShowAndEnableWindow (hwndVol, fEnabled);
|
|
ShowAndEnableWindow (hwndLabel, fEnabled);
|
|
ShowAndEnableWindow (GetDlgItem (hDlg, IDC_MC_ZERO_LOW + uiIndx * 4), fEnabled);
|
|
ShowAndEnableWindow (GetDlgItem (hDlg, IDC_MC_ZERO_HIGH + uiIndx * 4), fEnabled);
|
|
|
|
if (fPlayback)
|
|
{
|
|
GetSpeakerLabel (dwSpeakerType, uiIndx, szLabel, sizeof(szLabel)/sizeof(TCHAR));
|
|
}
|
|
else
|
|
{
|
|
LoadString (ghInstance, IDS_MC_CHANNEL_ZERO + uiIndx, szLabel, MAX_PATH);
|
|
}
|
|
SetWindowText (hwndLabel, szLabel);
|
|
|
|
}
|
|
|
|
if (0 < g_mlMCDst.cChannels)
|
|
{
|
|
UpdateMCVolumeSliders (hDlg);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
BOOL GetSpeakerType (DWORD* pdwSpeakerType)
|
|
{
|
|
|
|
BOOL fReturn = FALSE;
|
|
|
|
if (pdwSpeakerType)
|
|
{
|
|
MIXERCAPS mc;
|
|
|
|
*pdwSpeakerType = TYPE_STEREODESKTOP; // Init Value
|
|
if (MMSYSERR_NOERROR == mixerGetDevCaps (g_uiMCMixID, &mc, sizeof (mc)))
|
|
{
|
|
GUID guid;
|
|
if (SUCCEEDED (DSGetGuidFromName (mc.szPname, FALSE, &guid)))
|
|
{
|
|
CPLDATA cpldata;
|
|
if (SUCCEEDED (DSGetCplValues (guid, FALSE, &cpldata)))
|
|
{
|
|
*pdwSpeakerType = cpldata.dwSpeakerType;
|
|
fReturn = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fReturn;
|
|
|
|
}
|
|
|
|
BOOL GetSpeakerLabel (DWORD dwSpeakerType, UINT uiSliderIndx, WCHAR* szLabel, int nSize)
|
|
{
|
|
|
|
if (uiSliderIndx >= MC_SLIDER_COUNT || !szLabel || nSize <= 0)
|
|
// Invalid
|
|
return FALSE;
|
|
|
|
switch (dwSpeakerType)
|
|
{
|
|
//
|
|
// Mono
|
|
//
|
|
case TYPE_NOSPEAKERS:
|
|
case TYPE_MONOLAPTOP:
|
|
if (0 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_CENTER, szLabel, nSize));
|
|
break;
|
|
|
|
//
|
|
// Stereo
|
|
//
|
|
case TYPE_HEADPHONES:
|
|
case TYPE_STEREODESKTOP:
|
|
case TYPE_STEREOLAPTOP:
|
|
case TYPE_STEREOMONITOR:
|
|
case TYPE_STEREOCPU:
|
|
case TYPE_MOUNTEDSTEREO:
|
|
case TYPE_STEREOKEYBOARD:
|
|
// Left & Right Channel ...
|
|
if (0 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_LEFT, szLabel, nSize));
|
|
else if (1 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_RIGHT, szLabel, nSize));
|
|
break;
|
|
|
|
//
|
|
// Greater than Stereo
|
|
//
|
|
case TYPE_SURROUND:
|
|
// Left & Right Channel ...
|
|
if (0 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_LEFT, szLabel, nSize));
|
|
else if (1 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_RIGHT, szLabel, nSize));
|
|
// Center Front & Back
|
|
else if (2 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_CENTER, szLabel, nSize));
|
|
else if (3 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_BACKCENTER, szLabel, nSize));
|
|
break;
|
|
|
|
case TYPE_QUADRAPHONIC:
|
|
// Left & Right Channel ...
|
|
if (0 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_LEFT, szLabel, nSize));
|
|
else if (1 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_RIGHT, szLabel, nSize));
|
|
// Back Left & Back Right Channel ...
|
|
else if (2 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_BACKLEFT, szLabel, nSize));
|
|
else if (3 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_BACKRIGHT, szLabel, nSize));
|
|
break;
|
|
|
|
case TYPE_SURROUND_5_1:
|
|
case TYPE_SURROUND_7_1:
|
|
// Left & Right Channel ...
|
|
if (0 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_LEFT, szLabel, nSize));
|
|
else if (1 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_RIGHT, szLabel, nSize));
|
|
|
|
// Center Channel ...
|
|
if (2 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_CENTER, szLabel, nSize));
|
|
// Low Frequency ...
|
|
if (3 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_LOWFREQUENCY, szLabel, nSize));
|
|
|
|
// Back Left & Back Right Channel ...
|
|
if (4 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_BACKLEFT, szLabel, nSize));
|
|
else if (5 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_BACKRIGHT, szLabel, nSize));
|
|
|
|
if (TYPE_SURROUND_5_1 == dwSpeakerType)
|
|
break;
|
|
|
|
// Left of Center & Right of Center Channel ...
|
|
if (6 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_LEFT_OF_CENTER, szLabel, nSize));
|
|
else if (7 == uiSliderIndx)
|
|
return (LoadString (ghInstance, IDS_MC_SPEAKER_RIGHT_OF_CENTER, szLabel, nSize));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// If we are here, we don't know the speaker type or we have too many
|
|
// channels for a known type, just use the generic channel text ...
|
|
return (LoadString (ghInstance, IDS_MC_CHANNEL_ZERO + uiSliderIndx, szLabel, nSize));
|
|
|
|
}
|
|
|
|
|
|
// Called to update the slider when the volume is changed externally
|
|
//
|
|
void UpdateMCVolumeSliders (HWND hDlg)
|
|
{
|
|
if (g_hMCMixer && g_mcdMC.paDetails && SUCCEEDED (GetMCVolume ()))
|
|
{
|
|
UINT uiIndx;
|
|
DWORD dwVolume;
|
|
MIXERCONTROLDETAILS_UNSIGNED mcuVolume;
|
|
for (uiIndx = 0; uiIndx < g_mlMCDst.cChannels; uiIndx++)
|
|
{
|
|
mcuVolume = *((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + uiIndx);
|
|
dwVolume = (VOLUME_TICS * mcuVolume.dwValue + VOLUME_MAX / 2) / VOLUME_MAX;
|
|
SendMessage (GetDlgItem (hDlg, IDC_MC_ZERO_VOLUME + uiIndx * 4), TBM_SETPOS, TRUE, dwVolume);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FreeAll ()
|
|
{
|
|
FreeMCMixer ();
|
|
if (g_mcdMC.paDetails)
|
|
{
|
|
LocalFree (g_mcdMC.paDetails);
|
|
g_mcdMC.paDetails = NULL;
|
|
}
|
|
if (g_paPrevious)
|
|
{
|
|
LocalFree (g_paPrevious);
|
|
g_paPrevious = NULL;
|
|
}
|
|
ZeroMemory (&g_mcdMC, sizeof (g_mcdMC));
|
|
ZeroMemory (&g_mlMCDst, sizeof (g_mlMCDst));
|
|
}
|
|
|
|
void FreeMCMixer ()
|
|
{
|
|
if (g_hMCMixer)
|
|
{
|
|
mixerClose (g_hMCMixer);
|
|
g_hMCMixer = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
HRESULT SetDevice (UINT uiMixID, DWORD dwDest, DWORD dwVolID)
|
|
{
|
|
|
|
HMIXER hMixer = NULL;
|
|
HRESULT hr = E_FAIL;
|
|
|
|
// Free any current mixer stuff
|
|
FreeAll ();
|
|
|
|
if (MMSYSERR_NOERROR == mixerOpen (&hMixer, uiMixID, 0, 0, MIXER_OBJECTF_MIXER))
|
|
{
|
|
g_mlMCDst.cbStruct = sizeof (g_mlMCDst);
|
|
g_mlMCDst.dwDestination = dwDest;
|
|
|
|
if (MMSYSERR_NOERROR == mixerGetLineInfo ((HMIXEROBJ) hMixer, &g_mlMCDst, MIXER_GETLINEINFOF_DESTINATION))
|
|
{
|
|
g_mcdMC.cbStruct = sizeof (g_mcdMC);
|
|
g_mcdMC.dwControlID = dwVolID;
|
|
g_mcdMC.cChannels = g_mlMCDst.cChannels;
|
|
g_mcdMC.hwndOwner = 0;
|
|
g_mcdMC.cMultipleItems = 0;
|
|
g_mcdMC.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.
|
|
g_mcdMC.paDetails = (MIXERCONTROLDETAILS_UNSIGNED*) LocalAlloc (LPTR, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
|
|
g_paPrevious = (MIXERCONTROLDETAILS_UNSIGNED*) LocalAlloc (LPTR, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
|
|
if (g_mcdMC.paDetails && g_paPrevious)
|
|
{
|
|
hr = S_OK;
|
|
|
|
// Init our other globals
|
|
g_uiMCMixID = uiMixID;
|
|
switch (g_mlMCDst.dwComponentType)
|
|
{
|
|
case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
|
|
g_uiMCPageStringID = IDS_MC_PLAYBACK;
|
|
g_uiMCDescStringID = IDS_MC_PLAYBACK_DESC;
|
|
break;
|
|
|
|
case MIXERLINE_COMPONENTTYPE_DST_WAVEIN:
|
|
case MIXERLINE_COMPONENTTYPE_DST_VOICEIN:
|
|
g_uiMCPageStringID = IDS_MC_RECORDING;
|
|
g_uiMCDescStringID = IDS_MC_RECORDING_DESC;
|
|
break;
|
|
|
|
default:
|
|
g_uiMCPageStringID = IDS_MC_OTHER;
|
|
g_uiMCDescStringID = IDS_MC_OTHER_DESC;
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
mixerClose (hMixer);
|
|
}
|
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
|
HRESULT GetMCVolume ()
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
if (g_hMCMixer && g_mcdMC.paDetails)
|
|
{
|
|
ZeroMemory (g_mcdMC.paDetails, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
|
|
hr = mixerGetControlDetails ((HMIXEROBJ)g_hMCMixer, &g_mcdMC, MIXER_GETCONTROLDETAILSF_VALUE);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
UINT GetPageStringID ()
|
|
{
|
|
return g_uiMCPageStringID;
|
|
}
|
|
|
|
|
|
void MCDeviceChange_Cleanup ()
|
|
{
|
|
if (g_hMCDeviceEventContext)
|
|
{
|
|
UnregisterDeviceNotification (g_hMCDeviceEventContext);
|
|
g_hMCDeviceEventContext = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void MCDeviceChange_Init (HWND hWnd, DWORD dwMixerID)
|
|
{
|
|
DEV_BROADCAST_HANDLE DevBrodHandle;
|
|
HANDLE hMixerDevice=NULL;
|
|
|
|
//If we had registered already for device notifications, unregister ourselves.
|
|
MCDeviceChange_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_hMCDeviceEventContext = 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 MCDeviceChange_Change (HWND hDlg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
PDEV_BROADCAST_HANDLE bh = (PDEV_BROADCAST_HANDLE)lParam;
|
|
|
|
if(!g_hMCDeviceEventContext || !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
|
|
{
|
|
FreeMCMixer ();
|
|
}
|
|
break;
|
|
|
|
case DBT_DEVICEQUERYREMOVEFAILED: // Didn't happen, need to re-acquire mixer
|
|
{
|
|
InitMCVolume (hDlg);
|
|
}
|
|
break;
|
|
}
|
|
}
|