Poll devices at 1ms using Multimedia Timer

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

@ -42,6 +42,7 @@
<ItemGroup>
<Compile Include="Helpers\HelperFunctions.cs" />
<Compile Include="Helpers\ManagedWrapper.cs" />
<Compile Include="Helpers\MultimediaTimer.cs" />
<Compile Include="Manager.cs" />
<Compile Include="Monitor.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 =
new ConcurrentDictionary<int, ConcurrentDictionary<ushort, WorkerThread>>();
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,12 +490,11 @@ 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++)
{
@ -712,9 +715,7 @@ namespace AutoHotInterception
}
}
// 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

@ -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,12 +125,12 @@ 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++)
{
while (ManagedWrapper.Receive(_deviceContext, i, ref stroke, 1) > 0)
@ -200,8 +204,8 @@ namespace AutoHotInterception
}
}
Thread.Sleep(10);
}
_pollThreadRunning = false;
}
private void FireKeyboardCallback(int id, KeyboardCallback data)

@ -16,14 +16,16 @@ namespace TestApp
{
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);
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);
}
}
}

Loading…
Cancel
Save