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