December 2nd, 2013

Logging the foreground process as it changes

Today’s Little Program simply logs all changes to the foreground window by recording the path to the application the user switched to. You might use this as part of a usability study to monitor what applications users spend most of their time in.

Most of this code is just taking things we already know and snapping them together.

  1. Using accessibility to monitor events, specifically to monitor foreground changes.

  2. Get­Window­Thread­Process­Id to get the process ID from a window.

  3. Open­Process to get a handle to a process given the process ID.

  4. Query­Full­Process­ImageName to get the path to the application from the handle. (For Windows XP, you can use Get­Process­Image­File­Name.)

Take our scratch program and make these changes:

BOOL QueryWindowFullProcessImageName(
    HWND hwnd,
    DWORD dwFlags,
    PTSTR lpExeName,
    DWORD dwSize)
{
 DWORD pid = 0;
 BOOL fRc = FALSE;
 if (GetWindowThreadProcessId(hwnd, &pid)) {
  HANDLE hProcess = OpenProcess(
          PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
  if (hProcess) {
   fRc = QueryFullProcessImageName(
          hProcess, dwFlags, lpExeName, &dwSize);
   CloseHandle(hProcess);
  }
 }
 return fRc;
}

The Query­Window­Full­Process­Image­Name function is the meat of the program, performing steps 2 through 4 above.

Now we just hook this up in our event callback function. This should look really familiar, since we did pretty much the same thing earlier this year.

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
 g_hwndChild = CreateWindow(TEXT("listbox"), NULL,
     LBS_HASSTRINGS | WS_CHILD | WS_VISIBLE | WS_VSCROLL,
     0, 0, 0, 0, hwnd, NULL, g_hinst, 0);
 if (!g_hwndChild) return FALSE;
 return TRUE;
}
void CALLBACK WinEventProc(
    HWINEVENTHOOK hWinEventHook,
    DWORD event,
    HWND hwnd,
    LONG idObject,
    LONG idChild,
    DWORD dwEventThread,
    DWORD dwmsEventTime
)
{
 if (event == EVENT_SYSTEM_FOREGROUND &
     idObject == OBJID_WINDOW &&
     idChild == CHILDID_SELF)
 {
  PCTSTR pszMsg;
  TCHAR szBuf[MAX_PATH];
  if (hwnd) {
   DWORD cch = ARRAYSIZE(szBuf);
   if (QueryWindowFullProcessImageName(hwnd, 0,
                      szBuf, ARRAYSIZE(szBuf))) {
    pszMsg = szBuf;
   } else {
    pszMsg = TEXT("<unknown>");
   }
  } else {
   pszMsg = TEXT("<none>");
  }
  ListBox_AddString(g_hwndChild, pszMsg);
 }
}
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
 ...
  ShowWindow(hwnd, nShowCmd);
 HWINEVENTHOOK hWinEventHook = SetWinEventHook(
     EVENT_SYSTEM_FOREGROUND,
     EVENT_SYSTEM_FOREGROUND,
     NULL, WinEventProc, 0, 0,
     WINEVENT_OUTOFCONTEXT);
  while (GetMessage(&msg, NULL, 0, 0)) {
   TranslateMessage(&msg);
   DispatchMessage(&msg);
  }
  if (hWinEventHook) UnhookWinEvent(hWinEventHook);
...
}

The main program installs an accessibility hook for the EVENT_SYSTEM_FOREGROUND event, and each time the event fires, it extracts the process name and logs it to the screen. Since the notification is asynchronous, the foreground window may have been destroyed by the time the notification is received, so we have to be prepared for that case.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.