Spring – Rev 1

Subversion Repositories:
Rev:
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
using Gma.System.MouseKeyHook;
using Spring.Utilities;

namespace Spring.MouseKeyboard
{
    public class KeyWatch : IDisposable
    {
#region Public Events & Delegates

        public event EventHandler<KeyUpEventArgs> KeyUp;

        public event EventHandler<KeyDownEventArgs> KeyDown;

        public event EventHandler<KeySpeedAdjustedEventArgs> SpeedAdjusted;

#endregion

#region Private Delegates, Events, Enums, Properties, Indexers and Fields

        private ActionBlock<KeyAction[]> WatchSlowKeysActionBlock { get; set; }

        private BatchBlock<KeyAction> WatchSlowKeysBatchBlock { get; set; }

        private ActionBlock<KeyAction[]> WatchSpeedKeysActionBlock { get; set; }

        private BatchBlock<KeyAction> WatchSpeedKeysBatchBlock { get; set; }

        private ActionBlock<KeyAction[]> WatchKeysActionBlock { get; set; }

        private BatchBlock<KeyAction> WatchKeysBatchBlock { get; set; }

        private IKeyboardMouseEvents KeyboardMouseEvents { get; }

        private Key Key { get; }

        private BufferBlock<KeyAction> WatchKeysBufferBlock { get; }

        private Stopwatch KeyPressStopwatch { get; }

        private BroadcastBlock<KeyAction> WatchKeysBroadcastBlock { get; }

        private bool _disposing;

        private IDisposable _watchKeysBatchActionBlockLink;

        private IDisposable _watchKeysBroadcastBatchBlockLink;

        private IDisposable _watchSlowKeysBatchActionBlockLink;

        private IDisposable _watchSlowKeysBroadcastBatchBlockLink;

        private IDisposable _watchSpeedKeysBatchActionBlockLink;

        private IDisposable _watchSpeedKeysBroadcastBatchBlockLink;

#endregion

#region Constructors, Destructors and Finalizers

        public KeyWatch()
        {
            KeyPressStopwatch = new Stopwatch();
            WatchKeysBufferBlock = new BufferBlock<KeyAction>();
            WatchKeysBroadcastBlock = new BroadcastBlock<KeyAction>(key => key);
        }

        public KeyWatch(IKeyboardMouseEvents keyboardMouseEvents, Key springKey) :
            this()
        {
            KeyboardMouseEvents = keyboardMouseEvents;
            Key = springKey;

            KeyboardMouseEvents.KeyUp += KeyboardMouseEvents_KeyUp;
            KeyboardMouseEvents.KeyDown += KeyboardMouseEvents_KeyDown;
            KeyboardMouseEvents.MouseWheel += KeyboardMouseEvents_MouseWheel;

            Key.KeyComboConfigured += Key_SpringKeyComboConfigured;
            Key.KeySlowConfigured += Key_SpringKeySlowConfigured;
            Key.KeySpeedConfigured += Key_SpringKeySpeedConfigured;
        }

        public void Dispose()
        {
            if (_disposing)
            {
                return;
            }

            _disposing = true;

            KeyboardMouseEvents.KeyUp -= KeyboardMouseEvents_KeyUp;
            KeyboardMouseEvents.KeyDown -= KeyboardMouseEvents_KeyDown;
            KeyboardMouseEvents.MouseWheel -= KeyboardMouseEvents_MouseWheel;

            Key.KeyComboConfigured -= Key_SpringKeyComboConfigured;
            Key.KeySlowConfigured -= Key_SpringKeySlowConfigured;
            Key.KeySpeedConfigured -= Key_SpringKeySpeedConfigured;

            _watchKeysBroadcastBatchBlockLink?.Dispose();
            _watchKeysBatchActionBlockLink?.Dispose();

            _watchSpeedKeysBroadcastBatchBlockLink?.Dispose();
            _watchSpeedKeysBatchActionBlockLink?.Dispose();

            _watchSlowKeysBroadcastBatchBlockLink?.Dispose();
            _watchSlowKeysBatchActionBlockLink?.Dispose();
        }

#endregion

#region Event Handlers

        private void Key_SpringKeyComboConfigured(object sender, KeyComboConfiguredEventArgs e)
        {
            _watchKeysBroadcastBatchBlockLink?.Dispose();
            _watchKeysBroadcastBatchBlockLink = null;
            _watchKeysBatchActionBlockLink?.Dispose();
            _watchKeysBatchActionBlockLink = null;
            WatchKeysBatchBlock?.Complete();
            WatchKeysActionBlock?.Complete();
            WatchKeysBatchBlock = null;
            WatchKeysActionBlock = null;

            WatchKeysBatchBlock = new BatchBlock<KeyAction>(Key.KeyCombo.Count);

            _watchKeysBroadcastBatchBlockLink = WatchKeysBroadcastBlock.LinkTo(WatchKeysBatchBlock,
                new DataflowLinkOptions { PropagateCompletion = true });

            WatchKeysActionBlock =
                new ActionBlock<KeyAction[]>(OnComboKeyBatch,
                    new ExecutionDataflowBlockOptions { BoundedCapacity = 1, EnsureOrdered = true });

            _watchKeysBatchActionBlockLink = WatchKeysBatchBlock.LinkTo(WatchKeysActionBlock,
                new DataflowLinkOptions { PropagateCompletion = true });
        }

        private void Key_SpringKeySpeedConfigured(object sender, KeySpeedConfiguredEventArgs e)
        {
            _watchSpeedKeysBroadcastBatchBlockLink?.Dispose();
            _watchSpeedKeysBroadcastBatchBlockLink = null;
            _watchSpeedKeysBatchActionBlockLink?.Dispose();
            _watchSpeedKeysBatchActionBlockLink = null;
            WatchSpeedKeysBatchBlock?.Complete();
            WatchSpeedKeysActionBlock?.Complete();
            WatchSpeedKeysBatchBlock = null;
            WatchSpeedKeysActionBlock = null;

            WatchSpeedKeysBatchBlock = new BatchBlock<KeyAction>(Key.SpeedCombo.Count);

            _watchSpeedKeysBroadcastBatchBlockLink = WatchKeysBroadcastBlock.LinkTo(WatchSpeedKeysBatchBlock,
                new DataflowLinkOptions { PropagateCompletion = true });

            WatchSpeedKeysActionBlock =
                new ActionBlock<KeyAction[]>(OnSpeedKeyBatch,
                    new ExecutionDataflowBlockOptions { EnsureOrdered = true });

            _watchSpeedKeysBatchActionBlockLink = WatchSpeedKeysBatchBlock.LinkTo(
                WatchSpeedKeysActionBlock,
                new DataflowLinkOptions { PropagateCompletion = true });
        }

        private void Key_SpringKeySlowConfigured(object sender, KeySlowConfiguredEventArgs e)
        {
            _watchSlowKeysBroadcastBatchBlockLink?.Dispose();
            _watchSlowKeysBroadcastBatchBlockLink = null;
            _watchSlowKeysBatchActionBlockLink?.Dispose();
            _watchSlowKeysBatchActionBlockLink = null;
            WatchSlowKeysBatchBlock?.Complete();
            WatchSlowKeysActionBlock?.Complete();
            WatchSlowKeysBatchBlock = null;
            WatchSlowKeysActionBlock = null;

            WatchSlowKeysBatchBlock = new BatchBlock<KeyAction>(Key.SlowCombo.Count);

            _watchSlowKeysBroadcastBatchBlockLink = WatchKeysBroadcastBlock.LinkTo(WatchSlowKeysBatchBlock,
                new DataflowLinkOptions { PropagateCompletion = true });

            WatchSlowKeysActionBlock =
                new ActionBlock<KeyAction[]>(OnSlowKeyBatch,
                    new ExecutionDataflowBlockOptions { EnsureOrdered = true });

            _watchSlowKeysBatchActionBlockLink = WatchSlowKeysBatchBlock.LinkTo(WatchSlowKeysActionBlock,
                new DataflowLinkOptions { PropagateCompletion = true });
        }

        private async void KeyboardMouseEvents_MouseWheel(object sender, MouseEventArgs e)
        {
            if (!Key.IsConfigured)
            {
                return;
            }

            if (!KeyPressStopwatch.IsRunning)
            {
                KeyPressStopwatch.Restart();
            }

            var wheel = e.Delta > 0 ? MouseScrollDirection.Up : MouseScrollDirection.Down;

            await WatchKeysBufferBlock.SendAsync(new KeyActionMouseScrollDirection(wheel));

            if (WatchKeysBroadcastBlock != null)
            {
                await WatchKeysBroadcastBlock.SendAsync(new KeyActionMouseScrollDirection(wheel));
            }

            if (!WatchKeysBufferBlock.TryReceiveAll(out _))
            {
            }
        }

        private async void KeyboardMouseEvents_KeyDown(object sender, KeyEventArgs e)
        {
            if (!Key.IsConfigured)
            {
                return;
            }

            if (!KeyPressStopwatch.IsRunning)
            {
                KeyPressStopwatch.Restart();
            }

            await WatchKeysBufferBlock.SendAsync(new KeyActionKeys(e.KeyCode));

            if (WatchKeysBroadcastBlock != null)
            {
                await WatchKeysBroadcastBlock.SendAsync(new KeyActionKeys(e.KeyCode));
            }
        }

        private void KeyboardMouseEvents_KeyUp(object sender, KeyEventArgs e)
        {
            if (!Key.IsConfigured)
            {
                return;
            }

            var keyPressTime = KeyPressStopwatch.Elapsed;

            KeyPressStopwatch.Stop();

            if (!WatchKeysBufferBlock.TryReceiveAll(out var keys))
            {
                return;
            }

            if (!Key.KeyCombo.SequenceContains(keys))
            {
                return;
            }

            KeyUp?.Invoke(this, new KeyUpEventArgs(keyPressTime));
        }

#endregion

#region Private Methods

        private void OnComboKeyBatch(KeyAction[] keys)
        {
            if (!Key.IsConfigured)
            {
                return;
            }

            if (!Key.KeyCombo.SequenceContains(keys))
            {
                KeyPressStopwatch.Reset();

                return;
            }

            KeyDown?.Invoke(this, new KeyDownEventArgs(KeyPressStopwatch.Elapsed));
        }

        private void OnSlowKeyBatch(KeyAction[] keys)
        {
            if (!Key.IsSlowConfigured)
            {
                return;
            }

            if (!Key.SlowCombo.SequenceEqual(keys))
            {
                return;
            }

            SpeedAdjusted?.Invoke(this, new KeySpeedAdjustedEventArgs(-1));
        }

        private void OnSpeedKeyBatch(KeyAction[] keys)
        {
            if (!Key.IsSpeedConfigured)
            {
                return;
            }

            if (!Key.SpeedCombo.SequenceEqual(keys))
            {
                return;
            }

            SpeedAdjusted?.Invoke(this, new KeySpeedAdjustedEventArgs(1));
        }

#endregion
    }
}