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