//--------------------------------------------------------------------------; // // File: volopt.cpp // // Copyright (c) 1998 Microsoft Corporation. All rights reserved // //--------------------------------------------------------------------------; #include "precomp.h" #include #include #include #include "optres.h" #include "cdopti.h" #include "cdoptimp.h" #include "helpids.h" #include "winbase.h" ////////////// // Help ID's ////////////// #pragma data_seg(".text") const static DWORD aVolOptsHelp[] = { IDC_VOL_MSG_TEXT, IDH_VOL_MSG, IDC_VOL_CONFIG_GROUP, IDH_VOL_MSG, IDC_DEFAULTMIXER, IDH_USEMIXERDEFAULTS, IDC_SELECTPLAYER_TEXT, IDH_SELECTCDPLAYER, IDC_CDDRIVE, IDH_SELECTCDPLAYER, IDC_SELECTMIXER_TEXT, IDH_SELECTCDMIXER, IDC_AUDIOMIXER, IDH_SELECTCDMIXER, IDC_SELECTCONTROL_TEXT, IDH_SELECTCDCONTROL, IDC_AUDIOCONTROL, IDH_SELECTCDCONTROL, 0, 0 }; #pragma data_seg() //////////// // Types //////////// typedef struct CDCTL // Used to write to reg (don't change) { DWORD dwVolID; DWORD dwMuteID; } CDCTL, *LPCDCTL; //////////// // Globals //////////// #define MYREGSTR_PATH_MEDIA TEXT("SYSTEM\\CurrentControlSet\\Control\\MediaResources") const TCHAR gszRegstrCDAPath[] = MYREGSTR_PATH_MEDIA TEXT("\\mci\\cdaudio"); const TCHAR gszDefaultCDA[] = TEXT("Default Drive"); const TCHAR szRegstrCDROMPath[] = TEXT("System\\CurrentControlSet\\Services\\Class\\"); const TCHAR szPrefMixer[] = TEXT("Preferred Mixer"); const TCHAR szPrefControls[] = TEXT("Preferred Controls"); const TCHAR szSelected[] = TEXT("Selected"); const TCHAR szMapperPath[] = TEXT("Software\\Microsoft\\Multimedia\\Sound Mapper"); const TCHAR szPlayback[] = TEXT("Playback"); const TCHAR szPreferredOnly[] = TEXT("PreferredOnly"); const TCHAR szNTCDROMPath[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Applets\\DeluxeCD\\Settings\\"); //////////// // Functions //////////// ///////////// // Uses new winmm feature to get the preferred wave ID, much cleaner. // STDMETHODIMP_(MMRESULT) CCDOpt::GetDefaultMixID(DWORD *pdwMixID) { MMRESULT mmr; DWORD dwWaveID; DWORD dwFlags = 0; mmr = waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR) &dwWaveID, (DWORD_PTR) &dwFlags); if (!mmr && pdwMixID) { *pdwMixID = dwWaveID; } return(mmr); } /////////// // A mixer line has been found with controls, this function is called to either 1) verify // that passed in VolID and MuteID's can be found on this mixer line and are of the right // control type. or 2) to find the Volume Slider and Mute ID controls that do exist on // this mixer line. // STDMETHODIMP_(void) CCDOpt::SearchControls(int mxid, LPMIXERLINE pml, LPDWORD pdwVolID, LPDWORD pdwMuteID, TCHAR *szName, BOOL *pfFound, BOOL fVerify) { 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) new(MIXERCONTROL[pml->cControls]); if (mlc.pamxctrl) { if (mixerGetLineControls((HMIXEROBJ) 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; } } if (fVerify) { if (*pdwVolID == dwVolID && *pdwMuteID == dwMuteID) { if (szName) { lstrcpy(szName, pml->szName); // mlc.pamxctrl[dwControl].szName); } *pfFound = TRUE; } } else { if (szName) { lstrcpy(szName, pml->szName); // mlc.pamxctrl[dwControl].szName); } *pfFound = TRUE; *pdwVolID = dwVolID; *pdwMuteID = dwMuteID; } } } } delete mlc.pamxctrl; } } /////////////// // If a mixer line has connects, this function is called to enumerate all lines that have controls // that meet our criteria and then seek out the controls on those connections using the above SearchControls // function. // // NOTE: This function makes two scans over the connections, first looking for CompactDisc lines, and then // if unsuccessful, it makes a second scan looking for other lines that might have a CD connected, like line-in // and aux lines. // STDMETHODIMP_(void) CCDOpt::SearchConnections(int mxid, DWORD dwDestination, DWORD dwConnections, LPDWORD pdwVolID, LPDWORD pdwMuteID, TCHAR *szName, BOOL *pfFound, BOOL fVerify) { MIXERLINE mlDst; DWORD dwConnection; DWORD dwScan; for (dwScan = 0; dwScan < 2 && !(*pfFound); dwScan++) // On first scan look for CD, on second scan, look for anything else. { for (dwConnection = 0; dwConnection < dwConnections && !(*pfFound); dwConnection++) { mlDst.cbStruct = sizeof ( mlDst ); mlDst.dwDestination = dwDestination; mlDst.dwSource = dwConnection; if (mixerGetLineInfo((HMIXEROBJ) mxid, &mlDst, MIXER_GETLINEINFOF_SOURCE) == MMSYSERR_NOERROR) { if (mlDst.cControls) // Make sure this source has controls on it { if (((dwScan == 0) && (mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC)) || ((dwScan == 1) && (mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_LINE || mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY || mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_DIGITAL || mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_ANALOG))) { SearchControls(mxid, &mlDst, pdwVolID, pdwMuteID, szName, pfFound, fVerify); } } } } } } ///////////////// // Used in two modes, fVerify is TRUE, the VolID and MuteID are inputs and will return TRUE if valid // if not in verification mode, this function is used to compute the default vol and mute ID's for this device. // It scans all the destinations on the Mixer looking for output destinations (speakers, headphones, etc) // Once it finds them, it then Searchs for controls on itself and/or any connections itself. // // NOTE: The current default behavior is to locate CD type connections, and then, if not finding any that // work, to attempt to use the destination master volume. To reverse this, look for master volume first, and then // look for CD lines if master can't be found, Switch the two intermost If conditions and calls so that // SearchControls on the line happens before the connections are searched. STDMETHODIMP_(BOOL) CCDOpt::SearchDevice(DWORD dwMixID, LPCDUNIT pCDUnit, LPDWORD pdwVolID, LPDWORD pdwMuteID, TCHAR *szName, BOOL fVerify) { MIXERCAPS mc; MMRESULT mmr; BOOL fFound = FALSE; mmr = mixerGetDevCaps(dwMixID, &mc, sizeof(mc)); if (mmr == MMSYSERR_NOERROR) { MIXERLINE mlDst; DWORD dwDestination; if (pCDUnit) { lstrcpy(pCDUnit->szMixerName, mc.szPname); } for (dwDestination = 0; dwDestination < mc.cDestinations && !fFound; dwDestination++) { mlDst.cbStruct = sizeof ( mlDst ); mlDst.dwDestination = dwDestination; if (mixerGetLineInfo((HMIXEROBJ) 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) { // Note: To default to Master Volume (instead of CD volume) just SearchControls first. if (!fFound && mlDst.cConnections) // only look at connections if there is no master slider control. { SearchConnections(dwMixID, dwDestination, mlDst.cConnections, pdwVolID, pdwMuteID, szName, &fFound, fVerify); } if (!fFound && mlDst.cControls) // If there are controls, we'll take the master { SearchControls(dwMixID, &mlDst, pdwVolID, pdwMuteID, szName, &fFound, fVerify); } } } } } return(fFound); } //////////////// // When a new CD is found, and there are no valid enteries in the registry for it, we compute // defaults for that unit. This function uses the above function SearchDevice to do just that. // First it finds the preferred MixerID and assumes this CD is connected to it, this it finds // the default controls on that mixer and assignes them. // STDMETHODIMP_(void) CCDOpt::GetUnitDefaults(LPCDUNIT pCDUnit) { if (pCDUnit) { pCDUnit->dwMixID = DWORD(-1); pCDUnit->dwVolID = DWORD(-1); pCDUnit->dwMuteID = DWORD(-1); if (GetDefaultMixID(&pCDUnit->dwMixID) == MMSYSERR_NOERROR) { SearchDevice(pCDUnit->dwMixID, pCDUnit, &pCDUnit->dwVolID, &pCDUnit->dwMuteID, pCDUnit->szVolName, FALSE); } } } ////////////// // This function enumerates installed disk devices that are of type CDROM and are installed // It seeks out the one that matches the specified drive letter and collects information about it // It returns the class driver path and the descriptive name of the drive. // // NOTE This function assumes both szDriver and szDevDesc are the dwSize bytes each. // STDMETHODIMP_(BOOL) CCDOpt::MapLetterToDevice(TCHAR DriveLetter, TCHAR *szDriver, TCHAR *szDevDesc, DWORD dwSize) { HKEY hkEnum; BOOL fResult = FALSE; if (!RegOpenKey(HKEY_DYN_DATA, REGSTR_PATH_DYNA_ENUM, &hkEnum)) { HKEY hkDev; DWORD dwEnumDevCnt; TCHAR aszCMKey[MAX_PATH]; BOOLEAN found = FALSE; for (dwEnumDevCnt = 0; !found && !RegEnumKey(hkEnum, dwEnumDevCnt, aszCMKey, sizeof(aszCMKey)/sizeof(TCHAR)); dwEnumDevCnt++) { if (!RegOpenKey(hkEnum, aszCMKey, &hkDev)) { TCHAR aszDrvKey[MAX_PATH]; TCHAR tmp; DWORD cb = sizeof(aszDrvKey); RegQueryValueEx(hkDev, REGSTR_VAL_HARDWARE_KEY, NULL, NULL, (LPBYTE)&aszDrvKey, &cb); tmp = aszDrvKey[5]; aszDrvKey[5] = TEXT('\0'); if ( !lstrcmpi( REGSTR_VAL_SCSI, aszDrvKey ) ) { HKEY hkDrv; TCHAR aszEnumKey[MAX_PATH]; aszDrvKey[5] = tmp; wsprintf(aszEnumKey, TEXT("Enum\\%s"), aszDrvKey); if (!RegOpenKey(HKEY_LOCAL_MACHINE, aszEnumKey, &hkDrv)) { TCHAR DrvLet[3]; cb = sizeof( DrvLet ); RegQueryValueEx(hkDrv, REGSTR_VAL_CURDRVLET, NULL, NULL, (LPBYTE)&DrvLet, &cb); if ( DrvLet[0] == DriveLetter ) { DWORD cb2 = dwSize; cb = dwSize; if ((RegQueryValueEx(hkDrv, REGSTR_VAL_DEVDESC, NULL, NULL, (LPBYTE)szDevDesc, &cb) == NO_ERROR) && (RegQueryValueEx(hkDrv, REGSTR_VAL_DRIVER, NULL, NULL, (LPBYTE)szDriver, &cb2) == NO_ERROR)) { fResult = TRUE; } found = TRUE; } RegCloseKey(hkDrv); } } RegCloseKey(hkDev); } } RegCloseKey(hkEnum); } if (!fResult) { //check to see if we're on NT OSVERSIONINFO os; os.dwOSVersionInfoSize = sizeof(os); GetVersionEx(&os); if (os.dwPlatformId == VER_PLATFORM_WIN32_NT) { TCHAR szDrive[MAX_PATH]; TCHAR szDriverTemp[MAX_PATH*2]; TCHAR* szGUID = NULL; ZeroMemory(szDriverTemp,sizeof(szDriverTemp)); ZeroMemory(szDriver,dwSize); wsprintf(szDrive,TEXT("%c:\\"),DriveLetter); if (GetVolumeNameForVolumeMountPoint(szDrive,szDriverTemp,dwSize/sizeof(TCHAR))) { //szDriverTemp now has a string like \\?\Volume{GUID}\ ... we just want //the {GUID} part, as that is the drive's unique ID. szGUID = _tcschr(szDriverTemp,TEXT('{')); if (szGUID!=NULL) { fResult = TRUE; _tcscpy(szDriver,szGUID); if (szDriver[_tcslen(szDriver)-1] != TEXT('}')) { szDriver[_tcslen(szDriver)-1] = TEXT('\0'); } } } } //end if NT } return(fResult); } /////////////// // Since audio devices can come and go, the names of the devices can change since there is // number appended by the system inclosed in brackets at the end of the string. // This function attempts to truncate this appended number after copying the original into // the destination buffer. // STDMETHODIMP_(BOOL) CCDOpt::TruncName(TCHAR *pDest, TCHAR *pSrc) { BOOL fSuccess = TRUE; TCHAR *pTrunc; lstrcpy(pDest, pSrc); pTrunc = pDest; while (*pTrunc++); while (pTrunc-- > pDest) { if (*pTrunc == TEXT('[')) { *pTrunc = TEXT('\0'); break; } } if (pTrunc == pDest) { fSuccess = FALSE; } return(fSuccess); } ///////////////// // After reading the preferred mixer device name from the registry, we have to locate that // device in the system by mixer ID. To do this, we scan thru all available mixers looking // for an exact match of the name. If an exact match can not be found, we then scan again // looking for a match using truncated strings where the system appended instance number is // removed. If we can't find it at that point, we are in trouble and have to give up. // STDMETHODIMP CCDOpt::ComputeMixID(LPDWORD pdwMixID, TCHAR *szMixerName) { HRESULT hr = E_FAIL; DWORD dwNumMixers; DWORD dwID; MMRESULT mmr; BOOL fFound = FALSE; TCHAR szTruncName[MAXPNAMELEN]; TCHAR szTruncSeek[MAXPNAMELEN]; MIXERCAPS mc; dwNumMixers = mixerGetNumDevs(); for (dwID = 0; dwID < dwNumMixers && !fFound; dwID++) // First look for EXACT match. { mmr = mixerGetDevCaps(dwID, &mc, sizeof(mc)); if (mmr == MMSYSERR_NOERROR) { if (!lstrcmp(mc.szPname, szMixerName)) { hr = S_OK; fFound = TRUE; *pdwMixID = dwID; } } } if (!fFound) // Exact match isn't found, strip off (#) and look again { if (TruncName(szTruncName, szMixerName)) { for (dwID = 0; dwID < dwNumMixers && !fFound; dwID++) { mmr = mixerGetDevCaps(dwID, &mc, sizeof(mc)); if (mmr == MMSYSERR_NOERROR) { TruncName(szTruncSeek, mc.szPname); if (!lstrcmp(szTruncSeek, szTruncName)) { lstrcpy(szMixerName, mc.szPname); // repair the name we matched hr = S_OK; fFound = TRUE; *pdwMixID = dwID; } } } } } return(hr); } /////////////// // This function looks up this devices registry information using the driver information that // was mapped from the drive letter. It attempts to read in the preferred mixer ID and then // calculate the mixerID from it, if that fails, the function fails and the unit will use default // information calcuated by this program. If it succeeds, it then reads in the control ID's for // the volume and mute controls for this mixer. If it can't find them or the ones it does find // can not be located on the specified device, then we compute defaults. If we can't compute defaults // the entire function fails and the entire drive is re-computed. // STDMETHODIMP CCDOpt::GetUnitRegData(LPCDUNIT pCDUnit) { HRESULT hr = E_FAIL; TCHAR szRegDriverStr[MAX_PATH]; HKEY hKey; DWORD dwMixID; CDCTL cdCtl; HKEY hKeyRoot = HKEY_LOCAL_MACHINE; if (pCDUnit) { OSVERSIONINFO os; os.dwOSVersionInfoSize = sizeof(os); GetVersionEx(&os); if (os.dwPlatformId == VER_PLATFORM_WIN32_NT) { hKeyRoot = HKEY_CURRENT_USER; wsprintf(szRegDriverStr,TEXT("%s%s"),szNTCDROMPath, pCDUnit->szDriver); } else { wsprintf(szRegDriverStr,TEXT("%s%s"),szRegstrCDROMPath, pCDUnit->szDriver); } if (RegOpenKeyEx(hKeyRoot , szRegDriverStr , 0 , KEY_READ , &hKey) == ERROR_SUCCESS) { DWORD dwSize = sizeof(BOOL); RegQueryValueEx(hKey, szSelected, NULL, NULL, (LPBYTE) &(pCDUnit->fSelected), &dwSize); dwSize = MAXPNAMELEN*sizeof(TCHAR); if (RegQueryValueEx(hKey, szPrefMixer, NULL, NULL, (LPBYTE) pCDUnit->szMixerName, &dwSize) == NO_ERROR) { hr = ComputeMixID(&dwMixID, pCDUnit->szMixerName); if (SUCCEEDED(hr)) { BOOL fGotControls = FALSE; TCHAR szVolName[MIXER_LONG_NAME_CHARS] = TEXT("\0"); dwSize = sizeof(cdCtl); if (RegQueryValueEx(hKey, szPrefControls, NULL, NULL, (LPBYTE) &cdCtl, &dwSize) == NO_ERROR) { fGotControls = SearchDevice(dwMixID, NULL, &cdCtl.dwVolID, &cdCtl.dwMuteID, szVolName, TRUE); // Verify Controls } if (!fGotControls) // Either were not in reg or fail verification, compute defaults for this device { fGotControls = SearchDevice(dwMixID, NULL, &cdCtl.dwVolID, &cdCtl.dwMuteID, szVolName, FALSE); } if (!fGotControls) // If we don't have them by now, we are in trouble. { hr = E_FAIL; } else { pCDUnit->dwMixID = dwMixID; pCDUnit->dwVolID = cdCtl.dwVolID; pCDUnit->dwMuteID = cdCtl.dwMuteID; lstrcpy(pCDUnit->szVolName, szVolName); } } } RegCloseKey(hKey); } } return(hr); } //////////////// // This function writes out the preferred mixer device name and the preferred control ID's into the // cdrom driver registry for safe keeping. // STDMETHODIMP_(void) CCDOpt::SetUnitRegData(LPCDUNIT pCDUnit) { HRESULT hr = E_FAIL; TCHAR szRegDriverStr[MAX_PATH]; HKEY hKey; CDCTL cdCtl; //this function is very different on NT OSVERSIONINFO os; os.dwOSVersionInfoSize = sizeof(os); GetVersionEx(&os); if (os.dwPlatformId == VER_PLATFORM_WIN32_NT) { if (pCDUnit) { wsprintf(szRegDriverStr,TEXT("%s%s"),szNTCDROMPath, pCDUnit->szDriver); if (RegCreateKeyEx(HKEY_CURRENT_USER , szRegDriverStr , 0 , NULL, 0, KEY_WRITE , NULL, &hKey, NULL) == ERROR_SUCCESS) { cdCtl.dwVolID = pCDUnit->dwVolID; cdCtl.dwMuteID = pCDUnit->dwMuteID; RegSetValueEx(hKey, szPrefMixer, NULL, REG_SZ, (LPBYTE) pCDUnit->szMixerName, (sizeof(TCHAR) * lstrlen(pCDUnit->szMixerName))+sizeof(TCHAR)); RegSetValueEx(hKey, szPrefControls, NULL, REG_BINARY, (LPBYTE) &cdCtl, sizeof(cdCtl)); RegSetValueEx(hKey, szSelected, NULL, REG_BINARY, (LPBYTE) &(pCDUnit->fSelected), sizeof(BOOL)); RegCloseKey(hKey); } } } //end if NT else { if (pCDUnit) { wsprintf(szRegDriverStr,TEXT("%s%s"),szRegstrCDROMPath, pCDUnit->szDriver); if (RegOpenKeyEx(HKEY_LOCAL_MACHINE , szRegDriverStr , 0 , KEY_WRITE , &hKey) == ERROR_SUCCESS) { cdCtl.dwVolID = pCDUnit->dwVolID; cdCtl.dwMuteID = pCDUnit->dwMuteID; RegSetValueEx(hKey, szPrefMixer, NULL, REG_SZ, (LPBYTE) pCDUnit->szMixerName, (sizeof(TCHAR) * lstrlen(pCDUnit->szMixerName))+sizeof(TCHAR)); RegSetValueEx(hKey, szPrefControls, NULL, REG_BINARY, (LPBYTE) &cdCtl, sizeof(cdCtl)); RegSetValueEx(hKey, szSelected, NULL, REG_BINARY, (LPBYTE) &(pCDUnit->fSelected), sizeof(BOOL)); RegCloseKey(hKey); } } } //end else Win9x } ////////////////// // Given just the drive letter of a CDROM, this function will obtain all information needed by the // program on this drive. Some is calcuated via the PnP registry for this drive's driver, other information // is obtained from the registry or defaults are computed based on the installed audio components // STDMETHODIMP_(void) CCDOpt::GetUnitValues(LPCDUNIT pCDUnit) { if (pCDUnit) { TCHAR cDriveLetter = pCDUnit->szDriveName[0]; if (MapLetterToDevice(cDriveLetter, pCDUnit->szDriver, pCDUnit->szDeviceDesc, sizeof(pCDUnit->szDriver))) { if (FAILED(GetUnitRegData(pCDUnit))) // Can fail if reg is empty or mixer ID can't be computed { GetUnitDefaults(pCDUnit); } } else { GetUnitDefaults(pCDUnit); } } } ////////////////// // This method is called in response to a PnP notify or a WinMM Device Change message. It will verify // the existance of all assigned MixerID's and the corresponding Volume and Mute ID on that device. // In the event the device no-longer exists a default will be computed. // // STDMETHODIMP_(void) CCDOpt::MMDeviceChanged(void) { LPCDUNIT pCDUnit = m_pCDOpts->pCDUnitList; while (pCDUnit) { GetUnitValues(pCDUnit); pCDUnit = pCDUnit->pNext; } } ////////////////// // This function will save all the information for all the CD drives in the system out to the registry // it does not modify or delete any of this information. // STDMETHODIMP_(void) CCDOpt::WriteCDList(LPCDUNIT pCDList) { if (pCDList) { LPCDUNIT pUnit = pCDList; while (pUnit) { SetUnitRegData(pUnit); pUnit = pUnit->pNext; } } } //////////////// // This function will destory the list containing all the drive information in the system // freeing up all memory, this function does not save any information. // STDMETHODIMP_(void) CCDOpt::DestroyCDList(LPCDUNIT *ppCDList) { if (ppCDList && *ppCDList) { while(*ppCDList) { LPCDUNIT pTemp = *ppCDList; *ppCDList = (*ppCDList)->pNext; delete pTemp; } } } STDMETHODIMP_(UINT) CCDOpt::GetDefDrive(void) { HKEY hkTmp; UINT uDrive = 0; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, gszRegstrCDAPath, 0, KEY_READ, &hkTmp ) == ERROR_SUCCESS) { DWORD cb = sizeof(UINT); RegQueryValueEx(hkTmp, gszDefaultCDA, NULL, NULL, (LPBYTE)&uDrive, &cb); RegCloseKey(hkTmp); } return uDrive; } ///////////// // This function builds a list of all the CD drives in the system, the list // is a linked list with each node containing information that is specific // to that CD player as well as the user options for each player. // STDMETHODIMP CCDOpt::CreateCDList(LPCDUNIT *ppCDList) { DWORD dwBytes = 0; TCHAR *szDriveNames = NULL; HRESULT hr = S_OK; UINT uDefDrive = GetDefDrive(); if (ppCDList == NULL) { hr = E_INVALIDARG; } else { LPCDUNIT *ppUnit = NULL; *ppCDList = NULL; ppUnit = ppCDList; dwBytes = GetLogicalDriveStrings(0, NULL); if (dwBytes) { szDriveNames = new(TCHAR[dwBytes]); if (szDriveNames == NULL) { hr = E_OUTOFMEMORY; } else { dwBytes = GetLogicalDriveStrings(dwBytes, szDriveNames); if (dwBytes) { UINT uDrive = 0; while (*szDriveNames) { if (GetDriveType(szDriveNames) == DRIVE_CDROM) { *ppUnit = new(CDUNIT); if (*ppUnit == NULL) { hr = E_OUTOFMEMORY; break; } else { memset(*ppUnit,0,sizeof(CDUNIT)); _tcsncpy((*ppUnit)->szDriveName,szDriveNames,min(sizeof((*ppUnit)->szDriveName)/sizeof(TCHAR),(UINT)lstrlen(szDriveNames))); (*ppUnit)->dwTitleID = (DWORD)CDTITLE_NODISC; CharUpper((*ppUnit)->szDriveName); GetUnitValues(*ppUnit); if (uDrive == uDefDrive) { (*ppUnit)->fDefaultDrive = TRUE; } ppUnit = &((*ppUnit)->pNext); uDrive++; } } while(*szDriveNames++); } } } } } if (FAILED(hr)) { DestroyCDList(ppCDList); } return hr; } ///////////// // Traverse the UI Tree, destroying it as it goes. // STDMETHODIMP_(void) CCDOpt::DestroyUITree(LPCDDRIVE *ppCDRoot) { if (ppCDRoot) { LPCDDRIVE pDriveList; pDriveList = *ppCDRoot; *ppCDRoot = NULL; while (pDriveList) { LPCDDRIVE pNextDrive = pDriveList->pNext; while (pDriveList->pMixerList) { LPCDMIXER pNextMixer = pDriveList->pMixerList->pNext; while(pDriveList->pMixerList->pControlList) { LPCDCONTROL pNextControl = pDriveList->pMixerList->pControlList->pNext; delete pDriveList->pMixerList->pControlList; pDriveList->pMixerList->pControlList = pNextControl; } delete pDriveList->pMixerList; pDriveList->pMixerList = pNextMixer; } delete pDriveList; pDriveList = pNextDrive; } } } /////////////////// // Add line controls to internal tree data structure for UI // STDMETHODIMP CCDOpt::AddLineControls(LPCDMIXER pMixer, LPCDCONTROL *ppLastControl, int mxid, LPMIXERLINE pml) { MIXERLINECONTROLS mlc; DWORD dwControl; HRESULT hr = S_OK; memset(&mlc, 0, sizeof(mlc)); mlc.cbStruct = sizeof(mlc); mlc.dwLineID = pml->dwLineID; mlc.cControls = pml->cControls; mlc.cbmxctrl = sizeof(MIXERCONTROL); mlc.pamxctrl = (LPMIXERCONTROL) new(MIXERCONTROL[pml->cControls]); if (mlc.pamxctrl) { if (mixerGetLineControls((HMIXEROBJ) mxid, &mlc, MIXER_GETLINECONTROLSF_ALL) == MMSYSERR_NOERROR) { for (dwControl = 0; dwControl < pml->cControls; dwControl++) { if (mlc.pamxctrl[dwControl].dwControlType == (DWORD)MIXERCONTROL_CONTROLTYPE_VOLUME) { DWORD dwIndex; DWORD dwVolID = mlc.pamxctrl[dwControl].dwControlID; DWORD dwMuteID = DWORD(-1); LPCDCONTROL pControl; for (dwIndex = 0; dwIndex < pml->cControls; dwIndex++) { if (mlc.pamxctrl[dwIndex].dwControlType == (DWORD)MIXERCONTROL_CONTROLTYPE_MUTE) { dwMuteID = mlc.pamxctrl[dwIndex].dwControlID; break; } } pControl = new (CDCONTROL); if (pControl == NULL) { hr = E_OUTOFMEMORY; } else { memset(pControl, 0, sizeof(CDCONTROL)); if (pMixer->pControlList == NULL) { pMixer->pControlList = pControl; } if (*ppLastControl) { (*ppLastControl)->pNext = pControl; } lstrcpy(pControl->szName, pml->szName); // mlc.pamxctrl[dwControl].szName); pControl->dwVolID = dwVolID; pControl->dwMuteID = dwMuteID; *ppLastControl = pControl; } break; } } } delete mlc.pamxctrl; } return(hr); } ///////////////// // Searchs connections for controls to add to UI tree // STDMETHODIMP CCDOpt::AddConnections(LPCDMIXER pMixer, LPCDCONTROL *ppLastControl, int mxid, DWORD dwDestination, DWORD dwConnections) { MIXERLINE mlDst; DWORD dwConnection; HRESULT hr = S_OK; for (dwConnection = 0; dwConnection < dwConnections; dwConnection++) { mlDst.cbStruct = sizeof ( mlDst ); mlDst.dwDestination = dwDestination; mlDst.dwSource = dwConnection; if (mixerGetLineInfo((HMIXEROBJ) mxid, &mlDst, MIXER_GETLINEINFOF_SOURCE) == MMSYSERR_NOERROR) { if (mlDst.cControls) // Make sure this source has controls on it { if (mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC || mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_LINE || mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY || mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_DIGITAL || mlDst.dwComponentType == (DWORD)MIXERLINE_COMPONENTTYPE_SRC_ANALOG) { hr = AddLineControls(pMixer, ppLastControl, mxid, &mlDst); if (FAILED(hr)) { break; } } } } } return(hr); } ////////////// // Adds control nodes to the UI tree for the specified device // STDMETHODIMP CCDOpt::AddControls(LPCDMIXER pMixer) { MIXERLINE mlDst; DWORD dwDestination; MIXERCAPS mc; LPCDCONTROL pLastControl = NULL; HRESULT hr = S_OK; if (mixerGetDevCaps(pMixer->dwMixID, &mc, sizeof(mc)) == MMSYSERR_NOERROR) { for (dwDestination = 0; dwDestination < mc.cDestinations; dwDestination++) { mlDst.cbStruct = sizeof ( mlDst ); mlDst.dwDestination = dwDestination; if (mixerGetLineInfo((HMIXEROBJ) pMixer->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 (mlDst.cControls) // If there are no controls, we won't present it to the user as an option { hr = AddLineControls(pMixer, &pLastControl, pMixer->dwMixID, &mlDst); if (FAILED(hr)) { break; } } if (mlDst.cConnections) // If there are connections to this line, lets add thier controls { AddConnections(pMixer, &pLastControl, pMixer->dwMixID, dwDestination, mlDst.cConnections); if (FAILED(hr)) { break; } } } } } } return(hr); } /////////////////// // Adds audio mixers to UI tree for the cd player unit specified // STDMETHODIMP CCDOpt::AddMixers(LPCDDRIVE pDevice) { HRESULT hr = S_OK; DWORD dwNumMixers; DWORD dwID; MMRESULT mmr; MIXERCAPS mc; LPCDMIXER pMixer; LPCDMIXER pLastMixer = NULL; if (pDevice) { dwNumMixers = mixerGetNumDevs(); for (dwID = 0; dwID < dwNumMixers; dwID++) { mmr = mixerGetDevCaps(dwID, &mc, sizeof(mc)); if (mmr == MMSYSERR_NOERROR && mc.cDestinations) { pMixer = new (CDMIXER); if (pMixer == NULL) { hr = E_OUTOFMEMORY; break; } else { memset(pMixer, 0, sizeof(CDMIXER)); if (pDevice->pMixerList == NULL) { pDevice->pMixerList = pMixer; } if (pLastMixer) { pLastMixer->pNext = pMixer; } lstrcpy(pMixer->szPname, mc.szPname); pMixer->dwMixID = dwID; hr = AddControls(pMixer); if (FAILED(hr)) { break; } pLastMixer = pMixer; } } } } return(hr); } ////////////// // Takes the UI tree and updates it's current and default setting links based on data // from the CDINFO tree which was created from the registry // STDMETHODIMP_(void) CCDOpt::SetUIDefaults(LPCDDRIVE pCDTree, LPCDUNIT pCDList) { LPCDDRIVE pDriveList; LPCDUNIT pCDUnit = pCDList; pDriveList = pCDTree; while(pDriveList && pCDUnit) { LPCDMIXER pMixer; pMixer = pDriveList->pMixerList; while (pMixer) { LPCDCONTROL pControl; DWORD dwVolID = DWORD(-1); DWORD dwMuteID = DWORD(-1); if (pMixer->dwMixID == pCDUnit->dwMixID) { pDriveList->pCurrentMixer = pMixer; pDriveList->pOriginalMixer = pMixer; } pControl = pMixer->pControlList; SearchDevice(pMixer->dwMixID, NULL, &dwVolID, &dwMuteID, NULL, FALSE); while (pControl) { if (pControl->dwVolID == dwVolID && pControl->dwMuteID == dwMuteID) { pMixer->pDefaultControl = pControl; } if (pMixer->dwMixID == pCDUnit->dwMixID && pControl->dwVolID == pCDUnit->dwVolID) { pMixer->pCurrentControl = pControl; pMixer->pOriginalControl = pControl; } pControl = pControl->pNext; } pMixer = pMixer->pNext; } pDriveList = pDriveList->pNext; pCDUnit = pCDUnit->pNext; } } ////////////// // Takes the UI tree and updates it's current and default setting links based on data // from the CDINFO tree which was created from the registry // STDMETHODIMP_(void) CCDOpt::RestoreOriginals(void) { LPCDDRIVE pDriveList; pDriveList = m_pCDTree; while(pDriveList) { LPCDMIXER pMixer; pMixer = pDriveList->pMixerList; while (pMixer) { LPCDCONTROL pControl; pDriveList->pCurrentMixer = pDriveList->pOriginalMixer; pControl = pMixer->pControlList; while (pControl) { pMixer->pCurrentControl = pMixer->pOriginalControl; pControl = pControl->pNext; } pMixer = pMixer->pNext; } pDriveList = pDriveList->pNext; } } ////////////// // Updates the CD Tree from the UI Tree after the dialog closes (on OK), if there // are any changes it returns true. The caller is expected to write these changes out to reg // STDMETHODIMP_(BOOL) CCDOpt::UpdateCDList(LPCDDRIVE pCDTree, LPCDUNIT pCDList) { BOOL fChanged = FALSE; LPCDDRIVE pDriveList; LPCDUNIT pCDUnit = pCDList; pDriveList = pCDTree; while(pDriveList && pCDUnit) { if (pCDUnit->fSelected != pDriveList->fSelected) // Selected drive has changed { pCDUnit->fSelected = pDriveList->fSelected; fChanged = TRUE; } if (pDriveList->pCurrentMixer && pDriveList->pCurrentMixer->pCurrentControl) { LPCDMIXER pMixer = pDriveList->pCurrentMixer; if (pMixer->dwMixID != pCDUnit->dwMixID) // Mixer has changed { pCDUnit->dwMixID = pMixer->dwMixID; lstrcpy(pCDUnit->szMixerName, pMixer->szPname); fChanged = TRUE; } LPCDCONTROL pControl = pDriveList->pCurrentMixer->pCurrentControl; if (pControl->dwVolID != pCDUnit->dwVolID) // Control has changed { pCDUnit->dwVolID = pControl->dwVolID; pCDUnit->dwMuteID = pControl->dwMuteID; lstrcpy(pCDUnit->szVolName, pControl->szName); fChanged = TRUE; } } pDriveList = pDriveList->pNext; pCDUnit = pCDUnit->pNext; } return(fChanged); } //////////// // Contructs UI tree for all devices/mixers/controls // STDMETHODIMP CCDOpt::BuildUITree(LPCDDRIVE *ppCDRoot, LPCDUNIT pCDList) { LPCDUNIT pCDUnit = pCDList; LPCDDRIVE pDevice; LPCDDRIVE pLastDevice; HRESULT hr = S_OK; m_pCDSelected = NULL; if (ppCDRoot) { *ppCDRoot = NULL; pLastDevice = NULL; while (pCDUnit) { pDevice = new (CDDRIVE); if (pDevice == NULL) { hr = E_OUTOFMEMORY; break; } else { memset(pDevice, 0, sizeof(CDDRIVE)); if (*ppCDRoot == NULL) { *ppCDRoot = pDevice; } if (pLastDevice) { pLastDevice->pNext = pDevice; } lstrcpy(pDevice->szDriveName, pCDUnit->szDriveName); lstrcpy(pDevice->szDeviceDesc, pCDUnit->szDeviceDesc); pDevice->fSelected = pCDUnit->fSelected; hr = AddMixers(pDevice); if (FAILED(hr)) { break; } pCDUnit = pCDUnit->pNext; pLastDevice = pDevice; } } if (FAILED(hr)) { if (*ppCDRoot) { DestroyUITree(ppCDRoot); } } else { SetUIDefaults(*ppCDRoot, pCDList); } } return(hr); } ///////////////// // Updates the control combo box using the mixer node of the UI tree // STDMETHODIMP_(void) CCDOpt::InitControlUI(HWND hDlg, LPCDMIXER pMixer) { LPCDCONTROL pControl; LRESULT dwIndex; SendDlgItemMessage(hDlg, IDC_AUDIOCONTROL, CB_RESETCONTENT,0,0); pControl = pMixer->pControlList; if (pMixer->pCurrentControl == NULL) { pMixer->pCurrentControl = pMixer->pDefaultControl; } while(pControl) { dwIndex = SendDlgItemMessage(hDlg, IDC_AUDIOCONTROL, CB_INSERTSTRING, (WPARAM) -1, (LPARAM) pControl->szName); if (dwIndex != CB_ERR && dwIndex != CB_ERRSPACE) { SendDlgItemMessage(hDlg, IDC_AUDIOCONTROL, CB_SETITEMDATA, (WPARAM) dwIndex, (LPARAM) pControl); if (pMixer->pCurrentControl == pControl) { SendDlgItemMessage(hDlg, IDC_AUDIOCONTROL, CB_SETCURSEL, (WPARAM) dwIndex, 0); } } pControl = pControl->pNext; } } //////////////// // Sets up the mixer combobox using the specified device // STDMETHODIMP_(void) CCDOpt::InitMixerUI(HWND hDlg, LPCDDRIVE pDevice) { LPCDMIXER pMixer; LRESULT dwIndex; SendDlgItemMessage(hDlg, IDC_AUDIOMIXER, CB_RESETCONTENT,0,0); pMixer = pDevice->pMixerList; while (pMixer) { dwIndex = SendDlgItemMessage(hDlg, IDC_AUDIOMIXER, CB_INSERTSTRING, (WPARAM) -1, (LPARAM) pMixer->szPname); if (dwIndex != CB_ERR && dwIndex != CB_ERRSPACE) { SendDlgItemMessage(hDlg, IDC_AUDIOMIXER, CB_SETITEMDATA, (WPARAM) dwIndex, (LPARAM) pMixer); if (pDevice->pCurrentMixer == pMixer) { SendDlgItemMessage(hDlg, IDC_AUDIOMIXER, CB_SETCURSEL, (WPARAM) dwIndex, 0); InitControlUI(hDlg, pMixer); } } pMixer = pMixer->pNext; } } //////////////// // Sets up the cd device combo box // STDMETHODIMP_(void) CCDOpt::InitDeviceUI(HWND hDlg, LPCDDRIVE pCDTree, LPCDDRIVE pCurrentDevice) { LPCDDRIVE pDevice; LRESULT dwIndex; SendDlgItemMessage(hDlg, IDC_CDDRIVE, CB_RESETCONTENT,0,0); pDevice = pCDTree; while (pDevice) { TCHAR str[MAX_PATH]; wsprintf(str, TEXT("( %s ) %s"), pDevice->szDriveName, pDevice->szDeviceDesc); dwIndex = SendDlgItemMessage(hDlg, IDC_CDDRIVE, CB_INSERTSTRING, (WPARAM) -1, (LPARAM) str); if (dwIndex != CB_ERR && dwIndex != CB_ERRSPACE) { SendDlgItemMessage(hDlg, IDC_CDDRIVE, CB_SETITEMDATA, (WPARAM) dwIndex, (LPARAM) pDevice); if (pDevice == pCurrentDevice) { SendDlgItemMessage(hDlg, IDC_CDDRIVE, CB_SETCURSEL, (WPARAM) dwIndex, 0); InitMixerUI(hDlg, pDevice); } } pDevice = pDevice->pNext; } } /////////////////// // Called to init the dialog when it first appears. Given a list of CD devices, this // function fills out the dialog using the first device in the list. // STDMETHODIMP_(BOOL) CCDOpt::InitMixerConfig(HWND hDlg) { LPCDDRIVE pDevice = m_pCDTree; while (pDevice) { if (pDevice->fSelected) { break; } pDevice = pDevice->pNext; } if (pDevice == NULL) { pDevice = m_pCDTree; } InitDeviceUI(hDlg, m_pCDTree, pDevice); // Init to the selected device return(TRUE); } ////////////// // This function pulls the CD Drive node out of the combo box and returns it. It returns a // reference into the main Drive tree. // STDMETHODIMP_(LPCDDRIVE) CCDOpt::GetCurrentDevice(HWND hDlg) { LRESULT dwResult; LPCDDRIVE pDevice = NULL; dwResult = SendDlgItemMessage(hDlg, IDC_CDDRIVE, CB_GETCURSEL, 0, 0); if (dwResult != CB_ERR) { dwResult = SendDlgItemMessage(hDlg, IDC_CDDRIVE, CB_GETITEMDATA, (WPARAM) dwResult, 0); if (dwResult != CB_ERR) { pDevice = (LPCDDRIVE) dwResult; } } return(pDevice); } ////////////// // This function pulls the CD Drive mixer out of the combo box and returns it. It returns a // reference into the main Drive tree. // STDMETHODIMP_(LPCDMIXER) CCDOpt::GetCurrentMixer(HWND hDlg) { LRESULT dwResult; LPCDMIXER pMixer = NULL; dwResult = SendDlgItemMessage(hDlg, IDC_AUDIOMIXER, CB_GETCURSEL, 0, 0); if (dwResult != CB_ERR) { dwResult = SendDlgItemMessage(hDlg, IDC_AUDIOMIXER, CB_GETITEMDATA, (WPARAM) dwResult, 0); if (dwResult != CB_ERR) { pMixer = (LPCDMIXER) dwResult; } } return(pMixer); } ////////////// // Called when the user changes the CD Device. it pulls the CD Drive node out of the combo box and // then resets the UI to reflect that drives current settings. // STDMETHODIMP_(void) CCDOpt::ChangeCDDrives(HWND hDlg) { LPCDDRIVE pDevice = GetCurrentDevice(hDlg); if (pDevice) { LPCDDRIVE pDev = m_pCDTree; InitDeviceUI(hDlg, m_pCDTree, pDevice); while (pDev) { pDev->fSelected = (BOOL) (pDev == pDevice); pDev = pDev->pNext; } } } ////////// // Called when the user changes the current mixer for the drive, it updates the // display and the info for the drive. // STDMETHODIMP_(void) CCDOpt::ChangeCDMixer(HWND hDlg) { LRESULT dwResult; LPCDDRIVE pDevice = GetCurrentDevice(hDlg); dwResult = SendDlgItemMessage(hDlg, IDC_AUDIOMIXER, CB_GETCURSEL, 0, 0); if (dwResult != CB_ERR) { dwResult = SendDlgItemMessage(hDlg, IDC_AUDIOMIXER, CB_GETITEMDATA, (WPARAM) dwResult, 0); if (dwResult != CB_ERR) { pDevice->pCurrentMixer = (LPCDMIXER) dwResult; InitMixerUI(hDlg, pDevice); } } } ///////////////// // Called when the user changes the current control for the mixer on the current device. // It updates the internal data structure for that drive. // STDMETHODIMP_(void) CCDOpt::ChangeCDControl(HWND hDlg) { LPCDMIXER pMixer = GetCurrentMixer(hDlg); LRESULT dwResult; dwResult = SendDlgItemMessage(hDlg, IDC_AUDIOCONTROL, CB_GETCURSEL, 0, 0); if (dwResult != CB_ERR) { dwResult = SendDlgItemMessage(hDlg, IDC_AUDIOCONTROL, CB_GETITEMDATA, (WPARAM) dwResult, 0); if (dwResult != CB_ERR) { pMixer->pCurrentControl = (LPCDCONTROL) dwResult; InitControlUI(hDlg, pMixer); } } } ////////////////// // Called to set the default control for the current mixer (lets the system pick) // STDMETHODIMP_(void) CCDOpt::SetMixerDefaults(HWND hDlg) { LPCDMIXER pMixer = GetCurrentMixer(hDlg); if (pMixer) { pMixer->pCurrentControl = NULL; InitControlUI(hDlg, pMixer); } } /////////////////// // Dialog handler for the mixer configuration dialog // STDMETHODIMP_(BOOL) CCDOpt::MixerConfig(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) { BOOL fResult = TRUE; switch (msg) { default: fResult = FALSE; break; case WM_CONTEXTMENU: { WinHelp((HWND)wParam, gszHelpFile, HELP_CONTEXTMENU, (ULONG_PTR)(LPSTR)aVolOptsHelp); } break; case WM_HELP: { WinHelp((HWND) ((LPHELPINFO)lParam)->hItemHandle, gszHelpFile, HELP_WM_HELP, (ULONG_PTR)(LPSTR)aVolOptsHelp); } break; case WM_INITDIALOG: { fResult = InitMixerConfig(hDlg); } break; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDOK: EndDialog(hDlg, TRUE); break; case IDCANCEL: RestoreOriginals(); EndDialog(hDlg,FALSE); break; case IDC_DEFAULTMIXER: SetMixerDefaults(hDlg); break; case IDC_CDDRIVE: if (HIWORD(wParam) == CBN_SELCHANGE) { ChangeCDDrives(hDlg); } break; case IDC_AUDIOMIXER: if (HIWORD(wParam) == CBN_SELCHANGE) { ChangeCDMixer(hDlg); } break; case IDC_AUDIOCONTROL: if (HIWORD(wParam) == CBN_SELCHANGE) { ChangeCDControl(hDlg); } break; default: fResult = FALSE; break; } } break; } return fResult; } /////////////////// // Dialog handler // BOOL CALLBACK CCDOpt::MixerConfigProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) { BOOL fResult = TRUE; CCDOpt *pCDOpt = (CCDOpt *) GetWindowLongPtr(hDlg, DWLP_USER); if (msg == WM_INITDIALOG) { pCDOpt = (CCDOpt *) lParam; SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR) pCDOpt); } if (pCDOpt) { fResult = pCDOpt->MixerConfig(hDlg, msg, wParam, lParam); } if (msg == WM_DESTROY) { pCDOpt = NULL; } return(fResult); } //////////// // Called to put up the UI to allow the user to change the CD Volume Configuration // STDMETHODIMP_(BOOL) CCDOpt::VolumeDialog(HWND hDlg) { BOOL fChanged = FALSE; if (m_pCDOpts && m_pCDOpts->pCDUnitList) { if (SUCCEEDED(BuildUITree(&m_pCDTree, m_pCDOpts->pCDUnitList))) { if(DialogBoxParam( m_hInst, MAKEINTRESOURCE(IDD_MIXERPICKER), hDlg, (DLGPROC) CCDOpt::MixerConfigProc, (LPARAM) this) == TRUE) { fChanged = UpdateCDList(m_pCDTree, m_pCDOpts->pCDUnitList); if (fChanged) { WriteCDList(m_pCDOpts->pCDUnitList); } } DestroyUITree(&m_pCDTree); } } return(fChanged); }