Add SubscribeButtons

device-handlers
Clive Galway 2 years ago
parent 82e6b7c21e
commit 719807f497

@ -1,18 +1,280 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using AutoHotInterception.Helpers;
namespace AutoHotInterception.DeviceHandlers
{
class MouseHandler : DeviceHandler
{
dynamic ContextCallback;
// Holds MappingOptions for individual mouse button subscriptions
private ConcurrentDictionary<ushort, MappingOptions> MouseButtonMappings = new ConcurrentDictionary<ushort, MappingOptions>();
// If all mouse buttons are subscribed, this holds the mapping options
private MappingOptions MouseButtonsMappings;
MappingOptions MouseMoveAbsoluteMapping;
MappingOptions MouseMoveRelativeMapping;
protected readonly ConcurrentDictionary<ushort, WorkerThread> WorkerThreads = new ConcurrentDictionary<ushort, WorkerThread>();
protected WorkerThread DeviceWorkerThread;
private bool _absoluteMode00Reported;
public MouseHandler(IntPtr deviceContext, int deviceId) : base(deviceContext, deviceId)
{
}
/// <summary>
/// Called when we are removing a Subscription or Context Mode
/// If there are no other subscriptions, and Context Mode is disabled, turn the filter off
/// </summary>
private void DisableFilterIfNeeded()
{
if (MouseButtonsMappings != null || MouseButtonMappings.Count > 0 || ContextCallback != null)
{
IsFiltered = false;
}
}
/// <summary>
/// Creates an AllButtons subscription
/// </summary>
/// <param name="mappingOptions">Options for the subscription (block, callback to fire etc)</param>
public void SubscribeMouseButtons(MappingOptions mappingOptions)
{
MouseButtonsMappings = mappingOptions;
if (!mappingOptions.Concurrent && DeviceWorkerThread == null)
{
DeviceWorkerThread = new WorkerThread();
DeviceWorkerThread.Start();
}
IsFiltered = true;
}
/// <summary>
/// Remove an AllButtons subscription
/// </summary>
public void UnsubscribeMouseButtons()
{
if (MouseButtonsMappings == null) return;
// Stop DeviceWorkerThread
if (!MouseButtonsMappings.Concurrent && DeviceWorkerThread != null)
{
DeviceWorkerThread.Dispose();
}
MouseButtonsMappings = null;
DisableFilterIfNeeded();
}
public override void ProcessStroke(ManagedWrapper.Stroke stroke)
{
throw new NotImplementedException();
var hasSubscription = false;
var hasContext = ContextCallback != null;
var moveRemoved = false;
var hasMove = false;
var x = stroke.mouse.x;
var y = stroke.mouse.y;
// Process mouse movement
var isAbsolute = (stroke.mouse.flags & (ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute) ==
(ushort)ManagedWrapper.MouseFlag.MouseMoveAbsolute;
//Determine whether or not to report mouse movement.
// For Relative mode, this is fairly simple - if x and y are both 0, no movement was reported (Since a real mouse never reports x=0/y=0)
// For Absolute mode, x=0/y=0 is reported, but we should limit this to only reporting once...
// ... so when x=0/y=0 is seen in absolute mode, set the flag _absoluteMode00Reported to true and allow it to be reported...
// then on subsequent reports of x=0/y=0 for absolute mode, if _absoluteMode00Reported is already true, then do not report movement...
// ... In absolute mode, when x!=0/y!=0 is received, clear the _absoluteMode00Reported flag
if (isAbsolute)
{
if (x == 0 && y == 0)
{
if (!_absoluteMode00Reported)
{
hasMove = true;
_absoluteMode00Reported = true;
}
else
{
hasMove = false;
}
}
else
{
hasMove = true;
_absoluteMode00Reported = false;
}
}
else
{
hasMove = (x != 0 || y != 0);
}
if (hasMove)
{
// Process Absolute Mouse Move
if (isAbsolute)
{
if (MouseMoveAbsoluteMapping != null)
{
var mapping = MouseMoveAbsoluteMapping;
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(7))
WorkerThreads[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
{
if (MouseMoveRelativeMapping != null)
{
var mapping = MouseMoveRelativeMapping;
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(8))
WorkerThreads[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);
}
}
}
var isMouseButtonsMapping = MouseButtonsMappings != null;
// 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.Count > 0 || isMouseButtonsMapping)
{
var btnStates = HelperFunctions.MouseStrokeToButtonStates(stroke);
foreach (var btnState in btnStates)
{
if (!isMouseButtonsMapping && !MouseButtonMappings.ContainsKey(btnState.Button))
continue;
hasSubscription = true;
MappingOptions mapping = null;
if (isMouseButtonsMapping)
{
mapping = MouseButtonsMappings;
}
else
{
mapping = MouseButtonMappings[btnState.Button];
}
var state = btnState;
if (mapping.Concurrent)
{
if (isMouseButtonsMapping)
{
ThreadPool.QueueUserWorkItem(threadProc =>
mapping.Callback(btnState.Button, state.State));
}
else
{
ThreadPool.QueueUserWorkItem(threadProc => mapping.Callback(state.State));
}
}
else
{
if (isMouseButtonsMapping)
{
DeviceWorkerThread?.Actions
.Add(() => mapping.Callback(btnState.Button, state.State));
}
else
{
WorkerThreads[btnState.Button]?.Actions
.Add(() => mapping.Callback(state.State));
}
}
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}");
}
else
{
//Debug.WriteLine($"AHK| Leaving flag {btnState.Flag} in stroke");
}
}
}
// 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, _deviceId, ref stroke, 1);
}
else
{
// Everything removed from stroke, do not forward
//Debug.WriteLine("AHK| Mouse stroke now empty, not forwarding");
}
}
else if (hasContext)
{
// Context Mode - forward stroke with context wrapping
ContextCallback(1);
ManagedWrapper.Send(DeviceContext, _deviceId, ref stroke, 1);
ContextCallback(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, _deviceId, ref stroke, 1);
}
}
}
}

@ -220,37 +220,40 @@ namespace AutoHotInterception
SetThreadState(true);
}
/// <summary>
/// Create am AllButtons subscription for the specified mouse
/// </summary>
/// <param name="id">The ID of the mouse</param>
/// <param name="block">Whether or not to block the button</param>
/// <param name="callback">The callback to fire when the button changes state</param>
/// <param name="concurrent">Whether or not to execute callbacks concurrently</param>
public void SubscribeMouseButtons(int id, bool block, dynamic callback, bool concurrent = false)
{
HelperFunctions.IsValidDeviceId(true, id);
SetFilterState(false);
MouseButtonsMappings.TryAdd(id,
new MappingOptions { Block = block, Concurrent = concurrent, Callback = callback });
if (!concurrent)
{
DeviceWorkerThreads.TryAdd(id, new WorkerThread());
DeviceWorkerThreads[id].Start();
}
SetDeviceFilterState(id, true);
var handler = (MouseHandler)DeviceHandlers[id];
handler.SubscribeMouseButtons(new MappingOptions { Block = block, Concurrent = concurrent, Callback = callback });
SetFilterState(true);
SetThreadState(true);
}
/// <summary>
/// Remove an AllButtons subscription for the specified mouse
/// </summary>
/// <param name="id">The ID of the mouse</param>
public void UnsubscribeMouseButtons(int id)
{
HelperFunctions.IsValidDeviceId(true, id);
SetFilterState(false);
if (!MouseButtonMappings.ContainsKey(id))
{
SetDeviceFilterState(id, false);
}
var handler = (MouseHandler)DeviceHandlers[id];
handler.UnsubscribeMouseButtons();
SetFilterState(true);
SetThreadState(true);
}
/// <summary>
/// Subscribes to Absolute mouse movement
/// </summary>

@ -9,7 +9,7 @@ namespace TestApp
{
public class MouseButtonsTester
{
public MouseButtonsTester(TestDevice device)
public MouseButtonsTester(TestDevice device, bool block = false)
{
var im = new Manager();
@ -17,7 +17,7 @@ namespace TestApp
if (devId != 0)
{
im.SubscribeMouseButtons(devId, true, new Action<ushort, int>(OnButtonEvent));
im.SubscribeMouseButtons(devId, block, new Action<ushort, int>(OnButtonEvent));
}
}

@ -8,8 +8,8 @@ namespace TestApp
private static void Main()
{
//var mmt = new MouseMoveTester(TestDevices.LogitechWheelMouse);
//var mbt = new MouseButtonsTester(TestDevices.LogitechWheelMouse);
var kt = new KeyboardTester(TestDevices.WyseKeyboard, true);
var mbt = new MouseButtonsTester(TestDevices.LogitechWheelMouse, true);
//var kt = new KeyboardTester(TestDevices.WyseKeyboard, true);
//var kkt = new KeyboardKeyTester(TestDevices.WyseKeyboard, AhkKeys.Obj("1"), true);
//var tt = new TabletTester(TestDevices.ParbloIslandA609);
//var sct = new ScanCodeTester(TestDevices.WyseKeyboard, true);

Loading…
Cancel
Save