Spring – Rev 1
?pathlinks?
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
using Gma.System.MouseKeyHook;
using NetSparkleUpdater;
using NetSparkleUpdater.Enums;
using NetSparkleUpdater.SignatureVerifiers;
using NetSparkleUpdater.UI.WinForms;
using QRCoder;
using Spring.About;
using Spring.Charging;
using Spring.Discharging;
using Spring.Editing;
using Spring.HUD;
using Spring.MouseKeyboard;
using Spring.Properties;
using Spring.Serialization;
using Spring.Serialization.Combos;
using Spring.Serialization.Configuration;
using Spring.Settings;
using Spring.Utilities;
using Spring.Utilities.Collections.RangeTree;
using SpringCombos;
namespace Spring.Main
{
public partial class MainForm : Form
{
#region Public Events & Delegates
public event EventHandler<EventArgs> SpringKeyButtonClicked;
public event EventHandler<EventArgs> SpringSlowButtonClicked;
public event EventHandler<EventArgs> SpringSpeedButtonClicked;
public event EventHandler<SpeedbarValueChangedEventArgs> SpeedbarValueChanged;
public event EventHandler<ConfigurationRestoredEventArgs> ConfigurationRestored;
#endregion
#region Private Delegates, Events, Enums, Properties, Indexers and Fields
private Configuration.Configuration Configuration { get; set; }
private OSD OSD { get; set; }
private Key Key { get; set; }
private KeyWatch WatchKey { get; set; }
private Charge Charge { get; set; }
private Discharge Discharge { get; set; }
private IKeyboardMouseEvents KeyboardMouseEvents { get; }
private EditorForm EditorForm { get; set; }
private SettingsForm SettingsForm { get; set; }
private BufferBlock<ComboQueueItem> ComboQueue { get; }
private ActionBlock<ComboQueueItem> ComboQueueAction { get; }
private static QRCodeGenerator QrGenerator { get; set; }
private readonly IDisposable _comboQueueLink;
private RangeTreeAction<TimeSpan, int> _rangeTree;
private readonly SparkleUpdater _sparkle;
#endregion
#region Constructors, Destructors and Finalizers
public MainForm()
{
InitializeComponent();
Utilities.WindowState.FormTracker.Track(this);
// Start application update.
var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName;
var icon = Icon.ExtractAssociatedIcon(manifestModuleName);
_sparkle = new SparkleUpdater("https://spring.grimore.org/update/appcast.xml",
new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY="))
{
UIFactory = new UIFactory(icon),
RelaunchAfterUpdate = true,
SecurityProtocolType = SecurityProtocolType.Tls12
};
_sparkle.StartLoop(true, true);
Directory.CreateDirectory(Constants.UserApplicationDirectory);
QrGenerator = new QRCodeGenerator();
ComboQueue =
new BufferBlock<ComboQueueItem>(new DataflowBlockOptions
{ BoundedCapacity = 4, EnsureOrdered = true });
ComboQueueAction = new ActionBlock<ComboQueueItem>(DoComboQueueAction,
new ExecutionDataflowBlockOptions { EnsureOrdered = true, BoundedCapacity = 4 });
_comboQueueLink = ComboQueue.LinkTo(ComboQueueAction);
toolStripStatusLabel1.Text = Strings.Welcome_to_Spring;
ConfigurationRestored += MainForm_ConfigurationRestored;
KeyboardMouseEvents = Hook.GlobalEvents();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
{
ConfigurationRestored -= MainForm_ConfigurationRestored;
_comboQueueLink?.Dispose();
Discharge?.Dispose();
Charge?.Dispose();
WatchKey?.Dispose();
Key?.Dispose();
components.Dispose();
}
OSD?.Dispose();
OSD = null;
base.Dispose(disposing);
}
#endregion
#region Event Handlers
private void ComboQueuePictureBox_Click(object sender, EventArgs e)
{
var pictureBox = (PictureBox) sender;
Charge.Load((Combos) pictureBox.Tag);
}
private void AboutToolStripMenuItem_Click(object sender, EventArgs e)
{
var aboutForm = new AboutForm();
aboutForm.Show();
}
private void TutorialToolStripMenuItem_Click(object sender, EventArgs e)
{
new Tutorial.Tutorial(this, KeyboardMouseEvents).Show();
}
private async void MainForm_ConfigurationRestored(object sender, ConfigurationRestoredEventArgs e)
{
await Initialize(e.Combos);
}
private void SpeedBar_ValueChanged(object sender, EventArgs e)
{
var trackBar = (TrackBar) sender;
SpeedbarValueChanged?.Invoke(sender, new SpeedbarValueChangedEventArgs(trackBar.Value));
}
private void QuitToolStripMenuItem_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void BindSpeedButton_Click(object sender, EventArgs e)
{
SpringSpeedButtonClicked?.Invoke(this, e);
}
private void BindSlowButton_Click(object sender, EventArgs e)
{
SpringSlowButtonClicked?.Invoke(this, e);
}
private void BindKeyButton_Click(object sender, EventArgs e)
{
SpringKeyButtonClicked?.Invoke(this, e);
}
private async void SaveFileDialog1_FileOk(object sender, CancelEventArgs e)
{
switch (await Charge.Combos.Serialize(saveFileDialog1.FileName))
{
case SerializationSuccess<Combos> serializationSuccess:
toolStripStatusLabel1.Text = $"{Strings.Combos_wrote_successfully}";
Charge.StartCharge(serializationSuccess.Result);
Configuration.Spring.Store = saveFileDialog1.FileName;
if (!Configuration.Spring.History.Contains(saveFileDialog1.FileName))
{
Configuration.Spring.History.Add(saveFileDialog1.FileName);
}
break;
case SerializationFailure serializationFailed:
toolStripStatusLabel1.Text =
$"{Strings.Failed_to_write_combos}: {serializationFailed.Exception.Message}";
break;
}
}
private async void OpenFileDialog1_FileOk(object sender, CancelEventArgs e)
{
var fileInfo = new FileInfo(openFileDialog1.FileName);
var combos = await LoadCombos(fileInfo);
if (combos == null)
{
return;
}
Charge.StartCharge(combos);
Configuration.Spring.Store = openFileDialog1.FileName;
if (!Configuration.Spring.History.Contains(openFileDialog1.FileName))
{
Configuration.Spring.History.Add(openFileDialog1.FileName);
}
}
private void ExportToolStripMenuItem_Click(object sender, EventArgs e)
{
saveFileDialog1.ShowDialog();
}
private void ImportToolStripMenuItem_Click(object sender, EventArgs e)
{
openFileDialog1.ShowDialog();
}
private async void MainForm_Load(object sender, EventArgs e)
{
var configurationFileInfo = new FileInfo(Constants.ConfigurationFilePath);
// Load configuration.
switch (ConfigurationSerialization.Deserialize(configurationFileInfo))
{
case SerializationSuccess<Configuration.Configuration> serializationSuccess:
Configuration = serializationSuccess.Result;
toolStripStatusLabel1.Text = $"{Strings.Read_configuration_successfully}";
break;
case SerializationFailure serializationFailed:
toolStripStatusLabel1.Text =
$"{Strings.Failed_to_read_configuration}: {serializationFailed.Exception.Message}";
// Attempt to load default configuration
var fileInfo = new FileInfo("Configuration.xml.default");
switch (ConfigurationSerialization.Deserialize(fileInfo))
{
case SerializationSuccess<Configuration.Configuration> serializationSuccess:
toolStripStatusLabel1.Text =
$"{Strings.Read_default_configuration_successfully}";
Configuration = serializationSuccess.Result;
break;
case SerializationFailure defaultSerializationFailure:
toolStripStatusLabel1.Text =
$"{Strings.Failed_to_read_default_configuration}: {defaultSerializationFailure.Exception.Message}";
Configuration = new Configuration.Configuration();
break;
}
break;
}
// Load combos.
if (string.IsNullOrEmpty(Configuration.Spring.Store))
{
ConfigurationRestored?.Invoke(this, new ConfigurationRestoredEventArgs(new Combos()));
return;
}
var storeFileInfo = new FileInfo(Configuration.Spring.Store);
var combos = await LoadCombos(storeFileInfo);
if (combos == null)
{
ConfigurationRestored?.Invoke(this, new ConfigurationRestoredEventArgs(new Combos()));
return;
}
await ComboQueue.SendAsync(new ComboQueueItem(comboQueuePictureBox1, combos));
ConfigurationRestored?.Invoke(this, new ConfigurationRestoredEventArgs(combos));
}
private void WatchKey_SpeedAdjusted(object sender, KeySpeedAdjustedEventArgs e)
{
this.InvokeIfRequired(form =>
{
switch (e.Multiplier)
{
case -1:
form.SpeedBar.Value = Math.Max(form.SpeedBar.Value - 1, form.SpeedBar.Minimum);
break;
case 1:
form.SpeedBar.Value = Math.Min(form.SpeedBar.Value + 1, form.SpeedBar.Maximum);
break;
}
});
}
private void Key_KeyMessage(object sender, KeyMessageEventArgs e)
{
this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = e.Message; });
}
private void Loading_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
_rangeTree = new RangeTreeAction<TimeSpan, int>
{
{
TimeSpan.FromSeconds(0),
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
ActionNoCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
ActionNoCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
Configuration.Spring.Charge.ChargeSeconds),
ActionCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.MaxValue, ActionNoCharge
}
};
}
private void WatchKey_KeyUp(object sender, KeyUpEventArgs e)
{
OSD.Text = OSD.Symbols.KeyReleased();
}
private void Charge_ChargeLoaded(object sender, ChargeLoadedEventArgs e)
{
this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = Strings.Spring_charged; });
if (e.Combos == null)
{
return;
}
switch (e.Combos.ComboRepeat)
{
case 0:
OSD.Text = OSD.Symbols.ChargeStop(Resources.Infinity);
break;
default:
OSD.Text = OSD.Symbols.ChargeStop(e.Combos.ComboRepeat.ToString());
break;
}
}
private async void ChargeChanged(object sender, ChargeChangedEventArgs e)
{
this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = Strings.Spring_charged; });
if (e.Combos == null)
{
return;
}
var combos = Charge.Combos;
await ComboQueue.SendAsync(new ComboQueueItem(comboQueuePictureBox1, combos));
switch (e.Combos.ComboRepeat)
{
case 0:
OSD.Text = OSD.Symbols.ChargeStop(Resources.Infinity);
break;
default:
OSD.Text = OSD.Symbols.ChargeStop(e.Combos.ComboRepeat.ToString());
break;
}
}
private void ChargeStart(object sender, ChargeStartEventArgs e)
{
if (Charge.Combos == null)
{
return;
}
this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = Strings.Spring_is_charging; });
OSD.Text = OSD.Symbols.ChargeStart();
}
private void Discharge_DischargeStart(object sender, DischargeStartEventArgs e)
{
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text = Strings.Spring_is_discharging;
form.toolStripProgressBar1.Value = 0;
switch (e.Repeat)
{
case 0:
form.label5.Text = Resources.Infinity;
form.toolStripProgressBar1.Visible = false;
OSD.Text = OSD.Symbols.DischargeProgress(Resources.Infinity);
break;
default:
form.label5.Text = e.Repeat.ToString();
form.toolStripProgressBar1.Visible = true;
OSD.Text = OSD.Symbols.DischargeProgress(e.Repeat.ToString());
break;
}
});
}
private void Discharge_DischargeStop(object sender, DischargeStopEventArgs e)
{
OSD.Text = string.Empty;
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text = Strings.Spring_discharge_cancelled;
form.toolStripProgressBar1.Visible = false;
switch (e.Repeat)
{
case 0:
form.label5.Text = Resources.Infinity;
OSD.Text = OSD.Symbols.DischargeStop(Resources.Infinity);
break;
default:
form.label5.Text = e.Repeat.ToString();
OSD.Text = OSD.Symbols.DischargeStop(e.Repeat.ToString());
break;
}
});
}
private void Discharge_DischargeProgress(object sender, DischargeProgressEventArgs e)
{
this.InvokeIfRequired(form =>
{
switch (e.Repeat)
{
case 0:
break;
default:
var progress = (int) (100f - 100f * e.Current / (e.Total * e.Repeat));
if (progress < 0 || progress > 100)
{
break;
}
form.toolStripProgressBar1.Value = progress;
var repeats = (int) (1f * e.Current / e.Total);
form.label5.Text = $@"{repeats}";
OSD.Text = OSD.Symbols.DischargeProgress(repeats.ToString());
break;
}
});
}
private void WatchKey_KeyDown(object sender, KeyDownEventArgs e)
{
_rangeTree.Query(e.Elapsed);
}
private async void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
// Save combos.
await SaveCombos(Configuration.Spring.Store);
// Save configuration file.
switch (await Configuration.Serialize(Constants.ConfigurationFilePath))
{
case SerializationSuccess _:
toolStripStatusLabel1.Text = $"{Strings.Wrote_configuration_successfully}";
break;
case SerializationFailure serializationFailed:
toolStripStatusLabel1.Text =
$"{Strings.Failed_to_write_configuration}: {serializationFailed.Exception.Message}";
break;
}
}
private void EditorButton_Click(object sender, EventArgs e)
{
if (EditorForm != null)
{
return;
}
EditorForm = new EditorForm(Configuration, Charge.Combos);
EditorForm.Closing += EditorForm_Closing;
EditorForm.Edited += EditorForm_Edited;
EditorForm.Show();
}
private void EditorForm_Edited(object sender, EditEventArgs e)
{
switch (e)
{
case EditSuccessEventArgs editSuccessEventArgs:
Charge.StartCharge(editSuccessEventArgs.Combos);
break;
case EditFailureEventArgs editFailureEventArgs:
break;
}
}
private void EditorForm_Closing(object sender, CancelEventArgs e)
{
if (EditorForm == null)
{
return;
}
EditorForm.Closing -= EditorForm_Closing;
EditorForm.Edited -= EditorForm_Edited;
EditorForm.Dispose();
EditorForm = null;
toolStripStatusLabel1.Text = $"{Strings.Combos_read_successfully}";
}
private void SettingsButton_Click(object sender, EventArgs e)
{
if (SettingsForm != null)
{
return;
}
SettingsForm = new SettingsForm(Configuration);
SettingsForm.FormClosing += SettingsForm_FormClosing;
SettingsForm.Show();
}
private void SettingsForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (SettingsForm == null)
{
return;
}
SettingsForm.FormClosing -= SettingsForm_FormClosing;
SettingsForm.Dispose();
SettingsForm = null;
}
private void History_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RecreateHistoryMenu(e.NewItems.OfType<string>());
}
private void Spring_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var springHistory = ((Configuration.Spring) sender).History;
RecreateHistoryMenu(springHistory);
}
private async void HistoryToolStripMenu_Click(object sender, EventArgs e)
{
var selectedFile = ((ToolStripMenuItem) sender).Text;
if (string.IsNullOrEmpty(selectedFile))
{
return;
}
var fileInfo = new FileInfo(selectedFile);
var combos = await LoadCombos(fileInfo);
if (combos == null)
{
return;
}
Charge.StartCharge(combos);
Configuration.Spring.Store = openFileDialog1.FileName;
if (!Configuration.Spring.History.Contains(openFileDialog1.FileName))
{
Configuration.Spring.History.Add(openFileDialog1.FileName);
}
}
private async void CheckForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)
{
// Manually check for updates, this will not show a ui
var result = await _sparkle.CheckForUpdatesQuietly();
var updates = result.Updates;
if (result.Status == UpdateStatus.UpdateAvailable)
{
// if update(s) are found, then we have to trigger the UI to show it gracefully
_sparkle.ShowUpdateNeededUI();
return;
}
MessageBox.Show("No updates available at this time.", "Spring", MessageBoxButtons.OK,
MessageBoxIcon.Asterisk,
MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
}
#endregion
#region Private Methods
private async void DoComboQueueAction(ComboQueueItem springComboQueueItem)
{
var serialization = await springComboQueueItem.Combos.Serialize();
switch (serialization)
{
case SerializationSuccess<Combos> serializationSuccess:
var comboText = serializationSuccess.Text;
var qrCodeData = QrGenerator.CreateQrCode(Helpers.Sha1Hash(comboText),
QRCodeGenerator.ECCLevel.L,
false,
false,
QRCodeGenerator.EciMode.Default,
1);
var qrCode = new QRCode(qrCodeData);
var qrCodeImage = qrCode.GetGraphic(20);
var image = Helpers.Crop(qrCodeImage);
// Set the QR code hash and the combos.
comboQueuePictureBox4.BackgroundImage = comboQueuePictureBox3.BackgroundImage;
comboQueuePictureBox4.Tag = comboQueuePictureBox3.Tag;
comboQueuePictureBox3.BackgroundImage = comboQueuePictureBox2.BackgroundImage;
comboQueuePictureBox3.Tag = comboQueuePictureBox2.Tag;
comboQueuePictureBox2.BackgroundImage = comboQueuePictureBox1.BackgroundImage;
comboQueuePictureBox2.Tag = comboQueuePictureBox1.Tag;
comboQueuePictureBox1.BackgroundImage = image;
comboQueuePictureBox1.Tag = serializationSuccess.Result;
break;
case SerializationFailure serializationFailure:
break;
}
}
private async Task<Combos> LoadCombos(FileInfo file)
{
switch (await ComboSerialization.Deserialize(file, 0))
{
case SerializationSuccess<Combos> serializationSuccess:
toolStripStatusLabel1.Text = $"{Strings.Combos_read_successfully}";
return serializationSuccess.Result;
case SerializationFailure deserializationFailure:
toolStripStatusLabel1.Text =
$"{Strings.Failed_to_read_combos}: {deserializationFailure.Exception.Message}";
break;
}
return null;
}
/// <summary>
/// Serialize combos to file.
/// </summary>
/// <param name="file">the file to which the combos will be serialized</param>
/// <returns>an awaitable task</returns>
private async Task SaveCombos(string file)
{
switch (await Charge.Combos.Serialize(file))
{
case SerializationSuccess<Combos> serializationSuccess:
toolStripStatusLabel1.Text = $"{Strings.Combos_wrote_successfully}";
break;
case SerializationFailure deserializationFailure:
toolStripStatusLabel1.Text =
$"{Strings.Failed_to_write_combos}: {deserializationFailure.Exception.Message}";
break;
}
}
private void ActionCharge(TimeSpan x)
{
this.InvokeIfRequired(form =>
{
switch (x.Seconds - Configuration.Spring.Charge.ChargeSeconds)
{
case 0:
form.label5.Text = Resources.Infinity;
OSD.Text = OSD.Symbols.KeyDown(Resources.Infinity);
break;
default:
var comboText = (x.Seconds - Configuration.Spring.Charge.ChargeSeconds).ToString();
form.label5.Text = comboText;
OSD.Text = OSD.Symbols.KeyDown(comboText);
break;
}
});
}
private void ActionNoCharge(TimeSpan x)
{
this.InvokeIfRequired(form =>
{
switch (x.Seconds < Configuration.Spring.Charge.ChargeSeconds)
{
case true:
form.label5.Text = string.Empty;
OSD.Text = OSD.Symbols.KeyPressed();
break;
default:
form.label5.Text = Resources.Infinity;
OSD.Text = OSD.Symbols.KeyDown(Resources.Infinity);
break;
}
});
}
private async Task Initialize(Combos springCombos)
{
Key = new Key(this, Configuration, KeyboardMouseEvents);
Key.KeyMessage += Key_KeyMessage;
WatchKey = new KeyWatch(KeyboardMouseEvents, Key);
WatchKey.SpeedAdjusted += WatchKey_SpeedAdjusted;
WatchKey.KeyDown += WatchKey_KeyDown;
WatchKey.KeyUp += WatchKey_KeyUp;
Charge = new Charge(springCombos, Configuration, KeyboardMouseEvents, Key, WatchKey);
Charge.ChargeStart += ChargeStart;
Charge.ChargeChanged += ChargeChanged;
Charge.ChargeLoaded += Charge_ChargeLoaded;
Discharge = new Discharge(this, Configuration, Key, WatchKey, Charge, SpeedBar.Value);
Discharge.SpringDischargeStart += Discharge_DischargeStart;
Discharge.SpringDischargeStop += Discharge_DischargeStop;
Discharge.SpringDischargeProgress += Discharge_DischargeProgress;
Configuration.Spring.Charge.PropertyChanged += Loading_PropertyChanged;
Configuration.Spring.PropertyChanged += Spring_PropertyChanged;
Configuration.Spring.History.CollectionChanged += History_CollectionChanged;
OSD = new OSD(Configuration, KeyboardMouseEvents);
if (OSD.Configuration.HUD.Enable)
{
OSD.Text = OSD.Symbols.HUDShown();
}
_rangeTree = new RangeTreeAction<TimeSpan, int>
{
{
TimeSpan.FromSeconds(0),
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
ActionNoCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
ActionNoCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
Configuration.Spring.Charge.ChargeSeconds),
ActionCharge
},
{
TimeSpan.FromSeconds(Configuration.Spring.Charge.MaximumRepeats +
Configuration.Spring.Charge.ChargeSeconds),
TimeSpan.MaxValue, ActionNoCharge
}
};
RecreateHistoryMenu(Configuration.Spring.History);
await Key.Load();
}
private void RecreateHistoryMenu(IEnumerable<string> springHistory)
{
var history = springHistory.ToList();
foreach (var toolStripMenuItem in
historyToolStripMenuItem.DropDownItems.OfType<ToolStripMenuItem>())
{
toolStripMenuItem.Click -= HistoryToolStripMenu_Click;
history.Add(toolStripMenuItem.Text);
}
historyToolStripMenuItem.DropDownItems.Clear();
foreach (var item in history.Distinct())
{
if (string.IsNullOrEmpty(item))
{
continue;
}
historyToolStripMenuItem.DropDownItems.Add(item, null, HistoryToolStripMenu_Click);
}
}
#endregion
}
}