EventHook Object

class EventHook extends Object

The base class of every event subscription a script holds. A script never creates one directly; each kind of hook is its own subclass:

ClassCreated byInitial state
WinEventWinEvent(WinTitle, WinText, ExcludeTitle, ExcludeText)Idle until Start()
MonitorHookMonitor.OnChangeRunning
ClipboardHookClipboard.OnChangeRunning
Audio.DeviceHookAudio.OnDeviceChangeRunning
Clr.EventSubscriptionOnEvent on a CLR object or typeRunning
InputHookInputHook()Idle until Start()

This class is exported by the KS module.

Members

MemberDescription
InProgress1 while the hook is running, so that its callback is called when the event occurs; otherwise 0. Read-only.
EndReasonAn empty string while the hook is running; otherwise why it is not. Read-only.
Start()Begins a new run. It does nothing to a hook which is running.
Stop()Ends the run with EndReason "Stopped" and releases its native event source. It does nothing to a hook which is not running.

Some subclasses add members of their own, such as a WinEvent's event slots and a CLR subscription's EventName and Target; MonitorHook, ClipboardHook and Audio.DeviceHook add none.

A hook's callback is checked when it is given: a value which is not an object raises a TypeError, an object which cannot be called raises a MethodError, and a callback which cannot take the arguments the hook passes raises a ValueError.

InProgress describes the subscription, not what the platform delivers. An event source which installed but reports nothing — a window event the platform does not expose, for example — leaves the hook reading 1.

States

When a hook is not running, EndReason is one of these, the first reason its last run ended for:

EndReasonMeaning
"Stopped"Stop() was called, or the hook has not been started yet.
"Exit"The real thread which started the run ended, or the script is exiting.
"Failed"The native event source could not be installed, for example because a platform component it needs is missing. Monitor.OnChange, Clipboard.OnChange and Audio.OnDeviceChange return the hook already ended in this case, and Start() leaves a hook ended in the same way. OnEvent on a CLR object or type, and Start() on its subscription, raise an error instead.

InputHook reports its own end reasons instead, which include "Stopped" and "Failed" but not "Exit".

On Windows, for Notepad, a hook reads as not running until it is started:

#Import "Ks" { WinEvent }

Hook := WinEvent("ahk_exe notepad.exe")
Hook.OnActive := (Hook, Hwnd, Time) => ToolTip("Notepad is active")
MsgBox Hook.InProgress " [" Hook.EndReason "]"   ; 0 [Stopped]
Hook.Start()
MsgBox Hook.InProgress " [" Hook.EndReason "]"   ; 1 []
Hook.Stop()
MsgBox Hook.InProgress " [" Hook.EndReason "]"   ; 0 [Stopped]

Starting and Stopping

Start() begins a fresh run of a hook which is not running, including one which has been stopped:

An event which occurred while the hook was not running is not reported. The callbacks of a run are called on the real thread which started it: the one which called Start() or the subscribing method. An InputHook is the exception: each of its callbacks is called on the real thread which assigned it, or on the main thread once that thread has ended.

Stop() takes effect when it is called. An event which has already arrived but whose callback has not started is discarded, and a callback which is already running finishes. A discarded event is not reported by a later Start(). For how an InputHook differs, see its Stop method.

Handling an Event Once

Call Stop() as the first statement of the callback. On Windows, for Notepad:

#Import "Ks" { WinEvent }

Hook := WinEvent("ahk_exe notepad.exe")
Hook.OnActive := OnNotepad
Hook.Start()

OnNotepad(Hook, Hwnd, Time) {
    Hook.Stop()
    MsgBox "Notepad was activated."
}

A second event which arrived before the callback started is then discarded. The callback can still run twice if it waits before stopping — with Sleep, MsgBox, WinWait or anything else which lets another thread run — because the next event's callback can start during that wait. Guard such a callback with a static flag.

Lifetime

A run lasts until Stop(), until the real thread which started it ends, or until the script exits. Letting go of the hook object does not stop it, so the return value need not be kept; the class's Hooks list still reaches it.

An InputHook's Input does not end when a real thread ends.

Keeping the Script Running

Whether a running hook keeps the script running:

HookKeeps the script running
WinEventYes
Clipboard.OnChangeYes
InputHookYes
Monitor.OnChangeNo
Audio.OnDeviceChangeNo
OnEvent on a CLR object or typeNo

A script which responds only to hooks of the second kind calls Persistent to keep waiting for events. Such a hook started on a real thread does not keep that thread running either.

Listing Running Hooks

Hooks := WinEvent.Hooks
Hooks := Monitor.Hooks
Hooks := Clipboard.Hooks
Hooks := Audio.Hooks
Hooks := Clr.Hooks

Each class which creates hooks has a static Hooks property: an Array of that class's hooks which are running, in the order they were started, as they stood when it was read. A hook which is restarted moves to the end. It covers the hooks started by every real thread. Clr.Hooks mixes instance and static events, which a hook's Target tells apart. InputHook has no such list.

To stop every hook of a class, iterate Hooks:

#Import "Ks" { WinEvent }

for Hook in WinEvent.Hooks
    Hook.Stop()

WinEvent, Monitor.OnChange, Clipboard.OnChange, Audio.OnDeviceChange, Clr events, InputHook, Persistent