Poll devices at 1ms using Multimedia Timer

feature/multimedia-timer
Clive Galway 5 years ago
parent 140376067c
commit ab757ee0cd

@ -42,6 +42,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Helpers\HelperFunctions.cs" /> <Compile Include="Helpers\HelperFunctions.cs" />
<Compile Include="Helpers\ManagedWrapper.cs" /> <Compile Include="Helpers\ManagedWrapper.cs" />
<Compile Include="Helpers\MultimediaTimer.cs" />
<Compile Include="Manager.cs" /> <Compile Include="Manager.cs" />
<Compile Include="Monitor.cs" /> <Compile Include="Monitor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.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<object>(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);
}
/// <summary>
/// The period of the timer in milliseconds.
/// </summary>
public int Interval
{
get
{
return interval;
}
set
{
CheckDisposed();
if (value < 0)
throw new ArgumentOutOfRangeException("value");
interval = value;
if (Resolution > Interval)
Resolution = value;
}
}
/// <summary>
/// The resolution of the timer in milliseconds. The minimum resolution is 0, meaning highest possible resolution.
/// </summary>
public int Resolution
{
get
{
return resolution;
}
set
{
CheckDisposed();
if (value < 0)
throw new ArgumentOutOfRangeException("value");
resolution = value;
}
}
/// <summary>
/// Gets whether the timer has been started yet.
/// </summary>
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<object>(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);
}
}

@ -34,7 +34,8 @@ namespace AutoHotInterception
private readonly ConcurrentDictionary<int, ConcurrentDictionary<ushort, WorkerThread>> _workerThreads = private readonly ConcurrentDictionary<int, ConcurrentDictionary<ushort, WorkerThread>> _workerThreads =
new ConcurrentDictionary<int, ConcurrentDictionary<ushort, WorkerThread>>(); new ConcurrentDictionary<int, ConcurrentDictionary<ushort, WorkerThread>>();
private Thread _pollThread; private readonly MultimediaTimer _timer;
private readonly int _pollRate = 1;
private volatile bool _pollThreadRunning; private volatile bool _pollThreadRunning;
#region Public #region Public
@ -44,6 +45,8 @@ namespace AutoHotInterception
public Manager() public Manager()
{ {
_deviceContext = ManagedWrapper.CreateContext(); _deviceContext = ManagedWrapper.CreateContext();
_timer = new MultimediaTimer() { Interval = _pollRate };
_timer.Elapsed += DoPoll;
} }
public void Dispose() public void Dispose()
@ -433,18 +436,19 @@ namespace AutoHotInterception
private void SetThreadState(bool state) private void SetThreadState(bool state)
{ {
if (state) if (state && !_timer.IsRunning)
{ {
if (_pollThreadRunning) return; SetFilterState(true);
_pollThreadRunning = true; _timer.Start();
_pollThread = new Thread(PollThread);
_pollThread.Start();
} }
else else if (!state && _timer.IsRunning)
{ {
_pollThreadRunning = false; SetFilterState(false);
_pollThread.Join(); _timer.Stop();
_pollThread = null; 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 // 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(); var stroke = new ManagedWrapper.Stroke();
while (_pollThreadRunning) // Iterate through all Keyboards
for (var i = 1; i < 11; i++)
{ {
// Iterate through all Keyboards var isMonitoredKeyboard = IsMonitoredDevice(i) == 1;
for (var i = 1; i < 11; i++) 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 // Process any waiting input for this keyboard
while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) 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; // Process Subscription Mode
// 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
#region KeyCode, State, Extended Flag translation #region KeyCode, State, Extended Flag translation
// Begin translation of incoming key code, state, extended flag etc... // Begin translation of incoming key code, state, extended flag etc...
var processMappings = true; var processMappings = true;
var processedState = HelperFunctions.KeyboardStrokeToKeyboardState(stroke); var processedState = HelperFunctions.KeyboardStrokeToKeyboardState(stroke);
#endregion #endregion
if (processedState.Ignore) if (processedState.Ignore)
{ {
// Set flag to stop Context Mode from firing // Set flag to stop Context Mode from firing
hasSubscription = true; hasSubscription = true;
// Set flag to indicate disable mapping processing // Set flag to indicate disable mapping processing
processMappings = false; processMappings = false;
} }
var code = processedState.Code; var code = processedState.Code;
var state = processedState.State; var state = processedState.State;
// Code and state now normalized, proceed with checking for subscriptions... // Code and state now normalized, proceed with checking for subscriptions...
if (processMappings && _keyboardMappings[i].ContainsKey(code)) if (processMappings && _keyboardMappings[i].ContainsKey(code))
{ {
hasSubscription = true; hasSubscription = true;
var mapping = _keyboardMappings[i][code]; var mapping = _keyboardMappings[i][code];
if (mapping.Block) block = true; if (mapping.Block) block = true;
if (mapping.Concurrent) if (mapping.Concurrent)
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state)); ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state));
else if (_workerThreads.ContainsKey(i) && _workerThreads[i].ContainsKey(code)) else if (_workerThreads.ContainsKey(i) && _workerThreads[i].ContainsKey(code))
_workerThreads[i][code]?.Actions.Add(() => mapping.Callback(state)); _workerThreads[i][code]?.Actions.Add(() => mapping.Callback(state));
}
} }
}
// If the key was blocked by Subscription Mode, then move on to next key... // If the key was blocked by Subscription Mode, then move on to next key...
if (block) continue; if (block) continue;
// If this key had no subscriptions, but Context Mode is set for this keyboard... // If this key had no subscriptions, but Context Mode is set for this keyboard...
// ... then set the Context before sending the key // ... 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. // Pass the key through to the OS.
ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); ManagedWrapper.Send(_deviceContext, i, ref stroke, 1);
// If we are processing Context Mode, then Unset the context variable after sending the key // 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);
}
} }
}
// Process Mice // Process Mice
for (var i = 11; i < 21; i++) for (var i = 11; i < 21; i++)
{ {
var isMonitoredMouse = IsMonitoredDevice(i) == 1; var isMonitoredMouse = IsMonitoredDevice(i) == 1;
var hasSubscription = false; var hasSubscription = false;
var hasContext = _contextCallbacks.ContainsKey(i); var hasContext = _contextCallbacks.ContainsKey(i);
while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0) while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0)
{ {
if (!isMonitoredMouse) continue; if (!isMonitoredMouse) continue;
var moveRemoved = false; var moveRemoved = false;
var hasMove = false; var hasMove = false;
var x = stroke.mouse.x; var x = stroke.mouse.x;
var y = stroke.mouse.y; var y = stroke.mouse.y;
//Debug.WriteLine($"AHK| Stroke Seen. State = {stroke.mouse.state}, Flags = {stroke.mouse.flags}, x={x}, y={y}"); //Debug.WriteLine($"AHK| Stroke Seen. State = {stroke.mouse.state}, Flags = {stroke.mouse.flags}, x={x}, y={y}");
// Process mouse movement // Process mouse movement
if (x != 0 || y != 0) 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; if (_mouseMoveAbsoluteMappings.ContainsKey(i))
// Process Absolute Mouse Move
if ((stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) == (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute)
{ {
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]; moveRemoved = true;
hasSubscription = true; stroke.mouse.x = 0;
//var debugStr = $"AHK| Mouse stroke has absolute move of {x}, {y}..."; stroke.mouse.y = 0;
//debugStr += "Blocking";
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);
} }
} else
// 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))
{ {
var mapping = _mouseMoveRelativeMappings[i]; //debugStr += "Not Blocking";
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);
} }
//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 // Process Relative Mouse Move
if (stroke.mouse.state != 0 && _mouseButtonMappings.ContainsKey(i)) //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); if (_mouseMoveRelativeMappings.ContainsKey(i))
foreach (var btnState in btnStates)
{ {
if (!_mouseButtonMappings[i].ContainsKey(btnState.Button)) continue; var mapping = _mouseMoveRelativeMappings[i];
hasSubscription = true; hasSubscription = true;
var mapping = _mouseButtonMappings[i][btnState.Button]; //var debugStr = $"AHK| Mouse stroke has relative move of {x}, {y}...";
var state = btnState;
if (mapping.Concurrent) if (mapping.Concurrent)
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state.State)); ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(x, y));
else if (_workerThreads.ContainsKey(i) && else if (_workerThreads.ContainsKey(i) && _workerThreads[i].ContainsKey(8))
_workerThreads[i].ContainsKey(btnState.Button)) _workerThreads[i][8]?.Actions.Add(() => mapping.Callback(x, y));
_workerThreads[i][btnState.Button]?.Actions
.Add(() => mapping.Callback(state.State));
if (mapping.Block) if (mapping.Block)
{ {
// Remove the event for this button from the stroke, leaving other button events intact moveRemoved = true;
stroke.mouse.state -= btnState.Flag; stroke.mouse.x = 0;
// If we are removing a mouse wheel event, then set rolling to 0 if no mouse wheel event left stroke.mouse.y = 0;
if (btnState.Flag == 0x400 || btnState.Flag == 0x800) //debugStr += "Blocking";
{
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 else
{ {
//Debug.WriteLine($"AHK| Leaving flag {btnState.Flag} in stroke"); //debugStr += "Not Blocking";
} }
//Debug.WriteLine(debugStr);
} }
} }
}
// Forward on the stroke if required // 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 (hasSubscription) if (stroke.mouse.state != 0 && _mouseButtonMappings.ContainsKey(i))
{
var btnStates = HelperFunctions.MouseStrokeToButtonStates(stroke);
foreach (var btnState in btnStates)
{ {
// Subscription mode if (!_mouseButtonMappings[i].ContainsKey(btnState.Button)) continue;
// 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) 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}"); // Remove the event for this button from the stroke, leaving other button events intact
ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); 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 else
{ {
// Everything removed from stroke, do not forward //Debug.WriteLine($"AHK| Leaving flag {btnState.Flag} in stroke");
//Debug.WriteLine("AHK| Mouse stroke now empty, not forwarding");
} }
} }
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 //Debug.WriteLine($"AHK| Sending stroke. State = {stroke.mouse.state}. hasMove={hasMove}, moveRemoved={moveRemoved}");
_contextCallbacks[i](1);
ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); ManagedWrapper.Send(_deviceContext, i, ref stroke, 1);
_contextCallbacks[i](0);
} }
else else
{ {
// No subscription or context mode - forward on // Everything removed from stroke, do not forward
//Debug.WriteLine($"AHK| Sending stroke. State = {stroke.mouse.state}. hasMove={hasMove}, moveRemoved={moveRemoved}"); //Debug.WriteLine("AHK| Mouse stroke now empty, not forwarding");
ManagedWrapper.Send(_deviceContext, i, ref stroke, 1);
} }
//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 private class MappingOptions

@ -14,7 +14,8 @@ namespace AutoHotInterception
private dynamic _keyboardCallback; private dynamic _keyboardCallback;
private dynamic _mouseCallback; private dynamic _mouseCallback;
private Thread _pollThread; private readonly MultimediaTimer _timer;
private readonly int _pollRate = 1;
private volatile bool _pollThreadRunning; private volatile bool _pollThreadRunning;
#region Public #region Public
@ -22,6 +23,8 @@ namespace AutoHotInterception
public Monitor() public Monitor()
{ {
_deviceContext = ManagedWrapper.CreateContext(); _deviceContext = ManagedWrapper.CreateContext();
_timer = new MultimediaTimer() { Interval = _pollRate };
_timer.Elapsed += DoPoll;
SetThreadState(true); SetThreadState(true);
} }
@ -95,18 +98,19 @@ namespace AutoHotInterception
private void SetThreadState(bool state) private void SetThreadState(bool state)
{ {
if (state) if (state && !_timer.IsRunning)
{ {
if (_pollThreadRunning) return; SetFilterState(true);
_pollThreadRunning = true; _timer.Start();
_pollThread = new Thread(PollThread);
_pollThread.Start();
} }
else else if (!state && _timer.IsRunning)
{ {
_pollThreadRunning = true; SetFilterState(false);
_pollThread.Join(); _timer.Stop();
_pollThread = null; 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); state ? ManagedWrapper.Filter.All : ManagedWrapper.Filter.None);
} }
private void PollThread() private void DoPoll(object sender, EventArgs e)
{ {
_pollThreadRunning = true;
var stroke = new ManagedWrapper.Stroke(); 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);
ManagedWrapper.Send(_deviceContext, i, ref stroke, 1); if (processedState.Ignore)
var processedState = HelperFunctions.KeyboardStrokeToKeyboardState(stroke); FireKeyboardCallback(i, new KeyboardCallback
if (processedState.Ignore) {
FireKeyboardCallback(i, new KeyboardCallback Id = i,
{ Code = stroke.key.code,
Id = i, State = stroke.key.state,
Code = stroke.key.code, Info = "Ignored - showing raw values"
State = stroke.key.state, });
Info = "Ignored - showing raw values" else
}); FireKeyboardCallback(i, new KeyboardCallback
else {
FireKeyboardCallback(i, new KeyboardCallback Id = i,
{ Code = processedState.Code,
Id = i, State = processedState.State,
Code = processedState.Code, Info = stroke.key.code > 255 ? "Extended" : ""
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); // Mouse Button
if (stroke.mouse.state != 0) 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 FireMouseCallback(new MouseCallback
{ {
Id = i, Id = i,
X = stroke.mouse.x, Code = btnState.Button,
Y = stroke.mouse.y, State = btnState.State,
Info = "Absolute Move" 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 Id = i,
FireMouseCallback(new MouseCallback X = stroke.mouse.x,
{ Y = stroke.mouse.y,
Id = i, Info = "Relative Move"
X = stroke.mouse.x, });
Y = stroke.mouse.y,
Info = "Relative Move"
});
}
} }
} }
Thread.Sleep(10);
} }
_pollThreadRunning = false;
} }
private void FireKeyboardCallback(int id, KeyboardCallback data) private void FireKeyboardCallback(int id, KeyboardCallback data)

@ -16,14 +16,16 @@ namespace TestApp
{ {
Console.WriteLine($"Keyboard: ID={id}, Code={code}, Value={value}, Info={info}"); Console.WriteLine($"Keyboard: ID={id}, Code={code}, Value={value}, Info={info}");
}), }),
new Action<int, int, int, string>((id, code, value, info) => new Action<int, int, int, int, int, string>((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); var keyboardId = mon.GetKeyboardId(0x04F2, 0x0112);
mon.SetDeviceFilterState(devId, true); mon.SetDeviceFilterState(keyboardId, true);
var mouseId = mon.GetMouseIdFromHandle(@"HID\VID_046D&PID_C00C&REV_0620");
mon.SetDeviceFilterState(mouseId, true);
} }
} }
} }

Loading…
Cancel
Save