WingMan – Rev 32

Subversion Repositories:
Rev:
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;
        }
    }
}

Generated by GNU Enscript 1.6.5.90.