Spring – Rev 1
?pathlinks?
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
using Gma.System.MouseKeyHook;
using Spring.MouseKeyboard;
using Spring.Utilities;
using Spring.Utilities.Collections.RangeTree;
using SpringCombos;
using Debug = System.Diagnostics.Debug;
namespace Spring.Charging
{
public class Charge : IDisposable
{
#region Public Events & Delegates
public event EventHandler<ChargeLoadedEventArgs> ChargeLoaded;
public event EventHandler<ChargeChangedEventArgs> ChargeChanged;
public event EventHandler<ChargeStartEventArgs> ChargeStart;
#endregion
#region Public Enums, Properties and Fields
public Combos Combos { get; private set; }
public int Fuzz { get; set; }
public static Random Random { get; set; }
#endregion
#region Private Delegates, Events, Enums, Properties, Indexers and Fields
private KeyWatch WatchKey { get; }
private Key Key { get; }
private IKeyboardMouseEvents KeyboardMouseEvents { get; }
private Configuration.Configuration Configuration { get; }
private BufferBlock<Combo> RecordKeyComboInputBlock { get; }
private BufferBlock<Combo> RecordKeyComboOutputBlock { get; }
private TransformBlock<Combo, Combo> RecordKeyTransformBlock { get; }
private Stopwatch Stopwatch { get; }
private int ChargeRepeat { get; set; }
private RangeTreeFunc<TimeSpan, int> RangeTreeFunc { get; set; }
private long _comboCount;
private bool _disposing;
private volatile bool _isCharging;
#endregion
#region Constructors, Destructors and Finalizers
public Charge() => Stopwatch = new Stopwatch();
public Charge(Combos springCombos,
Configuration.Configuration springConfiguration,
IKeyboardMouseEvents keyboardMouseEvents,
Key springKey,
KeyWatch watchKey) : this()
{
Combos = springCombos;
Configuration = springConfiguration;
KeyboardMouseEvents = keyboardMouseEvents;
Key = springKey;
WatchKey = watchKey;
Random = new Random();
RangeTreeFunc = new RangeTreeFunc<TimeSpan, int>
{
{
TimeSpan.FromSeconds(0),
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
FuncNoCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
FuncNoCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
Configuration.Spring.Charge.ChargeSeconds),
FuncCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.MaxValue, FuncNoCharge
}
};
RecordKeyComboInputBlock = new BufferBlock<Combo>();
RecordKeyTransformBlock =
new TransformBlock<Combo, Combo>(TransformSpringCombos,
new ExecutionDataflowBlockOptions { EnsureOrdered = true });
RecordKeyComboInputBlock.LinkTo(RecordKeyTransformBlock);
RecordKeyComboOutputBlock = new BufferBlock<Combo>();
RecordKeyTransformBlock.LinkTo(RecordKeyComboOutputBlock, combo => combo != null);
RecordKeyTransformBlock.LinkTo(DataflowBlock.NullTarget<Combo>());
Configuration.Spring.Charge.PropertyChanged += SpringLoading_PropertyChanged;
Configuration.Spring.PropertyChanged += Spring_PropertyChanged;
WatchKey.KeyUp += WatchKey_SpringKeyUp;
WatchKey.KeyDown += WatchKey_KeyDown;
}
public void Dispose()
{
if (_disposing)
{
return;
}
_disposing = true;
Configuration.Spring.Charge.PropertyChanged -= SpringLoading_PropertyChanged;
Configuration.Spring.PropertyChanged -= Spring_PropertyChanged;
WatchKey.KeyUp -= WatchKey_SpringKeyUp;
WatchKey.KeyDown -= WatchKey_KeyDown;
}
#endregion
#region Event Handlers
private void Spring_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Fuzz = ((Configuration.Spring) sender).Fuzz;
}
private void WatchKey_KeyDown(object sender, KeyDownEventArgs e)
{
if (_isCharging)
{
return;
}
ChargeRepeat = RangeTreeFunc.Query(e.Elapsed)
.SingleOrDefault();
$"Loaded repeat: {ChargeRepeat}".ToDebugConsole();
}
private void SpringLoading_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Configuration.Spring.Charge = (Configuration.Charge) sender;
RangeTreeFunc = new RangeTreeFunc<TimeSpan, int>
{
{
TimeSpan.FromSeconds(0),
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
FuncNoCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
FuncNoCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
Configuration.Spring.Charge.ChargeSeconds),
FuncCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.MaxValue, FuncNoCharge
}
};
}
private void WatchKey_SpringKeyUp(object sender, KeyUpEventArgs e)
{
if (!Key.IsConfigured)
{
return;
}
StopCharging();
// Do not record if press time is smaller than two seconds.
if (e.TimePressed < TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds))
{
return;
}
StartCharging();
}
private async void IKeyboardMouseEvents_MouseMove(object sender, MouseEventArgs e)
{
if (!Configuration.Spring.Charge.MouseMove)
{
return;
}
await StartCharge(new MouseCombo(e, ComboAction.Move));
}
private async void IKeyboardMouseEvents_MouseUp(object sender, MouseEventArgs e)
{
if (!Configuration.Spring.Charge.MouseClick)
{
return;
}
await StartCharge(new MouseCombo(e, ComboAction.Up));
}
private async void IKeyboardMouseEvents_MouseDown(object sender, MouseEventArgs e)
{
if (!Configuration.Spring.Charge.MouseClick)
{
return;
}
await StartCharge(new MouseCombo(e, ComboAction.Down));
}
private async void IKeyboardMouseEvents_KeyDown(object sender, KeyEventArgs e)
{
if (!Configuration.Spring.Charge.Keyboard)
{
return;
}
await StartCharge(new KeyboardCombo(e, ComboAction.Down));
}
private async void IKeyboardMouseEvents_KeyUp(object sender, KeyEventArgs e)
{
if (!Configuration.Spring.Charge.Keyboard)
{
return;
}
await StartCharge(new KeyboardCombo(e, ComboAction.Up));
}
#endregion
#region Public Methods
public void StartCharge(Combos combos)
{
Combos = combos;
ChargeChanged?.Invoke(this, new ChargeChangedEventArgs(Combos));
}
public void Load(Combos combos)
{
Combos = combos;
ChargeLoaded?.Invoke(this, new ChargeLoadedEventArgs(Combos));
}
#endregion
#region Private Methods
private int FuncCharge(TimeSpan x)
{
var value = x.Seconds - Configuration.Spring.Charge.ChargeSeconds;
return value == 0 ? 0 : value;
}
private static int FuncNoCharge(TimeSpan _) => 0;
private async Task StartCharge<T>(T eventArgs) where T : Combo
{
var fuzz = Random.Next(0, Configuration.Spring.Fuzz);
var pauseCombo = new PauseCombo(Stopwatch.Elapsed) { Fuzz = fuzz };
await RecordKeyComboInputBlock.SendAsync(pauseCombo);
Stopwatch.Restart();
await RecordKeyComboInputBlock.SendAsync(eventArgs);
}
private void StopCharging()
{
Stopwatch.Stop();
KeyboardMouseEvents.KeyUp -= IKeyboardMouseEvents_KeyUp;
KeyboardMouseEvents.KeyDown -= IKeyboardMouseEvents_KeyDown;
KeyboardMouseEvents.MouseDown -= IKeyboardMouseEvents_MouseDown;
KeyboardMouseEvents.MouseUp -= IKeyboardMouseEvents_MouseUp;
KeyboardMouseEvents.MouseMove -= IKeyboardMouseEvents_MouseMove;
if (!RecordKeyComboOutputBlock.TryReceiveAll(out var springCombos))
{
return;
}
if (!_isCharging)
{
return;
}
var cleanCombos = springCombos.SequenceSubtract(Key.KeyCombo, CompareSpringKeys);
Combos = new Combos(cleanCombos, ChargeRepeat);
ChargeChanged?.Invoke(this, new ChargeChangedEventArgs(Combos));
_isCharging = false;
}
private void StartCharging()
{
_isCharging = true;
Stopwatch.Restart();
KeyboardMouseEvents.KeyUp += IKeyboardMouseEvents_KeyUp;
KeyboardMouseEvents.KeyDown += IKeyboardMouseEvents_KeyDown;
KeyboardMouseEvents.MouseDown += IKeyboardMouseEvents_MouseDown;
KeyboardMouseEvents.MouseUp += IKeyboardMouseEvents_MouseUp;
KeyboardMouseEvents.MouseMove += IKeyboardMouseEvents_MouseMove;
Interlocked.Exchange(ref _comboCount, 0);
ChargeStart?.Invoke(this, new ChargeStartEventArgs());
}
private Combo TransformSpringCombos(Combo arg)
{
arg.Index = (int) Interlocked.Read(ref _comboCount);
switch (arg)
{
case PauseCombo _:
Interlocked.Increment(ref _comboCount);
return arg;
case KeyboardCombo _:
if (Configuration.Spring.Charge.Keyboard)
{
Interlocked.Increment(ref _comboCount);
return arg;
}
break;
case MouseCombo mouseCombo:
switch (mouseCombo.ComboAction)
{
case ComboAction.Move:
if (Configuration.Spring.Charge.MouseMove)
{
Interlocked.Increment(ref _comboCount);
return arg;
}
break;
case ComboAction.Up:
case ComboAction.Down:
if (Configuration.Spring.Charge.MouseClick)
{
Interlocked.Increment(ref _comboCount);
return arg;
}
break;
}
break;
default:
Debug.Assert(true, "Unknown combo found in combo record charge queue.");
break;
}
return null;
}
private static bool CompareSpringKeys(Combo combo, KeyAction keyAction)
{
if (!(combo is KeyboardCombo springKeyEventArgs) || !(keyAction is KeyActionKeys keyActionKeys))
{
return false;
}
return springKeyEventArgs.Keys == keyActionKeys.Keys;
}
#endregion
}
}