still locking up when disposing, because the log callback is trying to write to the...
[deb_libcec.git] / src / LibCecTray / ui / CECTray.cs
1 /*
2 * This file is part of the libCEC(R) library.
3 *
4 * libCEC(R) is Copyright (C) 2011-2013 Pulse-Eight Limited. All rights reserved.
5 * libCEC(R) is an original work, containing original code.
6 *
7 * libCEC(R) is a trademark of Pulse-Eight Limited.
8 *
9 * This program is dual-licensed; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 *
23 *
24 * Alternatively, you can license this library under a commercial license,
25 * please contact Pulse-Eight Licensing for more information.
26 *
27 * For more information contact:
28 * Pulse-Eight Licensing <license@pulse-eight.com>
29 * http://www.pulse-eight.com/
30 * http://www.pulse-eight.net/
31 */
32
33 using System;
34 using System.Windows.Forms;
35 using CecSharp;
36 using System.IO;
37 using LibCECTray.Properties;
38 using LibCECTray.controller;
39 using LibCECTray.controller.applications;
40 using LibCECTray.settings;
41 using Microsoft.Win32;
42 using System.Security.Permissions;
43 using System.Runtime.InteropServices;
44 using System.Threading;
45
46 namespace LibCECTray.ui
47 {
48 /// <summary>
49 /// The tab pages in this application
50 /// </summary>
51 internal enum ConfigTab
52 {
53 Configuration,
54 KeyConfiguration,
55 Tester,
56 Log,
57 WMC,
58 XBMC
59 }
60
61 class AsyncDisconnect
62 {
63 public AsyncDisconnect(CECController controller)
64 {
65 _controller = controller;
66 }
67
68 public void Process()
69 {
70 _controller.Close();
71 }
72
73 private CECController _controller;
74 }
75
76 /// <summary>
77 /// Main LibCecTray GUI
78 /// </summary>
79 partial class CECTray : AsyncForm
80 {
81 public CECTray()
82 {
83 Text = Resources.app_name;
84 InitializeComponent();
85 VisibleChanged += delegate
86 {
87 if (!Visible)
88 OnHide();
89 else
90 OnShow();
91 };
92
93 SystemEvents.SessionEnding += new SessionEndingEventHandler(OnSessionEnding);
94 }
95
96 public void OnSessionEnding(object sender, SessionEndingEventArgs e)
97 {
98 Controller.CECActions.SuppressUpdates = true;
99 Controller.Close();
100 }
101
102 #region Power state change window messages
103 private const int WM_POWERBROADCAST = 0x0218;
104 private const int PBT_APMSUSPEND = 0x0004;
105 private const int PBT_APMRESUMESUSPEND = 0x0007;
106 private const int PBT_APMRESUMECRITICAL = 0x0006;
107 private const int PBT_APMRESUMEAUTOMATIC = 0x0012;
108 private const int PBT_POWERSETTINGCHANGE = 0x8013;
109 private static Guid GUID_SYSTEM_AWAYMODE = new Guid("98a7f580-01f7-48aa-9c0f-44352c29e5c0");
110
111 [StructLayout(LayoutKind.Sequential, Pack = 4)]
112 internal struct POWERBROADCAST_SETTING
113 {
114 public Guid PowerSetting;
115 public uint DataLength;
116 public byte Data;
117 }
118 #endregion
119
120 /// <summary>
121 /// Check for power state changes, and pass up when it's something we don't care about
122 /// </summary>
123 /// <param name="msg">The incoming window message</param>
124 protected override void WndProc(ref Message msg)
125 {
126 if (msg.Msg == WM_POWERBROADCAST)
127 {
128 switch (msg.WParam.ToInt32())
129 {
130 case PBT_APMSUSPEND:
131 OnSleep();
132 return;
133
134 case PBT_APMRESUMESUSPEND:
135 case PBT_APMRESUMECRITICAL:
136 case PBT_APMRESUMEAUTOMATIC:
137 OnWake();
138 return;
139
140 case PBT_POWERSETTINGCHANGE:
141 {
142 POWERBROADCAST_SETTING pwr = (POWERBROADCAST_SETTING)Marshal.PtrToStructure(msg.LParam, typeof(POWERBROADCAST_SETTING));
143 if (pwr.PowerSetting == GUID_SYSTEM_AWAYMODE && pwr.DataLength == Marshal.SizeOf(typeof(Int32)))
144 {
145 switch (pwr.Data)
146 {
147 case 0:
148 // do _not_ wake the pc when away mode is deactivated
149 //OnWake();
150 //return;
151 case 1:
152 OnSleep();
153 return;
154 default:
155 break;
156 }
157 }
158 }
159 break;
160 default:
161 break;
162 }
163 }
164
165 // pass up when not handled
166 base.WndProc(ref msg);
167 }
168
169 private void OnWake()
170 {
171 Controller.Initialise();
172 }
173
174 private void OnSleep()
175 {
176 Controller.Close();
177 }
178
179 public override sealed string Text
180 {
181 get { return base.Text; }
182 set { base.Text = value; }
183 }
184
185 public void Initialise()
186 {
187 Controller.Initialise();
188 }
189
190 protected override void Dispose(bool disposing)
191 {
192 Hide();
193 if (disposing)
194 {
195 Controller.CECActions.SuppressUpdates = true;
196 AsyncDisconnect dc = new AsyncDisconnect(Controller);
197 (new Thread(dc.Process)).Start();
198 }
199 if (disposing && (components != null))
200 {
201 components.Dispose();
202 }
203 base.Dispose(disposing);
204 }
205
206 #region Configuration tab
207 /// <summary>
208 /// Replaces the gui controls by the ones that are bound to the settings.
209 /// this is a fugly way to do it, but the gui designer doesn't allow us to ref CECSettings, since it uses symbols from LibCecSharp
210 /// </summary>
211 public void InitialiseSettingsComponent(CECSettings settings)
212 {
213 settings.WakeDevices.ReplaceControls(this, Configuration.Controls, lWakeDevices, cbWakeDevices);
214 settings.PowerOffDevices.ReplaceControls(this, Configuration.Controls, lPowerOff, cbPowerOffDevices);
215 settings.OverridePhysicalAddress.ReplaceControls(this, Configuration.Controls, cbOverrideAddress);
216 settings.OverrideTVVendor.ReplaceControls(this, Configuration.Controls, cbVendorOverride);
217 settings.PhysicalAddress.ReplaceControls(this, Configuration.Controls, tbPhysicalAddress);
218 settings.HDMIPort.ReplaceControls(this, Configuration.Controls, lPortNumber, cbPortNumber);
219 settings.ConnectedDevice.ReplaceControls(this, Configuration.Controls, lConnectedDevice, cbConnectedDevice);
220 settings.ActivateSource.ReplaceControls(this, Configuration.Controls, cbActivateSource);
221 settings.DeviceType.ReplaceControls(this, Configuration.Controls, lDeviceType, cbDeviceType);
222 settings.TVVendor.ReplaceControls(this, Configuration.Controls, cbVendorId);
223 settings.StartHidden.ReplaceControls(this, Configuration.Controls, cbStartMinimised);
224 }
225
226 private void BSaveClick(object sender, EventArgs e)
227 {
228 Controller.PersistSettings();
229 }
230
231 private void BReloadConfigClick(object sender, EventArgs e)
232 {
233 Controller.ResetDefaultSettings();
234 }
235 #endregion
236
237 #region CEC Tester tab
238 delegate void SetActiveDevicesCallback(string[] activeDevices);
239 public void SetActiveDevices(string[] activeDevices)
240 {
241 if (cbCommandDestination.InvokeRequired)
242 {
243 SetActiveDevicesCallback d = SetActiveDevices;
244 try
245 {
246 Invoke(d, new object[] { activeDevices });
247 }
248 catch (Exception) { }
249 }
250 else
251 {
252 cbCommandDestination.Items.Clear();
253 foreach (string item in activeDevices)
254 cbCommandDestination.Items.Add(item);
255 }
256 }
257
258 delegate CecLogicalAddress GetTargetDeviceCallback();
259 private CecLogicalAddress GetTargetDevice()
260 {
261 if (cbCommandDestination.InvokeRequired)
262 {
263 GetTargetDeviceCallback d = GetTargetDevice;
264 CecLogicalAddress retval = CecLogicalAddress.Unknown;
265 try
266 {
267 retval = (CecLogicalAddress)Invoke(d, new object[] { });
268 }
269 catch (Exception) { }
270 return retval;
271 }
272
273 return CECSettingLogicalAddresses.GetLogicalAddressFromString(cbCommandDestination.Text);
274 }
275
276 private void BSendImageViewOnClick(object sender, EventArgs e)
277 {
278 Controller.CECActions.SendImageViewOn(GetTargetDevice());
279 }
280
281 private void BStandbyClick(object sender, EventArgs e)
282 {
283 Controller.CECActions.SendStandby(GetTargetDevice());
284 }
285
286 private void BScanClick(object sender, EventArgs e)
287 {
288 Controller.CECActions.ShowDeviceInfo(GetTargetDevice());
289 }
290
291 private void BActivateSourceClick(object sender, EventArgs e)
292 {
293 Controller.CECActions.ActivateSource(GetTargetDevice());
294 }
295
296 private void CbCommandDestinationSelectedIndexChanged(object sender, EventArgs e)
297 {
298 bool enableVolumeButtons = (GetTargetDevice() == CecLogicalAddress.AudioSystem);
299 bVolUp.Enabled = enableVolumeButtons;
300 bVolDown.Enabled = enableVolumeButtons;
301 bMute.Enabled = enableVolumeButtons;
302 bActivateSource.Enabled = (GetTargetDevice() != CecLogicalAddress.Broadcast);
303 bScan.Enabled = (GetTargetDevice() != CecLogicalAddress.Broadcast);
304 }
305
306 private void BVolUpClick(object sender, EventArgs e)
307 {
308 Controller.Lib.VolumeUp(true);
309 }
310
311 private void BVolDownClick(object sender, EventArgs e)
312 {
313 Controller.Lib.VolumeDown(true);
314 }
315
316 private void BMuteClick(object sender, EventArgs e)
317 {
318 Controller.Lib.MuteAudio(true);
319 }
320
321 private void BRescanDevicesClick(object sender, EventArgs e)
322 {
323 Controller.CECActions.RescanDevices();
324 }
325 #endregion
326
327 #region Log tab
328 delegate void UpdateLogCallback();
329 private void UpdateLog()
330 {
331 if (tbLog.InvokeRequired)
332 {
333 UpdateLogCallback d = UpdateLog;
334 try
335 {
336 Invoke(d, new object[] { });
337 }
338 catch (Exception) { }
339 }
340 else
341 {
342 tbLog.Text = _log;
343 tbLog.Select(tbLog.Text.Length, 0);
344 tbLog.ScrollToCaret();
345 }
346 }
347
348 public void AddLogMessage(CecLogMessage message)
349 {
350 string strLevel = "";
351 bool display = false;
352 switch (message.Level)
353 {
354 case CecLogLevel.Error:
355 strLevel = "ERROR: ";
356 display = cbLogError.Checked;
357 break;
358 case CecLogLevel.Warning:
359 strLevel = "WARNING: ";
360 display = cbLogWarning.Checked;
361 break;
362 case CecLogLevel.Notice:
363 strLevel = "NOTICE: ";
364 display = cbLogNotice.Checked;
365 break;
366 case CecLogLevel.Traffic:
367 strLevel = "TRAFFIC: ";
368 display = cbLogTraffic.Checked;
369 break;
370 case CecLogLevel.Debug:
371 strLevel = "DEBUG: ";
372 display = cbLogDebug.Checked;
373 break;
374 }
375
376 if (display)
377 {
378 string strLog = string.Format("{0} {1,16} {2}", strLevel, message.Time, message.Message) + Environment.NewLine;
379 AddLogMessage(strLog);
380 }
381 }
382
383 public void AddLogMessage(string message)
384 {
385 _log += message;
386
387 if (_selectedTab == ConfigTab.Log)
388 UpdateLog();
389 }
390
391 private void BClearLogClick(object sender, EventArgs e)
392 {
393 _log = string.Empty;
394 UpdateLog();
395 }
396
397 private void BSaveLogClick(object sender, EventArgs e)
398 {
399 SaveFileDialog dialog = new SaveFileDialog
400 {
401 Title = Resources.where_do_you_want_to_store_the_log,
402 InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
403 FileName = Resources.cec_log_filename,
404 Filter = Resources.cec_log_filter,
405 FilterIndex = 1
406 };
407
408 if (dialog.ShowDialog() == DialogResult.OK)
409 {
410 FileStream fs = (FileStream)dialog.OpenFile();
411 if (!fs.CanWrite)
412 {
413 MessageBox.Show(string.Format(Resources.cannot_open_for_writing, dialog.FileName), Resources.app_name, MessageBoxButtons.OK, MessageBoxIcon.Error);
414 }
415 else
416 {
417 StreamWriter writer = new StreamWriter(fs);
418 writer.Write(_log);
419 writer.Close();
420 fs.Close();
421 fs.Dispose();
422 MessageBox.Show(string.Format(Resources.log_stored_as, dialog.FileName), Resources.app_name, MessageBoxButtons.OK, MessageBoxIcon.Information);
423 }
424 }
425 }
426 #endregion
427
428 #region Tray icon and window controls
429 private void HideToolStripMenuItemClick(object sender, EventArgs e)
430 {
431 ShowHideToggle();
432 }
433
434 private void CloseToolStripMenuItemClick(object sender, EventArgs e)
435 {
436 Dispose();
437 }
438
439 private void AboutToolStripMenuItemClick(object sender, EventArgs e)
440 {
441 (new About(Controller.LibServerVersion, Controller.LibClientVersion, Controller.LibInfo)).ShowDialog();
442 }
443
444 private void AdvancedModeToolStripMenuItemClick(object sender, EventArgs e)
445 {
446 Controller.Settings.AdvancedMode.Value = !advancedModeToolStripMenuItem.Checked;
447 ShowHideAdvanced(!advancedModeToolStripMenuItem.Checked);
448 }
449
450 private void BCancelClick(object sender, EventArgs e)
451 {
452 Dispose();
453 }
454
455 private void TrayIconClick(object sender, EventArgs e)
456 {
457 if (e is MouseEventArgs && (e as MouseEventArgs).Button == MouseButtons.Left)
458 ShowHideToggle();
459 }
460
461 public void OnHide()
462 {
463 ShowInTaskbar = false;
464 Visible = false;
465 tsMenuShowHide.Text = Resources.show;
466 }
467
468 public void OnShow()
469 {
470 ShowInTaskbar = true;
471 WindowState = FormWindowState.Normal;
472 Activate();
473 tsMenuShowHide.Text = Resources.hide;
474 }
475
476 private void ShowHideToggle()
477 {
478 if (Visible && WindowState != FormWindowState.Minimized)
479 {
480 Controller.Settings.StartHidden.Value = true;
481 Hide();
482 }
483 else
484 {
485 Controller.Settings.StartHidden.Value = false;
486 Show();
487 }
488 }
489
490 private void TsMenuCloseClick(object sender, EventArgs e)
491 {
492 Dispose();
493 }
494
495 private void CECTrayResize(object sender, EventArgs e)
496 {
497 if (WindowState == FormWindowState.Minimized)
498 Hide();
499 else
500 Show();
501 }
502
503 private void TsMenuShowHideClick(object sender, EventArgs e)
504 {
505 ShowHideToggle();
506 }
507
508 public void ShowHideAdvanced(bool setTo)
509 {
510 if (setTo)
511 {
512 tsAdvanced.Checked = true;
513 advancedModeToolStripMenuItem.Checked = true;
514 SuspendLayout();
515 if (!tabPanel.Controls.Contains(tbTestCommands))
516 TabControls.Add(tbTestCommands);
517 if (!tabPanel.Controls.Contains(LogOutput))
518 TabControls.Add(LogOutput);
519 ResumeLayout();
520 }
521 else
522 {
523 tsAdvanced.Checked = false;
524 advancedModeToolStripMenuItem.Checked = false;
525 SuspendLayout();
526 tabPanel.Controls.Remove(tbTestCommands);
527 tabPanel.Controls.Remove(LogOutput);
528 ResumeLayout();
529 }
530 }
531
532 private void TsAdvancedClick(object sender, EventArgs e)
533 {
534 Controller.Settings.AdvancedMode.Value = !tsAdvanced.Checked;
535 ShowHideAdvanced(!tsAdvanced.Checked);
536 }
537
538 public void SetStatusText(string status)
539 {
540 SetControlText(lStatus, status);
541 }
542
543 public void SetProgressBar(int progress, bool visible)
544 {
545 SetControlVisible(pProgress, visible);
546 SetProgressValue(pProgress, progress);
547 }
548
549 public void SetControlsEnabled(bool val)
550 {
551 //main tab
552 SetControlEnabled(bClose, val);
553 SetControlEnabled(bSaveConfig, val);
554 SetControlEnabled(bReloadConfig, val);
555
556 //tester tab
557 SetControlEnabled(bRescanDevices, val);
558 SetControlEnabled(bSendImageViewOn, val);
559 SetControlEnabled(bStandby, val);
560 SetControlEnabled(bActivateSource, val);
561 SetControlEnabled(bScan, val);
562
563 bool enableVolumeButtons = (GetTargetDevice() == CecLogicalAddress.AudioSystem) && val;
564 SetControlEnabled(bVolUp, enableVolumeButtons);
565 SetControlEnabled(bVolDown, enableVolumeButtons);
566 SetControlEnabled(bMute, enableVolumeButtons);
567 }
568
569 private void TabControl1SelectedIndexChanged(object sender, EventArgs e)
570 {
571 switch (tabPanel.TabPages[tabPanel.SelectedIndex].Name)
572 {
573 case "tbTestCommands":
574 _selectedTab = ConfigTab.Tester;
575 break;
576 case "LogOutput":
577 _selectedTab = ConfigTab.Log;
578 UpdateLog();
579 break;
580 default:
581 _selectedTab = ConfigTab.Configuration;
582 break;
583 }
584 }
585 #endregion
586
587 #region Class members
588 private ConfigTab _selectedTab = ConfigTab.Configuration;
589 private string _log = string.Empty;
590 private CECController _controller;
591 public CECController Controller
592 {
593 get
594 {
595 return _controller ?? (_controller = new CECController(this));
596 }
597 }
598 public Control.ControlCollection TabControls
599 {
600 get { return tabPanel.Controls; }
601 }
602 public string SelectedTabName
603 {
604 get { return GetSelectedTabName(tabPanel, tabPanel.TabPages); }
605 }
606 #endregion
607
608 private void AddNewApplicationToolStripMenuItemClick(object sender, EventArgs e)
609 {
610 ConfigureApplication appConfig = new ConfigureApplication(Controller.Settings, Controller);
611 Controller.DisplayDialog(appConfig, false);
612 }
613 }
614 }