Logs hour formatting switch to 24h format.
2 // Copyright (C) 2014-2015 Stéphane Lenclud.
4 // This file is part of SharpDisplayManager.
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.
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.
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/>.
21 using System.Collections.Generic;
22 using System.ComponentModel;
27 using System.Threading.Tasks;
28 using System.Windows.Forms;
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;
38 using NAudio.CoreAudioApi;
39 using NAudio.CoreAudioApi.Interfaces;
40 using System.Runtime.InteropServices;
45 using SharpDisplayClient;
47 using MiniDisplayInterop;
48 using SharpLib.Display;
50 namespace SharpDisplayManager
53 public delegate uint ColorProcessingDelegate(int aX, int aY, uint aPixel);
54 public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
55 //Delegates are used for our thread safe method
56 public delegate void AddClientDelegate(string aSessionId, ICallback aCallback);
57 public delegate void RemoveClientDelegate(string aSessionId);
58 public delegate void SetFieldDelegate(string SessionId, DataField aField);
59 public delegate void SetFieldsDelegate(string SessionId, System.Collections.Generic.IList<DataField> aFields);
60 public delegate void SetLayoutDelegate(string SessionId, TableLayout aLayout);
61 public delegate void SetClientNameDelegate(string aSessionId, string aName);
62 public delegate void SetClientPriorityDelegate(string aSessionId, uint aPriority);
63 public delegate void PlainUpdateDelegate();
64 public delegate void WndProcDelegate(ref Message aMessage);
67 /// Our Display manager main form
69 [System.ComponentModel.DesignerCategory("Form")]
70 public partial class MainForm : MainFormHid, IMMNotificationClient
72 DateTime LastTickTime;
74 System.Drawing.Bitmap iBmp;
75 bool iCreateBitmap; //Workaround render to bitmap issues when minimized
76 ServiceHost iServiceHost;
77 // Our collection of clients sorted by session id.
78 public Dictionary<string, ClientData> iClients;
79 // The name of the client which informations are currently displayed.
80 public string iCurrentClientSessionId;
81 ClientData iCurrentClientData;
85 public bool iSkipFrameRendering;
86 //Function pointer for pixel color filtering
87 ColorProcessingDelegate iColorFx;
88 //Function pointer for pixel X coordinate intercept
89 CoordinateTranslationDelegate iScreenX;
90 //Function pointer for pixel Y coordinate intercept
91 CoordinateTranslationDelegate iScreenY;
93 private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
94 private MMDevice iMultiMediaDevice;
96 private NetworkManager iNetworkManager;
99 /// CEC - Consumer Electronic Control.
100 /// Notably used to turn TV on and off as Windows broadcast monitor on and off notifications.
102 private ConsumerElectronicControl iCecManager;
105 /// Manage run when Windows startup option
107 private StartupManager iStartupManager;
110 /// System notification icon used to hide our application from the task bar.
112 private SharpLib.Notification.Control iNotifyIcon;
115 /// System recording notification icon.
117 private SharpLib.Notification.Control iRecordingNotification;
122 RichTextBoxTextWriter iWriter;
126 /// Allow user to receive window messages;
128 public event WndProcDelegate OnWndProc;
132 iSkipFrameRendering = false;
134 iCurrentClientSessionId = "";
135 iCurrentClientData = null;
136 LastTickTime = DateTime.Now;
137 //Instantiate our display and register for events notifications
138 iDisplay = new Display();
139 iDisplay.OnOpened += OnDisplayOpened;
140 iDisplay.OnClosed += OnDisplayClosed;
142 iClients = new Dictionary<string, ClientData>();
143 iStartupManager = new StartupManager();
144 iNotifyIcon = new SharpLib.Notification.Control();
145 iRecordingNotification = new SharpLib.Notification.Control();
147 //Have our designer initialize its controls
148 InitializeComponent();
150 //Redirect console output
151 iWriter = new RichTextBoxTextWriter(richTextBoxLogs);
152 Console.SetOut(iWriter);
154 //Populate device types
155 PopulateDeviceTypes();
157 //Populate optical drives
158 PopulateOpticalDrives();
160 //Initial status update
163 //We have a bug when drawing minimized and reusing our bitmap
164 //Though I could not reproduce it on Windows 10
165 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
166 iCreateBitmap = false;
168 //Minimize our window if desired
169 if (Properties.Settings.Default.StartMinimized)
171 WindowState = FormWindowState.Minimized;
179 /// <param name="sender"></param>
180 /// <param name="e"></param>
181 private void MainForm_Load(object sender, EventArgs e)
183 //Check if we are running a Click Once deployed application
184 if (ApplicationDeployment.IsNetworkDeployed)
186 //This is a proper Click Once installation, fetch and show our version number
187 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
191 //Not a proper Click Once installation, assuming development build then
192 this.Text += " - development";
196 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
197 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
198 UpdateAudioDeviceAndMasterVolumeThreadSafe();
201 iNetworkManager = new NetworkManager();
202 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
203 UpdateNetworkStatus();
206 iCecManager = new ConsumerElectronicControl();
207 OnWndProc += iCecManager.OnWndProc;
211 //Setup notification icon
214 //Setup recording notification
215 SetupRecordingNotification();
217 // To make sure start up with minimize to tray works
218 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
224 //When not debugging we want the screen to be empty until a client takes over
227 //When developing we want at least one client for testing
228 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
231 //Open display connection on start-up if needed
232 if (Properties.Settings.Default.DisplayConnectOnStartup)
234 OpenDisplayConnection();
237 //Start our server so that we can get client requests
240 //Register for HID events
241 RegisterHidDevices();
243 //Start Idle client if needed
244 if (Properties.Settings.Default.StartIdleClient)
251 /// Called when our display is opened.
253 /// <param name="aDisplay"></param>
254 private void OnDisplayOpened(Display aDisplay)
256 //Make sure we resume frame rendering
257 iSkipFrameRendering = false;
259 //Set our screen size now that our display is connected
260 //Our panelDisplay is the container of our tableLayoutPanel
261 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
262 //panelDisplay needs an extra 2 pixels for borders on each sides
263 //tableLayoutPanel will eventually be the exact size of our display
264 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
265 panelDisplay.Size = size;
267 //Our display was just opened, update our UI
269 //Initiate asynchronous request
270 iDisplay.RequestFirmwareRevision();
273 UpdateMasterVolumeThreadSafe();
275 UpdateNetworkStatus();
278 //Testing icon in debug, no arm done if icon not supported
279 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
280 //iDisplay.SetAllIconsStatus(2);
286 /// Called when our display is closed.
288 /// <param name="aDisplay"></param>
289 private void OnDisplayClosed(Display aDisplay)
291 //Our display was just closed, update our UI consequently
295 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
297 //Update network status
298 UpdateNetworkStatus();
302 /// Update our Network Status
304 private void UpdateNetworkStatus()
306 if (iDisplay.IsOpen())
308 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
309 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
314 int iLastNetworkIconIndex = 0;
315 int iUpdateCountSinceLastNetworkAnimation = 0;
320 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
322 iUpdateCountSinceLastNetworkAnimation++;
323 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
325 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
327 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
330 //Prevents div by zero and other undefined behavior
333 iLastNetworkIconIndex++;
334 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
335 for (int i=0;i<iconCount;i++)
337 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
339 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
343 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
352 /// Receive volume change notification and reflect changes on our slider.
354 /// <param name="data"></param>
355 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
357 UpdateMasterVolumeThreadSafe();
361 /// Update master volume when user moves our slider.
363 /// <param name="sender"></param>
364 /// <param name="e"></param>
365 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
367 //Just like Windows Volume Mixer we unmute if the volume is adjusted
368 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
369 //Set volume level according to our volume slider new position
370 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
375 /// Mute check box changed.
377 /// <param name="sender"></param>
378 /// <param name="e"></param>
379 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
381 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
385 /// Device State Changed
387 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
392 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
397 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
400 /// Default Device Changed
402 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
404 if (role == Role.Multimedia && flow == DataFlow.Render)
406 UpdateAudioDeviceAndMasterVolumeThreadSafe();
411 /// Property Value Changed
413 /// <param name="pwstrDeviceId"></param>
414 /// <param name="key"></param>
415 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
421 /// Update master volume indicators based our current system states.
422 /// This typically includes volume levels and mute status.
424 private void UpdateMasterVolumeThreadSafe()
426 if (this.InvokeRequired)
428 //Not in the proper thread, invoke ourselves
429 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
430 this.Invoke(d, new object[] { });
434 //Update volume slider
435 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
436 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
437 //Update mute checkbox
438 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
440 //If our display connection is open we need to update its icons
441 if (iDisplay.IsOpen())
443 //First take care our our volume level icons
444 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
445 if (volumeIconCount > 0)
447 //Compute current volume level from system level and the number of segments in our display volume bar.
448 //That tells us how many segments in our volume bar needs to be turned on.
449 float currentVolume = volumeLevelScalar * volumeIconCount;
450 int segmentOnCount = Convert.ToInt32(currentVolume);
451 //Check if our segment count was rounded up, this will later be used for half brightness segment
452 bool roundedUp = segmentOnCount > currentVolume;
454 for (int i = 0; i < volumeIconCount; i++)
456 if (i < segmentOnCount)
458 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
459 if (i == segmentOnCount - 1 && roundedUp)
462 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
467 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
472 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
477 //Take care of our mute icon
478 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
486 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
488 if (this.InvokeRequired)
490 //Not in the proper thread, invoke ourselves
491 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
492 this.Invoke(d, new object[] { });
496 //We are in the correct thread just go ahead.
499 //Get our master volume
500 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
502 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
504 //Show our volume in our track bar
505 UpdateMasterVolumeThreadSafe();
507 //Register to get volume modifications
508 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
510 trackBarMasterVolume.Enabled = true;
514 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
515 Debug.WriteLine(ex.ToString());
516 //Something went wrong S/PDIF device ca throw exception I guess
517 trackBarMasterVolume.Enabled = false;
524 private void PopulateDeviceTypes()
526 int count = Display.TypeCount();
528 for (int i = 0; i < count; i++)
530 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
537 private void PopulateOpticalDrives()
539 //Reset our list of drives
540 comboBoxOpticalDrives.Items.Clear();
541 comboBoxOpticalDrives.Items.Add("None");
543 //Go through each drives on our system and collected the optical ones in our list
544 DriveInfo[] allDrives = DriveInfo.GetDrives();
545 foreach (DriveInfo d in allDrives)
547 Debug.WriteLine("Drive " + d.Name);
548 Debug.WriteLine(" Drive type: {0}", d.DriveType);
550 if (d.DriveType==DriveType.CDRom)
552 //This is an optical drive, add it now
553 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
561 /// <returns></returns>
562 public string OpticalDriveToEject()
564 return comboBoxOpticalDrives.SelectedItem.ToString();
572 private void SetupTrayIcon()
574 iNotifyIcon.Icon = GetIcon("vfd.ico");
575 iNotifyIcon.Text = "Sharp Display Manager";
576 iNotifyIcon.Visible = true;
578 //Double click toggles visibility - typically brings up the application
579 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
584 //Adding a context menu, useful to be able to exit the application
585 ContextMenu contextMenu = new ContextMenu();
586 //Context menu item to toggle visibility
587 MenuItem hideShowItem = new MenuItem("Hide/Show");
588 hideShowItem.Click += delegate(object obj, EventArgs args)
592 contextMenu.MenuItems.Add(hideShowItem);
594 //Context menu item separator
595 contextMenu.MenuItems.Add(new MenuItem("-"));
597 //Context menu exit item
598 MenuItem exitItem = new MenuItem("Exit");
599 exitItem.Click += delegate(object obj, EventArgs args)
603 contextMenu.MenuItems.Add(exitItem);
605 iNotifyIcon.ContextMenu = contextMenu;
611 private void SetupRecordingNotification()
613 iRecordingNotification.Icon = GetIcon("record.ico");
614 iRecordingNotification.Text = "No recording";
615 iRecordingNotification.Visible = false;
619 /// Access icons from embedded resources.
621 /// <param name="aName"></param>
622 /// <returns></returns>
623 public static Icon GetIcon(string aName)
625 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
626 foreach (string name in names)
628 //Find a resource name that ends with the given name
629 if (name.EndsWith(aName))
631 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
633 return new Icon(stream);
643 /// Set our current client.
644 /// This will take care of applying our client layout and set data fields.
646 /// <param name="aSessionId"></param>
647 void SetCurrentClient(string aSessionId, bool aForce=false)
649 if (aSessionId == iCurrentClientSessionId)
651 //Given client is already the current one.
652 //Don't bother changing anything then.
656 ClientData requestedClientData = iClients[aSessionId];
658 //Check when was the last time we switched to that client
659 if (iCurrentClientData != null)
661 //Do not switch client if priority of current client is higher
662 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
668 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
669 //TODO: put that hard coded value as a client property
670 //Clients should be able to define how often they can be interrupted
671 //Thus a background client can set this to zero allowing any other client to interrupt at any time
672 //We could also compute this delay by looking at the requests frequencies?
674 requestedClientData.Priority == iCurrentClientData.Priority && //Time sharing is only if clients have the same priority
675 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
677 //Don't switch clients too often
682 //Set current client ID.
683 iCurrentClientSessionId = aSessionId;
684 //Set the time we last switched to that client
685 iClients[aSessionId].LastSwitchTime = DateTime.Now;
686 //Fetch and set current client data.
687 iCurrentClientData = requestedClientData;
688 //Apply layout and set data fields.
689 UpdateTableLayoutPanel(iCurrentClientData);
692 private void buttonFont_Click(object sender, EventArgs e)
694 //fontDialog.ShowColor = true;
695 //fontDialog.ShowApply = true;
696 fontDialog.ShowEffects = true;
697 fontDialog.Font = cds.Font;
699 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
701 //fontDialog.ShowHelp = true;
703 //fontDlg.MaxSize = 40;
704 //fontDlg.MinSize = 22;
706 //fontDialog.Parent = this;
707 //fontDialog.StartPosition = FormStartPosition.CenterParent;
709 //DlgBox.ShowDialog(fontDialog);
711 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
712 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
714 //Set the fonts to all our labels in our layout
715 foreach (Control ctrl in iTableLayoutPanel.Controls)
717 if (ctrl is MarqueeLabel)
719 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
724 cds.Font = fontDialog.Font;
725 Properties.Settings.Default.Save();
734 void CheckFontHeight()
736 //Show font height and width
737 labelFontHeight.Text = "Font height: " + cds.Font.Height;
738 float charWidth = IsFixedWidth(cds.Font);
739 if (charWidth == 0.0f)
741 labelFontWidth.Visible = false;
745 labelFontWidth.Visible = true;
746 labelFontWidth.Text = "Font width: " + charWidth;
749 MarqueeLabel label = null;
750 //Get the first label control we can find
751 foreach (Control ctrl in iTableLayoutPanel.Controls)
753 if (ctrl is MarqueeLabel)
755 label = (MarqueeLabel)ctrl;
760 //Now check font height and show a warning if needed.
761 if (label != null && label.Font.Height > label.Height)
763 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
764 labelWarning.Visible = true;
768 labelWarning.Visible = false;
773 private void buttonCapture_Click(object sender, EventArgs e)
775 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
776 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
777 //Bitmap bmpToSave = new Bitmap(bmp);
778 bmp.Save("D:\\capture.png");
780 ((MarqueeLabel)iTableLayoutPanel.Controls[0]).Text = "Captured";
783 string outputFileName = "d:\\capture.png";
784 using (MemoryStream memory = new MemoryStream())
786 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
788 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
789 byte[] bytes = memory.ToArray();
790 fs.Write(bytes, 0, bytes.Length);
797 private void CheckForRequestResults()
799 if (iDisplay.IsRequestPending())
801 switch (iDisplay.AttemptRequestCompletion())
803 case MiniDisplay.Request.FirmwareRevision:
804 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
805 //Issue next request then
806 iDisplay.RequestPowerSupplyStatus();
809 case MiniDisplay.Request.PowerSupplyStatus:
810 if (iDisplay.PowerSupplyStatus())
812 toolStripStatusLabelPower.Text = "ON";
816 toolStripStatusLabelPower.Text = "OFF";
818 //Issue next request then
819 iDisplay.RequestDeviceId();
822 case MiniDisplay.Request.DeviceId:
823 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
824 //No more request to issue
830 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
832 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
839 public static uint ColorUntouched(int aX, int aY, uint aPixel)
844 public static uint ColorInversed(int aX, int aY, uint aPixel)
849 public static uint ColorChessboard(int aX, int aY, uint aPixel)
851 if ((aX % 2 == 0) && (aY % 2 == 0))
855 else if ((aX % 2 != 0) && (aY % 2 != 0))
863 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
865 return aBmp.Width - aX - 1;
868 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
870 return iBmp.Height - aY - 1;
873 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
878 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
884 /// Select proper pixel delegates according to our current settings.
886 private void SetupPixelDelegates()
888 //Select our pixel processing routine
889 if (cds.InverseColors)
891 //iColorFx = ColorChessboard;
892 iColorFx = ColorInversed;
896 iColorFx = ColorWhiteIsOn;
899 //Select proper coordinate translation functions
900 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
901 if (cds.ReverseScreen)
903 iScreenX = ScreenReversedX;
904 iScreenY = ScreenReversedY;
914 //This is our timer tick responsible to perform our render
915 private void timer_Tick(object sender, EventArgs e)
917 //Not ideal cause this has nothing to do with display render
920 //Update our animations
921 DateTime NewTickTime = DateTime.Now;
923 UpdateNetworkSignal(LastTickTime, NewTickTime);
925 //Update animation for all our marquees
926 foreach (Control ctrl in iTableLayoutPanel.Controls)
928 if (ctrl is MarqueeLabel)
930 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
935 if (iDisplay.IsOpen())
937 CheckForRequestResults();
939 //Check if frame rendering is needed
940 //Typically used when showing clock
941 if (!iSkipFrameRendering)
946 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
947 iCreateBitmap = false;
949 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
950 //iBmp.Save("D:\\capture.png");
952 //Send it to our display
953 for (int i = 0; i < iBmp.Width; i++)
955 for (int j = 0; j < iBmp.Height; j++)
959 //Get our processed pixel coordinates
960 int x = iScreenX(iBmp, i);
961 int y = iScreenY(iBmp, j);
963 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
964 //Apply color effects
965 color = iColorFx(x, y, color);
967 iDisplay.SetPixel(x, y, color);
972 iDisplay.SwapBuffers();
976 //Compute instant FPS
977 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
979 LastTickTime = NewTickTime;
984 /// Attempt to establish connection with our display hardware.
986 private void OpenDisplayConnection()
988 CloseDisplayConnection();
990 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
993 toolStripStatusLabelConnect.Text = "Connection error";
997 private void CloseDisplayConnection()
999 //Status will be updated upon receiving the closed event
1001 if (iDisplay == null || !iDisplay.IsOpen())
1006 //Do not clear if we gave up on rendering already.
1007 //This means we will keep on displaying clock on MDM166AA for instance.
1008 if (!iSkipFrameRendering)
1011 iDisplay.SwapBuffers();
1014 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1018 private void buttonOpen_Click(object sender, EventArgs e)
1020 OpenDisplayConnection();
1023 private void buttonClose_Click(object sender, EventArgs e)
1025 CloseDisplayConnection();
1028 private void buttonClear_Click(object sender, EventArgs e)
1031 iDisplay.SwapBuffers();
1034 private void buttonFill_Click(object sender, EventArgs e)
1037 iDisplay.SwapBuffers();
1040 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1042 cds.Brightness = trackBarBrightness.Value;
1043 Properties.Settings.Default.Save();
1044 iDisplay.SetBrightness(trackBarBrightness.Value);
1050 /// CDS stands for Current Display Settings
1052 private DisplaySettings cds
1056 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1057 if (settings == null)
1059 settings = new DisplaysSettings();
1061 Properties.Settings.Default.DisplaysSettings = settings;
1064 //Make sure all our settings have been created
1065 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1067 settings.Displays.Add(new DisplaySettings());
1070 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1071 return displaySettings;
1076 /// Check if the given font has a fixed character pitch.
1078 /// <param name="ft"></param>
1079 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1080 public float IsFixedWidth(Font ft)
1082 Graphics g = CreateGraphics();
1083 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1084 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1086 bool fixedWidth = true;
1088 foreach (char c in charSizes)
1089 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1101 /// Synchronize UI with settings
1103 private void UpdateStatus()
1106 checkBoxShowBorders.Checked = cds.ShowBorders;
1107 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1109 //Set the proper font to each of our labels
1110 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1112 ctrl.Font = cds.Font;
1116 //Check if "run on Windows startup" is enabled
1117 checkBoxAutoStart.Checked = iStartupManager.Startup;
1119 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1120 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1121 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1122 iCheckBoxStartIdleClient.Checked = Properties.Settings.Default.StartIdleClient;
1123 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1126 //Try find our drive in our drive list
1127 int opticalDriveItemIndex=0;
1128 bool driveNotFound = true;
1129 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1130 foreach (object item in comboBoxOpticalDrives.Items)
1132 if (opticalDriveToEject == item.ToString())
1134 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1135 driveNotFound = false;
1138 opticalDriveItemIndex++;
1143 //We could not find the drive we had saved.
1144 //Select "None" then.
1145 comboBoxOpticalDrives.SelectedIndex = 0;
1149 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1150 checkBoxCecMonitorOn.Checked = Properties.Settings.Default.CecMonitorOn;
1151 checkBoxCecMonitorOff.Checked = Properties.Settings.Default.CecMonitorOff;
1152 checkBoxCecReconnectToPowerTv.Checked = Properties.Settings.Default.CecReconnectToPowerTv;
1153 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1155 //Mini Display settings
1156 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1157 checkBoxInverseColors.Checked = cds.InverseColors;
1158 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1159 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1160 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1161 labelMinFontSize.Enabled = cds.ScaleToFit;
1162 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1163 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1164 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1165 timer.Interval = cds.TimerInterval;
1166 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1167 textBoxScrollLoopSeparator.Text = cds.Separator;
1169 SetupPixelDelegates();
1171 if (iDisplay.IsOpen())
1173 //We have a display connection
1174 //Reflect that in our UI
1177 iTableLayoutPanel.Enabled = true;
1178 panelDisplay.Enabled = true;
1180 //Only setup brightness if display is open
1181 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1182 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1183 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1185 //Brightness out of range, this can occur when using auto-detect
1186 //Use max brightness instead
1187 trackBarBrightness.Value = iDisplay.MaxBrightness();
1188 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1192 trackBarBrightness.Value = cds.Brightness;
1193 iDisplay.SetBrightness(cds.Brightness);
1196 //Try compute the steps to something that makes sense
1197 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1198 trackBarBrightness.SmallChange = 1;
1201 buttonFill.Enabled = true;
1202 buttonClear.Enabled = true;
1203 buttonOpen.Enabled = false;
1204 buttonClose.Enabled = true;
1205 trackBarBrightness.Enabled = true;
1206 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1207 //+ " - " + iDisplay.SerialNumber();
1209 if (iDisplay.SupportPowerOnOff())
1211 buttonPowerOn.Enabled = true;
1212 buttonPowerOff.Enabled = true;
1216 buttonPowerOn.Enabled = false;
1217 buttonPowerOff.Enabled = false;
1220 if (iDisplay.SupportClock())
1222 buttonShowClock.Enabled = true;
1223 buttonHideClock.Enabled = true;
1227 buttonShowClock.Enabled = false;
1228 buttonHideClock.Enabled = false;
1232 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1233 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1235 if (cds.ShowVolumeLabel)
1237 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1241 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1246 //Display connection not available
1247 //Reflect that in our UI
1249 //In debug start our timer even if we don't have a display connection
1252 //In production environment we don't need our timer if no display connection
1255 checkBoxShowVolumeLabel.Enabled = false;
1256 iTableLayoutPanel.Enabled = false;
1257 panelDisplay.Enabled = false;
1258 buttonFill.Enabled = false;
1259 buttonClear.Enabled = false;
1260 buttonOpen.Enabled = true;
1261 buttonClose.Enabled = false;
1262 trackBarBrightness.Enabled = false;
1263 buttonPowerOn.Enabled = false;
1264 buttonPowerOff.Enabled = false;
1265 buttonShowClock.Enabled = false;
1266 buttonHideClock.Enabled = false;
1267 toolStripStatusLabelConnect.Text = "Disconnected";
1268 toolStripStatusLabelPower.Text = "N/A";
1277 /// <param name="sender"></param>
1278 /// <param name="e"></param>
1279 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1281 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1282 Properties.Settings.Default.Save();
1286 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1288 //Save our show borders setting
1289 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1290 cds.ShowBorders = checkBoxShowBorders.Checked;
1291 Properties.Settings.Default.Save();
1295 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1297 //Save our connect on startup setting
1298 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1299 Properties.Settings.Default.Save();
1302 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1304 //Save our "Minimize to tray" setting
1305 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1306 Properties.Settings.Default.Save();
1310 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1312 //Save our "Start minimized" setting
1313 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1314 Properties.Settings.Default.Save();
1317 private void checkBoxStartIdleClient_CheckedChanged(object sender, EventArgs e)
1319 Properties.Settings.Default.StartIdleClient = iCheckBoxStartIdleClient.Checked;
1320 Properties.Settings.Default.Save();
1323 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1325 iStartupManager.Startup = checkBoxAutoStart.Checked;
1329 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1331 //Save our reverse screen setting
1332 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1333 Properties.Settings.Default.Save();
1334 SetupPixelDelegates();
1337 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1339 //Save our inverse colors setting
1340 cds.InverseColors = checkBoxInverseColors.Checked;
1341 Properties.Settings.Default.Save();
1342 SetupPixelDelegates();
1345 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1347 //Save our scale to fit setting
1348 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1349 Properties.Settings.Default.Save();
1351 labelMinFontSize.Enabled = cds.ScaleToFit;
1352 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1355 private void MainForm_Resize(object sender, EventArgs e)
1357 if (WindowState == FormWindowState.Minimized)
1359 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1360 // That's apparently not needed on Windows 10 but we better leave it in place.
1361 iCreateBitmap = true;
1365 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1368 iNetworkManager.Dispose();
1369 CloseDisplayConnection();
1371 e.Cancel = iClosing;
1374 public void StartServer()
1376 iServiceHost = new ServiceHost
1379 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1382 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1383 iServiceHost.Open();
1386 public void StopServer()
1388 if (iClients.Count > 0 && !iClosing)
1392 BroadcastCloseEvent();
1396 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1398 iClosing = false; //We make sure we force close if asked twice
1403 //We removed that as it often lags for some reason
1404 //iServiceHost.Close();
1408 public void BroadcastCloseEvent()
1410 Trace.TraceInformation("BroadcastCloseEvent - start");
1412 var inactiveClients = new List<string>();
1413 foreach (var client in iClients)
1415 //if (client.Key != eventData.ClientName)
1419 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1420 client.Value.Callback.OnCloseOrder(/*eventData*/);
1422 catch (Exception ex)
1424 inactiveClients.Add(client.Key);
1429 if (inactiveClients.Count > 0)
1431 foreach (var client in inactiveClients)
1433 iClients.Remove(client);
1434 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(client, false)[0]);
1438 if (iClients.Count==0)
1445 /// Just remove all our fields.
1447 private void ClearLayout()
1449 iTableLayoutPanel.Controls.Clear();
1450 iTableLayoutPanel.RowStyles.Clear();
1451 iTableLayoutPanel.ColumnStyles.Clear();
1452 iCurrentClientData = null;
1456 /// Just launch a demo client.
1458 private void StartNewClient(string aTopText = "", string aBottomText = "")
1460 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1461 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1462 clientThread.Start(myParams);
1467 /// Just launch our idle client.
1469 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1471 Thread clientThread = new Thread(SharpDisplayIdleClient.Program.MainWithParams);
1472 SharpDisplayIdleClient.StartParams myParams = new SharpDisplayIdleClient.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1473 clientThread.Start(myParams);
1478 private void buttonStartClient_Click(object sender, EventArgs e)
1483 private void buttonSuspend_Click(object sender, EventArgs e)
1488 private void StartTimer()
1490 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1491 timer.Enabled = true;
1492 UpdateSuspendButton();
1495 private void StopTimer()
1497 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1498 timer.Enabled = false;
1499 UpdateSuspendButton();
1502 private void ToggleTimer()
1504 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1505 timer.Enabled = !timer.Enabled;
1506 UpdateSuspendButton();
1509 private void UpdateSuspendButton()
1513 buttonSuspend.Text = "Run";
1517 buttonSuspend.Text = "Pause";
1522 private void buttonCloseClients_Click(object sender, EventArgs e)
1524 BroadcastCloseEvent();
1527 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1529 //Root node must have at least one child
1530 if (e.Node.Nodes.Count == 0)
1535 //If the selected node is the root node of a client then switch to it
1536 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1537 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1539 //We have a valid session just switch to that client
1540 SetCurrentClient(sessionId,true);
1549 /// <param name="aSessionId"></param>
1550 /// <param name="aCallback"></param>
1551 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1553 if (this.InvokeRequired)
1555 //Not in the proper thread, invoke ourselves
1556 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1557 this.Invoke(d, new object[] { aSessionId, aCallback });
1561 //We are in the proper thread
1562 //Add this session to our collection of clients
1563 ClientData newClient = new ClientData(aSessionId, aCallback);
1564 Program.iMainForm.iClients.Add(aSessionId, newClient);
1565 //Add this session to our UI
1566 UpdateClientTreeViewNode(newClient);
1572 /// Find the client with the highest priority if any.
1574 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1575 public ClientData FindHighestPriorityClient()
1577 ClientData highestPriorityClient = null;
1578 foreach (var client in iClients)
1580 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1582 highestPriorityClient = client.Value;
1586 return highestPriorityClient;
1592 /// <param name="aSessionId"></param>
1593 public void RemoveClientThreadSafe(string aSessionId)
1595 if (this.InvokeRequired)
1597 //Not in the proper thread, invoke ourselves
1598 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1599 this.Invoke(d, new object[] { aSessionId });
1603 //We are in the proper thread
1604 //Remove this session from both client collection and UI tree view
1605 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1607 Program.iMainForm.iClients.Remove(aSessionId);
1608 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1609 //Update recording status too whenever a client is removed
1610 UpdateRecordingNotification();
1613 if (iCurrentClientSessionId == aSessionId)
1615 //The current client is closing
1616 iCurrentClientData = null;
1617 //Find the client with the highest priority and set it as current
1618 ClientData newCurrentClient = FindHighestPriorityClient();
1619 if (newCurrentClient!=null)
1621 SetCurrentClient(newCurrentClient.SessionId, true);
1625 if (iClients.Count == 0)
1627 //Clear our screen when last client disconnects
1632 //We were closing our form
1633 //All clients are now closed
1634 //Just resume our close operation
1645 /// <param name="aSessionId"></param>
1646 /// <param name="aLayout"></param>
1647 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1649 if (this.InvokeRequired)
1651 //Not in the proper thread, invoke ourselves
1652 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1653 this.Invoke(d, new object[] { aSessionId, aLayout });
1657 ClientData client = iClients[aSessionId];
1660 //Don't change a thing if the layout is the same
1661 if (!client.Layout.IsSameAs(aLayout))
1663 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1664 //Set our client layout then
1665 client.Layout = aLayout;
1666 //So that next time we update all our fields at ones
1667 client.HasNewLayout = true;
1668 //Layout has changed clear our fields then
1669 client.Fields.Clear();
1671 UpdateClientTreeViewNode(client);
1675 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1684 /// <param name="aSessionId"></param>
1685 /// <param name="aField"></param>
1686 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1688 if (this.InvokeRequired)
1690 //Not in the proper thread, invoke ourselves
1691 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1692 this.Invoke(d, new object[] { aSessionId, aField });
1696 //We are in the proper thread
1697 //Call the non-thread-safe variant
1698 SetClientField(aSessionId, aField);
1706 /// Set a data field in the given client.
1708 /// <param name="aSessionId"></param>
1709 /// <param name="aField"></param>
1710 private void SetClientField(string aSessionId, DataField aField)
1712 //TODO: should check if the field actually changed?
1714 ClientData client = iClients[aSessionId];
1715 bool layoutChanged = false;
1716 bool contentChanged = true;
1718 //Fetch our field index
1719 int fieldIndex = client.FindSameFieldIndex(aField);
1723 //No corresponding field, just bail out
1727 //Keep our previous field in there
1728 DataField previousField = client.Fields[fieldIndex];
1729 //Just update that field then
1730 client.Fields[fieldIndex] = aField;
1732 if (!aField.IsTableField)
1734 //We are done then if that field is not in our table layout
1738 TableField tableField = (TableField) aField;
1740 if (previousField.IsSameLayout(aField))
1742 //If we are updating a field in our current client we need to update it in our panel
1743 if (aSessionId == iCurrentClientSessionId)
1745 Control ctrl=iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1746 if (aField.IsTextField && ctrl is MarqueeLabel)
1748 TextField textField=(TextField)aField;
1749 //Text field control already in place, just change the text
1750 MarqueeLabel label = (MarqueeLabel)ctrl;
1751 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1752 label.Text = textField.Text;
1753 label.TextAlign = textField.Alignment;
1755 else if (aField.IsBitmapField && ctrl is PictureBox)
1757 BitmapField bitmapField = (BitmapField)aField;
1758 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1759 //Bitmap field control already in place just change the bitmap
1760 PictureBox pictureBox = (PictureBox)ctrl;
1761 pictureBox.Image = bitmapField.Bitmap;
1765 layoutChanged = true;
1771 layoutChanged = true;
1774 //If either content or layout changed we need to update our tree view to reflect the changes
1775 if (contentChanged || layoutChanged)
1777 UpdateClientTreeViewNode(client);
1781 Debug.Print("Layout changed");
1782 //Our layout has changed, if we are already the current client we need to update our panel
1783 if (aSessionId == iCurrentClientSessionId)
1785 //Apply layout and set data fields.
1786 UpdateTableLayoutPanel(iCurrentClientData);
1791 Debug.Print("Layout has not changed.");
1796 Debug.Print("WARNING: content and layout have not changed!");
1799 //When a client field is set we try switching to this client to present the new information to our user
1800 SetCurrentClient(aSessionId);
1806 /// <param name="aTexts"></param>
1807 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1809 if (this.InvokeRequired)
1811 //Not in the proper thread, invoke ourselves
1812 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1813 this.Invoke(d, new object[] { aSessionId, aFields });
1817 ClientData client = iClients[aSessionId];
1819 if (client.HasNewLayout)
1821 //TODO: Assert client.Count == 0
1822 //Our layout was just changed
1823 //Do some special handling to avoid re-creating our panel N times, once for each fields
1824 client.HasNewLayout = false;
1825 //Just set all our fields then
1826 client.Fields.AddRange(aFields);
1827 //Try switch to that client
1828 SetCurrentClient(aSessionId);
1830 //If we are updating the current client update our panel
1831 if (aSessionId == iCurrentClientSessionId)
1833 //Apply layout and set data fields.
1834 UpdateTableLayoutPanel(iCurrentClientData);
1837 UpdateClientTreeViewNode(client);
1841 //Put each our text fields in a label control
1842 foreach (DataField field in aFields)
1844 SetClientField(aSessionId, field);
1853 /// <param name="aSessionId"></param>
1854 /// <param name="aName"></param>
1855 public void SetClientNameThreadSafe(string aSessionId, string aName)
1857 if (this.InvokeRequired)
1859 //Not in the proper thread, invoke ourselves
1860 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1861 this.Invoke(d, new object[] { aSessionId, aName });
1865 //We are in the proper thread
1867 ClientData client = iClients[aSessionId];
1871 client.Name = aName;
1872 //Update our tree-view
1873 UpdateClientTreeViewNode(client);
1879 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1881 if (this.InvokeRequired)
1883 //Not in the proper thread, invoke ourselves
1884 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1885 this.Invoke(d, new object[] { aSessionId, aPriority });
1889 //We are in the proper thread
1891 ClientData client = iClients[aSessionId];
1895 client.Priority = aPriority;
1896 //Update our tree-view
1897 UpdateClientTreeViewNode(client);
1898 //Change our current client as per new priority
1899 ClientData newCurrentClient = FindHighestPriorityClient();
1900 if (newCurrentClient!=null)
1902 SetCurrentClient(newCurrentClient.SessionId);
1911 /// <param name="value"></param>
1912 /// <param name="maxChars"></param>
1913 /// <returns></returns>
1914 public static string Truncate(string value, int maxChars)
1916 return value.Length <= maxChars ? value : value.Substring(0, maxChars-3) + "...";
1920 /// Update our recording notification.
1922 private void UpdateRecordingNotification()
1925 bool activeRecording = false;
1927 RecordingField recField=new RecordingField();
1928 foreach (var client in iClients)
1930 RecordingField rec=(RecordingField)client.Value.FindSameFieldAs(recField);
1931 if (rec!=null && rec.IsActive)
1933 activeRecording = true;
1934 //Don't break cause we are collecting the names/texts.
1935 if (!String.IsNullOrEmpty(rec.Text))
1937 text += (rec.Text + "\n");
1941 //Not text for that recording, use client name instead
1942 text += client.Value.Name + " recording\n";
1948 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1949 iRecordingNotification.Text = Truncate(text,63);
1951 //Change visibility of notification if needed
1952 if (iRecordingNotification.Visible != activeRecording)
1954 iRecordingNotification.Visible = activeRecording;
1955 //Assuming the notification icon is in sync with our display icon
1956 //Take care of our REC icon
1957 if (iDisplay.IsOpen())
1959 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
1967 /// <param name="aClient"></param>
1968 private void UpdateClientTreeViewNode(ClientData aClient)
1970 Debug.Print("UpdateClientTreeViewNode");
1972 if (aClient == null)
1977 //Hook in record icon update too
1978 UpdateRecordingNotification();
1980 TreeNode node = null;
1981 //Check that our client node already exists
1982 //Get our client root node using its key which is our session ID
1983 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
1984 if (nodes.Count()>0)
1986 //We already have a node for that client
1988 //Clear children as we are going to recreate them below
1993 //Client node does not exists create a new one
1994 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1995 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
2001 if (!String.IsNullOrEmpty(aClient.Name))
2003 //We have a name, use it as text for our root node
2004 node.Text = aClient.Name;
2005 //Add a child with SessionId
2006 node.Nodes.Add(new TreeNode(aClient.SessionId));
2010 //No name, use session ID instead
2011 node.Text = aClient.SessionId;
2014 //Display client priority
2015 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2017 if (aClient.Fields.Count > 0)
2019 //Create root node for our texts
2020 TreeNode textsRoot = new TreeNode("Fields");
2021 node.Nodes.Add(textsRoot);
2022 //For each text add a new entry
2023 foreach (DataField field in aClient.Fields)
2025 if (field.IsTextField)
2027 TextField textField = (TextField)field;
2028 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2030 else if (field.IsBitmapField)
2032 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2034 else if (field.IsRecordingField)
2036 RecordingField recordingField = (RecordingField)field;
2037 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2047 /// Update our table layout row styles to make sure each rows have similar height
2049 private void UpdateTableLayoutRowStyles()
2051 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2053 rowStyle.SizeType = SizeType.Percent;
2054 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
2059 /// Update our display table layout.
2060 /// Will instanciated every field control as defined by our client.
2061 /// Fields must be specified by rows from the left.
2063 /// <param name="aLayout"></param>
2064 private void UpdateTableLayoutPanel(ClientData aClient)
2066 Debug.Print("UpdateTableLayoutPanel");
2068 if (aClient == null)
2075 TableLayout layout = aClient.Layout;
2077 //First clean our current panel
2078 iTableLayoutPanel.Controls.Clear();
2079 iTableLayoutPanel.RowStyles.Clear();
2080 iTableLayoutPanel.ColumnStyles.Clear();
2081 iTableLayoutPanel.RowCount = 0;
2082 iTableLayoutPanel.ColumnCount = 0;
2084 //Then recreate our rows...
2085 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2087 iTableLayoutPanel.RowCount++;
2091 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2093 iTableLayoutPanel.ColumnCount++;
2097 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2099 //Create our column styles
2100 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2103 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2107 //Create our row styles
2108 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2118 foreach (DataField field in aClient.Fields)
2120 if (!field.IsTableField)
2122 //That field is not taking part in our table layout skip it
2126 TableField tableField = (TableField)field;
2128 //Create a control corresponding to the field specified for that cell
2129 Control control = CreateControlForDataField(tableField);
2131 //Add newly created control to our table layout at the specified row and column
2132 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2133 //Make sure we specify column and row span for that new control
2134 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2135 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2143 /// Check our type of data field and create corresponding control
2145 /// <param name="aField"></param>
2146 private Control CreateControlForDataField(DataField aField)
2148 Control control=null;
2149 if (aField.IsTextField)
2151 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2152 label.AutoEllipsis = true;
2153 label.AutoSize = true;
2154 label.BackColor = System.Drawing.Color.Transparent;
2155 label.Dock = System.Windows.Forms.DockStyle.Fill;
2156 label.Location = new System.Drawing.Point(1, 1);
2157 label.Margin = new System.Windows.Forms.Padding(0);
2158 label.Name = "marqueeLabel" + aField;
2159 label.OwnTimer = false;
2160 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2161 label.Separator = cds.Separator;
2162 label.MinFontSize = cds.MinFontSize;
2163 label.ScaleToFit = cds.ScaleToFit;
2164 //control.Size = new System.Drawing.Size(254, 30);
2165 //control.TabIndex = 2;
2166 label.Font = cds.Font;
2168 TextField field = (TextField)aField;
2169 label.TextAlign = field.Alignment;
2170 label.UseCompatibleTextRendering = true;
2171 label.Text = field.Text;
2175 else if (aField.IsBitmapField)
2177 //Create picture box
2178 PictureBox picture = new PictureBox();
2179 picture.AutoSize = true;
2180 picture.BackColor = System.Drawing.Color.Transparent;
2181 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2182 picture.Location = new System.Drawing.Point(1, 1);
2183 picture.Margin = new System.Windows.Forms.Padding(0);
2184 picture.Name = "pictureBox" + aField;
2186 BitmapField field = (BitmapField)aField;
2187 picture.Image = field.Bitmap;
2191 //TODO: Handle recording field?
2197 /// Called when the user selected a new display type.
2199 /// <param name="sender"></param>
2200 /// <param name="e"></param>
2201 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2203 //Store the selected display type in our settings
2204 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2205 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2206 Properties.Settings.Default.Save();
2208 //Try re-opening the display connection if we were already connected.
2209 //Otherwise just update our status to reflect display type change.
2210 if (iDisplay.IsOpen())
2212 OpenDisplayConnection();
2220 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2222 if (maskedTextBoxTimerInterval.Text != "")
2224 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2228 timer.Interval = interval;
2229 cds.TimerInterval = timer.Interval;
2230 Properties.Settings.Default.Save();
2235 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2237 if (maskedTextBoxMinFontSize.Text != "")
2239 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2241 if (minFontSize > 0)
2243 cds.MinFontSize = minFontSize;
2244 Properties.Settings.Default.Save();
2245 //We need to recreate our layout for that change to take effect
2246 UpdateTableLayoutPanel(iCurrentClientData);
2252 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2254 if (maskedTextBoxScrollingSpeed.Text != "")
2256 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2258 if (scrollingSpeed > 0)
2260 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2261 Properties.Settings.Default.Save();
2262 //We need to recreate our layout for that change to take effect
2263 UpdateTableLayoutPanel(iCurrentClientData);
2268 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2270 cds.Separator = textBoxScrollLoopSeparator.Text;
2271 Properties.Settings.Default.Save();
2273 //Update our text fields
2274 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2276 ctrl.Separator = cds.Separator;
2281 private void buttonPowerOn_Click(object sender, EventArgs e)
2286 private void buttonPowerOff_Click(object sender, EventArgs e)
2288 iDisplay.PowerOff();
2291 private void buttonShowClock_Click(object sender, EventArgs e)
2296 private void buttonHideClock_Click(object sender, EventArgs e)
2301 private void buttonUpdate_Click(object sender, EventArgs e)
2303 InstallUpdateSyncWithInfo();
2311 if (!iDisplay.IsOpen())
2316 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2317 iSkipFrameRendering = true;
2320 iDisplay.SwapBuffers();
2321 //Then show our clock
2322 iDisplay.ShowClock();
2330 if (!iDisplay.IsOpen())
2335 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2336 iSkipFrameRendering = false;
2337 iDisplay.HideClock();
2340 private void InstallUpdateSyncWithInfo()
2342 UpdateCheckInfo info = null;
2344 if (ApplicationDeployment.IsNetworkDeployed)
2346 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2350 info = ad.CheckForDetailedUpdate();
2353 catch (DeploymentDownloadException dde)
2355 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);
2358 catch (InvalidDeploymentException ide)
2360 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);
2363 catch (InvalidOperationException ioe)
2365 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2369 if (info.UpdateAvailable)
2371 Boolean doUpdate = true;
2373 if (!info.IsUpdateRequired)
2375 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2376 if (!(DialogResult.OK == dr))
2383 // Display a message that the application MUST reboot. Display the minimum required version.
2384 MessageBox.Show("This application has detected a mandatory update from your current " +
2385 "version to version " + info.MinimumRequiredVersion.ToString() +
2386 ". The application will now install the update and restart.",
2387 "Update Available", MessageBoxButtons.OK,
2388 MessageBoxIcon.Information);
2396 MessageBox.Show("The application has been upgraded, and will now restart.");
2397 Application.Restart();
2399 catch (DeploymentDownloadException dde)
2401 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2408 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2417 private void SysTrayHideShow()
2423 WindowState = FormWindowState.Normal;
2428 /// Use to handle minimize events.
2430 /// <param name="sender"></param>
2431 /// <param name="e"></param>
2432 private void MainForm_SizeChanged(object sender, EventArgs e)
2434 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2446 /// <param name="sender"></param>
2447 /// <param name="e"></param>
2448 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2450 //Our table layout size has changed which means our display size has changed.
2451 //We need to re-create our bitmap.
2452 iCreateBitmap = true;
2458 /// <param name="sender"></param>
2459 /// <param name="e"></param>
2460 private void buttonSelectFile_Click(object sender, EventArgs e)
2462 //openFileDialog1.InitialDirectory = "c:\\";
2463 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2464 //openFileDialog.FilterIndex = 1;
2465 openFileDialog.RestoreDirectory = true;
2467 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2469 labelStartFileName.Text = openFileDialog.FileName;
2470 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2471 Properties.Settings.Default.Save();
2478 /// <param name="sender"></param>
2479 /// <param name="e"></param>
2480 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2482 //Save the optical drive the user selected for ejection
2483 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2484 Properties.Settings.Default.Save();
2491 private void LogsUpdate()
2493 if (iWriter != null)
2501 /// Broadcast messages to subscribers.
2503 /// <param name="message"></param>
2504 protected override void WndProc(ref Message aMessage)
2508 if (OnWndProc!=null)
2510 OnWndProc(ref aMessage);
2513 base.WndProc(ref aMessage);
2516 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2518 //Save CEC enabled status
2519 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2520 Properties.Settings.Default.Save();
2525 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2527 //Save CEC HDMI port
2528 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2529 Properties.Settings.Default.CecHdmiPort++;
2530 Properties.Settings.Default.Save();
2535 private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
2537 Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
2538 Properties.Settings.Default.Save();
2543 private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
2545 Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
2546 Properties.Settings.Default.Save();
2551 private void checkBoxCecReconnectToPowerTv_CheckedChanged(object sender, EventArgs e)
2553 Properties.Settings.Default.CecReconnectToPowerTv = checkBoxCecReconnectToPowerTv.Checked;
2554 Properties.Settings.Default.Save();
2562 private void ResetCec()
2564 if (iCecManager==null)
2566 //Thus skipping initial UI setup
2572 if (Properties.Settings.Default.CecEnabled)
2574 iCecManager.Start(Handle, "CEC",
2575 Properties.Settings.Default.CecHdmiPort,
2576 Properties.Settings.Default.CecMonitorOn,
2577 Properties.Settings.Default.CecMonitorOff,
2578 Properties.Settings.Default.CecReconnectToPowerTv);
2587 private void SetupCecLogLevel()
2590 iCecManager.Client.LogLevel = 0;
2592 if (checkBoxCecLogError.Checked)
2593 iCecManager.Client.LogLevel |= (int)CecLogLevel.Error;
2595 if (checkBoxCecLogWarning.Checked)
2596 iCecManager.Client.LogLevel |= (int)CecLogLevel.Warning;
2598 if (checkBoxCecLogNotice.Checked)
2599 iCecManager.Client.LogLevel |= (int)CecLogLevel.Notice;
2601 if (checkBoxCecLogTraffic.Checked)
2602 iCecManager.Client.LogLevel |= (int)CecLogLevel.Traffic;
2604 if (checkBoxCecLogDebug.Checked)
2605 iCecManager.Client.LogLevel |= (int)CecLogLevel.Debug;
2607 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2611 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2616 private void buttonClearLogs_Click(object sender, EventArgs e)
2618 richTextBoxLogs.Clear();
2621 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)