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