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