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