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