WinEvent Object

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.

Creating a WinEvent

Hook := WinEvent(WinTitle, WinText, ExcludeTitle, ExcludeText)

Parameters

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.

Return Value

Type: Object

This returns a new WinEvent, which is idle until Start() is called.

Criteria and Settings

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.

Event Slots

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.

SlotCalled when
OnActiveA 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.
OnNotActiveThe 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.
OnExistA 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.
OnNotExistA 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.
OnMoveA 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.
OnMinimizeA matching window is minimized.
OnRestoreA matching window is restored from the minimized state.
OnTitleChangeA matching window's title changes.
OnCaretMoveThe 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.

Assigning a Slot

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 Parameters

Callback(Hook, Hwnd, Time)
Hook

The WinEvent object.

Hwnd

The window's unique ID (HWND). It becomes the Last Found Window of the callback's thread.

Time

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.

Starting and Stopping

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

Hooks := WinEvent.Hooks

An Array of the running WinEvent hooks. See Listing Running Hooks.

Comparison with the WinEvent Library

This class follows the AutoHotkey WinEvent library's event semantics, but its shape differs:

AspectAutoHotkey WinEvent libraryKeysharp WinEvent
SubscribingWinEvent.Exist(Callback, WinTitle, Count, WinText, ExcludeTitle, ExcludeText) registers one event at onceConstruct 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 existExist reports one window which already matches, the one WinExist finds, at registrationOnExist reports only windows which start to match after Start(); check for existing windows explicitly
Limiting callsCount, decremented when the callback returns 0No Count, and the return value is ignored; call Stop()
PausingPause and IsPaused, per hook and for all hooksStop() and Start(), with WinEvent.Hooks to reach every hook
EventsAlso Show, Create, Close, MoveStart, MoveEnd and Maximize, with a PropertyCallback for Close and NotExistAlso 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 windowsCreate and Show turn hidden-window detection onEvery slot uses the DetectHiddenWindows setting captured at construction
Title changesActive is reported again whenever the active matching window's title changesOnActive reports a title change only when it brings the active window into a match; OnTitleChange reports every one
Keeping the script runningRegistering a hook calls Persistent, which stays in effect after the hooks are removedA running hook keeps the script running; once the last one stops, the script can exit

Remarks

Linux

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.

macOS

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

Examples

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
}