WingMan

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 13  →  ?path2? @ 14
/trunk/WingMan/Bindings/KeyInterceptor.cs
@@ -0,0 +1,155 @@
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.
var maxKeyComboLength = RemoteKeyBindings.Bindings.Max(binding => binding.Keys.Count);
 
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;
}
}
}