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