; The delay between mouse and keyboard messages can be altered using pClickDelay and pSendDelay
; respectively. Valid values: -1 no sleep, 0 yields its time slice to any other needy programs,
; any positive integer
; This function doesn't currently release mouse buttons (as I don't require this), hence
; ensure no mouse buttons are down before calling releaseKeys()! e.g. use getkeystate(key, "P")
; Same with Windows keys (as releasing these will cause the windows menu to appear) - although the automation
; may still work
; I think it's best to use SendInput with {Blind} to prevent any unnecessary extra keyups/downs
class Input
{
static keys := ["LControl", "RControl", "LAlt", "RAlt", "LShift", "RShift", "LWin", "RWin"
, "AppsKey", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"
, "Left", "Right", "Up", "Down", "Home", "End", "PgUp", "PgDn", "Del", "Ins", "BS", "Capslock", "Numlock", "PrintScreen"
, "Pause", "Space", "Enter", "Tab", "Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "B", "C", "D", "E", "F", "G"
, "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
, MouseButtons := ["LButton", "RButton", "MButton", "XButton1", "XButton2"]
, downSequence
, MouseBlocked := False
, KybdBlocked := False
releaseKeys()
{
this.downSequence := ""
SetFormat, IntegerFast, hex
for index, key in this.keys
if GetKeyState(key) ; check the logical state
upsequence .= "{VK" GetKeyVK(key) " Up}", this.downSequence .= "{" key " Down}"
SetFormat, IntegerFast, d
if upsequence
{
SendInput, {BLIND}%upsequence%
return upsequence ; This will indicate that we should sleep for 10-15ms (after activating critical)
} ; to prevent out of order command sequence with sendinput vs. post message
return
}
revertKeyState()
{
if this.downSequence
pSend(this.downSequence)
return
}
userInputModified()
{
return this.downSequence
}
pClickDelay(newDelay := "")
{
static clickDelay := -1
if newDelay is number
clickDelay := newDelay
return clickDelay
}
pSendDelay(newDelay := "")
{
static SendDelay := -1
if newDelay is number
SendDelay := newDelay
return SendDelay
}
hookBlock(kybd := False, mouse := False)
{
this.KybdBlocked := kybd
this.MouseBlocked := mouse
return
}
iskeyboardBlocked()
{
return this.KybdBlocked
}
isMouseBlocked()
{
return this.KybdBlocked
}
}
MTclick(x,y, button := "Left" , Modifiers := "", count := 1, MouseMove := False)
{
if instr(modifiers, "+")
ModifersDown .= "{Shift Down}", ModifersUp .= "{Shift Up}"
if instr(modifiers, "^")
ModifersDown .= "{Ctrl Down}", ModifersUp .= "{Ctrl Up}"
if instr(modifiers, "!")
ModifersDown .= "{Alt Down}", ModifersUp .= "{Alt Up}"
if ModifersDown
pSend(ModifersDown)
pClick(x, y, button, count, Modifiers, MouseMove)
if ModifersUp
pSend(ModifersUp)
return
}
; This command can be used with the same syntax as AHKs sendInput command
; pSend("^+ap") Result: Control+Shift+a, p
; pSend("{ctrl down}{Home}{Ctrl Up} Result: Ctrl+Home
; pSend("+{click}") Result: Shift Left click the mouse (down and up event)
; pSend("{click D " x1 " " y1 "}{Click U " x2 " " y2 "}") ; Result: Box drag the mouse with the LButton
pSend(Sequence := "")
{
Global GameIdentifier, classIdentifier
static WM_KEYDOWN := 0x100
, WM_KEYUP := 0x101
, WM_CHAR = 0x102
pKeyDelay := Input.pSendDelay()
pClickDelay := Input.pClickDelay()
SetFormat, IntegerFast, hex
aSend := []
C_Index := 1
StringReplace, Sequence, Sequence, `t , %A_Space%, All
Currentmodifiers := []
length := strlen(Sequence)
while (C_Index <= length)
{
char := SubStr(Sequence, C_Index, 1)
if (char = " ")
{
C_Index++
continue
}
if char in +,^,!
{
if (char = "+")
Modifier := "Shift"
else if (char = "^")
Modifier := "Ctrl"
else if (char = "!")
Modifier :="Alt"
CurrentmodifierString .= char
Currentmodifiers.insert( {"wParam": GetKeyVK(Modifier)
, "sc": GetKeySC(Modifier)})
aSend.insert({ "message": WM_KEYDOWN
, "sc": GetKeySC(Modifier)
, "wParam": GetKeyVK(Modifier)})
C_Index++
continue
}
if (char = "{") ; send {}} will fail with this test but cant use that
{ ; hotkey anyway in program would be ]
if (Position := instr(Sequence, "}", False, C_Index, 1)) ; lets find the closing bracket) n
{
key := trim(substr(Sequence, C_Index+1, Position - C_Index - 1))
C_Index := Position ;PositionOfClosingBracket
while (if instr(key, A_space A_space))
StringReplace, key, key, %A_space%%A_space%, %A_space%, All
if instr(key, "click")
{
StringSplit, clickOutput, key, %A_space%, %A_Space%%A_Tab%`,
numbers := []
SetFormat, IntegerFast, d ; otherwise A_Index is 0x and doesnt work with var%A_Index%
loop, % clickOutput0
{
command := clickOutput%A_index%
if command is number
numbers.insert(command)
}
if (!numbers.maxindex() || numbers.maxindex() = 1)
{
MouseGetPos, x, y ; will cause problems if send hex number to insertpClickObject
clickCount := numbers.maxindex() = 1 ? numbers.1 : 1
}
else if (numbers.maxindex() = 2 || numbers.maxindex() = 3)
x := numbers.1, y := numbers.2, clickCount := numbers.maxindex() = 3 ? numbers.3 : 1
else
{
SetFormat, IntegerFast, hex
continue ; error
}
SetFormat, IntegerFast, hex
; msgbox % key "`n" x ", " y "`n" clickCount
insertpClickObject(aSend, x, y, key, clickCount, CurrentmodifierString, instr(key, "MM")) ; MM - Insert MouseMove
}
else
{
StringSplit, outputKey, key, %A_Space%
if (outputKey0 = 2)
{
if instr(outputKey2, "Down")
aSend.insert({ "message": WM_KEYDOWN
, "sc": GetKeySC(outputKey1)
, "wParam": GetKeyVK(outputKey1)})
else if instr(outputKey2, "Up")
aSend.insert({ "message": WM_KEYUP
, "sc": GetKeySC(outputKey1)
, "wParam": GetKeyVK(outputKey1)})
}
else
{
aSend.insert({ "message": WM_KEYDOWN
, "sc": GetKeySC(outputKey1)
, "wParam": GetKeyVK(outputKey1)})
aSend.insert({ "message": WM_KEYUP
, "sc": GetKeySC(outputKey1)
, "wParam": GetKeyVK(outputKey1)})
}
}
}
}
Else
{
aSend.insert({ "message": WM_KEYDOWN
, "sc": GetKeySC(char)
, "wParam": GetKeyVK(char)})
aSend.insert({ "message": WM_KEYUP
, "sc": GetKeySC(char)
, "wParam": GetKeyVK(char)})
}
if Modifier
{
for index, modifier in Currentmodifiers
aSend.insert({ "message": WM_KEYUP
, "sc": modifier.sc
, "wParam": modifier.wParam})
Modifier := False
CurrentmodifierString := "", Currentmodifiers := []
}
C_Index++
}
SetFormat, IntegerFast, d
for index, message in aSend
{
if (WM_KEYDOWN = message.message)
{
; repeat code | (scan code << 16)
lparam := 1 | (message.sc << 16)
postmessage, message.message, message.wParam, lparam, %classIdentifier%, % GameIdentifier
}
else if (WM_KEYUP = message.message)
{
lparam := 1 | (message.sc << 16) | (1 << 31) ; transition state
postmessage, message.message, message.wParam, lparam, %classIdentifier%, % GameIdentifier
}
else
{
postmessage, message.message, message.wParam, message.lparam, %classIdentifier%, % GameIdentifier
if (pClickDelay != -1)
DllCall("Sleep", Uint, pClickDelay)
continue
}
if (pKeyDelay != -1)
DllCall("Sleep", Uint, pKeyDelay)
}
return aSend
}
; You dont need to use this function, this is just used by the MTclick function
; The Modifiers in this function are simply used to set the correct
; WParam flags for the mouse event
pClick(x, y, button := "L", count := 1, Modifiers := "", MouseMove := False)
{
Global GameIdentifier, classIdentifier
static WM_MOUSEFIRST := 0x200
, WM_MOUSEMOVE = 0x200
, WM_LBUTTONDOWN = 0x201
, WM_LBUTTONUP = 0x202
, WM_LBUTTONDBLCLK = 0x203 ; double click
, WM_RBUTTONDOWN = 0x204
, WM_RBUTTONUP = 0x205
, WM_RBUTTONDBLCLK = 0x206
, WM_MBUTTONDOWN = 0x207
, WM_MBUTTONUP = 0x208
, WM_MBUTTONDBLCLK = 0x209
, WM_MOUSEWHEEL = 0x20A
, WM_MOUSEHWHEEL = 0x20E
, WM_XBUTTONDOWN := 0x020B
, WM_XBUTTONUP := 0x020C
, MK_LBUTTON := 0x0001
, MK_RBUTTON := 0x0002
, MK_SHIFT := 0x0004
, MK_CONTROL := 0x0008
, MK_MBUTTON := 0x0010
, MK_XBUTTON1 := 0x0020
, MK_XBUTTON2 := 0x0040
pKeyDelay := Input.pClickDelay()
lParam := x & 0xFFFF | (y & 0xFFFF) << 16
WParam := 0 ; Needed for the |= to work
if instr(Modifiers, "+")
WParam |= MK_SHIFT
if instr(Modifiers, "^")
WParam |= MK_CONTROL
if instr(Modifiers, "x1")
WParam |= MK_XBUTTON1
if instr(Modifiers, "x2")
WParam |= MK_XBUTTON2
if MouseMove
PostMessage, %WM_MOUSEMOVE%, , %lParam%, %classIdentifier%, %GameIdentifier%
if button contains r
message := "WM_RBUTTON", WParam |= MK_RBUTTON
else if button contains M
message := "WM_MBUTTON", WParam |= MK_MBUTTON
else if button contains x1
message := "WM_XBUTTON", WParam |= MK_XBUTTON1
else if button contains x2
message := "WM_XBUTTON", WParam |= MK_XBUTTON2
else if button contains WheelUp,WU,WheelDown,WD
{
if button contains WheelUp,WU
direction := 1
else direction := -1
WParam |= (direction * count * 120) << 16
PostMessage, %WM_MOUSEWHEEL%, %WParam%, %lParam%, %classIdentifier%, %GameIdentifier%
return
}
else message := "WM_LBUTTON", WParam |= MK_LBUTTON
if button contains up,U
message .= "UP"
else if button contains down,D
message .= "DOWN"
else
{
mdown := message . "DOWN"
mup := message . "UP"
mdown := %mdown%
mup := %mup%
loop % count
{
PostMessage, %mdown%, %WParam%, %lParam%, %classIdentifier%, %GameIdentifier%
if (pKeyDelay != -1)
DllCall("Sleep", Uint, pKeyDelay)
PostMessage, %mup%, %WParam%, %lParam%, %classIdentifier%, %GameIdentifier%
}
return
}
message := %message%
PostMessage, %message%, %WParam% , %lParam%, %classIdentifier%, %GameIdentifier%
return
}
; This function is used by the pSend command to insert mouse messages
; into the send command (you don't need to call this one yourself)
insertpClickObject(ByRef sendObject, x, y, button := "L", count := 1, Modifiers := "", MouseMove := False)
{
static WM_MOUSEFIRST := 0x200
, WM_MOUSEMOVE = 0x200
, WM_LBUTTONDOWN = 0x201
, WM_LBUTTONUP = 0x202
, WM_LBUTTONDBLCLK = 0x203 ; double click
, WM_RBUTTONDOWN = 0x204
, WM_RBUTTONUP = 0x205
, WM_RBUTTONDBLCLK = 0x206
, WM_MBUTTONDOWN = 0x207
, WM_MBUTTONUP = 0x208
, WM_MBUTTONDBLCLK = 0x209
, WM_MOUSEWHEEL = 0x20A
, WM_MOUSEHWHEEL = 0x20E
, WM_XBUTTONDOWN := 0x020B
, WM_XBUTTONUP := 0x020C
, MK_LBUTTON := 0x0001
, MK_RBUTTON := 0x0002
, MK_SHIFT := 0x0004
, MK_CONTROL := 0x0008
, MK_MBUTTON := 0x0010
, MK_XBUTTON1 := 0x0020
, MK_XBUTTON2 := 0x0040
lParam := x & 0xFFFF | (y & 0xFFFF) << 16
WParam := 0 ; Needed for the |= to work
if instr(Modifiers, "+")
WParam |= MK_SHIFT
if instr(Modifiers, "^")
WParam |= MK_CONTROL
if instr(Modifiers, "x1")
WParam |= MK_XBUTTON1
if instr(Modifiers, "x2")
WParam |= MK_XBUTTON2
if MouseMove
sendObject.insert({ "message": WM_MOUSEMOVE
, "lParam": lParam})
if button contains r
message := "WM_RBUTTON", WParam |= MK_RBUTTON
else if button contains M
message := "WM_MBUTTON", WParam |= MK_MBUTTON
else if button contains x1
message := "WM_XBUTTON", WParam |= MK_XBUTTON1
else if button contains x2
message := "WM_XBUTTON", WParam |= MK_XBUTTON2
else if button contains WheelUp,WU,WheelDown,WD
{
if button contains WheelUp,WU
direction := 1
else direction := -1
sendObject.insert({ "message": WM_MOUSEWHEEL
, "wParam": WParam |= (direction * count * 120) << 16
, "lParam": lParam})
return
}
else message := "WM_LBUTTON", WParam |= MK_LBUTTON
if button contains up,U
message .= "UP"
else if button contains down,D
message .= "DOWN"
else
{
mdown := message . "DOWN"
mup := message . "UP"
loop % count
{
sendObject.insert({ "message": %mdown%
, "wParam": WParam
, "lParam": lParam})
sendObject.insert({ "message": %mup%
, "wParam": WParam
, "lParam": lParam})
}
return
}
sendObject.insert({ "message": %message%
, "wParam": WParam
, "lParam": lParam})
return
}
setLowLevelInputHooks(Install, KeyboardFunction := "KeyboardHook", MouseFunction := "MouseHook")
{
; WH_KEYBOARD_LL := 13, WH_MOUSE_LL := 14
static hHooks := [], CallBacks := [] ; only lookup the callback once for each function
if Install
{
if (KeyboardFunction && !CallBacks[KeyboardFunction])
hHooks.hHookKeybd := SetWindowsHookEx(13, CallBacks[KeyboardFunction] := RegisterCallback(KeyboardFunction))
else if KeyboardFunction
hHooks.hHookKeybd := SetWindowsHookEx(13, CallBacks[KeyboardFunction])
if (MouseFunction && !CallBacks[MouseFunction])
hHooks.hHookMouse := SetWindowsHookEx(14, CallBacks[MouseFunction] := RegisterCallback(MouseFunction))
else if MouseFunction
hHooks.hHookMouse := SetWindowsHookEx(14, CallBacks[MouseFunction])
return
}
else
{
if (KeyboardFunction && hHooks.hHookKeybd) ; Don't attempt to remove it if it was never properly installed
UnhookWindowsHookEx(hHooks.hHookKeybd), hHooks.hHookKeybd := False
if (MouseFunction && hHooks.hHookMouse)
UnhookWindowsHookEx(hHooks.hHookMouse), hHooks.hHookMouse := False
}
return
}
; ncode < 0 means the message shouldn't be processed and I should Return CallNextHookEx(nCode, wParam, lParam)
; ncode 0 - message contains information
; return a negative value to prevent other programs reading the key
KeyboardHook(nCode, wParam, lParam)
{
Critical 1000
static WM_KEYUP := 0x101
Global MT_HookBlock
If !nCode ; if this var contains some info about a keyboard event, then process it
{
; Input is blocked and this is a user pressed / released button
if (input.iskeyboardBlocked() && !(NumGet(lParam+8) & 0x10)) ; LLKHF_INJECTED
return -1
}
Return CallNextHookEx(nCode, wParam, lParam) ; make sure other hooks in the chain receive this event if we didn't process it
}
; I always allow a mouse move command through
MouseHook(nCode, wParam, lParam)
{
Critical 1000
If (!nCode && wParam != 0x200) ;WM_MOUSEMOVE := 0x200
{
; Input is blocked and this is a user pressed / released button
if (input.isMouseBlocked() && !(NumGet(lParam+12) & 0x10)) ; LLKHF_INJECTED
return -1
}
Return CallNextHookEx(nCode, wParam, lParam) ; make sure other hooks in the chain receive this event if we didn't process it
}
SetWindowsHookEx(idHook, pfn)
{
Return DllCall("SetWindowsHookEx", "int", idHook, "Uint", pfn, "Uint", DllCall("GetModuleHandle", "Uint", 0), "Uint", 0)
}
UnhookWindowsHookEx(hHook)
{
Return DllCall("UnhookWindowsHookEx", "Uint", hHook)
}
CallNextHookEx(nCode, wParam, lParam, hHook = 0)
{
Return DllCall("CallNextHookEx", "Uint", hHook, "int", nCode, "Uint", wParam, "Uint", lParam)
}