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