Spring – Rev 1
?pathlinks?
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
}
}