/trunk/WingMan/Communication/MqttCommunication.cs |
@@ -0,0 +1,391 @@ |
using System; |
using System.Net; |
using System.Text; |
using System.Threading; |
using System.Threading.Tasks; |
using MQTTnet; |
using MQTTnet.Client; |
using MQTTnet.Extensions.ManagedClient; |
using MQTTnet.Protocol; |
using MQTTnet.Server; |
using WingMan.Utilities; |
using MqttClientConnectedEventArgs = MQTTnet.Client.MqttClientConnectedEventArgs; |
using MqttClientDisconnectedEventArgs = MQTTnet.Client.MqttClientDisconnectedEventArgs; |
|
namespace WingMan.Communication |
{ |
public class MqttCommunication : IDisposable |
{ |
public delegate void ClientAuthenticationFailed(object sender, MqttAuthenticationFailureEventArgs e); |
|
public delegate void ClientConnected(object sender, MqttClientConnectedEventArgs e); |
|
public delegate void ClientConnectionFailed(object sender, MqttManagedProcessFailedEventArgs e); |
|
public delegate void ClientDisconnected(object sender, MqttClientDisconnectedEventArgs e); |
|
public delegate void ClientSubscribed(object sender, MqttClientSubscribedTopicEventArgs e); |
|
public delegate void ClientUnsubscribed(object sender, MqttClientUnsubscribedTopicEventArgs e); |
|
public delegate void MessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e); |
|
public delegate void ServerAuthenticationFailed(object sender, MqttAuthenticationFailureEventArgs e); |
|
public delegate void ServerClientConnected(object sender, MQTTnet.Server.MqttClientConnectedEventArgs e); |
|
public delegate void ServerClientDisconnected(object sender, MQTTnet.Server.MqttClientDisconnectedEventArgs e); |
|
public delegate void ServerStarted(object sender, EventArgs e); |
|
public delegate void ServerStopped(object sender, EventArgs e); |
|
public MqttCommunication(TaskScheduler taskScheduler, CancellationToken cancellationToken) |
{ |
TaskScheduler = taskScheduler; |
CancellationToken = cancellationToken; |
|
Client = new MqttFactory().CreateManagedMqttClient(); |
Server = new MqttFactory().CreateMqttServer(); |
} |
|
private TaskScheduler TaskScheduler { get; } |
|
private IManagedMqttClient Client { get; } |
|
private IMqttServer Server { get; } |
|
public bool Running { get; set; } |
|
public string Nick { get; set; } |
|
private IPAddress IpAddress { get; set; } |
|
private int Port { get; set; } |
|
private string Password { get; set; } |
|
private CancellationToken CancellationToken { get; } |
|
public MqttCommunicationType Type { get; set; } |
|
public async void Dispose() |
{ |
await Stop(); |
} |
|
public event ClientAuthenticationFailed OnClientAuthenticationFailed; |
|
public event ServerAuthenticationFailed OnServerAuthenticationFailed; |
|
public event MessageReceived OnMessageReceived; |
|
public event ClientConnected OnClientConnected; |
|
public event ClientDisconnected OnClientDisconnected; |
|
public event ClientConnectionFailed OnClientConnectionFailed; |
|
public event ClientUnsubscribed OnClientUnsubscribed; |
|
public event ClientSubscribed OnClientSubscribed; |
|
public event ServerClientDisconnected OnServerClientDisconnected; |
|
public event ServerClientConnected OnServerClientConnected; |
|
public event ServerStarted OnServerStarted; |
|
public event ServerStopped OnServerStopped; |
|
public async Task<bool> Start(MqttCommunicationType type, IPAddress ipAddress, int port, string nick, |
string password) |
{ |
Type = type; |
IpAddress = ipAddress; |
Port = port; |
Nick = nick; |
Password = password; |
|
switch (type) |
{ |
case MqttCommunicationType.Client: |
return await StartClient(); |
case MqttCommunicationType.Server: |
return await StartServer(); |
} |
|
return false; |
} |
|
private async Task<bool> StartClient() |
{ |
var clientOptions = new MqttClientOptionsBuilder() |
.WithTcpServer(IpAddress.ToString(), Port); |
|
// Setup and start a managed MQTT client. |
var options = new ManagedMqttClientOptionsBuilder() |
.WithClientOptions(clientOptions.Build()) |
.Build(); |
|
BindClientHandlers(); |
|
await Client.SubscribeAsync( |
new TopicFilterBuilder() |
.WithTopic("lobby") |
.Build() |
); |
|
await Client.SubscribeAsync( |
new TopicFilterBuilder() |
.WithTopic("exchange") |
.Build() |
); |
|
await Client.SubscribeAsync( |
new TopicFilterBuilder() |
.WithTopic("execute") |
.Build() |
); |
|
await Client.StartAsync(options); |
|
Running = true; |
|
return Running; |
} |
|
private async Task StopClient() |
{ |
UnbindClientHandlers(); |
|
await Client.StopAsync(); |
} |
|
public void BindClientHandlers() |
{ |
Client.Connected += ClientOnConnected; |
Client.Disconnected += ClientOnDisconnected; |
Client.ConnectingFailed += ClientOnConnectingFailed; |
Client.ApplicationMessageReceived += ClientOnApplicationMessageReceived; |
} |
|
public void UnbindClientHandlers() |
{ |
Client.Connected -= ClientOnConnected; |
Client.Disconnected -= ClientOnDisconnected; |
Client.ConnectingFailed -= ClientOnConnectingFailed; |
Client.ApplicationMessageReceived -= ClientOnApplicationMessageReceived; |
} |
|
private async void ClientOnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e) |
{ |
try |
{ |
var load = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); |
|
e.ApplicationMessage.Payload = AES.Decrypt(e.ApplicationMessage.Payload, Password); |
|
var load2 = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); |
|
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnMessageReceived?.Invoke(sender, e), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
catch (Exception ex) |
{ |
await Task.Delay(0, CancellationToken).ContinueWith( |
_ => OnClientAuthenticationFailed?.Invoke(sender, new MqttAuthenticationFailureEventArgs(e, ex)), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
} |
|
private async void ClientOnConnectingFailed(object sender, MqttManagedProcessFailedEventArgs e) |
{ |
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnClientConnectionFailed?.Invoke(sender, e), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
|
private async void ClientOnDisconnected(object sender, MqttClientDisconnectedEventArgs e) |
{ |
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnClientDisconnected?.Invoke(sender, e), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
|
private async void ClientOnConnected(object sender, MqttClientConnectedEventArgs e) |
{ |
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnClientConnected?.Invoke(sender, e), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
|
private async Task<bool> StartServer() |
{ |
var optionsBuilder = new MqttServerOptionsBuilder() |
.WithDefaultEndpointBoundIPAddress(IpAddress) |
.WithSubscriptionInterceptor(MqttSubscriptionIntercept) |
.WithConnectionValidator(MqttConnectionValidator) |
.WithDefaultEndpointPort(Port); |
|
BindServerHandlers(); |
|
try |
{ |
await Server.StartAsync(optionsBuilder.Build()); |
|
Running = true; |
} |
catch (Exception) |
{ |
Running = false; |
} |
|
return Running; |
} |
|
private void MqttConnectionValidator(MqttConnectionValidatorContext context) |
{ |
context.ReturnCode = MqttConnectReturnCode.ConnectionAccepted; |
} |
|
private async Task StopServer() |
{ |
UnbindServerHandlers(); |
|
await Server.StopAsync(); |
} |
|
private void MqttSubscriptionIntercept(MqttSubscriptionInterceptorContext context) |
{ |
if (context.TopicFilter.Topic != "lobby" && |
context.TopicFilter.Topic != "exchange" && |
context.TopicFilter.Topic != "execute") |
{ |
context.AcceptSubscription = false; |
context.CloseConnection = true; |
return; |
} |
|
context.AcceptSubscription = true; |
context.CloseConnection = false; |
} |
|
private void BindServerHandlers() |
{ |
Server.Started += ServerOnStarted; |
Server.Stopped += ServerOnStopped; |
Server.ClientConnected += ServerOnClientConnected; |
Server.ClientDisconnected += ServerOnClientDisconnected; |
Server.ClientSubscribedTopic += ServerOnClientSubscribedTopic; |
Server.ClientUnsubscribedTopic += ServerOnClientUnsubscribedTopic; |
Server.ApplicationMessageReceived += ServerOnApplicationMessageReceived; |
} |
|
private void UnbindServerHandlers() |
{ |
Server.Started -= ServerOnStarted; |
Server.Stopped -= ServerOnStopped; |
Server.ClientConnected -= ServerOnClientConnected; |
Server.ClientDisconnected -= ServerOnClientDisconnected; |
Server.ClientSubscribedTopic -= ServerOnClientSubscribedTopic; |
Server.ClientUnsubscribedTopic -= ServerOnClientUnsubscribedTopic; |
Server.ApplicationMessageReceived -= ServerOnApplicationMessageReceived; |
} |
|
private async void ServerOnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e) |
{ |
try |
{ |
var load = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); |
|
e.ApplicationMessage.Payload = AES.Decrypt(e.ApplicationMessage.Payload, Password); |
|
var load2 = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); |
|
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnMessageReceived?.Invoke(sender, e), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
catch (Exception ex) |
{ |
foreach (var clientSessionStatus in await Server.GetClientSessionsStatusAsync()) |
{ |
if (!string.Equals(clientSessionStatus.ClientId, e.ClientId, StringComparison.Ordinal)) |
continue; |
|
await clientSessionStatus.DisconnectAsync(); |
} |
|
await Task.Delay(0, CancellationToken).ContinueWith( |
_ => OnServerAuthenticationFailed?.Invoke(sender, new MqttAuthenticationFailureEventArgs(e, ex)), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
} |
|
private async void ServerOnClientUnsubscribedTopic(object sender, MqttClientUnsubscribedTopicEventArgs e) |
{ |
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnClientUnsubscribed?.Invoke(sender, e), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
|
private async void ServerOnClientSubscribedTopic(object sender, MqttClientSubscribedTopicEventArgs e) |
{ |
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnClientSubscribed?.Invoke(sender, e), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
|
private async void ServerOnClientDisconnected(object sender, MQTTnet.Server.MqttClientDisconnectedEventArgs e) |
{ |
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnServerClientDisconnected?.Invoke(sender, e), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
|
private async void ServerOnClientConnected(object sender, MQTTnet.Server.MqttClientConnectedEventArgs e) |
{ |
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnServerClientConnected?.Invoke(sender, e), |
CancellationToken, TaskContinuationOptions.None, TaskScheduler); |
} |
|
private void ServerOnStopped(object sender, EventArgs e) |
{ |
OnServerStopped?.Invoke(sender, e); |
} |
|
private void ServerOnStarted(object sender, EventArgs e) |
{ |
OnServerStarted?.Invoke(sender, e); |
} |
|
public async Task Stop() |
{ |
switch (Type) |
{ |
case MqttCommunicationType.Server: |
await StopServer(); |
break; |
case MqttCommunicationType.Client: |
await StopClient(); |
break; |
} |
|
Running = false; |
} |
|
public async Task Broadcast(string topic, byte[] payload) |
{ |
var encryptedPayload = AES.Encrypt(payload, Password); |
|
var load = Encoding.UTF8.GetString(encryptedPayload); |
|
switch (Type) |
{ |
case MqttCommunicationType.Client: |
await Client.PublishAsync(new ManagedMqttApplicationMessage |
{ |
ApplicationMessage = new MqttApplicationMessage {Topic = topic, Payload = encryptedPayload } |
}); |
break; |
case MqttCommunicationType.Server: |
await Server.PublishAsync(new MqttApplicationMessage {Topic = topic, Payload = encryptedPayload }); |
break; |
} |
} |
} |
} |
/trunk/WingMan/Utilities/AES.cs |
@@ -2,7 +2,6 @@ |
using System.IO; |
using System.Security.Cryptography; |
using System.Text; |
using System.Threading.Tasks; |
|
namespace WingMan.Utilities |
{ |
@@ -24,7 +23,7 @@ |
/// <param name="key">the encryption key</param> |
/// <param name="separator">the separator to use between the cyphertext and the IV</param> |
/// <returns>Base64 encoded encrypted data</returns> |
public static async Task<byte[]> Encrypt(byte[] data, string key, string separator = ":") |
public static byte[] Encrypt(byte[] data, string key, string separator = ":") |
{ |
using (var rijdanelManaged = new RijndaelManaged()) |
{ |
@@ -36,29 +35,31 @@ |
// Compute the salt and the IV from the key. |
var salt = new byte[AesKeySaltBytes]; |
Rng.GetBytes(salt); |
var derivedKey = new Rfc2898DeriveBytes(key, salt); |
rijdanelManaged.Key = derivedKey.GetBytes(rijdanelManaged.KeySize / 8); |
rijdanelManaged.IV = derivedKey.GetBytes(rijdanelManaged.BlockSize / 8); |
using (var derivedKey = new Rfc2898DeriveBytes(key, salt)) |
{ |
rijdanelManaged.Key = derivedKey.GetBytes(rijdanelManaged.KeySize / 8); |
rijdanelManaged.IV = derivedKey.GetBytes(rijdanelManaged.BlockSize / 8); |
|
using (var encryptor = rijdanelManaged.CreateEncryptor(rijdanelManaged.Key, rijdanelManaged.IV)) |
{ |
using (var memoryStream = new MemoryStream()) |
using (var encryptor = rijdanelManaged.CreateEncryptor(rijdanelManaged.Key, rijdanelManaged.IV)) |
{ |
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) |
using (var memoryStream = new MemoryStream()) |
{ |
using (var inputStream = new MemoryStream(data)) |
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) |
{ |
await inputStream.CopyToAsync(cryptoStream).ConfigureAwait(false); |
cryptoStream.FlushFinalBlock(); |
using (var inputStream = new MemoryStream(data)) |
{ |
inputStream.CopyTo(cryptoStream); |
cryptoStream.FlushFinalBlock(); |
|
inputStream.Position = 0L; |
inputStream.Position = 0L; |
|
var payload = memoryStream.ToArray(); |
var payload = memoryStream.ToArray(); |
|
var base64Salt = Convert.ToBase64String(salt); |
var base64Payload = Convert.ToBase64String(payload); |
var base64Salt = Convert.ToBase64String(salt); |
var base64Payload = Convert.ToBase64String(payload); |
|
return Encoding.UTF8.GetBytes($"{base64Salt}{separator}{base64Payload}"); |
return Encoding.UTF8.GetBytes($"{base64Salt}{separator}{base64Payload}"); |
} |
} |
} |
} |
@@ -79,7 +80,7 @@ |
/// <param name="key">the encryption key</param> |
/// <param name="separator">the separator to use between the cyphertext and the IV</param> |
/// <returns>the decrypted data</returns> |
public static async Task<byte[]> Decrypt(byte[] data, string key, string separator = ":") |
public static byte[] Decrypt(byte[] data, string key, string separator = ":") |
{ |
var input = Encoding.UTF8.GetString(data); |
|
@@ -86,7 +87,7 @@ |
// retrieve the salt from the data. |
var segments = input.Split(new[] {separator}, StringSplitOptions.None); |
if (segments.Length != 2) |
throw new ArgumentException("Invalid data."); |
throw new ArgumentException("Invalid data: " + input); |
|
using (var rijdanelManaged = new RijndaelManaged()) |
{ |
@@ -96,20 +97,21 @@ |
rijdanelManaged.Padding = AesPaddingMode; |
|
// Retrieve the key and the IV from the salt. |
var derivedKey = new Rfc2898DeriveBytes(key, Convert.FromBase64String(segments[0].Trim())); |
rijdanelManaged.Key = derivedKey.GetBytes(rijdanelManaged.KeySize / 8); |
rijdanelManaged.IV = derivedKey.GetBytes(rijdanelManaged.BlockSize / 8); |
using (var derivedKey = new Rfc2898DeriveBytes(key, Convert.FromBase64String(segments[0].Trim()))) |
{ |
rijdanelManaged.Key = derivedKey.GetBytes(rijdanelManaged.KeySize / 8); |
rijdanelManaged.IV = derivedKey.GetBytes(rijdanelManaged.BlockSize / 8); |
|
using (var decryptor = rijdanelManaged.CreateDecryptor(rijdanelManaged.Key, rijdanelManaged.IV)) |
{ |
using (var memoryStream = new MemoryStream(Convert.FromBase64String(segments[1].Trim()))) |
using (var decryptor = rijdanelManaged.CreateDecryptor(rijdanelManaged.Key, rijdanelManaged.IV)) |
{ |
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) |
using (var memoryStream = new MemoryStream(Convert.FromBase64String(segments[1].Trim()))) |
{ |
using (var streamReader = new StreamReader(cryptoStream)) |
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) |
{ |
return Encoding.UTF8.GetBytes(await streamReader.ReadToEndAsync() |
.ConfigureAwait(false)); |
using (var streamReader = new StreamReader(cryptoStream)) |
{ |
return Encoding.UTF8.GetBytes(streamReader.ReadToEnd()); |
} |
} |
} |
} |
@@ -117,7 +119,7 @@ |
} |
} |
|
public static string LinearFeedbackShiftPassword(string password, int size = 32) |
public static string ExpandKey(string password, int size = 32) |
{ |
var sb = new StringBuilder(password); |
do |
/trunk/WingMan/WingManForm.cs |
@@ -38,28 +38,28 @@ |
MqttCommunication.OnServerClientConnected += OnMqttServerClientConnected; |
MqttCommunication.OnServerClientDisconnected += OnMqttServerClientDisconnected; |
|
LocalMouseKeyBindings = new MouseKeyBindings(new List<MouseKeyBinding>()); |
RemoteMouseKeyBindings = new RemoteMouseKeyBindings(new List<RemoteMouseKeyBinding>()); |
LocalKeyBindings = new LocalKeyBindings(new List<KeyBinding>()); |
RemoteKeyBindings = new RemoteKeyBindings(new List<RemoteKeyBinding>()); |
|
LocalListBoxBindingSource = new BindingSource |
{ |
DataSource = LocalMouseKeyBindings.Bindings |
DataSource = LocalKeyBindings.Bindings |
}; |
LocalBindingsListBox.DisplayMember = "DisplayName"; |
LocalBindingsListBox.ValueMember = "Keys"; |
LocalBindingsListBox.DataSource = LocalListBoxBindingSource; |
|
MouseKeyBindingsExchange = new MouseKeyBindingsExchange |
KeyBindingsExchange = new KeyBindingsExchange |
{ |
ExchangeBindings = new List<MouseKeyBindingExchange>() |
ExchangeBindings = new List<KeyBindingExchange>() |
}; |
|
RemoteBindingsComboBoxSource = new BindingSource |
{ |
DataSource = MouseKeyBindingsExchange.ExchangeBindings |
DataSource = KeyBindingsExchange.ExchangeBindings |
}; |
RemoteBindingsComboBox.DisplayMember = "Nick"; |
RemoteBindingsComboBox.ValueMember = "MouseKeyBindings"; |
RemoteBindingsComboBox.ValueMember = "KeyBindings"; |
RemoteBindingsComboBox.DataSource = RemoteBindingsComboBoxSource; |
|
// Start lobby message synchronizer. |
@@ -68,11 +68,19 @@ |
LobbyMessageSynchronizer.OnLobbyMessageReceived += OnLobbyMessageReceived; |
|
// Start mouse key bindings synchronizer. |
MouseKeyBindingsSynchronizer = new MouseKeyBindingsSynchronizer(LocalMouseKeyBindings, MqttCommunication, |
KeyBindingsSynchronizer = new KeyBindingsSynchronizer(LocalKeyBindings, MqttCommunication, |
FormTaskScheduler, FormCancellationTokenSource.Token); |
MouseKeyBindingsSynchronizer.OnMouseKeyBindingsSynchronized += OnMouseKeyBindingsSynchronized; |
KeyBindingsSynchronizer.OnMouseKeyBindingsSynchronized += OnMouseKeyBindingsSynchronized; |
|
// Start key binding simulator. |
// 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 CancellationTokenSource FormCancellationTokenSource { get; set; } |
@@ -83,45 +91,56 @@ |
|
private List<string> MouseKeyCombo { get; set; } |
|
private MouseKeyBindings LocalMouseKeyBindings { get; } |
private LocalKeyBindings LocalKeyBindings { get; } |
|
private RemoteMouseKeyBindings RemoteMouseKeyBindings { get; } |
private RemoteKeyBindings RemoteKeyBindings { get; } |
|
private BindingSource LocalListBoxBindingSource { get; } |
|
private BindingSource RemoteBindingsComboBoxSource { get; } |
|
private MouseKeyBindingsExchange MouseKeyBindingsExchange { get; } |
private KeyBindingsExchange KeyBindingsExchange { get; } |
|
public MqttCommunication MqttCommunication { get; set; } |
|
public LobbyMessageSynchronizer LobbyMessageSynchronizer { get; set; } |
|
public MouseKeyBindingsSynchronizer MouseKeyBindingsSynchronizer { get; set; } |
public KeyBindingsSynchronizer KeyBindingsSynchronizer { get; set; } |
|
private async Task SaveLocalMouseKeyBindings() |
public KeyInterceptor KeyInterceptor { get; set; } |
|
public KeySimulator KeySimulator { get; set; } |
|
/// <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) |
{ |
try |
{ |
using (var memoryStream = new MemoryStream()) |
{ |
MouseKeyBindings.XmlSerializer.Serialize(memoryStream, LocalMouseKeyBindings); |
FormCancellationTokenSource?.Dispose(); |
FormCancellationTokenSource = null; |
|
memoryStream.Position = 0L; |
|
using (var fileStream = new FileStream("LocalMouseKeyBindings.xml", FileMode.Create)) |
{ |
await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false); |
} |
} |
} |
catch (Exception) |
if (disposing && components != null) |
{ |
ActivityTextBox.AppendText( |
$"{Strings.Failed_saving_local_bindings}{Environment.NewLine}"); |
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( |
@@ -164,80 +183,75 @@ |
$"{Strings.Client_connection_failed}{Environment.NewLine}"); |
} |
|
private void OnMqttServerAuthenticationFailed(object sender, EventArgs e) |
private void OnMqttServerAuthenticationFailed(object sender, MqttAuthenticationFailureEventArgs e) |
{ |
ActivityTextBox.AppendText( |
$"{Strings.Failed_to_authenticate_client}{Environment.NewLine}"); |
$"{Strings.Failed_to_authenticate_client} : {e.Exception}{Environment.NewLine}"); |
} |
|
private void OnMqttClientAuthenticationFailed(object sender, EventArgs e) |
private void OnMqttClientAuthenticationFailed(object sender, MqttAuthenticationFailureEventArgs e) |
{ |
ActivityTextBox.AppendText( |
$"{Strings.Server_authentication_failed}{Environment.NewLine}"); |
$"{Strings.Failed_to_authenticate_with_server} : {e.Exception}{Environment.NewLine}"); |
} |
|
/// <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) |
private void OnMouseKeyBindingsSynchronized(object sender, KeyBindingsSynchronizerEventArgs e) |
{ |
if (disposing && components != null) |
{ |
FormCancellationTokenSource.Dispose(); |
FormCancellationTokenSource = null; |
|
components.Dispose(); |
} |
|
base.Dispose(disposing); |
} |
|
private void OnMouseKeyBindingsSynchronized(object sender, MouseKeyBindingsSynchronizedEventArgs e) |
{ |
ActivityTextBox.AppendText( |
$"{Strings.Synchronized_bindings_with_client} : {e.Bindings.Nick} : {e.Bindings.MouseKeyBindings.Count}{Environment.NewLine}"); |
$"{Strings.Synchronized_bindings_with_client} : {e.Bindings.Nick} : {e.Bindings.KeyBindings.Count}{Environment.NewLine}"); |
|
var exchangeBindings = MouseKeyBindingsExchange.ExchangeBindings.FirstOrDefault(exchangeBinding => |
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) |
{ |
MouseKeyBindingsExchange.ExchangeBindings.Add(e.Bindings); |
KeyBindingsExchange.ExchangeBindings.Add(e.Bindings); |
RemoteBindingsComboBoxSource.ResetBindings(false); |
UpdateRemoteListBoxItems(); |
UpdateRemoteItems(); |
return; |
} |
|
// If the bindings for the nick have not changed then do not update. |
if (exchangeBindings.MouseKeyBindings.SequenceEqual(e.Bindings.MouseKeyBindings)) |
if (exchangeBindings.KeyBindings.SequenceEqual(e.Bindings.KeyBindings)) |
{ |
RemoteBindingsComboBoxSource.ResetBindings(false); |
UpdateRemoteListBoxItems(); |
UpdateRemoteItems(); |
return; |
} |
|
// Update the bindings. |
exchangeBindings.MouseKeyBindings = e.Bindings.MouseKeyBindings; |
exchangeBindings.KeyBindings = e.Bindings.KeyBindings; |
RemoteBindingsComboBoxSource.ResetBindings(false); |
UpdateRemoteListBoxItems(); |
UpdateRemoteItems(); |
} |
|
private void UpdateRemoteListBoxItems() |
private void UpdateRemoteItems() |
{ |
var exchangeBinding = (List<MouseKeyBinding>) RemoteBindingsComboBox.SelectedValue; |
if (exchangeBinding == null) |
var exchangeBindings = (List<KeyBinding>) RemoteBindingsComboBox.SelectedValue; |
if (exchangeBindings == null) |
return; |
|
var replaceMouseBindings = new List<RemoteKeyBinding>(); |
foreach (var remoteBinding in RemoteKeyBindings.Bindings) |
{ |
if (!exchangeBindings.Any(binding => |
string.Equals(binding.Name, remoteBinding.Name, StringComparison.Ordinal))) |
continue; |
|
replaceMouseBindings.Add(remoteBinding); |
} |
|
RemoteKeyBindings.Bindings = replaceMouseBindings; |
|
RemoteBindingsListBox.Items.Clear(); |
RemoteBindingsListBox.DisplayMember = "Name"; |
RemoteBindingsListBox.ValueMember = "Name"; |
var i = exchangeBinding.Select(binding => (object) binding.Name).ToArray(); |
if (i.Length == 0) |
var bindings = exchangeBindings.Select(binding => (object) binding.Name).ToArray(); |
if (bindings.Length == 0) |
return; |
|
RemoteBindingsListBox.Items.AddRange(i); |
RemoteBindingsListBox.Items.AddRange(bindings); |
} |
|
private void OnLobbyMessageReceived(object sender, LobbyMessageReceivedEventArgs e) |
@@ -260,7 +274,7 @@ |
// Stop the MQTT server if it is running. |
if (MqttCommunication.Running) |
{ |
await MqttCommunication.Stop().ConfigureAwait(false); |
await MqttCommunication.Stop(); |
HostButton.BackColor = Color.Empty; |
|
// Enable controls. |
@@ -268,7 +282,7 @@ |
Address.Enabled = true; |
Port.Enabled = true; |
Nick.Enabled = true; |
|
Password.Enabled = true; |
return; |
} |
|
@@ -276,8 +290,8 @@ |
return; |
|
// Start the MQTT server. |
if (!await MqttCommunication.Start(MqttCommunicationType.Server, ipAddress, port, nick, password) |
.ConfigureAwait(false)) |
if (!await MqttCommunication |
.Start(MqttCommunicationType.Server, ipAddress, port, nick, password)) |
{ |
ActivityTextBox.AppendText( |
$"{Strings.Failed_starting_server}{Environment.NewLine}"); |
@@ -291,6 +305,7 @@ |
Address.Enabled = false; |
Port.Enabled = false; |
Nick.Enabled = false; |
Password.Enabled = false; |
} |
|
private bool ValidateAddressPort(out IPAddress address, out int port, out string nick, out string password) |
@@ -340,7 +355,7 @@ |
return false; |
} |
|
password = AES.LinearFeedbackShiftPassword(Password.Text); |
password = AES.ExpandKey(Password.Text); |
|
Address.BackColor = Color.Empty; |
Port.BackColor = Color.Empty; |
@@ -354,7 +369,7 @@ |
{ |
if (MqttCommunication.Running) |
{ |
await MqttCommunication.Stop().ConfigureAwait(false); |
await MqttCommunication.Stop(); |
ConnectButton.Text = Strings.Connect; |
ConnectButton.BackColor = Color.Empty; |
|
@@ -361,6 +376,7 @@ |
Address.Enabled = true; |
Port.Enabled = true; |
Nick.Enabled = true; |
Password.Enabled = true; |
HostButton.Enabled = true; |
return; |
} |
@@ -368,8 +384,8 @@ |
if (!ValidateAddressPort(out var ipAddress, out var port, out var nick, out var password)) |
return; |
|
if (!await MqttCommunication.Start(MqttCommunicationType.Client, ipAddress, port, nick, password) |
.ConfigureAwait(false)) |
if (!await MqttCommunication |
.Start(MqttCommunicationType.Client, ipAddress, port, nick, password)) |
{ |
ActivityTextBox.AppendText( |
$"{Strings.Failed_starting_client}{Environment.NewLine}"); |
@@ -383,6 +399,7 @@ |
Address.Enabled = false; |
Port.Enabled = false; |
Nick.Enabled = false; |
Password.Enabled = false; |
} |
|
private async void LobbySayTextBoxKeyDown(object sender, KeyEventArgs e) |
@@ -390,12 +407,12 @@ |
if (e.KeyCode != Keys.Enter) |
return; |
|
await LobbyMessageSynchronizer.Broadcast(LobbySayTextBox.Text).ConfigureAwait(false); |
await LobbyMessageSynchronizer.Broadcast(LobbySayTextBox.Text); |
|
LobbySayTextBox.Text = string.Empty; |
} |
|
private void HelmAddButtonClick(object sender, EventArgs e) |
private void LocalAddBindingButtonClick(object sender, EventArgs e) |
{ |
if (string.IsNullOrEmpty(LocalNameTextBox.Text)) |
{ |
@@ -403,15 +420,25 @@ |
return; |
} |
|
// Only unique names allowed. |
if (LocalKeyBindings.Bindings.Any(binding => |
string.Equals(binding.Name, LocalNameTextBox.Text, StringComparison.Ordinal))) |
{ |
LocalNameTextBox.BackColor = Color.LightPink; |
LocalBindingsListBox.BackColor = Color.LightPink; |
return; |
} |
|
LocalNameTextBox.BackColor = Color.Empty; |
LocalBindingsListBox.BackColor = Color.Empty; |
|
ShowOverlayPanel(); |
|
MouseKeyCombo = new List<string>(); |
|
MouseKeyApplicationHook = Hook.AppEvents(); |
MouseKeyApplicationHook.MouseDown += LocalMouseKeyHookOnMouseDown; |
MouseKeyApplicationHook = Hook.GlobalEvents(); |
MouseKeyApplicationHook.KeyUp += LocalMouseKeyHookOnKeyUp; |
MouseKeyApplicationHook.KeyDown += LocalMouseKeyHookOnKeyDown; |
MouseKeyApplicationHook.MouseUp += LocalMouseKeyHookOnMouseUp; |
} |
|
private void ShowOverlayPanel() |
@@ -421,16 +448,14 @@ |
OverlayPanel.Invalidate(); |
} |
|
private async void LocalMouseKeyHookOnKeyUp(object sender, KeyEventArgs e) |
private void LocalMouseKeyHookOnKeyUp(object sender, KeyEventArgs e) |
{ |
LocalMouseKeyBindings.Bindings.Add(new MouseKeyBinding(LocalNameTextBox.Text, MouseKeyCombo)); |
LocalKeyBindings.Bindings.Add(new KeyBinding(LocalNameTextBox.Text, MouseKeyCombo)); |
|
LocalListBoxBindingSource.ResetBindings(false); |
|
MouseKeyApplicationHook.KeyDown -= LocalMouseKeyHookOnKeyDown; |
MouseKeyApplicationHook.KeyUp -= LocalMouseKeyHookOnKeyUp; |
MouseKeyApplicationHook.KeyDown -= LocalMouseKeyHookOnKeyDown; |
MouseKeyApplicationHook.KeyUp -= LocalMouseKeyHookOnKeyUp; |
|
MouseKeyApplicationHook.Dispose(); |
|
@@ -437,7 +462,7 @@ |
LocalNameTextBox.Text = string.Empty; |
HideOverlayPanel(); |
|
await SaveLocalMouseKeyBindings().ConfigureAwait(false); |
//await SaveLocalMouseKeyBindings(); |
} |
|
private void HideOverlayPanel() |
@@ -447,58 +472,35 @@ |
OverlayPanel.Invalidate(); |
} |
|
private async void LocalMouseKeyHookOnMouseUp(object sender, MouseEventArgs e) |
{ |
LocalMouseKeyBindings.Bindings.Add(new MouseKeyBinding(LocalNameTextBox.Text, MouseKeyCombo)); |
|
LocalListBoxBindingSource.ResetBindings(false); |
|
MouseKeyApplicationHook.KeyDown -= LocalMouseKeyHookOnKeyDown; |
MouseKeyApplicationHook.KeyUp -= LocalMouseKeyHookOnKeyUp; |
MouseKeyApplicationHook.KeyDown -= LocalMouseKeyHookOnKeyDown; |
MouseKeyApplicationHook.KeyUp -= LocalMouseKeyHookOnKeyUp; |
|
MouseKeyApplicationHook.Dispose(); |
|
LocalNameTextBox.Text = string.Empty; |
HideOverlayPanel(); |
|
await SaveLocalMouseKeyBindings().ConfigureAwait(false); |
} |
|
|
private void LocalMouseKeyHookOnMouseDown(object sender, MouseEventArgs e) |
{ |
MouseKeyCombo.Add(e.Button.ToDisplayName()); |
} |
|
private void LocalMouseKeyHookOnKeyDown(object sender, KeyEventArgs e) |
{ |
e.SuppressKeyPress = true; |
|
MouseKeyCombo.Add(e.KeyCode.ToDisplayName()); |
KeyConversion.KeysToString.TryGetValue((byte) e.KeyCode, out var key); |
|
MouseKeyCombo.Add(key); |
} |
|
private void HelmNameTextBoxClick(object sender, EventArgs e) |
private void LocalNameTextBoxClick(object sender, EventArgs e) |
{ |
LocalNameTextBox.BackColor = Color.Empty; |
} |
|
private async void HelmRemoveButtonClick(object sender, EventArgs e) |
private void LocalBindingsRemoveButtonClick(object sender, EventArgs e) |
{ |
var helmBinding = (MouseKeyBinding) LocalBindingsListBox.SelectedItem; |
var helmBinding = (KeyBinding) LocalBindingsListBox.SelectedItem; |
if (helmBinding == null) |
return; |
|
LocalMouseKeyBindings.Bindings.Remove(helmBinding); |
LocalKeyBindings.Bindings.Remove(helmBinding); |
LocalListBoxBindingSource.ResetBindings(false); |
|
await SaveLocalMouseKeyBindings().ConfigureAwait(false); |
// await SaveLocalMouseKeyBindings(); |
} |
|
private async void LobbySayButtonClick(object sender, EventArgs e) |
{ |
await LobbyMessageSynchronizer.Broadcast(LobbySayTextBox.Text).ConfigureAwait(false); |
await LobbyMessageSynchronizer.Broadcast(LobbySayTextBox.Text); |
|
LobbySayTextBox.Text = string.Empty; |
} |
@@ -505,54 +507,54 @@ |
|
private void RemoteBindingsComboBoxSelectionChangeCompleted(object sender, EventArgs e) |
{ |
UpdateRemoteListBoxItems(); |
UpdateRemoteItems(); |
} |
|
private async void WingManFormOnLoad(object sender, EventArgs e) |
private void WingManFormOnLoad(object sender, EventArgs e) |
{ |
await LoadLocalMouseKeyBindings(); |
await LoadRemoteMouseKeyBindings(); |
// await LoadLocalMouseKeyBindings(); |
|
// await LoadRemoteMouseKeyBindings(); |
} |
|
private async Task LoadLocalMouseKeyBindings() |
private void RemoteBindingsBindButtonClicked(object sender, EventArgs e) |
{ |
try |
if (string.IsNullOrEmpty((string) RemoteBindingsListBox.SelectedItem)) |
{ |
using (var fileStream = new FileStream("LocalMouseKeyBindings.xml", FileMode.Open)) |
{ |
using (var memoryStream = new MemoryStream()) |
{ |
await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false); |
RemoteBindingsListBox.BackColor = Color.LightPink; |
return; |
} |
|
memoryStream.Position = 0L; |
RemoteBindingsListBox.BackColor = Color.Empty; |
|
var loadedBindings = |
(MouseKeyBindings) MouseKeyBindings.XmlSerializer.Deserialize(memoryStream); |
ShowOverlayPanel(); |
|
foreach (var binding in loadedBindings.Bindings) LocalMouseKeyBindings.Bindings.Add(binding); |
MouseKeyCombo = new List<string>(); |
|
LocalListBoxBindingSource.ResetBindings(false); |
} |
} |
} |
catch (Exception) |
{ |
ActivityTextBox.AppendText( |
$"{Strings.Failed_loading_local_bindings}{Environment.NewLine}"); |
} |
MouseKeyApplicationHook = Hook.GlobalEvents(); |
MouseKeyApplicationHook.KeyUp += RemoteMouseKeyHookOnKeyUp; |
MouseKeyApplicationHook.KeyDown += RemoteMouseKeyHookOnKeyDown; |
} |
|
private void RemoteBindingsBindButtonClicked(object sender, EventArgs e) |
private void RemoteBindingsUnbindButtonClicked(object sender, EventArgs e) |
{ |
ShowOverlayPanel(); |
var item = (string) RemoteBindingsListBox.SelectedItem; |
if (string.IsNullOrEmpty(item)) |
{ |
RemoteBindingsListBox.BackColor = Color.LightPink; |
return; |
} |
|
MouseKeyCombo = new List<string>(); |
RemoteBindingsListBox.BackColor = Color.Empty; |
|
MouseKeyApplicationHook = Hook.AppEvents(); |
MouseKeyApplicationHook.MouseDown += RemoteMouseKeyHookOnMouseDown; |
MouseKeyApplicationHook.KeyUp += RemoteMouseKeyHookOnKeyUp; |
MouseKeyApplicationHook.KeyDown += RemoteMouseKeyHookOnKeyDown; |
MouseKeyApplicationHook.MouseUp += RemoteMouseKeyHookOnMouseUp; |
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 RemoteMouseKeyHookOnKeyDown(object sender, KeyEventArgs e) |
@@ -559,23 +561,18 @@ |
{ |
e.SuppressKeyPress = true; |
|
MouseKeyCombo.Add(e.KeyCode.ToDisplayName()); |
} |
KeyConversion.KeysToString.TryGetValue((byte) e.KeyCode, out var key); |
|
private void RemoteMouseKeyHookOnMouseDown(object sender, MouseEventArgs e) |
{ |
MouseKeyCombo.Add(e.Button.ToDisplayName()); |
MouseKeyCombo.Add(key); |
} |
|
private async void RemoteMouseKeyHookOnMouseUp(object sender, MouseEventArgs e) |
private void RemoteMouseKeyHookOnKeyUp(object sender, KeyEventArgs e) |
{ |
RemoteMouseKeyBindings.Bindings.Add(new RemoteMouseKeyBinding(RemoteBindingsComboBox.Text, |
RemoteKeyBindings.Bindings.Add(new RemoteKeyBinding(RemoteBindingsComboBox.Text, |
(string) RemoteBindingsListBox.SelectedItem, MouseKeyCombo)); |
|
MouseKeyApplicationHook.KeyDown -= RemoteMouseKeyHookOnKeyDown; |
MouseKeyApplicationHook.KeyUp -= RemoteMouseKeyHookOnKeyUp; |
MouseKeyApplicationHook.KeyDown -= RemoteMouseKeyHookOnKeyDown; |
MouseKeyApplicationHook.KeyUp -= RemoteMouseKeyHookOnKeyUp; |
|
MouseKeyApplicationHook.Dispose(); |
|
@@ -582,27 +579,80 @@ |
RemoteBindingsBindToBox.Text = string.Join(" + ", MouseKeyCombo); |
HideOverlayPanel(); |
|
await SaveRemoteMouseKeyBindings().ConfigureAwait(false); |
// await SaveRemoteMouseKeyBindings(); |
} |
|
private async void RemoteMouseKeyHookOnKeyUp(object sender, KeyEventArgs e) |
private void RemoteBindingsListBoxSelectedValueChanged(object sender, EventArgs e) |
{ |
RemoteMouseKeyBindings.Bindings.Add(new RemoteMouseKeyBinding(RemoteBindingsComboBox.Text, |
(string) RemoteBindingsListBox.SelectedItem, MouseKeyCombo)); |
RemoteBindingsBindToBox.Text = ""; |
|
MouseKeyApplicationHook.KeyDown -= RemoteMouseKeyHookOnKeyDown; |
MouseKeyApplicationHook.KeyUp -= RemoteMouseKeyHookOnKeyUp; |
MouseKeyApplicationHook.KeyDown -= RemoteMouseKeyHookOnKeyDown; |
MouseKeyApplicationHook.KeyUp -= RemoteMouseKeyHookOnKeyUp; |
var name = (string) RemoteBindingsListBox.SelectedItem; |
if (string.IsNullOrEmpty(name)) |
return; |
|
MouseKeyApplicationHook.Dispose(); |
foreach (var binding in RemoteKeyBindings.Bindings) |
{ |
if (!string.Equals(binding.Name, name)) |
continue; |
|
RemoteBindingsBindToBox.Text = string.Join(" + ", MouseKeyCombo); |
HideOverlayPanel(); |
RemoteBindingsBindToBox.Text = string.Join(" + ", binding.Keys); |
break; |
} |
} |
|
await SaveRemoteMouseKeyBindings().ConfigureAwait(false); |
#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); |
|
LocalListBoxBindingSource.ResetBindings(false); |
} |
} |
} |
catch (Exception) |
{ |
ActivityTextBox.AppendText( |
$"{Strings.Failed_loading_local_bindings}{Environment.NewLine}"); |
} |
} |
|
private async Task SaveRemoteMouseKeyBindings() |
{ |
try |
@@ -609,13 +659,13 @@ |
{ |
using (var memoryStream = new MemoryStream()) |
{ |
RemoteMouseKeyBindings.XmlSerializer.Serialize(memoryStream, RemoteMouseKeyBindings); |
RemoteKeyBindings.XmlSerializer.Serialize(memoryStream, RemoteKeyBindings); |
|
memoryStream.Position = 0L; |
|
using (var fileStream = new FileStream("RemoteMouseKeyBindings.xml", FileMode.Create)) |
using (var fileStream = new FileStream("RemoteKeyBindings.xml", FileMode.Create)) |
{ |
await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false); |
await memoryStream.CopyToAsync(fileStream); |
} |
} |
} |
@@ -630,18 +680,18 @@ |
{ |
try |
{ |
using (var fileStream = new FileStream("RemoteMouseKeyBindings.xml", FileMode.Open)) |
using (var fileStream = new FileStream("RemoteKeyBindings.xml", FileMode.Open)) |
{ |
using (var memoryStream = new MemoryStream()) |
{ |
await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false); |
await fileStream.CopyToAsync(memoryStream); |
|
memoryStream.Position = 0L; |
|
var loadedBindings = |
(RemoteMouseKeyBindings) RemoteMouseKeyBindings.XmlSerializer.Deserialize(memoryStream); |
(RemoteKeyBindings) RemoteKeyBindings.XmlSerializer.Deserialize(memoryStream); |
|
foreach (var binding in loadedBindings.Bindings) RemoteMouseKeyBindings.Bindings.Add(binding); |
foreach (var binding in loadedBindings.Bindings) RemoteKeyBindings.Bindings.Add(binding); |
} |
} |
} |
@@ -651,5 +701,7 @@ |
$"{Strings.Failed_loading_remote_bindings}{Environment.NewLine}"); |
} |
} |
|
#endregion |
} |
} |