Hush – Rev 4

Subversion Repositories:
Rev:
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;
        }


        /// <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)
        {
            // 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<uint, Rectangle>
                {
                    {
                        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);
        }
    }
}

Generated by GNU Enscript 1.6.5.90.