// ForeGroundAppMonitor.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include #include #include #include #ifdef DEBUG #define TERM_THREAD_TIMEOUT 60000 // milliseconds #define INIT_THREAD_TIMEOUT 60000 // milliseconds #else #define TERM_THREAD_TIMEOUT 1000 // milliseconds #define INIT_THREAD_TIMEOUT 1000 // milliseconds #endif #define COALLESCE_WINEVENT_TIMER_ID 1 #ifdef DEBUG #define COALLESCE_WINEVENTS_TIME 1000 // milliseconds #else #define COALLESCE_WINEVENTS_TIME 100 // milliseconds #endif #define MAX_CCH_PROCESS_NAME 1024 class CForegroundAppMonitor { public: CForegroundAppMonitor(); ~CForegroundAppMonitor(); HRESULT StartMonitoring(); HRESULT StopMonitoring(); virtual void OnAppChanged(BSTR bstrProcessName, BSTR bstrTitle) = 0; private: HRESULT InitThread(); HRESULT TermThread(); static unsigned int __stdcall ThreadProcSTATIC(void *pv); unsigned int ThreadProc(); void PumpMessageQueue(); HRESULT InitHook(); HRESULT TermHook(); static void CALLBACK WinEventProcSTATIC( HWINEVENTHOOK hWinEventHook, DWORD dwEvent, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime); void WinEventProc( HWINEVENTHOOK hWinEventHook, DWORD dwEvent, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime); static void CALLBACK TimerProcSTATIC( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); void CALLBACK TimerProc( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); HRESULT UpdateForegroundAppInfo(); HRESULT GetForegroundAppInfo(BSTR *pbstrProcessName, BSTR *pbstrTitle); HRESULT GetProcessName(DWORD dwProcessId, BSTR *pbstrProcessName); HRESULT GetTitle(HWND hwnd, BSTR *pbstrTitle); private: static CForegroundAppMonitor *ms_pmonitor; HANDLE m_hthread; UINT m_uiThreadId; HANDLE m_heventThreadInit; HANDLE m_heventThreadStop; HWINEVENTHOOK m_hookForeground; HWINEVENTHOOK m_hookStateChanged; HWINEVENTHOOK m_hookNameChanged; UINT_PTR m_timerid; CComBSTR m_cbstrProcess; CComBSTR m_cbstrTitle; }; CForegroundAppMonitor *CForegroundAppMonitor::ms_pmonitor = NULL; CForegroundAppMonitor::CForegroundAppMonitor() { m_hthread = NULL; m_uiThreadId = 0; m_heventThreadInit = NULL; m_heventThreadStop = NULL; m_hookForeground = NULL; m_hookStateChanged = NULL; m_hookNameChanged = NULL; m_timerid = 0; } CForegroundAppMonitor::~CForegroundAppMonitor() { if (m_hthread != NULL) { StopMonitoring(); } } HRESULT CForegroundAppMonitor::StartMonitoring() { HRESULT hr = S_OK; // This class is only implemented to allow one derived class instance // of CForegroundAppMonitor to be monitoring at any given instant if (ms_pmonitor != NULL || m_hthread != NULL) { hr = E_UNEXPECTED; } // Create the events to communicate to the worker thread if (SUCCEEDED(hr)) { if (m_heventThreadInit == NULL) { m_heventThreadInit = CreateEvent(NULL, FALSE, FALSE, NULL); } if (m_heventThreadStop == NULL) { m_heventThreadStop = CreateEvent(NULL, FALSE, FALSE, NULL); } if (m_heventThreadInit == NULL || m_heventThreadStop == NULL) { hr = E_UNEXPECTED; } } // Create the thread if (SUCCEEDED(hr)) { ms_pmonitor = this; m_hthread = (HANDLE) _beginthreadex( NULL, 0, ThreadProcSTATIC, (void *)this, 0, &m_uiThreadId); if (m_hthread == NULL) { hr = E_UNEXPECTED; } } // Wait for the thread to initialize if (SUCCEEDED(hr)) { if (WaitForSingleObject(m_heventThreadInit, INIT_THREAD_TIMEOUT) != WAIT_OBJECT_0) { hr = E_UNEXPECTED; } } // Are we running a monitor now? if (FAILED(hr)) { ms_pmonitor = NULL; } return hr; } HRESULT CForegroundAppMonitor::StopMonitoring() { HRESULT hr = S_OK; BOOL fTermed = false; if (m_hthread != NULL) { SetEvent(m_heventThreadStop); fTermed = WaitForSingleObject(m_hthread, TERM_THREAD_TIMEOUT) == WAIT_OBJECT_0; } if (!fTermed) { hr = E_UNEXPECTED; } if (m_heventThreadInit != NULL) { CloseHandle(m_heventThreadInit); m_heventThreadInit = NULL; } if (m_heventThreadStop != NULL) { CloseHandle(m_heventThreadStop); m_heventThreadStop = NULL; } if (m_hthread != NULL) { m_hthread = NULL; m_uiThreadId = 0; } if (ms_pmonitor == this) { ms_pmonitor = NULL; } return hr; } unsigned int __stdcall CForegroundAppMonitor::ThreadProcSTATIC(void *pv) { HRESULT hr = S_OK; if (ms_pmonitor == NULL) { hr = E_UNEXPECTED; } if (SUCCEEDED(hr)) { hr = ms_pmonitor->ThreadProc(); } return hr; } unsigned int CForegroundAppMonitor::ThreadProc() { HRESULT hr = S_OK; // Init the thread and inform the caller that we've finished initialization hr = InitThread(); if (SUCCEEDED(hr)) { ::SetEvent(m_heventThreadInit); } // Park the thread waiting for a shutdown request, and pumping messages if (SUCCEEDED(hr)) { HANDLE rghevents[1]; rghevents[0] = m_heventThreadStop; BOOL fContinue = TRUE; while (fContinue) { DWORD dwWait = MsgWaitForMultipleObjects(ARRAYSIZE(rghevents), rghevents, false, INFINITE, QS_ALLINPUT); switch (dwWait) { case WAIT_OBJECT_0: // Shutdown request // ASSERT(rghevents[0] == m_heventThreadStop); fContinue = false; break; default: PumpMessageQueue(); break; } } } // Now terminate the thread if (SUCCEEDED(hr)) { hr = TermThread(); } return hr; } HRESULT CForegroundAppMonitor::InitThread() { HRESULT hr = InitHook(); if (SUCCEEDED(hr)) { m_cbstrProcess.Empty(); m_cbstrTitle.Empty(); hr = GetForegroundAppInfo(&m_cbstrProcess, &m_cbstrTitle); } return hr; } HRESULT CForegroundAppMonitor::TermThread() { HRESULT hr = S_OK; if (m_timerid != 0) { KillTimer(NULL, m_timerid); m_timerid = 0; } m_cbstrProcess.Empty(); m_cbstrTitle.Empty(); hr = TermHook(); return hr; } void CForegroundAppMonitor::PumpMessageQueue() { MSG Msg; while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) { DispatchMessage(&Msg); } } HRESULT CForegroundAppMonitor::InitHook() { HRESULT hr = S_OK; m_hookForeground = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, NULL, WinEventProcSTATIC, 0, 0, WINEVENT_OUTOFCONTEXT); m_hookStateChanged = SetWinEventHook(EVENT_OBJECT_STATECHANGE, EVENT_OBJECT_STATECHANGE, NULL, WinEventProcSTATIC, 0, 0, WINEVENT_OUTOFCONTEXT); m_hookNameChanged = SetWinEventHook(EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE, NULL, WinEventProcSTATIC, 0, 0, WINEVENT_OUTOFCONTEXT); if (m_hookForeground == NULL || m_hookStateChanged == NULL || m_hookNameChanged == NULL) { hr = E_UNEXPECTED; } return hr; } HRESULT CForegroundAppMonitor::TermHook() { HRESULT hr = S_OK; if (m_hookForeground != NULL) { UnhookWinEvent(m_hookForeground); m_hookForeground = NULL; } if (m_hookStateChanged != NULL) { UnhookWinEvent(m_hookStateChanged); m_hookStateChanged = NULL; } if (m_hookNameChanged != NULL) { UnhookWinEvent(m_hookNameChanged); m_hookNameChanged = NULL; } return hr; } void CALLBACK CForegroundAppMonitor::WinEventProcSTATIC(HWINEVENTHOOK hWinEventHook, DWORD dwEvent, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) { if (ms_pmonitor != NULL) { ms_pmonitor->WinEventProc(hWinEventHook, dwEvent, hwnd, idObject, idChild, dwEventThread, dwmsEventTime); } } void CForegroundAppMonitor::WinEventProc(HWINEVENTHOOK hWinEventHook, DWORD dwEvent, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) { if (m_timerid == 0) { m_timerid = SetTimer(NULL, COALLESCE_WINEVENT_TIMER_ID, COALLESCE_WINEVENTS_TIME, TimerProcSTATIC); } } void CALLBACK CForegroundAppMonitor::TimerProcSTATIC(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { if (ms_pmonitor != NULL) { ms_pmonitor->TimerProc(hwnd, uMsg, idEvent, dwTime); } } void CALLBACK CForegroundAppMonitor::TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { KillTimer(NULL, m_timerid); m_timerid = 0; UpdateForegroundAppInfo(); } HRESULT CForegroundAppMonitor::UpdateForegroundAppInfo() { HRESULT hr = S_OK; CComBSTR cbstrProcess; CComBSTR cbstrTitle; hr = GetForegroundAppInfo(&cbstrProcess, &cbstrTitle); #ifdef DEBUG OutputDebugStringW(L"Process: "); OutputDebugStringW(cbstrProcess); OutputDebugStringW(L"\nTitle: "); OutputDebugStringW(cbstrTitle); OutputDebugStringW(L"\n"); #endif BOOL fChanged = FALSE; if (SUCCEEDED(hr)) { if (wcscmp(m_cbstrProcess, cbstrProcess) != 0 || wcscmp(m_cbstrTitle,cbstrTitle) != 0) { fChanged = TRUE; } if (fChanged) { m_cbstrProcess = cbstrProcess.Detach(); m_cbstrTitle = cbstrTitle.Detach(); OnAppChanged(m_cbstrProcess, m_cbstrTitle); } } return hr; } HRESULT CForegroundAppMonitor::GetForegroundAppInfo(BSTR *pbstrProcessName, BSTR *pbstrTitle) { HRESULT hr = S_OK; HWND hwnd = GetForegroundWindow(); DWORD dwProcessId; DWORD dwThreadId = GetWindowThreadProcessId(hwnd, &dwProcessId); CComBSTR cbstrProcessName; hr = GetProcessName(dwProcessId, &cbstrProcessName); CComBSTR cbstrTitle; if (SUCCEEDED(hr)) { hr = GetTitle(hwnd, &cbstrTitle); } if (SUCCEEDED(hr)) { *pbstrProcessName = cbstrProcessName.Detach(); *pbstrTitle =cbstrTitle.Detach(); } return hr; } HRESULT CForegroundAppMonitor::GetProcessName(DWORD dwProcessId, BSTR *pbstrProcessName) { HRESULT hr = S_OK; HANDLE hprocess = OpenProcess(PROCESS_QUERY_INFORMATION, false, dwProcessId); if (hprocess == NULL) { hr = E_UNEXPECTED; } WCHAR szFileName[MAX_CCH_PROCESS_NAME + 1]; szFileName[0] = '\0'; GetProcessImageFileNameW(hprocess, szFileName, MAX_CCH_PROCESS_NAME); szFileName[MAX_CCH_PROCESS_NAME] = '\0'; CloseHandle(hprocess); CComBSTR cbstrProcessName; if (SUCCEEDED(hr)) { cbstrProcessName = szFileName; if (cbstrProcessName == NULL) { hr = E_OUTOFMEMORY; } } if (SUCCEEDED(hr)) { *pbstrProcessName = cbstrProcessName.Detach(); } return hr; } HRESULT CForegroundAppMonitor::GetTitle(HWND hwnd, BSTR *pbstrTitle) { HRESULT hr = S_OK; int cch = GetWindowTextLengthW(hwnd); WCHAR *pszTitle = new WCHAR[cch + 1]; pszTitle[0] = '\0'; if (cch > 0) { GetWindowTextW(hwnd, pszTitle, cch + 1); pszTitle[cch] = '\0'; } CComBSTR cbstrTitle; if (SUCCEEDED(hr)) { cbstrTitle = pszTitle; if (cbstrTitle == NULL) { hr = E_OUTOFMEMORY; } } delete [] pszTitle; if (SUCCEEDED(hr)) { *pbstrTitle = cbstrTitle.Detach(); } return hr; } class CTestForegroundAppMonitor : public CForegroundAppMonitor { public: void OnAppChanged(BSTR bstrProcessName, BSTR bstrTitle); }; void CTestForegroundAppMonitor::OnAppChanged(BSTR bstrProcessName, BSTR bstrTitle) { wprintf(L"process=\"%s\",\ntitle=\"%s\"\n\n", bstrProcessName, bstrTitle); } int _tmain(int argc, _TCHAR* argv[]) { CTestForegroundAppMonitor mon; HRESULT hr; printf("Starting monitor...\n"); hr = mon.StartMonitoring(); if (SUCCEEDED(hr)) { printf("The monitor is now running. Press any key to stop...\n"); char ch = getchar(); printf("Stopping monitor...\n"); hr = mon.StopMonitoring(); } if (SUCCEEDED(hr)) { printf("The monitor has now stopped."); } return SUCCEEDED(hr) ? 0 : hr; }