WingMan – Rev 24
?pathlinks?
using System;
using System.Collections.Specialized;
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
{
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;
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; }
public void Dispose()
{
MouseKeyGloalHook.KeyUp -= MouseKeyGloalHookOnKeyUp;
MouseKeyGloalHook.KeyDown -= MouseKeyGloalHookOnKeyDown;
RemoteKeyBindings.Bindings.CollectionChanged -= OnRemoteKeyBindingsChanged;
KeyComboDataFlowLink?.Dispose();
KeyComboDataFlowLink = null;
MouseKeyGloalHook?.Dispose();
MouseKeyGloalHook = null;
}
private async void OnRemoteKeyBindingsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
await DataFlowSemaphoreSlim.WaitAsync(CancellationToken);
try
{
// Break the link and dispose it.
KeyComboDataFlowLink?.Dispose();
KeyComboDataFlowLink = null;
// Create a sliding window of size equal to the longest key combination.
if (!RemoteKeyBindings.Bindings.Any())
return;
var remoteKeyBindings = RemoteKeyBindings.Bindings.Where(binding => binding.Keys.Any()).ToList();
if (!remoteKeyBindings.Any())
return;
var maxKeyComboLength = remoteKeyBindings.Max(binding => binding.Keys.Count);
if (maxKeyComboLength <= 0)
return;
KeyComboBatchBlock =
new BatchBlock<string>(maxKeyComboLength);
KeyComboActionBlock = new ActionBlock<string[]>(ProcessKeyCombos,
new ExecutionDataflowBlockOptions {CancellationToken = CancellationToken});
KeyComboDataFlowLink = KeyComboBatchBlock.LinkTo(KeyComboActionBlock);
}
finally
{
DataFlowSemaphoreSlim.Release();
}
}
private async Task ProcessKeyCombos(string[] keys)
{
await DataFlowSemaphoreSlim.WaitAsync(CancellationToken);
try
{
if (!ProcessPipe)
return;
foreach (var binding in RemoteKeyBindings.Bindings)
{
if (!keys.SubsetEquals(binding.Keys))
continue;
// 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())
{
ExecuteKeyBinding.XmlSerializer.Serialize(memoryStream,
new ExecuteKeyBinding(binding.Nick, binding.Name));
memoryStream.Position = 0L;
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;
}
}
}