// Disable the notification of WinEvents. // This function must be called once for each WinEvent to track. private static void StopListening(ref EventHookParams hp) { // remove the hook Misc.UnhookWinEvent(hp._winEventHook); hp._procHookHandle.Free(); }
// ------------------------------------------------------ // // Private Methods // // ------------------------------------------------------ // Enables the notification of WinEvents. // This function must be called once for each WinEvent to track. private static void StartListening(ref EventHookParams hp) { NativeMethods.WinEventProcDef proc = new NativeMethods.WinEventProcDef(WinEventProc); uint processId = hp._process; // The console window is special. It does not raise its own WinEvents. The CSRSS raises the // WinEvents for console windows. When calling SetWinEventHook use the CSRSS process id instead of the // console windows process id, so that we receive the WinEvents for the console windows. if (IsConsoleProcess((int)processId)) { try { processId = CSRSSProcessId; } catch (Exception e) { if (Misc.IsCriticalException(e)) { throw; } processId = hp._process; } } hp._procHookHandle = GCHandle.Alloc(proc); hp._winEventHook = Misc.SetWinEventHook(hp._evtId, hp._evtId, IntPtr.Zero, proc, processId, 0, NativeMethods.WINEVENT_OUTOFCONTEXT); }
// Update the Array of hwnd requesting notifications // eFlag - Add or Remove // hwnd // raiseEvents - function to call to create a raw element // aEvtIdProp - Array of Tupples WinEvent and Automation properties // cProps - Number of valid props in the array private static void BuildEventsList(EventFlag eFlag, IntPtr hwnd, ProxyRaiseEvents raiseEvents, EvtIdProperty[] aEvtIdProp, int cProps) { // All operations in the list of events and windows handle must be atomic lock (_ahp) { for (int i = 0; i < cProps; i++) { EvtIdProperty evtIdProp = aEvtIdProp[i]; // Map a property into a WinEventHookProperty int evt = Array.BinarySearch(_eventIdToIndex, evtIdProp._evtId); // add the window to the list if (evt >= 0) { EventHookParams hookParams = null; uint processId; if (hwnd == IntPtr.Zero) { // if its a global event use this well known key to the hash processId = _globalEventKey; } else { if (Misc.GetWindowThreadProcessId(hwnd, out processId) == 0) { processId = _globalEventKey; } } // If never seens this EventId before. Create the notification object if (_ahp[evt] == null) { // create the hash table the key is the process id _ahp[evt] = new Hashtable(10); } // Find the EventHookParams. // _ahp is an array of Hashtables where each Hashtable corrasponds to one event. // Get the correct Hashtable using the index evt. Then lookup the EventHookParams // in the hash table, using the process id. hookParams = (EventHookParams)_ahp[evt][processId]; // If there is not an entry for the event for the specified process then create one. if (hookParams == null) { hookParams = new EventHookParams(); hookParams._process = processId; _ahp[evt].Add(processId, hookParams); } ArrayList eventCreateParams = hookParams._alHwnd; if (eFlag == EventFlag.Add) { if (eventCreateParams == null) { // empty array, create the hwnd arraylist hookParams._evtId = evtIdProp._evtId; eventCreateParams = hookParams._alHwnd = new ArrayList(16); } // Check if the event for that window already exist. // Discard it as no dups are allowed for (int index = eventCreateParams.Count - 1; index >= 0; index--) { EventCreateParams ecp = (EventCreateParams)eventCreateParams[index]; // Code below will discard duplicates: // Proxy cannot subscribe same hwnd to the same event more than once // However proxy can be globaly registered to be always notified of some event, in order to // do this proxy will send IntPtr.Zero as hwnd. Please notice that a given Proxy can be globaly registered // to some EVENT_XXX only once. This will be ensured via delegate comparison. if ((hwnd == IntPtr.Zero || ecp._hwnd == hwnd) && ecp._idProp == evtIdProp._idProp && ecp._raiseEventFromRawElement == raiseEvents) { return; } } // Set the WinEventHook if first time around if (eventCreateParams.Count == 0) { _callbackQueue.PostSyncWorkItem(new QueueItem.WinEventItem(ref hookParams, _startDelegate)); } // add the event into the list // Called after the Post does not matter because of the lock eventCreateParams.Add(new EventCreateParams(hwnd, evtIdProp._idProp, raiseEvents)); } else { if (eventCreateParams == null) { return; } // Remove a notification // Go through the list of window to find the one for (int index = eventCreateParams.Count - 1; index >= 0; index--) { EventCreateParams ecp = (EventCreateParams)eventCreateParams[index]; // Detect if caller should be removed from notification list bool remove = false; if (raiseEvents == null) { // Not a global wide events remove = (ecp._hwnd == hwnd && ecp._idProp == evtIdProp._idProp); } else { // Global events Debug.Assert(hwnd == IntPtr.Zero, @"BuildEventsList: event is global but hwnd is not null"); remove = (ecp._hwnd == hwnd && ecp._raiseEventFromRawElement == raiseEvents); } if (remove) { eventCreateParams.RemoveAt(index); // if empty then stop listening for this event arg if (eventCreateParams.Count == 0) { _callbackQueue.PostSyncWorkItem(new QueueItem.WinEventItem(ref hookParams, _stopDelegate)); _ahp[evt].Remove(processId); } break; } } } } } } }
// Callback function for WinEvents // // Notifications are processed that way: // Convert an EventID into an index for the above array. // Sequential traverse of the list of windows handles for a give EventID // to find a match. // Call the delegate associated with the hwnd to create a raw element. // Call the automation code to queue a new notification for the client. private static void WinEventProc(int winEventHook, int eventId, IntPtr hwnd, int idObject, int idChild, int eventThread, uint eventTime) { if (hwnd == IntPtr.Zero) { // filter out non-hwnd events - eg. listening for hide/show also gets us mouse pointer (OBJID_CURSOR) // hide/show events (eg. when mouse is hidden as text is being entered) that have NULL hwnd... return; } try { int evt = Array.BinarySearch(_eventIdToIndex, eventId); if (evt < 0) { return; // negative means this event is unknown so ignore it } // All operations in the list of events and windows handle must be atomic lock (_ahp) { EventHookParams hookParams = null; // Don't use the Misc.GetWindowThreadProcessId() helper since that throws; some events we want even // though the hwnd is no longer valid (e.g. menu item events). uint processId; // Disabling the PreSharp error since GetWindowThreadProcessId doesn't use SetLastError(). #pragma warning suppress 6523 if (UnsafeNativeMethods.GetWindowThreadProcessId(hwnd, out processId) != 0) { // Find the EventHookParams. // _ahp is an array of Hashtables where each Hashtable corrasponds to one event. // Get the correct Hashtable using the index evt. Then lookup the EventHookParams // in the hash table, using the process id. hookParams = (EventHookParams)_ahp[evt][processId]; } // Sanity check if (hookParams != null && hookParams._alHwnd != null) { ArrayList eventCreateParams = hookParams._alHwnd; // Loop for all the registered hwnd listeners for this event for (int index = eventCreateParams.Count - 1; index >= 0; index--) { EventCreateParams ecp = (EventCreateParams)eventCreateParams[index]; // if hwnd of the event matches the registered hwnd -OR- this is a global event (all hwnds) // -AND- the hwnd is still valid have the proxies raise appropriate events. if (ecp._hwnd == hwnd || ecp._hwnd == IntPtr.Zero) { // If this event isn't on a menu element that has just been invoked, throw away the event if the hwnd is gone if (!((idObject == NativeMethods.OBJID_MENU || idObject == NativeMethods.OBJID_SYSMENU) && eventId == NativeMethods.EventObjectInvoke) && !UnsafeNativeMethods.IsWindow(hwnd)) { continue; } try { // Call the proxy code to create a raw element. null is valid as a result // The proxy must fill in the parameters for the AutomationPropertyChangedEventArgs ecp._raiseEventFromRawElement(hwnd, eventId, ecp._idProp, idObject, idChild); } catch (ElementNotAvailableException) { // the element has gone away from the time the event happened and now // So continue the loop and allow the other proxies a chance to raise the event. continue; } catch (Exception e) { // If we get here there is a problem in a proxy that needs fixing. Debug.Assert(false, "Exception raising event " + eventId + " for prop " + ecp._idProp + " on hwnd " + hwnd + "\n" + e.Message); if (Misc.IsCriticalException(e)) { throw; } // Do not break the loop for one mis-behaving proxy. continue; } } } } // handle global events. These are usually for things that do not yet exist like show events // where the hwnd is not there until it is shown. So we need to raise these event all the time. // Office command bars use this. hookParams = (EventHookParams)_ahp[evt][_globalEventKey]; if (hookParams != null && hookParams._alHwnd != null) { ArrayList eventCreateParams = hookParams._alHwnd; // Loop for all the registered hwnd listeners for this event for (int index = eventCreateParams.Count - 1; index >= 0; index--) { EventCreateParams ecp = (EventCreateParams)eventCreateParams[index]; // We have global event if ((ecp._hwnd == IntPtr.Zero)) { try { // Call the proxy code to create a raw element. null is valid as a result // The proxy must fill in the parameters for the AutomationPropertyChangedEventArgs ecp._raiseEventFromRawElement(hwnd, eventId, ecp._idProp, idObject, idChild); } catch (ElementNotAvailableException) { // the element has gone away from the time the event happened and now // So continue the loop and allow the other proxies a chance to raise the event. continue; } catch (Exception e) { // If we get here there is a problem in a proxy that needs fixing. Debug.Assert(false, "Exception raising event " + eventId + " for prop " + ecp._idProp + " on hwnd " + hwnd + "\n" + e.Message); if (Misc.IsCriticalException(e)) { throw; } // Do not break the loop for one mis-behaving proxy. continue; } } } } } } catch (Exception e) { if (Misc.IsCriticalException(e)) { throw; } // ignore non-critical errors from external code } }
// Update the Array of hwnd requesting notifications // eFlag - Add or Remove // hwnd // raiseEvents - function to call to create a raw element // aEvtIdProp - Array of Tupples WinEvent and Automation properties // cProps - Number of valid props in the array private static void BuildEventsList (EventFlag eFlag, IntPtr hwnd, ProxyRaiseEvents raiseEvents, EvtIdProperty[] aEvtIdProp, int cProps) { // All operations in the list of events and windows handle must be atomic lock (_ahp) { for (int i = 0; i < cProps; i++) { EvtIdProperty evtIdProp = aEvtIdProp[i]; // Map a property into a WinEventHookProperty int evt = Array.BinarySearch (_eventIdToIndex, evtIdProp._evtId); // add the window to the list if (evt >= 0) { EventHookParams hookParams = null; uint processId; if (hwnd == IntPtr.Zero) { // if its a global event use this well known key to the hash processId = _globalEventKey; } else { if (Misc.GetWindowThreadProcessId(hwnd, out processId) == 0) { processId = _globalEventKey; } } // If never seens this EventId before. Create the notification object if (_ahp[evt] == null) { // create the hash table the key is the process id _ahp[evt] = new Hashtable(10); } // Find the EventHookParams. // _ahp is an array of Hashtables where each Hashtable corrasponds to one event. // Get the correct Hashtable using the index evt. Then lookup the EventHookParams // in the hash table, using the process id. hookParams = (EventHookParams)_ahp[evt][processId]; // If there is not an entry for the event for the specified process then create one. if (hookParams == null) { hookParams = new EventHookParams(); hookParams._process = processId; _ahp[evt].Add(processId, hookParams); } ArrayList eventCreateParams = hookParams._alHwnd; if (eFlag == EventFlag.Add) { if (eventCreateParams == null) { // empty array, create the hwnd arraylist hookParams._evtId = evtIdProp._evtId; eventCreateParams = hookParams._alHwnd = new ArrayList (16); } // Check if the event for that window already exist. // Discard it as no dups are allowed for (int index = eventCreateParams.Count - 1; index >= 0; index--) { EventCreateParams ecp = (EventCreateParams)eventCreateParams[index]; // Code below will discard duplicates: // Proxy cannot subscribe same hwnd to the same event more than once // However proxy can be globaly registered to be always notified of some event, in order to // do this proxy will send IntPtr.Zero as hwnd. Please notice that a given Proxy can be globaly registered // to some EVENT_XXX only once. This will be ensured via delegate comparison. if ( (hwnd == IntPtr.Zero || ecp._hwnd == hwnd) && ecp._idProp == evtIdProp._idProp && ecp._raiseEventFromRawElement == raiseEvents) { return; } } // Set the WinEventHook if first time around if (eventCreateParams.Count == 0) { _callbackQueue.PostSyncWorkItem (new QueueItem.WinEventItem (ref hookParams, _startDelegate)); } // add the event into the list // Called after the Post does not matter because of the lock eventCreateParams.Add (new EventCreateParams (hwnd, evtIdProp._idProp, raiseEvents)); } else { if ( eventCreateParams == null ) return; // Remove a notification // Go through the list of window to find the one for (int index = eventCreateParams.Count - 1; index >= 0; index--) { EventCreateParams ecp = (EventCreateParams)eventCreateParams[index]; // Detect if caller should be removed from notification list bool remove = false; if (raiseEvents == null) { // Not a global wide events remove = (ecp._hwnd == hwnd && ecp._idProp == evtIdProp._idProp); } else { // Global events Debug.Assert (hwnd == IntPtr.Zero, @"BuildEventsList: event is global but hwnd is not null"); remove = (ecp._hwnd == hwnd && ecp._raiseEventFromRawElement == raiseEvents); } if (remove) { eventCreateParams.RemoveAt (index); // if empty then stop listening for this event arg if (eventCreateParams.Count == 0) { _callbackQueue.PostSyncWorkItem (new QueueItem.WinEventItem (ref hookParams, _stopDelegate)); _ahp[evt].Remove(processId); } break; } } } } } } }
// Disable the notification of WinEvents. // This function must be called once for each WinEvent to track. private static void StopListening (ref EventHookParams hp) { // remove the hook Misc.UnhookWinEvent(hp._winEventHook); hp._procHookHandle.Free (); }
// ------------------------------------------------------ // // Private Methods // // ------------------------------------------------------ // Enables the notification of WinEvents. // This function must be called once for each WinEvent to track. private static void StartListening (ref EventHookParams hp) { NativeMethods.WinEventProcDef proc = new NativeMethods.WinEventProcDef (WinEventProc); uint processId = hp._process; // The console window is special. It does not raise its own WinEvents. The CSRSS raises the // WinEvents for console windows. When calling SetWinEventHook use the CSRSS process id instead of the // console windows process id, so that we receive the WinEvents for the console windows. if (IsConsoleProcess((int)processId)) { try { processId = CSRSSProcessId; } catch (Exception e) { if (Misc.IsCriticalException(e)) { throw; } processId = hp._process; } } hp._procHookHandle = GCHandle.Alloc (proc); hp._winEventHook = Misc.SetWinEventHook(hp._evtId, hp._evtId, IntPtr.Zero, proc, processId, 0, NativeMethods.WINEVENT_OUTOFCONTEXT); }