diff --git a/AHK v1/Lib/AutoHotInterception.ahk b/AHK v1/Lib/AutoHotInterception.ahk index 9bde5ba..efa5827 100644 --- a/AHK v1/Lib/AutoHotInterception.ahk +++ b/AHK v1/Lib/AutoHotInterception.ahk @@ -166,7 +166,13 @@ class AutoHotInterception { ; ---------------------- Subscription Mode ---------------------- SubscribeKey(id, code, block, callback, concurrent := false) { - this.Instance.SubscribeKey(id, code, block, callback, concurrent) + switch callback.MaxParams + { + case 1: + this.Instance.SubscribeKey(id, code, block, callback, concurrent) + case 2: + this.Instance.SubscribeKeyEx(id, code, block, callback, concurrent) + } } UnsubscribeKey(id, code){ @@ -261,4 +267,4 @@ class AutoHotInterception { this.parent.Instance.RemoveContextCallback(this.id) } } -} \ No newline at end of file +} diff --git a/C#/AutoHotInterception/DeviceHandlers/DeviceHandler.cs b/C#/AutoHotInterception/DeviceHandlers/DeviceHandler.cs index c48f97f..50a3795 100644 --- a/C#/AutoHotInterception/DeviceHandlers/DeviceHandler.cs +++ b/C#/AutoHotInterception/DeviceHandlers/DeviceHandler.cs @@ -15,6 +15,7 @@ abstract class DeviceHandler : IDeviceHandler // Holds MappingOptions for individual mouse button / keyboard key subscriptions protected ConcurrentDictionary SingleButtonMappings = new ConcurrentDictionary(); + protected ConcurrentDictionary SingleButtonMappingsEx = new ConcurrentDictionary(); // If all mouse buttons or keyboard keys are subscribed, this holds the mapping options protected MappingOptions AllButtonsMapping; @@ -42,6 +43,21 @@ public void SubscribeSingleButton(ushort code, MappingOptions mappingOptions) _isFiltered = true; } + /// + /// Subscribes to a single key or button of this device + /// + /// The ScanCode (keyboard) or Button Code (mouse) for the key or button + /// Options for the subscription (block, callback to fire etc) + public void SubscribeSingleButtonEx(ushort code, MappingOptions mappingOptions) + { + SingleButtonMappingsEx.TryAdd(code, mappingOptions); + if (!mappingOptions.Concurrent && !WorkerThreads.ContainsKey(code)) + { + WorkerThreads.TryAdd(code, new WorkerThread()); + } + _isFiltered = true; + } + /// /// Unsubscribes from a single key or button of this device /// @@ -58,6 +74,22 @@ public void UnsubscribeSingleButton(ushort code) DisableFilterIfNeeded(); } + /// + /// Unsubscribes from a single key or button of this device + /// + /// The ScanCode (keyboard) or Button Code (mouse) for the key or button + public void UnsubscribeSingleButtonEx(ushort code) + { + if (!SingleButtonMappingsEx.ContainsKey(code)) return; + SingleButtonMappingsEx.TryRemove(code, out var mappingOptions); + if (!mappingOptions.Concurrent && WorkerThreads.ContainsKey(code)) + { + WorkerThreads[code].Dispose(); + WorkerThreads.TryRemove(code, out _); + } + DisableFilterIfNeeded(); + } + /// /// Subscribes to all keys or buttons of this device /// diff --git a/C#/AutoHotInterception/DeviceHandlers/IDeviceHandler.cs b/C#/AutoHotInterception/DeviceHandlers/IDeviceHandler.cs index b3caa60..5424919 100644 --- a/C#/AutoHotInterception/DeviceHandlers/IDeviceHandler.cs +++ b/C#/AutoHotInterception/DeviceHandlers/IDeviceHandler.cs @@ -19,6 +19,13 @@ interface IDeviceHandler /// Options for the subscription (block, callback to fire etc) void SubscribeSingleButton(ushort code, MappingOptions mappingOptions); + /// + /// Subscribes to a single key or button of this device + /// + /// The ScanCode (keyboard) or Button Code (mouse) for the key or button + /// Options for the subscription (block, callback to fire etc) + void SubscribeSingleButtonEx(ushort code, MappingOptions mappingOptions); + /// /// Unsubscribes from a single key or button of this device /// diff --git a/C#/AutoHotInterception/DeviceHandlers/KeyboardHandler.cs b/C#/AutoHotInterception/DeviceHandlers/KeyboardHandler.cs index 106610c..80509ab 100644 --- a/C#/AutoHotInterception/DeviceHandlers/KeyboardHandler.cs +++ b/C#/AutoHotInterception/DeviceHandlers/KeyboardHandler.cs @@ -21,6 +21,7 @@ public override void DisableFilterIfNeeded() { if (AllButtonsMapping == null && SingleButtonMappings.Count == 0 + && SingleButtonMappingsEx.Count == 0 && ContextCallback == null) { _isFiltered = false; @@ -56,6 +57,7 @@ public override void ProcessStroke(List strokes) if (_isFiltered) { var isKeyMapping = false; // True if this is a mapping to a single key, else it would be a mapping to a whole device + var useExtendedCallback = false; var processedState = ScanCodeHelper.TranslateScanCodes(strokes); var code = processedState.Code; var state = processedState.State; @@ -65,8 +67,16 @@ public override void ProcessStroke(List strokes) if (SingleButtonMappings.ContainsKey(code)) { isKeyMapping = true; + useExtendedCallback = false; mapping = SingleButtonMappings[code]; } + // If there is an mapping (via SubscribeKeyEx) to this specific key, then use that ... + if (SingleButtonMappingsEx.ContainsKey(code)) + { + isKeyMapping = true; + useExtendedCallback = true; + mapping = SingleButtonMappingsEx[code]; + } // ... otherwise, if there is a mapping to the whole keyboard, use that else if (AllButtonsMapping != null) { @@ -82,7 +92,14 @@ public override void ProcessStroke(List strokes) { if (isKeyMapping) { - ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state)); + if (useExtendedCallback) + { + ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state, code)); + } + else + { + ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state)); + } } else { @@ -94,7 +111,14 @@ public override void ProcessStroke(List strokes) //mapping.Callback(code, state); if (isKeyMapping) { - WorkerThreads[code]?.Actions.Add(() => mapping.Callback(state)); + if (useExtendedCallback) + { + WorkerThreads[code]?.Actions.Add(() => mapping.Callback(state, code)); + } + else + { + WorkerThreads[code]?.Actions.Add(() => mapping.Callback(state)); + } } else { diff --git a/C#/AutoHotInterception/Manager.cs b/C#/AutoHotInterception/Manager.cs index 3a6a500..0665d8b 100644 --- a/C#/AutoHotInterception/Manager.cs +++ b/C#/AutoHotInterception/Manager.cs @@ -86,6 +86,27 @@ public void SubscribeKey(int id, ushort code, bool block, dynamic callback, bool SetThreadState(true); } + /// + /// Subscribes to a Keyboard key + /// + /// The ID of the Keyboard + /// The ScanCode of the key + /// Whether or not to block the key + /// The callback to fire when the key changes state + /// Whether or not to execute callbacks concurrently + /// + public void SubscribeKeyEx(int id, ushort code, bool block, dynamic callback, bool concurrent = false) + { + HelperFunctions.IsValidDeviceId(false, id); + SetFilterState(false); + + var handler = DeviceHandlers[id]; + handler.SubscribeSingleButtonEx(code, new MappingOptions { Block = block, Concurrent = concurrent, Callback = callback }); + + SetFilterState(true); + SetThreadState(true); + } + /// /// Unsubscribe from a keyboard key /// @@ -103,6 +124,23 @@ public void UnsubscribeKey(int id, ushort code) SetThreadState(true); } + /// + /// Unsubscribe from a keyboard key + /// + /// The id of the keyboard + /// The Scancode of the key + public void UnsubscribeKeyEx(int id, ushort code) + { + HelperFunctions.IsValidDeviceId(false, id); + SetFilterState(false); + + var handler = DeviceHandlers[id]; + handler.UnsubscribeSingleButton(code); + + SetFilterState(true); + SetThreadState(true); + } + /// /// Subscribe to all keys on a keyboard /// @@ -583,4 +621,4 @@ private static void PollThread(object obj) } #endregion } -} \ No newline at end of file +} diff --git a/C#/TestApp/KeyboardKeyTester.cs b/C#/TestApp/KeyboardKeyTester.cs index 912cd8f..c17238d 100644 --- a/C#/TestApp/KeyboardKeyTester.cs +++ b/C#/TestApp/KeyboardKeyTester.cs @@ -16,12 +16,17 @@ public KeyboardKeyTester(TestDevice device, AhkKey key, bool block = false) if (devId == 0) return; im.SubscribeKey(devId, 0x2, block, new Action(OnKeyEvent)); + im.SubscribeKeyEx(devId, 0x3, block, new Action(OnKeyEventEx)); } public void OnKeyEvent(int value) { Console.WriteLine($"State: {value}"); } + public void OnKeyEventEx(int value, ushort code) + { + Console.WriteLine($"State: {value} Code: {code}"); + } } }