From ab757ee0cdb119307d551ef9c95d5a30e4242505 Mon Sep 17 00:00:00 2001 From: Clive Galway Date: Sun, 13 Oct 2019 18:19:19 +0100 Subject: [PATCH] Poll devices at 1ms using Multimedia Timer --- .../AutoHotInterception.csproj | 1 + .../Helpers/MultimediaTimer.cs | 208 ++++++++++ C#/AutoHotInterception/Manager.cs | 355 +++++++++--------- C#/AutoHotInterception/Monitor.cs | 146 +++---- C#/TestApp/MonitorTester.cs | 10 +- 5 files changed, 468 insertions(+), 252 deletions(-) create mode 100644 C#/AutoHotInterception/Helpers/MultimediaTimer.cs diff --git a/C#/AutoHotInterception/AutoHotInterception.csproj b/C#/AutoHotInterception/AutoHotInterception.csproj index a3b0a5d..f6283c0 100644 --- a/C#/AutoHotInterception/AutoHotInterception.csproj +++ b/C#/AutoHotInterception/AutoHotInterception.csproj @@ -42,6 +42,7 @@ + diff --git a/C#/AutoHotInterception/Helpers/MultimediaTimer.cs b/C#/AutoHotInterception/Helpers/MultimediaTimer.cs new file mode 100644 index 0000000..fd10334 --- /dev/null +++ b/C#/AutoHotInterception/Helpers/MultimediaTimer.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoHotInterception.Helpers +{ + public class MultimediaTimer : IDisposable + { + private const int EventTypeSingle = 0; + private const int EventTypePeriodic = 1; + + private static readonly Task TaskDone = Task.FromResult(null); + + private bool disposed = false; + private int interval, resolution; + private volatile uint timerId; + + // Hold the timer callback to prevent garbage collection. + private readonly MultimediaTimerCallback Callback; + + public MultimediaTimer() + { + Callback = new MultimediaTimerCallback(TimerCallbackMethod); + Resolution = 5; + Interval = 10; + } + + ~MultimediaTimer() + { + Dispose(false); + } + + /// + /// The period of the timer in milliseconds. + /// + public int Interval + { + get + { + return interval; + } + set + { + CheckDisposed(); + + if (value < 0) + throw new ArgumentOutOfRangeException("value"); + + interval = value; + if (Resolution > Interval) + Resolution = value; + } + } + + /// + /// The resolution of the timer in milliseconds. The minimum resolution is 0, meaning highest possible resolution. + /// + public int Resolution + { + get + { + return resolution; + } + set + { + CheckDisposed(); + + if (value < 0) + throw new ArgumentOutOfRangeException("value"); + + resolution = value; + } + } + + /// + /// Gets whether the timer has been started yet. + /// + public bool IsRunning + { + get { return timerId != 0; } + } + + public static Task Delay(int millisecondsDelay, CancellationToken token = default(CancellationToken)) + { + if (millisecondsDelay < 0) + { + throw new ArgumentOutOfRangeException("millisecondsDelay", millisecondsDelay, "The value cannot be less than 0."); + } + + if (millisecondsDelay == 0) + { + return TaskDone; + } + + token.ThrowIfCancellationRequested(); + + // allocate an object to hold the callback in the async state. + object[] state = new object[1]; + var completionSource = new TaskCompletionSource(state); + MultimediaTimerCallback callback = (uint id, uint msg, ref uint uCtx, uint rsv1, uint rsv2) => + { + // Note we don't need to kill the timer for one-off events. + completionSource.TrySetResult(null); + }; + + state[0] = callback; + UInt32 userCtx = 0; + var timerId = NativeMethods.TimeSetEvent((uint)millisecondsDelay, (uint)0, callback, ref userCtx, EventTypeSingle); + if (timerId == 0) + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error); + } + + return completionSource.Task; + } + + public void Start() + { + CheckDisposed(); + + if (IsRunning) + throw new InvalidOperationException("Timer is already running"); + + // Event type = 0, one off event + // Event type = 1, periodic event + UInt32 userCtx = 0; + timerId = NativeMethods.TimeSetEvent((uint)Interval, (uint)Resolution, Callback, ref userCtx, EventTypePeriodic); + if (timerId == 0) + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error); + } + } + + public void Stop() + { + CheckDisposed(); + + if (!IsRunning) + throw new InvalidOperationException("Timer has not been started"); + + StopInternal(); + } + + private void StopInternal() + { + NativeMethods.TimeKillEvent(timerId); + timerId = 0; + } + + public event EventHandler Elapsed; + + public void Dispose() + { + Dispose(true); + } + + private void TimerCallbackMethod(uint id, uint msg, ref uint userCtx, uint rsv1, uint rsv2) + { + var handler = Elapsed; + if (handler != null) + { + handler(this, EventArgs.Empty); + } + } + + private void CheckDisposed() + { + if (disposed) + throw new ObjectDisposedException("MultimediaTimer"); + } + + private void Dispose(bool disposing) + { + if (disposed) + return; + + disposed = true; + if (IsRunning) + { + StopInternal(); + } + + if (disposing) + { + Elapsed = null; + GC.SuppressFinalize(this); + } + } + } + + internal delegate void MultimediaTimerCallback(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2); + + internal static class NativeMethods + { + [DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeSetEvent")] + internal static extern UInt32 TimeSetEvent(UInt32 msDelay, UInt32 msResolution, MultimediaTimerCallback callback, ref UInt32 userCtx, UInt32 eventType); + + [DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeKillEvent")] + internal static extern void TimeKillEvent(UInt32 uTimerId); + } +} diff --git a/C#/AutoHotInterception/Manager.cs b/C#/AutoHotInterception/Manager.cs index f0899bd..367cfa2 100644 --- a/C#/AutoHotInterception/Manager.cs +++ b/C#/AutoHotInterception/Manager.cs @@ -34,7 +34,8 @@ namespace AutoHotInterception private readonly ConcurrentDictionary> _workerThreads = new ConcurrentDictionary>(); - private Thread _pollThread; + private readonly MultimediaTimer _timer; + private readonly int _pollRate = 1; private volatile bool _pollThreadRunning; #region Public @@ -44,6 +45,8 @@ namespace AutoHotInterception public Manager() { _deviceContext = ManagedWrapper.CreateContext(); + _timer = new MultimediaTimer() { Interval = _pollRate }; + _timer.Elapsed += DoPoll; } public void Dispose() @@ -433,18 +436,19 @@ namespace AutoHotInterception private void SetThreadState(bool state) { - if (state) + if (state && !_timer.IsRunning) { - if (_pollThreadRunning) return; - _pollThreadRunning = true; - _pollThread = new Thread(PollThread); - _pollThread.Start(); + SetFilterState(true); + _timer.Start(); } - else + else if (!state && _timer.IsRunning) { - _pollThreadRunning = false; - _pollThread.Join(); - _pollThread = null; + SetFilterState(false); + _timer.Stop(); + while (_pollThreadRunning) // Are we mid-poll? + { + Thread.Sleep(10); // Wait until poll ends + } } } @@ -486,235 +490,232 @@ namespace AutoHotInterception } // ScanCode notes: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html - private void PollThread() + private void DoPoll(object sender, EventArgs e) { + _pollThreadRunning = true; var stroke = new ManagedWrapper.Stroke(); - while (_pollThreadRunning) + // Iterate through all Keyboards + for (var i = 1; i < 11; i++) { - // Iterate through all Keyboards - for (var i = 1; i < 11; i++) - { - var isMonitoredKeyboard = IsMonitoredDevice(i) == 1; - var hasSubscription = false; - var hasContext = _contextCallbacks.ContainsKey(i); + var isMonitoredKeyboard = IsMonitoredDevice(i) == 1; + var hasSubscription = false; + var hasContext = _contextCallbacks.ContainsKey(i); - // Process any waiting input for this keyboard - while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) + // Process any waiting input for this keyboard + while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) + { + var block = false; + // If this is not a monitored keyboard, skip. + // This check should not really be needed as the IsMonitoredDevice() predicate should only match monitored keyboards... + // ... but in case it does, we want to ignore this bit and pass the input through + if (isMonitoredKeyboard && _keyboardMappings.ContainsKey(i)) { - var block = false; - // If this is not a monitored keyboard, skip. - // This check should not really be needed as the IsMonitoredDevice() predicate should only match monitored keyboards... - // ... but in case it does, we want to ignore this bit and pass the input through - if (isMonitoredKeyboard && _keyboardMappings.ContainsKey(i)) - { - // Process Subscription Mode + // Process Subscription Mode - #region KeyCode, State, Extended Flag translation + #region KeyCode, State, Extended Flag translation - // Begin translation of incoming key code, state, extended flag etc... - var processMappings = true; - var processedState = HelperFunctions.KeyboardStrokeToKeyboardState(stroke); + // Begin translation of incoming key code, state, extended flag etc... + var processMappings = true; + var processedState = HelperFunctions.KeyboardStrokeToKeyboardState(stroke); - #endregion + #endregion - if (processedState.Ignore) - { - // Set flag to stop Context Mode from firing - hasSubscription = true; - // Set flag to indicate disable mapping processing - processMappings = false; - } + if (processedState.Ignore) + { + // Set flag to stop Context Mode from firing + hasSubscription = true; + // Set flag to indicate disable mapping processing + processMappings = false; + } - var code = processedState.Code; - var state = processedState.State; + var code = processedState.Code; + var state = processedState.State; - // Code and state now normalized, proceed with checking for subscriptions... - if (processMappings && _keyboardMappings[i].ContainsKey(code)) - { - hasSubscription = true; - var mapping = _keyboardMappings[i][code]; - 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)); - } + // Code and state now normalized, proceed with checking for subscriptions... + if (processMappings && _keyboardMappings[i].ContainsKey(code)) + { + hasSubscription = true; + var mapping = _keyboardMappings[i][code]; + 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)); } + } - // If the key was blocked by Subscription Mode, then move on to next key... - if (block) continue; + // If the key was blocked by Subscription Mode, then move on to next key... + if (block) continue; - // 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 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); - // Pass the key through to the OS. - ManagedWrapper.Send(_deviceContext, i, ref stroke, 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 we are processing Context Mode, then Unset the context variable after sending the key + if (!hasSubscription && hasContext) _contextCallbacks[i](0); } + } - // Process Mice - for (var i = 11; i < 21; i++) - { - var isMonitoredMouse = IsMonitoredDevice(i) == 1; - var hasSubscription = false; - var hasContext = _contextCallbacks.ContainsKey(i); + // Process Mice + for (var i = 11; i < 21; i++) + { + var isMonitoredMouse = IsMonitoredDevice(i) == 1; + var hasSubscription = false; + var hasContext = _contextCallbacks.ContainsKey(i); - while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) - { - if (!isMonitoredMouse) continue; + while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) + { + 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}"); + 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 movement - if (x != 0 || y != 0) + // Process mouse movement + if (x != 0 || y != 0) + { + hasMove = true; + // Process Absolute Mouse Move + if ((stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) == (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) { - hasMove = true; - // Process Absolute Mouse Move - if ((stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) == (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) + if (_mouseMoveAbsoluteMappings.ContainsKey(i)) { - 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) { - 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; - stroke.mouse.x = 0; - stroke.mouse.y = 0; - //debugStr += "Blocking"; - } - else - { - //debugStr += "Not Blocking"; - } - //Debug.WriteLine(debugStr); + moveRemoved = true; + stroke.mouse.x = 0; + stroke.mouse.y = 0; + //debugStr += "Blocking"; } - } - - // Process Relative Mouse Move - //else if ((stroke.mouse.flags & (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative) == (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative) / flag is 0, so always true! - else - { - if (_mouseMoveRelativeMappings.ContainsKey(i)) + else { - 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; - stroke.mouse.x = 0; - stroke.mouse.y = 0; - //debugStr += "Blocking"; - } - else - { - //debugStr += "Not Blocking"; - } - //Debug.WriteLine(debugStr); + //debugStr += "Not Blocking"; } + //Debug.WriteLine(debugStr); } - } - // Process Mouse Buttons - do this AFTER mouse movement, so that absolute mode has coordinates available at the point that the button callback is fired - if (stroke.mouse.state != 0 && _mouseButtonMappings.ContainsKey(i)) + // Process Relative Mouse Move + //else if ((stroke.mouse.flags & (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative) == (ushort) ManagedWrapper.MouseFlag.MouseMoveRelative) / flag is 0, so always true! + else { - var btnStates = HelperFunctions.MouseStrokeToButtonStates(stroke); - foreach (var btnState in btnStates) + if (_mouseMoveRelativeMappings.ContainsKey(i)) { - if (!_mouseButtonMappings[i].ContainsKey(btnState.Button)) continue; - + var mapping = _mouseMoveRelativeMappings[i]; hasSubscription = true; - var mapping = _mouseButtonMappings[i][btnState.Button]; - - var state = btnState; + //var debugStr = $"AHK| Mouse stroke has relative move of {x}, {y}..."; 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)); + 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) { - // 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) - { - 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}"); + moveRemoved = true; + stroke.mouse.x = 0; + stroke.mouse.y = 0; + //debugStr += "Blocking"; } else { - //Debug.WriteLine($"AHK| Leaving flag {btnState.Flag} in stroke"); + //debugStr += "Not Blocking"; } + //Debug.WriteLine(debugStr); } } + } - // Forward on the stroke if required - if (hasSubscription) + // Process Mouse Buttons - do this AFTER mouse movement, so that absolute mode has coordinates available at the point that the button callback is fired + if (stroke.mouse.state != 0 && _mouseButtonMappings.ContainsKey(i)) + { + var btnStates = HelperFunctions.MouseStrokeToButtonStates(stroke); + foreach (var btnState in btnStates) { - // 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) + 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) { - //Debug.WriteLine($"AHK| Sending stroke. State = {stroke.mouse.state}. hasMove={hasMove}, moveRemoved={moveRemoved}"); - ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); + // 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) + { + 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 { - // Everything removed from stroke, do not forward - //Debug.WriteLine("AHK| Mouse stroke now empty, not forwarding"); + //Debug.WriteLine($"AHK| Leaving flag {btnState.Flag} in stroke"); } } - else if (hasContext) + } + + + // 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) { - // Context Mode - forward stroke with context wrapping - _contextCallbacks[i](1); + //Debug.WriteLine($"AHK| Sending stroke. State = {stroke.mouse.state}. hasMove={hasMove}, moveRemoved={moveRemoved}"); 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); + // Everything removed from stroke, do not forward + //Debug.WriteLine("AHK| Mouse stroke now empty, not forwarding"); } - //Debug.WriteLine($"AHK| "); } + 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); } + + _pollThreadRunning = false; } private class MappingOptions diff --git a/C#/AutoHotInterception/Monitor.cs b/C#/AutoHotInterception/Monitor.cs index 5dae64d..0e39a09 100644 --- a/C#/AutoHotInterception/Monitor.cs +++ b/C#/AutoHotInterception/Monitor.cs @@ -14,7 +14,8 @@ namespace AutoHotInterception private dynamic _keyboardCallback; private dynamic _mouseCallback; - private Thread _pollThread; + private readonly MultimediaTimer _timer; + private readonly int _pollRate = 1; private volatile bool _pollThreadRunning; #region Public @@ -22,6 +23,8 @@ namespace AutoHotInterception public Monitor() { _deviceContext = ManagedWrapper.CreateContext(); + _timer = new MultimediaTimer() { Interval = _pollRate }; + _timer.Elapsed += DoPoll; SetThreadState(true); } @@ -95,18 +98,19 @@ namespace AutoHotInterception private void SetThreadState(bool state) { - if (state) + if (state && !_timer.IsRunning) { - if (_pollThreadRunning) return; - _pollThreadRunning = true; - _pollThread = new Thread(PollThread); - _pollThread.Start(); + SetFilterState(true); + _timer.Start(); } - else + else if (!state && _timer.IsRunning) { - _pollThreadRunning = true; - _pollThread.Join(); - _pollThread = null; + SetFilterState(false); + _timer.Stop(); + while (_pollThreadRunning) // Are we mid-poll? + { + Thread.Sleep(10); // Wait until poll ends + } } } @@ -121,87 +125,87 @@ namespace AutoHotInterception state ? ManagedWrapper.Filter.All : ManagedWrapper.Filter.None); } - private void PollThread() + private void DoPoll(object sender, EventArgs e) { + _pollThreadRunning = true; + var stroke = new ManagedWrapper.Stroke(); - while (_pollThreadRunning) + for (var i = 1; i < 11; i++) { - for (var i = 1; i < 11; i++) + while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) { - while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) - { - ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); - var processedState = HelperFunctions.KeyboardStrokeToKeyboardState(stroke); - if (processedState.Ignore) - FireKeyboardCallback(i, new KeyboardCallback - { - Id = i, - Code = stroke.key.code, - State = stroke.key.state, - Info = "Ignored - showing raw values" - }); - else - FireKeyboardCallback(i, new KeyboardCallback - { - Id = i, - Code = processedState.Code, - State = processedState.State, - Info = stroke.key.code > 255 ? "Extended" : "" - }); - } + ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); + var processedState = HelperFunctions.KeyboardStrokeToKeyboardState(stroke); + if (processedState.Ignore) + FireKeyboardCallback(i, new KeyboardCallback + { + Id = i, + Code = stroke.key.code, + State = stroke.key.state, + Info = "Ignored - showing raw values" + }); + else + FireKeyboardCallback(i, new KeyboardCallback + { + Id = i, + Code = processedState.Code, + State = processedState.State, + Info = stroke.key.code > 255 ? "Extended" : "" + }); } + } - for (var i = 11; i < 21; i++) + for (var i = 11; i < 21; i++) + { + while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) { - while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) + ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); + if (stroke.mouse.state != 0) { - ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); - if (stroke.mouse.state != 0) + // Mouse Button + var btnStates = HelperFunctions.MouseStrokeToButtonStates(stroke); + foreach (var btnState in btnStates) { - // Mouse Button - var btnStates = HelperFunctions.MouseStrokeToButtonStates(stroke); - foreach (var btnState in btnStates) - { - FireMouseCallback(new MouseCallback - { - Id = i, - Code = btnState.Button, - State = btnState.State, - Info = "Mouse Button" - }); - } - } - else if ((stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) == - (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) - { - // Absolute Mouse Move FireMouseCallback(new MouseCallback { Id = i, - X = stroke.mouse.x, - Y = stroke.mouse.y, - Info = "Absolute Move" + Code = btnState.Button, + State = btnState.State, + Info = "Mouse Button" }); } - else if ((stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveRelative) == - (ushort)ManagedWrapper.MouseFlag.MouseMoveRelative) + } + else if ((stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) == + (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) + { + // Absolute Mouse Move + FireMouseCallback(new MouseCallback + { + Id = i, + X = stroke.mouse.x, + Y = stroke.mouse.y, + Info = "Absolute Move" + }); + } + else if ((stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveRelative) == + (ushort)ManagedWrapper.MouseFlag.MouseMoveRelative) + { + // Relative Mouse Move + FireMouseCallback(new MouseCallback { - // Relative Mouse Move - FireMouseCallback(new MouseCallback - { - Id = i, - X = stroke.mouse.x, - Y = stroke.mouse.y, - Info = "Relative Move" - }); - } + Id = i, + X = stroke.mouse.x, + Y = stroke.mouse.y, + Info = "Relative Move" + }); } } - - Thread.Sleep(10); } + + + _pollThreadRunning = false; } private void FireKeyboardCallback(int id, KeyboardCallback data) diff --git a/C#/TestApp/MonitorTester.cs b/C#/TestApp/MonitorTester.cs index f68cf15..a45bf45 100644 --- a/C#/TestApp/MonitorTester.cs +++ b/C#/TestApp/MonitorTester.cs @@ -16,14 +16,16 @@ namespace TestApp { Console.WriteLine($"Keyboard: ID={id}, Code={code}, Value={value}, Info={info}"); }), - new Action((id, code, value, info) => + new Action((id, code, value, x, y, info) => { - Console.WriteLine($"Mouse: ID={id}, Code={code}, Value={value}, Info={info}"); + Console.WriteLine($"Mouse: ID={id}, Code={code}, Value={value}, X={x}, Y={y}, Info={info}"); }) ); - var devId = mon.GetKeyboardId(0x04F2, 0x0112); - mon.SetDeviceFilterState(devId, true); + var keyboardId = mon.GetKeyboardId(0x04F2, 0x0112); + mon.SetDeviceFilterState(keyboardId, true); + var mouseId = mon.GetMouseIdFromHandle(@"HID\VID_046D&PID_C00C&REV_0620"); + mon.SetDeviceFilterState(mouseId, true); } } }