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