/trunk/WingMan/MouseKey/KeyInterceptor.cs |
@@ -1,155 +1,98 @@ |
using System; |
using System.Collections.Specialized; |
using System.Collections.Generic; |
using System.IO; |
using System.Linq; |
using System.Threading; |
using System.Threading.Tasks; |
using System.Threading.Tasks.Dataflow; |
using System.Windows.Forms; |
using Gma.System.MouseKeyHook; |
using WingMan.Communication; |
using WingMan.Utilities; |
|
namespace WingMan.Bindings |
namespace WingMan.MouseKey |
{ |
public class KeyInterceptor : IDisposable |
{ |
public delegate void MouseKeyBindingMatched(object sender, KeyBindingMatchedEventArgs args); |
|
private volatile bool ProcessPipe; |
|
public KeyInterceptor(RemoteKeyBindings remoteKeyBindings, MqttCommunication mqttCommunication, |
TaskScheduler taskScheduler, CancellationToken cancellationToken) |
{ |
DataFlowSemaphoreSlim = new SemaphoreSlim(1, 1); |
|
RemoteKeyBindings = remoteKeyBindings; |
RemoteKeyBindings.Bindings.CollectionChanged += OnRemoteKeyBindingsChanged; |
|
MqttCommunication = mqttCommunication; |
TaskScheduler = taskScheduler; |
CancellationToken = cancellationToken; |
|
KeyCombo = new List<string>(); |
|
MouseKeyGloalHook = Hook.GlobalEvents(); |
MouseKeyGloalHook.KeyUp += MouseKeyGloalHookOnKeyUp; |
MouseKeyGloalHook.KeyDown += MouseKeyGloalHookOnKeyDown; |
} |
|
private BatchBlock<string> KeyComboBatchBlock { get; set; } |
|
private ActionBlock<string[]> KeyComboActionBlock { get; set; } |
|
private IDisposable KeyComboDataFlowLink { get; set; } |
|
private SemaphoreSlim DataFlowSemaphoreSlim { get; } |
|
private RemoteKeyBindings RemoteKeyBindings { get; } |
private MqttCommunication MqttCommunication { get; } |
private TaskScheduler TaskScheduler { get; } |
private CancellationToken CancellationToken { get; } |
|
private IKeyboardMouseEvents MouseKeyGloalHook { get; set; } |
private IKeyboardMouseEvents MouseKeyGloalHook { get; } |
|
public List<string> KeyCombo { get; set; } |
|
public void Dispose() |
{ |
MouseKeyGloalHook.KeyUp -= MouseKeyGloalHookOnKeyUp; |
MouseKeyGloalHook.KeyDown -= MouseKeyGloalHookOnKeyDown; |
RemoteKeyBindings.Bindings.CollectionChanged -= OnRemoteKeyBindingsChanged; |
|
KeyComboDataFlowLink?.Dispose(); |
KeyComboDataFlowLink = null; |
MouseKeyGloalHook.Dispose(); |
} |
|
MouseKeyGloalHook?.Dispose(); |
MouseKeyGloalHook = null; |
public event MouseKeyBindingMatched OnMouseKeyBindingMatched; |
|
private void MouseKeyGloalHookOnKeyDown(object sender, KeyEventArgs e) |
{ |
e.SuppressKeyPress = false; |
|
if (KeyConversion.KeysToString.TryGetValue((byte) e.KeyCode, out var key)) KeyCombo.Add(key); |
} |
|
private async void OnRemoteKeyBindingsChanged(object sender, NotifyCollectionChangedEventArgs e) |
private void MouseKeyGloalHookOnKeyUp(object sender, KeyEventArgs e) |
{ |
await DataFlowSemaphoreSlim.WaitAsync(CancellationToken); |
e.SuppressKeyPress = false; |
|
try |
{ |
// Break the link and dispose it. |
KeyComboDataFlowLink?.Dispose(); |
KeyComboDataFlowLink = null; |
var combo = new List<string>(KeyCombo); |
|
// Create a sliding window of size equal to the longest key combination. |
var maxKeyComboLength = RemoteKeyBindings.Bindings.Max(binding => binding.Keys.Count); |
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed |
Task.Run(() => SimulateMouseKey(new List<string>(combo)), CancellationToken); |
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed |
|
KeyComboBatchBlock = |
new BatchBlock<string>(maxKeyComboLength); |
KeyComboActionBlock = new ActionBlock<string[]>(ProcessKeyCombos, |
new ExecutionDataflowBlockOptions {CancellationToken = CancellationToken}); |
KeyComboDataFlowLink = KeyComboBatchBlock.LinkTo(KeyComboActionBlock); |
} |
finally |
{ |
DataFlowSemaphoreSlim.Release(); |
} |
KeyCombo.Clear(); |
} |
|
private async Task ProcessKeyCombos(string[] keys) |
private async Task SimulateMouseKey(List<string> mouseKeyCombo) |
{ |
await DataFlowSemaphoreSlim.WaitAsync(CancellationToken); |
|
try |
foreach (var binding in RemoteKeyBindings.Bindings) |
{ |
if (!ProcessPipe) |
return; |
if (!binding.Keys.SequenceEqual(mouseKeyCombo)) |
continue; |
|
foreach (var binding in RemoteKeyBindings.Bindings) |
// Raise the match event. |
await Task.Delay(0, CancellationToken) |
.ContinueWith( |
_ => OnMouseKeyBindingMatched?.Invoke(this, |
new KeyBindingMatchedEventArgs(binding.Nick, binding.Name, binding.Keys)), |
CancellationToken, |
TaskContinuationOptions.None, TaskScheduler); |
|
using (var memoryStream = new MemoryStream()) |
{ |
if (!keys.SubsetEquals(binding.Keys)) |
continue; |
ExecuteKeyBinding.XmlSerializer.Serialize(memoryStream, |
new ExecuteKeyBinding(binding.Nick, binding.Name)); |
|
// Raise the match event. |
await Task.Delay(0, CancellationToken) |
.ContinueWith( |
_ => OnMouseKeyBindingMatched?.Invoke(this, |
new KeyBindingMatchedEventArgs(binding.Nick, binding.Name, binding.Keys)), |
CancellationToken, |
TaskContinuationOptions.None, TaskScheduler); |
memoryStream.Position = 0L; |
|
using (var memoryStream = new MemoryStream()) |
{ |
ExecuteKeyBinding.XmlSerializer.Serialize(memoryStream, |
new ExecuteKeyBinding(binding.Nick, binding.Name)); |
|
memoryStream.Position = 0L; |
|
await MqttCommunication.Broadcast("execute", memoryStream.ToArray()); |
} |
await MqttCommunication.Broadcast("execute", memoryStream.ToArray()); |
} |
} |
finally |
{ |
DataFlowSemaphoreSlim.Release(); |
} |
} |
|
public event MouseKeyBindingMatched OnMouseKeyBindingMatched; |
|
private async void MouseKeyGloalHookOnKeyDown(object sender, KeyEventArgs e) |
{ |
ProcessPipe = true; |
|
if (!KeyConversion.KeysToString.TryGetValue((byte) e.KeyCode, out var key)) |
return; |
|
await DataFlowSemaphoreSlim.WaitAsync(CancellationToken); |
try |
{ |
if (KeyComboBatchBlock != null) await KeyComboBatchBlock.SendAsync(key, CancellationToken); |
} |
finally |
{ |
DataFlowSemaphoreSlim.Release(); |
} |
} |
|
private void MouseKeyGloalHookOnKeyUp(object sender, KeyEventArgs e) |
{ |
ProcessPipe = false; |
} |
} |
} |
/trunk/WingMan/MouseKey/KeySimulator.cs |
@@ -2,12 +2,12 @@ |
using System.IO; |
using System.Threading; |
using System.Threading.Tasks; |
using System.Windows.Forms; |
using MQTTnet; |
using SimWinInput; |
using WingMan.Communication; |
using WingMan.Utilities; |
|
namespace WingMan.Bindings |
namespace WingMan.MouseKey |
{ |
public class KeySimulator : IDisposable |
{ |
@@ -72,19 +72,49 @@ |
// Press |
foreach (var key in localBinding.Keys) |
{ |
if (!KeyConversion.StringToKeys.TryGetValue(key, out var pressKey)) |
if (KeyConversion.StringToKeys.TryGetValue(key, out var pressKey)) |
{ |
SimKeyboard.KeyDown(pressKey); |
continue; |
} |
|
SimKeyboard.KeyDown(pressKey); |
if (KeyConversion.StringToMouseButtons.TryGetValue(key, out var pressMouse)) |
switch (pressMouse) |
{ |
case MouseButtons.Left: |
SimMouse.Act(SimMouse.Action.LeftButtonDown, Cursor.Position.X, Cursor.Position.Y); |
break; |
case MouseButtons.Middle: |
SimMouse.Act(SimMouse.Action.MiddleButtonDown, Cursor.Position.X, Cursor.Position.Y); |
break; |
case MouseButtons.Right: |
SimMouse.Act(SimMouse.Action.RightButtonDown, Cursor.Position.X, Cursor.Position.Y); |
break; |
} |
} |
|
// Depress |
foreach (var key in localBinding.Keys) |
{ |
if (!KeyConversion.StringToKeys.TryGetValue(key, out var pressKey)) |
if (KeyConversion.StringToKeys.TryGetValue(key, out var pressKey)) |
{ |
SimKeyboard.KeyUp(pressKey); |
continue; |
} |
|
SimKeyboard.KeyUp(pressKey); |
if (KeyConversion.StringToMouseButtons.TryGetValue(key, out var pressMouse)) |
switch (pressMouse) |
{ |
case MouseButtons.Left: |
SimMouse.Act(SimMouse.Action.LeftButtonUp, Cursor.Position.X, Cursor.Position.Y); |
break; |
case MouseButtons.Middle: |
SimMouse.Act(SimMouse.Action.MiddleButtonUp, Cursor.Position.X, Cursor.Position.Y); |
break; |
case MouseButtons.Right: |
SimMouse.Act(SimMouse.Action.RightButtonUp, Cursor.Position.X, Cursor.Position.Y); |
break; |
} |
} |
} |
} |