* * *

Author Topic: Window Enumeration and Tracking Via Events  (Read 358 times)

R0b0t1

  • Full Member
  • ***
  • Posts: 127
Window Enumeration and Tracking Via Events
« on: June 19, 2017, 06:39:54 pm »
Hello,

I'm asking here mostly because this is one of the best places I know of to ask WinAPI questions. I'm doing this in C++ but I can provide FreePascal code if it is imperative (I was considering switching because it might be easier to prototype with, anyway).

Basically the issues I am having have to do with the poorly documented behavior of EnumWindows and the accessibility window event hooks (SetWinEventHook).

For example when using EnumWindows with the checks below, I can (for the most part) enumerate all windows on the desktop that have some visible presence, whether or not they are minimized:
Code: Pascal  [Select]
  1. LStyle := GetWindowLong(HWnd, GWL_STYLE);
  2. if LStyle and (WS_BORDER or WS_VISIBLE) <> (WS_BORDER or WS_VISIBLE) then
  3.   exit;
Unfortunately with the advent of Windows 10 there are "background applications" which are most commonly Windows Store apps. If a compatible application is closed they will be suspended similar to an Android application's lifecycle. They will sometimes show up in the EnumWindows listing and match the given criteria despite having no interactable desktop presence (they are not minimized nor are they in the tray). I attempted to figure out if I could cross reference the process list and check for suspended applications (Task Manager seems to be able to tell) but wasn't able to find anything. Does anyone have any suggestions?

With the accessibility hooks, I am interested in updates of the desktop, so I decided to hook the event range EVENT_OBJECT_CREATE to EVENT_OBJECT_DESTROY using SetWinEventHook. There are similar problems with this as with EnumWindows.

Code: Pascal  [Select]
  1. if not (event = EVENT_OBJECT_CREATE and
  2.         idObject = OBJID_WINDOW and
  3.         idChild = INDEXID_CONTAINER) then
  4.   exit;
  5.  
  6. LStyle := GetWindowLong(HWnd, GWL_STYLE);
  7. if LStyle and (WS_BORDER) <> (WS_BORDER) then
  8.   exit;
WS_VISIBLE is never set, even though it should be as far as I know, but this difference mostly doesn't matter. What does matter is that while simple applications like File Explorer windows will send EVENT_OBJECT_CREATE and EVENT_OBJECT_DESTROY lots of other applications do not, e.g. an already opened Windows Store app does not send these events when resumed or suspended, and multiwindow applications like Firefox do not send them when creating new windows.

Does anyone have any suggestions? As far as I know the only relevant documentation is the documentation for the functions I am using, so any pointers are welcome. I've started Spy++ to try to investigate window properties that I could select based on, and this got me the values to use with EnumWindows, but the current issues are a nonstarter that I can't get around.

If sample code would be helpful I can whip something up. Since I was mostly planning to do it anyway I will edit this when I get it done.

Thanks in advance,
     R0b0t1.

R0b0t1

  • Full Member
  • ***
  • Posts: 127
Re: Window Enumeration and Tracking Via Events
« Reply #1 on: June 22, 2017, 05:37:50 pm »
Here is complete FreePascal compatible code. Run the program and launch e.g. a file explorer, you will see "File Explorer" printed. Per the original post some windows do not trigger an event. Does anyone know why? What should I be looking for?

Code: Pascal  [Select]
  1. program PTile;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$calling stdcall}
  5.  
  6. uses
  7.   Classes, Windows;
  8.  
  9. type
  10.   HWINEVENTHOOK = HANDLE;
  11.   WINEVENTPROC = procedure(
  12.     EventHook: HWINEVENTHOOK;
  13.     Event: DWORD;
  14.     HWnd: HWND;
  15.     IDObject, IDChild: LONG;
  16.     EventThread, EventTime: DWORD);
  17.  
  18. const
  19.   WINEVENT_OUTOFCONTEXT = $0000;
  20.   EVENT_OBJECT_CREATE   = $8000;
  21.   EVENT_OBJECT_DESTROY  = $8001;
  22.   OBJID_WINDOW          = $0000;
  23.   INDEXID_CONTAINER     = $0000;
  24.  
  25. function SetWinEventHook(
  26.   EventMin, EventMax: UINT;
  27.   HMod: HMODULE;
  28.   EventProc: WINEVENTPROC;
  29.   IDProcess, IDThread: DWORD;
  30.   Flags: UINT): HWINEVENTHOOK; external 'User32.dll';
  31.  
  32. function UnhookWinEvent(
  33.   EventHook: HWINEVENTHOOK): LongBool; external 'User32.dll';
  34.  
  35. function {%H-}EnumWindowsCallback(
  36.   {%H-}HWnd: HWND;
  37.   {%H-}LParam: LPARAM): LongBool;
  38. begin
  39.   Result := True;
  40.   WriteLn('.');
  41. end;
  42.  
  43. procedure WinEventCallback(
  44.   {%H-}EventHook: HWINEVENTHOOK;
  45.   {%H-}Event: DWORD;
  46.   {%H-}HWnd: HWND;
  47.   {%H-}IDObject, {%H-}IDChild: LONG;
  48.   {%H-}EventThread, {%H-}EventTime: DWORD);
  49. var
  50.   Len: LongInt;
  51.   Title: array of WideChar;
  52.   LStyle: LONG;
  53. begin
  54.   if not ((Event = EVENT_OBJECT_CREATE) and
  55.           (IDObject = OBJID_WINDOW) and
  56.           (IDChild = INDEXID_CONTAINER)) then
  57.     exit;
  58.  
  59.   Len := GetWindowTextLengthW(HWnd) + 1;
  60.   if Len - 1 = 0 then
  61.     exit;
  62.  
  63.   SetLength(Title, Len);
  64.   GetWindowTextW(HWnd, @Title[0], Len);
  65.  
  66.   LStyle := GetWindowLong(HWnd, GWL_STYLE);
  67.   if LStyle = 0 then
  68.     exit;
  69.  
  70.   if (LStyle and WS_BORDER) <> WS_BORDER then
  71.     exit;
  72.  
  73.   WriteLn(WideCharToString(@Title[0]));
  74. end;
  75.  
  76. var
  77.   Message: TMsg;
  78.   EventHook: HWINEVENTHOOK;
  79. begin
  80.   Message := Default(TMsg);
  81.   EventHook := SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY, 0,
  82.     @WinEventCallback, 0, 0, WINEVENT_OUTOFCONTEXT);
  83.  
  84.   while GetMessage(Message, 0, 0, 0) do
  85.     DispatchMessage(Message);
  86.  
  87.   if EventHook <> 0 then
  88.     UnhookWinEvent(EventHook);
  89. end.

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus