Logging the foreground process as it changes

Raymond Chen

Raymond

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.

0 comments

Comments are closed. Login to edit/delete your existing comments