Tracking Windows Events using Microsoft Active Accessibility

Source code for this article can be downloaded from the downloads Page.

Introduction

Hooking is a method of tracking Windows events and messages by installing a subroutine in the default message/event handling chain. A common way of achieving this is by using the SetWindowsHookEx function. However, tracking the events system wide using this function requires an external dll mechanism that poses unnecessary communication problems while notifying the events. A better method is WinEvents introduced by Microsoft Active Accessibility - a mechanism that allows servers and the operating system to notify clients when an accessible object changes in some manner. By accessible object it is meant an object that exposes IAccessible interface. All interface elements (such as buttons, listboxes etc...) expose this interface and hence are accessible objects. The changes that trigger the notifications about accessible objects can be any of the following:

These are typical notifications generated by operating system. There can also be custom notifications such as alerts and selection changes etc.. that would be generated by server applications. A server application uses the NotifyWinEvent function to notify about such events. In turn, a client application uses the SetWinEventHook function to register a callback hook function to be notified of the generated events (by both the server applications as well the operating system). This SetWinEventHook function allows the client application to specify which processes and threads it is interested in receiving events from, which would be useful when the client application wants to work with only a selected set of processes and/or threads. The call back procedure supplied by the client application should be of the following  format:

VOID CALLBACK WinEventProc(HWINEVENTHOOK hWinEventHook,
        DWORD event,
        HWND hwnd,
        LONG idObject,
        LONG idChild,
        DWORD dwEventThread,
        DWORD dwmsEventTime);

This callback procedure would be invoked each time an event that has been registered by the client application occurs. From within the callback procedure, a client application can use the AccessibleObjectFromEvent function to retrieve the address of the IAccessible interface for the object that generated the event. Once equipped with IAccessible interface, many interesting properties of the object can be determined using the methods supported by the interface such as IAccessible::get_accName, IAccessible::get_accRole, IAccessible::get_accState etc... Typical usage of this mechanism is as shown below.

VOID CALLBACK WinEventProc(HWINEVENTHOOK hWinEventHook,
        DWORD event,
        HWND hwnd,
        LONG idObject,
        LONG idChild,
        DWORD dwEventThread,
        DWORD dwmsEventTime)
{
  IAccessible *pAcc=NULL;

  VARIANT varChild; 
  varChild.vt = VT_EMPTY;

  HRESULT hr= AccessibleObjectFromEvent(hWnd, idObject, idChild, &pAcc, &varChild);
  if(hr!= S_OK || pAcc == NULL)
    return;
      
  RECT rect;
  if(S_OK != pAcc->accLocation(&rect.left, &rect.top, &rect.right, &rect.bottom, varChild))
    return;
            
  // rect now contains the object location in screen coordinates
  
  return;	// No need to free or release the pAcc pointer;
}

When the client application no longer wants to receive the event notifications, it can use the UnhookWinEvent function to unhook the callback function it has registered.  UnhookWinEvent takes the HWINEVENTHOOK handle returned by the SetWinEventHook as argument.

Caret Position Tracking

Tracking the caret position system-wide using Active Accessibility is pretty easy and straight forward. All that is needed is registering a callback function to be notified of  object creation, destroy, show, hide, location change, focus change events from all processes and threads, using the SetWinEventHook function. These events are represened by the constants EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY, EVENT_OBJECT_SHOW, EVENT_OBJECT_HIDE, EVENT_OBJECT_LOCATIONCHANGE, and EVENT_OBJECT_FOCUS respectively. Once registered, the callback function would get triggered whenever an object is created, destroyed, shown, hidden, moved or focused. Since the object of interest here is caret, all events that are not from caret object can be discarded. Whether an event is generated by caret object or not can be determined by using the method IAccessible::get_accRole. For caret object, the text returned by the method should be equal to caret. Once confirmed that the event is from caret, the IAccessible::accLocation method can be used to determine its screen location.

However, a simpler approach would be to process the events from all objects irrespective of who generated it and to use the GetForegroundWindow in conjunction with GetWindowThreadProcessId and GetGUIThreadInfo functions to actually decide the caret position information. The GUITHREADINFO structure contains hwndCaret and rcCaret members that gives the handle to the window that owns the caret and client-coordinates of the caret. If hwndCaret is NULL, then there is no active caret object available in the system at that moment. The code fragment that presents this mechanism is as shown below.

VOID CALLBACK WinEventProc(HWINEVENTHOOK hWinEventHook,
        DWORD event,
        HWND hwnd,
        LONG idObject,
        LONG idChild,
        DWORD dwEventThread,
        DWORD dwmsEventTime)
{
  DWORD OtherThreadID = GetWindowThreadProcessId(GetForegroundWindow(), NULL);

  GUITHREADINFO guiThreadInfo; 
  guiThreadInfo.cbSize = sizeof(GUITHREADINFO);
  
  if(GetGUIThreadInfo(OtherThreadID, &guiThreadInfo))
  {
    if(guiThreadInfo.hwndCaret != NULL)
    {
      POINT pt = { guiThreadInfo.rcCaret.left, guiThreadInfo.rcCaret.top };
      
      ClientToScreen(guiThreadInfo.hwndCaret, &pt);
      
      // pt now contains the caret position in screen coordinates
    }
  }
  return;
}

Conclusions

Microsoft Active Accessibility provides a mechanism that allows servers and the operating system to notify clients when an accessible object changes in some manner. The set of events supported includes object creation, destruction, focus change, location change, ...so on. A full list of events supported is available at: Event Constants. A server application uses NotifyWinEvent function to raise the events, and a client application should use the SetWinEventHook function to recieve the events. However, it should be noted that the client thread that calls the SetWinEventHook function should have a message loop in it in order to receive the events (in other words, it should be a GUIThread). Further, the system should have Active Accessibility RDK installed on it for this mechanism to work. Latest version can be downloaded from: http://msdn2.microsoft.com/en-us/library/aa286482.aspx.

References:

  1. List of header files and library files required
  2. Generating Appropriate Events
  3. Supported User Interface Elements Reference

By   

P.Gopalakrishna

Homepage     Other Articles