diff --git a/C#/AutoHotInterception/Helpers/HelperFunctions.cs b/C#/AutoHotInterception/Helpers/HelperFunctions.cs index 1ee4468..9ca02ab 100644 --- a/C#/AutoHotInterception/Helpers/HelperFunctions.cs +++ b/C#/AutoHotInterception/Helpers/HelperFunctions.cs @@ -55,18 +55,18 @@ namespace AutoHotInterception.Helpers return stroke; } - private static readonly Dictionary ButtonStateLookupTable = new Dictionary() + private static readonly Dictionary StrokeFlagToButtonState = new Dictionary() { - { 1, new ButtonState{Button = 0, State = 1} }, - { 2, new ButtonState{Button = 0, State = 0} }, - { 4, new ButtonState{Button = 1, State = 1} }, - { 8, new ButtonState{Button = 1, State = 0} }, - { 16, new ButtonState{Button = 2, State = 1} }, - { 32, new ButtonState{Button = 2, State = 0} }, - { 64, new ButtonState{Button = 3, State = 1} }, - { 128, new ButtonState{Button = 3, State = 0} }, - { 256, new ButtonState{Button = 4, State = 1} }, - { 512, new ButtonState{Button = 4, State = 0} }, + { 1, new ButtonState{Button = 0, State = 1, Flag = 1} }, // LMB Press + { 2, new ButtonState{Button = 0, State = 0, Flag = 2} }, // LMB Release + { 4, new ButtonState{Button = 1, State = 1, Flag = 4} }, // RMB Press + { 8, new ButtonState{Button = 1, State = 0, Flag = 8} }, // RMB Release + { 16, new ButtonState{Button = 2, State = 1, Flag = 16} }, // MMB Press + { 32, new ButtonState{Button = 2, State = 0, Flag = 32} }, // MMB Release + { 64, new ButtonState{Button = 3, State = 1, Flag = 64} }, // XB1 Press + { 128, new ButtonState{Button = 3, State = 0, Flag = 128} }, // XB1 Release + { 256, new ButtonState{Button = 4, State = 1, Flag = 256} }, // XB2 Press + { 512, new ButtonState{Button = 4, State = 0, Flag = 512} } // XB2 Release }; public static ButtonState[] MouseStrokeToButtonStates(ManagedWrapper.Stroke stroke) @@ -75,7 +75,7 @@ namespace AutoHotInterception.Helpers // Buttons var buttonStates = new List(); - foreach (var buttonState in ButtonStateLookupTable) + foreach (var buttonState in StrokeFlagToButtonState) { if (state < buttonState.Key) break; if ((state & buttonState.Key) != buttonState.Key) continue; @@ -91,7 +91,8 @@ namespace AutoHotInterception.Helpers new ButtonState { Button = 5, - State = (stroke.mouse.rolling < 0 ? -1 : 1) + State = (stroke.mouse.rolling < 0 ? -1 : 1), + Flag = 0x400 } ); } @@ -101,7 +102,8 @@ namespace AutoHotInterception.Helpers new ButtonState { Button = 6, - State = (stroke.mouse.rolling < 0 ? -1 : 1) + State = (stroke.mouse.rolling < 0 ? -1 : 1), + Flag = 0x800 } ); } @@ -165,6 +167,7 @@ namespace AutoHotInterception.Helpers { public ushort Button { get; set; } public int State { get; set; } + public ushort Flag { get; set; } // Preserve original flag, so it can be removed from stroke } public class KeyboardState diff --git a/C#/AutoHotInterception/Manager.cs b/C#/AutoHotInterception/Manager.cs index 6b61acc..0a347a5 100644 --- a/C#/AutoHotInterception/Manager.cs +++ b/C#/AutoHotInterception/Manager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Diagnostics; using System.Threading; using AutoHotInterception.Helpers; using static AutoHotInterception.Helpers.HelperFunctions; @@ -601,79 +602,147 @@ namespace AutoHotInterception while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) { - //Debug.WriteLine($"AHK| Mouse {i} seen - flags: {stroke.mouse.flags}, raw state: {stroke.mouse.state}"); - var block = false; - if (isMonitoredMouse) + if (!isMonitoredMouse) continue; + + var moveRemoved = false; + var hasMove = false; + var x = stroke.mouse.x; + var y = stroke.mouse.y; + //Debug.WriteLine($"AHK| Stroke Seen. State = {stroke.mouse.state}, Flags = {stroke.mouse.flags}, x={x}, y={y}"); + // Process Mouse Buttons + if (stroke.mouse.state != 0 && _mouseButtonMappings.ContainsKey(i)) { - if (stroke.mouse.state != 0 && _mouseButtonMappings.ContainsKey(i)) + var btnStates = MouseStrokeToButtonStates(stroke); + foreach (var btnState in btnStates) { - // Mouse Button - //Debug.WriteLine($"AHK| Mouse {i} seen - flags: {stroke.mouse.flags}, raw state: {stroke.mouse.state}"); - var btnStates = MouseStrokeToButtonStates(stroke); - foreach (var btnState in btnStates) + if (!_mouseButtonMappings[i].ContainsKey(btnState.Button)) continue; + + hasSubscription = true; + var mapping = _mouseButtonMappings[i][btnState.Button]; + + var state = btnState; + + 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)); + if (mapping.Block) { - if (_mouseButtonMappings[i].ContainsKey(btnState.Button)) + // Remove the event for this button from the stroke, leaving other button events intact + stroke.mouse.state -= btnState.Flag; + // If we are removing a mouse wheel event, then set rolling to 0 if no mouse wheel event left + if (btnState.Flag == 0x400 || btnState.Flag == 0x800) { - hasSubscription = true; - var mapping = _mouseButtonMappings[i][btnState.Button]; - if (mapping.Block) block = true; - - var state = btnState; - - 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)); + if ((stroke.mouse.state & 0x400) != 0x400 && (stroke.mouse.state & 0x800) != 0x800) + { + //Debug.WriteLine("AHK| Removing rolling flag from stroke"); + stroke.mouse.rolling = 0; + } } + //Debug.WriteLine($"AHK| Removing flag {btnState.Flag} from stoke, leaving state {stroke.mouse.state}"); } + else + { + //Debug.WriteLine($"AHK| Leaving flag {btnState.Flag} in stroke"); + } + } + } - //Console.WriteLine($"AHK| Mouse {i} seen - button {btnState.Button}, state: {stroke.mouse.state}, rolling: {stroke.mouse.rolling}"); + // Process Relative Mouse Move + if ((stroke.mouse.flags & (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative) == (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative) + { + if (x != 0 || y != 0) + { + hasMove = true; + if (_mouseMoveRelativeMappings.ContainsKey(i)) + { + var mapping = _mouseMoveRelativeMappings[i]; + hasSubscription = true; + //var debugStr = $"AHK| Mouse stroke has relative move of {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 (mapping.Block) + { + moveRemoved = true; + //debugStr += "Blocking"; + } + else + { + //debugStr += "Not Blocking"; + } + //Debug.WriteLine(debugStr); + } } - else if ((stroke.mouse.flags & (ushort) ManagedWrapper.MouseFlag.MouseMoveAbsolute) == - (ushort) ManagedWrapper.MouseFlag.MouseMoveAbsolute - && _mouseMoveAbsoluteMappings.ContainsKey(i)) + } + // Process Absolute Mouse Move + else if ((stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) == (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) + { + if (x != 0 || y != 0) { - // Absolute Mouse Move - hasSubscription = true; - var mapping = _mouseMoveAbsoluteMappings[i]; - if (mapping.Block) block = true; + hasMove = true; + if (_mouseMoveAbsoluteMappings.ContainsKey(i)) + { + var mapping = _mouseMoveAbsoluteMappings[i]; + hasSubscription = true; + //var debugStr = $"AHK| Mouse stroke has absolute move of {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)); + if (mapping.Block) + { + moveRemoved = true; + //debugStr += "Blocking"; + } + else + { + //debugStr += "Not Blocking"; + } + //Debug.WriteLine(debugStr); + } + } + } - var x = stroke.mouse.x; - var y = stroke.mouse.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)); + // Forward on the stroke if required + if (hasSubscription) + { + // Subscription mode + // If the stroke has a move that was not removed, OR it has remaining button events, then forward on the stroke + if ((hasMove && !moveRemoved) || stroke.mouse.state != 0) + { + //Debug.WriteLine($"AHK| Sending stroke. State = {stroke.mouse.state}. hasMove={hasMove}, moveRemoved={moveRemoved}"); + ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); } - else if ((stroke.mouse.flags & (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative) == - (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative - && _mouseMoveRelativeMappings.ContainsKey(i)) + else { - // Relative Mouse Move - hasSubscription = true; - var mapping = _mouseMoveRelativeMappings[i]; - if (mapping.Block) block = true; - - var x = stroke.mouse.x; - var y = stroke.mouse.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)); + // Everything removed from stroke, do not forward + //Debug.WriteLine("AHK| Mouse stroke now empty, not forwarding"); } } - - // 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) _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) _contextCallbacks[i](0); + else if (hasContext) + { + // Context Mode - forward stroke with context wrapping + _contextCallbacks[i](1); + ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); + _contextCallbacks[i](0); + } + else + { + // No subscription or context mode - forward on + //Debug.WriteLine($"AHK| Sending stroke. State = {stroke.mouse.state}. hasMove={hasMove}, moveRemoved={moveRemoved}"); + ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); + } + //Debug.WriteLine($"AHK| "); } } + // ToDo: Can this sleep be removed? Will removing it consume a lot of CPU? It will certainly make updates only happen once every ~10ms, which is too slow for mice polling @ 1khz Thread.Sleep(10); } } diff --git a/CHANGELOG.md b/CHANGELOG.md index fed0cbf..5c402b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] ### Added -- Add option to filter key presses and only show key releases ### Changed ### Deprecated ### Removed ### Fixed -- GUI layout made more robust + +## [0.4.3] - 2019-06-10 **EXPERIMENTAL TEST RELEASE** +- Fixed issue #39 +Almost complete rewrite of mouse polling code +Multiple event types (Movement, mouse button events) supported per update ("stroke") of the mouse +It is now possible to block a button or movement, but leave unblocked events unblocked +Previously, a stroke was either blocked or not - if any one part of the stroke was blocked, it was all blocked +- [Monitor script] GUI layout made more robust +- [Monitor script] Add option to filter key presses and only show key releases ## [0.4.2] - 2019-06-08 - Fixed issue #37