Fixing our mess.
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;
44 using SharpDisplayClient;
46 using MiniDisplayInterop;
47 using SharpLib.Display;
49 namespace SharpDisplayManager
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);
66 /// Our Display manager main form
68 [System.ComponentModel.DesignerCategory("Form")]
69 public partial class MainForm : MainFormHid, IMMNotificationClient
71 DateTime LastTickTime;
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;
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;
92 private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
93 private MMDevice iMultiMediaDevice;
95 private NetworkManager iNetworkManager;
98 /// CEC - Consumer Electronic Control.
99 /// Notably used to turn TV on and off as Windows broadcast monitor on and off notifications.
101 private ConsumerElectronicControl iCecManager;
104 /// Manage run when Windows startup option
106 private StartupManager iStartupManager;
109 /// System notification icon used to hide our application from the task bar.
111 private SharpLib.Notification.Control iNotifyIcon;
114 /// System recording notifcation icon.
116 private SharpLib.Notification.Control iRecordingNotification;
120 /// Allow user to receive window messages;
122 public event WndProcDelegate OnWndProc;
126 iSkipFrameRendering = false;
128 iCurrentClientSessionId = "";
129 iCurrentClientData = null;
130 LastTickTime = DateTime.Now;
131 //Instantiate our display and register for events notifications
132 iDisplay = new Display();
133 iDisplay.OnOpened += OnDisplayOpened;
134 iDisplay.OnClosed += OnDisplayClosed;
136 iClients = new Dictionary<string, ClientData>();
137 iStartupManager = new StartupManager();
138 iNotifyIcon = new SharpLib.Notification.Control();
139 iRecordingNotification = new SharpLib.Notification.Control();
141 //Have our designer initialize its controls
142 InitializeComponent();
144 //Populate device types
145 PopulateDeviceTypes();
147 //Populate optical drives
148 PopulateOpticalDrives();
150 //Initial status update
153 //We have a bug when drawing minimized and reusing our bitmap
154 //Though I could not reproduce it on Windows 10
155 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
156 iCreateBitmap = false;
158 //Minimize our window if desired
159 if (Properties.Settings.Default.StartMinimized)
161 WindowState = FormWindowState.Minimized;
169 /// <param name="sender"></param>
170 /// <param name="e"></param>
171 private void MainForm_Load(object sender, EventArgs e)
173 //Check if we are running a Click Once deployed application
174 if (ApplicationDeployment.IsNetworkDeployed)
176 //This is a proper Click Once installation, fetch and show our version number
177 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
181 //Not a proper Click Once installation, assuming development build then
182 this.Text += " - development";
186 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
187 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
188 UpdateAudioDeviceAndMasterVolumeThreadSafe();
191 iNetworkManager = new NetworkManager();
192 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
193 UpdateNetworkStatus();
196 iCecManager = new ConsumerElectronicControl();
197 OnWndProc += iCecManager.OnWndProc;
201 //Setup notification icon
204 //Setup recording notification
205 SetupRecordingNotification();
207 // To make sure start up with minimize to tray works
208 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
214 //When not debugging we want the screen to be empty until a client takes over
217 //When developing we want at least one client for testing
218 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
221 //Open display connection on start-up if needed
222 if (Properties.Settings.Default.DisplayConnectOnStartup)
224 OpenDisplayConnection();
227 //Start our server so that we can get client requests
230 //Register for HID events
231 RegisterHidDevices();
235 /// Called when our display is opened.
237 /// <param name="aDisplay"></param>
238 private void OnDisplayOpened(Display aDisplay)
240 //Make sure we resume frame rendering
241 iSkipFrameRendering = false;
243 //Set our screen size now that our display is connected
244 //Our panelDisplay is the container of our tableLayoutPanel
245 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
246 //panelDisplay needs an extra 2 pixels for borders on each sides
247 //tableLayoutPanel will eventually be the exact size of our display
248 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
249 panelDisplay.Size = size;
251 //Our display was just opened, update our UI
253 //Initiate asynchronous request
254 iDisplay.RequestFirmwareRevision();
257 UpdateMasterVolumeThreadSafe();
259 UpdateNetworkStatus();
262 //Testing icon in debug, no arm done if icon not supported
263 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
264 //iDisplay.SetAllIconsStatus(2);
270 /// Called when our display is closed.
272 /// <param name="aDisplay"></param>
273 private void OnDisplayClosed(Display aDisplay)
275 //Our display was just closed, update our UI consequently
279 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
281 //Update network status
282 UpdateNetworkStatus();
286 /// Update our Network Status
288 private void UpdateNetworkStatus()
290 if (iDisplay.IsOpen())
292 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
293 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
298 int iLastNetworkIconIndex = 0;
299 int iUpdateCountSinceLastNetworkAnimation = 0;
304 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
306 iUpdateCountSinceLastNetworkAnimation++;
307 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
309 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
311 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
314 //Prevents div by zero and other undefined behavior
317 iLastNetworkIconIndex++;
318 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
319 for (int i=0;i<iconCount;i++)
321 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
323 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
327 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
336 /// Receive volume change notification and reflect changes on our slider.
338 /// <param name="data"></param>
339 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
341 UpdateMasterVolumeThreadSafe();
345 /// Update master volume when user moves our slider.
347 /// <param name="sender"></param>
348 /// <param name="e"></param>
349 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
351 //Just like Windows Volume Mixer we unmute if the volume is adjusted
352 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
353 //Set volume level according to our volume slider new position
354 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
359 /// Mute check box changed.
361 /// <param name="sender"></param>
362 /// <param name="e"></param>
363 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
365 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
369 /// Device State Changed
371 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
376 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
381 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
384 /// Default Device Changed
386 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
388 if (role == Role.Multimedia && flow == DataFlow.Render)
390 UpdateAudioDeviceAndMasterVolumeThreadSafe();
395 /// Property Value Changed
397 /// <param name="pwstrDeviceId"></param>
398 /// <param name="key"></param>
399 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
405 /// Update master volume indicators based our current system states.
406 /// This typically includes volume levels and mute status.
408 private void UpdateMasterVolumeThreadSafe()
410 if (this.InvokeRequired)
412 //Not in the proper thread, invoke ourselves
413 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
414 this.Invoke(d, new object[] { });
418 //Update volume slider
419 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
420 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
421 //Update mute checkbox
422 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
424 //If our display connection is open we need to update its icons
425 if (iDisplay.IsOpen())
427 //First take care our our volume level icons
428 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
429 if (volumeIconCount > 0)
431 //Compute current volume level from system level and the number of segments in our display volume bar.
432 //That tells us how many segments in our volume bar needs to be turned on.
433 float currentVolume = volumeLevelScalar * volumeIconCount;
434 int segmentOnCount = Convert.ToInt32(currentVolume);
435 //Check if our segment count was rounded up, this will later be used for half brightness segment
436 bool roundedUp = segmentOnCount > currentVolume;
438 for (int i = 0; i < volumeIconCount; i++)
440 if (i < segmentOnCount)
442 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
443 if (i == segmentOnCount - 1 && roundedUp)
446 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
451 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
456 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
461 //Take care of our mute icon
462 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
470 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
472 if (this.InvokeRequired)
474 //Not in the proper thread, invoke ourselves
475 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
476 this.Invoke(d, new object[] { });
480 //We are in the correct thread just go ahead.
483 //Get our master volume
484 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
486 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
488 //Show our volume in our track bar
489 UpdateMasterVolumeThreadSafe();
491 //Register to get volume modifications
492 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
494 trackBarMasterVolume.Enabled = true;
498 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
499 Debug.WriteLine(ex.ToString());
500 //Something went wrong S/PDIF device ca throw exception I guess
501 trackBarMasterVolume.Enabled = false;
508 private void PopulateDeviceTypes()
510 int count = Display.TypeCount();
512 for (int i = 0; i < count; i++)
514 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
521 private void PopulateOpticalDrives()
523 //Reset our list of drives
524 comboBoxOpticalDrives.Items.Clear();
525 comboBoxOpticalDrives.Items.Add("None");
527 //Go through each drives on our system and collected the optical ones in our list
528 DriveInfo[] allDrives = DriveInfo.GetDrives();
529 foreach (DriveInfo d in allDrives)
531 Debug.WriteLine("Drive " + d.Name);
532 Debug.WriteLine(" Drive type: {0}", d.DriveType);
534 if (d.DriveType==DriveType.CDRom)
536 //This is an optical drive, add it now
537 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
545 /// <returns></returns>
546 public string OpticalDriveToEject()
548 return comboBoxOpticalDrives.SelectedItem.ToString();
556 private void SetupTrayIcon()
558 iNotifyIcon.Icon = GetIcon("vfd.ico");
559 iNotifyIcon.Text = "Sharp Display Manager";
560 iNotifyIcon.Visible = true;
562 //Double click toggles visibility - typically brings up the application
563 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
568 //Adding a context menu, useful to be able to exit the application
569 ContextMenu contextMenu = new ContextMenu();
570 //Context menu item to toggle visibility
571 MenuItem hideShowItem = new MenuItem("Hide/Show");
572 hideShowItem.Click += delegate(object obj, EventArgs args)
576 contextMenu.MenuItems.Add(hideShowItem);
578 //Context menu item separator
579 contextMenu.MenuItems.Add(new MenuItem("-"));
581 //Context menu exit item
582 MenuItem exitItem = new MenuItem("Exit");
583 exitItem.Click += delegate(object obj, EventArgs args)
587 contextMenu.MenuItems.Add(exitItem);
589 iNotifyIcon.ContextMenu = contextMenu;
595 private void SetupRecordingNotification()
597 iRecordingNotification.Icon = GetIcon("record.ico");
598 iRecordingNotification.Text = "No recording";
599 iRecordingNotification.Visible = false;
603 /// Access icons from embedded resources.
605 /// <param name="aName"></param>
606 /// <returns></returns>
607 public static Icon GetIcon(string aName)
609 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
610 foreach (string name in names)
612 //Find a resource name that ends with the given name
613 if (name.EndsWith(aName))
615 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
617 return new Icon(stream);
627 /// Set our current client.
628 /// This will take care of applying our client layout and set data fields.
630 /// <param name="aSessionId"></param>
631 void SetCurrentClient(string aSessionId, bool aForce=false)
633 if (aSessionId == iCurrentClientSessionId)
635 //Given client is already the current one.
636 //Don't bother changing anything then.
640 ClientData requestedClientData = iClients[aSessionId];
642 //Check when was the last time we switched to that client
643 if (iCurrentClientData != null)
645 //Do not switch client if priority of current client is higher
646 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
652 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
653 //TODO: put that hard coded value as a client property
654 //Clients should be able to define how often they can be interrupted
655 //Thus a background client can set this to zero allowing any other client to interrupt at any time
656 //We could also compute this delay by looking at the requests frequencies?
658 requestedClientData.Priority == iCurrentClientData.Priority && //Time sharing is only if clients have the same priority
659 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
661 //Don't switch clients too often
666 //Set current client ID.
667 iCurrentClientSessionId = aSessionId;
668 //Set the time we last switched to that client
669 iClients[aSessionId].LastSwitchTime = DateTime.Now;
670 //Fetch and set current client data.
671 iCurrentClientData = requestedClientData;
672 //Apply layout and set data fields.
673 UpdateTableLayoutPanel(iCurrentClientData);
676 private void buttonFont_Click(object sender, EventArgs e)
678 //fontDialog.ShowColor = true;
679 //fontDialog.ShowApply = true;
680 fontDialog.ShowEffects = true;
681 fontDialog.Font = cds.Font;
683 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
685 //fontDialog.ShowHelp = true;
687 //fontDlg.MaxSize = 40;
688 //fontDlg.MinSize = 22;
690 //fontDialog.Parent = this;
691 //fontDialog.StartPosition = FormStartPosition.CenterParent;
693 //DlgBox.ShowDialog(fontDialog);
695 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
696 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
698 //Set the fonts to all our labels in our layout
699 foreach (Control ctrl in iTableLayoutPanel.Controls)
701 if (ctrl is MarqueeLabel)
703 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
708 cds.Font = fontDialog.Font;
709 Properties.Settings.Default.Save();
718 void CheckFontHeight()
720 //Show font height and width
721 labelFontHeight.Text = "Font height: " + cds.Font.Height;
722 float charWidth = IsFixedWidth(cds.Font);
723 if (charWidth == 0.0f)
725 labelFontWidth.Visible = false;
729 labelFontWidth.Visible = true;
730 labelFontWidth.Text = "Font width: " + charWidth;
733 MarqueeLabel label = null;
734 //Get the first label control we can find
735 foreach (Control ctrl in iTableLayoutPanel.Controls)
737 if (ctrl is MarqueeLabel)
739 label = (MarqueeLabel)ctrl;
744 //Now check font height and show a warning if needed.
745 if (label != null && label.Font.Height > label.Height)
747 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
748 labelWarning.Visible = true;
752 labelWarning.Visible = false;
757 private void buttonCapture_Click(object sender, EventArgs e)
759 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
760 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
761 //Bitmap bmpToSave = new Bitmap(bmp);
762 bmp.Save("D:\\capture.png");
764 ((MarqueeLabel)iTableLayoutPanel.Controls[0]).Text = "Captured";
767 string outputFileName = "d:\\capture.png";
768 using (MemoryStream memory = new MemoryStream())
770 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
772 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
773 byte[] bytes = memory.ToArray();
774 fs.Write(bytes, 0, bytes.Length);
781 private void CheckForRequestResults()
783 if (iDisplay.IsRequestPending())
785 switch (iDisplay.AttemptRequestCompletion())
787 case MiniDisplay.Request.FirmwareRevision:
788 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
789 //Issue next request then
790 iDisplay.RequestPowerSupplyStatus();
793 case MiniDisplay.Request.PowerSupplyStatus:
794 if (iDisplay.PowerSupplyStatus())
796 toolStripStatusLabelPower.Text = "ON";
800 toolStripStatusLabelPower.Text = "OFF";
802 //Issue next request then
803 iDisplay.RequestDeviceId();
806 case MiniDisplay.Request.DeviceId:
807 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
808 //No more request to issue
814 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
816 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
823 public static uint ColorUntouched(int aX, int aY, uint aPixel)
828 public static uint ColorInversed(int aX, int aY, uint aPixel)
833 public static uint ColorChessboard(int aX, int aY, uint aPixel)
835 if ((aX % 2 == 0) && (aY % 2 == 0))
839 else if ((aX % 2 != 0) && (aY % 2 != 0))
847 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
849 return aBmp.Width - aX - 1;
852 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
854 return iBmp.Height - aY - 1;
857 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
862 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
868 /// Select proper pixel delegates according to our current settings.
870 private void SetupPixelDelegates()
872 //Select our pixel processing routine
873 if (cds.InverseColors)
875 //iColorFx = ColorChessboard;
876 iColorFx = ColorInversed;
880 iColorFx = ColorWhiteIsOn;
883 //Select proper coordinate translation functions
884 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
885 if (cds.ReverseScreen)
887 iScreenX = ScreenReversedX;
888 iScreenY = ScreenReversedY;
898 //This is our timer tick responsible to perform our render
899 private void timer_Tick(object sender, EventArgs e)
901 //Update our animations
902 DateTime NewTickTime = DateTime.Now;
904 UpdateNetworkSignal(LastTickTime, NewTickTime);
906 //Update animation for all our marquees
907 foreach (Control ctrl in iTableLayoutPanel.Controls)
909 if (ctrl is MarqueeLabel)
911 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
916 if (iDisplay.IsOpen())
918 CheckForRequestResults();
920 //Check if frame rendering is needed
921 //Typically used when showing clock
922 if (!iSkipFrameRendering)
927 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
928 iCreateBitmap = false;
930 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
931 //iBmp.Save("D:\\capture.png");
933 //Send it to our display
934 for (int i = 0; i < iBmp.Width; i++)
936 for (int j = 0; j < iBmp.Height; j++)
940 //Get our processed pixel coordinates
941 int x = iScreenX(iBmp, i);
942 int y = iScreenY(iBmp, j);
944 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
945 //Apply color effects
946 color = iColorFx(x, y, color);
948 iDisplay.SetPixel(x, y, color);
953 iDisplay.SwapBuffers();
957 //Compute instant FPS
958 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
960 LastTickTime = NewTickTime;
965 /// Attempt to establish connection with our display hardware.
967 private void OpenDisplayConnection()
969 CloseDisplayConnection();
971 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
974 toolStripStatusLabelConnect.Text = "Connection error";
978 private void CloseDisplayConnection()
980 //Status will be updated upon receiving the closed event
982 if (iDisplay == null || !iDisplay.IsOpen())
987 //Do not clear if we gave up on rendering already.
988 //This means we will keep on displaying clock on MDM166AA for instance.
989 if (!iSkipFrameRendering)
992 iDisplay.SwapBuffers();
995 iDisplay.SetAllIconsStatus(0); //Turn off all icons
999 private void buttonOpen_Click(object sender, EventArgs e)
1001 OpenDisplayConnection();
1004 private void buttonClose_Click(object sender, EventArgs e)
1006 CloseDisplayConnection();
1009 private void buttonClear_Click(object sender, EventArgs e)
1012 iDisplay.SwapBuffers();
1015 private void buttonFill_Click(object sender, EventArgs e)
1018 iDisplay.SwapBuffers();
1021 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1023 cds.Brightness = trackBarBrightness.Value;
1024 Properties.Settings.Default.Save();
1025 iDisplay.SetBrightness(trackBarBrightness.Value);
1031 /// CDS stands for Current Display Settings
1033 private DisplaySettings cds
1037 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1038 if (settings == null)
1040 settings = new DisplaysSettings();
1042 Properties.Settings.Default.DisplaysSettings = settings;
1045 //Make sure all our settings have been created
1046 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1048 settings.Displays.Add(new DisplaySettings());
1051 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1052 return displaySettings;
1057 /// Check if the given font has a fixed character pitch.
1059 /// <param name="ft"></param>
1060 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1061 public float IsFixedWidth(Font ft)
1063 Graphics g = CreateGraphics();
1064 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1065 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1067 bool fixedWidth = true;
1069 foreach (char c in charSizes)
1070 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1082 /// Synchronize UI with settings
1084 private void UpdateStatus()
1087 checkBoxShowBorders.Checked = cds.ShowBorders;
1088 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1090 //Set the proper font to each of our labels
1091 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1093 ctrl.Font = cds.Font;
1097 //Check if "run on Windows startup" is enabled
1098 checkBoxAutoStart.Checked = iStartupManager.Startup;
1100 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1101 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1102 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1103 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1105 //Try find our drive in our drive list
1106 int opticalDriveItemIndex=0;
1107 bool driveNotFound = true;
1108 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1109 foreach (object item in comboBoxOpticalDrives.Items)
1111 if (opticalDriveToEject == item.ToString())
1113 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1114 driveNotFound = false;
1117 opticalDriveItemIndex++;
1122 //We could not find the drive we had saved.
1123 //Select "None" then.
1124 comboBoxOpticalDrives.SelectedIndex = 0;
1128 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1129 checkBoxCecMonitorOn.Checked = Properties.Settings.Default.CecMonitorOn;
1130 checkBoxCecMonitorOff.Checked = Properties.Settings.Default.CecMonitorOff;
1131 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1133 //Mini Display settings
1134 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1135 checkBoxInverseColors.Checked = cds.InverseColors;
1136 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1137 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1138 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1139 labelMinFontSize.Enabled = cds.ScaleToFit;
1140 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1141 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1142 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1143 timer.Interval = cds.TimerInterval;
1144 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1145 textBoxScrollLoopSeparator.Text = cds.Separator;
1147 SetupPixelDelegates();
1149 if (iDisplay.IsOpen())
1151 //We have a display connection
1152 //Reflect that in our UI
1155 iTableLayoutPanel.Enabled = true;
1156 panelDisplay.Enabled = true;
1158 //Only setup brightness if display is open
1159 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1160 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1161 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1163 //Brightness out of range, this can occur when using auto-detect
1164 //Use max brightness instead
1165 trackBarBrightness.Value = iDisplay.MaxBrightness();
1166 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1170 trackBarBrightness.Value = cds.Brightness;
1171 iDisplay.SetBrightness(cds.Brightness);
1174 //Try compute the steps to something that makes sense
1175 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1176 trackBarBrightness.SmallChange = 1;
1179 buttonFill.Enabled = true;
1180 buttonClear.Enabled = true;
1181 buttonOpen.Enabled = false;
1182 buttonClose.Enabled = true;
1183 trackBarBrightness.Enabled = true;
1184 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1185 //+ " - " + iDisplay.SerialNumber();
1187 if (iDisplay.SupportPowerOnOff())
1189 buttonPowerOn.Enabled = true;
1190 buttonPowerOff.Enabled = true;
1194 buttonPowerOn.Enabled = false;
1195 buttonPowerOff.Enabled = false;
1198 if (iDisplay.SupportClock())
1200 buttonShowClock.Enabled = true;
1201 buttonHideClock.Enabled = true;
1205 buttonShowClock.Enabled = false;
1206 buttonHideClock.Enabled = false;
1210 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1211 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1213 if (cds.ShowVolumeLabel)
1215 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1219 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1224 //Display connection not available
1225 //Reflect that in our UI
1227 //In debug start our timer even if we don't have a display connection
1230 //In production environment we don't need our timer if no display connection
1233 checkBoxShowVolumeLabel.Enabled = false;
1234 iTableLayoutPanel.Enabled = false;
1235 panelDisplay.Enabled = false;
1236 buttonFill.Enabled = false;
1237 buttonClear.Enabled = false;
1238 buttonOpen.Enabled = true;
1239 buttonClose.Enabled = false;
1240 trackBarBrightness.Enabled = false;
1241 buttonPowerOn.Enabled = false;
1242 buttonPowerOff.Enabled = false;
1243 buttonShowClock.Enabled = false;
1244 buttonHideClock.Enabled = false;
1245 toolStripStatusLabelConnect.Text = "Disconnected";
1246 toolStripStatusLabelPower.Text = "N/A";
1255 /// <param name="sender"></param>
1256 /// <param name="e"></param>
1257 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1259 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1260 Properties.Settings.Default.Save();
1264 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1266 //Save our show borders setting
1267 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1268 cds.ShowBorders = checkBoxShowBorders.Checked;
1269 Properties.Settings.Default.Save();
1273 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1275 //Save our connect on startup setting
1276 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1277 Properties.Settings.Default.Save();
1280 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1282 //Save our "Minimize to tray" setting
1283 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1284 Properties.Settings.Default.Save();
1288 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1290 //Save our "Start minimized" setting
1291 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1292 Properties.Settings.Default.Save();
1295 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1297 iStartupManager.Startup = checkBoxAutoStart.Checked;
1301 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1303 //Save our reverse screen setting
1304 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1305 Properties.Settings.Default.Save();
1306 SetupPixelDelegates();
1309 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1311 //Save our inverse colors setting
1312 cds.InverseColors = checkBoxInverseColors.Checked;
1313 Properties.Settings.Default.Save();
1314 SetupPixelDelegates();
1317 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1319 //Save our scale to fit setting
1320 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1321 Properties.Settings.Default.Save();
1323 labelMinFontSize.Enabled = cds.ScaleToFit;
1324 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1327 private void MainForm_Resize(object sender, EventArgs e)
1329 if (WindowState == FormWindowState.Minimized)
1331 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1332 // That's apparently not needed on Windows 10 but we better leave it in place.
1333 iCreateBitmap = true;
1337 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1340 iNetworkManager.Dispose();
1341 CloseDisplayConnection();
1343 e.Cancel = iClosing;
1346 public void StartServer()
1348 iServiceHost = new ServiceHost
1351 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1354 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1355 iServiceHost.Open();
1358 public void StopServer()
1360 if (iClients.Count > 0 && !iClosing)
1364 BroadcastCloseEvent();
1368 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1370 iClosing = false; //We make sure we force close if asked twice
1375 //We removed that as it often lags for some reason
1376 //iServiceHost.Close();
1380 public void BroadcastCloseEvent()
1382 Trace.TraceInformation("BroadcastCloseEvent - start");
1384 var inactiveClients = new List<string>();
1385 foreach (var client in iClients)
1387 //if (client.Key != eventData.ClientName)
1391 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1392 client.Value.Callback.OnCloseOrder(/*eventData*/);
1394 catch (Exception ex)
1396 inactiveClients.Add(client.Key);
1401 if (inactiveClients.Count > 0)
1403 foreach (var client in inactiveClients)
1405 iClients.Remove(client);
1406 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(client, false)[0]);
1410 if (iClients.Count==0)
1417 /// Just remove all our fields.
1419 private void ClearLayout()
1421 iTableLayoutPanel.Controls.Clear();
1422 iTableLayoutPanel.RowStyles.Clear();
1423 iTableLayoutPanel.ColumnStyles.Clear();
1424 iCurrentClientData = null;
1428 /// Just launch a demo client.
1430 private void StartNewClient(string aTopText = "", string aBottomText = "")
1432 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1433 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1434 clientThread.Start(myParams);
1439 /// Just launch our idle client.
1441 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1443 Thread clientThread = new Thread(SharpDisplayIdleClient.Program.MainWithParams);
1444 SharpDisplayIdleClient.StartParams myParams = new SharpDisplayIdleClient.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1445 clientThread.Start(myParams);
1450 private void buttonStartClient_Click(object sender, EventArgs e)
1455 private void buttonSuspend_Click(object sender, EventArgs e)
1460 private void StartTimer()
1462 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1463 timer.Enabled = true;
1464 UpdateSuspendButton();
1467 private void StopTimer()
1469 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1470 timer.Enabled = false;
1471 UpdateSuspendButton();
1474 private void ToggleTimer()
1476 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1477 timer.Enabled = !timer.Enabled;
1478 UpdateSuspendButton();
1481 private void UpdateSuspendButton()
1485 buttonSuspend.Text = "Run";
1489 buttonSuspend.Text = "Pause";
1494 private void buttonCloseClients_Click(object sender, EventArgs e)
1496 BroadcastCloseEvent();
1499 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1501 //Root node must have at least one child
1502 if (e.Node.Nodes.Count == 0)
1507 //If the selected node is the root node of a client then switch to it
1508 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1509 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1511 //We have a valid session just switch to that client
1512 SetCurrentClient(sessionId,true);
1521 /// <param name="aSessionId"></param>
1522 /// <param name="aCallback"></param>
1523 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1525 if (this.InvokeRequired)
1527 //Not in the proper thread, invoke ourselves
1528 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1529 this.Invoke(d, new object[] { aSessionId, aCallback });
1533 //We are in the proper thread
1534 //Add this session to our collection of clients
1535 ClientData newClient = new ClientData(aSessionId, aCallback);
1536 Program.iMainForm.iClients.Add(aSessionId, newClient);
1537 //Add this session to our UI
1538 UpdateClientTreeViewNode(newClient);
1544 /// Find the client with the highest priority if any.
1546 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1547 public ClientData FindHighestPriorityClient()
1549 ClientData highestPriorityClient = null;
1550 foreach (var client in iClients)
1552 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1554 highestPriorityClient = client.Value;
1558 return highestPriorityClient;
1564 /// <param name="aSessionId"></param>
1565 public void RemoveClientThreadSafe(string aSessionId)
1567 if (this.InvokeRequired)
1569 //Not in the proper thread, invoke ourselves
1570 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1571 this.Invoke(d, new object[] { aSessionId });
1575 //We are in the proper thread
1576 //Remove this session from both client collection and UI tree view
1577 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1579 Program.iMainForm.iClients.Remove(aSessionId);
1580 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1581 //Update recording status too whenever a client is removed
1582 UpdateRecordingNotification();
1585 if (iCurrentClientSessionId == aSessionId)
1587 //The current client is closing
1588 iCurrentClientData = null;
1589 //Find the client with the highest priority and set it as current
1590 ClientData newCurrentClient = FindHighestPriorityClient();
1591 if (newCurrentClient!=null)
1593 SetCurrentClient(newCurrentClient.SessionId, true);
1597 if (iClients.Count == 0)
1599 //Clear our screen when last client disconnects
1604 //We were closing our form
1605 //All clients are now closed
1606 //Just resume our close operation
1617 /// <param name="aSessionId"></param>
1618 /// <param name="aLayout"></param>
1619 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1621 if (this.InvokeRequired)
1623 //Not in the proper thread, invoke ourselves
1624 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1625 this.Invoke(d, new object[] { aSessionId, aLayout });
1629 ClientData client = iClients[aSessionId];
1632 //Don't change a thing if the layout is the same
1633 if (!client.Layout.IsSameAs(aLayout))
1635 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1636 //Set our client layout then
1637 client.Layout = aLayout;
1638 //So that next time we update all our fields at ones
1639 client.HasNewLayout = true;
1640 //Layout has changed clear our fields then
1641 client.Fields.Clear();
1643 UpdateClientTreeViewNode(client);
1647 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1656 /// <param name="aSessionId"></param>
1657 /// <param name="aField"></param>
1658 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1660 if (this.InvokeRequired)
1662 //Not in the proper thread, invoke ourselves
1663 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1664 this.Invoke(d, new object[] { aSessionId, aField });
1668 //We are in the proper thread
1669 //Call the non-thread-safe variant
1670 SetClientField(aSessionId, aField);
1678 /// Set a data field in the given client.
1680 /// <param name="aSessionId"></param>
1681 /// <param name="aField"></param>
1682 private void SetClientField(string aSessionId, DataField aField)
1684 //TODO: should check if the field actually changed?
1686 ClientData client = iClients[aSessionId];
1687 bool layoutChanged = false;
1688 bool contentChanged = true;
1690 //Fetch our field index
1691 int fieldIndex = client.FindSameFieldIndex(aField);
1695 //No corresponding field, just bail out
1699 //Keep our previous field in there
1700 DataField previousField = client.Fields[fieldIndex];
1701 //Just update that field then
1702 client.Fields[fieldIndex] = aField;
1704 if (!aField.IsTableField)
1706 //We are done then if that field is not in our table layout
1710 TableField tableField = (TableField) aField;
1712 if (previousField.IsSameLayout(aField))
1714 //If we are updating a field in our current client we need to update it in our panel
1715 if (aSessionId == iCurrentClientSessionId)
1717 Control ctrl=iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1718 if (aField.IsTextField && ctrl is MarqueeLabel)
1720 TextField textField=(TextField)aField;
1721 //Text field control already in place, just change the text
1722 MarqueeLabel label = (MarqueeLabel)ctrl;
1723 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1724 label.Text = textField.Text;
1725 label.TextAlign = textField.Alignment;
1727 else if (aField.IsBitmapField && ctrl is PictureBox)
1729 BitmapField bitmapField = (BitmapField)aField;
1730 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1731 //Bitmap field control already in place just change the bitmap
1732 PictureBox pictureBox = (PictureBox)ctrl;
1733 pictureBox.Image = bitmapField.Bitmap;
1737 layoutChanged = true;
1743 layoutChanged = true;
1746 //If either content or layout changed we need to update our tree view to reflect the changes
1747 if (contentChanged || layoutChanged)
1749 UpdateClientTreeViewNode(client);
1753 Debug.Print("Layout changed");
1754 //Our layout has changed, if we are already the current client we need to update our panel
1755 if (aSessionId == iCurrentClientSessionId)
1757 //Apply layout and set data fields.
1758 UpdateTableLayoutPanel(iCurrentClientData);
1763 Debug.Print("Layout has not changed.");
1768 Debug.Print("WARNING: content and layout have not changed!");
1771 //When a client field is set we try switching to this client to present the new information to our user
1772 SetCurrentClient(aSessionId);
1778 /// <param name="aTexts"></param>
1779 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1781 if (this.InvokeRequired)
1783 //Not in the proper thread, invoke ourselves
1784 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1785 this.Invoke(d, new object[] { aSessionId, aFields });
1789 ClientData client = iClients[aSessionId];
1791 if (client.HasNewLayout)
1793 //TODO: Assert client.Count == 0
1794 //Our layout was just changed
1795 //Do some special handling to avoid re-creating our panel N times, once for each fields
1796 client.HasNewLayout = false;
1797 //Just set all our fields then
1798 client.Fields.AddRange(aFields);
1799 //Try switch to that client
1800 SetCurrentClient(aSessionId);
1802 //If we are updating the current client update our panel
1803 if (aSessionId == iCurrentClientSessionId)
1805 //Apply layout and set data fields.
1806 UpdateTableLayoutPanel(iCurrentClientData);
1809 UpdateClientTreeViewNode(client);
1813 //Put each our text fields in a label control
1814 foreach (DataField field in aFields)
1816 SetClientField(aSessionId, field);
1825 /// <param name="aSessionId"></param>
1826 /// <param name="aName"></param>
1827 public void SetClientNameThreadSafe(string aSessionId, string aName)
1829 if (this.InvokeRequired)
1831 //Not in the proper thread, invoke ourselves
1832 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1833 this.Invoke(d, new object[] { aSessionId, aName });
1837 //We are in the proper thread
1839 ClientData client = iClients[aSessionId];
1843 client.Name = aName;
1844 //Update our tree-view
1845 UpdateClientTreeViewNode(client);
1851 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1853 if (this.InvokeRequired)
1855 //Not in the proper thread, invoke ourselves
1856 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1857 this.Invoke(d, new object[] { aSessionId, aPriority });
1861 //We are in the proper thread
1863 ClientData client = iClients[aSessionId];
1867 client.Priority = aPriority;
1868 //Update our tree-view
1869 UpdateClientTreeViewNode(client);
1870 //Change our current client as per new priority
1871 ClientData newCurrentClient = FindHighestPriorityClient();
1872 if (newCurrentClient!=null)
1874 SetCurrentClient(newCurrentClient.SessionId);
1883 /// <param name="value"></param>
1884 /// <param name="maxChars"></param>
1885 /// <returns></returns>
1886 public static string Truncate(string value, int maxChars)
1888 return value.Length <= maxChars ? value : value.Substring(0, maxChars-3) + "...";
1892 /// Update our recording notification.
1894 private void UpdateRecordingNotification()
1897 bool activeRecording = false;
1899 RecordingField recField=new RecordingField();
1900 foreach (var client in iClients)
1902 RecordingField rec=(RecordingField)client.Value.FindSameFieldAs(recField);
1903 if (rec!=null && rec.IsActive)
1905 activeRecording = true;
1906 //Don't break cause we are collecting the names/texts.
1907 if (!String.IsNullOrEmpty(rec.Text))
1909 text += (rec.Text + "\n");
1913 //Not text for that recording, use client name instead
1914 text += client.Value.Name + " recording\n";
1920 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1921 iRecordingNotification.Text = Truncate(text,63);
1923 //Change visibility of notification if needed
1924 if (iRecordingNotification.Visible != activeRecording)
1926 iRecordingNotification.Visible = activeRecording;
1927 //Assuming the notification icon is in sync with our display icon
1928 //Take care of our REC icon
1929 if (iDisplay.IsOpen())
1931 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
1939 /// <param name="aClient"></param>
1940 private void UpdateClientTreeViewNode(ClientData aClient)
1942 Debug.Print("UpdateClientTreeViewNode");
1944 if (aClient == null)
1949 //Hook in record icon update too
1950 UpdateRecordingNotification();
1952 TreeNode node = null;
1953 //Check that our client node already exists
1954 //Get our client root node using its key which is our session ID
1955 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
1956 if (nodes.Count()>0)
1958 //We already have a node for that client
1960 //Clear children as we are going to recreate them below
1965 //Client node does not exists create a new one
1966 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1967 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1973 if (!String.IsNullOrEmpty(aClient.Name))
1975 //We have a name, use it as text for our root node
1976 node.Text = aClient.Name;
1977 //Add a child with SessionId
1978 node.Nodes.Add(new TreeNode(aClient.SessionId));
1982 //No name, use session ID instead
1983 node.Text = aClient.SessionId;
1986 //Display client priority
1987 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
1989 if (aClient.Fields.Count > 0)
1991 //Create root node for our texts
1992 TreeNode textsRoot = new TreeNode("Fields");
1993 node.Nodes.Add(textsRoot);
1994 //For each text add a new entry
1995 foreach (DataField field in aClient.Fields)
1997 if (field.IsTextField)
1999 TextField textField = (TextField)field;
2000 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2002 else if (field.IsBitmapField)
2004 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2006 else if (field.IsRecordingField)
2008 RecordingField recordingField = (RecordingField)field;
2009 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2019 /// Update our table layout row styles to make sure each rows have similar height
2021 private void UpdateTableLayoutRowStyles()
2023 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2025 rowStyle.SizeType = SizeType.Percent;
2026 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
2031 /// Update our display table layout.
2032 /// Will instanciated every field control as defined by our client.
2033 /// Fields must be specified by rows from the left.
2035 /// <param name="aLayout"></param>
2036 private void UpdateTableLayoutPanel(ClientData aClient)
2038 Debug.Print("UpdateTableLayoutPanel");
2040 if (aClient == null)
2047 TableLayout layout = aClient.Layout;
2049 //First clean our current panel
2050 iTableLayoutPanel.Controls.Clear();
2051 iTableLayoutPanel.RowStyles.Clear();
2052 iTableLayoutPanel.ColumnStyles.Clear();
2053 iTableLayoutPanel.RowCount = 0;
2054 iTableLayoutPanel.ColumnCount = 0;
2056 //Then recreate our rows...
2057 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2059 iTableLayoutPanel.RowCount++;
2063 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2065 iTableLayoutPanel.ColumnCount++;
2069 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2071 //Create our column styles
2072 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2075 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2079 //Create our row styles
2080 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2090 foreach (DataField field in aClient.Fields)
2092 if (!field.IsTableField)
2094 //That field is not taking part in our table layout skip it
2098 TableField tableField = (TableField)field;
2100 //Create a control corresponding to the field specified for that cell
2101 Control control = CreateControlForDataField(tableField);
2103 //Add newly created control to our table layout at the specified row and column
2104 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2105 //Make sure we specify column and row span for that new control
2106 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2107 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2115 /// Check our type of data field and create corresponding control
2117 /// <param name="aField"></param>
2118 private Control CreateControlForDataField(DataField aField)
2120 Control control=null;
2121 if (aField.IsTextField)
2123 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2124 label.AutoEllipsis = true;
2125 label.AutoSize = true;
2126 label.BackColor = System.Drawing.Color.Transparent;
2127 label.Dock = System.Windows.Forms.DockStyle.Fill;
2128 label.Location = new System.Drawing.Point(1, 1);
2129 label.Margin = new System.Windows.Forms.Padding(0);
2130 label.Name = "marqueeLabel" + aField;
2131 label.OwnTimer = false;
2132 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2133 label.Separator = cds.Separator;
2134 label.MinFontSize = cds.MinFontSize;
2135 label.ScaleToFit = cds.ScaleToFit;
2136 //control.Size = new System.Drawing.Size(254, 30);
2137 //control.TabIndex = 2;
2138 label.Font = cds.Font;
2140 TextField field = (TextField)aField;
2141 label.TextAlign = field.Alignment;
2142 label.UseCompatibleTextRendering = true;
2143 label.Text = field.Text;
2147 else if (aField.IsBitmapField)
2149 //Create picture box
2150 PictureBox picture = new PictureBox();
2151 picture.AutoSize = true;
2152 picture.BackColor = System.Drawing.Color.Transparent;
2153 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2154 picture.Location = new System.Drawing.Point(1, 1);
2155 picture.Margin = new System.Windows.Forms.Padding(0);
2156 picture.Name = "pictureBox" + aField;
2158 BitmapField field = (BitmapField)aField;
2159 picture.Image = field.Bitmap;
2163 //TODO: Handle recording field?
2169 /// Called when the user selected a new display type.
2171 /// <param name="sender"></param>
2172 /// <param name="e"></param>
2173 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2175 //Store the selected display type in our settings
2176 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2177 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2178 Properties.Settings.Default.Save();
2180 //Try re-opening the display connection if we were already connected.
2181 //Otherwise just update our status to reflect display type change.
2182 if (iDisplay.IsOpen())
2184 OpenDisplayConnection();
2192 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2194 if (maskedTextBoxTimerInterval.Text != "")
2196 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2200 timer.Interval = interval;
2201 cds.TimerInterval = timer.Interval;
2202 Properties.Settings.Default.Save();
2207 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2209 if (maskedTextBoxMinFontSize.Text != "")
2211 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2213 if (minFontSize > 0)
2215 cds.MinFontSize = minFontSize;
2216 Properties.Settings.Default.Save();
2217 //We need to recreate our layout for that change to take effect
2218 UpdateTableLayoutPanel(iCurrentClientData);
2224 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2226 if (maskedTextBoxScrollingSpeed.Text != "")
2228 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2230 if (scrollingSpeed > 0)
2232 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2233 Properties.Settings.Default.Save();
2234 //We need to recreate our layout for that change to take effect
2235 UpdateTableLayoutPanel(iCurrentClientData);
2240 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2242 cds.Separator = textBoxScrollLoopSeparator.Text;
2243 Properties.Settings.Default.Save();
2245 //Update our text fields
2246 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2248 ctrl.Separator = cds.Separator;
2253 private void buttonPowerOn_Click(object sender, EventArgs e)
2258 private void buttonPowerOff_Click(object sender, EventArgs e)
2260 iDisplay.PowerOff();
2263 private void buttonShowClock_Click(object sender, EventArgs e)
2268 private void buttonHideClock_Click(object sender, EventArgs e)
2273 private void buttonUpdate_Click(object sender, EventArgs e)
2275 InstallUpdateSyncWithInfo();
2283 if (!iDisplay.IsOpen())
2288 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2289 iSkipFrameRendering = true;
2292 iDisplay.SwapBuffers();
2293 //Then show our clock
2294 iDisplay.ShowClock();
2302 if (!iDisplay.IsOpen())
2307 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2308 iSkipFrameRendering = false;
2309 iDisplay.HideClock();
2312 private void InstallUpdateSyncWithInfo()
2314 UpdateCheckInfo info = null;
2316 if (ApplicationDeployment.IsNetworkDeployed)
2318 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2322 info = ad.CheckForDetailedUpdate();
2325 catch (DeploymentDownloadException dde)
2327 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);
2330 catch (InvalidDeploymentException ide)
2332 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);
2335 catch (InvalidOperationException ioe)
2337 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2341 if (info.UpdateAvailable)
2343 Boolean doUpdate = true;
2345 if (!info.IsUpdateRequired)
2347 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2348 if (!(DialogResult.OK == dr))
2355 // Display a message that the application MUST reboot. Display the minimum required version.
2356 MessageBox.Show("This application has detected a mandatory update from your current " +
2357 "version to version " + info.MinimumRequiredVersion.ToString() +
2358 ". The application will now install the update and restart.",
2359 "Update Available", MessageBoxButtons.OK,
2360 MessageBoxIcon.Information);
2368 MessageBox.Show("The application has been upgraded, and will now restart.");
2369 Application.Restart();
2371 catch (DeploymentDownloadException dde)
2373 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2380 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2389 private void SysTrayHideShow()
2395 WindowState = FormWindowState.Normal;
2400 /// Use to handle minimize events.
2402 /// <param name="sender"></param>
2403 /// <param name="e"></param>
2404 private void MainForm_SizeChanged(object sender, EventArgs e)
2406 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2418 /// <param name="sender"></param>
2419 /// <param name="e"></param>
2420 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2422 //Our table layout size has changed which means our display size has changed.
2423 //We need to re-create our bitmap.
2424 iCreateBitmap = true;
2430 /// <param name="sender"></param>
2431 /// <param name="e"></param>
2432 private void buttonSelectFile_Click(object sender, EventArgs e)
2434 //openFileDialog1.InitialDirectory = "c:\\";
2435 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2436 //openFileDialog.FilterIndex = 1;
2437 openFileDialog.RestoreDirectory = true;
2439 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2441 labelStartFileName.Text = openFileDialog.FileName;
2442 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2443 Properties.Settings.Default.Save();
2450 /// <param name="sender"></param>
2451 /// <param name="e"></param>
2452 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2454 //Save the optical drive the user selected for ejection
2455 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2456 Properties.Settings.Default.Save();
2460 /// Broadcast messages to subscribers.
2462 /// <param name="message"></param>
2463 protected override void WndProc(ref Message aMessage)
2465 if (OnWndProc!=null)
2467 OnWndProc(ref aMessage);
2470 base.WndProc(ref aMessage);
2473 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2475 //Save CEC enabled status
2476 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2477 Properties.Settings.Default.Save();
2482 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2484 //Save CEC HDMI port
2485 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2486 Properties.Settings.Default.CecHdmiPort++;
2487 Properties.Settings.Default.Save();
2492 private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
2494 Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
2495 Properties.Settings.Default.Save();
2500 private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
2502 Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
2503 Properties.Settings.Default.Save();
2511 private void ResetCec()
2513 if (iCecManager==null)
2515 //Thus skipping initial UI setup
2521 if (Properties.Settings.Default.CecEnabled)
2523 iCecManager.Start(Handle, "CEC",
2524 Properties.Settings.Default.CecHdmiPort,
2525 Properties.Settings.Default.CecMonitorOn,
2526 Properties.Settings.Default.CecMonitorOff);
2530 private void ButtonStartIdleClient_Click(object sender, EventArgs e)