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