Merge pull request #36 from evilC/crumbl3d-release

Crumbl3d release
hotfix/interception-mouse-button-multiple-update
Clive Galway 5 years ago committed by GitHub
commit 9e0ec46ee9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

14
.gitignore vendored

@ -1,12 +1,13 @@
C#/Dependencies/*.lib
*.dll
*.zip
*.lib
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
C#/dependencies
*.dll
*.zip
# User-specific files
*.suo
*.user
@ -23,6 +24,11 @@ C#/dependencies
[Rr]eleases/
x64/
x86/
# Do not ignore x86 / x64 folders in Lib or Dependencies folders
!Lib/x64/
!Lib/x86/
!C#/Dependencies/x64
!C#/Dependencies/x86
bld/
[Bb]in/
[Oo]bj/

@ -7,6 +7,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoHotInterception", "Auto
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "TestApp\TestApp.csproj", "{02CBCBB9-C17F-4C6A-8F93-D7EAF038CAED}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{F66D373B-9F4C-4507-8520-9078F7E68E48}"
ProjectSection(SolutionItems) = preProject
Dependencies\Readme.md = Dependencies\Readme.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

@ -40,16 +40,18 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Helpers\Helpers.cs" />
<Compile Include="lib\ManagedWrapper.cs" />
<Compile Include="Helpers\HelperFunctions.cs" />
<Compile Include="Helpers\ManagedWrapper.cs" />
<Compile Include="Manager.cs" />
<Compile Include="Monitor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>if not exist "$(TargetDir)interception.dll" xcopy "$(SolutionDir)dependencies\interception.dll" "$(TargetDir)"
</PreBuildEvent>
<PreBuildEvent>if not exist "$(TargetDir)x86" mkdir "$(TargetDir)x86"
if not exist "$(TargetDir)x64" mkdir "$(TargetDir)x64"
if not exist "$(TargetDir)x86\interception.dll" xcopy /Q /Y "$(SolutionDir)dependencies\x86\interception.dll" "$(TargetDir)x86"
if not exist "$(TargetDir)x64\interception.dll" xcopy /Q /Y "$(SolutionDir)dependencies\x64\interception.dll" "$(TargetDir)x64"</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>xcopy /Q /Y "$(TargetPath)" "$(SolutionDir)..\Lib"</PostBuildEvent>

@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace AutoHotInterception.Helpers
{
@ -14,15 +11,14 @@ namespace AutoHotInterception.Helpers
var start = isMouse ? 11 : 1;
var end = start + 9;
if (id < start || id > end)
{
throw new ArgumentOutOfRangeException(nameof(id), $"Invalid id ID: {id} for device type {(isMouse ? "Mouse" : "Keyboard")}. Device IDs for this type should be between {start} and {end}");
}
throw new ArgumentOutOfRangeException(nameof(id),
$"Invalid id ID: {id} for device type {(isMouse ? "Mouse" : "Keyboard")}. Device IDs for this type should be between {start} and {end}");
}
public static void GetVidPid(string str, ref int vid, ref int pid)
{
var matches = Regex.Matches(str, @"VID_(\w{4})&PID_(\w{4})");
if ((matches.Count <= 0) || (matches[0].Groups.Count <= 1)) return;
if (matches.Count <= 0 || matches[0].Groups.Count <= 1) return;
vid = Convert.ToInt32(matches[0].Groups[1].Value, 16);
pid = Convert.ToInt32(matches[0].Groups[2].Value, 16);
}
@ -38,14 +34,14 @@ namespace AutoHotInterception.Helpers
GetVidPid(handle, ref foundVid, ref foundPid);
//if (foundVid == 0 || foundPid == 0) continue;
ret.Add(new DeviceInfo { Id = i, Vid = foundVid, Pid = foundPid, IsMouse = i > 10, Handle = handle});
ret.Add(new DeviceInfo {Id = i, Vid = foundVid, Pid = foundPid, IsMouse = i > 10, Handle = handle});
}
return ret.ToArray();
}
/// <summary>
/// Converts a button index plus a state into a State value for a mouse Stroke
/// Converts a button index plus a state into a State value for a mouse Stroke
/// </summary>
/// <param name="btn">0 = LMB, 1 = RMB etc</param>
/// <param name="state">1 = Press, 0 = Release</param>
@ -54,8 +50,8 @@ namespace AutoHotInterception.Helpers
{
var stroke = new ManagedWrapper.Stroke();
var power = btn < 5 ? btn * 2 + (state == 0 ? 1 : 0) : btn + 5;
stroke.mouse.state = (ushort)(1 << power);
if (btn >= 5) stroke.mouse.rolling = (short)(state * 120);
stroke.mouse.state = (ushort) (1 << power);
if (btn >= 5) stroke.mouse.rolling = (short) (state * 120);
return stroke;
}
@ -70,6 +66,7 @@ namespace AutoHotInterception.Helpers
state >>= 2;
btn++;
}
state = 2 - state; // 1 = Pressed, 0 = Released
}
else
@ -78,30 +75,8 @@ namespace AutoHotInterception.Helpers
else if (state == 0x800) btn = 6; // Horizontal mouse wheel
state = stroke.mouse.rolling < 0 ? -1 : 1;
}
return new ButtonState {Button = btn, State = state};
}
public class DeviceInfo
{
public int Id { get; set; }
public bool IsMouse { get; set; }
public int Vid { get; set; }
public int Pid { get; set; }
public string Handle { get; set; }
}
public class ButtonState
{
public ushort Button { get; set; }
public int State { get; set; }
}
public class KeyboardState
{
public ushort Code { get; set; }
public ushort State { get; set; }
public bool Ignore { get; set; }
return new ButtonState {Button = btn, State = state};
}
public static KeyboardState KeyboardStrokeToKeyboardState(ManagedWrapper.Stroke stroke)
@ -109,14 +84,7 @@ namespace AutoHotInterception.Helpers
var code = stroke.key.code;
var state = stroke.key.state;
var retVal = new KeyboardState();
if (code == 54)
{
// Interception seems to report Right Shift as 54 / 0x36 with state 0/1...
// ... this code is normally unused (Alt-SysRq according to linked page) ...
// ... and AHK uses 54 + 256 = 310 (0x36 + 0x100 = 0x136)...
// ... so change the code, but leave the state as 0/1
code = 310;
}
if (code == 54) code = 310;
// If state is shifted up by 2 (1 or 2 instead of 0 or 1), then this is an "Extended" key code
if (state > 1)
@ -154,5 +122,27 @@ namespace AutoHotInterception.Helpers
retVal.State = (ushort) (1 - state);
return retVal;
}
public class DeviceInfo
{
public int Id { get; set; }
public bool IsMouse { get; set; }
public int Vid { get; set; }
public int Pid { get; set; }
public string Handle { get; set; }
}
public class ButtonState
{
public ushort Button { get; set; }
public int State { get; set; }
}
public class KeyboardState
{
public ushort Code { get; set; }
public ushort State { get; set; }
public bool Ignore { get; set; }
}
}
}
}

@ -3,93 +3,12 @@ using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace AutoHotInterception
namespace AutoHotInterception.Helpers
{
public static class ManagedWrapper
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int Predicate(int device);
/*
typedef void *InterceptionContext;
typedef int InterceptionDevice;
typedef int InterceptionPrecedence;
typedef unsigned short InterceptionFilter;
typedef int (*InterceptionPredicate)(InterceptionDevice device);
*/
[Flags]
public enum KeyState
{
Down = 0x00,
Up = 0x01,
E0 = 0x02,
E1 = 0x04,
TermsrvSetLED = 0x08,
TermsrvShadow = 0x10,
TermsrvVKPacket = 0x20
/*
enum InterceptionKeyState
INTERCEPTION_KEY_DOWN = 0x00,
INTERCEPTION_KEY_UP = 0x01,
INTERCEPTION_KEY_E0 = 0x02,
INTERCEPTION_KEY_E1 = 0x04,
INTERCEPTION_KEY_TERMSRV_SET_LED = 0x08,
INTERCEPTION_KEY_TERMSRV_SHADOW = 0x10,
INTERCEPTION_KEY_TERMSRV_VKPACKET = 0x20
*/
}
[Flags]
public enum MouseState
{
LeftButtonDown = 0x001,
LeftButtonUp = 0x002,
RightButtonDown = 0x004,
RightButtonUp = 0x008,
MiddleButtonDown = 0x010,
MiddleButtonUp = 0x020,
Button1Down = LeftButtonDown,
Button1Up = LeftButtonUp,
Button2Down = RightButtonDown,
Button2Up = RightButtonUp,
Button3Down = MiddleButtonDown,
Button3Up = MiddleButtonUp,
Button4Down = 0x040,
Button4Up = 0x080,
Button5Down = 0x100,
Button5Up = 0x200,
Wheel = 0x400,
HWheel = 0x800
/*
enum InterceptionMouseState
{
INTERCEPTION_MOUSE_LEFT_BUTTON_DOWN = 0x001,
INTERCEPTION_MOUSE_LEFT_BUTTON_UP = 0x002,
INTERCEPTION_MOUSE_RIGHT_BUTTON_DOWN = 0x004,
INTERCEPTION_MOUSE_RIGHT_BUTTON_UP = 0x008,
INTERCEPTION_MOUSE_MIDDLE_BUTTON_DOWN = 0x010,
INTERCEPTION_MOUSE_MIDDLE_BUTTON_UP = 0x020,
INTERCEPTION_MOUSE_BUTTON_1_DOWN = INTERCEPTION_MOUSE_LEFT_BUTTON_DOWN,
INTERCEPTION_MOUSE_BUTTON_1_UP = INTERCEPTION_MOUSE_LEFT_BUTTON_UP,
INTERCEPTION_MOUSE_BUTTON_2_DOWN = INTERCEPTION_MOUSE_RIGHT_BUTTON_DOWN,
INTERCEPTION_MOUSE_BUTTON_2_UP = INTERCEPTION_MOUSE_RIGHT_BUTTON_UP,
INTERCEPTION_MOUSE_BUTTON_3_DOWN = INTERCEPTION_MOUSE_MIDDLE_BUTTON_DOWN,
INTERCEPTION_MOUSE_BUTTON_3_UP = INTERCEPTION_MOUSE_MIDDLE_BUTTON_UP,
INTERCEPTION_MOUSE_BUTTON_4_DOWN = 0x040,
INTERCEPTION_MOUSE_BUTTON_4_UP = 0x080,
INTERCEPTION_MOUSE_BUTTON_5_DOWN = 0x100,
INTERCEPTION_MOUSE_BUTTON_5_UP = 0x200,
INTERCEPTION_MOUSE_WHEEL = 0x400,
INTERCEPTION_MOUSE_HWHEEL = 0x800
};
*/
}
[Flags]
public enum Filter : ushort
@ -139,10 +58,15 @@ namespace AutoHotInterception
MouseButton4Up = MouseState.Button4Up,
MouseButton5Down = MouseState.Button5Down,
MouseButton5Up = MouseState.Button5Up,
MouseButtonAnyDown = MouseState.Button1Down | MouseState.Button2Down | MouseState.Button3Down | MouseState.Button4Down | MouseState.Button5Down,
MouseButtonAnyUp = MouseState.Button1Up | MouseState.Button2Up | MouseState.Button3Up | MouseState.Button4Up | MouseState.Button5Up,
MouseButtonAnyDown = MouseState.Button1Down | MouseState.Button2Down | MouseState.Button3Down |
MouseState.Button4Down | MouseState.Button5Down,
MouseButtonAnyUp = MouseState.Button1Up | MouseState.Button2Up | MouseState.Button3Up |
MouseState.Button4Up | MouseState.Button5Up,
MouseWheel = MouseState.Wheel,
MouseHWheel = MouseState.HWheel
/*
enum InterceptionFilterMouseState
@ -176,6 +100,36 @@ namespace AutoHotInterception
};
*/
}
/*
typedef void *InterceptionContext;
typedef int InterceptionDevice;
typedef int InterceptionPrecedence;
typedef unsigned short InterceptionFilter;
typedef int (*InterceptionPredicate)(InterceptionDevice device);
*/
[Flags]
public enum KeyState
{
Down = 0x00,
Up = 0x01,
E0 = 0x02,
E1 = 0x04,
TermsrvSetLED = 0x08,
TermsrvShadow = 0x10,
TermsrvVKPacket = 0x20
/*
enum InterceptionKeyState
INTERCEPTION_KEY_DOWN = 0x00,
INTERCEPTION_KEY_UP = 0x01,
INTERCEPTION_KEY_E0 = 0x02,
INTERCEPTION_KEY_E1 = 0x04,
INTERCEPTION_KEY_TERMSRV_SET_LED = 0x08,
INTERCEPTION_KEY_TERMSRV_SHADOW = 0x10,
INTERCEPTION_KEY_TERMSRV_VKPACKET = 0x20
*/
}
[Flags]
public enum MouseFlag : ushort
@ -185,6 +139,7 @@ namespace AutoHotInterception
MouseVirturalDesktop = 0x002,
MouseAttributesChanged = 0x004,
MouseMoveNocoalesce = 0x008,
MouseTermsrvSrcShadow = 0x100
/*
enum InterceptionMouseFlag
@ -199,33 +154,57 @@ namespace AutoHotInterception
*/
}
[StructLayout(LayoutKind.Sequential)]
public struct MouseStroke
[Flags]
public enum MouseState
{
public ushort state;
public ushort flags;
public short rolling;
public int x;
public int y;
public uint information;
}
LeftButtonDown = 0x001,
LeftButtonUp = 0x002,
RightButtonDown = 0x004,
RightButtonUp = 0x008,
MiddleButtonDown = 0x010,
MiddleButtonUp = 0x020,
[StructLayout(LayoutKind.Sequential)]
public struct KeyStroke
{
public ushort code;
public ushort state;
public uint information;
}
Button1Down = LeftButtonDown,
Button1Up = LeftButtonUp,
Button2Down = RightButtonDown,
Button2Up = RightButtonUp,
Button3Down = MiddleButtonDown,
Button3Up = MiddleButtonUp,
[StructLayout(LayoutKind.Explicit)]
public struct Stroke
{
[FieldOffset(0)]
public MouseStroke mouse;
Button4Down = 0x040,
Button4Up = 0x080,
Button5Down = 0x100,
Button5Up = 0x200,
[FieldOffset(0)]
public KeyStroke key;
Wheel = 0x400,
HWheel = 0x800
/*
enum InterceptionMouseState
{
INTERCEPTION_MOUSE_LEFT_BUTTON_DOWN = 0x001,
INTERCEPTION_MOUSE_LEFT_BUTTON_UP = 0x002,
INTERCEPTION_MOUSE_RIGHT_BUTTON_DOWN = 0x004,
INTERCEPTION_MOUSE_RIGHT_BUTTON_UP = 0x008,
INTERCEPTION_MOUSE_MIDDLE_BUTTON_DOWN = 0x010,
INTERCEPTION_MOUSE_MIDDLE_BUTTON_UP = 0x020,
INTERCEPTION_MOUSE_BUTTON_1_DOWN = INTERCEPTION_MOUSE_LEFT_BUTTON_DOWN,
INTERCEPTION_MOUSE_BUTTON_1_UP = INTERCEPTION_MOUSE_LEFT_BUTTON_UP,
INTERCEPTION_MOUSE_BUTTON_2_DOWN = INTERCEPTION_MOUSE_RIGHT_BUTTON_DOWN,
INTERCEPTION_MOUSE_BUTTON_2_UP = INTERCEPTION_MOUSE_RIGHT_BUTTON_UP,
INTERCEPTION_MOUSE_BUTTON_3_DOWN = INTERCEPTION_MOUSE_MIDDLE_BUTTON_DOWN,
INTERCEPTION_MOUSE_BUTTON_3_UP = INTERCEPTION_MOUSE_MIDDLE_BUTTON_UP,
INTERCEPTION_MOUSE_BUTTON_4_DOWN = 0x040,
INTERCEPTION_MOUSE_BUTTON_4_UP = 0x080,
INTERCEPTION_MOUSE_BUTTON_5_DOWN = 0x100,
INTERCEPTION_MOUSE_BUTTON_5_UP = 0x200,
INTERCEPTION_MOUSE_WHEEL = 0x400,
INTERCEPTION_MOUSE_HWHEEL = 0x800
};
*/
}
static ManagedWrapper()
@ -239,33 +218,41 @@ namespace AutoHotInterception
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("interception.dll", EntryPoint = "interception_create_context", CallingConvention = CallingConvention.Cdecl)]
[DllImport("interception.dll", EntryPoint = "interception_create_context",
CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr CreateContext();
[DllImport("interception.dll", EntryPoint = "interception_destroy_context", CallingConvention = CallingConvention.Cdecl)]
[DllImport("interception.dll", EntryPoint = "interception_destroy_context",
CallingConvention = CallingConvention.Cdecl)]
public static extern void DestroyContext(IntPtr context);
[DllImport("interception.dll", EntryPoint = "interception_set_filter", CallingConvention = CallingConvention.Cdecl)]
[DllImport("interception.dll", EntryPoint = "interception_set_filter",
CallingConvention = CallingConvention.Cdecl)]
public static extern void SetFilter(IntPtr context, Predicate predicate, Filter filter);
// public static extern void SetFilter(IntPtr context, Predicate predicate, ushort filter);
// InterceptionFilter INTERCEPTION_API interception_get_filter(InterceptionContext context, InterceptionDevice device);
[DllImport("interception.dll", EntryPoint = "interception_get_filter", CallingConvention = CallingConvention.Cdecl)]
[DllImport("interception.dll", EntryPoint = "interception_get_filter",
CallingConvention = CallingConvention.Cdecl)]
public static extern ushort GetFilter(IntPtr context, int device);
[DllImport("interception.dll", EntryPoint = "interception_receive", CallingConvention = CallingConvention.Cdecl)]
[DllImport("interception.dll", EntryPoint = "interception_receive",
CallingConvention = CallingConvention.Cdecl)]
public static extern int Receive(IntPtr context, int device, ref Stroke stroke, uint nstroke);
[DllImport("interception.dll", EntryPoint = "interception_send", CallingConvention = CallingConvention.Cdecl)]
public static extern int Send(IntPtr context, int device, ref Stroke stroke, uint nstroke);
[DllImport("interception.dll", EntryPoint = "interception_is_keyboard", CallingConvention = CallingConvention.Cdecl)]
[DllImport("interception.dll", EntryPoint = "interception_is_keyboard",
CallingConvention = CallingConvention.Cdecl)]
public static extern int IsKeyboard(int device);
[DllImport("interception.dll", EntryPoint = "interception_is_mouse", CallingConvention = CallingConvention.Cdecl)]
[DllImport("interception.dll", EntryPoint = "interception_is_mouse",
CallingConvention = CallingConvention.Cdecl)]
public static extern int IsMouse(int device);
[DllImport("interception.dll", EntryPoint = "interception_is_invalid", CallingConvention = CallingConvention.Cdecl)]
[DllImport("interception.dll", EntryPoint = "interception_is_invalid",
CallingConvention = CallingConvention.Cdecl)]
public static extern int IsInvalid(int device);
//InterceptionDevice INTERCEPTION_API interception_wait(InterceptionContext context);
@ -273,11 +260,13 @@ namespace AutoHotInterception
public static extern int Wait(IntPtr context);
//InterceptionDevice INTERCEPTION_API interception_wait_with_timeout(InterceptionContext context, unsigned long milliseconds);
[DllImport("interception.dll", EntryPoint = "interception_wait_with_timeout", CallingConvention = CallingConvention.Cdecl)]
[DllImport("interception.dll", EntryPoint = "interception_wait_with_timeout",
CallingConvention = CallingConvention.Cdecl)]
public static extern int WaitWithTimeout(int device, ulong milliseconds);
// unsigned int INTERCEPTION_API interception_get_hardware_id(InterceptionContext context, InterceptionDevice device, void *hardware_id_buffer, unsigned int buffer_size);
[DllImport("interception.dll", EntryPoint = "interception_get_hardware_id", CallingConvention = CallingConvention.Cdecl)]
[DllImport("interception.dll", EntryPoint = "interception_get_hardware_id",
CallingConvention = CallingConvention.Cdecl)]
public static extern uint GetHardwareID(IntPtr context, int device, IntPtr hardwareidbuffer, uint buffersize);
// public static extern uint GetHardwareID(IntPtr context, int device, [MarshalAs(UnmanagedType.ByValArray,SizeConst=500)]char[] hardwareidbuffer, uint buffersize);
//public static extern uint GetHardwareID(IntPtr context, int device, ref _wchar_t[] hardwareidbuffer, uint buffersize);
@ -286,15 +275,41 @@ namespace AutoHotInterception
{
if (chars == 0)
chars = 500;
String result = "";
IntPtr bufferptr = Marshal.StringToHGlobalUni(new string(new char[chars]));
uint length = GetHardwareID(context, device, bufferptr, (uint)(chars * sizeof(char)));
if (length > 0 && length < (chars * sizeof(char)))
var result = "";
var bufferptr = Marshal.StringToHGlobalUni(new string(new char[chars]));
var length = GetHardwareID(context, device, bufferptr, (uint) (chars * sizeof(char)));
if (length > 0 && length < chars * sizeof(char))
result = Marshal.PtrToStringAuto(bufferptr);
Marshal.FreeHGlobal(bufferptr);
return result;
}
[StructLayout(LayoutKind.Sequential)]
public struct MouseStroke
{
public ushort state;
public ushort flags;
public short rolling;
public int x;
public int y;
public uint information;
}
[StructLayout(LayoutKind.Sequential)]
public struct KeyStroke
{
public ushort code;
public ushort state;
public uint information;
}
[StructLayout(LayoutKind.Explicit)]
public struct Stroke
{
[FieldOffset(0)] public MouseStroke mouse;
[FieldOffset(0)] public KeyStroke key;
}
/*
InterceptionContext INTERCEPTION_API interception_create_context(void);
void INTERCEPTION_API interception_destroy_context(InterceptionContext context);
@ -311,6 +326,5 @@ namespace AutoHotInterception
int INTERCEPTION_API interception_is_keyboard(InterceptionDevice device);
int INTERCEPTION_API interception_is_mouse(InterceptionDevice device);
*/
}
}
}

@ -1,13 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using AutoHotInterception.Helpers;
using static AutoHotInterception.Helpers.HelperFunctions;
@ -15,22 +8,35 @@ namespace AutoHotInterception
{
public class Manager : IDisposable
{
private readonly IntPtr _deviceContext;
private Thread _pollThread;
private bool _pollThreadRunning = false;
private bool _filterState = false;
private readonly ConcurrentDictionary<int, dynamic>
_contextCallbacks = new ConcurrentDictionary<int, dynamic>();
private readonly ConcurrentDictionary<int, ConcurrentDictionary<ushort, MappingOptions>> _keyboardMappings = new ConcurrentDictionary<int, ConcurrentDictionary<ushort, MappingOptions>>();
private readonly ConcurrentDictionary<int, ConcurrentDictionary<ushort, MappingOptions>> _mouseButtonMappings = new ConcurrentDictionary<int, ConcurrentDictionary<ushort, MappingOptions>>();
private readonly ConcurrentDictionary<int, MappingOptions> _mouseMoveRelativeMappings = new ConcurrentDictionary<int, MappingOptions>();
private readonly ConcurrentDictionary<int, MappingOptions> _mouseMoveAbsoluteMappings = new ConcurrentDictionary<int, MappingOptions>();
private readonly ConcurrentDictionary<int, dynamic> _contextCallbacks = new ConcurrentDictionary<int, dynamic>();
private readonly IntPtr _deviceContext;
// If a the ID of a device exists as a key in this Dictionary, then that device is filtered.
// If a device ID exists as a key in this Dictionary then that device is filtered.
// Used by IsMonitoredDevice, which is handed to Interception as a "Predicate".
private readonly ConcurrentDictionary<int, bool> _filteredDevices = new ConcurrentDictionary<int, bool>();
private readonly ConcurrentDictionary<int, ConcurrentDictionary<ushort, MappingOptions>> _keyboardMappings =
new ConcurrentDictionary<int, ConcurrentDictionary<ushort, MappingOptions>>();
private readonly ConcurrentDictionary<int, ConcurrentDictionary<ushort, MappingOptions>> _mouseButtonMappings =
new ConcurrentDictionary<int, ConcurrentDictionary<ushort, MappingOptions>>();
private readonly ConcurrentDictionary<int, MappingOptions> _mouseMoveAbsoluteMappings =
new ConcurrentDictionary<int, MappingOptions>();
private readonly ConcurrentDictionary<int, MappingOptions> _mouseMoveRelativeMappings =
new ConcurrentDictionary<int, MappingOptions>();
// If an event is subscribed to with concurrent set to false then use a single worker thread to process each event.
// Makes sure the events are handled synchronously and with a FIFO order.
private readonly ConcurrentDictionary<int, ConcurrentDictionary<ushort, WorkerThread>> _workerThreads =
new ConcurrentDictionary<int, ConcurrentDictionary<ushort, WorkerThread>>();
private Thread _pollThread;
private volatile bool _pollThreadRunning;
#region Public
#region Initialization
@ -40,6 +46,11 @@ namespace AutoHotInterception
_deviceContext = ManagedWrapper.CreateContext();
}
public void Dispose()
{
SetThreadState(false);
}
public string OkCheck()
{
return "OK";
@ -50,26 +61,35 @@ namespace AutoHotInterception
#region Subscription Mode
/// <summary>
/// Subscribes to a Keyboard key
/// Subscribes to a Keyboard key
/// </summary>
/// <param name="id">The ID of the Keyboard</param>
/// <param name="code">The ScanCode of the key</param>
/// <param name="block">Whether or not to block the key</param>
/// <param name="callback">The callback to fire when the key changes state</param>
/// <param name="concurrent">Whether or not to execute callbacks concurrently</param>
/// <returns></returns>
public void SubscribeKey(int id, ushort code, bool block, dynamic callback)
public void SubscribeKey(int id, ushort code, bool block, dynamic callback, bool concurrent = false)
{
IsValidDeviceId(false, id);
SetFilterState(false);
if (!_keyboardMappings.ContainsKey(id))
{
_keyboardMappings.TryAdd(id, new ConcurrentDictionary<ushort, MappingOptions>());
_keyboardMappings[id].TryAdd(code,
new MappingOptions {Block = block, Concurrent = concurrent, Callback = callback});
if (!concurrent)
{
if (!_workerThreads.ContainsKey(id))
_workerThreads.TryAdd(id, new ConcurrentDictionary<ushort, WorkerThread>());
_workerThreads[id].TryAdd(code, new WorkerThread());
_workerThreads[id][code].Start();
}
_keyboardMappings[id].TryAdd(code, new MappingOptions() { Block = block, Callback = callback });
SetDeviceFilterState(id, true);
SetFilterState(true);
SetThreadState(true);
}
@ -94,31 +114,41 @@ namespace AutoHotInterception
}
/// <summary>
/// Subscribe to a Mouse button
/// Subscribe to a Mouse button
/// </summary>
/// <param name="id">The ID of the mouse</param>
/// <param name="btn">The button number (LMB = 0, RMB = 1, MMB = 2, X1 = 3, X2 = 4</param>
/// <param name="btn">The button number (LMB = 0, RMB = 1, MMB = 2, X1 = 3, X2 = 4, WV = 5, WH = 6)</param>
/// <param name="block">Whether or not to block the button</param>
/// <param name="callback">The callback to fire when the button changes state</param>
/// <param name="concurrent">Whether or not to execute callbacks concurrently</param>
/// <returns></returns>
public void SubscribeMouseButton(int id, ushort btn, bool block, dynamic callback)
public void SubscribeMouseButton(int id, ushort btn, bool block, dynamic callback, bool concurrent = false)
{
IsValidDeviceId(true, id);
if (!_mouseButtonMappings.ContainsKey(id))
{
_mouseButtonMappings.TryAdd(id, new ConcurrentDictionary<ushort, MappingOptions>());
_mouseButtonMappings[id].TryAdd(btn,
new MappingOptions {Block = block, Concurrent = concurrent, Callback = callback});
if (!concurrent)
{
if (!_workerThreads.ContainsKey(id))
_workerThreads.TryAdd(id, new ConcurrentDictionary<ushort, WorkerThread>());
_workerThreads[id].TryAdd(btn, new WorkerThread());
_workerThreads[id][btn].Start();
}
_mouseButtonMappings[id].TryAdd(btn, new MappingOptions() { Block = block, Callback = callback });
SetDeviceFilterState(id, true);
SetDeviceFilterState(id, true);
SetFilterState(true);
SetThreadState(true);
}
public void UnsubscribeMouseButton(int id, ushort btn)
{
IsValidDeviceId(false, id);
IsValidDeviceId(true, id);
SetFilterState(false);
if (_mouseButtonMappings.TryGetValue(id, out var thisDevice))
@ -135,77 +165,93 @@ namespace AutoHotInterception
SetThreadState(true);
}
//Shorthand for SubscribeMouseMoveRelative
public void SubscribeMouseMove(int id, bool block, dynamic callback)
{
SubscribeMouseMoveRelative(id, block, callback);
}
public void UnsubscribeMouseMove(int id)
{
UnsubscribeMouseMoveRelative(id);
}
/// <summary>
/// Subscribes to Relative mouse movement
/// Subscribes to Absolute mouse movement
/// </summary>
/// <param name="id">The id of the Mouse</param>
/// <param name="block">Whether or not to block the movement</param>
/// <param name="callback">The callback to fire when the mouse moves</param>
/// <param name="concurrent">Whether or not to execute callbacks concurrently</param>
/// <returns></returns>
public void SubscribeMouseMoveRelative(int id, bool block, dynamic callback)
public void SubscribeMouseMoveAbsolute(int id, bool block, dynamic callback, bool concurrent = false)
{
IsValidDeviceId(true, id);
_mouseMoveRelativeMappings[id] = new MappingOptions() { Block = block, Callback = callback };
_mouseMoveAbsoluteMappings[id] = new MappingOptions
{Block = block, Concurrent = concurrent, Callback = callback};
if (!concurrent)
{
if (!_workerThreads.ContainsKey(id))
_workerThreads.TryAdd(id, new ConcurrentDictionary<ushort, WorkerThread>());
_workerThreads[id].TryAdd(7, new WorkerThread()); // Use 7 as second index for MouseMoveAbsolute
_workerThreads[id][7].Start();
}
SetDeviceFilterState(id, true);
SetFilterState(true);
SetThreadState(true);
}
public void UnsubscribeMouseMoveRelative(int id)
public void UnsubscribeMouseMoveAbsolute(int id)
{
IsValidDeviceId(true, id);
if (_mouseMoveRelativeMappings.TryRemove(id, out _))
{
if (_mouseMoveAbsoluteMappings.TryRemove(id, out _))
if (!DeviceHasBindings(id))
{
SetDeviceFilterState(id, false);
}
}
SetFilterState(true);
SetThreadState(true);
}
//Shorthand for SubscribeMouseMoveRelative
public void SubscribeMouseMove(int id, bool block, dynamic callback, bool concurrent = false)
{
SubscribeMouseMoveRelative(id, block, callback, concurrent);
}
public void UnsubscribeMouseMove(int id)
{
UnsubscribeMouseMoveRelative(id);
}
/// <summary>
///
/// Subscribes to Absolute mouse movement
/// Subscribes to Relative mouse movement
/// </summary>
/// <param name="id">The id of the Mouse</param>
/// <param name="block">Whether or not to block the movement</param>
/// <param name="callback">The callback to fire when the mouse moves</param>
/// <param name="concurrent">Whether or not to execute callbacks concurrently</param>
/// <returns></returns>
public void SubscribeMouseMoveAbsolute(int id, bool block, dynamic callback)
public void SubscribeMouseMoveRelative(int id, bool block, dynamic callback, bool concurrent = false)
{
IsValidDeviceId(true, id);
_mouseMoveAbsoluteMappings[id] = new MappingOptions() { Block = block, Callback = callback };
_mouseMoveRelativeMappings[id] = new MappingOptions
{Block = block, Concurrent = concurrent, Callback = callback};
if (!concurrent)
{
if (!_workerThreads.ContainsKey(id))
_workerThreads.TryAdd(id, new ConcurrentDictionary<ushort, WorkerThread>());
_workerThreads[id].TryAdd(8, new WorkerThread()); // Use 8 as second index for MouseMoveRelative
_workerThreads[id][8].Start();
}
SetDeviceFilterState(id, true);
SetFilterState(true);
SetThreadState(true);
}
public void UnsubscribeMouseMoveAbsolute(int id)
public void UnsubscribeMouseMoveRelative(int id)
{
IsValidDeviceId(true, id);
if (_mouseMoveAbsoluteMappings.TryRemove(id, out _))
{
if (_mouseMoveRelativeMappings.TryRemove(id, out _))
if (!DeviceHasBindings(id))
{
SetDeviceFilterState(id, false);
}
}
SetFilterState(true);
SetThreadState(true);
}
#endregion
@ -213,7 +259,7 @@ namespace AutoHotInterception
#region Context Mode
/// <summary>
/// Sets a callback for Context Mode for a given device
/// Sets a callback for Context Mode for a given device
/// </summary>
/// <param name="id">The ID of the device</param>
/// <param name="callback">The callback to fire before and after each key or button press</param>
@ -222,13 +268,11 @@ namespace AutoHotInterception
{
SetFilterState(false);
if (id < 1 || id > 20)
{
throw new ArgumentOutOfRangeException(nameof(id), "DeviceIds must be between 1 and 20");
}
_contextCallbacks[id] = callback;
SetDeviceFilterState(id, true);
SetDeviceFilterState(id, true);
SetFilterState(true);
SetThreadState(true);
}
@ -238,7 +282,7 @@ namespace AutoHotInterception
#region Input Synthesis
/// <summary>
/// Sends a keyboard key event
/// Sends a keyboard key event
/// </summary>
/// <param name="id">The ID of the Keyboard to send as</param>
/// <param name="code">The ScanCode to send</param>
@ -252,17 +296,16 @@ namespace AutoHotInterception
{
code -= 256;
if (code != 54) // RShift has > 256 code, but state is 0/1
{
st += 2;
}
}
stroke.key.code = code;
stroke.key.state = (ushort)st;
stroke.key.state = (ushort) st;
ManagedWrapper.Send(_deviceContext, id, ref stroke, 1);
}
/// <summary>
/// Sends Mouse button events
/// Sends Mouse button events
/// </summary>
/// <param name="id"></param>
/// <param name="btn"></param>
@ -277,7 +320,7 @@ namespace AutoHotInterception
}
/// <summary>
/// Same as <see cref="SendMouseButtonEvent"/>, but sends button events in Absolute mode (with coordinates)
/// Same as <see cref="SendMouseButtonEvent" />, but sends button events in Absolute mode (with coordinates)
/// </summary>
/// <param name="id"></param>
/// <param name="btn"></param>
@ -299,7 +342,7 @@ namespace AutoHotInterception
}
/// <summary>
/// Sends Relative Mouse Movement
/// Sends Relative Mouse Movement
/// </summary>
/// <param name="id"></param>
/// <param name="x"></param>
@ -309,14 +352,15 @@ namespace AutoHotInterception
{
IsValidDeviceId(true, id);
var stroke = new ManagedWrapper.Stroke { mouse = { x = x, y = y, flags = (ushort)ManagedWrapper.MouseFlag.MouseMoveRelative } };
var stroke = new ManagedWrapper.Stroke
{mouse = {x = x, y = y, flags = (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative}};
ManagedWrapper.Send(_deviceContext, id, ref stroke, 1);
}
/// <summary>
/// Sends Absolute Mouse Movement
/// Note: Newing up a stroke seems to make Absolute input be relative to main monitor
/// Calling Send on an actual stroke from an Absolute device results in input relative to all monitors
/// Sends Absolute Mouse Movement
/// Note: Creating a new stroke seems to make Absolute input become relative to main monitor
/// Calling Send on an actual stroke from an Absolute device results in input relative to all monitors
/// </summary>
/// <param name="id"></param>
/// <param name="x"></param>
@ -326,7 +370,8 @@ namespace AutoHotInterception
{
IsValidDeviceId(true, id);
var stroke = new ManagedWrapper.Stroke { mouse = { x = x, y = y, flags = (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute } };
var stroke = new ManagedWrapper.Stroke
{mouse = {x = x, y = y, flags = (ushort) ManagedWrapper.MouseFlag.MouseMoveAbsolute}};
ManagedWrapper.Send(_deviceContext, id, ref stroke, 1);
}
@ -355,7 +400,7 @@ namespace AutoHotInterception
}
/// <summary>
/// Tries to get Device ID from VID/PID
/// Tries to get Device ID from VID/PID
/// </summary>
/// <param name="isMouse">Whether the device is a mouse or a keyboard</param>
/// <param name="vid">The VID of the device</param>
@ -372,10 +417,7 @@ namespace AutoHotInterception
int foundVid = 0, foundPid = 0;
GetVidPid(hardwareStr, ref foundVid, ref foundPid);
if (foundVid != vid || foundPid != pid) continue;
if (instance == 1)
{
return i;
}
if (instance == 1) return i;
instance--;
}
@ -384,7 +426,7 @@ namespace AutoHotInterception
}
/// <summary>
/// Tries to get Device ID from Hardware String
/// Tries to get Device ID from Hardware String
/// </summary>
/// <param name="isMouse">Whether the device is a mouse or a keyboard</param>
/// <param name="handle">The Hardware String (handle) of the device</param>
@ -399,10 +441,7 @@ namespace AutoHotInterception
var hardwareStr = ManagedWrapper.GetHardwareStr(_deviceContext, i, 1000);
if (hardwareStr != handle) continue;
if (instance == 1)
{
return i;
}
if (instance == 1) return i;
instance--;
}
@ -411,9 +450,9 @@ namespace AutoHotInterception
}
/// <summary>
/// Gets a list of connected devices
/// Intended to be used called via the AHK wrapper...
/// ... so it can convert the return value into an AHK array
/// Gets a list of connected devices
/// Intended to be used called via the AHK wrapper...
/// ... so it can convert the return value into an AHK array
/// </summary>
/// <returns></returns>
public DeviceInfo[] GetDeviceList()
@ -431,26 +470,24 @@ namespace AutoHotInterception
{
if (state)
{
if (!_pollThreadRunning)
{
_pollThreadRunning = true;
_pollThread = new Thread(PollThread);
_pollThread.Start();
}
if (_pollThreadRunning) return;
_pollThreadRunning = true;
_pollThread = new Thread(PollThread);
_pollThread.Start();
}
else
{
_pollThread.Abort();
_pollThreadRunning = false;
_pollThread.Join();
_pollThread = null;
}
}
/// <summary>
/// Predicate used by Interception to decide whether to filter this device or not.
/// WARNING! Setting this to always return true is RISKY, as you could lock yourself out of Windows...
/// ... requiring a reboot.
/// When working with AHI, it's generally best to keep this matching as little as possible....
/// Predicate used by Interception to decide whether to filter this device or not.
/// WARNING! Setting this to always return true is RISKY, as you could lock yourself out of Windows...
/// ... requiring a reboot.
/// When working with AHI, it's generally best to keep this matching as little as possible....
/// </summary>
/// <param name="device"></param>
/// <returns></returns>
@ -463,41 +500,32 @@ namespace AutoHotInterception
{
ManagedWrapper.SetFilter(_deviceContext, IsMonitoredDevice,
state ? ManagedWrapper.Filter.All : ManagedWrapper.Filter.None);
_filterState = state;
}
private void SetDeviceFilterState(int device, bool state)
{
if (state && !_filteredDevices.ContainsKey(device))
{
_filteredDevices[device] = true;
}
else if (!state && _filteredDevices.ContainsKey(device))
{
_filteredDevices.TryRemove(device, out _);
}
}
private bool DeviceHasBindings(int id)
{
if (id < 11)
{
return _keyboardMappings.ContainsKey(id);
}
return _mouseButtonMappings.ContainsKey(id)
|| _mouseMoveRelativeMappings.ContainsKey(id)
|| _mouseMoveAbsoluteMappings.ContainsKey(id);
}
// ScanCode notes: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
private void PollThread()
{
ManagedWrapper.Stroke stroke = new ManagedWrapper.Stroke();
var stroke = new ManagedWrapper.Stroke();
while (true)
while (_pollThreadRunning)
{
// Iterate through all Keyboards
for (var i = 1; i < 11; i++)
@ -518,9 +546,11 @@ namespace AutoHotInterception
// Process Subscription Mode
#region KeyCode, State, Extended Flag translation
// Begin translation of incoming key code, state, extended flag etc...
var processMappings = true;
var processedState = KeyboardStrokeToKeyboardState(stroke);
#endregion
if (processedState.Ignore)
@ -539,12 +569,11 @@ namespace AutoHotInterception
{
hasSubscription = true;
var mapping = _keyboardMappings[i][code];
if (mapping.Block)
{
block = true;
}
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state));
if (mapping.Block) block = true;
if (mapping.Concurrent)
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state));
else if (_workerThreads.ContainsKey(i) && _workerThreads[i].ContainsKey(code))
_workerThreads[i][code]?.Actions.Add(() => mapping.Callback(state));
}
}
@ -553,19 +582,13 @@ namespace AutoHotInterception
// If this key had no subscriptions, but Context Mode is set for this keyboard...
// ... then set the Context before sending the key
if (!hasSubscription && hasContext)
{
_contextCallbacks[i](1);
}
if (!hasSubscription && hasContext) _contextCallbacks[i](1);
// Pass the key through to the OS.
ManagedWrapper.Send(_deviceContext, i, ref stroke, 1);
// If we are processing Context Mode, then Unset the context variable after sending the key
if (!hasSubscription && hasContext)
{
_contextCallbacks[i](0);
}
if (!hasSubscription && hasContext) _contextCallbacks[i](0);
}
}
@ -591,14 +614,18 @@ namespace AutoHotInterception
{
hasSubscription = true;
var mapping = _mouseButtonMappings[i][btnState.Button];
if (mapping.Block)
{
block = true;
}
if (mapping.Block) block = true;
var state = btnState;
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state.State));
if (mapping.Concurrent)
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state.State));
else if (_workerThreads.ContainsKey(i) &&
_workerThreads[i].ContainsKey(btnState.Button))
_workerThreads[i][btnState.Button]?.Actions
.Add(() => mapping.Callback(state.State));
}
//Console.WriteLine($"AHK| Mouse {i} seen - button {btnState.Button}, state: {stroke.mouse.state}, rolling: {stroke.mouse.rolling}");
}
else if ((stroke.mouse.flags & (ushort) ManagedWrapper.MouseFlag.MouseMoveAbsolute) ==
@ -608,51 +635,42 @@ namespace AutoHotInterception
// Absolute Mouse Move
hasSubscription = true;
var mapping = _mouseMoveAbsoluteMappings[i];
if (mapping.Block)
{
block = true;
}
if (mapping.Block) block = true;
var x = stroke.mouse.x;
var y = stroke.mouse.y;
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(x, y));
if (mapping.Concurrent)
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(x, y));
else if (_workerThreads.ContainsKey(i) && _workerThreads[i].ContainsKey(7))
_workerThreads[i][7]?.Actions.Add(() => mapping.Callback(x, y));
}
else if ((stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveRelative) ==
(ushort)ManagedWrapper.MouseFlag.MouseMoveRelative
else if ((stroke.mouse.flags & (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative) ==
(ushort) ManagedWrapper.MouseFlag.MouseMoveRelative
&& _mouseMoveRelativeMappings.ContainsKey(i))
{
// Relative Mouse Move
hasSubscription = true;
var mapping = _mouseMoveRelativeMappings[i];
if (mapping.Block)
{
block = true;
}
if (mapping.Block) block = true;
var x = stroke.mouse.x;
var y = stroke.mouse.y;
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(x, y));
if (mapping.Concurrent)
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(x, y));
else if (_workerThreads.ContainsKey(i) && _workerThreads[i].ContainsKey(8))
_workerThreads[i][8]?.Actions.Add(() => mapping.Callback(x, y));
}
}
// If this key had no subscriptions, but Context Mode is set for this mouse...
// ... then set the Context before sending the button
if (!hasSubscription && hasContext)
{
// Set Context
_contextCallbacks[i](1);
}
if (!(block))
{
ManagedWrapper.Send(_deviceContext, i, ref stroke, 1);
}
if (!hasSubscription && hasContext) _contextCallbacks[i](1); // Set Context
if (!block) ManagedWrapper.Send(_deviceContext, i, ref stroke, 1);
// If we are processing Context Mode, then Unset the context variable after sending the button
if (!hasSubscription && hasContext)
{
// Unset Context
_contextCallbacks[i](0);
}
if (!hasSubscription && hasContext) _contextCallbacks[i](0);
}
}
Thread.Sleep(10);
}
}
@ -660,15 +678,48 @@ namespace AutoHotInterception
private class MappingOptions
{
public bool Block { get; set; }
public bool Concurrent { get; set; }
public dynamic Callback { get; set; }
}
#endregion
public void Dispose()
private class WorkerThread : IDisposable
{
SetThreadState(false);
private readonly Thread _worker;
private volatile bool _running;
public WorkerThread()
{
Actions = new BlockingCollection<Action>();
_worker = new Thread(Run);
_running = false;
}
public BlockingCollection<Action> Actions { get; }
public void Dispose()
{
if (!_running) return;
_running = false;
_worker.Join();
}
public void Start()
{
if (_running) return;
_running = true;
_worker.Start();
}
private void Run()
{
while (_running)
{
var action = Actions.Take();
action.Invoke();
}
}
}
#endregion
}
}
}

@ -1,11 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AutoHotInterception.Helpers;
using static AutoHotInterception.Helpers.HelperFunctions;
@ -14,12 +9,16 @@ namespace AutoHotInterception
public class Monitor : IDisposable
{
private readonly IntPtr _deviceContext;
private Thread _pollThread;
private bool _pollThreadRunning = false;
private readonly ConcurrentDictionary<int, bool> _filteredDevices = new ConcurrentDictionary<int, bool>();
private dynamic _keyboardCallback;
private dynamic _mouseCallback;
private bool _filterState = false;
private readonly ConcurrentDictionary<int, bool> _filteredDevices = new ConcurrentDictionary<int, bool>();
private Thread _pollThread;
private volatile bool _pollThreadRunning;
#region Public
public Monitor()
{
@ -27,16 +26,17 @@ namespace AutoHotInterception
SetThreadState(true);
}
public void Dispose()
{
SetFilterState(false);
SetThreadState(false);
}
public string OkCheck()
{
return "OK";
}
//public void Log(string text)
//{
// Debug.WriteLine($"AHK| {text}");
//}
public void Subscribe(dynamic keyboardCallback, dynamic mouseCallback)
{
_keyboardCallback = keyboardCallback;
@ -47,20 +47,11 @@ namespace AutoHotInterception
{
SetFilterState(false);
if (state)
{
_filteredDevices[device] = true;
//Log($"Adding device {device}, count: {_filteredDevices.Count}");
}
else
{
_filteredDevices.TryRemove(device, out _);
//Log($"Removing device {device}, count: {_filteredDevices.Count}");
}
if (_filteredDevices.Count > 0)
{
SetFilterState(true);
}
if (_filteredDevices.Count > 0) SetFilterState(true);
return true;
}
@ -69,52 +60,50 @@ namespace AutoHotInterception
return HelperFunctions.GetDeviceList(_deviceContext);
}
private void SetFilterState(bool state)
{
ManagedWrapper.SetFilter(_deviceContext, IsMonitoredDevice,
state ? ManagedWrapper.Filter.All : ManagedWrapper.Filter.None);
_filterState = state;
}
private int IsMonitoredDevice(int device)
{
return Convert.ToInt32(_filteredDevices.ContainsKey(device));
}
#endregion
#region Private
private void SetThreadState(bool state)
{
if (state)
{
if (_pollThreadRunning) return;
_pollThreadRunning = true;
_pollThread = new Thread(PollThread);
_pollThread.Start();
}
else
{
_pollThread.Abort();
_pollThreadRunning = true;
_pollThread.Join();
_pollThread = null;
}
}
private int IsMonitoredDevice(int device)
{
return Convert.ToInt32(_filteredDevices.ContainsKey(device));
}
private void SetFilterState(bool state)
{
ManagedWrapper.SetFilter(_deviceContext, IsMonitoredDevice,
state ? ManagedWrapper.Filter.All : ManagedWrapper.Filter.None);
}
private void PollThread()
{
var stroke = new ManagedWrapper.Stroke();
while (true)
while (_pollThreadRunning)
{
for (var i = 1; i < 11; i++)
{
while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0)
{
ManagedWrapper.Send(_deviceContext, i, ref stroke, 1);
var processedState = KeyboardStrokeToKeyboardState(stroke);
var info = "";
if (processedState.Ignore)
{
FireKeyboardCallback(i, new KeyboardCallback
{
Id = i,
@ -122,9 +111,7 @@ namespace AutoHotInterception
State = stroke.key.state,
Info = "Ignored - showing raw values"
});
}
else
{
FireKeyboardCallback(i, new KeyboardCallback
{
Id = i,
@ -132,12 +119,9 @@ namespace AutoHotInterception
State = processedState.State,
Info = stroke.key.code > 255 ? "Extended" : ""
});
}
}
}
for (var i = 11; i < 21; i++)
{
while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0)
{
ManagedWrapper.Send(_deviceContext, i, ref stroke, 1);
@ -164,10 +148,9 @@ namespace AutoHotInterception
Y = stroke.mouse.y,
Info = "Absolute Move"
});
}
else if ((stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveRelative) ==
(ushort)ManagedWrapper.MouseFlag.MouseMoveRelative)
else if ((stroke.mouse.flags & (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative) ==
(ushort) ManagedWrapper.MouseFlag.MouseMoveRelative)
{
// Relative Mouse Move
@ -178,23 +161,25 @@ namespace AutoHotInterception
Y = stroke.mouse.y,
Info = "Relative Move"
});
}
//FireMouseCallback(i, stroke);
}
}
Thread.Sleep(10);
}
}
private void FireKeyboardCallback(int id, KeyboardCallback data)
{
ThreadPool.QueueUserWorkItem(threadProc => _keyboardCallback(data.Id, data.Code, data.State, data.Info));
ThreadPool.QueueUserWorkItem(threadProc =>
_keyboardCallback(data.Id, data.Code, data.State, data.Info));
}
private void FireMouseCallback(MouseCallback data)
{
ThreadPool.QueueUserWorkItem(threadProc => _mouseCallback(data.Id, data.Code, data.State, data.X, data.Y, data.Info));
ThreadPool.QueueUserWorkItem(threadProc =>
_mouseCallback(data.Id, data.Code, data.State, data.X, data.Y, data.Info));
}
public class MouseCallback
@ -215,10 +200,6 @@ namespace AutoHotInterception
public string Info { get; set; } = "";
}
public void Dispose()
{
SetFilterState(false);
SetThreadState(false);
}
#endregion
}
}
}

@ -0,0 +1,3 @@
In order to build, the Interception DLLs need to be placed in this folder .
It should contain `x86` and `x64` folders, as extracted from the `library` folder in the interception zip
YOU MAY ALSO NEED TO RUN UNBLOCKER.PS1 AS ADMIN!!

@ -0,0 +1,2 @@
In order to build, 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!!

@ -0,0 +1,2 @@
In order to build, 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!!

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutoHotInterception;
namespace TestApp
{
public class KeyboardTester
{
public KeyboardTester()
{
var im = new Manager();
var devId = im.GetKeyboardId(0x04F2, 0x0112);
if (devId == 0) return;
im.SubscribeKey(devId, 0x2, false, new Action<int>(value =>
{
Console.WriteLine($"State: {value}");
}));
}
}
}

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutoHotInterception;
namespace TestApp
{
public class MouseTester
{
public MouseTester()
{
var im = new Manager();
var mouseHandle = "HID\\VID_046D&PID_C52B&REV_2407&MI_02&Qid_1028&WI_01&Class_00000004";
var devId = im.GetMouseIdFromHandle(mouseHandle);
var counter = 0;
if (devId != 0)
{
im.SubscribeMouseButton(devId, 1, true, new Action<int>(value =>
{
Console.WriteLine("RButton Button Value: " + value);
}));
im.SubscribeMouseButton(devId, 3, true, new Action<int>(value =>
{
Console.WriteLine("XButton1 Button Value: " + value);
}));
im.SubscribeMouseButton(devId, 4, true, new Action<int>(value =>
{
Console.WriteLine("XButton2 Button Value: " + value);
}));
im.SubscribeMouseButton(devId, 5, true, new Action<int>(value =>
{
Console.Write("WheelVertical Value: " + value);
var mycounter = counter;
mycounter++;
Console.WriteLine(" Counter: " + mycounter);
counter = mycounter;
}));
}
Console.ReadLine();
}
}
}

@ -1,73 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AutoHotInterception;
class TestApp
namespace TestApp
{
static void Main(string[] args)
internal class TestApp
{
//var mon = new AutoHotInterception.Monitor();
//var devInfo = mon.GetDeviceList();
//mon.Subscribe(new Action<int, ushort, ushort, uint>((id, state, code, info) =>
//{
// Console.WriteLine($"Subscription Value: State={state}, Code={code}");
//}), new Action<int, ushort, ushort, short, int, int, uint>((id, state, flags, rolling, x, y, info) =>
//{
// Console.WriteLine($"Subscription Value: x={x}, y={y}");
//}));
//mon.SetDeviceFilterState(16, true);
//Console.ReadLine();
//return;
// --------------------------------------------------------------
var im = new Manager();
var keyboardId = 0;
//keyboardId = im.GetDeviceId(false, 0x04F2, 0x0112); // WYSE
//keyboardId = im.GetDeviceId(false, 0x413C, 0x2107); // Dell
var mouseId = 0;
//mouseId = im.GetDeviceId(true, 0x46D, 0xC531); // G700s
//mouseId = im.GetDeviceId(true, 0x46D, 0xC00C); // Logitech Wired
mouseId = im.GetDeviceId(true, 0xB57, 0x9091); // Parblo Tablet
//im.SendMouseButtonEvent(mouseId, 1, 1);
//Thread.Sleep(100);
//im.SendMouseButtonEvent(mouseId, 1, 0);
//im.SendMouseMoveRelative(mouseId, 100, 100);
//im.SendMouseMoveAbsolute(mouseId, 100, 100);
if (keyboardId != 0)
private static void Main()
{
im.SubscribeKey(keyboardId, 2, true, new Action<int>((value) =>
{
Console.WriteLine("Subscription Value: " + value);
}));
im.SetContextCallback(keyboardId, new Action<int>((value) =>
{
Console.WriteLine("Context Value: " + value);
}));
}
if (mouseId != 0)
{
im.SubscribeMouseButton(mouseId, 1, true, new Action<int>((value) =>
{
Console.WriteLine("Mouse Button Value: " + value);
}));
im.SubscribeMouseMoveRelative(mouseId, false, new Action<int, int>((x, y) =>
{
Console.WriteLine($"Mouse Axis Value: x={x}, y={y}");
}));
//var mt = new MouseTester();
var kt = new KeyboardTester();
}
Console.ReadLine();
}
}
}

@ -42,6 +42,8 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="KeyboardTester.cs" />
<Compile Include="MouseTester.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
@ -56,7 +58,9 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>if not exist "$(TargetDir)interception.dll" xcopy "$(SolutionDir)dependencies\interception.dll" "$(TargetDir)"
</PreBuildEvent>
<PreBuildEvent>if not exist "$(TargetDir)\x86" mkdir "$(TargetDir)\x86"
if not exist "$(TargetDir)\x64" mkdir "$(TargetDir)\x64"
if not exist "$(TargetDir)\x86\interception.dll" xcopy "$(SolutionDir)\dependencies\x86\interception.dll" "$(TargetDir)\x86"
if not exist "$(TargetDir)\x64\interception.dll" xcopy "$(SolutionDir)\dependencies\x64\interception.dll" "$(TargetDir)\x64"</PreBuildEvent>
</PropertyGroup>
</Project>

@ -5,14 +5,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
### Added
### Changed
### Changed
### Deprecated
### Removed
### Fixed
## [0.4.1] - 2019-05-15
### Fixed
- Button state is no longer inverted as it was in 0.4.0
## [0.4.0] - 2019-05-14
### Added
- Concurrency switch for executing subscription callback functions. Was implicitly executing on a new thread from the pool, now there is an option to execute each callback on a single thread (one worker per subscription).
- UnsubscribeKey, UnsubscribeMouseButton, UnsubscribeMouseMove, UnsubscribeMouseMoveRelative, UnsubscribeMouseMoveAbsolute methods added to Subscription Mode
- "Unsubscription Example.ahk" to demo Subscribe / Unsubscribe
### Changed
- By default the new concurrency switch will be set to false meaning that for every subscription there will be only a single worker thread and callbacks will be run sequentially.
- Monitor now outputs data as would be seen in Subscription mode, rather than as it comes raw from Interception
- Monitor now shows key names
### Deprecated
### Removed
- Interception DLLs are no longer bundled with AHI
### Fixed
- Pause button now works
- SubscribeMouseMove endpoint fixed to not return bool (Fix "Can not implicitly convert type Void to object" error)
- Pause button now works
- UnsubscribeMouseButton now correctly checks if the device is a mouse
## [0.3.7] - 2019-02-10
### Added

@ -2,18 +2,18 @@
class AutoHotInterception {
_contextManagers := {}
;_contextStates := {}
__New(cls := "Manager"){
__New(cls := "Manager") {
bitness := A_PtrSize == 8 ? "x64" : "x86"
dllName := "interception.dll"
dllFile := A_LineFile "\..\" bitness "\" dllName
if (!FileExist(dllFile)){
if (!FileExist(dllFile)) {
MsgBox % "Unable to find lib\" bitness "\" dllName ", 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){
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 "."
@ -24,7 +24,7 @@ class AutoHotInterception {
dllName := "AutoHotInterception.dll"
dllFile := A_LineFile "\..\" dllName
hintMessage := "Try right-clicking lib\" dllName ", 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)){
if (!FileExist(dllFile)) {
MsgBox % "Unable to find lib\" dllName ", exiting..."
ExitApp
}
@ -37,79 +37,79 @@ class AutoHotInterception {
MsgBox % dllName " failed to load`n`n" hintMessage
ExitApp
}
if (this.Instance.OkCheck() != "OK"){
if (this.Instance.OkCheck() != "OK") {
MsgBox % dllName " loaded but check failed!`n`n" hintMessage
ExitApp
}
}
GetInstance(){
GetInstance() {
return this.Instance
}
; --------------- Input Synthesis ----------------
SendKeyEvent(id, code, state){
SendKeyEvent(id, code, state) {
this.Instance.SendKeyEvent(id, code, state)
}
SendMouseButtonEvent(id, btn, state){
SendMouseButtonEvent(id, btn, state) {
this.Instance.SendMouseButtonEvent(id, btn, state)
}
SendMouseButtonEventAbsolute(id, btn, state, x, y){
SendMouseButtonEventAbsolute(id, btn, state, x, y) {
this.Instance.SendMouseButtonEventAbsolute(id, btn, state, x, y)
}
SendMouseMove(id, x, y){
SendMouseMove(id, x, y) {
this.Instance.SendMouseMove(id, x, y)
}
SendMouseMoveRelative(id, x, y){
SendMouseMoveRelative(id, x, y) {
this.Instance.SendMouseMoveRelative(id, x, y)
}
SendMouseMoveAbsolute(id, x, y){
SendMouseMoveAbsolute(id, x, y) {
this.Instance.SendMouseMoveAbsolute(id, x, y)
}
*/
; --------------- Querying ------------------------
GetDeviceID(IsMouse, VID, PID, instance := 1){
GetDeviceId(IsMouse, VID, PID, instance := 1) {
static devType := {0: "Keyboard", 1: "Mouse"}
dev := this.Instance.GetDeviceId(IsMouse, VID, PID, instance)
if (dev == 0){
if (dev == 0) {
MsgBox % "Could not get " devType[isMouse] " with VID " VID ", PID " PID ", Instance " instance
ExitApp
}
return dev
}
GetDeviceIdFromHandle(isMouse, handle, instance := 1){
GetDeviceIdFromHandle(isMouse, handle, instance := 1) {
static devType := {0: "Keyboard", 1: "Mouse"}
dev := this.Instance.GetDeviceIdFromHandle(IsMouse, handle, instance)
if (dev == 0){
if (dev == 0) {
MsgBox % "Could not get " devType[isMouse] " with Handle " handle ", Instance " instance
ExitApp
}
return dev
}
GetKeyboardID(VID, PID, instance := 1){
GetKeyboardId(VID, PID, instance := 1) {
return this.GetDeviceId(false, VID, PID, instance)
}
GetMouseID(VID, PID, instance := 1){
GetMouseId(VID, PID, instance := 1) {
return this.GetDeviceId(true, VID, PID, instance)
}
GetKeyboardIdFromHandle(handle, instance := 1){
GetKeyboardIdFromHandle(handle, instance := 1) {
return this.GetDeviceIdFromHandle(false, handle, instance)
}
GetMouseIDFromHandle(handle, instance := 1){
GetMouseIdFromHandle(handle, instance := 1) {
return this.GetDeviceIdFromHandle(true, handle, instance)
}
GetDeviceList(){
GetDeviceList() {
DeviceList := {}
arr := this.Instance.GetDeviceList()
for v in arr {
@ -117,52 +117,52 @@ class AutoHotInterception {
}
return DeviceList
}
; ---------------------- Subscription Mode ----------------------
SubscribeKey(id, code, block, callback){
this.Instance.SubscribeKey(id, code, block, callback)
SubscribeKey(id, code, block, callback, concurrent := false) {
this.Instance.SubscribeKey(id, code, block, callback, concurrent)
}
UnsubscribeKey(id, code){
this.Instance.UnsubscribeKey(id, code)
}
SubscribeMouseButton(id, btn, block, callback){
this.Instance.SubscribeMouseButton(id, btn, block, callback)
SubscribeMouseButton(id, btn, block, callback, concurrent := false) {
this.Instance.SubscribeMouseButton(id, btn, block, callback, concurrent)
}
UnsubscribeMouseButton(id, btn){
this.Instance.UnsubscribeMouseButton(id, btn)
}
SubscribeMouseMove(id, block, callback){
this.Instance.SubscribeMouseMove(id, block, callback)
SubscribeMouseMove(id, block, callback, concurrent := false) {
this.Instance.SubscribeMouseMove(id, block, callback, concurrent)
}
UnsubscribeMouseMove(id){
this.Instance.UnsubscribeMouseMove(id)
}
SubscribeMouseMoveRelative(id, block, callback){
this.Instance.SubscribeMouseMoveRelative(id, block, callback)
SubscribeMouseMoveRelative(id, block, callback, concurrent := false) {
this.Instance.SubscribeMouseMoveRelative(id, block, callback, concurrent)
}
UnsubscribeMouseMoveRelative(id){
this.Instance.UnsubscribeMouseMoveRelative(id)
}
SubscribeMouseMoveAbsolute(id, block, callback){
this.Instance.SubscribeMouseMoveAbsolute(id, block, callback)
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.ContainsKey(id)){
CreateContextManager(id) {
if (this._contextManagers.ContainsKey(id)) {
Msgbox % "ID " id " already has a Context Manager"
ExitApp
}
@ -174,14 +174,13 @@ class AutoHotInterception {
; Helper class for dealing with context mode
class ContextManager {
IsActive := 0
__New(parent, id){
__New(parent, id) {
this.parent := parent
this.id := id
result := this.parent.Instance.SetContextCallback(id, this.OnContextCallback.Bind(this))
}
OnContextCallback(state){
OnContextCallback(state) {
Sleep 0
this.IsActive := state
}

@ -1,16 +1,17 @@
; ==========================================================
; .NET Framework Interop
; http://www.autohotkey.com/forum/topic26191.html
; ==========================================================
; ========================================================================
; .NET Framework Interop
; http://www.autohotkey.com/forum/topic26191.html
; ========================================================================
;
; Author: Lexikos
; Version: 1.2
; Requires: AutoHotkey_L v1.0.96+
; Requires: AutoHotkey_L v1.0.96+
;
; Modified by evilC for compatibility with AHK_H as well as AHK_L
; "null" is a reserved word in AHK_H, so did search & Replace from "null" to "_null"
CLR_LoadLibrary(AssemblyName, AppDomain=0)
{
; "null" is a reserved word in AHK_H, so "null" was replaced with "_null"
; ========================================================================
CLR_LoadLibrary(AssemblyName, AppDomain=0) {
if !AppDomain
AppDomain := CLR_GetDefaultDomain()
e := ComObjError(0)
@ -29,47 +30,43 @@ CLR_LoadLibrary(AssemblyName, AppDomain=0)
return assembly
}
CLR_CreateObject(Assembly, TypeName, Args*)
{
CLR_CreateObject(Assembly, TypeName, Args*) {
if !(argCount := Args.MaxIndex())
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 := ComObject(13,0)
return Assembly.CreateInstance_3(TypeName, true, 0, _null, vargs, _null, Array_Empty)
}
CLR_CompileC#(Code, References="", AppDomain=0, FileName="", CompilerOptions="")
{
CLR_CompileC#(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="")
{
CLR_CompileVB(Code, References="", AppDomain=0, FileName="", CompilerOptions="") {
return CLR_CompileAssembly(Code, References, "System", "Microsoft.VisualBasic.VBCodeProvider", AppDomain, FileName, CompilerOptions)
}
CLR_StartDomain(ByRef AppDomain, BaseDirectory="")
{
CLR_StartDomain(ByRef AppDomain, BaseDirectory="") {
static _null := ComObject(13,0)
args := ComObjArray(0xC, 5), args[0] := "", args[2] := BaseDirectory, args[4] := ComObject(0xB,false)
AppDomain := CLR_GetDefaultDomain().GetType().InvokeMember_3("CreateDomain", 0x158, _null, _null, args)
return A_LastError >= 0
}
CLR_StopDomain(ByRef AppDomain)
{ ; ICorRuntimeHost::UnloadDomain
CLR_StopDomain(ByRef AppDomain) {
; ICorRuntimeHost::UnloadDomain
DllCall("SetLastError", "uint", hr := DllCall(NumGet(NumGet(0+RtHst:=CLR_Start())+20*A_PtrSize), "ptr", RtHst, "ptr", ComObjValue(AppDomain))), AppDomain := ""
return hr >= 0
}
; NOTE: IT IS NOT NECESSARY TO CALL THIS FUNCTION unless you need to load a specific version.
CLR_Start(Version="") ; returns ICorRuntimeHost*
{
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))
@ -92,22 +89,20 @@ CLR_Start(Version="") ; returns ICorRuntimeHost*
; INTERNAL FUNCTIONS
;
CLR_GetDefaultDomain()
{
CLR_GetDefaultDomain() {
static defaultDomain := 0
if !defaultDomain
{ ; ICorRuntimeHost::GetDefaultDomain
if !defaultDomain {
; ICorRuntimeHost::GetDefaultDomain
if DllCall(NumGet(NumGet(0+RtHst:=CLR_Start())+13*A_PtrSize), "ptr", RtHst, "ptr*", p:=0) >= 0
defaultDomain := ComObject(p), ObjRelease(p)
}
return defaultDomain
}
CLR_CompileAssembly(Code, References, ProviderAssembly, ProviderType, AppDomain=0, FileName="", CompilerOptions="")
{
CLR_CompileAssembly(Code, References, ProviderAssembly, ProviderType, AppDomain=0, FileName="", CompilerOptions="") {
if !AppDomain
AppDomain := CLR_GetDefaultDomain()
if !(asmProvider := CLR_LoadLibrary(ProviderAssembly, AppDomain))
|| !(codeProvider := asmProvider.CreateInstance(ProviderType))
|| !(codeCompiler := codeProvider.CreateCompiler())
@ -115,13 +110,13 @@ CLR_CompileAssembly(Code, References, ProviderAssembly, ProviderType, AppDomain=
if !(asmSystem := (ProviderAssembly="System") ? asmProvider : CLR_LoadLibrary("System", AppDomain))
return 0
; Convert | delimited list of references into an array.
StringSplit, Refs, References, |, %A_Space%%A_Tab%
aRefs := ComObjArray(8, Refs0)
Loop % Refs0
aRefs[A_Index-1] := Refs%A_Index%
; Set parameters for compiler.
prms := CLR_CreateObject(asmSystem, "System.CodeDom.Compiler.CompilerParameters", aRefs)
, prms.OutputAssembly := FileName
@ -129,12 +124,11 @@ CLR_CompileAssembly(Code, References, ProviderAssembly, ProviderType, AppDomain=
, prms.GenerateExecutable := SubStr(FileName,-3)=".exe"
, prms.CompilerOptions := CompilerOptions
, prms.IncludeDebugInformation := true
; Compile!
compilerRes := codeCompiler.CompileAssemblyFromSource(prms, Code)
if error_count := (errors := compilerRes.Errors).Count
{
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"
@ -145,8 +139,7 @@ CLR_CompileAssembly(Code, References, ProviderAssembly, ProviderType, AppDomain=
return compilerRes[FileName="" ? "CompiledAssembly" : "PathToAssembly"]
}
CLR_GUID(ByRef GUID, sGUID)
{
CLR_GUID(ByRef GUID, sGUID) {
VarSetCapacity(GUID, 16, 0)
return DllCall("ole32\CLSIDFromString", "wstr", sGUID, "ptr", &GUID) >= 0 ? &GUID : ""
}
}

@ -0,0 +1,2 @@
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!!

@ -0,0 +1,2 @@
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!!

@ -28,7 +28,7 @@ Loop 2 {
if (!IsObject(dev)){
continue
}
Gui, Add, Checkbox, % "hwndhCb w" colWidth, % "ID: " dev.id ", VID: 0x" FormatHex(dev.VID) ", PID: 0x" FormatHex(dev.PID) "`nHandle: " dev.Handle
Gui, Add, Checkbox, % "hwndhCb w" colWidth, % "ID: " dev.id ", VID: 0x" FormatHex(dev.VID) ", PID: 0x" FormatHex(dev.PID) "`nHandle: " StrReplace(dev.Handle, "&", "&&")
fn := Func("CheckboxChanged").Bind(dev.id)
GuiControl, +g, % hCb, % fn
}

@ -6,7 +6,7 @@
# AutoHotInterception
AutoHotInterception (AHI) allows you to execute AutoHotkey code in response to events from a *specific* keyboard or mouse, whilst (optionally) blocking the native functionality (ie stopping Windows from seeing that keyboard or mouse event).
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.
@ -50,18 +50,32 @@ You will need to know the VID / PID of at least one of your devices in order to
2. 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 libraries and files needed for AHI.
AHI comes with the latest x86 Interception dll, so as long as you are running x86 AHK (x86 Unicode is recommended), then you can probably skip step 3.
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.
From the folder that matches the bitness of AHK you have installed, take `interception.dll` and copy it to the AHI `lib` folder that was created in step (2).
Copy both of these folders into the AHI `lib` folder that you created in step (3) - the folder structure should end up looking like:
```
AHI Root Folder
Monitor.ahk
etc...
Lib
AutoHotInterception.ahk
AutoHotInterception.dll
CLR.ahk
Unblocker.ps1
etc..
x86
interception.dll
x64
interception.dll
```
4. Right-click `Unblocker.ps1` in the lib folder and select `Run as Admin`.
This is because downloaded DLLs are often blocked and will not work.
This can be done manually by right clicking the DLLs, selecting Properties, and checking a "Block" box if it exists.
This is because downloaded DLLs are often blocked and will not work.
This can be done manually by right clicking the DLLs, selecting Properties, and checking a "Block" box if it exists.
5. If you do not know the VID/PID of your device, use the included Monitor app to find it.
When using the monitor app, **DO NOT** tick all devices at once, as if it crashes, it will lock up all devices.
Instead, tick one at a time and see if it your device.
When using the monitor app, **DO NOT** tick all devices at once, as if it crashes, it will lock up all devices.
Instead, tick one at a time and see if it your device.
6. Edit one of the example remapping scripts, replacing the VID/PID(s) with that of your device 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.
7. (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.
------
@ -75,7 +89,7 @@ Include the library
Initialize the library
```
global AHI := InterceptionWrapper()
global AHI := new AutoHotInterception()
```
*Note*
@ -168,7 +182,7 @@ Each Subscribe endpont also has a corresponding Unsubscribe endpoint, which remo
#### Subscribing to Keyboard keys
Subscribe to a key on a specific keyboard
`SubscribeKey(<deviceId>, <scanCode>, <block>, <callback>)`
`SubscribeKey(<deviceId>, <scanCode>, <block>, <callback>, <concurrent>)`
`UnsubscribeKey(<deviceId>, <scanCode>)`
```
Interception.SubscribeKey(keyboardId, GetKeySC("1"), true, Func("KeyEvent"))
@ -182,8 +196,9 @@ KeyEvent(state){
}
```
Parameter `<concurrent>` is optional and is <b>false</b> by default meaning that all the events raised for that key will be handled sequentially (i.e. callback function will be called on a single thread). If set to <b>true</b>, a new thread will be created for each event and the callback function will be called on it.
#### Subscribing to Mouse Buttons
`SubscribeMouseButton(<deviceId>, <button>, <block>, <callback>)`
`SubscribeMouseButton(<deviceId>, <button>, <block>, <callback>, <concurrent>)`
`UnsubscribeMouseButton(<deviceId>, <button>)`
Where `button` is one of:
```
@ -210,8 +225,8 @@ Relative mode is for normal mice and most trackpads.
Coordinates will be delta (change)
Each endpoint has two naming variants for convenience, they both do the same.
`SubscribeMouseMove(<deviceId>, <block>, <callback>)`
`SubscribeMouseMoveRelative(<deviceId>, <block>, <callback>)`
`SubscribeMouseMove(<deviceId>, <block>, <callback>, <concurrent>)`
`SubscribeMouseMoveRelative(<deviceId>, <block>, <callback>, <concurrent>)`
`UnsubscribeMouseMove(<deviceId>)`
`UnsubscribeMouseMoveRelative(<deviceId>)`
For Mouse Movement, the callback is passed two ints - x and y.
@ -226,8 +241,9 @@ 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>)`
`SubscribeMouseMoveAbsolute(<deviceId>, <block>, <callback>, <concurrent>)`
`UnsubscribeMouseMoveAbsolute(<deviceId>)`
Again, the callback is passed two ints - x and y.
```
Interception.SubscribeMouseMoveAbsolute(mouseId, false, Func("MouseEvent"))

Loading…
Cancel
Save