Server/FormMain.cs
author StephaneLenclud
Mon, 15 Aug 2016 12:11:26 +0200
changeset 233 2b9541e54f7d
parent 232 5a739e2e5255
child 234 0c75dec19d39
permissions -rw-r--r--
Adding Harmony tab.
     1 //
     2 // Copyright (C) 2014-2015 Stéphane Lenclud.
     3 //
     4 // This file is part of SharpDisplayManager.
     5 //
     6 // SharpDisplayManager is free software: you can redistribute it and/or modify
     7 // it under the terms of the GNU General Public License as published by
     8 // the Free Software Foundation, either version 3 of the License, or
     9 // (at your option) any later version.
    10 //
    11 // SharpDisplayManager is distributed in the hope that it will be useful,
    12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14 // GNU General Public License for more details.
    15 //
    16 // You should have received a copy of the GNU General Public License
    17 // along with SharpDisplayManager.  If not, see <http://www.gnu.org/licenses/>.
    18 //
    19 
    20 using System;
    21 using System.Collections.Generic;
    22 using System.ComponentModel;
    23 using System.Data;
    24 using System.Drawing;
    25 using System.Linq;
    26 using System.Text;
    27 using System.Threading.Tasks;
    28 using System.Windows.Forms;
    29 using System.IO;
    30 using CodeProject.Dialog;
    31 using System.Drawing.Imaging;
    32 using System.ServiceModel;
    33 using System.Threading;
    34 using System.Diagnostics;
    35 using System.Deployment.Application;
    36 using System.Reflection;
    37 //NAudio
    38 using NAudio.CoreAudioApi;
    39 using NAudio.CoreAudioApi.Interfaces;
    40 using System.Runtime.InteropServices;
    41 using CecSharp;
    42 //Network
    43 using NETWORKLIST;
    44 //
    45 using SharpDisplayClient;
    46 using SharpDisplay;
    47 using MiniDisplayInterop;
    48 using SharpLib.Display;
    49 using SharpLib.Ear;
    50 
    51 namespace SharpDisplayManager
    52 {
    53     //Types declarations
    54     public delegate uint ColorProcessingDelegate(int aX, int aY, uint aPixel);
    55 
    56     public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
    57 
    58     //Delegates are used for our thread safe method
    59     public delegate void AddClientDelegate(string aSessionId, ICallback aCallback);
    60 
    61     public delegate void RemoveClientDelegate(string aSessionId);
    62 
    63     public delegate void SetFieldDelegate(string SessionId, DataField aField);
    64 
    65     public delegate void SetFieldsDelegate(string SessionId, System.Collections.Generic.IList<DataField> aFields);
    66 
    67     public delegate void SetLayoutDelegate(string SessionId, TableLayout aLayout);
    68 
    69     public delegate void SetClientNameDelegate(string aSessionId, string aName);
    70 
    71     public delegate void SetClientPriorityDelegate(string aSessionId, uint aPriority);
    72 
    73     public delegate void PlainUpdateDelegate();
    74 
    75     public delegate void WndProcDelegate(ref Message aMessage);
    76 
    77     /// <summary>
    78     /// Our Display manager main form
    79     /// </summary>
    80     [System.ComponentModel.DesignerCategory("Form")]
    81     public partial class FormMain : FormMainHid, IMMNotificationClient
    82     {
    83         //public ManagerEventAction iManager = new ManagerEventAction();        
    84         DateTime LastTickTime;
    85         Display iDisplay;
    86         System.Drawing.Bitmap iBmp;
    87         bool iCreateBitmap; //Workaround render to bitmap issues when minimized
    88         ServiceHost iServiceHost;
    89         // Our collection of clients sorted by session id.
    90         public Dictionary<string, ClientData> iClients;
    91         // The name of the client which informations are currently displayed.
    92         public string iCurrentClientSessionId;
    93         ClientData iCurrentClientData;
    94         //
    95         public bool iClosing;
    96         //
    97         public bool iSkipFrameRendering;
    98         //Function pointer for pixel color filtering
    99         ColorProcessingDelegate iColorFx;
   100         //Function pointer for pixel X coordinate intercept
   101         CoordinateTranslationDelegate iScreenX;
   102         //Function pointer for pixel Y coordinate intercept
   103         CoordinateTranslationDelegate iScreenY;
   104         //NAudio
   105         private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
   106         private MMDevice iMultiMediaDevice;
   107         //Network
   108         private NetworkManager iNetworkManager;
   109 
   110         /// <summary>
   111         /// CEC - Consumer Electronic Control.
   112         /// Notably used to turn TV on and off as Windows broadcast monitor on and off notifications.
   113         /// </summary>
   114         private ConsumerElectronicControl iCecManager;
   115 
   116         /// <summary>
   117         /// Manage run when Windows startup option
   118         /// </summary>
   119         private StartupManager iStartupManager;
   120 
   121         /// <summary>
   122         /// System notification icon used to hide our application from the task bar.
   123         /// </summary>
   124         private SharpLib.Notification.Control iNotifyIcon;
   125 
   126         /// <summary>
   127         /// System recording notification icon.
   128         /// </summary>
   129         private SharpLib.Notification.Control iRecordingNotification;
   130 
   131         /// <summary>
   132         /// 
   133         /// </summary>
   134         RichTextBoxTextWriter iWriter;
   135 
   136 
   137         /// <summary>
   138         /// Allow user to receive window messages;
   139         /// </summary>
   140         public event WndProcDelegate OnWndProc;
   141 
   142         public FormMain()
   143         {
   144             ManagerEventAction.Current = Properties.Settings.Default.Events;
   145             if (ManagerEventAction.Current == null)
   146             {
   147                 //No actions in our settings yet
   148                 ManagerEventAction.Current = new ManagerEventAction();
   149                 Properties.Settings.Default.Events = ManagerEventAction.Current;
   150             }
   151             else
   152             {
   153                 //We loaded actions from our settings
   154                 //We need to hook them with corresponding events
   155                 ManagerEventAction.Current.Init();
   156             }
   157             iSkipFrameRendering = false;
   158             iClosing = false;
   159             iCurrentClientSessionId = "";
   160             iCurrentClientData = null;
   161             LastTickTime = DateTime.Now;
   162             //Instantiate our display and register for events notifications
   163             iDisplay = new Display();
   164             iDisplay.OnOpened += OnDisplayOpened;
   165             iDisplay.OnClosed += OnDisplayClosed;
   166             //
   167             iClients = new Dictionary<string, ClientData>();
   168             iStartupManager = new StartupManager();
   169             iNotifyIcon = new SharpLib.Notification.Control();
   170             iRecordingNotification = new SharpLib.Notification.Control();
   171 
   172             //Have our designer initialize its controls
   173             InitializeComponent();
   174 
   175             //Redirect console output
   176             iWriter = new RichTextBoxTextWriter(richTextBoxLogs);
   177             Console.SetOut(iWriter);
   178 
   179             //Populate device types
   180             PopulateDeviceTypes();
   181 
   182             //Populate optical drives
   183             PopulateOpticalDrives();
   184 
   185             //Initial status update 
   186             UpdateStatus();
   187 
   188             //We have a bug when drawing minimized and reusing our bitmap
   189             //Though I could not reproduce it on Windows 10
   190             iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
   191                 PixelFormat.Format32bppArgb);
   192             iCreateBitmap = false;
   193 
   194             //Minimize our window if desired
   195             if (Properties.Settings.Default.StartMinimized)
   196             {
   197                 WindowState = FormWindowState.Minimized;
   198             }
   199 
   200         }
   201 
   202         /// <summary>
   203         ///
   204         /// </summary>
   205         /// <param name="sender"></param>
   206         /// <param name="e"></param>
   207         private void MainForm_Load(object sender, EventArgs e)
   208         {
   209             //Check if we are running a Click Once deployed application
   210             if (ApplicationDeployment.IsNetworkDeployed)
   211             {
   212                 //This is a proper Click Once installation, fetch and show our version number
   213                 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
   214             }
   215             else
   216             {
   217                 //Not a proper Click Once installation, assuming development build then
   218                 this.Text += " - development";
   219             }
   220 
   221             //NAudio
   222             iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
   223             iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
   224             UpdateAudioDeviceAndMasterVolumeThreadSafe();
   225 
   226             //Network
   227             iNetworkManager = new NetworkManager();
   228             iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
   229             UpdateNetworkStatus();
   230 
   231             //CEC
   232             iCecManager = new ConsumerElectronicControl();
   233             OnWndProc += iCecManager.OnWndProc;
   234             ResetCec();
   235 
   236             //Harmony
   237             ResetHarmony();
   238 
   239             //Setup Events
   240             PopulateEventsTreeView();
   241 
   242             //Setup notification icon
   243             SetupTrayIcon();
   244 
   245             //Setup recording notification
   246             SetupRecordingNotification();
   247 
   248             // To make sure start up with minimize to tray works
   249             if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
   250             {
   251                 Visible = false;
   252             }
   253 
   254 #if !DEBUG
   255     //When not debugging we want the screen to be empty until a client takes over
   256 			ClearLayout();
   257 #else
   258             //When developing we want at least one client for testing
   259             StartNewClient("abcdefghijklmnopqrst-0123456789", "ABCDEFGHIJKLMNOPQRST-0123456789");
   260 #endif
   261 
   262             //Open display connection on start-up if needed
   263             if (Properties.Settings.Default.DisplayConnectOnStartup)
   264             {
   265                 OpenDisplayConnection();
   266             }
   267 
   268             //Start our server so that we can get client requests
   269             StartServer();
   270 
   271             //Register for HID events
   272             RegisterHidDevices();
   273 
   274             //Start Idle client if needed
   275             if (Properties.Settings.Default.StartIdleClient)
   276             {
   277                 StartIdleClient();
   278             }
   279         }
   280 
   281         /// <summary>
   282         /// Called when our display is opened.
   283         /// </summary>
   284         /// <param name="aDisplay"></param>
   285         private void OnDisplayOpened(Display aDisplay)
   286         {
   287             //Make sure we resume frame rendering
   288             iSkipFrameRendering = false;
   289 
   290             //Set our screen size now that our display is connected
   291             //Our panelDisplay is the container of our tableLayoutPanel
   292             //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
   293             //panelDisplay needs an extra 2 pixels for borders on each sides
   294             //tableLayoutPanel will eventually be the exact size of our display
   295             Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
   296             panelDisplay.Size = size;
   297 
   298             //Our display was just opened, update our UI
   299             UpdateStatus();
   300             //Initiate asynchronous request
   301             iDisplay.RequestFirmwareRevision();
   302 
   303             //Audio
   304             UpdateMasterVolumeThreadSafe();
   305             //Network
   306             UpdateNetworkStatus();
   307 
   308 #if DEBUG
   309             //Testing icon in debug, no arm done if icon not supported
   310             //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
   311             //iDisplay.SetAllIconsStatus(2);
   312 #endif
   313 
   314         }
   315 
   316         /// <summary>
   317         /// Populate tree view with events and actions
   318         /// </summary>
   319         private void PopulateEventsTreeView()
   320         {
   321             //Disable action buttons
   322             buttonActionAdd.Enabled = false;
   323             buttonActionDelete.Enabled = false;
   324 
   325             Event currentEvent = CurrentEvent();
   326             SharpLib.Ear.Action currentAction = CurrentAction();
   327             TreeNode treeNodeToSelect = null;
   328             
   329             //Reset our tree
   330             iTreeViewEvents.Nodes.Clear();
   331             //Populate registered events
   332             foreach (SharpLib.Ear.Event e in ManagerEventAction.Current.Events)
   333             {
   334                 //Create our event node
   335                 TreeNode eventNode = iTreeViewEvents.Nodes.Add(e.Name);
   336                 eventNode.Tag = e; //For easy access to our event
   337                 if (!e.Enabled)
   338                 {
   339                     //Dim our nodes if disabled
   340                     eventNode.ForeColor = Color.DimGray;
   341                 }
   342 
   343                 //Add event description as child node
   344                 eventNode.Nodes.Add(e.Description).ForeColor = eventNode.ForeColor; 
   345                 //Create child node for actions root
   346                 TreeNode actionsNodes = eventNode.Nodes.Add("Actions");
   347                 actionsNodes.ForeColor = eventNode.ForeColor;
   348 
   349                 // Add our actions for that event
   350                 foreach (SharpLib.Ear.Action a in e.Actions)
   351                 {
   352                     TreeNode actionNode = actionsNodes.Nodes.Add(a.Brief());
   353                     actionNode.Tag = a;
   354                     actionNode.ForeColor = eventNode.ForeColor;
   355                     if (a == currentAction)
   356                     {
   357                         treeNodeToSelect = actionNode;
   358                     }
   359                 }
   360             }
   361 
   362             iTreeViewEvents.ExpandAll();
   363             SelectEvent(currentEvent);
   364             
   365             if (treeNodeToSelect != null)
   366             {
   367                 iTreeViewEvents.SelectedNode = treeNodeToSelect;
   368             }
   369             else if (iTreeViewEvents.SelectedNode != null && iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) > 0)
   370             {
   371                 //Select the last action if any 
   372                 iTreeViewEvents.SelectedNode =
   373                     iTreeViewEvents.SelectedNode.Nodes[1].Nodes[
   374                         iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) - 1];
   375             }
   376             else if (iTreeViewEvents.SelectedNode == null && iTreeViewEvents.Nodes.Count > 0)
   377             {
   378                 //Still no selected node select the first one then
   379                 iTreeViewEvents.SelectedNode = iTreeViewEvents.Nodes[0];
   380             }
   381 
   382 
   383             UpdateEventView();
   384         }
   385 
   386         /// <summary>
   387         /// Called when our display is closed.
   388         /// </summary>
   389         /// <param name="aDisplay"></param>
   390         private void OnDisplayClosed(Display aDisplay)
   391         {
   392             //Our display was just closed, update our UI consequently
   393             UpdateStatus();
   394         }
   395 
   396         public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
   397         {
   398             //Update network status
   399             UpdateNetworkStatus();
   400         }
   401 
   402         /// <summary>
   403         /// Update our Network Status
   404         /// </summary>
   405         private void UpdateNetworkStatus()
   406         {
   407             if (iDisplay.IsOpen())
   408             {
   409                 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet,
   410                     iNetworkManager.NetworkListManager.IsConnectedToInternet);
   411                 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
   412             }
   413         }
   414 
   415 
   416         int iLastNetworkIconIndex = 0;
   417         int iUpdateCountSinceLastNetworkAnimation = 0;
   418 
   419         /// <summary>
   420         /// 
   421         /// </summary>
   422         private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
   423         {
   424             iUpdateCountSinceLastNetworkAnimation++;
   425             iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation%4;
   426 
   427             if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected &&
   428                 iUpdateCountSinceLastNetworkAnimation == 0)
   429             {
   430                 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
   431                 if (iconCount <= 0)
   432                 {
   433                     //Prevents div by zero and other undefined behavior
   434                     return;
   435                 }
   436                 iLastNetworkIconIndex++;
   437                 iLastNetworkIconIndex = iLastNetworkIconIndex%(iconCount*2);
   438                 for (int i = 0; i < iconCount; i++)
   439                 {
   440                     if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) &&
   441                         !(i == 1 && iLastNetworkIconIndex > 4))
   442                     {
   443                         iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
   444                     }
   445                     else
   446                     {
   447                         iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
   448                     }
   449                 }
   450             }
   451         }
   452 
   453 
   454 
   455         /// <summary>
   456         /// Receive volume change notification and reflect changes on our slider.
   457         /// </summary>
   458         /// <param name="data"></param>
   459         public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
   460         {
   461             UpdateMasterVolumeThreadSafe();
   462         }
   463 
   464         /// <summary>
   465         /// Update master volume when user moves our slider.
   466         /// </summary>
   467         /// <param name="sender"></param>
   468         /// <param name="e"></param>
   469         private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
   470         {
   471             //Just like Windows Volume Mixer we unmute if the volume is adjusted
   472             iMultiMediaDevice.AudioEndpointVolume.Mute = false;
   473             //Set volume level according to our volume slider new position
   474             iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value/100.0f;
   475         }
   476 
   477 
   478         /// <summary>
   479         /// Mute check box changed.
   480         /// </summary>
   481         /// <param name="sender"></param>
   482         /// <param name="e"></param>
   483         private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
   484         {
   485             iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
   486         }
   487 
   488         /// <summary>
   489         /// Device State Changed
   490         /// </summary>
   491         public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId,
   492             [MarshalAs(UnmanagedType.I4)] DeviceState newState)
   493         {
   494         }
   495 
   496         /// <summary>
   497         /// Device Added
   498         /// </summary>
   499         public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId)
   500         {
   501         }
   502 
   503         /// <summary>
   504         /// Device Removed
   505         /// </summary>
   506         public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId)
   507         {
   508         }
   509 
   510         /// <summary>
   511         /// Default Device Changed
   512         /// </summary>
   513         public void OnDefaultDeviceChanged(DataFlow flow, Role role,
   514             [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
   515         {
   516             if (role == Role.Multimedia && flow == DataFlow.Render)
   517             {
   518                 UpdateAudioDeviceAndMasterVolumeThreadSafe();
   519             }
   520         }
   521 
   522         /// <summary>
   523         /// Property Value Changed
   524         /// </summary>
   525         /// <param name="pwstrDeviceId"></param>
   526         /// <param name="key"></param>
   527         public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key)
   528         {
   529         }
   530 
   531 
   532 
   533 
   534         /// <summary>
   535         /// Update master volume indicators based our current system states.
   536         /// This typically includes volume levels and mute status.
   537         /// </summary>
   538         private void UpdateMasterVolumeThreadSafe()
   539         {
   540             if (this.InvokeRequired)
   541             {
   542                 //Not in the proper thread, invoke ourselves
   543                 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
   544                 this.Invoke(d, new object[] {});
   545                 return;
   546             }
   547 
   548             //Update volume slider
   549             float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
   550             trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar*100);
   551             //Update mute checkbox
   552             checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
   553 
   554             //If our display connection is open we need to update its icons
   555             if (iDisplay.IsOpen())
   556             {
   557                 //First take care our our volume level icons
   558                 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
   559                 if (volumeIconCount > 0)
   560                 {
   561                     //Compute current volume level from system level and the number of segments in our display volume bar.
   562                     //That tells us how many segments in our volume bar needs to be turned on.
   563                     float currentVolume = volumeLevelScalar*volumeIconCount;
   564                     int segmentOnCount = Convert.ToInt32(currentVolume);
   565                     //Check if our segment count was rounded up, this will later be used for half brightness segment
   566                     bool roundedUp = segmentOnCount > currentVolume;
   567 
   568                     for (int i = 0; i < volumeIconCount; i++)
   569                     {
   570                         if (i < segmentOnCount)
   571                         {
   572                             //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
   573                             if (i == segmentOnCount - 1 && roundedUp)
   574                             {
   575                                 //Half brightness
   576                                 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
   577                                     (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1)/2);
   578                             }
   579                             else
   580                             {
   581                                 //Full brightness
   582                                 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
   583                                     iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
   584                             }
   585                         }
   586                         else
   587                         {
   588                             iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
   589                         }
   590                     }
   591                 }
   592 
   593                 //Take care of our mute icon
   594                 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
   595             }
   596 
   597         }
   598 
   599         /// <summary>
   600         /// 
   601         /// </summary>
   602         private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
   603         {
   604             if (this.InvokeRequired)
   605             {
   606                 //Not in the proper thread, invoke ourselves
   607                 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
   608                 this.Invoke(d, new object[] {});
   609                 return;
   610             }
   611 
   612             //We are in the correct thread just go ahead.
   613             try
   614             {
   615                 //Get our master volume            
   616                 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
   617                 //Update our label
   618                 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
   619 
   620                 //Show our volume in our track bar
   621                 UpdateMasterVolumeThreadSafe();
   622 
   623                 //Register to get volume modifications
   624                 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
   625                 //
   626                 trackBarMasterVolume.Enabled = true;
   627             }
   628             catch (Exception ex)
   629             {
   630                 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
   631                 Debug.WriteLine(ex.ToString());
   632                 //Something went wrong S/PDIF device ca throw exception I guess
   633                 trackBarMasterVolume.Enabled = false;
   634             }
   635         }
   636 
   637         /// <summary>
   638         /// 
   639         /// </summary>
   640         private void PopulateDeviceTypes()
   641         {
   642             int count = Display.TypeCount();
   643 
   644             for (int i = 0; i < count; i++)
   645             {
   646                 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type) i));
   647             }
   648         }
   649 
   650         /// <summary>
   651         /// 
   652         /// </summary>
   653         private void PopulateOpticalDrives()
   654         {
   655             //Reset our list of drives
   656             comboBoxOpticalDrives.Items.Clear();
   657             comboBoxOpticalDrives.Items.Add("None");
   658 
   659             //Go through each drives on our system and collected the optical ones in our list
   660             DriveInfo[] allDrives = DriveInfo.GetDrives();
   661             foreach (DriveInfo d in allDrives)
   662             {
   663                 Debug.WriteLine("Drive " + d.Name);
   664                 Debug.WriteLine("  Drive type: {0}", d.DriveType);
   665 
   666                 if (d.DriveType == DriveType.CDRom)
   667                 {
   668                     //This is an optical drive, add it now
   669                     comboBoxOpticalDrives.Items.Add(d.Name.Substring(0, 2));
   670                 }
   671             }
   672         }
   673 
   674         /// <summary>
   675         /// 
   676         /// </summary>
   677         /// <returns></returns>
   678         public string OpticalDriveToEject()
   679         {
   680             return comboBoxOpticalDrives.SelectedItem.ToString();
   681         }
   682 
   683 
   684 
   685         /// <summary>
   686         ///
   687         /// </summary>
   688         private void SetupTrayIcon()
   689         {
   690             iNotifyIcon.Icon = GetIcon("vfd.ico");
   691             iNotifyIcon.Text = "Sharp Display Manager";
   692             iNotifyIcon.Visible = true;
   693 
   694             //Double click toggles visibility - typically brings up the application
   695             iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
   696             {
   697                 SysTrayHideShow();
   698             };
   699 
   700             //Adding a context menu, useful to be able to exit the application
   701             ContextMenu contextMenu = new ContextMenu();
   702             //Context menu item to toggle visibility
   703             MenuItem hideShowItem = new MenuItem("Hide/Show");
   704             hideShowItem.Click += delegate(object obj, EventArgs args)
   705             {
   706                 SysTrayHideShow();
   707             };
   708             contextMenu.MenuItems.Add(hideShowItem);
   709 
   710             //Context menu item separator
   711             contextMenu.MenuItems.Add(new MenuItem("-"));
   712 
   713             //Context menu exit item
   714             MenuItem exitItem = new MenuItem("Exit");
   715             exitItem.Click += delegate(object obj, EventArgs args)
   716             {
   717                 Application.Exit();
   718             };
   719             contextMenu.MenuItems.Add(exitItem);
   720 
   721             iNotifyIcon.ContextMenu = contextMenu;
   722         }
   723 
   724         /// <summary>
   725         ///
   726         /// </summary>
   727         private void SetupRecordingNotification()
   728         {
   729             iRecordingNotification.Icon = GetIcon("record.ico");
   730             iRecordingNotification.Text = "No recording";
   731             iRecordingNotification.Visible = false;
   732         }
   733 
   734         /// <summary>
   735         /// Access icons from embedded resources.
   736         /// </summary>
   737         /// <param name="aName"></param>
   738         /// <returns></returns>
   739         public static Icon GetIcon(string aName)
   740         {
   741             string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
   742             foreach (string name in names)
   743             {
   744                 //Find a resource name that ends with the given name
   745                 if (name.EndsWith(aName))
   746                 {
   747                     using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
   748                     {
   749                         return new Icon(stream);
   750                     }
   751                 }
   752             }
   753 
   754             return null;
   755         }
   756 
   757 
   758         /// <summary>
   759         /// Set our current client.
   760         /// This will take care of applying our client layout and set data fields.
   761         /// </summary>
   762         /// <param name="aSessionId"></param>
   763         void SetCurrentClient(string aSessionId, bool aForce = false)
   764         {
   765             if (aSessionId == iCurrentClientSessionId)
   766             {
   767                 //Given client is already the current one.
   768                 //Don't bother changing anything then.
   769                 return;
   770             }
   771 
   772             ClientData requestedClientData = iClients[aSessionId];
   773 
   774             //Check when was the last time we switched to that client
   775             if (iCurrentClientData != null)
   776             {
   777                 //Do not switch client if priority of current client is higher 
   778                 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
   779                 {
   780                     return;
   781                 }
   782 
   783 
   784                 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
   785                 //TODO: put that hard coded value as a client property
   786                 //Clients should be able to define how often they can be interrupted
   787                 //Thus a background client can set this to zero allowing any other client to interrupt at any time
   788                 //We could also compute this delay by looking at the requests frequencies?
   789                 if (!aForce &&
   790                     requestedClientData.Priority == iCurrentClientData.Priority &&
   791                     //Time sharing is only if clients have the same priority
   792                     (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
   793                 {
   794                     //Don't switch clients too often
   795                     return;
   796                 }
   797             }
   798 
   799             //Set current client ID.
   800             iCurrentClientSessionId = aSessionId;
   801             //Set the time we last switched to that client
   802             iClients[aSessionId].LastSwitchTime = DateTime.Now;
   803             //Fetch and set current client data.
   804             iCurrentClientData = requestedClientData;
   805             //Apply layout and set data fields.
   806             UpdateTableLayoutPanel(iCurrentClientData);
   807         }
   808 
   809         private void buttonFont_Click(object sender, EventArgs e)
   810         {
   811             //fontDialog.ShowColor = true;
   812             //fontDialog.ShowApply = true;
   813             fontDialog.ShowEffects = true;
   814             fontDialog.Font = cds.Font;
   815 
   816             fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
   817 
   818             //fontDialog.ShowHelp = true;
   819 
   820             //fontDlg.MaxSize = 40;
   821             //fontDlg.MinSize = 22;
   822 
   823             //fontDialog.Parent = this;
   824             //fontDialog.StartPosition = FormStartPosition.CenterParent;
   825 
   826             //DlgBox.ShowDialog(fontDialog);
   827 
   828             //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
   829             if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
   830             {
   831                 //Set the fonts to all our labels in our layout
   832                 foreach (Control ctrl in iTableLayoutPanel.Controls)
   833                 {
   834                     if (ctrl is MarqueeLabel)
   835                     {
   836                         ((MarqueeLabel) ctrl).Font = fontDialog.Font;
   837                     }
   838                 }
   839 
   840                 //Save font settings
   841                 cds.Font = fontDialog.Font;
   842                 Properties.Settings.Default.Save();
   843                 //
   844                 CheckFontHeight();
   845             }
   846         }
   847 
   848         /// <summary>
   849         ///
   850         /// </summary>
   851         void CheckFontHeight()
   852         {
   853             //Show font height and width
   854             labelFontHeight.Text = "Font height: " + cds.Font.Height;
   855             float charWidth = IsFixedWidth(cds.Font);
   856             if (charWidth == 0.0f)
   857             {
   858                 labelFontWidth.Visible = false;
   859             }
   860             else
   861             {
   862                 labelFontWidth.Visible = true;
   863                 labelFontWidth.Text = "Font width: " + charWidth;
   864             }
   865 
   866             MarqueeLabel label = null;
   867             //Get the first label control we can find
   868             foreach (Control ctrl in iTableLayoutPanel.Controls)
   869             {
   870                 if (ctrl is MarqueeLabel)
   871                 {
   872                     label = (MarqueeLabel) ctrl;
   873                     break;
   874                 }
   875             }
   876 
   877             //Now check font height and show a warning if needed.
   878             if (label != null && label.Font.Height > label.Height)
   879             {
   880                 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) +
   881                                     " pixels!";
   882                 labelWarning.Visible = true;
   883             }
   884             else
   885             {
   886                 labelWarning.Visible = false;
   887             }
   888 
   889         }
   890 
   891         private void buttonCapture_Click(object sender, EventArgs e)
   892         {
   893             System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
   894             iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
   895             //Bitmap bmpToSave = new Bitmap(bmp);
   896             bmp.Save("D:\\capture.png");
   897 
   898             ((MarqueeLabel) iTableLayoutPanel.Controls[0]).Text = "Captured";
   899 
   900             /*
   901             string outputFileName = "d:\\capture.png";
   902             using (MemoryStream memory = new MemoryStream())
   903             {
   904                 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
   905                 {
   906                     bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
   907                     byte[] bytes = memory.ToArray();
   908                     fs.Write(bytes, 0, bytes.Length);
   909                 }
   910             }
   911              */
   912 
   913         }
   914 
   915         private void CheckForRequestResults()
   916         {
   917             if (iDisplay.IsRequestPending())
   918             {
   919                 switch (iDisplay.AttemptRequestCompletion())
   920                 {
   921                     case MiniDisplay.Request.FirmwareRevision:
   922                         toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
   923                         //Issue next request then
   924                         iDisplay.RequestPowerSupplyStatus();
   925                         break;
   926 
   927                     case MiniDisplay.Request.PowerSupplyStatus:
   928                         if (iDisplay.PowerSupplyStatus())
   929                         {
   930                             toolStripStatusLabelPower.Text = "ON";
   931                         }
   932                         else
   933                         {
   934                             toolStripStatusLabelPower.Text = "OFF";
   935                         }
   936                         //Issue next request then
   937                         iDisplay.RequestDeviceId();
   938                         break;
   939 
   940                     case MiniDisplay.Request.DeviceId:
   941                         toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
   942                         //No more request to issue
   943                         break;
   944                 }
   945             }
   946         }
   947 
   948         public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
   949         {
   950             if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
   951             {
   952                 return 0xFFFFFFFF;
   953             }
   954             return 0x00000000;
   955         }
   956 
   957         public static uint ColorUntouched(int aX, int aY, uint aPixel)
   958         {
   959             return aPixel;
   960         }
   961 
   962         public static uint ColorInversed(int aX, int aY, uint aPixel)
   963         {
   964             return ~aPixel;
   965         }
   966 
   967         public static uint ColorChessboard(int aX, int aY, uint aPixel)
   968         {
   969             if ((aX%2 == 0) && (aY%2 == 0))
   970             {
   971                 return ~aPixel;
   972             }
   973             else if ((aX%2 != 0) && (aY%2 != 0))
   974             {
   975                 return ~aPixel;
   976             }
   977             return 0x00000000;
   978         }
   979 
   980 
   981         public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
   982         {
   983             return aBmp.Width - aX - 1;
   984         }
   985 
   986         public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
   987         {
   988             return iBmp.Height - aY - 1;
   989         }
   990 
   991         public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
   992         {
   993             return aX;
   994         }
   995 
   996         public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
   997         {
   998             return aY;
   999         }
  1000 
  1001         /// <summary>
  1002         /// Select proper pixel delegates according to our current settings.
  1003         /// </summary>
  1004         private void SetupPixelDelegates()
  1005         {
  1006             //Select our pixel processing routine
  1007             if (cds.InverseColors)
  1008             {
  1009                 //iColorFx = ColorChessboard;
  1010                 iColorFx = ColorInversed;
  1011             }
  1012             else
  1013             {
  1014                 iColorFx = ColorWhiteIsOn;
  1015             }
  1016 
  1017             //Select proper coordinate translation functions
  1018             //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
  1019             if (cds.ReverseScreen)
  1020             {
  1021                 iScreenX = ScreenReversedX;
  1022                 iScreenY = ScreenReversedY;
  1023             }
  1024             else
  1025             {
  1026                 iScreenX = ScreenX;
  1027                 iScreenY = ScreenY;
  1028             }
  1029 
  1030         }
  1031 
  1032         //This is our timer tick responsible to perform our render
  1033         private void timer_Tick(object sender, EventArgs e)
  1034         {
  1035             //Not ideal cause this has nothing to do with display render
  1036             LogsUpdate();
  1037 
  1038             //Update our animations
  1039             DateTime NewTickTime = DateTime.Now;
  1040 
  1041             UpdateNetworkSignal(LastTickTime, NewTickTime);
  1042 
  1043             //Update animation for all our marquees
  1044             foreach (Control ctrl in iTableLayoutPanel.Controls)
  1045             {
  1046                 if (ctrl is MarqueeLabel)
  1047                 {
  1048                     ((MarqueeLabel) ctrl).UpdateAnimation(LastTickTime, NewTickTime);
  1049                 }
  1050             }
  1051 
  1052             //Update our display
  1053             if (iDisplay.IsOpen())
  1054             {
  1055                 CheckForRequestResults();
  1056 
  1057                 //Check if frame rendering is needed
  1058                 //Typically used when showing clock
  1059                 if (!iSkipFrameRendering)
  1060                 {
  1061                     //Draw to bitmap
  1062                     if (iCreateBitmap)
  1063                     {
  1064                         iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
  1065                             PixelFormat.Format32bppArgb);
  1066                         iCreateBitmap = false;
  1067                     }
  1068                     iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
  1069                     //iBmp.Save("D:\\capture.png");
  1070 
  1071                     //Send it to our display
  1072                     for (int i = 0; i < iBmp.Width; i++)
  1073                     {
  1074                         for (int j = 0; j < iBmp.Height; j++)
  1075                         {
  1076                             unchecked
  1077                             {
  1078                                 //Get our processed pixel coordinates
  1079                                 int x = iScreenX(iBmp, i);
  1080                                 int y = iScreenY(iBmp, j);
  1081                                 //Get pixel color
  1082                                 uint color = (uint) iBmp.GetPixel(i, j).ToArgb();
  1083                                 //Apply color effects
  1084                                 color = iColorFx(x, y, color);
  1085                                 //Now set our pixel
  1086                                 iDisplay.SetPixel(x, y, color);
  1087                             }
  1088                         }
  1089                     }
  1090 
  1091                     iDisplay.SwapBuffers();
  1092                 }
  1093             }
  1094 
  1095             //Compute instant FPS
  1096             toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " +
  1097                                            (1000/timer.Interval).ToString() + " FPS";
  1098 
  1099             LastTickTime = NewTickTime;
  1100 
  1101         }
  1102 
  1103         /// <summary>
  1104         /// Attempt to establish connection with our display hardware.
  1105         /// </summary>
  1106         private void OpenDisplayConnection()
  1107         {
  1108             CloseDisplayConnection();
  1109 
  1110             if (!iDisplay.Open((MiniDisplay.Type) cds.DisplayType))
  1111             {
  1112                 UpdateStatus();
  1113                 toolStripStatusLabelConnect.Text = "Connection error";
  1114             }
  1115         }
  1116 
  1117         private void CloseDisplayConnection()
  1118         {
  1119             //Status will be updated upon receiving the closed event
  1120 
  1121             if (iDisplay == null || !iDisplay.IsOpen())
  1122             {
  1123                 return;
  1124             }
  1125 
  1126             //Do not clear if we gave up on rendering already.
  1127             //This means we will keep on displaying clock on MDM166AA for instance.
  1128             if (!iSkipFrameRendering)
  1129             {
  1130                 iDisplay.Clear();
  1131                 iDisplay.SwapBuffers();
  1132             }
  1133 
  1134             iDisplay.SetAllIconsStatus(0); //Turn off all icons
  1135             iDisplay.Close();
  1136         }
  1137 
  1138         private void buttonOpen_Click(object sender, EventArgs e)
  1139         {
  1140             OpenDisplayConnection();
  1141         }
  1142 
  1143         private void buttonClose_Click(object sender, EventArgs e)
  1144         {
  1145             CloseDisplayConnection();
  1146         }
  1147 
  1148         private void buttonClear_Click(object sender, EventArgs e)
  1149         {
  1150             iDisplay.Clear();
  1151             iDisplay.SwapBuffers();
  1152         }
  1153 
  1154         private void buttonFill_Click(object sender, EventArgs e)
  1155         {
  1156             iDisplay.Fill();
  1157             iDisplay.SwapBuffers();
  1158         }
  1159 
  1160         private void trackBarBrightness_Scroll(object sender, EventArgs e)
  1161         {
  1162             cds.Brightness = trackBarBrightness.Value;
  1163             Properties.Settings.Default.Save();
  1164             iDisplay.SetBrightness(trackBarBrightness.Value);
  1165 
  1166         }
  1167 
  1168 
  1169         /// <summary>
  1170         /// CDS stands for Current Display Settings
  1171         /// </summary>
  1172         private DisplaySettings cds
  1173         {
  1174             get
  1175             {
  1176                 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
  1177                 if (settings == null)
  1178                 {
  1179                     settings = new DisplaysSettings();
  1180                     settings.Init();
  1181                     Properties.Settings.Default.DisplaysSettings = settings;
  1182                 }
  1183 
  1184                 //Make sure all our settings have been created
  1185                 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
  1186                 {
  1187                     settings.Displays.Add(new DisplaySettings());
  1188                 }
  1189 
  1190                 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
  1191                 return displaySettings;
  1192             }
  1193         }
  1194 
  1195         /// <summary>
  1196         /// Check if the given font has a fixed character pitch.
  1197         /// </summary>
  1198         /// <param name="ft"></param>
  1199         /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
  1200         public float IsFixedWidth(Font ft)
  1201         {
  1202             Graphics g = CreateGraphics();
  1203             char[] charSizes = new char[] {'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.'};
  1204             float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
  1205 
  1206             bool fixedWidth = true;
  1207 
  1208             foreach (char c in charSizes)
  1209                 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width !=
  1210                     charWidth)
  1211                     fixedWidth = false;
  1212 
  1213             if (fixedWidth)
  1214             {
  1215                 return charWidth;
  1216             }
  1217 
  1218             return 0.0f;
  1219         }
  1220 
  1221         /// <summary>
  1222         /// Synchronize UI with settings
  1223         /// </summary>
  1224         private void UpdateStatus()
  1225         {
  1226             //Load settings
  1227             checkBoxShowBorders.Checked = cds.ShowBorders;
  1228             iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders
  1229                 ? TableLayoutPanelCellBorderStyle.Single
  1230                 : TableLayoutPanelCellBorderStyle.None);
  1231 
  1232             //Set the proper font to each of our labels
  1233             foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
  1234             {
  1235                 ctrl.Font = cds.Font;
  1236             }
  1237 
  1238             CheckFontHeight();
  1239             //Check if "run on Windows startup" is enabled
  1240             checkBoxAutoStart.Checked = iStartupManager.Startup;
  1241             //
  1242             checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
  1243             checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
  1244             checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
  1245             iCheckBoxStartIdleClient.Checked = Properties.Settings.Default.StartIdleClient;
  1246             labelStartFileName.Text = Properties.Settings.Default.StartFileName;
  1247 
  1248 
  1249             //Try find our drive in our drive list
  1250             int opticalDriveItemIndex = 0;
  1251             bool driveNotFound = true;
  1252             string opticalDriveToEject = Properties.Settings.Default.OpticalDriveToEject;
  1253             foreach (object item in comboBoxOpticalDrives.Items)
  1254             {
  1255                 if (opticalDriveToEject == item.ToString())
  1256                 {
  1257                     comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
  1258                     driveNotFound = false;
  1259                     break;
  1260                 }
  1261                 opticalDriveItemIndex++;
  1262             }
  1263 
  1264             if (driveNotFound)
  1265             {
  1266                 //We could not find the drive we had saved.
  1267                 //Select "None" then.
  1268                 comboBoxOpticalDrives.SelectedIndex = 0;
  1269             }
  1270 
  1271             //Harmony settings
  1272             iCheckBoxHarmonyEnabled.Checked = Properties.Settings.Default.HarmonyEnabled;
  1273             iTextBoxHarmonyHubAddress.Text = Properties.Settings.Default.HarmonyHubAddress;
  1274 
  1275             //CEC settings
  1276             checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
  1277             comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
  1278 
  1279             //Mini Display settings
  1280             checkBoxReverseScreen.Checked = cds.ReverseScreen;
  1281             checkBoxInverseColors.Checked = cds.InverseColors;
  1282             checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
  1283             checkBoxScaleToFit.Checked = cds.ScaleToFit;
  1284             maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
  1285             labelMinFontSize.Enabled = cds.ScaleToFit;
  1286             maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
  1287             maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
  1288             comboBoxDisplayType.SelectedIndex = cds.DisplayType;
  1289             timer.Interval = cds.TimerInterval;
  1290             maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
  1291             textBoxScrollLoopSeparator.Text = cds.Separator;
  1292             //
  1293             SetupPixelDelegates();
  1294 
  1295             if (iDisplay.IsOpen())
  1296             {
  1297                 //We have a display connection
  1298                 //Reflect that in our UI
  1299                 StartTimer();
  1300 
  1301                 iTableLayoutPanel.Enabled = true;
  1302                 panelDisplay.Enabled = true;
  1303 
  1304                 //Only setup brightness if display is open
  1305                 trackBarBrightness.Minimum = iDisplay.MinBrightness();
  1306                 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
  1307                 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
  1308                 {
  1309                     //Brightness out of range, this can occur when using auto-detect
  1310                     //Use max brightness instead
  1311                     trackBarBrightness.Value = iDisplay.MaxBrightness();
  1312                     iDisplay.SetBrightness(iDisplay.MaxBrightness());
  1313                 }
  1314                 else
  1315                 {
  1316                     trackBarBrightness.Value = cds.Brightness;
  1317                     iDisplay.SetBrightness(cds.Brightness);
  1318                 }
  1319 
  1320                 //Try compute the steps to something that makes sense
  1321                 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness())/5);
  1322                 trackBarBrightness.SmallChange = 1;
  1323 
  1324                 //
  1325                 buttonFill.Enabled = true;
  1326                 buttonClear.Enabled = true;
  1327                 buttonOpen.Enabled = false;
  1328                 buttonClose.Enabled = true;
  1329                 trackBarBrightness.Enabled = true;
  1330                 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
  1331                 //+ " - " + iDisplay.SerialNumber();
  1332 
  1333                 if (iDisplay.SupportPowerOnOff())
  1334                 {
  1335                     buttonPowerOn.Enabled = true;
  1336                     buttonPowerOff.Enabled = true;
  1337                 }
  1338                 else
  1339                 {
  1340                     buttonPowerOn.Enabled = false;
  1341                     buttonPowerOff.Enabled = false;
  1342                 }
  1343 
  1344                 if (iDisplay.SupportClock())
  1345                 {
  1346                     buttonShowClock.Enabled = true;
  1347                     buttonHideClock.Enabled = true;
  1348                 }
  1349                 else
  1350                 {
  1351                     buttonShowClock.Enabled = false;
  1352                     buttonHideClock.Enabled = false;
  1353                 }
  1354 
  1355 
  1356                 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
  1357                 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel) > 0;
  1358 
  1359                 if (cds.ShowVolumeLabel)
  1360                 {
  1361                     iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
  1362                 }
  1363                 else
  1364                 {
  1365                     iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
  1366                 }
  1367             }
  1368             else
  1369             {
  1370                 //Display connection not available
  1371                 //Reflect that in our UI
  1372 #if DEBUG
  1373                 //In debug start our timer even if we don't have a display connection
  1374                 StartTimer();
  1375 #else
  1376     //In production environment we don't need our timer if no display connection
  1377                 StopTimer();
  1378 #endif
  1379                 checkBoxShowVolumeLabel.Enabled = false;
  1380                 iTableLayoutPanel.Enabled = false;
  1381                 panelDisplay.Enabled = false;
  1382                 buttonFill.Enabled = false;
  1383                 buttonClear.Enabled = false;
  1384                 buttonOpen.Enabled = true;
  1385                 buttonClose.Enabled = false;
  1386                 trackBarBrightness.Enabled = false;
  1387                 buttonPowerOn.Enabled = false;
  1388                 buttonPowerOff.Enabled = false;
  1389                 buttonShowClock.Enabled = false;
  1390                 buttonHideClock.Enabled = false;
  1391                 toolStripStatusLabelConnect.Text = "Disconnected";
  1392                 toolStripStatusLabelPower.Text = "N/A";
  1393             }
  1394 
  1395         }
  1396 
  1397 
  1398         /// <summary>
  1399         /// 
  1400         /// </summary>
  1401         /// <param name="sender"></param>
  1402         /// <param name="e"></param>
  1403         private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
  1404         {
  1405             cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
  1406             Properties.Settings.Default.Save();
  1407             UpdateStatus();
  1408         }
  1409 
  1410         private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
  1411         {
  1412             //Save our show borders setting
  1413             iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked
  1414                 ? TableLayoutPanelCellBorderStyle.Single
  1415                 : TableLayoutPanelCellBorderStyle.None);
  1416             cds.ShowBorders = checkBoxShowBorders.Checked;
  1417             Properties.Settings.Default.Save();
  1418             CheckFontHeight();
  1419         }
  1420 
  1421         private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
  1422         {
  1423             //Save our connect on startup setting
  1424             Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
  1425             Properties.Settings.Default.Save();
  1426         }
  1427 
  1428         private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
  1429         {
  1430             //Save our "Minimize to tray" setting
  1431             Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
  1432             Properties.Settings.Default.Save();
  1433 
  1434         }
  1435 
  1436         private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
  1437         {
  1438             //Save our "Start minimized" setting
  1439             Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
  1440             Properties.Settings.Default.Save();
  1441         }
  1442 
  1443         private void checkBoxStartIdleClient_CheckedChanged(object sender, EventArgs e)
  1444         {
  1445             Properties.Settings.Default.StartIdleClient = iCheckBoxStartIdleClient.Checked;
  1446             Properties.Settings.Default.Save();
  1447         }
  1448 
  1449         private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
  1450         {
  1451             iStartupManager.Startup = checkBoxAutoStart.Checked;
  1452         }
  1453 
  1454 
  1455         private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
  1456         {
  1457             //Save our reverse screen setting
  1458             cds.ReverseScreen = checkBoxReverseScreen.Checked;
  1459             Properties.Settings.Default.Save();
  1460             SetupPixelDelegates();
  1461         }
  1462 
  1463         private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
  1464         {
  1465             //Save our inverse colors setting
  1466             cds.InverseColors = checkBoxInverseColors.Checked;
  1467             Properties.Settings.Default.Save();
  1468             SetupPixelDelegates();
  1469         }
  1470 
  1471         private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
  1472         {
  1473             //Save our scale to fit setting
  1474             cds.ScaleToFit = checkBoxScaleToFit.Checked;
  1475             Properties.Settings.Default.Save();
  1476             //
  1477             labelMinFontSize.Enabled = cds.ScaleToFit;
  1478             maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
  1479         }
  1480 
  1481         private void MainForm_Resize(object sender, EventArgs e)
  1482         {
  1483             if (WindowState == FormWindowState.Minimized)
  1484             {
  1485                 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
  1486                 // That's apparently not needed on Windows 10 but we better leave it in place.
  1487                 iCreateBitmap = true;
  1488             }
  1489         }
  1490 
  1491         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
  1492         {
  1493             iCecManager.Stop();
  1494             iNetworkManager.Dispose();
  1495             CloseDisplayConnection();
  1496             StopServer();
  1497             e.Cancel = iClosing;
  1498         }
  1499 
  1500         public void StartServer()
  1501         {
  1502             iServiceHost = new ServiceHost
  1503                 (
  1504                 typeof(Session),
  1505                 new Uri[] {new Uri("net.tcp://localhost:8001/")}
  1506                 );
  1507 
  1508             iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true),
  1509                 "DisplayService");
  1510             iServiceHost.Open();
  1511         }
  1512 
  1513         public void StopServer()
  1514         {
  1515             if (iClients.Count > 0 && !iClosing)
  1516             {
  1517                 //Tell our clients
  1518                 iClosing = true;
  1519                 BroadcastCloseEvent();
  1520             }
  1521             else if (iClosing)
  1522             {
  1523                 if (
  1524                     MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo,
  1525                         MessageBoxIcon.Warning) == DialogResult.Yes)
  1526                 {
  1527                     iClosing = false; //We make sure we force close if asked twice
  1528                 }
  1529             }
  1530             else
  1531             {
  1532                 //We removed that as it often lags for some reason
  1533                 //iServiceHost.Close();
  1534             }
  1535         }
  1536 
  1537         public void BroadcastCloseEvent()
  1538         {
  1539             Trace.TraceInformation("BroadcastCloseEvent - start");
  1540 
  1541             var inactiveClients = new List<string>();
  1542             foreach (var client in iClients)
  1543             {
  1544                 //if (client.Key != eventData.ClientName)
  1545                 {
  1546                     try
  1547                     {
  1548                         Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
  1549                         client.Value.Callback.OnCloseOrder( /*eventData*/);
  1550                     }
  1551                     catch (Exception ex)
  1552                     {
  1553                         inactiveClients.Add(client.Key);
  1554                     }
  1555                 }
  1556             }
  1557 
  1558             if (inactiveClients.Count > 0)
  1559             {
  1560                 foreach (var client in inactiveClients)
  1561                 {
  1562                     iClients.Remove(client);
  1563                     Program.iFormMain.iTreeViewClients.Nodes.Remove(
  1564                         Program.iFormMain.iTreeViewClients.Nodes.Find(client, false)[0]);
  1565                 }
  1566             }
  1567 
  1568             if (iClients.Count == 0)
  1569             {
  1570                 ClearLayout();
  1571             }
  1572         }
  1573 
  1574         /// <summary>
  1575         /// Just remove all our fields.
  1576         /// </summary>
  1577         private void ClearLayout()
  1578         {
  1579             iTableLayoutPanel.Controls.Clear();
  1580             iTableLayoutPanel.RowStyles.Clear();
  1581             iTableLayoutPanel.ColumnStyles.Clear();
  1582             iCurrentClientData = null;
  1583         }
  1584 
  1585         /// <summary>
  1586         /// Just launch a demo client.
  1587         /// </summary>
  1588         private void StartNewClient(string aTopText = "", string aBottomText = "")
  1589         {
  1590             Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
  1591             SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(
  1592                 new Point(this.Right, this.Top), aTopText, aBottomText);
  1593             clientThread.Start(myParams);
  1594             BringToFront();
  1595         }
  1596 
  1597         /// <summary>
  1598         /// Just launch our idle client.
  1599         /// </summary>
  1600         private void StartIdleClient(string aTopText = "", string aBottomText = "")
  1601         {
  1602             Thread clientThread = new Thread(SharpDisplayClientIdle.Program.MainWithParams);
  1603             SharpDisplayClientIdle.StartParams myParams =
  1604                 new SharpDisplayClientIdle.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
  1605             clientThread.Start(myParams);
  1606             BringToFront();
  1607         }
  1608 
  1609 
  1610         private void buttonStartClient_Click(object sender, EventArgs e)
  1611         {
  1612             StartNewClient();
  1613         }
  1614 
  1615         private void buttonSuspend_Click(object sender, EventArgs e)
  1616         {
  1617             ToggleTimer();
  1618         }
  1619 
  1620         private void StartTimer()
  1621         {
  1622             LastTickTime = DateTime.Now; //Reset timer to prevent jump
  1623             timer.Enabled = true;
  1624             UpdateSuspendButton();
  1625         }
  1626 
  1627         private void StopTimer()
  1628         {
  1629             LastTickTime = DateTime.Now; //Reset timer to prevent jump
  1630             timer.Enabled = false;
  1631             UpdateSuspendButton();
  1632         }
  1633 
  1634         private void ToggleTimer()
  1635         {
  1636             LastTickTime = DateTime.Now; //Reset timer to prevent jump
  1637             timer.Enabled = !timer.Enabled;
  1638             UpdateSuspendButton();
  1639         }
  1640 
  1641         private void UpdateSuspendButton()
  1642         {
  1643             if (!timer.Enabled)
  1644             {
  1645                 buttonSuspend.Text = "Run";
  1646             }
  1647             else
  1648             {
  1649                 buttonSuspend.Text = "Pause";
  1650             }
  1651         }
  1652 
  1653 
  1654         private void buttonCloseClients_Click(object sender, EventArgs e)
  1655         {
  1656             BroadcastCloseEvent();
  1657         }
  1658 
  1659         private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
  1660         {
  1661             //Root node must have at least one child
  1662             if (e.Node.Nodes.Count == 0)
  1663             {
  1664                 return;
  1665             }
  1666 
  1667             //If the selected node is the root node of a client then switch to it
  1668             string sessionId = e.Node.Nodes[0].Text; //First child of a root node is the sessionId
  1669             if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
  1670             {
  1671                 //We have a valid session just switch to that client
  1672                 SetCurrentClient(sessionId, true);
  1673             }
  1674 
  1675         }
  1676 
  1677 
  1678         /// <summary>
  1679         ///
  1680         /// </summary>
  1681         /// <param name="aSessionId"></param>
  1682         /// <param name="aCallback"></param>
  1683         public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
  1684         {
  1685             if (this.InvokeRequired)
  1686             {
  1687                 //Not in the proper thread, invoke ourselves
  1688                 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
  1689                 this.Invoke(d, new object[] {aSessionId, aCallback});
  1690             }
  1691             else
  1692             {
  1693                 //We are in the proper thread
  1694                 //Add this session to our collection of clients
  1695                 ClientData newClient = new ClientData(aSessionId, aCallback);
  1696                 Program.iFormMain.iClients.Add(aSessionId, newClient);
  1697                 //Add this session to our UI
  1698                 UpdateClientTreeViewNode(newClient);
  1699             }
  1700         }
  1701 
  1702 
  1703         /// <summary>
  1704         /// Find the client with the highest priority if any.
  1705         /// </summary>
  1706         /// <returns>Our highest priority client or null if not a single client is connected.</returns>
  1707         public ClientData FindHighestPriorityClient()
  1708         {
  1709             ClientData highestPriorityClient = null;
  1710             foreach (var client in iClients)
  1711             {
  1712                 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
  1713                 {
  1714                     highestPriorityClient = client.Value;
  1715                 }
  1716             }
  1717 
  1718             return highestPriorityClient;
  1719         }
  1720 
  1721         /// <summary>
  1722         ///
  1723         /// </summary>
  1724         /// <param name="aSessionId"></param>
  1725         public void RemoveClientThreadSafe(string aSessionId)
  1726         {
  1727             if (this.InvokeRequired)
  1728             {
  1729                 //Not in the proper thread, invoke ourselves
  1730                 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
  1731                 this.Invoke(d, new object[] {aSessionId});
  1732             }
  1733             else
  1734             {
  1735                 //We are in the proper thread
  1736                 //Remove this session from both client collection and UI tree view
  1737                 if (Program.iFormMain.iClients.Keys.Contains(aSessionId))
  1738                 {
  1739                     Program.iFormMain.iClients.Remove(aSessionId);
  1740                     Program.iFormMain.iTreeViewClients.Nodes.Remove(
  1741                         Program.iFormMain.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
  1742                     //Update recording status too whenever a client is removed
  1743                     UpdateRecordingNotification();
  1744                 }
  1745 
  1746                 if (iCurrentClientSessionId == aSessionId)
  1747                 {
  1748                     //The current client is closing
  1749                     iCurrentClientData = null;
  1750                     //Find the client with the highest priority and set it as current
  1751                     ClientData newCurrentClient = FindHighestPriorityClient();
  1752                     if (newCurrentClient != null)
  1753                     {
  1754                         SetCurrentClient(newCurrentClient.SessionId, true);
  1755                     }
  1756                 }
  1757 
  1758                 if (iClients.Count == 0)
  1759                 {
  1760                     //Clear our screen when last client disconnects
  1761                     ClearLayout();
  1762 
  1763                     if (iClosing)
  1764                     {
  1765                         //We were closing our form
  1766                         //All clients are now closed
  1767                         //Just resume our close operation
  1768                         iClosing = false;
  1769                         Close();
  1770                     }
  1771                 }
  1772             }
  1773         }
  1774 
  1775         /// <summary>
  1776         ///
  1777         /// </summary>
  1778         /// <param name="aSessionId"></param>
  1779         /// <param name="aLayout"></param>
  1780         public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
  1781         {
  1782             if (this.InvokeRequired)
  1783             {
  1784                 //Not in the proper thread, invoke ourselves
  1785                 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
  1786                 this.Invoke(d, new object[] {aSessionId, aLayout});
  1787             }
  1788             else
  1789             {
  1790                 ClientData client = iClients[aSessionId];
  1791                 if (client != null)
  1792                 {
  1793                     //Don't change a thing if the layout is the same
  1794                     if (!client.Layout.IsSameAs(aLayout))
  1795                     {
  1796                         Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
  1797                         //Set our client layout then
  1798                         client.Layout = aLayout;
  1799                         //So that next time we update all our fields at ones
  1800                         client.HasNewLayout = true;
  1801                         //Layout has changed clear our fields then
  1802                         client.Fields.Clear();
  1803                         //
  1804                         UpdateClientTreeViewNode(client);
  1805                     }
  1806                     else
  1807                     {
  1808                         Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
  1809                     }
  1810                 }
  1811             }
  1812         }
  1813 
  1814         /// <summary>
  1815         ///
  1816         /// </summary>
  1817         /// <param name="aSessionId"></param>
  1818         /// <param name="aField"></param>
  1819         public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
  1820         {
  1821             if (this.InvokeRequired)
  1822             {
  1823                 //Not in the proper thread, invoke ourselves
  1824                 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
  1825                 this.Invoke(d, new object[] {aSessionId, aField});
  1826             }
  1827             else
  1828             {
  1829                 //We are in the proper thread
  1830                 //Call the non-thread-safe variant
  1831                 SetClientField(aSessionId, aField);
  1832             }
  1833         }
  1834 
  1835 
  1836 
  1837 
  1838         /// <summary>
  1839         /// Set a data field in the given client.
  1840         /// </summary>
  1841         /// <param name="aSessionId"></param>
  1842         /// <param name="aField"></param>
  1843         private void SetClientField(string aSessionId, DataField aField)
  1844         {
  1845             //TODO: should check if the field actually changed?
  1846 
  1847             ClientData client = iClients[aSessionId];
  1848             bool layoutChanged = false;
  1849             bool contentChanged = true;
  1850 
  1851             //Fetch our field index
  1852             int fieldIndex = client.FindSameFieldIndex(aField);
  1853 
  1854             if (fieldIndex < 0)
  1855             {
  1856                 //No corresponding field, just bail out
  1857                 return;
  1858             }
  1859 
  1860             //Keep our previous field in there
  1861             DataField previousField = client.Fields[fieldIndex];
  1862             //Just update that field then 
  1863             client.Fields[fieldIndex] = aField;
  1864 
  1865             if (!aField.IsTableField)
  1866             {
  1867                 //We are done then if that field is not in our table layout
  1868                 return;
  1869             }
  1870 
  1871             TableField tableField = (TableField) aField;
  1872 
  1873             if (previousField.IsSameLayout(aField))
  1874             {
  1875                 //If we are updating a field in our current client we need to update it in our panel
  1876                 if (aSessionId == iCurrentClientSessionId)
  1877                 {
  1878                     Control ctrl = iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
  1879                     if (aField.IsTextField && ctrl is MarqueeLabel)
  1880                     {
  1881                         TextField textField = (TextField) aField;
  1882                         //Text field control already in place, just change the text
  1883                         MarqueeLabel label = (MarqueeLabel) ctrl;
  1884                         contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
  1885                         label.Text = textField.Text;
  1886                         label.TextAlign = textField.Alignment;
  1887                     }
  1888                     else if (aField.IsBitmapField && ctrl is PictureBox)
  1889                     {
  1890                         BitmapField bitmapField = (BitmapField) aField;
  1891                         contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
  1892                         //Bitmap field control already in place just change the bitmap
  1893                         PictureBox pictureBox = (PictureBox) ctrl;
  1894                         pictureBox.Image = bitmapField.Bitmap;
  1895                     }
  1896                     else
  1897                     {
  1898                         layoutChanged = true;
  1899                     }
  1900                 }
  1901             }
  1902             else
  1903             {
  1904                 layoutChanged = true;
  1905             }
  1906 
  1907             //If either content or layout changed we need to update our tree view to reflect the changes
  1908             if (contentChanged || layoutChanged)
  1909             {
  1910                 UpdateClientTreeViewNode(client);
  1911                 //
  1912                 if (layoutChanged)
  1913                 {
  1914                     Debug.Print("Layout changed");
  1915                     //Our layout has changed, if we are already the current client we need to update our panel
  1916                     if (aSessionId == iCurrentClientSessionId)
  1917                     {
  1918                         //Apply layout and set data fields.
  1919                         UpdateTableLayoutPanel(iCurrentClientData);
  1920                     }
  1921                 }
  1922                 else
  1923                 {
  1924                     Debug.Print("Layout has not changed.");
  1925                 }
  1926             }
  1927             else
  1928             {
  1929                 Debug.Print("WARNING: content and layout have not changed!");
  1930             }
  1931 
  1932             //When a client field is set we try switching to this client to present the new information to our user
  1933             SetCurrentClient(aSessionId);
  1934         }
  1935 
  1936         /// <summary>
  1937         ///
  1938         /// </summary>
  1939         /// <param name="aTexts"></param>
  1940         public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
  1941         {
  1942             if (this.InvokeRequired)
  1943             {
  1944                 //Not in the proper thread, invoke ourselves
  1945                 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
  1946                 this.Invoke(d, new object[] {aSessionId, aFields});
  1947             }
  1948             else
  1949             {
  1950                 ClientData client = iClients[aSessionId];
  1951 
  1952                 if (client.HasNewLayout)
  1953                 {
  1954                     //TODO: Assert client.Count == 0
  1955                     //Our layout was just changed
  1956                     //Do some special handling to avoid re-creating our panel N times, once for each fields
  1957                     client.HasNewLayout = false;
  1958                     //Just set all our fields then
  1959                     client.Fields.AddRange(aFields);
  1960                     //Try switch to that client
  1961                     SetCurrentClient(aSessionId);
  1962 
  1963                     //If we are updating the current client update our panel
  1964                     if (aSessionId == iCurrentClientSessionId)
  1965                     {
  1966                         //Apply layout and set data fields.
  1967                         UpdateTableLayoutPanel(iCurrentClientData);
  1968                     }
  1969 
  1970                     UpdateClientTreeViewNode(client);
  1971                 }
  1972                 else
  1973                 {
  1974                     //Put each our text fields in a label control
  1975                     foreach (DataField field in aFields)
  1976                     {
  1977                         SetClientField(aSessionId, field);
  1978                     }
  1979                 }
  1980             }
  1981         }
  1982 
  1983         /// <summary>
  1984         ///
  1985         /// </summary>
  1986         /// <param name="aSessionId"></param>
  1987         /// <param name="aName"></param>
  1988         public void SetClientNameThreadSafe(string aSessionId, string aName)
  1989         {
  1990             if (this.InvokeRequired)
  1991             {
  1992                 //Not in the proper thread, invoke ourselves
  1993                 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
  1994                 this.Invoke(d, new object[] {aSessionId, aName});
  1995             }
  1996             else
  1997             {
  1998                 //We are in the proper thread
  1999                 //Get our client
  2000                 ClientData client = iClients[aSessionId];
  2001                 if (client != null)
  2002                 {
  2003                     //Set its name
  2004                     client.Name = aName;
  2005                     //Update our tree-view
  2006                     UpdateClientTreeViewNode(client);
  2007                 }
  2008             }
  2009         }
  2010 
  2011         ///
  2012         public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
  2013         {
  2014             if (this.InvokeRequired)
  2015             {
  2016                 //Not in the proper thread, invoke ourselves
  2017                 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
  2018                 this.Invoke(d, new object[] {aSessionId, aPriority});
  2019             }
  2020             else
  2021             {
  2022                 //We are in the proper thread
  2023                 //Get our client
  2024                 ClientData client = iClients[aSessionId];
  2025                 if (client != null)
  2026                 {
  2027                     //Set its name
  2028                     client.Priority = aPriority;
  2029                     //Update our tree-view
  2030                     UpdateClientTreeViewNode(client);
  2031                     //Change our current client as per new priority
  2032                     ClientData newCurrentClient = FindHighestPriorityClient();
  2033                     if (newCurrentClient != null)
  2034                     {
  2035                         SetCurrentClient(newCurrentClient.SessionId);
  2036                     }
  2037                 }
  2038             }
  2039         }
  2040 
  2041         /// <summary>
  2042         /// 
  2043         /// </summary>
  2044         /// <param name="value"></param>
  2045         /// <param name="maxChars"></param>
  2046         /// <returns></returns>
  2047         public static string Truncate(string value, int maxChars)
  2048         {
  2049             return value.Length <= maxChars ? value : value.Substring(0, maxChars - 3) + "...";
  2050         }
  2051 
  2052         /// <summary>
  2053         /// Update our recording notification.
  2054         /// </summary>
  2055         private void UpdateRecordingNotification()
  2056         {
  2057             //Go through each 
  2058             bool activeRecording = false;
  2059             string text = "";
  2060             RecordingField recField = new RecordingField();
  2061             foreach (var client in iClients)
  2062             {
  2063                 RecordingField rec = (RecordingField) client.Value.FindSameFieldAs(recField);
  2064                 if (rec != null && rec.IsActive)
  2065                 {
  2066                     activeRecording = true;
  2067                     //Don't break cause we are collecting the names/texts.
  2068                     if (!String.IsNullOrEmpty(rec.Text))
  2069                     {
  2070                         text += (rec.Text + "\n");
  2071                     }
  2072                     else
  2073                     {
  2074                         //Not text for that recording, use client name instead
  2075                         text += client.Value.Name + " recording\n";
  2076                     }
  2077 
  2078                 }
  2079             }
  2080 
  2081             //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
  2082             iRecordingNotification.Text = Truncate(text, 63);
  2083 
  2084             //Change visibility of notification if needed
  2085             if (iRecordingNotification.Visible != activeRecording)
  2086             {
  2087                 iRecordingNotification.Visible = activeRecording;
  2088                 //Assuming the notification icon is in sync with our display icon
  2089                 //Take care of our REC icon
  2090                 if (iDisplay.IsOpen())
  2091                 {
  2092                     iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
  2093                 }
  2094             }
  2095         }
  2096 
  2097         /// <summary>
  2098         ///
  2099         /// </summary>
  2100         /// <param name="aClient"></param>
  2101         private void UpdateClientTreeViewNode(ClientData aClient)
  2102         {
  2103             Debug.Print("UpdateClientTreeViewNode");
  2104 
  2105             if (aClient == null)
  2106             {
  2107                 return;
  2108             }
  2109 
  2110             //Hook in record icon update too
  2111             UpdateRecordingNotification();
  2112 
  2113             TreeNode node = null;
  2114             //Check that our client node already exists
  2115             //Get our client root node using its key which is our session ID
  2116             TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
  2117             if (nodes.Count() > 0)
  2118             {
  2119                 //We already have a node for that client
  2120                 node = nodes[0];
  2121                 //Clear children as we are going to recreate them below
  2122                 node.Nodes.Clear();
  2123             }
  2124             else
  2125             {
  2126                 //Client node does not exists create a new one
  2127                 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
  2128                 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
  2129             }
  2130 
  2131             if (node != null)
  2132             {
  2133                 //Change its name
  2134                 if (!String.IsNullOrEmpty(aClient.Name))
  2135                 {
  2136                     //We have a name, use it as text for our root node
  2137                     node.Text = aClient.Name;
  2138                     //Add a child with SessionId
  2139                     node.Nodes.Add(new TreeNode(aClient.SessionId));
  2140                 }
  2141                 else
  2142                 {
  2143                     //No name, use session ID instead
  2144                     node.Text = aClient.SessionId;
  2145                 }
  2146 
  2147                 //Display client priority
  2148                 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
  2149 
  2150                 if (aClient.Fields.Count > 0)
  2151                 {
  2152                     //Create root node for our texts
  2153                     TreeNode textsRoot = new TreeNode("Fields");
  2154                     node.Nodes.Add(textsRoot);
  2155                     //For each text add a new entry
  2156                     foreach (DataField field in aClient.Fields)
  2157                     {
  2158                         if (field.IsTextField)
  2159                         {
  2160                             TextField textField = (TextField) field;
  2161                             textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
  2162                         }
  2163                         else if (field.IsBitmapField)
  2164                         {
  2165                             textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
  2166                         }
  2167                         else if (field.IsRecordingField)
  2168                         {
  2169                             RecordingField recordingField = (RecordingField) field;
  2170                             textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
  2171                         }
  2172                     }
  2173                 }
  2174 
  2175                 node.ExpandAll();
  2176             }
  2177         }
  2178 
  2179         /// <summary>
  2180         /// Update our table layout row styles to make sure each rows have similar height
  2181         /// </summary>
  2182         private void UpdateTableLayoutRowStyles()
  2183         {
  2184             foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
  2185             {
  2186                 rowStyle.SizeType = SizeType.Percent;
  2187                 rowStyle.Height = 100/iTableLayoutPanel.RowCount;
  2188             }
  2189         }
  2190 
  2191         /// <summary>
  2192         /// Update our display table layout.
  2193         /// Will instanciated every field control as defined by our client.
  2194         /// Fields must be specified by rows from the left.
  2195         /// </summary>
  2196         /// <param name="aLayout"></param>
  2197         private void UpdateTableLayoutPanel(ClientData aClient)
  2198         {
  2199             Debug.Print("UpdateTableLayoutPanel");
  2200 
  2201             if (aClient == null)
  2202             {
  2203                 //Just drop it
  2204                 return;
  2205             }
  2206 
  2207 
  2208             TableLayout layout = aClient.Layout;
  2209 
  2210             //First clean our current panel
  2211             iTableLayoutPanel.Controls.Clear();
  2212             iTableLayoutPanel.RowStyles.Clear();
  2213             iTableLayoutPanel.ColumnStyles.Clear();
  2214             iTableLayoutPanel.RowCount = 0;
  2215             iTableLayoutPanel.ColumnCount = 0;
  2216 
  2217             //Then recreate our rows...
  2218             while (iTableLayoutPanel.RowCount < layout.Rows.Count)
  2219             {
  2220                 iTableLayoutPanel.RowCount++;
  2221             }
  2222 
  2223             // ...and columns 
  2224             while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
  2225             {
  2226                 iTableLayoutPanel.ColumnCount++;
  2227             }
  2228 
  2229             //For each column
  2230             for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
  2231             {
  2232                 //Create our column styles
  2233                 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
  2234 
  2235                 //For each rows
  2236                 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
  2237                 {
  2238                     if (i == 0)
  2239                     {
  2240                         //Create our row styles
  2241                         this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
  2242                     }
  2243                     else
  2244                     {
  2245                         continue;
  2246                     }
  2247                 }
  2248             }
  2249 
  2250             //For each field
  2251             foreach (DataField field in aClient.Fields)
  2252             {
  2253                 if (!field.IsTableField)
  2254                 {
  2255                     //That field is not taking part in our table layout skip it
  2256                     continue;
  2257                 }
  2258 
  2259                 TableField tableField = (TableField) field;
  2260 
  2261                 //Create a control corresponding to the field specified for that cell
  2262                 Control control = CreateControlForDataField(tableField);
  2263 
  2264                 //Add newly created control to our table layout at the specified row and column
  2265                 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
  2266                 //Make sure we specify column and row span for that new control
  2267                 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
  2268                 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
  2269             }
  2270 
  2271 
  2272             CheckFontHeight();
  2273         }
  2274 
  2275         /// <summary>
  2276         /// Check our type of data field and create corresponding control
  2277         /// </summary>
  2278         /// <param name="aField"></param>
  2279         private Control CreateControlForDataField(DataField aField)
  2280         {
  2281             Control control = null;
  2282             if (aField.IsTextField)
  2283             {
  2284                 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
  2285                 label.AutoEllipsis = true;
  2286                 label.AutoSize = true;
  2287                 label.BackColor = System.Drawing.Color.Transparent;
  2288                 label.Dock = System.Windows.Forms.DockStyle.Fill;
  2289                 label.Location = new System.Drawing.Point(1, 1);
  2290                 label.Margin = new System.Windows.Forms.Padding(0);
  2291                 label.Name = "marqueeLabel" + aField;
  2292                 label.OwnTimer = false;
  2293                 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
  2294                 label.Separator = cds.Separator;
  2295                 label.MinFontSize = cds.MinFontSize;
  2296                 label.ScaleToFit = cds.ScaleToFit;
  2297                 //control.Size = new System.Drawing.Size(254, 30);
  2298                 //control.TabIndex = 2;
  2299                 label.Font = cds.Font;
  2300 
  2301                 TextField field = (TextField) aField;
  2302                 label.TextAlign = field.Alignment;
  2303                 label.UseCompatibleTextRendering = true;
  2304                 label.Text = field.Text;
  2305                 //
  2306                 control = label;
  2307             }
  2308             else if (aField.IsBitmapField)
  2309             {
  2310                 //Create picture box
  2311                 PictureBox picture = new PictureBox();
  2312                 picture.AutoSize = true;
  2313                 picture.BackColor = System.Drawing.Color.Transparent;
  2314                 picture.Dock = System.Windows.Forms.DockStyle.Fill;
  2315                 picture.Location = new System.Drawing.Point(1, 1);
  2316                 picture.Margin = new System.Windows.Forms.Padding(0);
  2317                 picture.Name = "pictureBox" + aField;
  2318                 //Set our image
  2319                 BitmapField field = (BitmapField) aField;
  2320                 picture.Image = field.Bitmap;
  2321                 //
  2322                 control = picture;
  2323             }
  2324             //TODO: Handle recording field?
  2325 
  2326             return control;
  2327         }
  2328 
  2329         /// <summary>
  2330         /// Called when the user selected a new display type.
  2331         /// </summary>
  2332         /// <param name="sender"></param>
  2333         /// <param name="e"></param>
  2334         private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
  2335         {
  2336             //Store the selected display type in our settings
  2337             Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
  2338             cds.DisplayType = comboBoxDisplayType.SelectedIndex;
  2339             Properties.Settings.Default.Save();
  2340 
  2341             //Try re-opening the display connection if we were already connected.
  2342             //Otherwise just update our status to reflect display type change.
  2343             if (iDisplay.IsOpen())
  2344             {
  2345                 OpenDisplayConnection();
  2346             }
  2347             else
  2348             {
  2349                 UpdateStatus();
  2350             }
  2351         }
  2352 
  2353         private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
  2354         {
  2355             if (maskedTextBoxTimerInterval.Text != "")
  2356             {
  2357                 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
  2358 
  2359                 if (interval > 0)
  2360                 {
  2361                     timer.Interval = interval;
  2362                     cds.TimerInterval = timer.Interval;
  2363                     Properties.Settings.Default.Save();
  2364                 }
  2365             }
  2366         }
  2367 
  2368         private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
  2369         {
  2370             if (maskedTextBoxMinFontSize.Text != "")
  2371             {
  2372                 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
  2373 
  2374                 if (minFontSize > 0)
  2375                 {
  2376                     cds.MinFontSize = minFontSize;
  2377                     Properties.Settings.Default.Save();
  2378                     //We need to recreate our layout for that change to take effect
  2379                     UpdateTableLayoutPanel(iCurrentClientData);
  2380                 }
  2381             }
  2382         }
  2383 
  2384 
  2385         private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
  2386         {
  2387             if (maskedTextBoxScrollingSpeed.Text != "")
  2388             {
  2389                 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
  2390 
  2391                 if (scrollingSpeed > 0)
  2392                 {
  2393                     cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
  2394                     Properties.Settings.Default.Save();
  2395                     //We need to recreate our layout for that change to take effect
  2396                     UpdateTableLayoutPanel(iCurrentClientData);
  2397                 }
  2398             }
  2399         }
  2400 
  2401         private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
  2402         {
  2403             cds.Separator = textBoxScrollLoopSeparator.Text;
  2404             Properties.Settings.Default.Save();
  2405 
  2406             //Update our text fields
  2407             foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
  2408             {
  2409                 ctrl.Separator = cds.Separator;
  2410             }
  2411 
  2412         }
  2413 
  2414         private void buttonPowerOn_Click(object sender, EventArgs e)
  2415         {
  2416             iDisplay.PowerOn();
  2417         }
  2418 
  2419         private void buttonPowerOff_Click(object sender, EventArgs e)
  2420         {
  2421             iDisplay.PowerOff();
  2422         }
  2423 
  2424         private void buttonShowClock_Click(object sender, EventArgs e)
  2425         {
  2426             ShowClock();
  2427         }
  2428 
  2429         private void buttonHideClock_Click(object sender, EventArgs e)
  2430         {
  2431             HideClock();
  2432         }
  2433 
  2434         private void buttonUpdate_Click(object sender, EventArgs e)
  2435         {
  2436             InstallUpdateSyncWithInfo();
  2437         }
  2438 
  2439         /// <summary>
  2440         /// 
  2441         /// </summary>
  2442         void ShowClock()
  2443         {
  2444             if (!iDisplay.IsOpen())
  2445             {
  2446                 return;
  2447             }
  2448 
  2449             //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
  2450             iSkipFrameRendering = true;
  2451             //Clear our screen 
  2452             iDisplay.Clear();
  2453             iDisplay.SwapBuffers();
  2454             //Then show our clock
  2455             iDisplay.ShowClock();
  2456         }
  2457 
  2458         /// <summary>
  2459         /// 
  2460         /// </summary>
  2461         void HideClock()
  2462         {
  2463             if (!iDisplay.IsOpen())
  2464             {
  2465                 return;
  2466             }
  2467 
  2468             //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
  2469             iSkipFrameRendering = false;
  2470             iDisplay.HideClock();
  2471         }
  2472 
  2473         private void InstallUpdateSyncWithInfo()
  2474         {
  2475             UpdateCheckInfo info = null;
  2476 
  2477             if (ApplicationDeployment.IsNetworkDeployed)
  2478             {
  2479                 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
  2480 
  2481                 try
  2482                 {
  2483                     info = ad.CheckForDetailedUpdate();
  2484 
  2485                 }
  2486                 catch (DeploymentDownloadException dde)
  2487                 {
  2488                     MessageBox.Show(
  2489                         "The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " +
  2490                         dde.Message);
  2491                     return;
  2492                 }
  2493                 catch (InvalidDeploymentException ide)
  2494                 {
  2495                     MessageBox.Show(
  2496                         "Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " +
  2497                         ide.Message);
  2498                     return;
  2499                 }
  2500                 catch (InvalidOperationException ioe)
  2501                 {
  2502                     MessageBox.Show(
  2503                         "This application cannot be updated. It is likely not a ClickOnce application. Error: " +
  2504                         ioe.Message);
  2505                     return;
  2506                 }
  2507 
  2508                 if (info.UpdateAvailable)
  2509                 {
  2510                     Boolean doUpdate = true;
  2511 
  2512                     if (!info.IsUpdateRequired)
  2513                     {
  2514                         DialogResult dr =
  2515                             MessageBox.Show("An update is available. Would you like to update the application now?",
  2516                                 "Update Available", MessageBoxButtons.OKCancel);
  2517                         if (!(DialogResult.OK == dr))
  2518                         {
  2519                             doUpdate = false;
  2520                         }
  2521                     }
  2522                     else
  2523                     {
  2524                         // Display a message that the application MUST reboot. Display the minimum required version.
  2525                         MessageBox.Show("This application has detected a mandatory update from your current " +
  2526                                         "version to version " + info.MinimumRequiredVersion.ToString() +
  2527                                         ". The application will now install the update and restart.",
  2528                             "Update Available", MessageBoxButtons.OK,
  2529                             MessageBoxIcon.Information);
  2530                     }
  2531 
  2532                     if (doUpdate)
  2533                     {
  2534                         try
  2535                         {
  2536                             ad.Update();
  2537                             MessageBox.Show("The application has been upgraded, and will now restart.");
  2538                             Application.Restart();
  2539                         }
  2540                         catch (DeploymentDownloadException dde)
  2541                         {
  2542                             MessageBox.Show(
  2543                                 "Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " +
  2544                                 dde);
  2545                             return;
  2546                         }
  2547                     }
  2548                 }
  2549                 else
  2550                 {
  2551                     MessageBox.Show("You are already running the latest version.", "Application up-to-date");
  2552                 }
  2553             }
  2554         }
  2555 
  2556 
  2557         /// <summary>
  2558         /// Used to
  2559         /// </summary>
  2560         private void SysTrayHideShow()
  2561         {
  2562             Visible = !Visible;
  2563             if (Visible)
  2564             {
  2565                 Activate();
  2566                 WindowState = FormWindowState.Normal;
  2567             }
  2568         }
  2569 
  2570         /// <summary>
  2571         /// Use to handle minimize events.
  2572         /// </summary>
  2573         /// <param name="sender"></param>
  2574         /// <param name="e"></param>
  2575         private void MainForm_SizeChanged(object sender, EventArgs e)
  2576         {
  2577             if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
  2578             {
  2579                 if (Visible)
  2580                 {
  2581                     SysTrayHideShow();
  2582                 }
  2583             }
  2584         }
  2585 
  2586         /// <summary>
  2587         /// 
  2588         /// </summary>
  2589         /// <param name="sender"></param>
  2590         /// <param name="e"></param>
  2591         private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
  2592         {
  2593             //Our table layout size has changed which means our display size has changed.
  2594             //We need to re-create our bitmap.
  2595             iCreateBitmap = true;
  2596         }
  2597 
  2598         /// <summary>
  2599         /// 
  2600         /// </summary>
  2601         /// <param name="sender"></param>
  2602         /// <param name="e"></param>
  2603         private void buttonSelectFile_Click(object sender, EventArgs e)
  2604         {
  2605             //openFileDialog1.InitialDirectory = "c:\\";
  2606             //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
  2607             //openFileDialog.FilterIndex = 1;
  2608             openFileDialog.RestoreDirectory = true;
  2609 
  2610             if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
  2611             {
  2612                 labelStartFileName.Text = openFileDialog.FileName;
  2613                 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
  2614                 Properties.Settings.Default.Save();
  2615             }
  2616         }
  2617 
  2618         /// <summary>
  2619         /// 
  2620         /// </summary>
  2621         /// <param name="sender"></param>
  2622         /// <param name="e"></param>
  2623         private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
  2624         {
  2625             //Save the optical drive the user selected for ejection
  2626             Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
  2627             Properties.Settings.Default.Save();
  2628         }
  2629 
  2630 
  2631         /// <summary>
  2632         /// 
  2633         /// </summary>
  2634         private void LogsUpdate()
  2635         {
  2636             if (iWriter != null)
  2637             {
  2638                 iWriter.Flush();
  2639             }
  2640 
  2641         }
  2642 
  2643         /// <summary>
  2644         /// Broadcast messages to subscribers.
  2645         /// </summary>
  2646         /// <param name="message"></param>
  2647         protected override void WndProc(ref Message aMessage)
  2648         {
  2649             LogsUpdate();
  2650 
  2651             if (OnWndProc != null)
  2652             {
  2653                 OnWndProc(ref aMessage);
  2654             }
  2655 
  2656             base.WndProc(ref aMessage);
  2657         }
  2658 
  2659         private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
  2660         {
  2661             //Save CEC enabled status
  2662             Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
  2663             Properties.Settings.Default.Save();
  2664             //
  2665             ResetCec();
  2666         }
  2667 
  2668         private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
  2669         {
  2670             //Save CEC HDMI port
  2671             Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
  2672             Properties.Settings.Default.CecHdmiPort++;
  2673             Properties.Settings.Default.Save();
  2674             //
  2675             ResetCec();
  2676         }
  2677 
  2678         /// <summary>
  2679         /// 
  2680         /// </summary>
  2681         private void ResetCec()
  2682         {
  2683             if (iCecManager == null)
  2684             {
  2685                 //Thus skipping initial UI setup
  2686                 return;
  2687             }
  2688 
  2689             iCecManager.Stop();
  2690             //
  2691             if (Properties.Settings.Default.CecEnabled)
  2692             {
  2693                 iCecManager.Start(Handle, "CEC",
  2694                     Properties.Settings.Default.CecHdmiPort);
  2695 
  2696                 SetupCecLogLevel();
  2697             }
  2698         }
  2699 
  2700         /// <summary>
  2701         /// 
  2702         /// </summary>
  2703         private async void ResetHarmony()
  2704         {
  2705             // ConnectAsync already if we have an existing session cookie
  2706             if (Properties.Settings.Default.HarmonyEnabled && File.Exists("SessionToken"))
  2707             {
  2708 
  2709                 iButtonHarmonyConnect.Enabled = false;
  2710                 try
  2711                 {
  2712                     await ConnectHarmonyAsync();
  2713                 }
  2714                 finally
  2715                 {
  2716                     iButtonHarmonyConnect.Enabled = true;
  2717                 }
  2718             }
  2719         }
  2720 
  2721         /// <summary>
  2722         /// 
  2723         /// </summary>
  2724         private void SetupCecLogLevel()
  2725         {
  2726             //Setup log level
  2727             iCecManager.Client.LogLevel = 0;
  2728 
  2729             if (checkBoxCecLogError.Checked)
  2730                 iCecManager.Client.LogLevel |= (int) CecLogLevel.Error;
  2731 
  2732             if (checkBoxCecLogWarning.Checked)
  2733                 iCecManager.Client.LogLevel |= (int) CecLogLevel.Warning;
  2734 
  2735             if (checkBoxCecLogNotice.Checked)
  2736                 iCecManager.Client.LogLevel |= (int) CecLogLevel.Notice;
  2737 
  2738             if (checkBoxCecLogTraffic.Checked)
  2739                 iCecManager.Client.LogLevel |= (int) CecLogLevel.Traffic;
  2740 
  2741             if (checkBoxCecLogDebug.Checked)
  2742                 iCecManager.Client.LogLevel |= (int) CecLogLevel.Debug;
  2743 
  2744             iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
  2745 
  2746         }
  2747 
  2748         private void ButtonStartIdleClient_Click(object sender, EventArgs e)
  2749         {
  2750             StartIdleClient();
  2751         }
  2752 
  2753         private void buttonClearLogs_Click(object sender, EventArgs e)
  2754         {
  2755             richTextBoxLogs.Clear();
  2756         }
  2757 
  2758         private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)
  2759         {
  2760             SetupCecLogLevel();
  2761         }
  2762 
  2763 
  2764         /// <summary>
  2765         /// 
  2766         /// </summary>
  2767         /// <param name="aEvent"></param>
  2768         private void SelectEvent(Event aEvent)
  2769         {
  2770             if (aEvent == null)
  2771             {
  2772                 return;
  2773             }
  2774 
  2775             foreach (TreeNode node in iTreeViewEvents.Nodes)
  2776             {
  2777                 if (node.Tag == aEvent)
  2778                 {
  2779                     iTreeViewEvents.SelectedNode = node;
  2780                     iTreeViewEvents.Focus();
  2781                 }
  2782             }
  2783         }
  2784 
  2785 
  2786 
  2787         /// <summary>
  2788         /// Get the current event based on event tree view selection.
  2789         /// </summary>
  2790         /// <returns></returns>
  2791         private Event CurrentEvent()
  2792         {
  2793             //Walk up the tree from the selected node to find our event
  2794             TreeNode node = iTreeViewEvents.SelectedNode;
  2795             Event selectedEvent = null;
  2796             while (node != null)
  2797             {
  2798                 if (node.Tag is Event)
  2799                 {
  2800                     selectedEvent = (Event) node.Tag;
  2801                     break;
  2802                 }
  2803                 node = node.Parent;
  2804             }
  2805 
  2806             return selectedEvent;
  2807         }
  2808 
  2809         /// <summary>
  2810         /// Get the current action based on event tree view selection
  2811         /// </summary>
  2812         /// <returns></returns>
  2813         private SharpLib.Ear.Action CurrentAction()
  2814         {
  2815             TreeNode node = iTreeViewEvents.SelectedNode;
  2816             if (node != null && node.Tag is SharpLib.Ear.Action)
  2817             {
  2818                 return (SharpLib.Ear.Action) node.Tag;
  2819             }
  2820 
  2821             return null;
  2822         }
  2823 
  2824         /// <summary>
  2825         /// 
  2826         /// </summary>
  2827         /// <param name="sender"></param>
  2828         /// <param name="e"></param>
  2829         private void buttonActionAdd_Click(object sender, EventArgs e)
  2830         {
  2831             Event selectedEvent = CurrentEvent();
  2832             if (selectedEvent == null)
  2833             {
  2834                 //We did not find a corresponding event
  2835                 return;
  2836             }
  2837 
  2838             FormEditObject<SharpLib.Ear.Action> ea = new FormEditObject<SharpLib.Ear.Action>();
  2839             ea.Text = "Add action";
  2840             DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
  2841             if (res == DialogResult.OK)
  2842             {
  2843                 selectedEvent.Actions.Add(ea.Object);
  2844                 Properties.Settings.Default.Events = ManagerEventAction.Current;
  2845                 Properties.Settings.Default.Save();
  2846                 PopulateEventsTreeView();
  2847             }
  2848         }
  2849 
  2850         /// <summary>
  2851         /// 
  2852         /// </summary>
  2853         /// <param name="sender"></param>
  2854         /// <param name="e"></param>
  2855         private void buttonActionEdit_Click(object sender, EventArgs e)
  2856         {
  2857             Event selectedEvent = CurrentEvent();
  2858             SharpLib.Ear.Action selectedAction = CurrentAction();
  2859             if (selectedEvent == null || selectedAction == null)
  2860             {
  2861                 //We did not find a corresponding event
  2862                 return;
  2863             }
  2864 
  2865             FormEditObject<SharpLib.Ear.Action> ea = new FormEditObject<SharpLib.Ear.Action>();
  2866             ea.Text = "Edit action";
  2867             ea.Object = selectedAction;
  2868             int actionIndex = iTreeViewEvents.SelectedNode.Index;
  2869             DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
  2870             if (res == DialogResult.OK)
  2871             {
  2872                 //Update our action
  2873                 selectedEvent.Actions[actionIndex]=ea.Object;
  2874                 //Save and rebuild our event tree view
  2875                 Properties.Settings.Default.Events = ManagerEventAction.Current;
  2876                 Properties.Settings.Default.Save();
  2877                 PopulateEventsTreeView();
  2878             }
  2879         }
  2880 
  2881         /// <summary>
  2882         /// 
  2883         /// </summary>
  2884         /// <param name="sender"></param>
  2885         /// <param name="e"></param>
  2886         private void buttonActionDelete_Click(object sender, EventArgs e)
  2887         {
  2888 
  2889             SharpLib.Ear.Action action = CurrentAction();
  2890             if (action == null)
  2891             {
  2892                 //Must select action node
  2893                 return;
  2894             }
  2895 
  2896             ManagerEventAction.Current.RemoveAction(action);
  2897             Properties.Settings.Default.Events = ManagerEventAction.Current;
  2898             Properties.Settings.Default.Save();
  2899             PopulateEventsTreeView();
  2900         }
  2901 
  2902         /// <summary>
  2903         /// 
  2904         /// </summary>
  2905         /// <param name="sender"></param>
  2906         /// <param name="e"></param>
  2907         private void buttonActionTest_Click(object sender, EventArgs e)
  2908         {
  2909             SharpLib.Ear.Action a = CurrentAction();
  2910             if (a != null)
  2911             {
  2912                 a.Test();
  2913             }
  2914             iTreeViewEvents.Focus();
  2915         }
  2916 
  2917         /// <summary>
  2918         /// 
  2919         /// </summary>
  2920         /// <param name="sender"></param>
  2921         /// <param name="e"></param>
  2922         private void buttonActionMoveUp_Click(object sender, EventArgs e)
  2923         {
  2924             SharpLib.Ear.Action a = CurrentAction();
  2925             if (a == null || 
  2926                 //Action already at the top of the list
  2927                 iTreeViewEvents.SelectedNode.Index == 0)
  2928             {
  2929                 return;
  2930             }
  2931 
  2932             //Swap actions in event's action list
  2933             Event currentEvent = CurrentEvent();
  2934             int currentIndex = iTreeViewEvents.SelectedNode.Index;
  2935             SharpLib.Ear.Action movingUp = currentEvent.Actions[currentIndex];
  2936             SharpLib.Ear.Action movingDown = currentEvent.Actions[currentIndex-1];
  2937             currentEvent.Actions[currentIndex] = movingDown;
  2938             currentEvent.Actions[currentIndex-1] = movingUp;
  2939 
  2940             //Save and populate our tree again
  2941             Properties.Settings.Default.Events = ManagerEventAction.Current;
  2942             Properties.Settings.Default.Save();
  2943             PopulateEventsTreeView();
  2944 
  2945         }
  2946 
  2947         /// <summary>
  2948         /// 
  2949         /// </summary>
  2950         /// <param name="sender"></param>
  2951         /// <param name="e"></param>
  2952         private void buttonActionMoveDown_Click(object sender, EventArgs e)
  2953         {
  2954             SharpLib.Ear.Action a = CurrentAction();
  2955             if (a == null ||
  2956                 //Action already at the bottom of the list
  2957                 iTreeViewEvents.SelectedNode.Index == iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1)
  2958             {
  2959                 return;
  2960             }
  2961 
  2962             //Swap actions in event's action list
  2963             Event currentEvent = CurrentEvent();
  2964             int currentIndex = iTreeViewEvents.SelectedNode.Index;
  2965             SharpLib.Ear.Action movingDown = currentEvent.Actions[currentIndex];
  2966             SharpLib.Ear.Action movingUp = currentEvent.Actions[currentIndex + 1];
  2967             currentEvent.Actions[currentIndex] = movingUp;
  2968             currentEvent.Actions[currentIndex + 1] = movingDown;
  2969 
  2970             //Save and populate our tree again
  2971             Properties.Settings.Default.Events = ManagerEventAction.Current;
  2972             Properties.Settings.Default.Save();
  2973             PopulateEventsTreeView();
  2974         }
  2975 
  2976 
  2977         /// <summary>
  2978         /// 
  2979         /// </summary>
  2980         /// <param name="sender"></param>
  2981         /// <param name="e"></param>
  2982         private void buttonEventTest_Click(object sender, EventArgs e)
  2983         {
  2984             Event earEvent = CurrentEvent();
  2985             if (earEvent != null)
  2986             {
  2987                 earEvent.Test();
  2988             }
  2989         }
  2990 
  2991         /// <summary>
  2992         /// Manages events and actions buttons according to selected item in event tree.
  2993         /// </summary>
  2994         /// <param name="sender"></param>
  2995         /// <param name="e"></param>
  2996         private void iTreeViewEvents_AfterSelect(object sender, TreeViewEventArgs e)
  2997         {
  2998             UpdateEventView();
  2999         }
  3000 
  3001         /// <summary>
  3002         /// 
  3003         /// </summary>
  3004         private void UpdateEventView()
  3005         {
  3006             //One can always add an event
  3007             buttonEventAdd.Enabled = true;
  3008 
  3009             //Enable buttons according to selected item
  3010             buttonActionAdd.Enabled =
  3011             buttonEventTest.Enabled =
  3012             buttonEventDelete.Enabled =
  3013             buttonEventEdit.Enabled =
  3014                 CurrentEvent() != null;
  3015 
  3016             SharpLib.Ear.Action currentAction = CurrentAction();
  3017             //If an action is selected enable the following buttons
  3018             buttonActionTest.Enabled =
  3019             buttonActionDelete.Enabled =
  3020             buttonActionMoveUp.Enabled =
  3021             buttonActionMoveDown.Enabled =
  3022             buttonActionEdit.Enabled =
  3023                     currentAction != null;
  3024 
  3025             if (currentAction != null)
  3026             {
  3027                 //If an action is selected enable move buttons if needed
  3028                 buttonActionMoveUp.Enabled = iTreeViewEvents.SelectedNode.Index != 0;
  3029                 buttonActionMoveDown.Enabled = iTreeViewEvents.SelectedNode.Index <
  3030                                                iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1;
  3031             }
  3032         }
  3033 
  3034         private void buttonEventAdd_Click(object sender, EventArgs e)
  3035         {
  3036             FormEditObject<SharpLib.Ear.Event> ea = new FormEditObject<SharpLib.Ear.Event>();
  3037             ea.Text = "Add event";
  3038             DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
  3039             if (res == DialogResult.OK)
  3040             {
  3041                 ManagerEventAction.Current.Events.Add(ea.Object);
  3042                 Properties.Settings.Default.Events = ManagerEventAction.Current;
  3043                 Properties.Settings.Default.Save();
  3044                 PopulateEventsTreeView();
  3045                 SelectEvent(ea.Object);
  3046             }
  3047         }
  3048 
  3049         private void buttonEventDelete_Click(object sender, EventArgs e)
  3050         {
  3051             SharpLib.Ear.Event currentEvent = CurrentEvent();
  3052             if (currentEvent == null)
  3053             {
  3054                 //Must select action node
  3055                 return;
  3056             }
  3057 
  3058             ManagerEventAction.Current.Events.Remove(currentEvent);
  3059             Properties.Settings.Default.Events = ManagerEventAction.Current;
  3060             Properties.Settings.Default.Save();
  3061             PopulateEventsTreeView();
  3062         }
  3063 
  3064         private void buttonEventEdit_Click(object sender, EventArgs e)
  3065         {
  3066             Event selectedEvent = CurrentEvent();
  3067             if (selectedEvent == null)
  3068             {
  3069                 //We did not find a corresponding event
  3070                 return;
  3071             }
  3072 
  3073             FormEditObject<SharpLib.Ear.Event> ea = new FormEditObject<SharpLib.Ear.Event>();
  3074             ea.Text = "Edit event";
  3075             ea.Object = selectedEvent;
  3076             int actionIndex = iTreeViewEvents.SelectedNode.Index;
  3077             DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
  3078             if (res == DialogResult.OK)
  3079             {
  3080                 //Save and rebuild our event tree view
  3081                 Properties.Settings.Default.Events = ManagerEventAction.Current;
  3082                 Properties.Settings.Default.Save();
  3083                 PopulateEventsTreeView();
  3084             }
  3085         }
  3086 
  3087         private void iTreeViewEvents_Leave(object sender, EventArgs e)
  3088         {
  3089             //Make sure our event tree never looses focus
  3090             ((TreeView) sender).Focus();
  3091         }
  3092 
  3093         private async void iButtonHarmonyConnect_Click(object sender, EventArgs e)
  3094         {
  3095             //Save hub address
  3096             Properties.Settings.Default.HarmonyHubAddress = iTextBoxHarmonyHubAddress.Text;
  3097             Properties.Settings.Default.Save();
  3098 
  3099             iButtonHarmonyConnect.Enabled = false;
  3100             try
  3101             {
  3102                 await ConnectHarmonyAsync();
  3103             }
  3104             catch (Exception)
  3105             {
  3106                 iButtonHarmonyConnect.Enabled = true;
  3107             }
  3108 
  3109         }
  3110 
  3111 
  3112         private async Task ConnectHarmonyAsync()
  3113         {
  3114             Console.WriteLine("Harmony: Connecting... ");
  3115             //First create our client and login
  3116             if (File.Exists("SessionToken"))
  3117             {
  3118                 var sessionToken = File.ReadAllText("SessionToken");
  3119                 Console.WriteLine("Harmony: Reusing token: {0}", sessionToken);
  3120                 Program.HarmonyClient = HarmonyHub.HarmonyClient.Create(iTextBoxHarmonyHubAddress.Text, sessionToken);
  3121             }
  3122             else
  3123             {
  3124                 if (string.IsNullOrEmpty(iTextBoxLogitechPassword.Text))
  3125                 {
  3126                     Console.WriteLine("Harmony: Credentials missing!");
  3127                     return;
  3128                 }
  3129 
  3130                 Console.WriteLine("Harmony: Authenticating with Logitech servers...");
  3131                 Program.HarmonyClient = await HarmonyHub.HarmonyClient.Create(iTextBoxHarmonyHubAddress.Text, iTextBoxLogitechUserName.Text, iTextBoxLogitechPassword.Text);
  3132                 File.WriteAllText("SessionToken", Program.HarmonyClient.Token);
  3133             }
  3134 
  3135             Console.WriteLine("Harmony: Fetching Harmony Hub configuration...");
  3136 
  3137             //Fetch our config
  3138             var harmonyConfig = await Program.HarmonyClient.GetConfigAsync();
  3139             PopulateTreeViewHarmony(harmonyConfig);
  3140 
  3141             Console.WriteLine("Harmony: Ready");
  3142         }
  3143 
  3144         /// <summary>
  3145         /// 
  3146         /// </summary>
  3147         /// <param name="aConfig"></param>
  3148         private void PopulateTreeViewHarmony(HarmonyHub.Entities.Response.Config aConfig)
  3149         {
  3150             iTreeViewHarmony.Nodes.Clear();
  3151             //Add our devices
  3152             foreach (HarmonyHub.Entities.Response.Device device in aConfig.Devices)
  3153             {
  3154                 TreeNode deviceNode = iTreeViewHarmony.Nodes.Add(device.Id, $"{device.Label} ({device.DeviceTypeDisplayName}/{device.Model})");
  3155                 deviceNode.Tag = device;
  3156 
  3157                 foreach (HarmonyHub.Entities.Response.ControlGroup cg in device.ControlGroups)
  3158                 {
  3159                     TreeNode cgNode = deviceNode.Nodes.Add(cg.Name);
  3160                     cgNode.Tag = cg;
  3161 
  3162                     foreach (HarmonyHub.Entities.Response.Function f in cg.Functions)
  3163                     {
  3164                         TreeNode fNode = cgNode.Nodes.Add(f.Name);
  3165                         fNode.Tag = f;
  3166                     }
  3167                 }
  3168             }
  3169 
  3170             //treeViewConfig.ExpandAll();
  3171         }
  3172 
  3173         private void iCheckBoxHarmonyEnabled_CheckedChanged(object sender, EventArgs e)
  3174         {
  3175             Properties.Settings.Default.HarmonyEnabled = iCheckBoxHarmonyEnabled.Checked;
  3176             Properties.Settings.Default.Save();
  3177         }
  3178 
  3179         private async void iTreeViewHarmony_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
  3180         {
  3181             //Upon function node double click we execute it
  3182             var tag = e.Node.Tag as HarmonyHub.Entities.Response.Function;
  3183             if (tag != null && e.Node.Parent.Parent.Tag is HarmonyHub.Entities.Response.Device)
  3184             {
  3185                 HarmonyHub.Entities.Response.Function f = tag;
  3186                 HarmonyHub.Entities.Response.Device d = (HarmonyHub.Entities.Response.Device)e.Node.Parent.Parent.Tag;
  3187 
  3188                 Console.WriteLine($"Harmony: Sending {f.Name} to {d.Label}...");
  3189 
  3190                 await Program.HarmonyClient.SendCommandAsync(d.Id, f.Name);
  3191             }
  3192         }
  3193     }
  3194 }