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