WingMan – Rev 36
?pathlinks?
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Gma.System.MouseKeyHook;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Server;
using WingMan.AutoCompletion;
using WingMan.Bindings;
using WingMan.Communication;
using WingMan.Discovery;
using WingMan.Lobby;
using WingMan.Properties;
using WingMan.Utilities;
namespace WingMan
{
public partial class WingManForm : Form
{
private const int TabControlDetachPixelOffset = 20;
public WingManForm()
{
InitializeComponent();
FormTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
FormCancellationTokenSource = new CancellationTokenSource();
// Set up discovery.
Discovery = new Discovery.Discovery(FormCancellationTokenSource, FormTaskScheduler);
Discovery.OnPortMapFailed += OnDiscoveryPortMapFailed;
// Set up autocompletion.
AutoCompletion = new AutoCompletion.AutoCompletion(FormTaskScheduler, FormCancellationTokenSource.Token);
AutoCompletion.OnSaveFailed += AutoCompletionOnSaveFailed;
AutoCompletion.OnLoadFailed += AutoCompletionOnLoadFailed;
Task.Run(() => AutoCompletion.Load(Address.Name, Address.AutoCompleteCustomSource));
Task.Run(() => AutoCompletion.Load(Port.Name, Address.AutoCompleteCustomSource));
Task.Run(() => AutoCompletion.Load(Nick.Name, Nick.AutoCompleteCustomSource));
Task.Run(() => AutoCompletion.Load(LocalNameTextBox.Name, LocalNameTextBox.AutoCompleteCustomSource));
MqttCommunication = new MqttCommunication(FormTaskScheduler, FormCancellationTokenSource.Token);
MqttCommunication.OnClientAuthenticationFailed += OnMqttClientAuthenticationFailed;
MqttCommunication.OnClientConnectionFailed += OnMqttClientConnectionFailed;
MqttCommunication.OnClientDisconnected += OnMqttClientDisconnected;
MqttCommunication.OnClientConnected += OnMqttClientConnected;
MqttCommunication.OnServerAuthenticationFailed += OnMqttServerAuthenticationFailed;
MqttCommunication.OnServerStopped += OnMqttServerStopped;
MqttCommunication.OnServerStarted += OnMqttServerStarted;
MqttCommunication.OnServerClientConnected += OnMqttServerClientConnected;
MqttCommunication.OnServerClientDisconnected += OnMqttServerClientDisconnected;
LocalKeyBindings = new LocalKeyBindings(new List<KeyBinding>());
RemoteKeyBindings = new RemoteKeyBindings(new ObservableCollection<RemoteKeyBinding>());
LocalCheckedListBoxBindingSource = new BindingSource
{
DataSource = LocalKeyBindings.Bindings
};
LocalCheckedListBoxBindingSource.ListChanged += LocalCheckedListBoxBindingSourceOnListChanged;
LocalBindingsCheckedListBox.DisplayMember = "DisplayName";
LocalBindingsCheckedListBox.ValueMember = "Keys";
LocalBindingsCheckedListBox.DataSource = LocalCheckedListBoxBindingSource;
KeyBindingsExchange = new KeyBindingsExchange
{
ExchangeBindings = new List<KeyBindingExchange>()
};
RemoteBindingsComboBoxSource = new BindingSource
{
DataSource = KeyBindingsExchange.ExchangeBindings
};
RemoteBindingsComboBoxSource.ListChanged += RemoteBindingsComboBoxSourceOnListChanged;
RemoteBindingsComboBox.DisplayMember = "Nick";
RemoteBindingsComboBox.ValueMember = "KeyBindings";
RemoteBindingsComboBox.DataSource = RemoteBindingsComboBoxSource;
// Start lobby message synchronizer.
LobbyMessageSynchronizer = new LobbyMessageSynchronizer(MqttCommunication, FormTaskScheduler,
FormCancellationTokenSource.Token);
LobbyMessageSynchronizer.OnLobbyMessageReceived += OnLobbyMessageReceived;
// Start mouse key bindings synchronizer.
KeyBindingsSynchronizer = new KeyBindingsSynchronizer(LocalKeyBindings, MqttCommunication,
FormTaskScheduler, FormCancellationTokenSource.Token);
KeyBindingsSynchronizer.OnMouseKeyBindingsSynchronized += OnMouseKeyBindingsSynchronized;
// Start mouse key interceptor.
KeyInterceptor = new KeyInterceptor(RemoteKeyBindings, MqttCommunication, FormTaskScheduler,
FormCancellationTokenSource.Token);
KeyInterceptor.OnMouseKeyBindingMatched += OnMouseKeyBindingMatched;
// Start mouse key simulator.
KeySimulator = new KeySimulator(LocalKeyBindings, MqttCommunication, FormTaskScheduler,
FormCancellationTokenSource.Token);
KeySimulator.OnMouseKeyBindingExecuting += OnMouseKeyBindingExecuting;
}
private static Discovery.Discovery Discovery { get; set; }
private static AutoCompletion.AutoCompletion AutoCompletion { get; set; }
private static CancellationTokenSource FormCancellationTokenSource { get; set; }
private static TaskScheduler FormTaskScheduler { get; set; }
private static IKeyboardMouseEvents MouseKeyApplicationHook { get; set; }
private List<string> MouseKeyCombo { get; set; }
public LocalKeyBindings LocalKeyBindings { get; set; }
private RemoteKeyBindings RemoteKeyBindings { get; }
private BindingSource LocalCheckedListBoxBindingSource { get; }
private BindingSource RemoteBindingsComboBoxSource { get; }
private KeyBindingsExchange KeyBindingsExchange { get; }
public MqttCommunication MqttCommunication { get; set; }
public LobbyMessageSynchronizer LobbyMessageSynchronizer { get; set; }
public KeyBindingsSynchronizer KeyBindingsSynchronizer { get; set; }
public KeyInterceptor KeyInterceptor { get; set; }
public KeySimulator KeySimulator { get; set; }
private static Point TabControlClickStartPosition { get; set; }
private static TabControl DetachedTabControl { get; set; }
private static Form DetachedForm { get; set; }
private void RemoteBindingsComboBoxSourceOnListChanged(object sender, ListChangedEventArgs e)
{
UpdateRemoteBindingsListBox();
}
public void OnDiscoveryPortMapFailed(object sender, DiscoveryFailedEventArgs args)
{
switch (args.Type)
{
case DiscoveryType.Upnp:
ActivityTextBox.AppendText(
$"{Strings.Failed_to_create_UPnP_port_mapping}{Environment.NewLine}");
break;
case DiscoveryType.Pmp:
ActivityTextBox.AppendText(
$"{Strings.Failed_to_create_PMP_port_mapping}{Environment.NewLine}");
break;
}
}
private void LocalCheckedListBoxBindingSourceOnListChanged(object sender, ListChangedEventArgs e)
{
// Check items
LocalBindingsCheckedListBox.ItemCheck -= LocalBindingsCheckedListBoxItemCheck;
for (var i = 0; i < LocalKeyBindings.Bindings.Count; ++i)
switch (LocalKeyBindings.Bindings[i].Enabled)
{
case true:
LocalBindingsCheckedListBox.SetItemCheckState(i, CheckState.Checked);
break;
default:
LocalBindingsCheckedListBox.SetItemCheckState(i, CheckState.Unchecked);
break;
}
LocalBindingsCheckedListBox.ItemCheck += LocalBindingsCheckedListBoxItemCheck;
}
private void AutoCompletionOnLoadFailed(object sender, AutoCompletionFailedEventArgs args)
{
ActivityTextBox.AppendText(
$"{Strings.Failed_loading_autocomplete_source} : {args.Name} : {args.Exception.Message}{Environment.NewLine}");
}
private void AutoCompletionOnSaveFailed(object sender, AutoCompletionFailedEventArgs args)
{
ActivityTextBox.AppendText(
$"{Strings.Failed_saving_autocomplete_source} : {args.Name} : {args.Exception.Message}{Environment.NewLine}");
}
/// <inheritdoc />
/// <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)
{
FormCancellationTokenSource?.Dispose();
FormCancellationTokenSource = null;
if (disposing && components != null)
{
components.Dispose();
components = null;
}
base.Dispose(disposing);
}
private void OnMouseKeyBindingExecuting(object sender, KeyBindingExecutingEventArgs args)
{
ActivityTextBox.AppendText(
$"{Strings.Executing_binding_from_remote_client} : {args.Nick} : {args.Name}{Environment.NewLine}");
}
private void OnMouseKeyBindingMatched(object sender, KeyBindingMatchedEventArgs args)
{
ActivityTextBox.AppendText(
$"{Strings.Matched_remote_key_binding} : {args.Nick} : {args.Name} : {string.Join(" + ", args.KeyCombo)}{Environment.NewLine}");
}
private void OnMqttServerClientDisconnected(object sender, MqttClientDisconnectedEventArgs e)
{
ActivityTextBox.AppendText(
$"{Strings.Client_disconnected}{Environment.NewLine}");
}
private void OnMqttServerClientConnected(object sender, MqttClientConnectedEventArgs e)
{
ActivityTextBox.AppendText(
$"{Strings.Client_connected}{Environment.NewLine}");
}
private void OnMqttServerStarted(object sender, EventArgs e)
{
ActivityTextBox.AppendText(
$"{Strings.Server_started}{Environment.NewLine}");
}
private void OnMqttServerStopped(object sender, EventArgs e)
{
ActivityTextBox.AppendText(
$"{Strings.Server_stopped}{Environment.NewLine}");
}
private void OnMqttClientConnected(object sender, MQTTnet.Client.MqttClientConnectedEventArgs e)
{
ActivityTextBox.AppendText(
$"{Strings.Client_connected}{Environment.NewLine}");
}
private void OnMqttClientDisconnected(object sender, MQTTnet.Client.MqttClientDisconnectedEventArgs e)
{
ActivityTextBox.AppendText(
$"{Strings.Client_disconnected}{Environment.NewLine}");
}
private void OnMqttClientConnectionFailed(object sender, MqttManagedProcessFailedEventArgs e)
{
ActivityTextBox.AppendText(
$"{Strings.Client_connection_failed}{Environment.NewLine}");
}
private void OnMqttServerAuthenticationFailed(object sender, MqttAuthenticationFailureEventArgs e)
{
ActivityTextBox.AppendText(
$"{Strings.Failed_to_authenticate_client} : {e.Exception}{Environment.NewLine}");
}
private void OnMqttClientAuthenticationFailed(object sender, MqttAuthenticationFailureEventArgs e)
{
ActivityTextBox.AppendText(
$"{Strings.Failed_to_authenticate_with_server} : {e.Exception}{Environment.NewLine}");
}
private void OnMouseKeyBindingsSynchronized(object sender, KeyBindingsSynchronizerEventArgs e)
{
ActivityTextBox.AppendText(
$"{Strings.Synchronized_bindings_with_client} : {e.Bindings.Nick} : {e.Bindings.KeyBindings.Count}{Environment.NewLine}");
var exchangeBindings = KeyBindingsExchange.ExchangeBindings.FirstOrDefault(exchangeBinding =>
string.Equals(exchangeBinding.Nick, e.Bindings.Nick, StringComparison.Ordinal));
// If the nick does not exist then add it.
if (exchangeBindings == null)
{
KeyBindingsExchange.ExchangeBindings.Add(e.Bindings);
RemoteBindingsComboBoxSource.ResetBindings(false);
return;
}
// If the bindings for the nick have not changed then do not update.
if (e.Bindings.KeyBindings.Count == exchangeBindings.KeyBindings.Count &&
e.Bindings.KeyBindings.All(exchangeBindings.KeyBindings.Contains))
{
RemoteBindingsComboBoxSource.ResetBindings(false);
return;
}
// Update the bindings.
exchangeBindings.KeyBindings.RemoveAll(binding => !e.Bindings.KeyBindings.Contains(binding));
exchangeBindings.KeyBindings.AddRange(
e.Bindings.KeyBindings.Where(binding => !exchangeBindings.KeyBindings.Contains(binding)));
RemoteBindingsComboBoxSource.ResetBindings(false);
}
private void UpdateRemoteBindingsListBox()
{
var keyBindingExchange = (KeyBindingExchange) RemoteBindingsComboBox.SelectedItem;
if (keyBindingExchange == null)
return;
RemoteBindingsListBox.Items.Clear();
var bindings = KeyBindingsExchange.ExchangeBindings
.Where(binding => binding.Nick == keyBindingExchange.Nick)
.SelectMany(binding => binding.KeyBindings.Select(keyBinding => keyBinding))
.Select(binding => (object) binding).ToArray();
if (bindings.Length == 0)
return;
RemoteBindingsListBox.Items.AddRange(bindings);
}
private void OnLobbyMessageReceived(object sender, LobbyMessageReceivedEventArgs e)
{
WingManNotifyIcon.BalloonTipTitle = Strings.Lobby_message;
WingManNotifyIcon.BalloonTipText = $"{e.Nick} : {e.Message}{Environment.NewLine}";
WingManNotifyIcon.ShowBalloonTip(1000);
LobbyTextBox.AppendText($"{e.Nick} : {e.Message}{Environment.NewLine}");
}
private void AddressTextBoxClick(object sender, EventArgs e)
{
Address.BackColor = Color.Empty;
}
private void PortTextBoxClick(object sender, EventArgs e)
{
Port.BackColor = Color.Empty;
}
private async void HostButtonClickAsync(object sender, EventArgs e)
{
// Stop the MQTT server if it is running.
if (MqttCommunication.Running)
{
// Remote UPnP and Pmp mappings.
await Task.Delay(0, FormCancellationTokenSource.Token).ContinueWith(async _ =>
{
await Discovery.DeleteMapping(DiscoveryType.Upnp, MqttCommunication.Port);
await Discovery.DeleteMapping(DiscoveryType.Pmp, MqttCommunication.Port);
},
FormCancellationTokenSource.Token, TaskContinuationOptions.LongRunning, FormTaskScheduler);
await MqttCommunication.Stop();
HostButton.BackColor = Color.Empty;
// Enable controls.
ConnectButton.Enabled = true;
Address.Enabled = true;
Port.Enabled = true;
Nick.Enabled = true;
Password.Enabled = true;
return;
}
if (!ValidateConnectionParameters(out var ipAddress, out var port, out var nick, out var password))
return;
StoreConnectionAutocomplete();
// Try to reserve port: try UPnP followed by PMP.
await Task.Delay(0, FormCancellationTokenSource.Token).ContinueWith(async _ =>
{
if (!await Discovery.CreateMapping(DiscoveryType.Upnp, port) &&
!await Discovery.CreateMapping(DiscoveryType.Pmp, port))
ActivityTextBox.AppendText(
$"{Strings.Failed_creating_automatic_port_mapping_please_make_sure_the_port_is_routed_properly}{Environment.NewLine}");
},
FormCancellationTokenSource.Token, TaskContinuationOptions.LongRunning, FormTaskScheduler);
// Start the MQTT server.
if (!await MqttCommunication
.Start(MqttCommunicationType.Server, ipAddress, port, nick, password))
{
ActivityTextBox.AppendText(
$"{Strings.Failed_starting_server}{Environment.NewLine}");
return;
}
HostButton.BackColor = Color.Aquamarine;
// Disable controls
ConnectButton.Enabled = false;
Address.Enabled = false;
Port.Enabled = false;
Nick.Enabled = false;
Password.Enabled = false;
}
private async void StoreConnectionAutocomplete()
{
Address.AutoCompleteCustomSource.Add(Address.Text);
await AutoCompletion.Save(Address.Name, Address.AutoCompleteCustomSource);
Port.AutoCompleteCustomSource.Add(Port.Text);
await AutoCompletion.Save(Port.Name, Port.AutoCompleteCustomSource);
Nick.AutoCompleteCustomSource.Add(Nick.Text);
await AutoCompletion.Save(Nick.Name, Nick.AutoCompleteCustomSource);
}
private bool ValidateConnectionParameters(
out IPAddress address,
out int port,
out string nick,
out string password)
{
address = IPAddress.Any;
port = 0;
nick = string.Empty;
password = string.Empty;
if (string.IsNullOrEmpty(Address.Text) &&
string.IsNullOrEmpty(Port.Text) &&
string.IsNullOrEmpty(Nick.Text) &&
string.IsNullOrEmpty(Password.Text))
{
Address.BackColor = Color.LightPink;
Port.BackColor = Color.LightPink;
Nick.BackColor = Color.LightPink;
Password.BackColor = Color.LightPink;
return false;
}
if (!IPAddress.TryParse(Address.Text, out address))
try
{
address = Dns.GetHostAddresses(Address.Text).FirstOrDefault();
}
catch (Exception ex)
{
ActivityTextBox.AppendText(
$"{Strings.Could_not_resolve_hostname} : {ex.Message}{Environment.NewLine}");
Address.BackColor = Color.LightPink;
return false;
}
if (!uint.TryParse(Port.Text, out var uPort))
{
Port.BackColor = Color.LightPink;
return false;
}
port = (int) uPort;
if (string.IsNullOrEmpty(Nick.Text))
{
Nick.BackColor = Color.LightPink;
return false;
}
nick = Nick.Text;
if (string.IsNullOrEmpty(Password.Text))
{
Password.BackColor = Color.LightPink;
return false;
}
password = Aes.ExpandKey(Password.Text);
Address.BackColor = Color.Empty;
Port.BackColor = Color.Empty;
Nick.BackColor = Color.Empty;
Password.BackColor = Color.Empty;
return true;
}
private async void ConnectButtonClickAsync(object sender, EventArgs e)
{
if (MqttCommunication.Running)
{
await MqttCommunication.Stop();
ConnectButton.Text = Strings.Connect;
ConnectButton.BackColor = Color.Empty;
Address.Enabled = true;
Port.Enabled = true;
Nick.Enabled = true;
Password.Enabled = true;
HostButton.Enabled = true;
return;
}
if (!ValidateConnectionParameters(out var ipAddress, out var port, out var nick, out var password))
return;
StoreConnectionAutocomplete();
if (!await MqttCommunication
.Start(MqttCommunicationType.Client, ipAddress, port, nick, password))
{
ActivityTextBox.AppendText(
$"{Strings.Failed_starting_client}{Environment.NewLine}");
return;
}
ConnectButton.Text = Strings.Disconnect;
ConnectButton.BackColor = Color.Aquamarine;
HostButton.Enabled = false;
Address.Enabled = false;
Port.Enabled = false;
Nick.Enabled = false;
Password.Enabled = false;
}
private async void LobbySayTextBoxKeyDown(object sender, KeyEventArgs e)
{
// Do not send messages if the communication is not running.
if (!MqttCommunication.Running)
return;
if (e.KeyCode != Keys.Enter)
return;
await LobbyMessageSynchronizer.Broadcast(LobbySayTextBox.Text);
LobbySayTextBox.Text = string.Empty;
}
private async void LocalAddBindingButtonClick(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(LocalNameTextBox.Text))
{
LocalNameTextBox.BackColor = Color.LightPink;
return;
}
// Only unique names allowed.
if (LocalKeyBindings.Bindings.Any(binding =>
string.Equals(binding.Name, LocalNameTextBox.Text, StringComparison.Ordinal)))
{
LocalNameTextBox.BackColor = Color.LightPink;
LocalBindingsCheckedListBox.BackColor = Color.LightPink;
return;
}
LocalNameTextBox.BackColor = Color.Empty;
LocalBindingsCheckedListBox.BackColor = Color.Empty;
LocalNameTextBox.AutoCompleteCustomSource.Add(LocalNameTextBox.Text);
await AutoCompletion.Save(LocalNameTextBox.Name, LocalNameTextBox.AutoCompleteCustomSource);
ShowOverlayPanel();
MouseKeyCombo = new List<string>();
MouseKeyApplicationHook = Hook.GlobalEvents();
MouseKeyApplicationHook.KeyUp += LocalMouseKeyHookOnKeyUp;
MouseKeyApplicationHook.KeyDown += LocalMouseKeyHookOnKeyDown;
}
private void ShowOverlayPanel()
{
OverlayPanel.BringToFront();
OverlayPanel.Visible = true;
OverlayPanel.Invalidate();
}
private async void LocalMouseKeyHookOnKeyUp(object sender, KeyEventArgs e)
{
LocalKeyBindings.Bindings.Add(new KeyBinding(LocalNameTextBox.Text, MouseKeyCombo));
LocalCheckedListBoxBindingSource.ResetBindings(false);
MouseKeyApplicationHook.KeyDown -= LocalMouseKeyHookOnKeyDown;
MouseKeyApplicationHook.KeyUp -= LocalMouseKeyHookOnKeyUp;
MouseKeyApplicationHook.Dispose();
LocalNameTextBox.Text = string.Empty;
HideOverlayPanel();
await SaveLocalMouseKeyBindings();
}
private void HideOverlayPanel()
{
OverlayPanel.SendToBack();
OverlayPanel.Visible = false;
OverlayPanel.Invalidate();
}
private void LocalMouseKeyHookOnKeyDown(object sender, KeyEventArgs e)
{
e.SuppressKeyPress = true;
KeyConversion.KeysToString.TryGetValue((byte) e.KeyCode, out var key);
MouseKeyCombo.Add(key);
}
private void LocalNameTextBoxClick(object sender, EventArgs e)
{
LocalNameTextBox.BackColor = Color.Empty;
}
private async void LocalBindingsRemoveButtonClick(object sender, EventArgs e)
{
var localBinding = (KeyBinding) LocalBindingsCheckedListBox.SelectedItem;
if (localBinding == null)
return;
LocalKeyBindings.Bindings.Remove(localBinding);
LocalCheckedListBoxBindingSource.ResetBindings(false);
await SaveLocalMouseKeyBindings();
}
private async void LobbySayButtonClick(object sender, EventArgs e)
{
// Do not send messages if the communication is not running.
if (!MqttCommunication.Running)
return;
await LobbyMessageSynchronizer.Broadcast(LobbySayTextBox.Text);
LobbySayTextBox.Text = string.Empty;
}
private void RemoteBindingsComboBoxSelectionChangeCompleted(object sender, EventArgs e)
{
UpdateRemoteBindingsListBox();
}
private async void WingManFormOnLoad(object sender, EventArgs e)
{
await LoadLocalMouseKeyBindings();
await LoadRemoteMouseKeyBindings();
}
private void RemoteBindingsBindButtonClicked(object sender, EventArgs e)
{
if (string.IsNullOrEmpty((string) RemoteBindingsListBox.SelectedItem))
{
RemoteBindingsListBox.BackColor = Color.LightPink;
return;
}
RemoteBindingsListBox.BackColor = Color.Empty;
ShowOverlayPanel();
MouseKeyCombo = new List<string>();
MouseKeyApplicationHook = Hook.GlobalEvents();
MouseKeyApplicationHook.KeyUp += RemoteKeyHookOnKeyUp;
MouseKeyApplicationHook.KeyDown += RemoteKeyHookOnKeyDown;
}
private void RemoteBindingsUnbindButtonClicked(object sender, EventArgs e)
{
var item = (string) RemoteBindingsListBox.SelectedItem;
if (string.IsNullOrEmpty(item))
{
RemoteBindingsListBox.BackColor = Color.LightPink;
return;
}
RemoteBindingsListBox.BackColor = Color.Empty;
var remoteKeyBinding = RemoteKeyBindings.Bindings.FirstOrDefault(binding =>
string.Equals(binding.Name, item, StringComparison.Ordinal));
if (remoteKeyBinding == null)
return;
RemoteKeyBindings.Bindings.Remove(remoteKeyBinding);
RemoteBindingsBindToBox.Text = string.Empty;
}
private void RemoteKeyHookOnKeyDown(object sender, KeyEventArgs e)
{
e.SuppressKeyPress = true;
if (!KeyConversion.KeysToString.TryGetValue((byte) e.KeyCode, out var key))
return;
MouseKeyCombo.Add(key);
}
private async void RemoteKeyHookOnKeyUp(object sender, KeyEventArgs e)
{
RemoteKeyBindings.Bindings.Add(new RemoteKeyBinding(RemoteBindingsComboBox.Text,
(string) RemoteBindingsListBox.SelectedItem, MouseKeyCombo));
MouseKeyApplicationHook.KeyDown -= RemoteKeyHookOnKeyDown;
MouseKeyApplicationHook.KeyUp -= RemoteKeyHookOnKeyUp;
MouseKeyApplicationHook.Dispose();
RemoteBindingsBindToBox.Text = string.Join(" + ", MouseKeyCombo);
HideOverlayPanel();
await SaveRemoteMouseKeyBindings();
}
private void RemoteBindingsListBoxSelectedValueChanged(object sender, EventArgs e)
{
RemoteBindingsBindToBox.Text = "";
var name = (string) RemoteBindingsListBox.SelectedItem;
if (string.IsNullOrEmpty(name))
return;
var nick = RemoteBindingsComboBox.Text;
if (string.IsNullOrEmpty(nick))
return;
foreach (var binding in RemoteKeyBindings.Bindings)
{
if (!string.Equals(binding.Nick, nick) || !string.Equals(binding.Name, name))
continue;
RemoteBindingsBindToBox.Text = string.Join(" + ", binding.Keys);
break;
}
}
private void WingManFormResized(object sender, EventArgs e)
{
if (WindowState == FormWindowState.Minimized) Hide();
}
private void NotifyIconDoubleClick(object sender, EventArgs e)
{
Show();
WindowState = FormWindowState.Normal;
}
private async void LocalBindingsCheckedListBoxItemCheck(object sender, ItemCheckEventArgs e)
{
var helmBinding = (KeyBinding) LocalBindingsCheckedListBox.Items[e.Index];
if (helmBinding == null)
return;
switch (e.NewValue)
{
case CheckState.Checked:
helmBinding.Enabled = true;
break;
case CheckState.Unchecked:
helmBinding.Enabled = false;
break;
}
await SaveLocalMouseKeyBindings();
}
private void WingManTabControlMouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
TabControlClickStartPosition = e.Location;
}
private void WingManTabControlMouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var mouseOffsetX = TabControlClickStartPosition.X - e.X;
var mouseOffsetY = TabControlClickStartPosition.Y - e.Y;
if (mouseOffsetX <= TabControlDetachPixelOffset && mouseOffsetY <= TabControlDetachPixelOffset)
return;
tabControl1.DoDragDrop(tabControl1.SelectedTab, DragDropEffects.Move);
TabControlClickStartPosition = new Point();
return;
}
TabControlClickStartPosition = new Point();
}
private void WingManTabControlGiveFeedback(object sender, GiveFeedbackEventArgs e)
{
e.UseDefaultCursors = false;
}
private void WingManTabControlQueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
if (MouseButtons != MouseButtons.Left)
{
e.Action = DragAction.Cancel;
DetachedForm = new Form
{
Size = new Size(
// Width = tab control width + tab control left and right margins + x-padding
tabControl1.Width +
tabControl1.Margin.Right +
tabControl1.Margin.Left +
tabControl1.Padding.X,
// Tab Height = tab control height - tab control tab page height
// Height = tab control height + Tab Height + top and bottom margin + x-padding
2 * tabControl1.Height -
tabControl1.SelectedTab.Height +
tabControl1.Margin.Top +
tabControl1.Margin.Bottom +
tabControl1.Padding.Y),
StartPosition = FormStartPosition.Manual,
Location = MousePosition,
MaximizeBox = false,
SizeGripStyle = SizeGripStyle.Hide,
FormBorderStyle = FormBorderStyle.FixedSingle,
Name = Name,
Text = Text,
Icon = Icon
};
DetachedTabControl = new TabControl
{
Dock = DockStyle.Fill,
SizeMode = TabSizeMode.Fixed
};
DetachedTabControl.TabPages.Add(tabControl1.SelectedTab);
DetachedForm.Controls.Add(DetachedTabControl);
DetachedForm.FormClosing += DetachedFormOnFormClosing;
DetachedForm.Show();
Cursor = Cursors.Default;
return;
}
e.Action = DragAction.Continue;
Cursor = Cursors.SizeAll;
}
private void DetachedFormOnFormClosing(object sender, FormClosingEventArgs e)
{
tabControl1.TabPages.Insert(DetachedTabControl.SelectedTab.TabIndex, DetachedTabControl.SelectedTab);
DetachedForm.FormClosing -= DetachedFormOnFormClosing;
}
#region Saving and loading
private async Task SaveLocalMouseKeyBindings()
{
try
{
using (var memoryStream = new MemoryStream())
{
LocalKeyBindings.XmlSerializer.Serialize(memoryStream, LocalKeyBindings);
memoryStream.Position = 0L;
using (var fileStream = new FileStream("LocalKeyBindings.xml", FileMode.Create))
{
await memoryStream.CopyToAsync(fileStream);
}
}
}
catch (Exception)
{
ActivityTextBox.AppendText(
$"{Strings.Failed_saving_local_bindings}{Environment.NewLine}");
}
}
private async Task LoadLocalMouseKeyBindings()
{
try
{
using (var fileStream = new FileStream("LocalKeyBindings.xml", FileMode.Open))
{
using (var memoryStream = new MemoryStream())
{
await fileStream.CopyToAsync(memoryStream);
memoryStream.Position = 0L;
var loadedBindings =
(LocalKeyBindings) LocalKeyBindings.XmlSerializer.Deserialize(memoryStream);
foreach (var binding in loadedBindings.Bindings)
LocalKeyBindings.Bindings.Add(binding);
LocalCheckedListBoxBindingSource.ResetBindings(false);
}
}
}
catch (Exception)
{
ActivityTextBox.AppendText(
$"{Strings.Failed_loading_local_bindings}{Environment.NewLine}");
}
}
private async Task SaveRemoteMouseKeyBindings()
{
try
{
using (var memoryStream = new MemoryStream())
{
RemoteKeyBindings.XmlSerializer.Serialize(memoryStream, RemoteKeyBindings);
memoryStream.Position = 0L;
using (var fileStream = new FileStream("RemoteKeyBindings.xml", FileMode.Create))
{
await memoryStream.CopyToAsync(fileStream);
}
}
}
catch (Exception)
{
ActivityTextBox.AppendText(
$"{Strings.Failed_saving_remote_bindings}{Environment.NewLine}");
}
}
private async Task LoadRemoteMouseKeyBindings()
{
try
{
using (var fileStream = new FileStream("RemoteKeyBindings.xml", FileMode.Open))
{
using (var memoryStream = new MemoryStream())
{
await fileStream.CopyToAsync(memoryStream);
memoryStream.Position = 0L;
var loadedBindings =
(RemoteKeyBindings) RemoteKeyBindings.XmlSerializer.Deserialize(memoryStream);
foreach (var binding in loadedBindings.Bindings)
RemoteKeyBindings.Bindings.Add(binding);
}
}
}
catch (Exception)
{
ActivityTextBox.AppendText(
$"{Strings.Failed_loading_remote_bindings}{Environment.NewLine}");
}
}
#endregion
}
}