These type hooks can be set manually with DllCall("RegisterShellHookWindow"...) and DllCall("SetWinEventHook"...) but it is rather complicated.
[Class] WinHook
Code: Select all
; [Class] WinHook
; Fanatic Guru
; 2019 02 18 v2
;
; Class to set hooks of windows or processes
;
;{============================
;
; Class (Nested): WinHook.Shell
;
; Method:
; Add(Func, wTitle:="", wClass:="", wExe:="", Event:=0)
;
; Desc: Add Shell Hook
;
; Parameters:
; 1) {Func} Function name or Function object to call on event
; 2) {wTitle} window Title to watch for event (default = "", all windows)
; 3) {wClass} window Class to watch for event (default = "", all windows)
; 4) {wExe} window Exe to watch for event (default = "", all windows)
; 5) {Event} Event (default = 0, all events)
;
; Returns: {Index} index to hook that can be used to Remove hook
;
; Shell Hook Events:
; 1 = HSHELL_WINDOWCREATED
; 2 = HSHELL_WINDOWDESTROYED
; 3 = HSHELL_ACTIVATESHELLWINDOW
; 4 = HSHELL_WINDOWACTIVATED
; 5 = HSHELL_GETMINRECT
; 6 = HSHELL_REDRAW
; 7 = HSHELL_TASKMAN
; 8 = HSHELL_LANGUAGE
; 9 = HSHELL_SYSMENU
; 10 = HSHELL_ENDTASK
; 11 = HSHELL_ACCESSIBILITYSTATE
; 12 = HSHELL_APPCOMMAND
; 13 = HSHELL_WINDOWREPLACED
; 14 = HSHELL_WINDOWREPLACING
; 32768 = 0x8000 = HSHELL_HIGHBIT
; 32772 = 0x8000 + 4 = 0x8004 = HSHELL_RUDEAPPACTIVATED (HSHELL_HIGHBIT + HSHELL_WINDOWACTIVATED)
; 32774 = 0x8000 + 6 = 0x8006 = HSHELL_FLASH (HSHELL_HIGHBIT + HSHELL_REDRAW)
;
; Note: ObjBindMethod(obj, Method) can be used to create a function object to a class method
; WinHook.Shell.Add(ObjBindMethod(TestClass.TestNestedClass, "MethodName"), wTitle, wClass, wExe, Event)
;
; ----------
;
; Desc: Function Called on Event
; FuncOrMethod(Win_Hwnd, Win_Title, Win_Class, Win_Exe, Win_Event)
;
; Parameters:
; 1) {Win_Hwnd} window handle ID of window with event
; 2) {Win_Title} window Title of window with event
; 3) {Win_Class} window Class of window with event
; 4) {Win_Exe} window Exe of window with event
; 5) {Win_Event} window Event
;
; Note: FuncOrMethod will be called with DetectHiddenWindows On.
;
; --------------------
;
; Method: Report(ByRef Object)
;
; Desc: Report Shell Hooks
;
; Returns: string report
; ByRef Object[Index].{Func, Title:, Class, Exe, Event}
;
; --------------------
;
; Method: Remove(Index)
; Method: Deregister()
;
;{============================
;
; Class (Nested): WinHook.Event
;
; Method:
; Add(eventMin, eventMax, eventProc, idProcess, WinTitle := "")
;
; Desc: Add Event Hook
;
; Parameters:
; 1) {eventMin} lowest Event value handled by the hook function
; 2) {eventMax} highest event value handled by the hook function
; 3) {eventProc} event hook function, call be function name or function object
; 4) {idProcess} ID of the process from which the hook function receives events (default = 0, all processes)
; 5) {WinTitle} WinTitle to identify which windows to operate on, (default = "", all windows)
;
; Returns: {hWinEventHook} handle to hook that can be used to unhook
;
; Event Hook Events:
; 0x8012 = EVENT_OBJECT_ACCELERATORCHANGE
; 0x8017 = EVENT_OBJECT_CLOAKED
; 0x8015 = EVENT_OBJECT_CONTENTSCROLLED
; 0x8000 = EVENT_OBJECT_CREATE
; 0x8011 = EVENT_OBJECT_DEFACTIONCHANGE
; 0x800D = EVENT_OBJECT_DESCRIPTIONCHANGE
; 0x8001 = EVENT_OBJECT_DESTROY
; 0x8021 = EVENT_OBJECT_DRAGSTART
; 0x8022 = EVENT_OBJECT_DRAGCANCEL
; 0x8023 = EVENT_OBJECT_DRAGCOMPLETE
; 0x8024 = EVENT_OBJECT_DRAGENTER
; 0x8025 = EVENT_OBJECT_DRAGLEAVE
; 0x8026 = EVENT_OBJECT_DRAGDROPPED
; 0x80FF = EVENT_OBJECT_END
; 0x8005 = EVENT_OBJECT_FOCUS
; 0x8010 = EVENT_OBJECT_HELPCHANGE
; 0x8003 = EVENT_OBJECT_HIDE
; 0x8020 = EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED
; 0x8028 = EVENT_OBJECT_IME_HIDE
; 0x8027 = EVENT_OBJECT_IME_SHOW
; 0x8029 = EVENT_OBJECT_IME_CHANGE
; 0x8013 = EVENT_OBJECT_INVOKED
; 0x8019 = EVENT_OBJECT_LIVEREGIONCHANGED
; 0x800B = EVENT_OBJECT_LOCATIONCHANGE
; 0x800C = EVENT_OBJECT_NAMECHANGE
; 0x800F = EVENT_OBJECT_PARENTCHANGE
; 0x8004 = EVENT_OBJECT_REORDER
; 0x8006 = EVENT_OBJECT_SELECTION
; 0x8007 = EVENT_OBJECT_SELECTIONADD
; 0x8008 = EVENT_OBJECT_SELECTIONREMOVE
; 0x8009 = EVENT_OBJECT_SELECTIONWITHIN
; 0x8002 = EVENT_OBJECT_SHOW
; 0x800A = EVENT_OBJECT_STATECHANGE
; 0x8030 = EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED
; 0x8014 = EVENT_OBJECT_TEXTSELECTIONCHANGED
; 0x8018 = EVENT_OBJECT_UNCLOAKED
; 0x800E = EVENT_OBJECT_VALUECHANGE
; 0x0002 = EVENT_SYSTEM_ALERT
; 0x8016 = EVENT_SYSTEM_ARRANGMENTPREVIEW
; 0x0009 = EVENT_SYSTEM_CAPTUREEND
; 0x0008 = EVENT_SYSTEM_CAPTURESTART
; 0x000D = EVENT_SYSTEM_CONTEXTHELPEND
; 0x000C = EVENT_SYSTEM_CONTEXTHELPSTART
; 0x0020 = EVENT_SYSTEM_DESKTOPSWITCH
; 0x0011 = EVENT_SYSTEM_DIALOGEND
; 0x0010 = EVENT_SYSTEM_DIALOGSTART
; 0x000F = EVENT_SYSTEM_DRAGDROPEND
; 0x000E = EVENT_SYSTEM_DRAGDROPSTART
; 0x00FF = EVENT_SYSTEM_END
; 0x0003 = EVENT_SYSTEM_FOREGROUND
; 0x0007 = EVENT_SYSTEM_MENUPOPUPEND
; 0x0006 = EVENT_SYSTEM_MENUPOPUPSTART
; 0x0005 = EVENT_SYSTEM_MENUEND
; 0x0004 = EVENT_SYSTEM_MENUSTART
; 0x0017 = EVENT_SYSTEM_MINIMIZEEND
; 0x0016 = EVENT_SYSTEM_MINIMIZESTART
; 0x000B = EVENT_SYSTEM_MOVESIZEEND
; 0x000A = EVENT_SYSTEM_MOVESIZESTART
; 0x0013 = EVENT_SYSTEM_SCROLLINGEND
; 0x0012 = EVENT_SYSTEM_SCROLLINGSTART
; 0x0001 = EVENT_SYSTEM_SOUND
; 0x0015 = EVENT_SYSTEM_SWITCHEND
; 0x0014 = EVENT_SYSTEM_SWITCHSTART
;
; Note: ObjBindMethod(obj, Method) can be used to create a function object to a class method
; WinHook.Event.Add((eventMin, eventMax, ObjBindMethod(TestClass.TestNestedClass, "MethodName"), idProcess, WinTitle := "")
;
; ----------
;
; Desc: Function Called on Event
; FuncOrMethod(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime)
;
; Parameters:
; 1) {hWinEventHook} Handle to an event hook instance.
; 2) {event} Event that occurred. This value is one of the event constants
; 3) {hwnd} Handle to the window that generates the event.
; 4) {idObject} Identifies the object that is associated with the event.
; 5) {idChild} Child ID if the event was triggered by a child element.
; 6) {dwEventThread} Identifies the thread that generated the event.
; 7) {dwmsEventTime} Specifies the time, in milliseconds, that the event was generated.
;
; Note: FuncOrMethod will be called with DetectHiddenWindows On.
;
; --------------------
;
; Method: Report(ByRef Object)
;
; Returns: string report
; ByRef Object[hWinEventHook].{eventMin, eventMax, eventProc, idProcess, WinTitle}
;
; --------------------
;
; Method: UnHook(hWinEventHook)
; Method: UnHookAll()
;
;{============================
class WinHook
{
class Shell
{
Add(Func, wTitle:="", wClass:="", wExe:="", Event:=0)
{
if !WinHook.Shell.Hooks
{
WinHook.Shell.Hooks := {}, WinHook.Shell.Events := {}
DllCall("RegisterShellHookWindow", UInt, A_ScriptHwnd)
MsgNum := DllCall("RegisterWindowMessage", Str, "SHELLHOOK")
OnMessage(MsgNum, ObjBindMethod(WinHook.Shell, "Message"))
}
if !IsObject(Func)
Func := Func(Func)
WinHook.Shell.Hooks.Push({Func: Func, Title: wTitle, Class: wClass, Exe: wExe, Event: Event})
WinHook.Shell.Events[Event] := true
return WinHook.Shell.Hooks.MaxIndex()
}
Remove(Index)
{
WinHook.Shell.Hooks.Delete(Index)
WinHook.Shell.Events[Event] := {} ; delete and rebuild Event list
For key, Hook in WinHook.Shell.Hooks
WinHook.Shell.Events[Hook.Event] := true
}
Report(ByRef Obj:="")
{
Obj := WinHook.Shell.Hooks
For key, Hook in WinHook.Shell.Hooks
Display .= key "|" Hook.Event "|" Hook.Func.Name "|" Hook.Title "|" Hook.Class "|" Hook.Exe "`n"
return Trim(Display, "`n")
}
Deregister()
{
DllCall("DeregisterShellHookWindow", UInt, A_ScriptHwnd)
WinHook.Shell.Hooks := "", WinHook.Shell.Events := ""
}
Message(Event, Hwnd) ; Private Method
{
DetectHiddenWindows, On
If (WinHook.Shell.Events[Event] or WinHook.Shell.Events[0])
{
WinGetTitle, wTitle, ahk_id %Hwnd%
WinGetClass, wClass, ahk_id %Hwnd%
WinGet, wExe, ProcessName, ahk_id %Hwnd%
for key, Hook in WinHook.Shell.Hooks
if ((Hook.Title = wTitle or Hook.Title = "") and (Hook.Class = wClass or Hook.Class = "") and (Hook.Exe = wExe or Hook.Exe = "") and (Hook.Event = Event or Hook.Event = 0))
return Hook.Func.Call(Hwnd, wTitle, wClass, wExe, Event)
}
}
}
class Event
{
Add(eventMin, eventMax, eventProc, idProcess := 0, WinTitle := "")
{
if !WinHook.Event.Hooks
{
WinHook.Event.Hooks := {}
static CB_WinEventProc := RegisterCallback(WinHook.Event.Message)
OnExit(ObjBindMethod(WinHook.Event, "UnHookAll"))
}
hWinEventHook := DllCall("SetWinEventHook"
, "UInt", eventMin ; UINT eventMin
, "UInt", eventMax ; UINT eventMax
, "Ptr" , 0x0 ; HMODULE hmodWinEventProc
, "Ptr" , CB_WinEventProc ; WINEVENTPROC lpfnWinEventProc
, "UInt" , idProcess ; DWORD idProcess
, "UInt", 0x0 ; DWORD idThread
, "UInt", 0x0|0x2) ; UINT dwflags, OutOfContext|SkipOwnProcess
if !IsObject(eventProc)
eventProc := Func(eventProc)
WinHook.Event.Hooks[hWinEventHook] := {eventMin: eventMin, eventMax: eventMax, eventProc: eventProc, idProcess: idProcess, WinTitle: WinTitle}
return hWinEventHook
}
Report(ByRef Obj:="")
{
Obj := WinHook.Event.Hooks
For hWinEventHook, Hook in WinHook.Event.Hooks
Display .= hWinEventHook "|" Hook.eventMin "|" Hook.eventMax "|" Hook.eventProc.Name "|" Hook.idProcess "|" Hook.WinTitle "`n"
return Trim(Display, "`n")
}
UnHook(hWinEventHook)
{
DllCall("UnhookWinEvent", "Ptr", hWinEventHook)
WinHook.Event.Hooks.Delete(hWinEventHook)
}
UnHookAll()
{
for hWinEventHook, Hook in WinHook.Event.Hooks
DllCall("UnhookWinEvent", "Ptr", hWinEventHook)
WinHook.Event.Hooks := "", CB_WinEventProc := ""
}
Message(event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) ; 'Private Method
{
DetectHiddenWindows, On
Hook := WinHook.Event.Hooks[hWinEventHook := this] ; this' is hidden param1 because method is called as func
WinGet, List, List, % Hook.WinTitle
Loop % List
if (List%A_Index% = hwnd)
return Hook.eventProc.Call(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime)
}
}
}
A fairly simple example that detects when a Notepad window is Created, Destroyed, Minimized, or Restored.
Code: Select all
WinHook.Shell.Add("Created",,, "NOTEPAD.EXE",1) ; Notepad Window Created
WinHook.Shell.Add("Destroyed",,, "NOTEPAD.EXE",2) ; Notepad Window Destroyed
Esc::ExitApp
Destroyed(Win_Hwnd, Win_Title, Win_Class, Win_Exe, Win_Event)
{
MsgBox Destroyed
}
Created(Win_Hwnd, Win_Title, Win_Class, Win_Exe, Win_Event)
{
MsgBox Created
WinGet, PID, PID, ahk_id %Win_Hwnd%
EH1 := WinHook.Event.Add(0x0016, 0x0016, "Minimized", PID)
EH2 := WinHook.Event.Add(0x0017, 0x0017, "Restored", PID)
}
Minimized(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime)
{
MsgBox Minimized
}
Restored(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime)
{
MsgBox Restored
}
This shows how to keep all the called functions in a class as subclasses and methods to keep everything acting on one type of window in one place.
Code: Select all
WinHook.Shell.Add(ObjBindMethod(Excel.Shell, "Button"),,, "EXCEL.EXE",1) ; Excel Window Created
Esc::ExitApp
;{ Excel Call Functions
class Excel
{
class Shell
{
Button(Win_Hwnd, Win_Title, Win_Class, Win_Exe, Win_Event) ; Create Button
{
static Button
; ----- Create WinHook.Event to handle button location with Resize method
WinGet, PID, PID, ahk_id %Win_Hwnd%
WinHook.Event.Add(0x800B, 0x800B, ObjBindMethod(Excel.Event, "Resize"), PID, "ahk_id " Win_Hwnd) ; LOCATIONCHANGE
;~ WinHook.Event.Add(0x000B, 0x000B, ObjBindMethod(Excel.Event, "Resize"), PID, "ahk_id " Win_Hwnd) ; MOVESIZEEND
; -----
WinGetPos,,, Win_W, Win_H, ahk_id %Win_Hwnd%
Gui_X := Win_W - 45 - 49 ; Exact numbers and scaling effected by screen DPI
Gui_Y := Win_H - 60 - 34 ; Exact numbers and scaling effected by screen DPI
; Gui Create
Gui Excel:Default
Gui +Resize
Gui, Font, s12, Bold Verdana
Gui, Margin, 0, 0
Gui, Add, Button, xp yp Default HWNDButton gButton, Click
Gui, +LastFound +ToolWindow +AlwaysOnTop -Caption -Border HWNDGui_Hwnd
Excel.Gui_Hwnd := Gui_Hwnd ; Assign to Class variable to allow access from other Methods
DllCall("SetParent", "uint", Gui_Hwnd, "uint", Win_Hwnd)
Gui, Show, x%Gui_X% y%Gui_Y%
return
; Gui Actions
Button:
MsgBox Clicked
return
ExcelGuiSize:
Excel.Gui_W := A_GuiWidth ; Assign to Class variable to allow access from other Methods
Excel.Gui_H := A_GuiHeight ; Assign to Class variable to allow access from other Methods
GuiControl, Move, %Button%, % "W" Excel.Gui_W " H" Excel.Gui_H
return
}
}
class Event
{
Resize(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime)
{
WinGetPos,,, Win_W, Win_H, ahk_id %hwnd%
Gui_X := Win_W - 45 - Excel.Gui_W
Gui_Y := Win_H - 60 - Excel.Gui_H
WinMove, % "ahk_id " Excel.Gui_Hwnd,, Gui_X, Gui_Y
return
}
}
}
;}
Here are a couple of tools to monitor all shell and event hooks to try to find the information needed to make a more targeted hook. The 'Event' code sent back is usually the crucial information needed.
WinHook.Shell Monitor
Code: Select all
Gui, +AlwaysOnTop
Gui, Add, Text,, WinHook.Shell
Gui, Add, ListView, r25 w1000, Win_Hwnd|Win_Title|Win_Class|Win_Exe|Win_Event
Gui, Show
WinHook.Shell.Add("AllShell") ; no additional filter parameters result in all windows
AllShell(Win_Hwnd, Win_Title, Win_Class, Win_Exe, Win_Event)
{
LV_Insert(1, "", Win_Hwnd, Win_Title, Win_Class, Win_Exe, Win_Event)
Loop, 5
LV_ModifyCol(A_Index, "AutoHdr")
}
return
GuiClose:
ExitApp
Code: Select all
Gui, +AlwaysOnTop
Gui, Add, Text,, WinHook.Event
Gui, Add, ListView, r25 w1000, hWinEventHook|Event|hWnd|idObject|idChild|dwEventThread|dwmsEventTime|wTitle|wClass
Gui, Show
WinHook.Event.Add(0x0000, 0xFFFF, "AllEvent") ; 0x0000 to 0xFFFF is all events, also with no process specified it defaults to all processes.
AllEvent(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime)
{
WinGetTitle, wTitle, ahk_id %Hwnd%
WinGetClass, wClass, ahk_id %Hwnd%
LV_Insert(1, "", hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime, wTitle, wClass)
Loop, 9
LV_ModifyCol(A_Index, "AutoHdr")
}
return
GuiClose:
ExitApp
I am not entirely sure what happens if a function is triggered faster than Autohotkey can return from the function. Normally the function called needs to resolve pretty quickly. I am not sure what would happen if a function took 10 seconds to resolve and then it is called a hundred times every 10 seconds. So even though WinHook makes it easier to use hooks there is still some required knowledge to find the right event numbers and use them effectively.
FG