/***************************************************************************** * * Component: sndvol32.exe * File: volume.c * Purpose: main application module * * Copyright (C) Microsoft Corporation 1985-1995. All rights reserved. * *****************************************************************************/ #include #include #include #include #include #include #include "vu.h" #include "dlg.h" #include "volids.h" #include "volumei.h" #include "utils.h" void Volume_SetControl(PMIXUIDIALOG pmxud, HWND hctl, int iLine, int iCtl); void Volume_GetControl(PMIXUIDIALOG pmxud, HWND hctl, int iLine, int iCtl); DWORD Volume_DialogBox(PMIXUIDIALOG pmxud); void Volume_Cleanup(PMIXUIDIALOG pmxud); void Volume_InitLine(PMIXUIDIALOG pmxud, DWORD iLine); /* stubs for NT */ typedef void (FAR PASCAL *PENWINREGISTERPROC)(UINT, BOOL); /* string declarations */ const TCHAR gszParentClass[] = TEXT( "SNDVOL32" ); const CHAR gszRegisterPenApp[] = "RegisterPenApp"; const TCHAR gszAppClassName[] = TEXT( "Volume Control" ); const TCHAR gszTrayClassName[] = TEXT( "Tray Volume" ); /* app global * */ TCHAR gszHelpFileName[MAX_PATH]; BOOL gfIsRTL; BOOL fCanDismissWindow = FALSE; /* * Number of uniquely supported devices. * * */ int Volume_NumDevs() { int cNumDevs = 0; #pragma message("----Nonmixer issue here.") // cNumDevs = Nonmixer_GetNumDevs(); cNumDevs += Mixer_GetNumDevs(); return cNumDevs; } /* * Volume_EndDialog * * */ void Volume_EndDialog( PMIXUIDIALOG pmxud, DWORD dwErr, MMRESULT mmr) { pmxud->dwReturn = dwErr; if (dwErr == MIXUI_MMSYSERR) pmxud->mmr = mmr; if (IsWindow(pmxud->hwnd)) PostMessage(pmxud->hwnd, WM_CLOSE, 0, 0); } /* * Volume_OnMenuCommand * * */ BOOL Volume_OnMenuCommand( HWND hwnd, int id, HWND hctl, UINT unotify) { PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); switch(id) { case IDM_PROPERTIES: if (Properties(pmxud, hwnd)) { Volume_GetSetStyle(&pmxud->dwStyle, SET); Volume_EndDialog(pmxud, MIXUI_RESTART, 0); } break; case IDM_HELPTOPICS: PostMessage(pmxud->hParent, MYWM_HELPTOPICS, 0, 0L); break; case IDM_HELPABOUT: { TCHAR ach[256]; GetWindowText(hwnd, ach, SIZEOF(ach)); ShellAbout(hwnd , ach , NULL , (HICON)SendMessage(hwnd, WM_QUERYDRAGICON, 0, 0L)); break; } case IDM_ADVANCED: { HMENU hmenu; pmxud->dwStyle ^= MXUD_STYLEF_ADVANCED; hmenu = GetMenu(hwnd); CheckMenuItem(hmenu, IDM_ADVANCED, MF_BYCOMMAND | ((pmxud->dwStyle & MXUD_STYLEF_ADVANCED)?MF_CHECKED:MF_UNCHECKED)); Volume_GetSetStyle(&pmxud->dwStyle, SET); Volume_EndDialog(pmxud, MIXUI_RESTART, 0); break; } case IDM_SMALLMODESWITCH: if (!(pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER)) { pmxud->dwStyle ^= MXUD_STYLEF_SMALL; if (pmxud->dwStyle & MXUD_STYLEF_SMALL) { pmxud->dwStyle &= ~MXUD_STYLEF_STATUS; } else pmxud->dwStyle |= MXUD_STYLEF_STATUS; Volume_GetSetStyle(&pmxud->dwStyle, SET); Volume_EndDialog(pmxud, MIXUI_RESTART, 0); } break; case IDM_EXIT: Volume_EndDialog(pmxud, MIXUI_EXIT, 0); return TRUE; } return FALSE; } /* * Volume_OnCommand * * - Process WM_COMMAND * * Note: We need a 2 way mapping. Dialog control -> Mixer control * and Mixer control -> Dialog control. * * */ void Volume_OnCommand( HWND hdlg, int id, HWND hctl, UINT unotify) { int iMixerLine; PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hdlg); // // Filter menu messages // if (Volume_OnMenuCommand(hdlg, id, hctl, unotify)) return; // Each control is offset from the original template control by IDOFFSET. // e.g. // IDC_VOLUME, IDC_VOLUME+IDOFFSET, .. IDC_VOLUME+(IDOFFSET*cMixerLines) // iMixerLine = id/IDOFFSET - 1; switch ((id % IDOFFSET) + IDC_MIXERCONTROLS) { case IDC_SWITCH: Volume_SetControl(pmxud, hctl, iMixerLine, MIXUI_SWITCH); break; case IDC_ADVANCED: if (MXUD_ADVANCED(pmxud) && !(pmxud->dwStyle & MXUD_STYLEF_SMALL)) Volume_SetControl(pmxud, hctl, iMixerLine, MIXUI_ADVANCED); break; } } /* * Volume_GetLineItem * * - Helper function. * */ HWND Volume_GetLineItem( HWND hdlg, DWORD iLine, DWORD idCtrl) { HWND hwnd; DWORD id; id = (iLine * IDOFFSET) + idCtrl; hwnd = GetDlgItem(hdlg, id); return hwnd; } /* - - - - - - - - - */ /* * Volume_TimeProc * * This is the callback for the periodic timer that does updates for * controls that need to be polled. We only allocate one per app to keep * the number of callbacks down. */ void CALLBACK Volume_TimeProc( UINT idEvent, UINT uReserved, DWORD dwUser, DWORD dwReserved1, DWORD dwReserved2) { PMIXUIDIALOG pmxud = (PMIXUIDIALOG)dwUser; if (!(pmxud->dwFlags & MXUD_FLAGSF_USETIMER)) return; if (pmxud->cTimeInQueue < 5) { pmxud->cTimeInQueue++; PostMessage(pmxud->hwnd, MYWM_TIMER, 0, 0L); } } #define PROPATOM TEXT("dingprivprop") const TCHAR gszDingPropAtom[] = PROPATOM; #define SETPROP(x,y) SetProp((x), gszDingPropAtom, (HANDLE)(y)) #define GETPROP(x) (PMIXUIDIALOG)GetProp((x), gszDingPropAtom) #define REMOVEPROP(x) RemoveProp(x,gszDingPropAtom) LRESULT CALLBACK Volume_TrayVolProc( HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) { PMIXUIDIALOG pmxud = (PMIXUIDIALOG)GETPROP(hwnd); static const TCHAR cszDefSnd[] = TEXT(".Default"); if (umsg == WM_KILLFOCUS) { // // if we've just been made inactive via keyboard, clear the signal // pmxud->dwTrayInfo &= ~MXUD_TRAYINFOF_SIGNAL; } if (umsg == WM_KEYUP && (pmxud->dwTrayInfo & MXUD_TRAYINFOF_SIGNAL)) { if (wParam == VK_UP || wParam == VK_DOWN || wParam == VK_END || wParam == VK_HOME || wParam == VK_LEFT || wParam == VK_RIGHT || wParam == VK_PRIOR || wParam == VK_NEXT || wParam == VK_SPACE) { PlaySound(cszDefSnd, NULL, SND_ASYNC | SND_ALIAS); pmxud->dwTrayInfo &= ~MXUD_TRAYINFOF_SIGNAL; } } if (umsg == WM_LBUTTONUP && (pmxud->dwTrayInfo & MXUD_TRAYINFOF_SIGNAL)) { PlaySound(cszDefSnd, NULL, SND_ASYNC | SND_ALIAS); pmxud->dwTrayInfo &= ~MXUD_TRAYINFOF_SIGNAL; } return CallWindowProc(pmxud->lpfnTrayVol, hwnd, umsg, wParam, lParam); } /* * * */ BOOL Volume_Init( PMIXUIDIALOG pmxud) { DWORD iLine, ictrl; RECT rc, rcWnd; if (!Mixer_Init(pmxud) && !Nonmixer_Init(pmxud)) Volume_EndDialog(pmxud, MIXUI_EXIT, 0); // // For all line controls, make sure we initialize the values. // for (iLine = 0; iLine < pmxud->cmxul; iLine++) { // // init the ui control // Volume_InitLine(pmxud, iLine); for (ictrl = MIXUI_FIRST; ictrl <= MIXUI_LAST; ictrl++) { PMIXUICTRL pmxc = &pmxud->amxul[iLine].acr[ictrl]; // // set initial settings // if (pmxc->state == MIXUI_CONTROL_INITIALIZED) Volume_GetControl(pmxud, pmxc->hwnd, iLine, ictrl); } } if (!(pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER)) { RECT rcBase; HWND hBase; RECT rcAdv,rcBorder; HWND hAdv,hBorder; DWORD i; LONG lPrev; POINT pos; HMENU hmenu; if (GetWindowRect(pmxud->hwnd, &rcWnd)) { if (pmxud->cmxul == 1) { rcWnd.right -= 20; ShowWindow(GetDlgItem(pmxud->hwnd, IDC_BORDER), SW_HIDE); } if (!Volume_GetSetRegistryRect(pmxud->szMixer , pmxud->szDestination , &rc , GET)) { rc.left = rcWnd.left; rc.top = rcWnd.top; } // // Adjusted bottom to match switch bottom // if (!(pmxud->dwStyle & MXUD_STYLEF_SMALL)) { hBase = GetDlgItem(pmxud->hwnd, IDC_SWITCH); if (hBase && GetWindowRect(hBase, &rcBase)) { rcWnd.bottom = rcBase.bottom; } // // Adjusted bottom to match "Advanced" bottom // if (MXUD_ADVANCED(pmxud)) { hAdv = GetDlgItem(pmxud->hwnd, IDC_ADVANCED); if (hAdv && GetWindowRect(hAdv, &rcAdv)) { lPrev = rcWnd.bottom; rcWnd.bottom = rcAdv.bottom; // // Adjust height of all border lines // lPrev = rcWnd.bottom - lPrev; for (i = 0; i < pmxud->cmxul; i++) { hBorder = GetDlgItem(pmxud->hwnd, IDC_BORDER+(IDOFFSET*i)); if (hBorder && GetWindowRect(hBorder, &rcBorder)) { pos.x = rcBorder.left; pos.y = rcBorder.top; ScreenToClient(pmxud->hwnd, &pos); MoveWindow(hBorder , pos.x , pos.y , rcBorder.right - rcBorder.left , (rcBorder.bottom - rcBorder.top) + lPrev , TRUE ); } } } } // // Allocate some more space. // rcWnd.bottom += 28; } MoveWindow(pmxud->hwnd, rc.left, rc.top, rcWnd.right - rcWnd.left, rcWnd.bottom - rcWnd.top, FALSE ); // // Tack on the status bar after resizing the dialog // if (pmxud->dwStyle & MXUD_STYLEF_STATUS) { pos.x = rcWnd.left; pos.y = rcWnd.bottom; ScreenToClient(pmxud->hwnd, &pos); pmxud->hStatus = CreateWindowEx ( gfIsRTL ? WS_EX_LEFTSCROLLBAR | WS_EX_RIGHT | WS_EX_RTLREADING : 0 , STATUSCLASSNAME , TEXT ("X") , WS_VISIBLE | WS_CHILD , 0 , pos.y , rcWnd.right - rcWnd.left , 14 , pmxud->hwnd , NULL , pmxud->hInstance , NULL); if (pmxud->hStatus) { SendMessage(pmxud->hStatus, WM_SETTEXT, 0, (LPARAM)(LPVOID)(LPTSTR)pmxud->szMixer); } else pmxud->dwStyle ^= MXUD_STYLEF_STATUS; } hmenu = GetMenu(pmxud->hwnd); CheckMenuItem(hmenu, IDM_ADVANCED, MF_BYCOMMAND | ((pmxud->dwStyle & MXUD_STYLEF_ADVANCED)?MF_CHECKED:MF_UNCHECKED)); if (pmxud->dwStyle & MXUD_STYLEF_SMALL || pmxud->dwFlags & MXUD_FLAGSF_NOADVANCED) EnableMenuItem(hmenu, IDM_ADVANCED, MF_BYCOMMAND | MF_GRAYED); } if (pmxud->dwFlags & MXUD_FLAGSF_USETIMER) { pmxud->cTimeInQueue = 0; pmxud->uTimerID = timeSetEvent(100 , 50 , Volume_TimeProc , (DWORD)pmxud , TIME_PERIODIC); if (!pmxud->uTimerID) pmxud->dwFlags &= ~MXUD_FLAGSF_USETIMER; } } else { WNDPROC lpfnOldTrayVol; HWND hVol; hVol = pmxud->amxul[0].acr[MIXUI_VOLUME].hwnd; lpfnOldTrayVol = SubclassWindow(hVol, Volume_TrayVolProc); if (lpfnOldTrayVol) { pmxud->lpfnTrayVol = lpfnOldTrayVol; SETPROP(hVol, pmxud); } } return TRUE; } /* * Volume_OnInitDialog * * - Process WM_INITDIALOG * * */ BOOL Volume_OnInitDialog( HWND hwnd, HWND hwndFocus, LPARAM lParam) { PMIXUIDIALOG pmxud; // // set app instance data // SETMIXUIDIALOG(hwnd, lParam); pmxud = (PMIXUIDIALOG)(LPVOID)lParam; pmxud->hwnd = hwnd; if (!Volume_Init(pmxud)) { Volume_EndDialog(pmxud, MIXUI_EXIT, 0); } else { if (pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER) PostMessage(hwnd, MYWM_WAKEUP, 0, 0); } // // If we are the tray master, don't ask to set focus // return (!(pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER)); } /* * Volume_OnDestroy * * Shut down this dialog. DO NOT TOUCH the hmixer! * * */ void Volume_OnDestroy( HWND hwnd) { PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); if (!pmxud) return; if (pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER) { HWND hVol; hVol = pmxud->amxul[0].acr[MIXUI_VOLUME].hwnd; SubclassWindow(hVol, pmxud->lpfnTrayVol); REMOVEPROP(hVol); } Volume_Cleanup(pmxud); if (!(pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER)) { // // save window position // if (!IsIconic(hwnd)) { RECT rc; GetWindowRect(hwnd, &rc); Volume_GetSetRegistryRect(pmxud->szMixer , pmxud->szDestination , &rc , SET); } } if (pmxud->dwReturn == MIXUI_RESTART) { PostMessage(pmxud->hParent, MYWM_RESTART, 0, (LPARAM)pmxud); } else PostMessage(pmxud->hParent, WM_CLOSE, 0, 0L); } /* * Volume_SetControl * * Update system controls from visual controls * * */ void Volume_SetControl( PMIXUIDIALOG pmxud, HWND hctl, int imxul, int itype) { if (pmxud->dwFlags & MXUD_FLAGSF_MIXER) Mixer_SetControl(pmxud, hctl, imxul, itype); else Nonmixer_SetControl(pmxud, hctl, imxul, itype); } /* * Volume_GetControl * * Update visual controls from system controls * */ void Volume_GetControl( PMIXUIDIALOG pmxud, HWND hctl, int imxul, int itype) { if (pmxud->dwFlags & MXUD_FLAGSF_MIXER) Mixer_GetControl(pmxud, hctl, imxul, itype); else Nonmixer_GetControl(pmxud, hctl, imxul, itype); } /* * Volume_OnXScroll * * Process Scroll bar messages * * */ void Volume_OnXScroll( HWND hwnd, HWND hwndCtl, UINT code, int pos) { PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); UINT id; int ictl; int iline; id = GetDlgCtrlID(hwndCtl); iline = id/IDOFFSET - 1; ictl = ((id % IDOFFSET) + IDC_MIXERCONTROLS == IDC_BALANCE) ? MIXUI_BALANCE : MIXUI_VOLUME; Volume_SetControl(pmxud, hwndCtl, iline, ictl); // // Make sure a note gets played // if (pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER) pmxud->dwTrayInfo |= MXUD_TRAYINFOF_SIGNAL; } /* * Volume_OnMyTimer * * Frequent update timer for meters * */ void Volume_OnMyTimer( HWND hwnd) { PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); if (!pmxud) return; if (pmxud->cTimeInQueue > 0) pmxud->cTimeInQueue--; if (!(pmxud->dwFlags & MXUD_FLAGSF_USETIMER)) return; if (pmxud->dwFlags & MXUD_FLAGSF_MIXER) Mixer_PollingUpdate(pmxud); else Nonmixer_PollingUpdate(pmxud); } /* * Volume_OnTimer * * Infrequent update timer for tray shutdown * */ void Volume_OnTimer( HWND hwnd, UINT id) { PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); KillTimer(hwnd, VOLUME_TRAYSHUTDOWN_ID); Volume_EndDialog(pmxud, MIXUI_EXIT, 0); } /* * Volume_OnMixmControlChange * * Handle control changes * * */ void Volume_OnMixmControlChange( HWND hwnd, HMIXER hmx, DWORD dwControlID) { PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); Mixer_GetControlFromID(pmxud, dwControlID); } /* * Volume_EnableLine * * Enable/Disable a line * * */ void Volume_EnableLine( PMIXUIDIALOG pmxud, DWORD iLine, BOOL fEnable) { DWORD iCtrl; PMIXUICTRL pmxc; for (iCtrl = MIXUI_FIRST; iCtrl <= MIXUI_LAST; iCtrl++ ) { pmxc = &pmxud->amxul[iLine].acr[iCtrl]; if (pmxc->state == MIXUI_CONTROL_INITIALIZED) EnableWindow(pmxc->hwnd, fEnable); } pmxud->amxul[iLine].dwStyle ^= MXUL_STYLEF_DISABLED; } /* * Volume_InitLine * * Initialize the UI controls for the dialog * * */ void Volume_InitLine( PMIXUIDIALOG pmxud, DWORD iLine) { HWND ctrl; PMIXUICTRL pmxc; // // Peakmeter control // pmxc = &pmxud->amxul[iLine].acr[MIXUI_VUMETER]; ctrl = Volume_GetLineItem(pmxud->hwnd, iLine, IDC_VUMETER); pmxc->hwnd = ctrl; pmxc->noset = 0; if (! (pmxc->state == MIXUI_CONTROL_ENABLED) ) { if (ctrl) ShowWindow(ctrl, SW_HIDE); } else if (ctrl) { HWND hvol; SendMessage(ctrl, VU_SETRANGEMAX, 0, 255); SendMessage(ctrl, VU_SETRANGEMIN, 0, 0); hvol = Volume_GetLineItem(pmxud->hwnd, iLine, IDC_VOLUME); if (hvol) { RECT rc; POINT pos; //bugbug: must base this on an invisible frame we can destroy GetWindowRect(hvol, &rc); pos.x = rc.left; pos.y = rc.top; ScreenToClient(pmxud->hwnd,&pos); MoveWindow(hvol , pos.x - 15 , pos.y , rc.right - rc.left , rc.bottom - rc.top , FALSE); } // // Signal use of update timer // pmxud->dwFlags |= MXUD_FLAGSF_USETIMER; pmxc->state = MIXUI_CONTROL_INITIALIZED; } else pmxc->state = MIXUI_CONTROL_UNINITIALIZED; // // Balance control // pmxc = &pmxud->amxul[iLine].acr[MIXUI_BALANCE]; ctrl = Volume_GetLineItem(pmxud->hwnd, iLine, IDC_BALANCE); pmxc->noset = 0; pmxc->hwnd = ctrl; if (ctrl) { SendMessage(ctrl, TBM_SETRANGE, 0, MAKELONG(0, 64)); SendMessage(ctrl, TBM_SETTICFREQ, 32, 0 ); SendMessage(ctrl, TBM_SETPOS, TRUE, 32); if (pmxc->state != MIXUI_CONTROL_ENABLED) { EnableWindow(ctrl, FALSE); } else pmxc->state = MIXUI_CONTROL_INITIALIZED; } else pmxc->state = MIXUI_CONTROL_UNINITIALIZED; // // Volume control // pmxc = &pmxud->amxul[iLine].acr[MIXUI_VOLUME]; ctrl = Volume_GetLineItem(pmxud->hwnd, iLine, IDC_VOLUME); pmxc->noset = 0; pmxc->hwnd = ctrl; if (ctrl) { SendMessage(ctrl, TBM_SETRANGE, 0, MAKELONG(0, 255)); SendMessage(ctrl, TBM_SETTICFREQ, 43, 0 ); if (pmxc->state != MIXUI_CONTROL_ENABLED) { SendMessage(ctrl, TBM_SETPOS, TRUE, 128); EnableWindow(ctrl, FALSE); } else pmxc->state = MIXUI_CONTROL_INITIALIZED; } else pmxc->state = MIXUI_CONTROL_UNINITIALIZED; // // Switch // pmxc = &pmxud->amxul[iLine].acr[MIXUI_SWITCH]; ctrl = Volume_GetLineItem(pmxud->hwnd, iLine, IDC_SWITCH); pmxc->hwnd = ctrl; pmxc->noset = 0; if (ctrl) { if (pmxc->state != MIXUI_CONTROL_ENABLED) EnableWindow(ctrl, FALSE); else pmxc->state = MIXUI_CONTROL_INITIALIZED; } else pmxc->state = MIXUI_CONTROL_UNINITIALIZED; } /* * Volume_OnMixmLineChange * * */ void Volume_OnMixmLineChange( HWND hwnd, HMIXER hmx, DWORD dwLineID) { PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); DWORD iLine; for (iLine = 0; iLine < pmxud->cmxul; iLine++) { if ( dwLineID == pmxud->amxul[iLine].pvcd->dwLineID ) { MIXERLINE ml; MMRESULT mmr; BOOL fEnable; ml.cbStruct = sizeof(ml); ml.dwLineID = dwLineID; mmr = mixerGetLineInfo((HMIXEROBJ)hmx, &ml, MIXER_GETLINEINFOF_LINEID); if (mmr != MMSYSERR_NOERROR) { fEnable = !(ml.fdwLine & MIXERLINE_LINEF_DISCONNECTED); Volume_EnableLine(pmxud, iLine, fEnable); } } } } /* * Volume_OnActivate * * Important for tray volume only. Dismisses the dialog and starts an * expiration timer. * * */ void Volume_OnActivate( HWND hwnd, UINT state, HWND hwndActDeact, BOOL fMinimized) { PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); if (!(pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER)) return; if (state != WA_INACTIVE) { fCanDismissWindow = TRUE; } else if (fCanDismissWindow) { DWORD dwTimeout = 5 * 60 * 1000; fCanDismissWindow = FALSE; ShowWindow(hwnd, SW_HIDE); // // Set expiration timer. If no one adjusts the volume, make the // application go away after 5 minutes. // dwTimeout = Volume_GetTrayTimeout(dwTimeout); SetTimer(hwnd, VOLUME_TRAYSHUTDOWN_ID, dwTimeout, NULL); } } /* * Volume_PropogateMessage * * WM_SYSCOLORCHANGE needs to be send to all child windows (esp. trackbars) */ void Volume_PropagateMessage( HWND hwnd, UINT uMessage, WPARAM wParam, LPARAM lParam) { HWND hwndChild; for (hwndChild = GetWindow(hwnd, GW_CHILD); hwndChild != NULL; hwndChild = GetWindow(hwndChild, GW_HWNDNEXT)) { SendMessage(hwndChild, uMessage, wParam, lParam); } } /* * Volume_OnPaint * * Handle custom painting * */ void Volume_OnPaint(HWND hwnd) { PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); RECT rc; POINT pos; PAINTSTRUCT ps; HDC hdc; hdc = BeginPaint(hwnd, &ps); // // for all styles other than the tray master, draw an etched // line to delinate the menu area // if (!(pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER)) { GetClientRect(hwnd, &rc); rc.bottom = 0; DrawEdge(hdc, &rc, EDGE_ETCHED, BF_TOP); EndPaint(hwnd, &ps); return; } // // for the tray master, draw some significant icon to indicate // volume // GetWindowRect(GetDlgItem(hwnd, IDC_VOLUMECUE), &rc); pos.x = rc.left; pos.y = rc.top; ScreenToClient(hwnd, &pos); rc.left = pos.x; rc.top = pos.y; pos.x = rc.right; pos.y = rc.bottom; ScreenToClient(hwnd, &pos); rc.right = pos.x; rc.bottom = pos.y; DrawEdge(hdc, &rc, BDR_RAISEDINNER, BF_DIAGONAL|BF_TOP|BF_LEFT); DrawEdge(hdc, &rc, BDR_RAISEDINNER, BF_TOP); rc.bottom -= 8; DrawEdge(hdc, &rc, BDR_RAISEDINNER, BF_RIGHT); EndPaint(hwnd, &ps); } /* * Volume_OnClose * * */ void Volume_OnClose( HWND hwnd) { DestroyWindow(hwnd); } /* * Volume_OnEndSession * * */ void Volume_OnEndSession( HWND hwnd, BOOL fEnding) { if (!fEnding) return; // // Be sure to call the close code to free open handles // Volume_OnClose(hwnd); } #define V_DC_STATEF_PENDING 0x00000001 #define V_DC_STATEF_REMOVING 0x00000002 #define V_DC_STATEF_ARRIVING 0x00000004 /* * Volume_OnDeviceChange * * */ void Volume_OnDeviceChange( HWND hwnd, WPARAM wParam, LPARAM lParam) { PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); MMRESULT mmr; UINT uMxID; switch (wParam) { case DBT_DEVNODES_CHANGED: // // We cannot reliably determine the final state of the devices in // the system until this message is broadcast. // if (pmxud->dwDeviceState & V_DC_STATEF_PENDING) { pmxud->dwDeviceState ^= V_DC_STATEF_PENDING; break; } return; case DBT_DEVICEREMOVECOMPLETE: // // This is our devnode and it's being removed. // if (((struct _DEV_BROADCAST_HEADER *)lParam)->dbcd_devicetype == DBT_DEVTYP_DEVNODE) { if (((struct _DEV_BROADCAST_DEVNODE *)lParam)->dbcd_devnode == pmxud->dwDevNode) { pmxud->dwDeviceState = V_DC_STATEF_PENDING | V_DC_STATEF_REMOVING; } } return; case DBT_DEVICEARRIVAL: // // A devnode is being added to the system // if (((struct _DEV_BROADCAST_HEADER *)lParam)->dbcd_devicetype == DBT_DEVTYP_DEVNODE) { pmxud->dwDeviceState = V_DC_STATEF_PENDING | V_DC_STATEF_ARRIVING; } return; default: return; } mmr = Volume_GetDefaultMixerID(&uMxID); if (pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER) { if ( mmr == MMSYSERR_NOERROR && (pmxud->dwDeviceState & V_DC_STATEF_ARRIVING)) { DWORD dwDevNode; if (!mixerMessage((HMIXER)uMxID, DRV_QUERYDEVNODE , (DWORD)&dwDevNode, 0L)) { if (dwDevNode == pmxud->dwDevNode) { // // ignore this device, it doesn't affect us // pmxud->dwDeviceState = 0L; return; } } } // // Our device state has changed. Just go away. // Volume_EndDialog(pmxud, MIXUI_EXIT, 0); } else if (pmxud->dwDeviceState & V_DC_STATEF_REMOVING) { // // Our device is gone. Restart with the default mixer if we can. // We don't care about arrivals. // pmxud->mxid = (mmr == MMSYSERR_NOERROR)?uMxID:0; pmxud->iDest = 0; Volume_EndDialog(pmxud, MIXUI_RESTART, 0); } pmxud->dwDeviceState = 0L; } void Volume_OnWakeup( HWND hwnd, WPARAM wParam) { POINT pos; RECT rc; LONG w,h; LONG scrw, scrh; HWND hTrack; PMIXUIDIALOG pmxud = GETMIXUIDIALOG(hwnd); if (!(pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER)) return; KillTimer(hwnd, VOLUME_TRAYSHUTDOWN_ID); if (wParam != 0) { Volume_EndDialog(pmxud, MIXUI_EXIT, 0); return; } // // Make the tray volume come up. // GetCursorPos(&pos); GetWindowRect(hwnd, &rc); w = rc.right - rc.left; h = rc.bottom - rc.top; scrw = GetSystemMetrics(SM_CXSCREEN); scrh = GetSystemMetrics(SM_CYSCREEN); // menu like behavior. // show up on the left of the cursor if you're too far to the // right. if (pos.x + w > scrw) pos.x -= w; if (pos.y + h > scrh) pos.y = scrh - h; SetWindowPos(hwnd , HWND_TOPMOST , pos.x , pos.y , w , h , SWP_SHOWWINDOW); // make us come to the front SetForegroundWindow(hwnd); fCanDismissWindow = TRUE; hTrack = GetDlgItem(hwnd, IDC_VOLUME); if (hTrack) SetFocus(hTrack); } /* * VolumeProc * * */ BOOL CALLBACK VolumeProc( HWND hdlg, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { case WM_INITDIALOG: return HANDLE_WM_INITDIALOG(hdlg, wparam, lparam, Volume_OnInitDialog); case WM_COMMAND: HANDLE_WM_COMMAND(hdlg, wparam, lparam, Volume_OnCommand); break; case WM_CLOSE: HANDLE_WM_CLOSE(hdlg, wparam, lparam, Volume_OnClose); break; case WM_DESTROY: HANDLE_WM_DESTROY(hdlg, wparam, lparam, Volume_OnDestroy); break; case WM_HSCROLL: case WM_VSCROLL: // // balance and volume are essentially the same // HANDLE_WM_XSCROLL(hdlg, wparam, lparam, Volume_OnXScroll); break; case MM_MIXM_LINE_CHANGE: HANDLE_MM_MIXM_LINE_CHANGE(hdlg , wparam , lparam , Volume_OnMixmLineChange); return FALSE; case MM_MIXM_CONTROL_CHANGE: HANDLE_MM_MIXM_CONTROL_CHANGE(hdlg , wparam , lparam , Volume_OnMixmControlChange); return FALSE; case WM_ACTIVATE: HANDLE_WM_ACTIVATE(hdlg, wparam, lparam, Volume_OnActivate); break; case MYWM_TIMER: HANDLE_MYWM_TIMER(hdlg, wparam, lparam, Volume_OnMyTimer); break; case WM_TIMER: HANDLE_WM_TIMER(hdlg, wparam, lparam, Volume_OnTimer); break; case WM_PAINT: HANDLE_WM_PAINT(hdlg, wparam, lparam, Volume_OnPaint); break; case WM_SYSCOLORCHANGE: Volume_PropagateMessage(hdlg, msg, wparam, lparam); break; case WM_DEVICECHANGE: HANDLE_WM_IDEVICECHANGE(hdlg, wparam, lparam, Volume_OnDeviceChange); break; case MYWM_WAKEUP: HANDLE_MYWM_WAKEUP(hdlg, wparam, lparam, Volume_OnWakeup); break; case WM_ENDSESSION: HANDLE_WM_ENDSESSION(hdlg, wparam, lparam, Volume_OnEndSession); break; default: break; } return FALSE; } /* * Volume_AddLine * * */ BOOL Volume_AddLine( PMIXUIDIALOG pmxud, LPBYTE lpAdd, DWORD cbAdd, DWORD dwStyle, PVOLCTRLDESC pvcd) { LPBYTE pbNew; DWORD cbNew; PMIXUILINE pmxul; if (pmxud->amxul) { pmxul = (PMIXUILINE)GlobalReAllocPtr(pmxud->amxul , (pmxud->cmxul+1)*sizeof(MIXUILINE) , GHND); } else { pmxul = (PMIXUILINE)GlobalAllocPtr(GHND, sizeof(MIXUILINE)); } if (!pmxul) return FALSE; pbNew = Dlg_HorizAttach(pmxud->lpDialog , pmxud->cbDialog , lpAdd , cbAdd , (WORD)(IDOFFSET * pmxud->cmxul) , &cbNew ); if (!pbNew) { if (!pmxud->amxul) GlobalFreePtr(pmxul); return FALSE; } pmxul[pmxud->cmxul].dwStyle = dwStyle; pmxul[pmxud->cmxul].pvcd = pvcd; pmxud->amxul = pmxul; pmxud->lpDialog = pbNew; pmxud->cbDialog = cbNew; pmxud->cmxul ++; return TRUE; } /* * Volume_Cleanup * * */ void Volume_Cleanup( PMIXUIDIALOG pmxud) { if (pmxud->dwFlags & MXUD_FLAGSF_USETIMER) { timeKillEvent(pmxud->uTimerID); pmxud->dwFlags ^= MXUD_FLAGSF_USETIMER; } if (pmxud->dwFlags & MXUD_FLAGSF_BADDRIVER) { pmxud->dwFlags ^= MXUD_FLAGSF_BADDRIVER; } if (pmxud->dwFlags & MXUD_FLAGSF_NOADVANCED) { pmxud->dwFlags ^= MXUD_FLAGSF_NOADVANCED; } if (pmxud->dwFlags & MXUD_FLAGSF_MIXER) Mixer_Shutdown(pmxud); else Nonmixer_Shutdown(pmxud); if (pmxud->lpDialog) GlobalFreePtr(pmxud->lpDialog); if (pmxud->amxul) GlobalFreePtr(pmxud->amxul); if (pmxud->avcd) GlobalFreePtr(pmxud->avcd); pmxud->amxul = NULL; pmxud->lpDialog = NULL; pmxud->cbDialog = 0; pmxud->cmxul = 0; pmxud->hwnd = NULL; pmxud->hStatus = NULL; pmxud->uTimerID = 0; pmxud->dwDevNode = 0L; } /* * Volume_CreateVolume * */ BOOL Volume_CreateVolume( PMIXUIDIALOG pmxud) { WNDCLASS wc; LPBYTE lpDst = NULL, lpSrc = NULL, lpMaster = NULL; DWORD cbDst, cbSrc, cbMaster; PVOLCTRLDESC avcd; DWORD cvcd; DWORD ivcd; DWORD imxul; DWORD dwSupport = 0L; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = LoadIcon(pmxud->hInstance, MAKEINTRESOURCE(IDI_MIXER)); wc.lpszMenuName = NULL; wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.hInstance = pmxud->hInstance; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = DefDlgProc; wc.cbClsExtra = 0; wc.cbWndExtra = DLGWINDOWEXTRA; wc.lpszClassName = (pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER) ? gszTrayClassName : gszAppClassName; RegisterClass(&wc); if (pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER) { lpMaster = (LPBYTE)Dlg_LoadResource(pmxud->hInstance , MAKEINTRESOURCE(IDD_TRAYMASTER) , &cbMaster); if (!lpMaster) return FALSE; } else { if (pmxud->dwStyle & MXUD_STYLEF_SMALL) { lpDst = (LPBYTE)Dlg_LoadResource(pmxud->hInstance , MAKEINTRESOURCE(IDD_SMDST) , &cbDst); lpSrc = (LPBYTE)Dlg_LoadResource(pmxud->hInstance , MAKEINTRESOURCE(IDD_SMSRC) , &cbSrc); } else { lpDst = (LPBYTE)Dlg_LoadResource(pmxud->hInstance , MAKEINTRESOURCE(IDD_DESTINATION) , &cbDst); lpSrc = (LPBYTE)Dlg_LoadResource(pmxud->hInstance , MAKEINTRESOURCE(IDD_SOURCE) , &cbSrc); } if (!lpDst || !lpSrc) return FALSE; } pmxud->lpDialog = NULL; pmxud->cbDialog = 0; pmxud->amxul = NULL; pmxud->cmxul = 0; pmxud->avcd = NULL; pmxud->cvcd = 0; // // Create the volume description // if (pmxud->dwFlags & MXUD_FLAGSF_MIXER) { avcd = Mixer_CreateVolumeDescription((HMIXEROBJ)pmxud->mxid , pmxud->iDest , &cvcd); if (!Mixer_GetDeviceName(pmxud)) { GlobalFreePtr(avcd); avcd = NULL; } } else { avcd = Nonmixer_CreateVolumeDescription(pmxud->iDest , &cvcd); if (!Nonmixer_GetDeviceName(pmxud)); { GlobalFreePtr(avcd); avcd = NULL; } } // // Create the dialog box to go along with it // if (avcd) { pmxud->avcd = avcd; pmxud->cvcd = cvcd; if (pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER) { Volume_AddLine(pmxud , lpMaster , cbMaster , MXUL_STYLEF_DESTINATION , &avcd[0]); } else { BOOL fFirstRun; // // Restore HIDDEN flags. // // On first run, be sure to re-save state so there's something // there. // fFirstRun = !Volume_GetSetRegistryLineStates(pmxud->szMixer , pmxud->avcd[0].szShortName , avcd , cvcd , GET); for (ivcd = 0; ivcd < cvcd; ivcd++) { // // Lines are marked hidden if a state has been saved in the // registry or no state has been saved and there are too many // unnecessary lines. // if (avcd[ivcd].dwSupport & VCD_SUPPORTF_HIDDEN) continue; // // Lines are marked VISIBLE if they have sufficient controls // to be useful. // if (!(avcd[ivcd].dwSupport & VCD_SUPPORTF_VISIBLE)) continue; // // Show only defaults on first run. // if (fFirstRun && !(avcd[ivcd].dwSupport & VCD_SUPPORTF_DEFAULT)) { avcd[ivcd].dwSupport |= VCD_SUPPORTF_HIDDEN; continue; } // // For those lines that have important controls, add them to // the UI. // if ((pmxud->dwFlags & MXUD_FLAGSF_MIXER) && ivcd == 0 ) Volume_AddLine(pmxud , lpDst , cbDst , MXUL_STYLEF_DESTINATION , &avcd[ivcd]); else Volume_AddLine(pmxud , lpSrc , cbSrc , MXUL_STYLEF_SOURCE , &avcd[ivcd]); } if (fFirstRun) Volume_GetSetRegistryLineStates(pmxud->szMixer , pmxud->avcd[0].szShortName , avcd , cvcd , SET); } // // Now that both arrays are now fixed, set back pointers for // the vcd's to ui lines. // for (imxul = 0; imxul < pmxud->cmxul; imxul++) { pmxud->amxul[imxul].pvcd->pmxul = &pmxud->amxul[imxul]; // // Accumulate support bits // dwSupport |= pmxud->amxul[imxul].pvcd->dwSupport; } // // Support bits say we have no advanced controls, so don't make // them available. // if (!(dwSupport & VCD_SUPPORTF_MIXER_ADVANCED)) { pmxud->dwFlags |= MXUD_FLAGSF_NOADVANCED; } // // Propogate bad driver bit to be app global. A bad driver was // detected during the construction of a volume description. // for (ivcd = 0; ivcd < pmxud->cvcd; ivcd++) { if (pmxud->avcd[ivcd].dwSupport & VCD_SUPPORTF_BADDRIVER) { dlout("Bad Control->Line mapping. Marking bad driver."); pmxud->dwFlags |= MXUD_FLAGSF_BADDRIVER; break; } } } // // Note: it isn't necessary to free/unlock the lpMaster/lpDst/lpSrc // because they are ptr's to resources and Win32 is smart about resources // return (avcd != NULL); } /* * Volume_DialogBox * * */ DWORD Volume_DialogBox( PMIXUIDIALOG pmxud) { pmxud->dwReturn = MIXUI_EXIT; if (Volume_CreateVolume(pmxud)) { HWND hdlg; hdlg = CreateDialogIndirectParam(pmxud->hInstance , (DLGTEMPLATE *)pmxud->lpDialog , NULL , VolumeProc , (LONG)(LPVOID)pmxud ); if (!hdlg) { Volume_Cleanup(pmxud); return MIXUI_ERROR; } ShowWindow(hdlg, pmxud->nShowCmd); } else return MIXUI_ERROR; } /* * VolumeParent_WndProc * * A generic invisible parent window. * * */ LRESULT CALLBACK VolumeParent_WndProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { PMIXUIDIALOG pmxud; switch (msg) { case WM_CREATE: { LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lparam; pmxud = (PMIXUIDIALOG)lpcs->lpCreateParams; SetWindowLong(hwnd, GWL_USERDATA, (LONG)pmxud); pmxud->hParent = hwnd; if (Volume_DialogBox(pmxud) == MIXUI_ERROR) { if ( !(pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER)) { if ( Volume_NumDevs() == 0 ) Volume_ErrorMessageBox(NULL, pmxud->hInstance, IDS_ERR_NODEV); else Volume_ErrorMessageBox(NULL, pmxud->hInstance, IDS_ERR_HARDWARE); } PostMessage(hwnd, WM_CLOSE, 0, 0L); } return 0; } case WM_CLOSE: DestroyWindow(hwnd); return 0; case WM_DESTROY: // // Post-close cleanup // pmxud = (PMIXUIDIALOG)GetWindowLong(hwnd, GWL_USERDATA); if (!(pmxud->dwStyle & MXUD_STYLEF_NOHELP)) WinHelp(hwnd, gszHelpFileName, HELP_QUIT, 0L); PostQuitMessage(0); return 0; case MYWM_HELPTOPICS: // // F1 Help // pmxud = (PMIXUIDIALOG)GetWindowLong(hwnd, GWL_USERDATA); if (!(pmxud->dwStyle & MXUD_STYLEF_NOHELP)) WinHelp(hwnd, gszHelpFileName, HELP_FINDER, 0); break; case MYWM_RESTART: // // A device change or other user property change caused a UI // change. Sending a restart to the parent prevents ugly stuff // like WinHelp shutting down and exiting our primary message // loop. // pmxud = (PMIXUIDIALOG)GetWindowLong(hwnd, GWL_USERDATA); if (!(pmxud->dwStyle & MXUD_STYLEF_TRAYMASTER)) { if (Volume_NumDevs() == 0) { Volume_ErrorMessageBox(NULL , pmxud->hInstance , IDS_ERR_NODEV); PostMessage(hwnd, WM_CLOSE, 0, 0L); } else if (Volume_DialogBox((PMIXUIDIALOG)lparam) == MIXUI_ERROR) { Volume_ErrorMessageBox(NULL , pmxud->hInstance , IDS_ERR_HARDWARE); PostMessage(hwnd, WM_CLOSE, 0, 0L); } } else { if (Mixer_GetNumDevs() == 0 || Volume_DialogBox((PMIXUIDIALOG)lparam) == MIXUI_ERROR) PostMessage(hwnd, WM_CLOSE, 0, 0L); } break; default: break; } DefWindowProc(hwnd, msg, wparam, lparam); } const TCHAR szNull[] = TEXT (""); /* * Parent Dialog * */ HWND VolumeParent_DialogMain( PMIXUIDIALOG pmxud) { WNDCLASS wc; HWND hwnd; wc.lpszClassName = gszParentClass; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = NULL; wc.lpszMenuName = NULL; wc.hbrBackground = NULL; wc.hInstance = pmxud->hInstance; wc.style = 0; wc.lpfnWndProc = VolumeParent_WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; if (!RegisterClass(&wc)) return NULL; hwnd = CreateWindow(gszParentClass , szNull , 0 , 0 , 0 , 0 , 0 , NULL , NULL , pmxud->hInstance , (LPVOID)pmxud ); return hwnd; } /* - - - - - - - - - */ /* * entry point * */ int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nShowCmd) { int err = 0; MIXUIDIALOG mxud; MSG msg; HWND hwnd; HANDLE hAccel; MMRESULT mmr; TCHAR ach[2]; PENWINREGISTERPROC lpfnRegisterPenApp; HINSTANCE hinstPen; if (hinstPen = (HINSTANCE)GetSystemMetrics(SM_PENWINDOWS)) lpfnRegisterPenApp = (PENWINREGISTERPROC)GetProcAddress(hinstPen , gszRegisterPenApp); else lpfnRegisterPenApp = NULL; if (lpfnRegisterPenApp) (*lpfnRegisterPenApp)(1, TRUE); LoadString(hInst, IDS_IS_RTL, ach, SIZEOF(ach)); gfIsRTL = ach[0] == TEXT('1'); // // initialize the app instance data // ZeroMemory(&mxud, sizeof(mxud)); mxud.hInstance = hInst; mxud.dwFlags = MXUD_FLAGSF_MIXER; // // parse the command line for "/Tray" // switch (lpCmdLine[0]) { case TEXT('-'): case TEXT('/'): switch (lpCmdLine[1]) { case TEXT('T'): case TEXT('t'): mxud.dwStyle |= MXUD_STYLEF_TRAYMASTER; break; case TEXT('S'): case TEXT('s'): mxud.dwStyle |= MXUD_STYLEF_SMALL; break; case TEXT('X'): case TEXT('x'): mxud.dwStyle |= MXUD_STYLEF_TRAYMASTER | MXUD_STYLEF_CLOSE; break; } break; default: break; } // // Restore last style // if (!(mxud.dwStyle & (MXUD_STYLEF_TRAYMASTER|MXUD_STYLEF_SMALL))) { Volume_GetSetStyle(&mxud.dwStyle, GET); } if (mxud.dwStyle & MXUD_STYLEF_TRAYMASTER) { HWND hwndSV; // // Locate a waiting instance of the tray volume and wake it up // hwndSV = FindWindow(gszTrayClassName, NULL); if (hwndSV) { SendMessage(hwndSV, MYWM_WAKEUP, (mxud.dwStyle & MXUD_STYLEF_CLOSE), 0); goto mxendapp; } } if (mxud.dwStyle & MXUD_STYLEF_CLOSE) { goto mxendapp; } // // Init to the default mixer // mmr = Volume_GetDefaultMixerID(&mxud.mxid); // // For the tray master, get the mix id associated with the default // wave device. If this fails, go away. // if (mxud.dwStyle & MXUD_STYLEF_TRAYMASTER) { if (mmr != MMSYSERR_NOERROR) goto mxendapp; mxud.dwStyle |= MXUD_STYLEF_NOHELP; mxud.nShowCmd = SW_HIDE; } else { if (!Volume_NumDevs()) { Volume_ErrorMessageBox(NULL, hInst, IDS_ERR_NODEV); goto mxendapp; } InitVUControl(hInst); if (!LoadString(hInst , IDS_HELPFILENAME , gszHelpFileName , SIZEOF(gszHelpFileName))) mxud.dwStyle |= MXUD_STYLEF_NOHELP; mxud.nShowCmd = (nShowCmd == SW_SHOWMAXIMIZED) ? SW_SHOWNORMAL:nShowCmd; if (!(mxud.dwStyle & MXUD_STYLEF_SMALL)) mxud.dwStyle |= MXUD_STYLEF_STATUS; // has status bar } // // Use the common controls // InitCommonControls(); hAccel = LoadAccelerators(hInst, MAKEINTRESOURCE(IDR_VOLUMEACCEL)); hwnd = VolumeParent_DialogMain(&mxud); if (hwnd) { while (GetMessage(&msg, NULL, 0, 0)) { if (mxud.hwnd) { if (hAccel && TranslateAccelerator(mxud.hwnd, hAccel, &msg)) continue; if (IsDialogMessage(mxud.hwnd,&msg)) continue; } TranslateMessage(&msg); DispatchMessage(&msg); } } mxendapp: return err; }