using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Drawing; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Hush.Chat; using Hush.Communication; using Hush.Discovery; using Hush.Properties; using Hush.Utilities; using MQTTnet.Extensions.ManagedClient; using MQTTnet.Server; using WingMan.Communication; namespace Hush { public partial class Hush : Form { private static TaskScheduler FormTaskScheduler { get; set; } private static CancellationTokenSource FormCancellationTokenSource { get; set; } private static MqttCommunication MqttCommunication { get; set; } private static ChatMessageSynchronizer ChatMessageSynchronizer { get; set; } private static Discovery.Discovery Discovery { get; set; } public Hush() { InitializeComponent(); FormTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); FormCancellationTokenSource = new CancellationTokenSource(); // Bind to settings changed event. Settings.Default.SettingsLoaded += DefaultOnSettingsLoaded; Settings.Default.SettingsSaving += DefaultOnSettingsSaving; Settings.Default.PropertyChanged += DefaultOnPropertyChanged; // Set up discovery. Discovery = new Discovery.Discovery(Constants.AssemblyName, FormCancellationTokenSource.Token, FormTaskScheduler); Discovery.OnPortMapFailed += OnDiscoveryPortMapFailed; // Bind to MQTT events. MqttCommunication = new MqttCommunication(FormTaskScheduler, FormCancellationTokenSource.Token); MqttCommunication.OnClientConnectionFailed += MqttOnClientConnectionFailed; MqttCommunication.OnClientAuthenticationFailed += MqttOnClientAuthenticationFailed; MqttCommunication.OnClientConnected += MqttOnClientConnected; MqttCommunication.OnClientDisconnected += MqttOnClientDisconnected; MqttCommunication.OnServerClientConnected += MqttCommunicationOnOnServerClientConnected; MqttCommunication.OnServerClientDisconnected += MqttOnServerClientDisconnected; // Start message synchronizer. ChatMessageSynchronizer = new ChatMessageSynchronizer(Constants.MqttTopic, MqttCommunication, FormTaskScheduler, FormCancellationTokenSource.Token); ChatMessageSynchronizer.OnMessageReceived += OnMessageReceived; } /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { // Unbind message synchronizer. ChatMessageSynchronizer.OnMessageReceived -= OnMessageReceived; // Unbind settings handlers. Settings.Default.SettingsLoaded -= DefaultOnSettingsLoaded; Settings.Default.SettingsSaving -= DefaultOnSettingsSaving; Settings.Default.PropertyChanged -= DefaultOnPropertyChanged; if (disposing && components != null) components.Dispose(); base.Dispose(disposing); } #region Overrides protected override void OnPaintBackground(PaintEventArgs e) { base.OnPaintBackground(e); //comment this out to prevent default painting using (var brush = new SolidBrush(Settings.Default.Color)) //any color you like { e.Graphics.FillRectangle(brush, e.ClipRectangle); } } protected override void WndProc(ref Message m) { const uint WM_NCHITTEST = 0x0084; const uint WM_MOUSEMOVE = 0x0200; const uint HTLEFT = 10; const uint HTRIGHT = 11; const uint HTBOTTOMRIGHT = 17; const uint HTBOTTOM = 15; const uint HTBOTTOMLEFT = 16; const uint HTTOP = 12; const uint HTTOPLEFT = 13; const uint HTTOPRIGHT = 14; const int RESIZE_HANDLE_SIZE = 10; var handled = false; if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE) { var formSize = Size; var screenPoint = new Point(m.LParam.ToInt32()); var clientPoint = PointToClient(screenPoint); var boxes = new Dictionary { { HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) }, { HTBOTTOM, new Rectangle(RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, formSize.Width - 2 * RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) }, { HTBOTTOMRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) }, { HTRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2 * RESIZE_HANDLE_SIZE) }, { HTTOPRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) }, { HTTOP, new Rectangle(RESIZE_HANDLE_SIZE, 0, formSize.Width - 2 * RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) }, {HTTOPLEFT, new Rectangle(0, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)}, { HTLEFT, new Rectangle(0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2 * RESIZE_HANDLE_SIZE) } }; foreach (var hitBox in boxes) if (WindowState != FormWindowState.Maximized && hitBox.Value.Contains(clientPoint)) { m.Result = (IntPtr) hitBox.Key; handled = true; break; } } if (!handled) base.WndProc(ref m); } #endregion #region Event Handlers private void MqttOnServerClientDisconnected(object sender, MqttClientDisconnectedEventArgs mqttClientDisconnectedEventArgs) { notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning; notifyIcon1.BalloonTipTitle = Strings.Network_warning; notifyIcon1.BalloonTipText = $"{Strings.Client_disconnected}"; notifyIcon1.ShowBalloonTip(1000); } private void MqttCommunicationOnOnServerClientConnected(object sender, MqttClientConnectedEventArgs mqttClientConnectedEventArgs) { notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning; notifyIcon1.BalloonTipTitle = Strings.Network_warning; notifyIcon1.BalloonTipText = $"{Strings.Client_connected}"; notifyIcon1.ShowBalloonTip(1000); } private void MqttOnClientDisconnected(object sender, MQTTnet.Client.MqttClientDisconnectedEventArgs mqttClientDisconnectedEventArgs) { notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning; notifyIcon1.BalloonTipTitle = Strings.Network_warning; notifyIcon1.BalloonTipText = $"{Strings.Client_disconnected}"; notifyIcon1.ShowBalloonTip(1000); } private void MqttOnClientConnected(object sender, MQTTnet.Client.MqttClientConnectedEventArgs mqttClientConnectedEventArgs) { notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning; notifyIcon1.BalloonTipTitle = Strings.Network_warning; notifyIcon1.BalloonTipText = $"{Strings.Client_connected}"; notifyIcon1.ShowBalloonTip(1000); } private void MqttOnClientAuthenticationFailed(object sender, MqttAuthenticationFailureEventArgs mqttAuthenticationFailureEventArgs) { notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning; notifyIcon1.BalloonTipTitle = Strings.Network_warning; notifyIcon1.BalloonTipText = $"{Strings.Failed_to_authenticate_to_server}"; notifyIcon1.ShowBalloonTip(1000); } private void MqttOnClientConnectionFailed(object sender, MqttManagedProcessFailedEventArgs mqttManagedProcessFailedEventArgs) { notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning; notifyIcon1.BalloonTipTitle = Strings.Network_warning; notifyIcon1.BalloonTipText = $"{Strings.Failed_to_connect_to_server} {mqttManagedProcessFailedEventArgs.Exception.Message}"; notifyIcon1.ShowBalloonTip(1000); } private void OnDiscoveryPortMapFailed(object sender, DiscoveryFailedEventArgs args) { notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning; notifyIcon1.BalloonTipTitle = Strings.Network_warning; notifyIcon1.BalloonTipText = Strings.Failed_to_create_automatic_NAT_port_mapping; notifyIcon1.ShowBalloonTip(1000); } private void OnMessageReceived(object sender, ChatMessageReceivedEventArgs e) { var message = $"{e.Nick} : {e.Message}{Environment.NewLine}"; chatTextBox.AppendText(message); } private async void DefaultOnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (string.Equals(e.PropertyName, "LaunchOnBoot")) LaunchOnBoot.Set(Settings.Default.LaunchOnBoot); if (string.Equals(e.PropertyName, "Start")) await ToggleStart(); } private void DefaultOnSettingsSaving(object sender, CancelEventArgs e) { } private void DefaultOnSettingsLoaded(object sender, SettingsLoadedEventArgs e) { LaunchOnBoot.Set(Settings.Default.LaunchOnBoot); } private void OnMouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { DllImports.ReleaseCapture(); DllImports.SendMessage(Handle, DllImports.WM_NCLBUTTONDOWN, DllImports.HT_CAPTION, 0); } } private void HushFocus(object sender, EventArgs e) { Opacity = .75; } private void HushUnfocus(object sender, EventArgs e) { Opacity = .33; } private void ContextMenuOnClickAbout(object sender, EventArgs e) { new About().Show(); } private void ContextMenuOnClickQuit(object sender, EventArgs e) { Application.Exit(); } private async void MessageTextBoxOnKeyDown(object sender, KeyEventArgs e) { // Prevent the enter key to be passed to the text box or we get a nasty ding. if (e.KeyCode == Keys.Enter) e.SuppressKeyPress = true; if (e.KeyCode != Keys.Enter) return; // Do not send messages if the communication is not running. if (!MqttCommunication.Running) return; if (string.IsNullOrEmpty(messageTextBox.Text)) return; // Send the message await ChatMessageSynchronizer.Broadcast($"{messageTextBox.Text}"); messageTextBox.Text = string.Empty; } private void ToolStripNickTextBoxOnTextChanged(object sender, EventArgs e) { Settings.Default.Nick = ((ToolStripTextBox) sender).Text; } private void ToolStripPasswordTextBoxOnTextChanged(object sender, EventArgs e) { Settings.Default.Password = ((ToolStripTextBox) sender).Text; } private void toolStripMenuItem1_Click(object sender, EventArgs e) { Settings.Default.LaunchOnBoot = ((ToolStripMenuItem) sender).Checked; } private void startToolStripMenuItem_CheckStateChanged(object sender, EventArgs e) { var toolStripMenuItem = (ToolStripMenuItem) sender; Settings.Default.Start = toolStripMenuItem.Checked; } private void toolStripComboBox1_SelectedIndexChanged(object sender, EventArgs e) { var toolStripComboBox = (ToolStripComboBox) sender; Settings.Default.Start = false; Settings.Default.Mode = toolStripComboBox.Items[toolStripComboBox.SelectedIndex].ToString(); } private void ToolStripAddressTextBoxOnTextChanged(object sender, EventArgs e) { var toolStripMenuItem = (ToolStripTextBox) sender; Settings.Default.Address = toolStripMenuItem.Text; } private void ToolStripPortTextBoxOnTextChanged(object sender, EventArgs e) { var toolStripMenuItem = (ToolStripTextBox) sender; Settings.Default.Port = toolStripMenuItem.Text; } #endregion private async Task ToggleStart() { switch (Settings.Default.Mode) { case "Server": await StopServer(); if (!Settings.Default.Start) break; try { await StartServer(); } catch (ToolTippedException ex) { notifyIcon1.BalloonTipIcon = ex.Icon; notifyIcon1.BalloonTipTitle = ex.Title; notifyIcon1.BalloonTipText = ex.Body; notifyIcon1.ShowBalloonTip(1000); } break; case "Client": await StopClient(); if (!Settings.Default.Start) break; try { await StartClient(); } catch (ToolTippedException ex) { notifyIcon1.BalloonTipIcon = ex.Icon; notifyIcon1.BalloonTipTitle = ex.Title; notifyIcon1.BalloonTipText = ex.Body; notifyIcon1.ShowBalloonTip(1000); } break; } startToolStripMenuItem.Checked = Settings.Default.Start; } private async Task StopClient() { // Stop the client if it is already started. await MqttCommunication.Stop(); } private async Task StopServer() { // Remove 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); // Stop the MQTT server if it is running. await MqttCommunication.Stop(); } private async Task StartClient() { if (!IPAddress.TryParse(Settings.Default.Address, out var address)) try { var getHostAddresses = await Dns.GetHostAddressesAsync(Settings.Default.Address); if (!getHostAddresses.Any()) throw new Exception(); address = getHostAddresses.FirstOrDefault(); } catch { throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, $"{Strings.Unable_to_determine_address} {Settings.Default.Address}"); } if (!uint.TryParse(Settings.Default.Port, out var port)) throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, $"{Strings.Unable_to_determine_port} {Settings.Default.Port}"); if (string.IsNullOrEmpty(Settings.Default.Nick)) throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.No_nickname_set); if (string.IsNullOrEmpty(Settings.Default.Password)) throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.No_password_set); if (!await MqttCommunication .Start(MqttCommunicationType.Client, address, (int) port, Settings.Default.Nick, Settings.Default.Password, new[] {Constants.MqttTopic})) throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.Unable_to_start_client); } private async Task StartServer() { if (!IPAddress.TryParse(Settings.Default.Address, out var address)) try { address = Dns.GetHostAddresses(Settings.Default.Address).FirstOrDefault(); } catch { throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, $"{Strings.Unable_to_determine_address} {Settings.Default.Address}"); } if (!uint.TryParse(Settings.Default.Port, out var port)) throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, $"{Strings.Unable_to_determine_port} {Settings.Default.Port}"); if (string.IsNullOrEmpty(Settings.Default.Nick)) throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.No_nickname_set); if (string.IsNullOrEmpty(Settings.Default.Password)) throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.No_password_set); // Try to reserve port: try UPnP followed by PMP. await Task.Delay(0, FormCancellationTokenSource.Token).ContinueWith(async _ => { if (!await Discovery.CreateMapping(DiscoveryType.Upnp, (int) port)) await Discovery.CreateMapping(DiscoveryType.Pmp, (int) port); }, FormCancellationTokenSource.Token, TaskContinuationOptions.LongRunning, FormTaskScheduler); // Start the MQTT server. if (!await MqttCommunication .Start(MqttCommunicationType.Server, address, (int) port, Settings.Default.Nick, Settings.Default.Password, new[] {Constants.MqttTopic})) throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.Unable_to_start_server); } } }