Spring – Rev 1

Subversion Repositories:
Rev:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using WindowsInput;
using WindowsInput.Native;
using Spring.Charging;
using Spring.Main;
using Spring.MouseKeyboard;
using Spring.Utilities;
using Spring.Utilities.Collections;
using SpringCombos;
using Debug = System.Diagnostics.Debug;

namespace Spring.Discharging
{
    public class Discharge : IDisposable
    {
#region Public Events & Delegates

        public event EventHandler<DischargeProgressEventArgs> SpringDischargeProgress;

        public event EventHandler<DischargeStartEventArgs> SpringDischargeStart;

        public event EventHandler<DischargeStopEventArgs> SpringDischargeStop;

#endregion

#region Public Enums, Properties and Fields

        public Key Key { get; set; }

        public int Fuzz { get; set; }

#endregion

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

        private int SpeedMultiplier { get; set; }

        private CancellationTokenSource PauseCancellationTokenSource { get; set; }

        private InputSimulator InputSimulator { get; }

        private KeyWatch WatchKey { get; }

        private MainForm Form { get; }

        private Charge Charge { get; }

        private CancellationTokenSource DischargeCancellationTokenSource { get; set; }

        private Configuration.Configuration Configuration { get; }

        private SemaphoreSlim DischargeSemaphore { get; }

        private Combos ExecutingSpringCombos { get; set; }

        private Combos OriginalSpringCombos { get; set; }

        private Task DischargeTask { get; set; }

        private static Random Random { get; set; }

        private bool _disposing;

        private volatile bool _isDischarging;

#endregion

#region Constructors, Destructors and Finalizers

        public Discharge()
        {
            InputSimulator = new InputSimulator();
            DischargeSemaphore = new SemaphoreSlim(1, 1);
            Random = new Random();
        }

        public Discharge(MainForm form,
                         Configuration.Configuration springConfiguration,
                         Key key,
                         KeyWatch springWatchKey,
                         Charge springSpringCharge,
                         int speedBarValue) :
            this()
        {
            Form = form;
            Configuration = springConfiguration;
            Key = key;
            WatchKey = springWatchKey;
            Charge = springSpringCharge;
            SpeedMultiplier = speedBarValue;

            Configuration.Spring.Charge.PropertyChanged += Loading_PropertyChanged;
            Configuration.Spring.PropertyChanged += Spring_PropertyChanged;

            Form.SpeedbarValueChanged += Form_SpeedbarValueChanged;

            WatchKey.KeyUp += SpringWatchKey_SpringKeyUp;

            Charge.ChargeChanged += SpringChange_ChargeChanged;

            Charge.ChargeLoaded += SpringCharge_ChargeLoaded;

            Key.KeyComboConfigured += Key_SpringKeyComboConfigured;

            if (Charge != null)
            {
                OriginalSpringCombos = Charge.Combos;
            }
        }

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

            _disposing = true;

            Configuration.Spring.Charge.PropertyChanged -= Loading_PropertyChanged;
            Configuration.Spring.PropertyChanged -= Spring_PropertyChanged;

            Form.SpeedbarValueChanged -= Form_SpeedbarValueChanged;

            WatchKey.KeyUp -= SpringWatchKey_SpringKeyUp;

            Charge.ChargeChanged -= SpringChange_ChargeChanged;

            Charge.ChargeLoaded -= SpringCharge_ChargeLoaded;

            Key.KeyComboConfigured -= Key_SpringKeyComboConfigured;

            StopDischarge()
                .Wait();
        }

#endregion

#region Event Handlers

        private void Spring_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Fuzz = ((Configuration.Spring) sender).Fuzz;
        }

        private async void Key_SpringKeyComboConfigured(object sender, KeyComboConfiguredEventArgs e)
        {
            await StopDischarge();
        }

        private void Loading_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Configuration.Spring.Charge = (Configuration.Charge) sender;
        }

        private async void Form_SpeedbarValueChanged(object sender, SpeedbarValueChangedEventArgs e)
        {
            if (ExecutingSpringCombos == null || OriginalSpringCombos == null)
            {
                return;
            }

            SpeedMultiplier = e.Value;

            try
            {
                await AdjustComboSpeed(e.Value);
            }
            catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
            {
            }
        }

        private async void SpringWatchKey_SpringKeyUp(object sender, KeyUpEventArgs e)
        {
            if (e.TimePressed > TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds))
            {
                return;
            }

            if (_isDischarging)
            {
                await StopDischarge();

                return;
            }

            if (OriginalSpringCombos == null)
            {
                return;
            }

            StartDischarge();
        }

        private void SpringCharge_ChargeLoaded(object sender, ChargeLoadedEventArgs e)
        {
            OriginalSpringCombos = e.Combos;
        }

        private void SpringChange_ChargeChanged(object sender, ChargeChangedEventArgs e)
        {
            OriginalSpringCombos = e.Combos;
        }

#endregion

#region Private Methods

        private async Task StopDischarge()
        {
            DischargeCancellationTokenSource?.Cancel();

            if (DischargeTask != null)
            {
                await DischargeTask;
                DischargeTask = null;
            }

            DischargeCancellationTokenSource = null;

            _isDischarging = false;
        }

        private void StartDischarge()
        {
            #pragma warning disable 4014
            DischargeTask = Task.Run(() => StartDischarge(OriginalSpringCombos));
            #pragma warning restore 4014
            _isDischarging = true;
        }

        private async Task AdjustComboSpeed(int speedMultiplier)
        {
            Debug.Assert(ExecutingSpringCombos.Combo.Count == OriginalSpringCombos.Combo.Count,
                "Executing combo count and original combo count are not the same.");

            if (DischargeCancellationTokenSource != null)
            {
                var cancellationToken = DischargeCancellationTokenSource.Token;

                await DischargeSemaphore.WaitAsync(cancellationToken);
            }

            try
            {
                foreach (var originalCombo in OriginalSpringCombos.Combo)
                {
                    if (!(originalCombo is PauseCombo originalPauseCombo))
                    {
                        continue;
                    }

                    var executingCombo = ExecutingSpringCombos.Combo.ElementAtOrDefault(originalCombo.Index);

                    if (executingCombo is PauseCombo executingPauseCombo)
                    {
                        executingPauseCombo.TimeSpan =
                            GetAdjustedPauseCombo(originalPauseCombo, speedMultiplier);

                        continue;
                    }

                    Debug.Assert(executingCombo is PauseCombo,
                        "Different combo found instead of pause combo when adjusting speed.");
                }

                PauseCancellationTokenSource?.Dispose();
                PauseCancellationTokenSource = null;
            }
            finally
            {
                if (DischargeCancellationTokenSource != null)
                {
                    DischargeSemaphore.Release();
                }
            }
        }

        private async Task StartDischarge(Combos args)
        {
            var adjustedCombos = AdjustCombos(args);

            ExecutingSpringCombos = new Combos(adjustedCombos)
                                    { ComboRepeat = args.ComboRepeat };

            Debug.Assert(ExecutingSpringCombos.Combo.Count == OriginalSpringCombos.Combo.Count,
                "Executing combo count and original combo count are not the same.");

            var ring = new Ring<Combo>(ExecutingSpringCombos.Combo);

            try
            {
                using (DischargeCancellationTokenSource = new CancellationTokenSource())
                {
                    var dischargeCancellationToken = DischargeCancellationTokenSource.Token;

                    SpringDischargeStart?.Invoke(this,
                        new DischargeStartEventArgs(ExecutingSpringCombos.ComboRepeat));

                    var loop = ExecutingSpringCombos.ComboRepeat * ring.Count;

                    do
                    {
                        await DischargeSemaphore.WaitAsync(dischargeCancellationToken);

                        try
                        {
                            // Circular queue. . .
                            ring.Get(out var @event);

                            await SimulateEvent(ring, @event, dischargeCancellationToken);

                            SpringDischargeProgress?.Invoke(this,
                                new DischargeProgressEventArgs(loop,
                                    ring.Count,
                                    ExecutingSpringCombos.ComboRepeat));
                        }
                        catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
                        {
                        }
                        finally
                        {
                            DischargeSemaphore.Release();
                        }
                    } while (!dischargeCancellationToken.IsCancellationRequested && (loop == 0 || --loop > 0));
                }
            }
            catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
            {
            }
            finally
            {
                DischargeCancellationTokenSource = null;

                await StopAllActions(ring, ExecutingSpringCombos, CancellationToken.None);

                _isDischarging = false;

                SpringDischargeStop?.Invoke(this,
                    new DischargeStopEventArgs(ExecutingSpringCombos.ComboRepeat));
            }
        }

        private async Task StopAllActions(Ring<Combo> combos, Combos springCombos, CancellationToken cancellationToken)
        {
            foreach (var combo in springCombos.Combo)
            {
                switch (combo)
                {
                    case KeyboardCombo keyboardCombo:
                        if (keyboardCombo.ComboAction == ComboAction.Down)
                        {
                            await SimulateEvent(new KeyboardCombo
                                                { ComboAction = ComboAction.Up, Keys = keyboardCombo.Keys },
                                cancellationToken);
                        }

                        break;

                    case MouseCombo mouseCombo:
                        if (mouseCombo.ComboAction == ComboAction.Down)
                        {
                            await SimulateEvent(combos,
                                new MouseCombo
                                { ComboAction = ComboAction.Up, Button = mouseCombo.Button },
                                cancellationToken);
                        }

                        break;
                }
            }
        }

        private IEnumerable<Combo> AdjustCombos(Combos springCombos)
        {
            foreach (var combo in springCombos.Combo)
            {
                switch (combo)
                {
                    case PauseCombo pauseCombo:
                        var adjustedPauseCombo = GetAdjustedPauseCombo(pauseCombo, SpeedMultiplier);

                        yield return new PauseCombo(adjustedPauseCombo)
                                     { Index = combo.Index, Fuzz = Fuzz };

                        break;

                    default:
                        yield return combo;

                        break;
                }
            }
        }

        private static TimeSpan GetAdjustedPauseCombo(PauseCombo pauseCombo, int multiplier)
        {
            var timeSpan = pauseCombo.TimeSpan;

            switch (Math.Sign(multiplier))
            {
                case -1:
                    timeSpan =
                        TimeSpan.FromTicks(
                            pauseCombo.TimeSpan.Ticks * Math.Abs(multiplier));

                    break;

                case 0:

                    break;

                case 1:
                    timeSpan =
                        TimeSpan.FromTicks(
                            pauseCombo.TimeSpan.Ticks / Math.Abs(multiplier));

                    break;
            }

            return timeSpan;
        }

        private async Task SimulateEvent(PauseCombo pauseCombo, CancellationToken cancellationToken)
        {
            try
            {
                using (PauseCancellationTokenSource = new CancellationTokenSource())
                {
                    var localCancellationToken = PauseCancellationTokenSource.Token;

                    using (var combinedCancellationTokenSource =
                        CancellationTokenSource.CreateLinkedTokenSource(
                            new[] { cancellationToken, localCancellationToken }))
                    {
                        var combinedCancellationToken = combinedCancellationTokenSource.Token;

                        TimeSpan fuzzedTimeSpan;

                        var ms = pauseCombo.Fuzz == 0 && Fuzz != 0 ? Fuzz : pauseCombo.Fuzz;

                        switch (Random.Next(0, 2))
                        {
                            case 1:
                                fuzzedTimeSpan = pauseCombo.TimeSpan + TimeSpan.FromMilliseconds(ms);

                                break;

                            default:
                                fuzzedTimeSpan =
                                    pauseCombo.TimeSpan - TimeSpan.FromMilliseconds(ms);

                                break;
                        }

                        $"Combo timespan: {pauseCombo.TimeSpan} vs. Fuzzed timespan: {fuzzedTimeSpan}".ToDebugConsole();

                        await Task.Delay(fuzzedTimeSpan, combinedCancellationToken);
                    }
                }
            }
            catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
            {
            }
        }

        private async Task SimulateEvent(Ring<Combo> combos, MouseCombo mouseCombo, CancellationToken cancellationToken)
        {
            Task simulate = null;

            try
            {
                switch (mouseCombo.ComboAction)
                {
                    case ComboAction.Up:
                    case ComboAction.Down:
                        switch (mouseCombo.Button)
                        {
                            case MouseButtons.Left:
                                switch (mouseCombo.ComboAction)
                                {
                                    case ComboAction.Down:
                                        simulate = Task.Run(() => { InputSimulator.Mouse.LeftButtonDown(); },
                                            cancellationToken);

                                        break;

                                    case ComboAction.Up:
                                        simulate = Task.Run(() => { InputSimulator.Mouse.LeftButtonUp(); },
                                            cancellationToken);

                                        break;
                                }

                                break;

                            case MouseButtons.Middle:
                                switch (mouseCombo.ComboAction)
                                {
                                    case ComboAction.Down:
                                        simulate = Task.Run(() => { InputSimulator.Mouse.MiddleButtonDown(); },
                                            cancellationToken);

                                        break;

                                    case ComboAction.Up:
                                        simulate = Task.Run(() => { InputSimulator.Mouse.MiddleButtonUp(); },
                                            cancellationToken);

                                        break;
                                }

                                break;

                            case MouseButtons.Right:
                                switch (mouseCombo.ComboAction)
                                {
                                    case ComboAction.Down:
                                        simulate = Task.Run(() => { InputSimulator.Mouse.RightButtonDown(); },
                                            cancellationToken);

                                        break;

                                    case ComboAction.Up:
                                        simulate = Task.Run(() => { InputSimulator.Mouse.RightButtonUp(); },
                                            cancellationToken);

                                        break;
                                }

                                break;
                        }

                        break;

                    case ComboAction.Move:
                        simulate = Task.Run(() =>
                                {
                                    switch (Configuration.Spring.Discharge.UseRelativeMouseMovement)
                                    {
                                        case true:
                                            var lastCombo = (MouseCombo) combos
                                                .SeekReverseUntil(combo =>
                                                    combo is MouseCombo lastMouseCombo &&
                                                    lastMouseCombo.ComboAction == ComboAction.Move);

                                            if (lastCombo == default(MouseCombo))
                                            {
                                                return;
                                            }

                                            var deltaX = mouseCombo.X - lastCombo.X;
                                            var deltaY = mouseCombo.Y - lastCombo.Y;

                                            // Relative mouse movements are subject to modification by the system-wide mouse speed settings
                                            // such that they need to be stored and restored before and after performing the mouse move.
                                            var mouseSpeed = MouseOptions.GetMouseSpeed();
                                            var mouseEnhancedPointerPrecision =
                                                MouseOptions.GetMouseEnhancedPrecision();

                                            try
                                            {
                                                MouseOptions.SetDefaultMouseSpeed();
                                                MouseOptions.SetMouseEnhancedPrecision(false);

                                                InputSimulator.Mouse.MoveMouseBy((int) deltaX, (int) deltaY);
                                            }
                                            finally
                                            {
                                                MouseOptions.SetMouseSpeed(mouseSpeed);
                                                MouseOptions.SetMouseEnhancedPrecision(mouseEnhancedPointerPrecision);
                                            }

                                            break;
                                        default:
                                            InputSimulator.Mouse.MoveMouseToPositionOnVirtualDesktop(
                                                mouseCombo.X.MapValueToRange(0d,
                                                    SystemInformation.VirtualScreen.Width,
                                                    0d,
                                                    65535d),
                                                mouseCombo.Y.MapValueToRange(0d,
                                                    SystemInformation.VirtualScreen.Height,
                                                    0d,
                                                    65535d));

                                            break;
                                    }
                                },
                            cancellationToken);

                        break;
                }

                if (simulate != null)
                {
                    await simulate;
                }
            }
            catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
            {
            }
        }

        private async Task SimulateEvent(KeyboardCombo keyboardCombo, CancellationToken cancellationToken)
        {
            Task simulate = null;

            try
            {
                switch (keyboardCombo.ComboAction)
                {
                    case ComboAction.Up:
                        simulate = Task.Run(() =>
                                {
                                    InputSimulator.Keyboard.KeyUp((VirtualKeyCode) keyboardCombo.Keys);
                                },
                            cancellationToken);

                        break;

                    case ComboAction.Down:
                        simulate = Task.Run(() =>
                                {
                                    InputSimulator.Keyboard.KeyDown((VirtualKeyCode) keyboardCombo.Keys);
                                },
                            cancellationToken);

                        break;
                }

                Debug.Assert(simulate != null, "Unknown key event found in key combo discharge simulate.");

                await simulate;
            }
            catch (Exception ex) when (ex is OperationCanceledException || ex is ObjectDisposedException)
            {
            }
        }

        private async Task SimulateEvent<T>(Ring<Combo> combos, T eventArgs, CancellationToken cancellationToken)
        {
            switch (eventArgs)
            {
                case MouseCombo springMouseEventArgs:
                    await SimulateEvent(combos, springMouseEventArgs, cancellationToken);

                    break;

                case KeyboardCombo springKeyEventArgs:
                    await SimulateEvent(springKeyEventArgs, cancellationToken);

                    break;

                case PauseCombo pauseEventArgs:
                    await SimulateEvent(pauseEventArgs, cancellationToken);

                    break;
            }
        }

#endregion
    }
}