KeyHistoryWindow.ahk
Shows keyboard events similar to KeyHistory, but updates in real-time as you press or release any key.
Sample output:
Code: Select all
48 023 u 0.00 h 0x0
45 012 0.03 e 0x0
4C 026 0.09 l 0x0
45 012 u 0.02 e 0x0
4C 026 u 0.08 l 0x0
4C 026 0.05 l 0x0
4C 026 u 0.09 l 0x0
4F 018 0.05 o 0x0
4F 018 u 0.09 o 0x0
Code: Select all
#define KEY_IGNORE 0xFFC3D44F
#define KEY_PHYS_IGNORE (KEY_IGNORE - 1) // Same as above but marked as physical for other instances of the hook.
#define KEY_IGNORE_ALL_EXCEPT_MODIFIER (KEY_IGNORE - 2) // Non-physical and ignored only if it's not a modifier.
...
#define KEY_IGNORE_LEVEL(LEVEL) (KEY_IGNORE_ALL_EXCEPT_MODIFIER - LEVEL)
Code: Select all
; https://autohotkey.com/boards/viewtopic.php?f=6&t=26059
; GUI Legend:
; VK SC_ _a!u Elapsed Key name_________ Extra_info
#NoEnv
#Persistent
#InstallKeybdHook
hHookKeybd := DllCall("SetWindowsHookEx", "int", 13 ; WH_KEYBOARD_LL = 13
, "ptr", RegisterCallback("Keyboard")
; hMod is not required on Win 7, but seems to be required on XP even
; though this type of hook is never injected into other processes:
, "ptr", DllCall("GetModuleHandle", "ptr", 0, "ptr")
, "uint", 0, "ptr") ; dwThreadId
#KeyHistory(10)
OnMessage(0x201, "WM_LBUTTONDOWN")
OnMessage(0x100, "WM_KEYDOWN")
Gui, +LastFound -DPIScale
WinSet, Transparent, 200
Gui, +ToolWindow +AlwaysOnTop
Gui, Margin, 10, 10
Gui, Font,, Lucida Console
Gui, Add, Text, vKH, 00 000 ____ 1000.00 Browser_Favorites 0xFFFFFFFF
GuiControlGet, KH, Pos
GuiControl,, KH ; clear dummy sizing text
gosub Resize
return
#MaxThreadsBuffer, On
!WheelUp::
!WheelDown::
#MaxThreadsBuffer, Off
history_size := #KeyHistory() + ((A_ThisHotkey="!WheelUp") ? +1 : -1)
#KeyHistory(history_size>0 ? history_size : 1)
; Delay resize to improve hotkey responsiveness.
SetTimer, Resize, -10
return
Resize:
; Resize label to fit key history.
gui_h := KHH*(#KeyHistory())
GuiControl, Move, KH, h%gui_h%
gui_h += 20
Gui, +LastFound
; Determine visibility.
WinGet, style, Style
gui_visible := style & 0x10000000
;Gui, Show, % "AutoSize NA " (gui_visible ? "" : "Hide")
;** Not used because we need to know the previous height,
; and its simpler to resize manually.
; Determine current position and height.
WinGetPos, gui_x, gui_y, , gui_h_old
; Use old height to determine if we should reposition, *only when shrinking*.
; This way we can move the GUI somewhere else, and the script won't reposition it.
;if (gui_h_old < gui_h)
; gui_h_old := gui_h
; Determine working area (primary screen size minus taskbar.)
SysGet, wa_, MonitorWorkArea
SysGet, twc_h, 51 ; SM_CYSMCAPTION
SysGet, bdr_h, 8 ; SM_CYFIXEDFRAME
if (!gui_visible)
{
gui_x = 72 ; Initially on the left side.
gui_y := wa_bottom-(gui_h+twc_h+bdr_h*2+10)
}
else
{ ; Move relative to bottom edge when closer to the bottom.
if (gui_y+gui_h//2 > (wa_bottom-wa_top)//2)
gui_y += gui_h_old-(gui_h+twc_h+bdr_h*2)
}
Gui, Show, x%gui_x% y%gui_y% h%gui_h% NA, Key History
return
Keyboard(nCode, wParam, lParam) {
global KeyBuffer
Critical
if KeyHistory(1, vk, sc, flags)
&& NumGet(lParam+0, "uint") = vk
&& NumGet(lParam+4, "uint") = sc
&& NumGet(lParam+8, "uint") = flags
buf_max := 0 ; Don't show key-repeat.
else
buf_max := #KeyHistory()
if (buf_max > 0)
{
; Push older key events to the back.
if (buf_max > 1)
DllCall("RtlMoveMemory", "ptr", &KeyBuffer+16+A_PtrSize, "ptr", &KeyBuffer, "ptr", buf_max*16+A_PtrSize)
; Copy current key event to the buffer.
DllCall("RtlMoveMemory", "ptr", &KeyBuffer, "ptr", lParam, "ptr", 16+A_PtrSize)
; "gosub Show" slows down the keyboard hook and causes problems, so use a timer.
SetTimer, Show, -10
}
return DllCall("CallNextHookEx", "ptr", 0, "int", nCode, "ptr", wParam, "ptr", lParam, "ptr")
}
KeyHistory(N, ByRef vk, ByRef sc, ByRef flags:=0, ByRef time:=0, ByRef elapsed:=0, ByRef info:=0)
{
global KeyBuffer
if N is not integer
return false
buf_max := #KeyHistory()
if (N < 0)
N += buf_max + 1
if (N < 1 or N > buf_max)
return false
static sz := 16+A_PtrSize
vk := NumGet(KeyBuffer, (N-1)*sz, "uint")
sc := NumGet(KeyBuffer, (N-1)*sz+4, "uint")
flags := NumGet(KeyBuffer, (N-1)*sz+8, "uint")
time := NumGet(KeyBuffer, (N-1)*sz+12, "uint")
info := NumGet(KeyBuffer, (N-1)*sz+16)
elapsed := time - ((time2 := NumGet(KeyBuffer, N*sz+12, "uint")) ? time2 : time)
return (vk or sc)
}
#KeyHistory(NewSize="")
{
global KeyBuffer
static sz := 16+A_PtrSize
; Get current history length.
if (NewSize="")
return (cap:=VarSetCapacity(KeyBuffer)//sz)>0 ? cap-1 : 0
if (NewSize)
{
new_cap := (NewSize+1)*sz
cap := VarSetCapacity(KeyBuffer)
if (cap > new_cap)
cap := new_cap
VarSetCapacity(old_buffer, cap)
; Back up previous history.
DllCall("RtlMoveMemory", "ptr", &old_buffer, "ptr", &KeyBuffer, "ptr", cap)
; Set new history length.
VarSetCapacity(KeyBuffer, 0) ; FORCE SHRINK
VarSetCapacity(KeyBuffer, new_cap, 0)
; Restore previous history.
DllCall("RtlMoveMemory", "ptr", &KeyBuffer, "ptr", &old_buffer, "ptr", cap)
; (Remember N+1 key events to simplify calculation of the Nth key event's elapsed time.)
; Put tick count so the initial key event has a meaningful value for "elapsed".
NumPut(A_TickCount, KeyBuffer, 12, "uint")
}
else
{ ; Clear history entirely.
VarSetCapacity(KeyBuffer, 0)
}
}
GetKeyFlagText(flags)
{
return ((flags & 0x1) ? "e" : " ") ; LLKHF_EXTENDED
. ((flags & 0x10) ? "a" : " ") ; LLKHF_INJECTED (artificial)
. ((flags & 0x20) ? "!" : " ") ; LLKHF_ALTDOWN
. ((flags & 0x80) ? "u" : " ") ; LLKHF_UP (key up)
}
; Gets readable key name, usually identical to the name in KeyHistory.
GetKeyNameText(vkCode, scanCode, isExtendedKey)
{
return GetKeyName(format("vk{1:02x}sc{3}{2:02x}", vkCode, scanCode, isExtendedKey))
/* ; For older versions of AutoHotkey:
; My Right Shift key shows as vk161 sc54 isExtendedKey=true. For some
; reason GetKeyNameText only returns a name for it if isExtendedKey=false.
if vkCode = 161
return "Right Shift"
VarSetCapacity(buffer, 32, 0)
DllCall("GetKeyNameText"
, "UInt", (scanCode & 0xFF) << 16 | (isExtendedKey ? 1<<24 : 0) ;| 1<<25
, "Str", buffer
, "Int", 32)
return buffer
*/
}
Show:
SetFormat, FloatFast, .2
SetFormat, IntegerFast, H
text =
buf_size := #KeyHistory()
Loop, % buf_size
{
if (KeyHistory(buf_size-A_Index, vk, sc, flags, time, elapsed, info))
{
keytext := GetKeyNameText(vk, sc, flags & 0x1)
if (elapsed < 0)
elapsed := "#err#"
else
dt := elapsed/1000.0
; AHK-style SC
sc_a := sc
if (flags & 1)
sc_a |= 0x100, flags &= ~1
sc_a := SubStr("000" SubStr(sc_a, 3), -2)
vk_a := SubStr(vk+0, 3)
if (StrLen(vk_a)<2)
vk_a = 0%vk_a%
StringUpper, vk_a, vk_a
StringUpper, sc_a, sc_a
flags := GetKeyFlagText(flags & ~0x1)
text .= vk_a " " sc_a " " flags " " SubStr(" " dt, -6) " "
. SubStr(keytext " ", 1, 17) " " info "`n"
}
}
GuiControl,, KH, % text
Return
GuiClose:
ExitApp
WM_KEYDOWN()
{
if A_Gui
return true
}
WM_LBUTTONDOWN(wParam, lParam)
{
global text
StringReplace, Clipboard, text, `n, `r`n, All
}
Shows mouse events in real-time.
If MERGE_MOVE is true and the last event is a mouse-move, it is replaced. Otherwise there will be one row for each mouse-move event, and events will quickly get pushed off the window while the mouse moves.
Sample output:
Code: Select all
190 991 2.02
Middle 1075 548 Up 0.08
Middle 1075 548 Down 1.70
WD 1090 427 120 0.39
Left 1110 391 Up 0.09
Left 1110 392 Down 0.52
WU 1111 408 120 1.11
* Right 1105 527 Up 0.00
* Right 1105 527 Down 0.02
Right 1105 527 Up 0.06
Code: Select all
; https://autohotkey.com/boards/viewtopic.php?f=6&t=26059
; If the most recent event is a mouse-move and the mouse moves again,
; enable this to update it instead of adding another mouse-move event.
MERGE_MOVE := true
#NoEnv
#Persistent
#MouseHistory(10)
Gui, +LastFound -DPIScale
WinSet, Transparent, 200
Gui, +ToolWindow +AlwaysOnTop
Gui, Margin, 10, 10
Gui, Font,, Lucida Console
Gui, Add, Text, vMH, . .
GuiControlGet, MH, Pos
GuiControl,, MH ; clear dummy sizing text
gosub Resize
OnMessage(0x201, "WM_LBUTTONDOWN")
return
#MaxThreadsBuffer, On
!WheelUp::
!WheelDown::
#MaxThreadsBuffer, Off
history_size := #MouseHistory() + ((A_ThisHotkey="!WheelUp") ? +1 : -1)
#MouseHistory(history_size>0 ? history_size : 1)
; Delay resize to improve hotkey responsiveness.
SetTimer, Resize, -10
return
Resize:
; Resize label to fit mouse history.
gui_h := MHH*(#MouseHistory())
GuiControl, Move, MH, h%gui_h%
gui_h += 20
Gui, +LastFound
; Determine visibility.
WinGet, style, Style
gui_visible := style & 0x10000000
; Determine current position and height.
WinGetPos, gui_x, gui_y, , gui_h_old
; Use old height to determine if we should reposition, *only when shrinking*.
; This way we can move the GUI somewhere else, and the script won't reposition it.
;if (gui_h_old < gui_h)
; gui_h_old := gui_h
; Determine working area (primary screen size minus taskbar.)
SysGet, wa_, MonitorWorkArea
SysGet, twc_h, 51 ; SM_CYSMCAPTION
SysGet, bdr_h, 8 ; SM_CYFIXEDFRAME
if (!gui_visible)
{
gui_x = 10 ; Initially on the left side.
gui_y := wa_bottom-(gui_h+twc_h+bdr_h*2+10)
}
else
{ ; Move relative to bottom edge when closer to the bottom.
if (gui_y+gui_h//2 > (wa_bottom-wa_top)//2)
gui_y += gui_h_old-(gui_h+twc_h+bdr_h*2)
}
Gui, Show, x%gui_x% y%gui_y% h%gui_h% NA, Mouse History
return
Show:
SetFormat, FloatFast, .2
text =
buf_size := #MouseHistory()
Loop, % buf_size
{
SetFormat, IntegerFast, D
if MouseHistory(A_Index, msg, x, y, mouseData, flags, time, elapsed)
{
SetFormat, IntegerFast, H
msg := (msg + 0) ""
SetFormat, IntegerFast, D
; WM_LBUTTONDOWN/UP/DBLCLK, WM_NC..
if msg in 0x201,0x202,0x203,0xA1,0xA2,0xA3
btn = Left
; WM_RBUTTONDOWN/UP/DBLCLK, WM_NC..
else if msg in 0x204,0x205,0x206,0xA4,0xA5,0xA6
btn = Right
; WM_MBUTTONDOWN/UP/DBLCLK, WM_NC..
else if msg in 0x207,0x208,0x209,0xA7,0xA8,0xA9
btn = Middle
; WM_XBUTTONDOWN/UP/DBLCLK, WM_NC..
else if msg in 0x20B,0x20C,0x20D,0xAB,0xAC,0xAD
btn := (mouseData & 0x10000) ? "X1" : "X2"
; WM_MOUSEWHEEL
else if msg = 0x20A
{
mouseData := mouseData << 32 >> 48
btn := (mouseData < 0) ? "WD" : "WU"
}
; WM_MOUSEHWHEEL
else if msg = 0x20E
{
mouseData := mouseData << 32 >> 48
btn := (mouseData < 0) ? "WL" : "WR"
}
; WM_MOUSEMOVE
else if msg = 0x200
btn =
; ???
else btn := msg
clickCount =
; WM_LBUTTONDBLCLK, WM_NC.., ..R/M/XBUTTONDBLCLK..
if msg in 0x203,0xA3,0x206,0xA6,0x209,0xA9,0x20D,0xAD
{
clickCount := 2
}
; WM_MOUSEWHEEL, WM_MOUSEHWHEEL
else if msg in 0x20A,0x20E
{
clickCount := Abs(mouseData)
if !clickCount
clickCount =
}
; WM_L/R/M/XBUTTONDOWN, WM_NC..
else if msg in 0x201,0x204,0x207,0x20B,0xA1,0xA4,0xA7,0xAB
{
clickCount = Down
}
; WM_L/R/M/XBUTTONUP, WM_NC..
else if msg in 0x202,0x205,0x208,0x20C,0xA2,0xA5,0xA8,0xAC
{
clickCount = Up
}
text .= ((flags & 1) ? "* " : " ")
;. SubStr(msg " ", 1, 6)
. SubStr(btn " ", 1, 8)
. SubStr(" " x, -4) " " SubStr(" " y, -4)
. SubStr(" " clickCount, -5)
. SubStr(" " elapsed/1000.0, -6) "`n"
}
else break
}
GuiControl,, MH, % text
Return
GuiClose:
ExitApp
MouseHistory(N, ByRef msg, ByRef x, ByRef y, ByRef mouseData, ByRef flags, ByRef time, ByRef elapsed=-1)
{
global MouseBuffer
if N is not integer
return false
buf_max := #MouseHistory()
if (N < 1 or N > buf_max)
return false
x := NumGet(MouseBuffer, ofs:=(N-1)*24, "int")
y := NumGet(MouseBuffer, ofs+4, "int")
mouseData := NumGet(MouseBuffer, ofs+8, "uint")
flags := NumGet(MouseBuffer, ofs+12, "uint")
time := NumGet(MouseBuffer, ofs+16, "uint")
msg := NumGet(MouseBuffer, ofs+20, "uint")
elapsed := time - ((time2 := NumGet(MouseBuffer, N*24+16, "uint")) ? time2 : time)
return !!msg
}
#MouseHistory(NewSize="")
{
global MouseBuffer
static MouseHook, MouseHookProc
if NewSize = ; Get current history length.
return (cap:=VarSetCapacity(MouseBuffer)//24)>0 ? cap-1 : 0
if NewSize
{
if !MouseHook
{ ; Register the mouse hook.
MouseHookProc := RegisterCallback("Mouse")
MouseHook := DllCall("SetWindowsHookEx", "int", 14, "ptr", MouseHookProc, "uint", 0, "uint", 0, "ptr")
}
new_cap := (NewSize+1)*24 ; sizeof(MSLLHOOKSTRUCT)=24
cap := VarSetCapacity(MouseBuffer)
if (cap > new_cap)
cap := new_cap
VarSetCapacity(old_buffer, cap)
; Back up previous history.
DllCall("RtlMoveMemory", "ptr", &old_buffer, "ptr", &MouseBuffer, "ptr", cap)
; Set new history length.
VarSetCapacity(MouseBuffer, 0) ; FORCE SHRINK
VarSetCapacity(MouseBuffer, new_cap, 0)
; Restore previous history.
DllCall("RtlMoveMemory", "ptr", &MouseBuffer, "ptr", &old_buffer, "ptr", cap)
; (Remember N+1 mouse events to simplify calculation of the Nth mouse event's elapsed time.)
; Put tick count so the initial mouse event has a meaningful value for "elapsed".
NumPut(A_TickCount, MouseBuffer, 16, "uint")
}
else
{
if MouseHook
{ ; Unregister the mouse hook.
DllCall("UnhookWindowsHookEx", "ptr", MouseHook)
DllCall("GlobalFree", "ptr", MouseHookProc)
MouseHook =
}
; Clear history entirely.
VarSetCapacity(MouseBuffer, 0)
}
}
; Mouse hook callback - records mouse events into MouseBuffer.
Mouse(nCode, wParam, lParam)
{
global MouseBuffer, MERGE_MOVE
Critical
if (buf_max:=#MouseHistory()) > 0
{
if MERGE_MOVE && NumGet(MouseBuffer, 20, "uint") = 0x200
;if MERGE_MOVE && wParam = 0x200 && NumGet(MouseBuffer, 20, "uint") = 0x200
{
; Update the most recent (mouse-move) event.
DllCall("RtlMoveMemory", "ptr", &MouseBuffer, "ptr", lParam, "ptr", 20)
}
else
{
; Push older mouse events to the back.
if (buf_max > 1)
DllCall("RtlMoveMemory", "ptr", &MouseBuffer+24, "ptr", &MouseBuffer, "ptr", buf_max*24)
; Copy current mouse event to the buffer.
DllCall("RtlMoveMemory", "ptr", &MouseBuffer, "ptr", lParam, "ptr", 20)
}
NumPut(wParam, MouseBuffer, 20, "uint") ; Put wParam in place of dwEventInfo.
; "gosub Show" slows down the mouse hook and causes problems, so use a timer.
SetTimer, Show, -10
}
return DllCall("CallNextHookEx", "uint", 0, "int", nCode, "ptr", wParam, "ptr", lParam, "ptr")
}
WM_LBUTTONDOWN(wParam, lParam)
{
global text
StringReplace, Clipboard, text, `n, `r`n, All
}
Clicking the GUI copies its content to the clipboard.
Alt+WheelUp/WheelDown can be used to change the number of events that are displayed. I typically only use one of these scripts at a time, so they both use the same hotkey. If both are running, the hotkey will only work for one.
These scripts contain code for collecting the last n keyboard/mouse events. You may reuse this code, but I will not explain how to use it.