#include "shellprv.h" #pragma hdrstop #include #include "uevttmr.h" // 1. Use dpa callback system to delete the entire hdpa array EXTERN_C const TCHAR c_szUserEventWindow[] = TEXT("UserEventWindow"); // *** IUnknown methods *** STDMETHODIMP CUserEventTimer::QueryInterface (REFIID riid, LPVOID * ppvObj) { static const QITAB qit[] = { QITABENT(CUserEventTimer, IUserEventTimer), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP_(ULONG) CUserEventTimer::AddRef() { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) CUserEventTimer::Release() { ASSERT( 0 != m_cRef ); ULONG cRef = InterlockedDecrement(&m_cRef); if ( 0 == cRef ) { delete this; } return cRef; } // *** Constructor and Destructor *** CUserEventTimer::CUserEventTimer() : m_cRef(1) { } CUserEventTimer::~CUserEventTimer() { _Destroy(); } // *** IUserEventTimer methods *** HRESULT CUserEventTimer::SetUserEventTimer( HWND hWnd, UINT uCallbackMessage, UINT uTimerElapse, IUserEventTimerCallback * pUserEventTimerCallback, ULONG * puUserEventTimerID ) { RIP(puUserEventTimerID != NULL); HRESULT hr; // Argument Validation if (!m_hWnd) hr = E_FAIL; else if (!_dpaUserEventInfo) hr = E_OUTOFMEMORY; else if (!hWnd && !pUserEventTimerCallback) hr = E_INVALIDARG; else if (!puUserEventTimerID || uTimerElapse <= 0) hr = E_INVALIDARG; else if (hWnd) { int nIndex = _GetTimerDetailsIndex(hWnd, *puUserEventTimerID); if (nIndex >= 0) hr = _ResetUserEventTimer(hWnd, uCallbackMessage, uTimerElapse, nIndex); else hr = _SetUserEventTimer(hWnd, uCallbackMessage, uTimerElapse, pUserEventTimerCallback, puUserEventTimerID); } else { ASSERT(pUserEventTimerCallback != NULL); hr = _SetUserEventTimer(hWnd, uCallbackMessage, uTimerElapse, pUserEventTimerCallback, puUserEventTimerID); } return hr; } HRESULT CUserEventTimer::InitTimerTickInterval(UINT uTimerTickIntervalMs) { // If there is more than one registered client to the user event timer, // then we cannot change the timer tick interval if (_dpaUserEventInfo.GetPtrCount() > 0) return E_FAIL; if (uTimerTickIntervalMs > 0) m_uTimerTickInterval = uTimerTickIntervalMs; else m_uTimerTickInterval = TIMER_ELAPSE; return S_OK; } HRESULT CUserEventTimer::_SetUserEventTimer( HWND hWnd, UINT uCallbackMessage, UINT uTimerElapse, IUserEventTimerCallback * pUserEventTimerCallback, ULONG * puUserEventTimerID ) { ASSERT(puUserEventTimerID); ASSERT(m_uTimerTickInterval > 0); HRESULT hr = E_OUTOFMEMORY; USEREVENTINFO * pUserEventInfo = new USEREVENTINFO; if (pUserEventInfo) { pUserEventInfo->hWnd = hWnd; if (hWnd) { pUserEventInfo->uCallbackMessage = uCallbackMessage; pUserEventInfo->uUserEventTimerID = *puUserEventTimerID; } else { pUserEventInfo->pUserEventTimerCallback = pUserEventTimerCallback; } // Timer ID cannot be zero.. if (!pUserEventInfo->uUserEventTimerID) { ULONG uTimerID = _GetNextInternalTimerID(hWnd); if (uTimerID != -1) pUserEventInfo->uUserEventTimerID = uTimerID; } int nRetInsert = -1; if (pUserEventInfo->uUserEventTimerID) { pUserEventInfo->uTimerElapse = uTimerElapse; pUserEventInfo->uIntervalCountdown = _CalcNumIntervals(uTimerElapse); pUserEventInfo->bFirstTime = TRUE; nRetInsert = _dpaUserEventInfo.AppendPtr(pUserEventInfo); if (nRetInsert != -1) { *puUserEventTimerID = pUserEventInfo->uUserEventTimerID; if (!_uUserTimerID) { _uUserTimerID = SetTimer(m_hWnd, TIMER_ID, m_uTimerTickInterval, NULL); } if (!_uUserTimerID) { _dpaUserEventInfo.DeletePtr(_dpaUserEventInfo.GetPtrCount()-1); } } } hr = S_OK; if (nRetInsert == -1 || _uUserTimerID == 0) { *puUserEventTimerID = 0; delete (pUserEventInfo); hr = E_FAIL; } else if (NULL == hWnd) { IUnknown_SetSite(pUserEventTimerCallback, this); pUserEventTimerCallback->AddRef(); } } if (SUCCEEDED(hr)) { if (!m_dwUserStartTime && _dpaUserEventInfo.GetPtrCount() == 1) m_dwUserStartTime = GetTickCount(); } return hr; } HRESULT CUserEventTimer::_ResetUserEventTimer( HWND hWnd, UINT uCallbackMessage, UINT uTimerElapse, int nIndex ) { ASSERT(m_hWnd != NULL); ASSERT(_dpaUserEventInfo != NULL); ASSERT(hWnd != NULL); ASSERT(nIndex >= 0); USEREVENTINFO * pUserEventInfo = _dpaUserEventInfo.GetPtr(nIndex); ASSERT(pUserEventInfo); ASSERT(pUserEventInfo->hWnd == hWnd); pUserEventInfo->uTimerElapse = uTimerElapse; pUserEventInfo->uIntervalCountdown = _CalcNumIntervals(uTimerElapse); pUserEventInfo->bFirstTime = TRUE; return S_OK; } int DeleteCB(USEREVENTINFO * lpData1, LPVOID lpData2) { USEREVENTINFO * pUserEventInfo = lpData1; ASSERT(pUserEventInfo); #ifdef DEBUG BOOL bErrorCheck = (lpData2 ? (*(BOOL *)lpData2) : FALSE); if (bErrorCheck) TraceMsg(TF_WARNING, "CUserEventTimer::s_DeleteCB App hasnt killed timer hwnd = %u, timerID = %u", pUserEventInfo->hWnd, pUserEventInfo->uUserEventTimerID); #endif IUserEventTimerCallback * pUserEventTimerCallback = (pUserEventInfo->hWnd == NULL) ? pUserEventInfo->pUserEventTimerCallback : NULL; ASSERT(pUserEventInfo->hWnd || pUserEventTimerCallback); if (pUserEventTimerCallback) { IUnknown_SetSite(pUserEventTimerCallback, NULL); pUserEventTimerCallback->Release(); } // Dangerous to delete pUserEventInfo here, but this function is called as the // DPA DestroyCallback, as well as from KillUserEventTimer // In KillUserEventTimer, we remove the event from the dpa, while in the callback, // we dont need to explicitly remove the event from the queue... delete pUserEventInfo; return TRUE; } HRESULT CUserEventTimer::GetUserEventTimerElapsed( HWND hWnd, ULONG uUserEventTimerID, UINT * puTimerElapsed) { HRESULT hr = E_FAIL; if (!puTimerElapsed || !hWnd || !m_hWnd || !_dpaUserEventInfo) return hr; int nIndex = _GetTimerDetailsIndex(hWnd, uUserEventTimerID); if (nIndex >= 0) { USEREVENTINFO * pUserEventInfo = _dpaUserEventInfo.GetPtr(nIndex); *puTimerElapsed = _CalcMilliSeconds( _CalcNumIntervals(pUserEventInfo->uTimerElapse) - pUserEventInfo->uIntervalCountdown ); hr = S_OK; } else *puTimerElapsed = 0; return hr; } HRESULT CUserEventTimer::KillUserEventTimer(HWND hWnd, ULONG uUserEventTimerID) { HRESULT hr = E_FAIL; if (!m_hWnd || !_dpaUserEventInfo) return hr; int nIndex = _GetTimerDetailsIndex(hWnd, uUserEventTimerID); if (nIndex >= 0) { DeleteCB(_dpaUserEventInfo.GetPtr(nIndex), NULL); _dpaUserEventInfo.DeletePtr(nIndex); _KillIntervalTimer(); hr = S_OK; } else { hr = E_FAIL; } if (0 == _dpaUserEventInfo.GetPtrCount()) m_dwUserStartTime = 0; return hr; } // Private helpers HRESULT CUserEventTimer::Init() { if (!_CreateWindow()) return E_FAIL; if (!_dpaUserEventInfo.Create(4)) return E_OUTOFMEMORY; m_uTimerTickInterval = TIMER_ELAPSE; ASSERT(!_uUserTimerID); ASSERT(!m_dwUserStartTime); return S_OK; } BOOL CUserEventTimer::_CreateWindow() { if (!m_hWnd) { WNDCLASSEX wc; DWORD dwExStyle = WS_EX_STATICEDGE; ZeroMemory(&wc, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); if (!GetClassInfoEx(HINST_THISDLL, c_szUserEventWindow, &wc)) { wc.lpszClassName = c_szUserEventWindow; wc.style = CS_DBLCLKS; wc.lpfnWndProc = s_WndProc; wc.hInstance = HINST_THISDLL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_3DFACE+1); wc.cbWndExtra = sizeof(CUserEventTimer *); if (!RegisterClassEx(&wc)) { return FALSE; } } m_hWnd = CreateWindowEx(dwExStyle, c_szUserEventWindow, NULL, WS_POPUP, 0, 0, 0, 0, HWND_MESSAGE, NULL, HINST_THISDLL, (void *)this); if (!m_hWnd) return FALSE; } return TRUE; } void CUserEventTimer::_Destroy() { if (_dpaUserEventInfo) { BOOL bErrorCheck = TRUE; _dpaUserEventInfo.DestroyCallback(DeleteCB, &bErrorCheck); } _KillIntervalTimer(); DestroyWindow(m_hWnd); } void CUserEventTimer::_KillIntervalTimer() { if (_uUserTimerID) { if (_dpaUserEventInfo && _dpaUserEventInfo.GetPtrCount() == 0) { KillTimer(m_hWnd, _uUserTimerID); _uUserTimerID = 0; } } } ULONG CUserEventTimer::_GetNextInternalTimerID(HWND hWnd) { ULONG uStartTimerID = MIN_TIMER_ID; for (; uStartTimerID <= MAX_TIMER_ID; uStartTimerID++) { if (!_IsAssignedTimerID(hWnd, uStartTimerID)) break; } if (uStartTimerID > MAX_TIMER_ID) uStartTimerID = -1; return uStartTimerID; } int CUserEventTimer::_GetTimerDetailsIndex(HWND hWnd, ULONG uUserEventTimerID) { if (!_dpaUserEventInfo || !uUserEventTimerID) return -1; for (int i = _dpaUserEventInfo.GetPtrCount()-1; i >= 0; i--) { USEREVENTINFO * pUserEventInfo = _dpaUserEventInfo.GetPtr(i); ASSERT(pUserEventInfo); if (pUserEventInfo->hWnd == hWnd && pUserEventInfo->uUserEventTimerID == uUserEventTimerID) { return i; } } return -1; } LRESULT CALLBACK CUserEventTimer::s_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CUserEventTimer *puet = (CUserEventTimer *)GetWindowLongPtr(hwnd, 0); if (WM_CREATE == uMsg) { CREATESTRUCT *pcs = (CREATESTRUCT *)lParam; puet = (CUserEventTimer *)pcs->lpCreateParams; puet->m_hWnd = hwnd; SetWindowLongPtr(hwnd, 0, (LONG_PTR)puet); } return puet ? puet->v_WndProc(uMsg, wParam, lParam) : DefWindowProc(hwnd, uMsg, wParam, lParam); } LRESULT CUserEventTimer::v_WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_TIMER: ASSERT(wParam == TIMER_ID); _OnTimer(); return 0; default: return (DefWindowProc(m_hWnd, uMsg, wParam, lParam)); } } void CUserEventTimer::_OnTimer() { ASSERT(m_hWnd); if (_dpaUserEventInfo) { LONG uTimerDifference = -1; LASTINPUTINFO lii = {0}; lii.cbSize = sizeof(LASTINPUTINFO); if (GetLastInputInfo(&lii)) { if (lii.dwTime < m_dwUserStartTime) uTimerDifference = 0; else uTimerDifference = lii.dwTime - m_dwUserStartTime; } LONG uMinTimerDifferenceThreshold = (LONG) (m_uTimerTickInterval * (float)(1-MIN_TIMER_THRESHOLD)); LONG uMaxTimerDifferenceThreshold = (LONG) (m_uTimerTickInterval * (float)(1+MAX_TIMER_THRESHOLD)); for (int i = _dpaUserEventInfo.GetPtrCount()-1; i >= 0; i--) { USEREVENTINFO * pUserEventInfo = _dpaUserEventInfo.GetPtr(i); ASSERT(pUserEventInfo); if (uTimerDifference != 0) { if ( (uTimerDifference == -1) || (pUserEventInfo->bFirstTime && uTimerDifference > uMinTimerDifferenceThreshold && uTimerDifference <= uMaxTimerDifferenceThreshold) || (!pUserEventInfo->bFirstTime && uTimerDifference <= uMaxTimerDifferenceThreshold) ) { pUserEventInfo->uIntervalCountdown --; if (pUserEventInfo->uIntervalCountdown == 0) { // Reset the countdown pUserEventInfo->uIntervalCountdown = _CalcNumIntervals(pUserEventInfo->uTimerElapse); if (pUserEventInfo->hWnd) { PostMessage(pUserEventInfo->hWnd, pUserEventInfo->uCallbackMessage, (WPARAM) pUserEventInfo->uTimerElapse, (LPARAM) pUserEventInfo->uUserEventTimerID); } else { pUserEventInfo->pUserEventTimerCallback->UserEventTimerProc( pUserEventInfo->uUserEventTimerID, pUserEventInfo->uTimerElapse); } } } } pUserEventInfo->bFirstTime = FALSE; } m_dwUserStartTime = GetTickCount(); } } STDAPI CUserEventTimer_CreateInstance(IUnknown* punkOuter, REFIID riid, void **ppv) { HRESULT hr = E_OUTOFMEMORY; CUserEventTimer * pUserEventTimer = new CUserEventTimer(); if (!pUserEventTimer || FAILED(hr = pUserEventTimer->Init())) { *ppv = NULL; } else { hr = pUserEventTimer->QueryInterface(riid, ppv); pUserEventTimer->Release(); // Already have a ref count from new } return hr; }