class WinEvent extends EventHook
Reports activation, appearance, disappearance, movement, minimize, restore, title-change and caret-movement events for the windows which match one set of criteria. Construct one with the criteria, assign a callback to each event slot it should report, then call Start.
This class is exported by the KS module and is modeled on the AutoHotkey WinEvent library; see Comparison with the WinEvent Library.
Hook := WinEvent(WinTitle, WinText, ExcludeTitle, ExcludeText)
Type: String, Integer or Object
If each of these is blank or omitted, every top-level window matches, subject to the captured DetectHiddenWindows setting; unlike WinExist, blank criteria do not mean the Last Found Window. Otherwise, specify for WinTitle a window title or other criteria, and optionally WinText, ExcludeTitle and ExcludeText, as for WinExist.
Type: Object
This returns a new WinEvent, which is idle until Start() is called.
The criteria are parsed when the hook is constructed. The current thread's DetectHiddenWindows, DetectHiddenText and SetTitleMatchMode settings are captured at the same time and used for every event, so set them before constructing the hook. The criteria can be read back from the read-only WinTitle, WinText, ExcludeTitle and ExcludeText properties.
Each event has a slot, a property which holds the callback for that event. A hook can use any number of its slots, which share its criteria.
| Slot | Called when |
|---|---|
OnActive | A matching window becomes the active (foreground) window. Every matching activation is reported, including one which moves from one matching window to another. With criteria, the active window is also reported when its title changes to match, so a window which became active before its title matched is caught. |
OnNotActive | The active window stops being a matching one: another window which does not match becomes active, or, with criteria, the reported window's title changes so that it no longer matches. Activation moving from one matching window to another is not reported. Hwnd is the matching window which was active last, which may be the one already active when Start() ran. |
OnExist | A window starts to match: it is created, shown or restored, or its title changes to match. A window is reported when it starts to match, and not again until it has stopped matching. Hidden windows are detected only if DetectHiddenWindows was on when the hook was constructed, so with it off, a window created hidden is reported when it is shown. |
OnNotExist | A window stops matching: it is destroyed; it is hidden or cloaked while the captured DetectHiddenWindows is off; or its title changes so that it no longer matches. Hwnd may already be destroyed, so a window function called with it can fail. |
OnMove | A matching window moves or resizes. On Windows and macOS, every such event is reported. On Linux, moves of one window which arrive faster than the runtime processes them are merged, so only the latest is reported. |
OnMinimize | A matching window is minimized. |
OnRestore | A matching window is restored from the minimized state. |
OnTitleChange | A matching window's title changes. |
OnCaretMove | The text caret (insertion point) moves inside a matching window: typing, clicking into text, arrow keys, scrolling a text view, or focus moving to another text field. Hwnd is the caret owner's top-level window, not the focused edit control, so ordinary window criteria apply. An event whose caret rectangle is the same as the last one reported is suppressed, and an event whose caret position cannot be resolved is dropped. |
Note: OnCaretMove reads the caret through the same accessibility APIs as CaretGetPos and has the same coverage. An application which draws its own caret without exposing it to accessibility — common in browser-based and Electron editors, and in some game and terminal interfaces — reports nothing on any platform.
Hook.OnActive := Callback Callback := Hook.OnActive
Assigning a function object sets the slot's callback, and assigning "" or unset clears it. Reading a slot returns its callback, or blank-unset if it has none.
Replacing a slot's callback takes effect at once; an event already queued still calls the callback it was queued for. Setting an empty slot or clearing one restarts a running hook on the same real thread: queued events are discarded and the matching and active windows are recorded afresh, so an event during the restart is not reported. Clearing the last slot stops the hook, as Stop() does. Setting an empty slot of a running hook repeats any permission check Start() makes (see Remarks); if it is refused, the assignment raises an error and the slot is unchanged.
Callback(Hook, Hwnd, Time)
The WinEvent object.
The window's unique ID (HWND). It becomes the Last Found Window of the callback's thread.
When the event occurred, in milliseconds on the same timebase as A_TickCount. On Windows it is the time the system reports for the event; on Linux and macOS it is the time the runtime received the event.
For OnMove and OnCaretMove, A_EventInfo holds a rectangle as an object with X, Y, Width and Height: the window's position and size when the event occurred, in the coordinates WinGetPos uses, or the caret's rectangle in screen coordinates. The other slots do not set A_EventInfo.
The callback's return value is ignored.
Start() and Stop() are described under EventHook. Start() does nothing while no slot is set.
Start() records the windows which already match, and the active window, without reporting them. Every slot therefore reports only what changes after Start(): OnExist is not called for a window which already matches, nor OnActive for a matching window which is already active. To handle those as well, check for them after Start(), using the same criteria. On Windows, the following reports every Notepad window, including those which already existed:
#Import "Ks" { WinEvent }
Hook := WinEvent("ahk_exe notepad.exe")
Hook.OnExist := NotepadAppeared
Hook.Start()
for Hwnd in WinGetList("ahk_exe notepad.exe") ; Windows which already existed.
NotepadAppeared(Hook, Hwnd, A_TickCount)
NotepadAppeared(Hook, Hwnd, Time) {
ToolTip "Notepad window " Hwnd
}
Checking after Start() rather than before means a window which appears in between is reported twice rather than missed. On some platforms the native source becomes live only shortly after Start() returns (on Windows, once the script's main thread next processes messages), and a window which appears after the check but before then is not reported. For OnActive, check WinActive in the same way.
A running WinEvent keeps the script running, so the script above needs no Persistent. See Lifetime.
Hooks := WinEvent.Hooks
An Array of the running WinEvent hooks. See Listing Running Hooks.
This class follows the AutoHotkey WinEvent library's event semantics, but its shape differs:
| Aspect | AutoHotkey WinEvent library | Keysharp WinEvent |
|---|---|---|
| Subscribing | WinEvent.Exist(Callback, WinTitle, Count, WinText, ExcludeTitle, ExcludeText) registers one event at once | Construct with the criteria, assign one or more slots, then call Start() |
| Callback parameters | (hWnd, eventObj, dwmsEventTime) | (Hook, Hwnd, Time), with the hook first; a callback written for the library needs its parameters reordered |
| Windows which already exist | Exist reports one window which already matches, the one WinExist finds, at registration | OnExist reports only windows which start to match after Start(); check for existing windows explicitly |
| Limiting calls | Count, decremented when the callback returns 0 | No Count, and the return value is ignored; call Stop() |
| Pausing | Pause and IsPaused, per hook and for all hooks | Stop() and Start(), with WinEvent.Hooks to reach every hook |
| Events | Also Show, Create, Close, MoveStart, MoveEnd and Maximize, with a PropertyCallback for Close and NotExist | Also OnTitleChange and OnCaretMove. OnExist and OnNotExist are the nearest equivalents of Create and Close; construct the hook with DetectHiddenWindows on to include hidden windows |
| Hidden windows | Create and Show turn hidden-window detection on | Every slot uses the DetectHiddenWindows setting captured at construction |
| Title changes | Active is reported again whenever the active matching window's title changes | OnActive reports a title change only when it brings the active window into a match; OnTitleChange reports every one |
| Keeping the script running | Registering a hook calls Persistent, which stays in effect after the hooks are removed | A running hook keeps the script running; once the last one stops, the script can exit |
A WinEvent requires keysharp-desktop and the WindowMonitoring grant, which Start() checks: if the grant is refused, Start() raises an error and the hook is left as it was. See Linux Platform Support.
X11: Window events are polled, every 250 ms by default.
Wayland: GNOME Shell and Cinnamon report window events as they occur. KWin and other compositors use polling, limited to the window properties they expose.
Polling can miss changes which occur between polls.
OnCaretMove works the same way under X11 and Wayland and requires accessibility (AT-SPI) to be enabled for the session.
A WinEvent requires Accessibility permission: if it is refused, Start() raises an error and the hook is left as it was.
EventHook, WinExist, WinGetList, WinWait, CaretGetPos, WinTitle
Shows the title of each window as it becomes active. With no criteria, every visible top-level window matches.
#Import "Ks" { WinEvent }
Hook := WinEvent()
Hook.OnActive := (Hook, Hwnd, Time) => ToolTip(WinGetTitle(Hwnd))
Hook.Start()
On Windows, reports Notepad windows opening, closing and moving through one hook. Hwnd is not used to read the title in OnNotExist, because the window may already be gone.
#Import "Ks" { WinEvent }
Notepad := WinEvent("ahk_exe notepad.exe")
Notepad.OnExist := (Hook, Hwnd, Time) => ToolTip("Opened: " WinGetTitle(Hwnd))
Notepad.OnNotExist := (Hook, Hwnd, Time) => ToolTip("Closed")
Notepad.OnMove := ShowPosition
Notepad.Start()
ShowPosition(Hook, Hwnd, Time) {
R := A_EventInfo
ToolTip R.X ", " R.Y " " R.Width " x " R.Height
}