Merge pull request #99 from evilC/ahk-v2-support

Ahk v2 support
ahk-v2-support
Clive Galway 1 year ago committed by GitHub
commit 696be1db5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

Binary file not shown.

@ -0,0 +1,28 @@
#SingleInstance force
Persistent
#include Lib\AutoHotInterception.ahk
AHI := AutoHotInterception()
id1 := AHI.GetKeyboardId(0x04F2, 0x0112, 1)
cm1 := AHI.CreateContextManager(id1)
return
#HotIf cm1.IsActive
::aaa::JACKPOT
1::
{
ToolTip("KEY DOWN EVENT @ " A_TickCount)
return
}
1 up::
{
ToolTip("KEY UP EVENT @ " A_TickCount)
return
}
#HotIf
^Esc::
{
ExitApp
}

@ -0,0 +1,264 @@
#include %A_LineFile%\..\CLR.ahk
class AutoHotInterception {
_contextManagers := Map()
__New() {
bitness := A_PtrSize == 8 ? "x64" : "x86"
dllName := "interception.dll"
if (A_IsCompiled) {
dllFile := A_LineFile "\..\Lib\" bitness "\" dllName
DirCreate("Lib")
FileInstall("Lib\AutoHotInterception.dll", "Lib\AutoHotInterception.dll")
if (bitness == "x86") {
DirCreate("Lib\x86")
FileInstall("Lib\x86\interception.dll", "Lib\x86\interception.dll")
} else {
DirCreate("Lib\x64")
FileInstall("Lib\x64\interception.dll", "Lib\x64\interception.dll")
}
} else {
dllFile := A_LineFile "\..\" bitness "\" dllName
}
if (!FileExist(dllFile)) {
MsgBox("Unable to find " dllFile ", exiting...`nYou should extract both x86 and x64 folders from the library folder in interception.zip into AHI's lib folder.")
ExitApp
}
hModule := DllCall("LoadLibrary", "Str", dllFile, "Ptr")
if (hModule == 0) {
this_bitness := A_PtrSize == 8 ? "64-bit" : "32-bit"
other_bitness := A_PtrSize == 4 ? "64-bit" : "32-bit"
MsgBox("Bitness of " dllName " does not match bitness of AHK.`nAHK is " this_bitness ", but " dllName " is " other_bitness ".")
ExitApp
}
DllCall("FreeLibrary", "Ptr", hModule)
dllName := "AutoHotInterception.dll"
if (A_IsCompiled) {
dllFile := A_LineFile "\..\Lib\" dllName
} else {
dllFile := A_LineFile "\..\" dllName
}
hintMessage := "Try right-clicking " dllFile ", select Properties, and if there is an 'Unblock' checkbox, tick it`nAlternatively, running Unblocker.ps1 in the lib folder (ideally as admin) can do this for you."
if (!FileExist(dllFile)) {
MsgBox("Unable to find " dllFile ", exiting...")
ExitApp
}
asm := CLR_LoadLibrary(dllFile)
try {
this.Instance := asm.CreateInstance("AutoHotInterception.Manager")
}
catch {
MsgBox(dllName " failed to load`n`n" hintMessage)
ExitApp
}
if (this.Instance.OkCheck() != "OK") {
MsgBox(dllName " loaded but check failed!`n`n" hintMessage)
ExitApp
}
}
GetInstance() {
return this.Instance
}
; --------------- Input Synthesis ----------------
SendKeyEvent(id, code, state) {
this.Instance.SendKeyEvent(id, code, state)
}
SendMouseButtonEvent(id, btn, state) {
this.Instance.SendMouseButtonEvent(id, btn, state)
}
SendMouseButtonEventAbsolute(id, btn, state, x, y) {
this.Instance.SendMouseButtonEventAbsolute(id, btn, state, x, y)
}
SendMouseMove(id, x, y) {
this.Instance.SendMouseMove(id, x, y)
}
SendMouseMoveRelative(id, x, y) {
this.Instance.SendMouseMoveRelative(id, x, y)
}
SendMouseMoveAbsolute(id, x, y) {
this.Instance.SendMouseMoveAbsolute(id, x, y)
}
SetState(state) {
this.Instance.SetState(state)
}
MoveCursor(x, y, cm := "Screen", mouseId := -1) {
if (mouseId == -1)
mouseId := 11 ; Use 1st found mouse
oldMode := A_CoordModeMouse
CoordMode("Mouse", cm)
Loop {
MouseGetPos(&cx, &cy)
dx := this.GetDirection(cx, x)
dy := this.GetDirection(cy, y)
if (dx == 0 && dy == 0)
break
this.SendMouseMove(mouseId, dx, dy)
}
CoordMode("Mouse", oldMode)
}
GetDirection(cp, dp) {
d := dp - cp
if (d > 0)
return 1
if (d < 0)
return -1
return 0
}
; --------------- Querying ------------------------
GetDeviceId(IsMouse, VID, PID, instance := 1) {
static devType := Map(0, "Keyboard", 1, "Mouse")
dev := this.Instance.GetDeviceId(IsMouse, VID, PID, instance)
if (dev == 0) {
MsgBox("Could not get " devType[isMouse] " with VID " VID ", PID " PID ", Instance " instance)
ExitApp
}
return dev
}
GetDeviceIdFromHandle(isMouse, handle, instance := 1) {
static devType := Map(0, "Keyboard", 1, "Mouse")
dev := this.Instance.GetDeviceIdFromHandle(IsMouse, handle, instance)
if (dev == 0) {
MsgBox("Could not get " devType[isMouse] " with Handle " handle ", Instance " instance)
ExitApp
}
return dev
}
GetKeyboardId(VID, PID, instance := 1) {
return this.GetDeviceId(false, VID, PID, instance)
}
GetMouseId(VID, PID, instance := 1) {
return this.GetDeviceId(true, VID, PID, instance)
}
GetKeyboardIdFromHandle(handle, instance := 1) {
return this.GetDeviceIdFromHandle(false, handle, instance)
}
GetMouseIdFromHandle(handle, instance := 1) {
return this.GetDeviceIdFromHandle(true, handle, instance)
}
GetDeviceList() {
DeviceList := Map()
arr := this.Instance.GetDeviceList()
for v in arr {
; ToDo: Return a class, so code completion works?
DeviceList[v.id] := { ID: v.id, VID: v.vid, PID: v.pid, IsMouse: v.IsMouse, Handle: v.Handle }
}
return DeviceList
}
; ---------------------- Subscription Mode ----------------------
SubscribeKey(id, code, block, callback, concurrent := false) {
this.Instance.SubscribeKey(id, code, block, callback, concurrent)
}
UnsubscribeKey(id, code) {
this.Instance.UnsubscribeKey(id, code)
}
SubscribeKeyboard(id, block, callback, concurrent := false) {
this.Instance.SubscribeKeyboard(id, block, callback, concurrent)
}
UnsubscribeKeyboard(id) {
this.Instance.UnsubscribeKeyboard(id)
}
SubscribeMouseButton(id, btn, block, callback, concurrent := false) {
this.Instance.SubscribeMouseButton(id, btn, block, callback, concurrent)
}
UnsubscribeMouseButton(id, btn) {
this.Instance.UnsubscribeMouseButton(id, btn)
}
SubscribeMouseButtons(id, block, callback, concurrent := false) {
this.Instance.SubscribeMouseButtons(id, block, callback, concurrent)
}
UnsubscribeMouseButtons(id) {
this.Instance.UnsubscribeMouseButtons(id)
}
SubscribeMouseMove(id, block, callback, concurrent := false) {
this.Instance.SubscribeMouseMove(id, block, callback, concurrent)
}
UnsubscribeMouseMove(id) {
this.Instance.UnsubscribeMouseMove(id)
}
SubscribeMouseMoveRelative(id, block, callback, concurrent := false) {
this.Instance.SubscribeMouseMoveRelative(id, block, callback, concurrent)
}
UnsubscribeMouseMoveRelative(id) {
this.Instance.UnsubscribeMouseMoveRelative(id)
}
SubscribeMouseMoveAbsolute(id, block, callback, concurrent := false) {
this.Instance.SubscribeMouseMoveAbsolute(id, block, callback, concurrent)
}
UnsubscribeMouseMoveAbsolute(id) {
this.Instance.UnsubscribeMouseMoveAbsolute(id)
}
; ------------- Context Mode ----------------
; Creates a context class to make it easy to turn on/off the hotkeys
CreateContextManager(id) {
if (this._contextManagers.Has(id)) {
Msgbox("ID " id " already has a Context Manager")
ExitApp
}
cm := AutoHotInterception.ContextManager(this, id)
this._contextManagers[id] := cm
return cm
}
RemoveContextManager(id) {
if (!this._contextManagers.Has(id)) {
Msgbox("ID " id " does not have a Context Manager")
ExitApp
}
this._contextManagers[id].Remove()
this._contextManagers.Delete(id)
}
; Helper class for dealing with context mode
class ContextManager {
IsActive := 0
__New(parent, id) {
this.parent := parent
this.id := id
result := this.parent.Instance.SetContextCallback(id, this.OnContextCallback.Bind(this))
}
OnContextCallback(state) {
Sleep 0
this.IsActive := state
}
Remove() {
this.parent.Instance.RemoveContextCallback(this.id)
}
}
}

@ -0,0 +1,142 @@
; ==========================================================
; .NET Framework Interop
; https://www.autohotkey.com/boards/viewtopic.php?t=4633
; ==========================================================
;
; Author: Lexikos
; Version: 2.0
; Requires: AutoHotkey v2.0-beta.1
;
CLR_LoadLibrary(AssemblyName, AppDomain:=0)
{
if !AppDomain
AppDomain := CLR_GetDefaultDomain()
try
return AppDomain.Load_2(AssemblyName)
static null := ComValue(13,0)
args := ComObjArray(0xC, 1), args[0] := AssemblyName
typeofAssembly := AppDomain.GetType().Assembly.GetType()
try
return typeofAssembly.InvokeMember_3("LoadWithPartialName", 0x158, null, null, args)
catch
return typeofAssembly.InvokeMember_3("LoadFrom", 0x158, null, null, args)
}
CLR_CreateObject(Assembly, TypeName, Args*)
{
if !(argCount := Args.Length)
return Assembly.CreateInstance_2(TypeName, true)
vargs := ComObjArray(0xC, argCount)
Loop argCount
vargs[A_Index-1] := Args[A_Index]
static Array_Empty := ComObjArray(0xC,0), null := ComValue(13,0)
return Assembly.CreateInstance_3(TypeName, true, 0, null, vargs, null, Array_Empty)
}
CLR_CompileCS(Code, References:="", AppDomain:=0, FileName:="", CompilerOptions:="")
{
return CLR_CompileAssembly(Code, References, "System", "Microsoft.CSharp.CSharpCodeProvider", AppDomain, FileName, CompilerOptions)
}
CLR_CompileVB(Code, References:="", AppDomain:=0, FileName:="", CompilerOptions:="")
{
return CLR_CompileAssembly(Code, References, "System", "Microsoft.VisualBasic.VBCodeProvider", AppDomain, FileName, CompilerOptions)
}
CLR_StartDomain(&AppDomain, BaseDirectory:="")
{
static null := ComValue(13,0)
args := ComObjArray(0xC, 5), args[0] := "", args[2] := BaseDirectory, args[4] := ComValue(0xB,false)
AppDomain := CLR_GetDefaultDomain().GetType().InvokeMember_3("CreateDomain", 0x158, null, null, args)
}
; ICorRuntimeHost::UnloadDomain
CLR_StopDomain(AppDomain) => ComCall(20, CLR_Start(), "ptr", ComObjValue(AppDomain))
; NOTE: IT IS NOT NECESSARY TO CALL THIS FUNCTION unless you need to load a specific version.
CLR_Start(Version:="") ; returns ICorRuntimeHost*
{
static RtHst := 0
; The simple method gives no control over versioning, and seems to load .NET v2 even when v4 is present:
; return RtHst ? RtHst : (RtHst:=COM_CreateObject("CLRMetaData.CorRuntimeHost","{CB2F6722-AB3A-11D2-9C40-00C04FA30A3E}"), DllCall(NumGet(NumGet(RtHst+0)+40),"uint",RtHst))
if RtHst
return RtHst
if Version = ""
Loop Files EnvGet("SystemRoot") "\Microsoft.NET\Framework" (A_PtrSize=8?"64":"") "\*","D"
if (FileExist(A_LoopFilePath "\mscorlib.dll") && StrCompare(A_LoopFileName, Version) > 0)
Version := A_LoopFileName
static CLSID_CorRuntimeHost := CLR_GUID("{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}")
static IID_ICorRuntimeHost := CLR_GUID("{CB2F6722-AB3A-11D2-9C40-00C04FA30A3E}")
DllCall("mscoree\CorBindToRuntimeEx", "wstr", Version, "ptr", 0, "uint", 0
, "ptr", CLSID_CorRuntimeHost, "ptr", IID_ICorRuntimeHost
, "ptr*", &RtHst:=0, "hresult")
ComCall(10, RtHst) ; Start
return RtHst
}
;
; INTERNAL FUNCTIONS
;
CLR_GetDefaultDomain()
{
; ICorRuntimeHost::GetDefaultDomain
static defaultDomain := (
ComCall(13, CLR_Start(), "ptr*", &p:=0),
ComObjFromPtr(p)
)
return defaultDomain
}
CLR_CompileAssembly(Code, References, ProviderAssembly, ProviderType, AppDomain:=0, FileName:="", CompilerOptions:="")
{
if !AppDomain
AppDomain := CLR_GetDefaultDomain()
asmProvider := CLR_LoadLibrary(ProviderAssembly, AppDomain)
codeProvider := asmProvider.CreateInstance(ProviderType)
codeCompiler := codeProvider.CreateCompiler()
asmSystem := (ProviderAssembly="System") ? asmProvider : CLR_LoadLibrary("System", AppDomain)
; Convert | delimited list of references into an array.
Refs := References is String ? StrSplit(References, "|", " `t") : References
aRefs := ComObjArray(8, Refs.Length)
Loop Refs.Length
aRefs[A_Index-1] := Refs[A_Index]
; Set parameters for compiler.
prms := CLR_CreateObject(asmSystem, "System.CodeDom.Compiler.CompilerParameters", aRefs)
, prms.OutputAssembly := FileName
, prms.GenerateInMemory := FileName=""
, prms.GenerateExecutable := SubStr(FileName,-4)=".exe"
, prms.CompilerOptions := CompilerOptions
, prms.IncludeDebugInformation := true
; Compile!
compilerRes := codeCompiler.CompileAssemblyFromSource(prms, Code)
if error_count := (errors := compilerRes.Errors).Count
{
error_text := ""
Loop error_count
error_text .= ((e := errors.Item[A_Index-1]).IsWarning ? "Warning " : "Error ") . e.ErrorNumber " on line " e.Line ": " e.ErrorText "`n`n"
throw Error("Compilation failed",, "`n" error_text)
}
; Success. Return Assembly object or path.
return FileName="" ? compilerRes.CompiledAssembly : compilerRes.PathToAssembly
}
; Usage 1: pGUID := CLR_GUID(&GUID, "{...}")
; Usage 2: GUID := CLR_GUID("{...}"), pGUID := GUID.Ptr
CLR_GUID(a, b:=unset)
{
DllCall("ole32\IIDFromString"
, "wstr", sGUID := IsSet(b) ? b : a
, "ptr", GUID := Buffer(16,0), "hresult")
return IsSet(b) ? GUID.Ptr : GUID
}

@ -0,0 +1 @@
Get-ChildItem -Path '.' -Recurse | Unblock-File

@ -0,0 +1,242 @@
/*
Script to show data flowing from Interception
*/
#SingleInstance force
Persistent
#include Lib\AutoHotInterception.ahk
OutputDebug("DBGVIEWCLEAR")
monitorGui := Gui("", "AutoHotInterception Monitor")
monitorGui.MarginX := 0
monitorGui.MarginY := 0
monitorGui.OnEvent("Close", GuiClosed)
DeviceList := {}
filterMouseMove := 1
filterKeyPress := 0
AHI := AutoHotInterception()
; Device List
DeviceList := AHI.GetDeviceList()
marginX := 10
marginY := 10
idW := 50 ; Width of the ID text
vhOff := 7 ; Width to space VIDPID / Handle above/below ID row
copyW := 40 ; Width of the Copy buttons
outputH := 350 ; Height of the Output boxes
rowH := 35 ; Distance between each row of devices
maxWidths := Map("K", 0, "M", 0) ; Max Width of device entries for each column
totalWidths := Map("K", 0, "M", 0) ; Total Width of each column
tW := Map("K", 0, "M", 0)
devTypes := ["K", "M"] ; Lookup table for device type
starts := Map("K", 0, "M", 10) ; Start IDs for each device type
columnTitles := Map("K", "Keyboards", "M", "Mice") ; Column Titles
columnX := Map("K", 0, "M", 0)
Loop 2 {
strings := Map()
devType := devTypes[A_Index]
columnX[devType] := GetColX(devType)
start := starts[devType]
UpdateWidth(0, 1) ; Reset max width
; Add device entries
Loop 10 {
i := start + A_Index
if (!DeviceList.Has(i)){
continue
}
dev := DeviceList[i]
rowY := (marginY * 3) + ((A_Index - 1) * rowH)
chkDevice := monitorGui.Add("Checkbox", "x" columnX[devType] " y" rowY " w" idW, "ID: " dev.id)
chkDevice.OnEvent("Click", CheckboxChanged.Bind(dev.id))
lowest := UpdateLowest(chkDevice)
strings[A_index] := {vid:FormatHex(dev.VID), pid: FormatHex(dev.PID), handle: dev.Handle}
textVidPid := monitorGui.Add("Text", "x" columnX[devType] + idW " y" rowY - vhOff, "VID / PID:`t0x" strings[A_index].vid ", 0x" strings[A_index].pid)
maxWidths[devType] := UpdateWidth(textVidPid)
textHandle := monitorGui.Add("Text", "x" columnX[devType] + idW " y" rowY + vhOff, "Handle:`t`t" StrReplace(strings[A_index].Handle, "&", "&&"))
maxWidths[devType] := UpdateWidth(textHandle)
}
; Add copy buttons
Loop 10 {
i := start + A_Index
if (!DeviceList.Has(i)){
continue
}
dev := DeviceList[i]
rowY := (marginY * 3) + ((A_Index - 1) * rowH)
xpos := columnX[devType] + idW + maxWidths[devType]
btnCopyVidPid := monitorGui.Add("Button", "x" xpos " y" rowY - vhOff " h14 w" copyW, "Copy")
btnCopyVidPid.OnEvent("Click", CopyClipboard.Bind("0x" strings[A_index].vid ", 0x" strings[A_index].pid))
btnCopyHandle := monitorGui.Add("Button", "x" xpos " y" rowY + vhOff " h14 w" copyW, "Copy")
btnCopyHandle.OnEvent("Click", CopyClipboard.Bind(strings[A_index].handle))
}
totalWidths[devType] := idW + maxWidths[devType] + copyW
monitorGui.Add("Text", "x" columnX[devType] " y5 w" totalWidths[devType] " Center", columnTitles[devType])
}
lowest += 2 * MarginY
; Options
chkFilterPress := monitorGui.Add("CheckBox", "x" columnX["K"] " y" lowest, "Only show key releases")
chkFilterPress.OnEvent("Click", FilterPress)
chkFilterMove := monitorGui.Add("CheckBox", "x" columnX["M"] " w" totalWidths[devType] " yp Checked", "Filter Movement (Warning: Turning off can cause crashes)")
chkFilterMove.OnEvent("Click", FilterMove)
lowest += 2 * MarginY
btnClearKeyboard := monitorGui.Add("Button", "x" columnX["K"] " y" lowest " w" totalWidths["K"] " Center", "Clear")
btnClearKeyboard.OnEvent("Click", ClearKeyboard)
btnClearMouse := monitorGui.Add("Button", "x" columnX["M"] " yp w" totalWidths["M"] " Center", "Clear")
btnClearMouse.OnEvent("Click", ClearMouse)
lowest += 30
; Output
lvKeyboard := monitorGui.Add("ListView", "x" columnX["K"] " y" lowest " w" totalWidths["K"] " h" outputH, ["ID", "Code", "State", "Key Name"])
lvKeyboard.ModifyCol(4, 100)
lvMouse := monitorGui.Add("ListView", "x" columnX["M"] " yp w" totalWidths["M"] " h" outputH, ["ID", "Code", "State", "X", "Y", "Info"])
lvMouse.ModifyCol(6, 200)
lowest += outputH
monitorGui.Show("w" (marginX * 3) + totalWidths["K"] + totalWidths["M"] " h" marginY + lowest)
return
GetColX(devType){
global marginX, idW, maxWidths, copyW
if (devType == "K")
return marginX
else
return (marginX * 2) + idW + maxWidths["K"] + copyW
}
UpdateLowest(ctrl){
static max := 0
ctrl.GetPos(&cpX, &cpY, &cpW, &cpH)
pos := cpY + cpH
if (pos > max){
max := pos
}
return max
}
UpdateWidth(ctrl, reset := 0){
static max := 0
if (reset){
max := 0
return
}
ctrl.GetPos(&cpX, &cpY, &cpW, &cpH)
if (cpW > max){
max := cpW
}
return max
}
CheckboxChanged(id, ctrl, info){
global AHI
if (ctrl.Value){
if (id < 11){
AHI.SubscribeKeyboard(id, false, KeyboardEvent.Bind(id))
} else {
AHI.SubscribeMouseButtons(id, false, MouseButtonEvent.Bind(id))
AHI.SubscribeMouseMoveRelative(id, false, MouseAxisEvent.Bind(id, "Relative Move"))
AHI.SubscribeMouseMoveAbsolute(id, false, MouseAxisEvent.Bind(id, "Absolute Move"))
}
} else {
if (id < 11){
AHI.UnsubscribeKeyboard(id)
} else {
AHI.UnsubscribeMouseButtons(id)
AHI.UnsubscribeMouseMoveRelative(id)
AHI.UnsubscribeMouseMoveAbsolute(id)
}
}
}
FilterMove(ctrl, info){
global filterMouseMove
filterMouseMove := ctrl.Value
}
FilterPress(ctrl, info){
global filterKeyPress
filterKeyPress := ctrl.Value
}
ClearKeyboard(ctrl, info){
global lvKeyboard
lvKeyboard.Delete()
}
ClearMouse(ctrl, info){
global lvMouse
lvMouse.Delete()
}
FormatHex(num){
return Format("{:04X}", num)
}
KeyboardEvent(id, code, state){
global lvKeyboard, filterKeyPress
if (filterKeyPress && state)
return
scanCode := Format("{:x}", code)
keyName := GetKeyName("SC" scanCode)
row := lvKeyboard.Add(, id, code, state, keyName)
lvKeyboard.Modify(row, "Vis")
}
MouseButtonEvent(id, code, state){
global lvMouse
row := lvMouse.Add(, id, code, state, "", "", "Button")
lvMouse.Modify(row, "Vis")
}
MouseAxisEvent(id, info, x, y){
global lvMouse, filterMouseMove
if (filterMouseMove)
return
row := lvMouse.Add(, id, "", "", x, y, info)
lvMouse.Modify(row, "Vis")
}
CopyClipboard(str, ctrl, info){
A_Clipboard := str
Tooltip("Copied to Clipboard")
SetTimer(ClearTooltip, 1000)
}
ClearTooltip(){
ToolTip
}
GuiClosed(gui){
ExitApp
}
^Esc::
{
ExitApp
}

@ -0,0 +1,25 @@
#SingleInstance force
Persistent
#include Lib\AutoHotInterception.ahk
AHI := AutoHotInterception()
keyboardId := AHI.GetKeyboardId(0x04F2, 0x0112)
AHI.SubscribeKeyboard(keyboardId, true, KeyEvent)
mouseId := AHI.GetMouseId(0x046D, 0xC00C)
AHI.SubscribeMouseButtons(mouseId, false, MouseButtonEvent)
return
KeyEvent(code, state){
ToolTip("Keyboard Key - Code: " code ", State: " state)
}
MouseButtonEvent(code, state){
ToolTip("Mouse Button - Code: " code ", State: " state)
}
^Esc::
{
ExitApp
}

@ -0,0 +1,18 @@
#SingleInstance force
Persistent
#include Lib\AutoHotInterception.ahk
AHI := AutoHotInterception()
keyboardId := AHI.GetKeyboardId(0x04F2, 0x0112)
AHI.SubscribeKey(keyboardId, GetKeySC("1"), true, KeyEvent)
return
KeyEvent(state){
ToolTip("State: " state)
}
^Esc::
{
ExitApp
}

@ -0,0 +1,61 @@
#Include Lib\AutoHotInterception.ahk
AHI := AutoHotInterception()
enabled := true
; Wooting Two: HID\VID_03EB&PID_FF02&REV_0090&MI_03
; G604: 0x046D, 0xC539
; keyboardId := AHI.GetKeyboardIdFromHandle("HID\VID_03EB&PID_FF02&REV_0090&MI_03")
;mouseId := AHI.GetMouseId(0x046D, 0xC539)
keyboardId := AHI.GetKeyboardId(0x04F2, 0x0112)
; AHI.SubscribeKey(keyboardId, GetKeySC("a"), false, KbSubscribeTest)
; AHI.SubscribeKeyboard(keyboardId, false, KbSubscribeAllTest)
; l := AHI.GetDeviceList()
; AHI.SubscribeMouseButton(mouseId, 1, false, MBSubscribeTest)
; AHI.SubscribeMouseMove(mouseId, false, MASubscribeTest)
cm1 := AHI.CreateContextManager(keyboardId)
return
KbSubscribeTest(state){
global AHI, keyboardId
ToolTip("KBSubscribe`nState: " state)
AHI.SendKeyEvent(keyboardId, GetKeySC("b"), state)
}
KbSubscribeAllTest(code, state){
ToolTip("KbSubscribeAll`ncode: " code "`nstate: " state)
}
MBSubscribeTest(state){
ToolTip("MBSubscribe`nstate: " state)
}
MASubscribeTest(x, y){
ToolTip("MASubscribe`nx: " x "`ny: " y)
}
x::
{
global enabled
enabled := !enabled
AHI.SetState(enabled)
}
#HotIf cm1.IsActive
::aaa::JACKPOT
1::
{
ToolTip("KEY DOWN EVENT @ " A_TickCount)
return
}
1 up::
{
ToolTip("KEY UP EVENT @ " A_TickCount)
return
}
#HotIf
^Esc::ExitApp

@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Removed
### Fixed
## [0.9.0] - 2023-03-21
### Added
- Added support for AHK v2
## [0.8.0] - 2022-02-20
### Added
- Add two SubscribeAbsolute example scripts which show how to process movement data coming from a tablet

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

@ -1,2 +0,0 @@
To use the AutoHotkey library, you should copy `interception.dll` into this folder from `library\x64` in the Interception zip
YOU MAY ALSO NEED TO RUN UNBLOCKER.PS1 AS ADMIN!!

@ -1,2 +0,0 @@
To use the AutoHotkey library, you should copy `interception.dll` into this folder from `library\x86` in the Interception zip
YOU MAY ALSO NEED TO RUN UNBLOCKER.PS1 AS ADMIN!!

@ -8,7 +8,9 @@
AutoHotInterception (AHI) allows you to execute AutoHotkey code in response to events from a *specific* keyboard or mouse, whilst (optionally) blocking the native functionality (i.e. stopping Windows from seeing that keyboard or mouse event).
In other words, you can use a key on a second (or third, or fourth...) keyboard to trigger AHK code, and that key will not be seen by applications. You can use the *same key* on multiple keyboards for individual actions.
Keyboard Keys, Mouse Buttons and Mouse movement (Both Relative and Absolute modes) are supported.
For example, you could have 3 keyboards connected, and on the 1st (Main keyboard), no changes are applied, but on keyboard #2, when you press `F1`, it replaces it with `a`, and on keyboard #3, when you press `F1`, it replaces it with `b`.
Keyboard Keys, Mouse Buttons and Mouse movement (Both Relative and Absolute modes) are supported.
Both AHK v1 and AHK v2 are supported.
AHI uses the Interception driver by Francisco Lopez
@ -60,16 +62,26 @@ There is nothing I can do to fix this issue, it is a limitation of the Intercept
# Setup
1. Download and install the [Interception Driver](http://www.oblita.com/interception)
## Install the Intereception driver
Download and install the [Interception Driver](http://www.oblita.com/interception)
Note that you **must** run `install-interception.exe` at an admin command prompt (**Not double-click it**) - once you do so, it will instruct you to execute `install-interception.exe /install` to actually perform the install.
Here is a GIF showing the process:
![](https://github.com/evilC/AutoHotInterception/blob/master/InterceptionInstall.gif)
2. Download an AHI release from the [releases page](https://github.com/evilC/AutoHotInterception/releases) and extract it to a folder.
## Build your AutoHotInterception folder
1. Download an AHI release from the [releases page](https://github.com/evilC/AutoHotInterception/releases) and extract it to a folder.
DO NOT use the "Clone or Download" link on the main page.
This is the folder where (at least initially) you will be running scripts from.
It contains a number of sample `.ahk` scripts and a `lib` folder, which contains all the AHI libraries.
3. In the Interception installer zip, there is a `library` folder containing `x86` and `x64` folders.
Copy both of these folders into the AHI `lib` folder that you created in step (3) - the folder structure should end up looking like:
2. From the AHI release zip, extract EITHER the `AHK v1` folder **OR** the `AHK v2` folder to somewhere on your disk.
This is the "working folder" where (at least initially) you will be running scripts from.
It contains a number of sample `.ahk` scripts and a `lib` folder.
3. From the AHI release zip, extract `AutoHotInterception.dll` from the `Common\lib` folder and place it into `lib` in your "working folder"
4. In the Interception installer zip, there is a `library` folder containing `x86` and `x64` folders.
Copy both of these folders into the `lib` folder in your "working" folder.
Example for AHK v1 - the "working folder" is on the left, top right is the AutoHotInterception zip, bottom right is the Interception zip.
![](https://github.com/evilC/AutoHotInterception/blob/master/FolderSetup.gif)
The folder structure should end up looking like:
```
AHI Root Folder
Monitor.ahk
@ -91,10 +103,12 @@ This can be done manually by right clicking the DLLs, selecting Properties, and
5. Edit one of the example remapping scripts, replacing the VID/PID(s) with that of your device (Use the Monitor app to find it) and run it to make sure it works.
6. (Optional) The contents of the `lib` folder can actually be placed in one of the AutoHotkey lib folders (eg `My Documents\AutoHotkey\lib` - make it if it does not exist), and the `#include` lines of the sample scripts changed to `#include <AutoHotInterception>`, to enable your AHI scripts to be in any folder, without each needing it's own copy of the library files.
------
# Usage
## Initializing the Library
### AHK v1
Include the library
```
#Persistent ; (Interception hotkeys do not stop AHK from exiting, so use this)
@ -106,6 +120,18 @@ Initialize the library
global AHI := new AutoHotInterception()
```
### AHK v2
Include the library
```
Persistent ; (Interception hotkeys do not stop AHK from exiting, so use this)
#include Lib\AutoHotInterception.ahk
```
Initialize the library
```
global AHI := AutoHotInterception()
```
*Note*
The `AHI` variable is an AHK class that makes it easy to interact with the AutoHotInterception DLL (Which itself then interacts with the Interception dll). For example, it wraps `GetDeviceList()` to make it return a normal AHK array. Most of the time you will not need it.
For advanced users, if you wish to directly communicate with the AHI DLL (eg for best possible performance), you can call `AHI.Instance` instead of `AHI` for most functions (eg when sending of synthesized input using `SendMouseMove`).
@ -176,9 +202,11 @@ Create a Context Manager for the keyboard or mouse, pass it the Interception ID
Then Create your hotkeys, wrapped in an `#if` block that checks the `.IsActive` property of your Context Manager
(Complete, working script)
#### AHK v1
```
#include Lib\AutoHotInterception.ahk
AHI := new AutoHotInterception()
keyboard1Id := AHI.GetKeyboardId(0x04F2, 0x0112)
cm1 := AHI.CreateContextManager(keyboard1Id)
@ -194,6 +222,30 @@ cm1 := AHI.CreateContextManager(keyboard1Id)
#if ; Close the #if block
```
#### AHK v2
```
#include Lib\AutoHotInterception.ahk
AHI := AutoHotInterception()
keyboard1Id := AHI.GetKeyboardId(0x04F2, 0x0112)
cm1 := AHI.CreateContextManager(keyboard1Id)
#HotIf cm1.IsActive ; Start the #if block
::aaa::JACKPOT
1::
{
ToolTip("KEY DOWN EVENT @ " A_TickCount)
return
}
1 up::
{
ToolTip("KEY UP EVENT @ " A_TickCount)
return
}
#HotIf ; Close the #if block
```
You can remove a Context Manager using `AHI.RemoveContextManager(keyboard1Id)`
### Subscription mode
@ -209,8 +261,9 @@ True means that a new thread will be used for each callback. If your callback ha
#### Subscribing to Keyboard keys
##### Subscribe to a specific key on a specific keyboard
`SubscribeKey(<deviceId>, <scanCode>, <block>, <callback>, <concurrent>)`
`UnsubscribeKey(<deviceId>, <scanCode>)`
`UnsubscribeKey(<deviceId>, <scanCode>)`
eg
###### AHK v1
`AHI.SubscribeKey(keyboardId, GetKeySC("1"), true, Func("KeyEvent"))`
Callback function is passed state `0` (released) or `1` (pressed)
@ -220,9 +273,20 @@ KeyEvent(state){
}
```
###### AHK v2
`AHI.SubscribeKey(keyboardId, GetKeySC("1"), true, KeyEvent)`
Callback function is passed state `0` (released) or `1` (pressed)
```
KeyEvent(state){
ToolTip("State: " state)
}
```
##### Subscribe to all keys on a specific keyboard
`SubscribeKeyboard(<deviceId>, <block>, <callback>, <concurrent>)`
eg
###### AHK v1
`AHI.SubscribeKeyboard(keyboardId, true, Func("KeyEvent"))`
Callback function is passed scancode of pressed key and state
@ -232,6 +296,16 @@ KeyEvent(code, state){
}
```
###### AHK v2
`AHI.SubscribeKeyboard(keyboardId, true, KeyEvent)`
Callback function is passed scancode of pressed key and state
```
KeyEvent(code, state){
ToolTip("Keyboard Key - Code: " code ", State: " state)
}
```
#### Subscribing to Mouse Buttons
##### Subscribing to a specific button on a specific mouse
`SubscribeMouseButton(<deviceId>, <button>, <block>, <callback>, <concurrent>)`
@ -253,6 +327,7 @@ Otherwise, usage is identical to `SubscribeKey`
##### Subscribing to all buttons on a specific mouse
`SubscribeMouseButtons(<deviceId>, <block>, <callback>, <concurrent>)`
eg
###### AHK v1
`AHI.SubscribeMouseButtons(mouseId, true, Func("MouseButtonEvent"))`
Callback function is passed ID (See above) of pressed button and state
@ -262,6 +337,16 @@ MouseButtonEvent(code, state){
}
```
###### AHK v2
`AHI.SubscribeMouseButtons(mouseId, true, MouseButtonEvent)`
Callback function is passed ID (See above) of pressed button and state
```
MouseButtonEvent(code, state){
ToolTip("Mouse Button - Code: " code ", State: " state)
}
```
#### Subscribing to Mouse Movement
**Warning!** When Subscribing to mouse movement, you will get **LOTS** of callbacks.
Note the CPU usage of the demo Monitor app.
@ -278,6 +363,8 @@ Each endpoint has two naming variants for convenience, they both do the same.
`UnsubscribeMouseMove(<deviceId>)`
`UnsubscribeMouseMoveRelative(<deviceId>)`
For Mouse Movement, the callback is passed two ints - x and y.
eg
###### AHK v1
```
AHI.SubscribeMouseMove(mouseId, false, Func("MouseEvent"))
@ -286,12 +373,23 @@ MouseEvent(x, y){
}
```
###### AHK v2
```
AHI.SubscribeMouseMove(mouseId, false, MouseEvent)
MouseEvent(x, y){
[...]
}
```
##### Absolute Mode
Absolute mode is used for Graphics Tablets, Light Guns etc.
Coordinates will be in the range 0..65535
`SubscribeMouseMoveAbsolute(<deviceId>, <block>, <callback>, <concurrent>)`
`UnsubscribeMouseMoveAbsolute(<deviceId>)`
Again, the callback is passed two ints - x and y.
eg
###### AHK v1
```
AHI.SubscribeMouseMoveAbsolute(mouseId, false, Func("MouseEvent"))
@ -299,6 +397,17 @@ MouseEvent(x, y){
[...]
}
```
###### AHK v2
```
AHI.SubscribeMouseMoveAbsolute(mouseId, false, MouseEvent)
MouseEvent(x, y){
[...]
}
```
## Synthesizing Output
Note that these commands will work in both Context and Subscription modes
Also note that you can send as any device, regardless of whether you have subscribed to it in some way or not.

@ -1,58 +0,0 @@
#SingleInstance force
;~ #Persistent
#include Lib\AutoHotInterception.ahk
;~ pk := GetKeySC("Pause")
;~ dk := GetKeySC("Delete")
;~ RCtrl := GetKeySC("RCtrl")
msgbox % GetKeySC("ScrollLock")
;~ return
;~ msgbox % Format("{:d}", GetKeySC("Pause"))
return
clipboard := ""
log := "`n// ============ DUPES ==================`n"
keys := {}
Loop 512 {
hex := Format("{:x}", A_Index)
name := GetKeyName("sc" hex)
if (name == "")
continue
str := "{" A_Index " /*(0x" hex ")*/, " """" name """" "}"
;~ if (A_Index == 86 || A_Index = 342)
;~ break = true
if (keys.HasKey(name)){
log .= "// " str " (Also " keys[name] ")`n"
} else {
clipboard .= str ",`n"
keys[name] := A_Index
}
}
clipboard .= log
return
state := true
AHI := new AutoHotInterception()
;~ keyboardId := AHI.GetKeyboardId(0x04F2, 0x0112)
;~ AHI.SubscribeKey(keyboardId, GetKeySC("1"), true, Func("KeyEvent"))
mouseId := AHI.GetMouseId(0x046D, 0xC00C)
AHI.SubscribeMouseMove(mouseId, true, Func("OnMouseMove"))
return
KeyEvent(state){
ToolTip % "State: " state
}
OnMouseMove(x, y){
Tooltip % x ", " y, 0, 0
}
F1::
state := !state
AHI.SetState(state)
Tooltip
return
^Esc::
ExitApp
Loading…
Cancel
Save