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