-
Notifications
You must be signed in to change notification settings - Fork 72
/
class_WinEventHook.ahk
241 lines (216 loc) · 9.94 KB
/
class_WinEventHook.ahk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
; Usage example:
/*
#NoEnv
#SingleInstance force
#Warn
#Persistent
ControlGet, hEdit1, Hwnd,, Edit1, ahk_class Notepad
if not (hEdit1)
ExitApp
WinEventHook.setForId(hEdit1, 0x800E, "f")
; WinEventHook.setForId(hEdit1, 0x800E, "f", 100) ; '100' means: "treat events separated by a period of less than 100ms as an influx of calls and buffer it as such"
return
!x::
count := WinEventHook.unsetById(hEdit1)
MsgBox % "ErrorLevel = " . ErrorLevel . "`nHook instance unset count = " . count
return
f(_params*) { ; _hWinEventHook, _event, _hwnd, _idObject, _idChild, _dwEventThread, _dwmsEventTime
static OBJID_CLIENT := 0xFFFFFFFC ; https://docs.microsoft.com/fr-fr/windows/desktop/WinAuto/object-identifiers#OBJID_CLIENT
local _idObject := _params.4
if (_idObject = OBJID_CLIENT) ; actually, both the (edit child control) window's horizontal and the vertical scroll bars (OBJID_VSCROLL/OBJID_HSCROLL) could otherwise update the tooltip below
ToolTip % Format("hWinEventHook = {}`nevent = {}`nhwnd = {}`nidObject = {}`nidChild = {}`ndwEventThread = {}`ndwmsEventTime = {}", _params*)
}
*/
; ========================================================
Class WinEventHook {
/*
Namespace: WinEventHook
Environment: Windows 8.1 64 bit - Autohotkey v1.1.30.01 32-bit Unicode
Credits:
Fanatic Guru for WinHook - https://www.autohotkey.com/boards/viewtopic.php?t=59149&p=254920
Related works:
[LIB] EWinHook - SetWinEventHook implementation by cyruz - https://www.autohotkey.com/boards/viewtopic.php?p=176444
Event Library by KuroiLight/klomb - https://www.autohotkey.com/boards/viewtopic.php?t=42657
*/
static _map := {}
static _instances := {} ; _map and _instances are combined with a design conceived to allow cross-referencing while keeping track of instances
static HANDLE_IDPROCESS := "" ; use internally to 'capture' events if a hWnd-filtering is set @._hookFunction. In any event, should not be 0 since the handle to the window that generates the event can be NULL - https://docs.microsoft.com/fr-fr/windows/desktop/api/winuser/nc-winuser-wineventproc#parameters
static _callback := RegisterCallback("WinEventHook._hookFunction") ; we only need to call registercallback ones, we use a static variable - https://www.autohotkey.com/boards/viewtopic.php?t=59149&p=254920#profile249257
Class _TimedCallback { ; nested class used internally to buffer an influx of calls as such, as influx
static instances := {} ; keep track of instances
_delegate := Func("Max") ; dummy variadic delegate
_range := "Off"
__New(_hWinEventHook) {
return WinEventHook._TimedCallback.instances[ (this.ID:=_hWinEventHook) ] := this
}
__Call(_callee, _params*) { ; triggered by %this%(_hWinEventHook, _event, _hwnd, _idObject, _idChild, _dwEventThread, _dwmsEventTime)
if (_callee = "") { ; for %fn%() or fn.() - https://www.autohotkey.com/docs/objects/Functor.htm#User-Defined
this.params := _params
SetTimer, % this, % -this._range
}
}
__Delete() {
SetTimer, % this, Off
SetTimer, % this, Delete
this._delegate := "" ; release the user function object
}
call() { ; triggered by this.call(_hWinEventHook, _event, _hwnd, _idObject, _idChild, _dwEventThread, _dwmsEventTime) yet if the object is being used by SetTimer, only the call method is needed - https://www.autohotkey.com/docs/objects/Functor.htm#User-Defined
_fn := this._delegate
return %_fn%(this.params*)
}
delegate {
set {
if not (IsObject(value) || value:=Func(value)) {
throw Exception("Invalid callback.")
return
}
return this._delegate := value
}
get {
return this._delegate
}
}
range {
set {
static USER_RANGE_MAXIMUM := 1000
if value not between -1 and %USER_RANGE_MAXIMUM%
{
throw Exception("The property could not be set to a value lower or greater than the ranged value.",, (value = "") ? """""" : value)
return
}
return this._range := (value = -1) ? "Off" : value
}
get {
return (this._range = "Off") ? -1 : this._range
}
}
}
__New(_idProcess, _event, _delegate, _hwnd, _offset:=-1, _range:=0) { ; designed to be used internally
static _junkTimedCallback := (new WinEventHook._TimedCallback(0)) ; junk 'timed callback' used to check input parameters against the prerequisites
static _junkFunc := Func("Func")
local _hWinEventHook, _exception, _cbObject
try { ; check input parameters against the prerequisites
_junkTimedCallback.delegate := _delegate
_junkTimedCallback.range := _range
} catch _exception {
throw Exception(_exception.message, _offset, _exception.extra)
return
} finally _junkTimedCallback.delegate := _junkFunc ; in any event, release the user function object
if not (_hWinEventHook:=WinEventHook._map[_idProcess, _event]) { ; if the hook instance is not yet listed...
_hWinEventHook := DllCall("SetWinEventHook"
, "UInt", _event
, "UInt", _event
, "Ptr", 0x0
, "Ptr", WinEventHook._callback
, "UInt", _idProcess
, "UInt", 0x0
, "UInt", 0x0) ; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-setwineventhook
if not (_hWinEventHook) {
throw Exception("Could not set the event hook function.", _offset)
return
}
WinEventHook._map[_idProcess, _event] := _hWinEventHook
WinEventHook._instances[_hWinEventHook] := {idProcess: _idProcess, event: _event} ; 'cross-referencing'
}
if (_range) { ; if influxes of calls need to be buffer as such, as influxes...
_cbObject := new WinEventHook._TimedCallback(_hWinEventHook)
_cbObject.delegate := _delegate
_cbObject.range := _range
WinEventHook._instances[_hWinEventHook].sieve[_hwnd] := _cbObject
} else WinEventHook._instances[_hWinEventHook].sieve[_hwnd] := _delegate
return _hWinEventHook
}
setForProcess(_idProcess, _event, _delegate, _p*) {
if (_idProcess <> 0) {
Process, Exist, % (_idProcess+0 <> "") ? _idProcess : 0 ; otherwise, if the PIDOrName parameter is blank, the script's own PID is retrieved - https://www.autohotkey.com/docs/commands/Process.htm#Exist
if not (ErrorLevel) {
throw Exception("The process could not be found.", -1, (_idProcess = "") ? """""" : _idProcess)
return
}
} else return new WinEventHook(_idProcess, _event, _delegate, WinEventHook.HANDLE_IDPROCESS, -2, _p*)
}
setForId(_hwnd, _event, _delegate, _p*) {
local _idProcess := ""
DllCall("User32.dll\GetWindowThreadProcessId", "Ptr", _hwnd, "UIntP", _idProcess, "UInt")
if not (_idProcess) {
throw Exception("Could not retrieve the identifier of the process that created the window.", -1)
return
} else return new WinEventHook(_idProcess, _event, _delegate, Format("{1:u}", _hwnd), -2, _p*)
}
unset(_hWinEventHook) {
local _unsetCount, _instance
_unsetCount := (_instance:=WinEventHook._instances[_hWinEventHook]) ? WinEventHook._unset(_instance.idProcess, _instance.event) : 0
return _unsetCount
}
unsetByProcess(_idProcess) {
local _event, _hWinEventHook, _unsetCount
for _event, _hWinEventHook in ObjClone(WinEventHook._map[_idProcess]), _unsetCount := 0
ErrorLevel += !_unsetCount += WinEventHook._unset(_idProcess, _event)
return _unsetCount
}
unsetById(_hwnd) {
local _h, _hWinEventHook, _obj, _unsetCount, _sieve
if not (_h:=Format("{1:u}", _hwnd)) { ; i.e. also if _hwnd = WinEventHook.HANDLE_IDPROCESS
throw Exception("Invalid handle.", -1, (_hwnd = "") ? """""" : _hwnd)
return
}
_hwnd := _h
for _hWinEventHook, _obj in ObjClone(WinEventHook._instances), _unsetCount := 0 {
if (ObjHasKey(_obj.sieve, _hwnd)) {
_sieve := WinEventHook._instances[_hWinEventHook].sieve, ObjDelete(_sieve, _hwnd)
if not (ObjCount(_sieve)) {
ErrorLevel += !_unsetCount += WinEventHook._unset(_obj.idProcess, _obj.event)
}
}
}
return _unsetCount
}
unsetByEvent(_eventNum) {
local _idProcess, _obj, _unsetCount, _event
for _idProcess, _obj in ObjClone(WinEventHook._map), _unsetCount := 0 {
for _event in _obj {
if (_event = _eventNum) {
ErrorLevel += !_unsetCount += WinEventHook._unset(_idProcess, _event)
}
}
}
return _unsetCount
}
unsetAll() {
local _idProcess, _obj, _unsetCount, _event
for _idProcess, _obj in ObjClone(WinEventHook._map), _unsetCount := 0
for _event in _obj
ErrorLevel += !_unsetCount += WinEventHook._unset(_idProcess, _event)
return _unsetCount
}
_unset(_idProcess, _event) {
local _hWinEventHook, _r
_hWinEventHook := ObjDelete(WinEventHook._map[_idProcess], _event)
(!ObjCount(WinEventHook._map[_idProcess]) && ObjDelete(WinEventHook._map, _idProcess))
_r := DllCall("UnhookWinEvent", "Ptr", _hWinEventHook) ; https://docs.microsoft.com/fr-fr/windows/desktop/api/winuser/nf-winuser-unhookwinevent
ObjDelete(WinEventHook._instances, _hWinEventHook)
if (ObjHasKey(WinEventHook._TimedCallback.instances, _hWinEventHook)) ; if a '_TimedCallback' instance is associated with this hook instance...
ObjDelete(WinEventHook._TimedCallback.instances, _hWinEventHook) ; release the function object (actually, the instance's own __Delete meta-function will be called)
return _r
}
_dispose(_params*) {
static _ := OnExit(ObjBindMethod(WinEventHook, "_dispose"))
static _preventExit := 1
return not _preventExit, WinEventHook.unsetAll()
}
_hookFunction(_event, _hwnd, _idObject, _idChild, _dwEventThread, _dwmsEventTime) {
local _hWinEventHook, _enum, _handle, _delegate
(_enum:=ObjNewEnum(WinEventHook._instances[ _hWinEventHook:=this ].sieve)).next(_handle, _delegate)
if (_handle <> WinEventHook.HANDLE_IDPROCESS) { ; unsigned integers (i.e. hWnd, if any) necessarily appear before "" (HANDLE_IDPROCESS) in an enumeration
Loop {
if (_handle = _hwnd) {
%_delegate%(_hWinEventHook, _event, _hwnd, _idObject, _idChild, _dwEventThread, _dwmsEventTime)
return ; 'capture' the event: do not go all the way up on the process
}
} Until not (_enum[_handle, _delegate])
if (_handle <> WinEventHook.HANDLE_IDPROCESS)
return
}
%_delegate%(_hWinEventHook, _event, _hwnd, _idObject, _idChild, _dwEventThread, _dwmsEventTime)
}
}