Hush – Blame information for rev 2

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Configuration;
5 using System.Drawing;
6 using System.Linq;
7 using System.Net;
8 using System.Threading;
9 using System.Threading.Tasks;
10 using System.Windows.Forms;
2 office 11 using Gma.System.MouseKeyHook;
12 using Hush.Chat;
1 office 13 using Hush.Communication;
14 using Hush.Discovery;
15 using Hush.Properties;
16 using Hush.Utilities;
17 using WingMan.Communication;
2 office 18 using Message = System.Windows.Forms.Message;
1 office 19  
20 namespace Hush
21 {
22 public partial class Hush : Form
23 {
24 public Hush()
25 {
26 InitializeComponent();
27  
28 FormTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
29 FormCancellationTokenSource = new CancellationTokenSource();
30  
2 office 31 // Initialize a message buffer.
32 Messages = new List<string>();
33  
1 office 34 // Bind to settings changed event.
35 Settings.Default.SettingsLoaded += DefaultOnSettingsLoaded;
36 Settings.Default.SettingsSaving += DefaultOnSettingsSaving;
37 Settings.Default.PropertyChanged += DefaultOnPropertyChanged;
38  
2 office 39 // Set up keyboard hook.
40 MouseKeyGlobalHook = Hook.GlobalEvents();
41 MouseKeyGlobalHook.KeyDown += MouseKeyGlobalHookOnKeyDown;
42  
1 office 43 // Set up discovery.
2 office 44 Discovery = new Discovery.Discovery(Constants.AssemblyName, FormCancellationTokenSource.Token,
1 office 45 FormTaskScheduler);
46 Discovery.OnPortMapFailed += OnDiscoveryPortMapFailed;
47  
48 // Bind to MQTT events.
49 MqttCommunication = new MqttCommunication(FormTaskScheduler, FormCancellationTokenSource.Token);
2 office 50 // TODO implement events.
51 //MqttCommunication.
1 office 52  
2 office 53 // Start message synchronizer.
54 ChatMessageSynchronizer = new ChatMessageSynchronizer(Constants.MqttTopic, MqttCommunication, FormTaskScheduler,
1 office 55 FormCancellationTokenSource.Token);
2 office 56 ChatMessageSynchronizer.OnMessageReceived += OnMessageReceived;
1 office 57 }
58  
2 office 59 private static IKeyboardMouseEvents MouseKeyGlobalHook { get; set; }
1 office 60 private static TaskScheduler FormTaskScheduler { get; set; }
61 private static CancellationTokenSource FormCancellationTokenSource { get; set; }
62 private static MqttCommunication MqttCommunication { get; set; }
2 office 63 private static ChatMessageSynchronizer ChatMessageSynchronizer { get; set; }
1 office 64 private static Discovery.Discovery Discovery { get; set; }
2 office 65 private static List<string> Messages { get; set; }
1 office 66  
2 office 67 private void MouseKeyGlobalHookOnKeyDown(object sender, KeyEventArgs keyEventArgs)
68 {
69 // Bind to CTRL+C
70 if (!keyEventArgs.Control || keyEventArgs.KeyCode != Keys.C)
71 return;
72  
73 ActiveControl = messageTextBox;
74 messageTextBox.Focus();
75 }
76  
1 office 77 private void OnDiscoveryPortMapFailed(object sender, DiscoveryFailedEventArgs args)
78 {
79 notifyIcon1.BalloonTipIcon = ToolTipIcon.Warning;
80 notifyIcon1.BalloonTipTitle = Strings.Network_warning;
81 notifyIcon1.BalloonTipText = Strings.Failed_to_create_automatic_NAT_port_mapping;
82 notifyIcon1.ShowBalloonTip(1000);
83 }
84  
2 office 85 private void OnMessageReceived(object sender, ChatMessageReceivedEventArgs e)
1 office 86 {
2 office 87 var message = $"{e.Nick} : {e.Message}";
88  
89 Messages.Add(message);
90  
91 // Keep the content of the text box limited to the size of the displayed text.
92 var lineHeight = TextRenderer.MeasureText(message, chatTextBox.Font).Height;
93 var linesPerPage = (int) Math.Ceiling(1.0f * chatTextBox.ClientSize.Height / lineHeight);
94  
95 if (Messages.Count > linesPerPage)
96 Messages.RemoveRange(0, Messages.Count - 1 - linesPerPage);
97  
98 chatTextBox.Text = string.Join(Environment.NewLine, Messages);
1 office 99 }
100  
101 private async void DefaultOnPropertyChanged(object sender, PropertyChangedEventArgs e)
102 {
2 office 103 if (string.Equals(e.PropertyName, "LaunchOnBoot"))
1 office 104 LaunchOnBoot.Set(Settings.Default.LaunchOnBoot);
105  
2 office 106 if (string.Equals(e.PropertyName, "Start"))
1 office 107 await ToggleStart();
108 }
109  
110 private async Task ToggleStart()
111 {
112 switch (Settings.Default.Mode)
113 {
114 case "Server":
115 await StopServer();
116  
117 if (!Settings.Default.Start)
118 break;
119  
120 try
121 {
122 await StartServer();
123 }
124 catch (ToolTippedException ex)
125 {
126 notifyIcon1.BalloonTipIcon = ex.Icon;
127 notifyIcon1.BalloonTipTitle = ex.Title;
128 notifyIcon1.BalloonTipText = ex.Body;
129 notifyIcon1.ShowBalloonTip(1000);
130 }
131  
132 break;
133 case "Client":
134 await StopClient();
135  
136 if (!Settings.Default.Start)
137 break;
138  
139 try
140 {
141 await StartClient();
142 }
143 catch (ToolTippedException ex)
144 {
145 notifyIcon1.BalloonTipIcon = ex.Icon;
146 notifyIcon1.BalloonTipTitle = ex.Title;
147 notifyIcon1.BalloonTipText = ex.Body;
148 notifyIcon1.ShowBalloonTip(1000);
149 }
150  
151 break;
152 }
153  
154 startToolStripMenuItem.Checked = Settings.Default.Start;
155 }
156  
157 private async Task StopClient()
158 {
159 // Stop the client if it is already started.
160 await MqttCommunication.Stop();
161 }
162  
163 private async Task StopServer()
164 {
165 // Remove UPnP and Pmp mappings.
166 await Task.Delay(0, FormCancellationTokenSource.Token).ContinueWith(async _ =>
167 {
168 await Discovery.DeleteMapping(DiscoveryType.Upnp, MqttCommunication.Port);
169 await Discovery.DeleteMapping(DiscoveryType.Pmp, MqttCommunication.Port);
170 },
171 FormCancellationTokenSource.Token, TaskContinuationOptions.LongRunning, FormTaskScheduler);
172  
173 // Stop the MQTT server if it is running.
174 await MqttCommunication.Stop();
175 }
176  
177 private async Task StartClient()
178 {
179 if (!IPAddress.TryParse(Settings.Default.Address, out var address))
180 try
181 {
2 office 182 var getHostAddresses = await Dns.GetHostAddressesAsync(Settings.Default.Address);
183 if (!getHostAddresses.Any())
184 throw new Exception();
185  
186 address = getHostAddresses.FirstOrDefault();
1 office 187 }
188 catch
189 {
190 throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error,
191 $"{Strings.Unable_to_determine_address} {Settings.Default.Address}");
192 }
193  
194 if (!uint.TryParse(Settings.Default.Port, out var port))
195 throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error,
196 $"{Strings.Unable_to_determine_port} {Settings.Default.Port}");
197  
198  
199 if (string.IsNullOrEmpty(Settings.Default.Nick))
200 throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.No_nickname_set);
201  
202 if (string.IsNullOrEmpty(Settings.Default.Password))
203 throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.No_password_set);
204  
205 if (!await MqttCommunication
206 .Start(MqttCommunicationType.Client, address, (int) port, Settings.Default.Nick,
207 Settings.Default.Password,
2 office 208 new[] {Constants.MqttTopic}))
1 office 209 throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.Unable_to_start_client);
210 }
211  
212 private async Task StartServer()
213 {
214 if (!IPAddress.TryParse(Settings.Default.Address, out var address))
215 try
216 {
217 address = Dns.GetHostAddresses(Settings.Default.Address).FirstOrDefault();
218 }
219 catch
220 {
221 throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error,
222 $"{Strings.Unable_to_determine_address} {Settings.Default.Address}");
223 }
224  
225 if (!uint.TryParse(Settings.Default.Port, out var port))
226 throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error,
227 $"{Strings.Unable_to_determine_port} {Settings.Default.Port}");
228  
229  
230 if (string.IsNullOrEmpty(Settings.Default.Nick))
231 throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.No_nickname_set);
232  
233 if (string.IsNullOrEmpty(Settings.Default.Password))
234 throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.No_password_set);
235  
236 // Try to reserve port: try UPnP followed by PMP.
237 await Task.Delay(0, FormCancellationTokenSource.Token).ContinueWith(async _ =>
238 {
239 if (!await Discovery.CreateMapping(DiscoveryType.Upnp, (int) port))
240 await Discovery.CreateMapping(DiscoveryType.Pmp, (int) port);
241 },
242 FormCancellationTokenSource.Token, TaskContinuationOptions.LongRunning, FormTaskScheduler);
243  
244 // Start the MQTT server.
245 if (!await MqttCommunication
246 .Start(MqttCommunicationType.Server, address, (int) port, Settings.Default.Nick,
2 office 247 Settings.Default.Password, new[] {Constants.MqttTopic}))
1 office 248 throw new ToolTippedException(ToolTipIcon.Error, Strings.Network_error, Strings.Unable_to_start_server);
249 }
250  
251 private void DefaultOnSettingsSaving(object sender, CancelEventArgs e)
252 {
253 }
254  
255 private void DefaultOnSettingsLoaded(object sender, SettingsLoadedEventArgs e)
256 {
257 LaunchOnBoot.Set(Settings.Default.LaunchOnBoot);
258 }
259  
260 /// <summary>
261 /// Clean up any resources being used.
262 /// </summary>
263 /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
264 protected override void Dispose(bool disposing)
265 {
2 office 266 // Unbind global key hook.
267 MouseKeyGlobalHook.KeyDown -= MouseKeyGlobalHookOnKeyDown;
268  
269 // Unbind message synchronizer.
270 ChatMessageSynchronizer.OnMessageReceived -= OnMessageReceived;
271  
1 office 272 // Unbind settings handlers.
273 Settings.Default.SettingsLoaded -= DefaultOnSettingsLoaded;
274 Settings.Default.SettingsSaving -= DefaultOnSettingsSaving;
275 Settings.Default.PropertyChanged -= DefaultOnPropertyChanged;
276  
277 if (disposing && components != null) components.Dispose();
278 base.Dispose(disposing);
279 }
280  
281 private void OnMouseDown(object sender, MouseEventArgs e)
282 {
283 if (e.Button == MouseButtons.Left)
284 {
2 office 285 DllImports.ReleaseCapture();
286 DllImports.SendMessage(Handle, DllImports.WM_NCLBUTTONDOWN, DllImports.HT_CAPTION, 0);
1 office 287 }
288 }
289  
290 protected override void OnPaintBackground(PaintEventArgs e)
291 {
292 base.OnPaintBackground(e); //comment this out to prevent default painting
293 using (var brush = new SolidBrush(Settings.Default.Color)) //any color you like
294 {
295 e.Graphics.FillRectangle(brush, e.ClipRectangle);
296 }
297 }
298  
299 protected override void WndProc(ref Message m)
300 {
301 const uint WM_NCHITTEST = 0x0084;
302 const uint WM_MOUSEMOVE = 0x0200;
303  
304 const uint HTLEFT = 10;
305 const uint HTRIGHT = 11;
306 const uint HTBOTTOMRIGHT = 17;
307 const uint HTBOTTOM = 15;
308 const uint HTBOTTOMLEFT = 16;
309 const uint HTTOP = 12;
310 const uint HTTOPLEFT = 13;
311 const uint HTTOPRIGHT = 14;
312  
313 const int RESIZE_HANDLE_SIZE = 10;
314 var handled = false;
315 if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
316 {
317 var formSize = Size;
318 var screenPoint = new Point(m.LParam.ToInt32());
319 var clientPoint = PointToClient(screenPoint);
320  
321 var boxes = new Dictionary<uint, Rectangle>
322 {
323 {
324 HTBOTTOMLEFT,
325 new Rectangle(0, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)
326 },
327 {
328 HTBOTTOM,
329 new Rectangle(RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE,
330 formSize.Width - 2 * RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)
331 },
332 {
333 HTBOTTOMRIGHT,
334 new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE,
335 RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)
336 },
337 {
338 HTRIGHT,
339 new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE,
340 formSize.Height - 2 * RESIZE_HANDLE_SIZE)
341 },
342 {
343 HTTOPRIGHT,
344 new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)
345 },
346 {
347 HTTOP,
348 new Rectangle(RESIZE_HANDLE_SIZE, 0, formSize.Width - 2 * RESIZE_HANDLE_SIZE,
349 RESIZE_HANDLE_SIZE)
350 },
351 {HTTOPLEFT, new Rectangle(0, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
352 {
353 HTLEFT,
354 new Rectangle(0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE,
355 formSize.Height - 2 * RESIZE_HANDLE_SIZE)
356 }
357 };
358  
359 foreach (var hitBox in boxes)
360 if (WindowState != FormWindowState.Maximized
361 && hitBox.Value.Contains(clientPoint))
362 {
363 m.Result = (IntPtr) hitBox.Key;
364 handled = true;
365 break;
366 }
367 }
368  
369 if (!handled)
370 base.WndProc(ref m);
371 }
372  
2 office 373 private void HushFocus(object sender, EventArgs e)
1 office 374 {
375 Opacity = .75;
376 }
377  
2 office 378 private void HushUnfocus(object sender, EventArgs e)
1 office 379 {
380 Opacity = .33;
381 }
382  
383 private void ContextMenuOnClickAbout(object sender, EventArgs e)
384 {
385 new About().Show();
386 }
387  
388 private void ContextMenuOnClickQuit(object sender, EventArgs e)
389 {
390 Application.Exit();
391 }
392  
393 private async void MessageTextBoxOnKeyDown(object sender, KeyEventArgs e)
394 {
2 office 395 // Prevent the enter key to be passed to the text box or we get a nasty ding.
396 if (e.KeyCode == Keys.Enter)
397 e.SuppressKeyPress = true;
398  
399 if (e.KeyCode != Keys.Enter)
400 return;
401  
1 office 402 // Do not send messages if the communication is not running.
403 if (!MqttCommunication.Running)
404 return;
405  
406 if (string.IsNullOrEmpty(messageTextBox.Text))
407 return;
408  
409 // Send the message
2 office 410 await ChatMessageSynchronizer.Broadcast($"{messageTextBox.Text}");
1 office 411  
412 messageTextBox.Text = string.Empty;
413 }
414  
415 private void ToolStripNickTextBoxOnTextChanged(object sender, EventArgs e)
416 {
417 Settings.Default.Nick = ((ToolStripTextBox) sender).Text;
418 }
419  
420 private void ToolStripPasswordTextBoxOnTextChanged(object sender, EventArgs e)
421 {
422 Settings.Default.Password = ((ToolStripTextBox) sender).Text;
423 }
424  
425 private void toolStripMenuItem1_Click(object sender, EventArgs e)
426 {
427 Settings.Default.LaunchOnBoot = ((ToolStripMenuItem) sender).Checked;
428 }
429  
430 private void startToolStripMenuItem_CheckStateChanged(object sender, EventArgs e)
431 {
432 var toolStripMenuItem = (ToolStripMenuItem) sender;
433  
434 Settings.Default.Start = toolStripMenuItem.Checked;
435 }
436  
437 private void toolStripComboBox1_SelectedIndexChanged(object sender, EventArgs e)
438 {
439 var toolStripComboBox = (ToolStripComboBox) sender;
440  
441 Settings.Default.Start = false;
442 Settings.Default.Mode = toolStripComboBox.Items[toolStripComboBox.SelectedIndex].ToString();
443 }
444  
445 private void ToolStripAddressTextBoxOnTextChanged(object sender, EventArgs e)
446 {
447 var toolStripMenuItem = (ToolStripTextBox) sender;
448  
449 Settings.Default.Address = toolStripMenuItem.Text;
450 }
451  
452 private void ToolStripPortTextBoxOnTextChanged(object sender, EventArgs e)
453 {
454 var toolStripMenuItem = (ToolStripTextBox) sender;
455  
456 Settings.Default.Port = toolStripMenuItem.Text;
457 }
2 office 458  
459 private void ChatTextBox_TextChanged(object sender, EventArgs e)
460 {
461 var textBox = (TextBox) sender;
462  
463 textBox.ScrollToCaret();
464 }
1 office 465 }
466 }