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