diff --git a/Combined Example.ahk b/AHK v1/Combined Example.ahk similarity index 100% rename from Combined Example.ahk rename to AHK v1/Combined Example.ahk diff --git a/Context Example.ahk b/AHK v1/Context Example.ahk similarity index 100% rename from Context Example.ahk rename to AHK v1/Context Example.ahk diff --git a/Development Tools/AhiScanCodeTester.ahk b/AHK v1/Development Tools/AhiScanCodeTester.ahk similarity index 100% rename from Development Tools/AhiScanCodeTester.ahk rename to AHK v1/Development Tools/AhiScanCodeTester.ahk diff --git a/Development Tools/AhkScanCodeTester.ahk b/AHK v1/Development Tools/AhkScanCodeTester.ahk similarity index 100% rename from Development Tools/AhkScanCodeTester.ahk rename to AHK v1/Development Tools/AhkScanCodeTester.ahk diff --git a/Lib/AutoHotInterception.ahk b/AHK v1/Lib/AutoHotInterception.ahk similarity index 100% rename from Lib/AutoHotInterception.ahk rename to AHK v1/Lib/AutoHotInterception.ahk diff --git a/Lib/CLR.ahk b/AHK v1/Lib/CLR.ahk similarity index 100% rename from Lib/CLR.ahk rename to AHK v1/Lib/CLR.ahk diff --git a/Lib/Unblocker.ps1 b/AHK v1/Lib/Unblocker.ps1 similarity index 100% rename from Lib/Unblocker.ps1 rename to AHK v1/Lib/Unblocker.ps1 diff --git a/Monitor.ahk b/AHK v1/Monitor.ahk similarity index 100% rename from Monitor.ahk rename to AHK v1/Monitor.ahk diff --git a/AHK v1/Monitor.exe b/AHK v1/Monitor.exe new file mode 100644 index 0000000..194d982 Binary files /dev/null and b/AHK v1/Monitor.exe differ diff --git a/RollMouse.ahk b/AHK v1/RollMouse.ahk similarity index 100% rename from RollMouse.ahk rename to AHK v1/RollMouse.ahk diff --git a/SubscribeAbsolute dragging example.ahk b/AHK v1/SubscribeAbsolute dragging example.ahk similarity index 100% rename from SubscribeAbsolute dragging example.ahk rename to AHK v1/SubscribeAbsolute dragging example.ahk diff --git a/SubscribeAbsolute example.ahk b/AHK v1/SubscribeAbsolute example.ahk similarity index 100% rename from SubscribeAbsolute example.ahk rename to AHK v1/SubscribeAbsolute example.ahk diff --git a/SubscribeAll Example.ahk b/AHK v1/SubscribeAll Example.ahk similarity index 100% rename from SubscribeAll Example.ahk rename to AHK v1/SubscribeAll Example.ahk diff --git a/Subscription Example.ahk b/AHK v1/Subscription Example.ahk similarity index 100% rename from Subscription Example.ahk rename to AHK v1/Subscription Example.ahk diff --git a/TabletButtonBuilder.ahk b/AHK v1/TabletButtonBuilder.ahk similarity index 100% rename from TabletButtonBuilder.ahk rename to AHK v1/TabletButtonBuilder.ahk diff --git a/TabletButtons.ahk b/AHK v1/TabletButtons.ahk similarity index 100% rename from TabletButtons.ahk rename to AHK v1/TabletButtons.ahk diff --git a/TabletLib/JSON.ahk b/AHK v1/TabletLib/JSON.ahk similarity index 100% rename from TabletLib/JSON.ahk rename to AHK v1/TabletLib/JSON.ahk diff --git a/TabletLib/TabletLib.ahk b/AHK v1/TabletLib/TabletLib.ahk similarity index 100% rename from TabletLib/TabletLib.ahk rename to AHK v1/TabletLib/TabletLib.ahk diff --git a/Unsubscription Example.ahk b/AHK v1/Unsubscription Example.ahk similarity index 100% rename from Unsubscription Example.ahk rename to AHK v1/Unsubscription Example.ahk diff --git a/AHK v2/Context Example.ahk b/AHK v2/Context Example.ahk new file mode 100644 index 0000000..ffd77ed --- /dev/null +++ b/AHK v2/Context Example.ahk @@ -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 +} \ No newline at end of file diff --git a/AHK v2/Lib/AutoHotInterception.ahk b/AHK v2/Lib/AutoHotInterception.ahk new file mode 100644 index 0000000..13f502a --- /dev/null +++ b/AHK v2/Lib/AutoHotInterception.ahk @@ -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) + } + } +} \ No newline at end of file diff --git a/AHK v2/Lib/CLR.ahk b/AHK v2/Lib/CLR.ahk new file mode 100644 index 0000000..57fe49b --- /dev/null +++ b/AHK v2/Lib/CLR.ahk @@ -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 +} diff --git a/AHK v2/Lib/Unblocker.ps1 b/AHK v2/Lib/Unblocker.ps1 new file mode 100644 index 0000000..dbbd5fe --- /dev/null +++ b/AHK v2/Lib/Unblocker.ps1 @@ -0,0 +1 @@ +Get-ChildItem -Path '.' -Recurse | Unblock-File \ No newline at end of file diff --git a/AHK v2/Monitor.ahk b/AHK v2/Monitor.ahk new file mode 100644 index 0000000..a5c48bd --- /dev/null +++ b/AHK v2/Monitor.ahk @@ -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 +} diff --git a/AHK v2/SubscribeAll Example.ahk b/AHK v2/SubscribeAll Example.ahk new file mode 100644 index 0000000..549cba4 --- /dev/null +++ b/AHK v2/SubscribeAll Example.ahk @@ -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 +} \ No newline at end of file diff --git a/AHK v2/Subscription Example.ahk b/AHK v2/Subscription Example.ahk new file mode 100644 index 0000000..34779c9 --- /dev/null +++ b/AHK v2/Subscription Example.ahk @@ -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 +} \ No newline at end of file diff --git a/AHK v2/test.ahk b/AHK v2/test.ahk new file mode 100644 index 0000000..8c11a42 --- /dev/null +++ b/AHK v2/test.ahk @@ -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 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 425ce05..b47543d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/FolderSetup.gif b/FolderSetup.gif new file mode 100644 index 0000000..007a3fc Binary files /dev/null and b/FolderSetup.gif differ diff --git a/Lib/x64/Readme.txt b/Lib/x64/Readme.txt deleted file mode 100644 index 30bd14a..0000000 --- a/Lib/x64/Readme.txt +++ /dev/null @@ -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!! diff --git a/Lib/x86/Readme.txt b/Lib/x86/Readme.txt deleted file mode 100644 index caf59cb..0000000 --- a/Lib/x86/Readme.txt +++ /dev/null @@ -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!! diff --git a/README.md b/README.md index d897a19..3361b9f 100644 --- a/README.md +++ b/README.md @@ -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 `, 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(, , , , )` -`UnsubscribeKey(, )` +`UnsubscribeKey(, )` 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(, , , )` 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(,