Updating libcec to 6d68d21243aa92862592435e8396b4280ea46c3f.
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 notification 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();
233 //Start Idle client if needed
234 if (Properties.Settings.Default.StartIdleClient)
241 /// Called when our display is opened.
243 /// <param name="aDisplay"></param>
244 private void OnDisplayOpened(Display aDisplay)
246 //Make sure we resume frame rendering
247 iSkipFrameRendering = false;
249 //Set our screen size now that our display is connected
250 //Our panelDisplay is the container of our tableLayoutPanel
251 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
252 //panelDisplay needs an extra 2 pixels for borders on each sides
253 //tableLayoutPanel will eventually be the exact size of our display
254 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
255 panelDisplay.Size = size;
257 //Our display was just opened, update our UI
259 //Initiate asynchronous request
260 iDisplay.RequestFirmwareRevision();
263 UpdateMasterVolumeThreadSafe();
265 UpdateNetworkStatus();
268 //Testing icon in debug, no arm done if icon not supported
269 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
270 //iDisplay.SetAllIconsStatus(2);
276 /// Called when our display is closed.
278 /// <param name="aDisplay"></param>
279 private void OnDisplayClosed(Display aDisplay)
281 //Our display was just closed, update our UI consequently
285 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
287 //Update network status
288 UpdateNetworkStatus();
292 /// Update our Network Status
294 private void UpdateNetworkStatus()
296 if (iDisplay.IsOpen())
298 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
299 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
304 int iLastNetworkIconIndex = 0;
305 int iUpdateCountSinceLastNetworkAnimation = 0;
310 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
312 iUpdateCountSinceLastNetworkAnimation++;
313 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
315 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
317 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
320 //Prevents div by zero and other undefined behavior
323 iLastNetworkIconIndex++;
324 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
325 for (int i=0;i<iconCount;i++)
327 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
329 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
333 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
342 /// Receive volume change notification and reflect changes on our slider.
344 /// <param name="data"></param>
345 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
347 UpdateMasterVolumeThreadSafe();
351 /// Update master volume when user moves our slider.
353 /// <param name="sender"></param>
354 /// <param name="e"></param>
355 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
357 //Just like Windows Volume Mixer we unmute if the volume is adjusted
358 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
359 //Set volume level according to our volume slider new position
360 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
365 /// Mute check box changed.
367 /// <param name="sender"></param>
368 /// <param name="e"></param>
369 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
371 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
375 /// Device State Changed
377 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
382 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
387 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
390 /// Default Device Changed
392 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
394 if (role == Role.Multimedia && flow == DataFlow.Render)
396 UpdateAudioDeviceAndMasterVolumeThreadSafe();
401 /// Property Value Changed
403 /// <param name="pwstrDeviceId"></param>
404 /// <param name="key"></param>
405 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
411 /// Update master volume indicators based our current system states.
412 /// This typically includes volume levels and mute status.
414 private void UpdateMasterVolumeThreadSafe()
416 if (this.InvokeRequired)
418 //Not in the proper thread, invoke ourselves
419 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
420 this.Invoke(d, new object[] { });
424 //Update volume slider
425 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
426 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
427 //Update mute checkbox
428 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
430 //If our display connection is open we need to update its icons
431 if (iDisplay.IsOpen())
433 //First take care our our volume level icons
434 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
435 if (volumeIconCount > 0)
437 //Compute current volume level from system level and the number of segments in our display volume bar.
438 //That tells us how many segments in our volume bar needs to be turned on.
439 float currentVolume = volumeLevelScalar * volumeIconCount;
440 int segmentOnCount = Convert.ToInt32(currentVolume);
441 //Check if our segment count was rounded up, this will later be used for half brightness segment
442 bool roundedUp = segmentOnCount > currentVolume;
444 for (int i = 0; i < volumeIconCount; i++)
446 if (i < segmentOnCount)
448 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
449 if (i == segmentOnCount - 1 && roundedUp)
452 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
457 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
462 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
467 //Take care of our mute icon
468 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
476 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
478 if (this.InvokeRequired)
480 //Not in the proper thread, invoke ourselves
481 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
482 this.Invoke(d, new object[] { });
486 //We are in the correct thread just go ahead.
489 //Get our master volume
490 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
492 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
494 //Show our volume in our track bar
495 UpdateMasterVolumeThreadSafe();
497 //Register to get volume modifications
498 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
500 trackBarMasterVolume.Enabled = true;
504 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
505 Debug.WriteLine(ex.ToString());
506 //Something went wrong S/PDIF device ca throw exception I guess
507 trackBarMasterVolume.Enabled = false;
514 private void PopulateDeviceTypes()
516 int count = Display.TypeCount();
518 for (int i = 0; i < count; i++)
520 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
527 private void PopulateOpticalDrives()
529 //Reset our list of drives
530 comboBoxOpticalDrives.Items.Clear();
531 comboBoxOpticalDrives.Items.Add("None");
533 //Go through each drives on our system and collected the optical ones in our list
534 DriveInfo[] allDrives = DriveInfo.GetDrives();
535 foreach (DriveInfo d in allDrives)
537 Debug.WriteLine("Drive " + d.Name);
538 Debug.WriteLine(" Drive type: {0}", d.DriveType);
540 if (d.DriveType==DriveType.CDRom)
542 //This is an optical drive, add it now
543 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
551 /// <returns></returns>
552 public string OpticalDriveToEject()
554 return comboBoxOpticalDrives.SelectedItem.ToString();
562 private void SetupTrayIcon()
564 iNotifyIcon.Icon = GetIcon("vfd.ico");
565 iNotifyIcon.Text = "Sharp Display Manager";
566 iNotifyIcon.Visible = true;
568 //Double click toggles visibility - typically brings up the application
569 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
574 //Adding a context menu, useful to be able to exit the application
575 ContextMenu contextMenu = new ContextMenu();
576 //Context menu item to toggle visibility
577 MenuItem hideShowItem = new MenuItem("Hide/Show");
578 hideShowItem.Click += delegate(object obj, EventArgs args)
582 contextMenu.MenuItems.Add(hideShowItem);
584 //Context menu item separator
585 contextMenu.MenuItems.Add(new MenuItem("-"));
587 //Context menu exit item
588 MenuItem exitItem = new MenuItem("Exit");
589 exitItem.Click += delegate(object obj, EventArgs args)
593 contextMenu.MenuItems.Add(exitItem);
595 iNotifyIcon.ContextMenu = contextMenu;
601 private void SetupRecordingNotification()
603 iRecordingNotification.Icon = GetIcon("record.ico");
604 iRecordingNotification.Text = "No recording";
605 iRecordingNotification.Visible = false;
609 /// Access icons from embedded resources.
611 /// <param name="aName"></param>
612 /// <returns></returns>
613 public static Icon GetIcon(string aName)
615 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
616 foreach (string name in names)
618 //Find a resource name that ends with the given name
619 if (name.EndsWith(aName))
621 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
623 return new Icon(stream);
633 /// Set our current client.
634 /// This will take care of applying our client layout and set data fields.
636 /// <param name="aSessionId"></param>
637 void SetCurrentClient(string aSessionId, bool aForce=false)
639 if (aSessionId == iCurrentClientSessionId)
641 //Given client is already the current one.
642 //Don't bother changing anything then.
646 ClientData requestedClientData = iClients[aSessionId];
648 //Check when was the last time we switched to that client
649 if (iCurrentClientData != null)
651 //Do not switch client if priority of current client is higher
652 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
658 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
659 //TODO: put that hard coded value as a client property
660 //Clients should be able to define how often they can be interrupted
661 //Thus a background client can set this to zero allowing any other client to interrupt at any time
662 //We could also compute this delay by looking at the requests frequencies?
664 requestedClientData.Priority == iCurrentClientData.Priority && //Time sharing is only if clients have the same priority
665 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
667 //Don't switch clients too often
672 //Set current client ID.
673 iCurrentClientSessionId = aSessionId;
674 //Set the time we last switched to that client
675 iClients[aSessionId].LastSwitchTime = DateTime.Now;
676 //Fetch and set current client data.
677 iCurrentClientData = requestedClientData;
678 //Apply layout and set data fields.
679 UpdateTableLayoutPanel(iCurrentClientData);
682 private void buttonFont_Click(object sender, EventArgs e)
684 //fontDialog.ShowColor = true;
685 //fontDialog.ShowApply = true;
686 fontDialog.ShowEffects = true;
687 fontDialog.Font = cds.Font;
689 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
691 //fontDialog.ShowHelp = true;
693 //fontDlg.MaxSize = 40;
694 //fontDlg.MinSize = 22;
696 //fontDialog.Parent = this;
697 //fontDialog.StartPosition = FormStartPosition.CenterParent;
699 //DlgBox.ShowDialog(fontDialog);
701 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
702 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
704 //Set the fonts to all our labels in our layout
705 foreach (Control ctrl in iTableLayoutPanel.Controls)
707 if (ctrl is MarqueeLabel)
709 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
714 cds.Font = fontDialog.Font;
715 Properties.Settings.Default.Save();
724 void CheckFontHeight()
726 //Show font height and width
727 labelFontHeight.Text = "Font height: " + cds.Font.Height;
728 float charWidth = IsFixedWidth(cds.Font);
729 if (charWidth == 0.0f)
731 labelFontWidth.Visible = false;
735 labelFontWidth.Visible = true;
736 labelFontWidth.Text = "Font width: " + charWidth;
739 MarqueeLabel label = null;
740 //Get the first label control we can find
741 foreach (Control ctrl in iTableLayoutPanel.Controls)
743 if (ctrl is MarqueeLabel)
745 label = (MarqueeLabel)ctrl;
750 //Now check font height and show a warning if needed.
751 if (label != null && label.Font.Height > label.Height)
753 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
754 labelWarning.Visible = true;
758 labelWarning.Visible = false;
763 private void buttonCapture_Click(object sender, EventArgs e)
765 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
766 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
767 //Bitmap bmpToSave = new Bitmap(bmp);
768 bmp.Save("D:\\capture.png");
770 ((MarqueeLabel)iTableLayoutPanel.Controls[0]).Text = "Captured";
773 string outputFileName = "d:\\capture.png";
774 using (MemoryStream memory = new MemoryStream())
776 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
778 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
779 byte[] bytes = memory.ToArray();
780 fs.Write(bytes, 0, bytes.Length);
787 private void CheckForRequestResults()
789 if (iDisplay.IsRequestPending())
791 switch (iDisplay.AttemptRequestCompletion())
793 case MiniDisplay.Request.FirmwareRevision:
794 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
795 //Issue next request then
796 iDisplay.RequestPowerSupplyStatus();
799 case MiniDisplay.Request.PowerSupplyStatus:
800 if (iDisplay.PowerSupplyStatus())
802 toolStripStatusLabelPower.Text = "ON";
806 toolStripStatusLabelPower.Text = "OFF";
808 //Issue next request then
809 iDisplay.RequestDeviceId();
812 case MiniDisplay.Request.DeviceId:
813 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
814 //No more request to issue
820 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
822 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
829 public static uint ColorUntouched(int aX, int aY, uint aPixel)
834 public static uint ColorInversed(int aX, int aY, uint aPixel)
839 public static uint ColorChessboard(int aX, int aY, uint aPixel)
841 if ((aX % 2 == 0) && (aY % 2 == 0))
845 else if ((aX % 2 != 0) && (aY % 2 != 0))
853 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
855 return aBmp.Width - aX - 1;
858 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
860 return iBmp.Height - aY - 1;
863 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
868 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
874 /// Select proper pixel delegates according to our current settings.
876 private void SetupPixelDelegates()
878 //Select our pixel processing routine
879 if (cds.InverseColors)
881 //iColorFx = ColorChessboard;
882 iColorFx = ColorInversed;
886 iColorFx = ColorWhiteIsOn;
889 //Select proper coordinate translation functions
890 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
891 if (cds.ReverseScreen)
893 iScreenX = ScreenReversedX;
894 iScreenY = ScreenReversedY;
904 //This is our timer tick responsible to perform our render
905 private void timer_Tick(object sender, EventArgs e)
907 //Update our animations
908 DateTime NewTickTime = DateTime.Now;
910 UpdateNetworkSignal(LastTickTime, NewTickTime);
912 //Update animation for all our marquees
913 foreach (Control ctrl in iTableLayoutPanel.Controls)
915 if (ctrl is MarqueeLabel)
917 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
922 if (iDisplay.IsOpen())
924 CheckForRequestResults();
926 //Check if frame rendering is needed
927 //Typically used when showing clock
928 if (!iSkipFrameRendering)
933 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
934 iCreateBitmap = false;
936 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
937 //iBmp.Save("D:\\capture.png");
939 //Send it to our display
940 for (int i = 0; i < iBmp.Width; i++)
942 for (int j = 0; j < iBmp.Height; j++)
946 //Get our processed pixel coordinates
947 int x = iScreenX(iBmp, i);
948 int y = iScreenY(iBmp, j);
950 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
951 //Apply color effects
952 color = iColorFx(x, y, color);
954 iDisplay.SetPixel(x, y, color);
959 iDisplay.SwapBuffers();
963 //Compute instant FPS
964 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
966 LastTickTime = NewTickTime;
971 /// Attempt to establish connection with our display hardware.
973 private void OpenDisplayConnection()
975 CloseDisplayConnection();
977 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
980 toolStripStatusLabelConnect.Text = "Connection error";
984 private void CloseDisplayConnection()
986 //Status will be updated upon receiving the closed event
988 if (iDisplay == null || !iDisplay.IsOpen())
993 //Do not clear if we gave up on rendering already.
994 //This means we will keep on displaying clock on MDM166AA for instance.
995 if (!iSkipFrameRendering)
998 iDisplay.SwapBuffers();
1001 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1005 private void buttonOpen_Click(object sender, EventArgs e)
1007 OpenDisplayConnection();
1010 private void buttonClose_Click(object sender, EventArgs e)
1012 CloseDisplayConnection();
1015 private void buttonClear_Click(object sender, EventArgs e)
1018 iDisplay.SwapBuffers();
1021 private void buttonFill_Click(object sender, EventArgs e)
1024 iDisplay.SwapBuffers();
1027 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1029 cds.Brightness = trackBarBrightness.Value;
1030 Properties.Settings.Default.Save();
1031 iDisplay.SetBrightness(trackBarBrightness.Value);
1037 /// CDS stands for Current Display Settings
1039 private DisplaySettings cds
1043 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1044 if (settings == null)
1046 settings = new DisplaysSettings();
1048 Properties.Settings.Default.DisplaysSettings = settings;
1051 //Make sure all our settings have been created
1052 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1054 settings.Displays.Add(new DisplaySettings());
1057 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1058 return displaySettings;
1063 /// Check if the given font has a fixed character pitch.
1065 /// <param name="ft"></param>
1066 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1067 public float IsFixedWidth(Font ft)
1069 Graphics g = CreateGraphics();
1070 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1071 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1073 bool fixedWidth = true;
1075 foreach (char c in charSizes)
1076 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1088 /// Synchronize UI with settings
1090 private void UpdateStatus()
1093 checkBoxShowBorders.Checked = cds.ShowBorders;
1094 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1096 //Set the proper font to each of our labels
1097 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1099 ctrl.Font = cds.Font;
1103 //Check if "run on Windows startup" is enabled
1104 checkBoxAutoStart.Checked = iStartupManager.Startup;
1106 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1107 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1108 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1109 iCheckBoxStartIdleClient.Checked = Properties.Settings.Default.StartIdleClient;
1110 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1113 //Try find our drive in our drive list
1114 int opticalDriveItemIndex=0;
1115 bool driveNotFound = true;
1116 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1117 foreach (object item in comboBoxOpticalDrives.Items)
1119 if (opticalDriveToEject == item.ToString())
1121 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1122 driveNotFound = false;
1125 opticalDriveItemIndex++;
1130 //We could not find the drive we had saved.
1131 //Select "None" then.
1132 comboBoxOpticalDrives.SelectedIndex = 0;
1136 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1137 checkBoxCecMonitorOn.Checked = Properties.Settings.Default.CecMonitorOn;
1138 checkBoxCecMonitorOff.Checked = Properties.Settings.Default.CecMonitorOff;
1139 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1141 //Mini Display settings
1142 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1143 checkBoxInverseColors.Checked = cds.InverseColors;
1144 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1145 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1146 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1147 labelMinFontSize.Enabled = cds.ScaleToFit;
1148 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1149 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1150 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1151 timer.Interval = cds.TimerInterval;
1152 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1153 textBoxScrollLoopSeparator.Text = cds.Separator;
1155 SetupPixelDelegates();
1157 if (iDisplay.IsOpen())
1159 //We have a display connection
1160 //Reflect that in our UI
1163 iTableLayoutPanel.Enabled = true;
1164 panelDisplay.Enabled = true;
1166 //Only setup brightness if display is open
1167 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1168 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1169 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1171 //Brightness out of range, this can occur when using auto-detect
1172 //Use max brightness instead
1173 trackBarBrightness.Value = iDisplay.MaxBrightness();
1174 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1178 trackBarBrightness.Value = cds.Brightness;
1179 iDisplay.SetBrightness(cds.Brightness);
1182 //Try compute the steps to something that makes sense
1183 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1184 trackBarBrightness.SmallChange = 1;
1187 buttonFill.Enabled = true;
1188 buttonClear.Enabled = true;
1189 buttonOpen.Enabled = false;
1190 buttonClose.Enabled = true;
1191 trackBarBrightness.Enabled = true;
1192 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1193 //+ " - " + iDisplay.SerialNumber();
1195 if (iDisplay.SupportPowerOnOff())
1197 buttonPowerOn.Enabled = true;
1198 buttonPowerOff.Enabled = true;
1202 buttonPowerOn.Enabled = false;
1203 buttonPowerOff.Enabled = false;
1206 if (iDisplay.SupportClock())
1208 buttonShowClock.Enabled = true;
1209 buttonHideClock.Enabled = true;
1213 buttonShowClock.Enabled = false;
1214 buttonHideClock.Enabled = false;
1218 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1219 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1221 if (cds.ShowVolumeLabel)
1223 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1227 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1232 //Display connection not available
1233 //Reflect that in our UI
1235 //In debug start our timer even if we don't have a display connection
1238 //In production environment we don't need our timer if no display connection
1241 checkBoxShowVolumeLabel.Enabled = false;
1242 iTableLayoutPanel.Enabled = false;
1243 panelDisplay.Enabled = false;
1244 buttonFill.Enabled = false;
1245 buttonClear.Enabled = false;
1246 buttonOpen.Enabled = true;
1247 buttonClose.Enabled = false;
1248 trackBarBrightness.Enabled = false;
1249 buttonPowerOn.Enabled = false;
1250 buttonPowerOff.Enabled = false;
1251 buttonShowClock.Enabled = false;
1252 buttonHideClock.Enabled = false;
1253 toolStripStatusLabelConnect.Text = "Disconnected";
1254 toolStripStatusLabelPower.Text = "N/A";
1263 /// <param name="sender"></param>
1264 /// <param name="e"></param>
1265 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1267 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1268 Properties.Settings.Default.Save();
1272 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1274 //Save our show borders setting
1275 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1276 cds.ShowBorders = checkBoxShowBorders.Checked;
1277 Properties.Settings.Default.Save();
1281 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1283 //Save our connect on startup setting
1284 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1285 Properties.Settings.Default.Save();
1288 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1290 //Save our "Minimize to tray" setting
1291 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1292 Properties.Settings.Default.Save();
1296 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1298 //Save our "Start minimized" setting
1299 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1300 Properties.Settings.Default.Save();
1303 private void checkBoxStartIdleClient_CheckedChanged(object sender, EventArgs e)
1305 Properties.Settings.Default.StartIdleClient = iCheckBoxStartIdleClient.Checked;
1306 Properties.Settings.Default.Save();
1309 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1311 iStartupManager.Startup = checkBoxAutoStart.Checked;
1315 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1317 //Save our reverse screen setting
1318 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1319 Properties.Settings.Default.Save();
1320 SetupPixelDelegates();
1323 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1325 //Save our inverse colors setting
1326 cds.InverseColors = checkBoxInverseColors.Checked;
1327 Properties.Settings.Default.Save();
1328 SetupPixelDelegates();
1331 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1333 //Save our scale to fit setting
1334 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1335 Properties.Settings.Default.Save();
1337 labelMinFontSize.Enabled = cds.ScaleToFit;
1338 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1341 private void MainForm_Resize(object sender, EventArgs e)
1343 if (WindowState == FormWindowState.Minimized)
1345 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1346 // That's apparently not needed on Windows 10 but we better leave it in place.
1347 iCreateBitmap = true;
1351 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1354 iNetworkManager.Dispose();
1355 CloseDisplayConnection();
1357 e.Cancel = iClosing;
1360 public void StartServer()
1362 iServiceHost = new ServiceHost
1365 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1368 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1369 iServiceHost.Open();
1372 public void StopServer()
1374 if (iClients.Count > 0 && !iClosing)
1378 BroadcastCloseEvent();
1382 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1384 iClosing = false; //We make sure we force close if asked twice
1389 //We removed that as it often lags for some reason
1390 //iServiceHost.Close();
1394 public void BroadcastCloseEvent()
1396 Trace.TraceInformation("BroadcastCloseEvent - start");
1398 var inactiveClients = new List<string>();
1399 foreach (var client in iClients)
1401 //if (client.Key != eventData.ClientName)
1405 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1406 client.Value.Callback.OnCloseOrder(/*eventData*/);
1408 catch (Exception ex)
1410 inactiveClients.Add(client.Key);
1415 if (inactiveClients.Count > 0)
1417 foreach (var client in inactiveClients)
1419 iClients.Remove(client);
1420 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(client, false)[0]);
1424 if (iClients.Count==0)
1431 /// Just remove all our fields.
1433 private void ClearLayout()
1435 iTableLayoutPanel.Controls.Clear();
1436 iTableLayoutPanel.RowStyles.Clear();
1437 iTableLayoutPanel.ColumnStyles.Clear();
1438 iCurrentClientData = null;
1442 /// Just launch a demo client.
1444 private void StartNewClient(string aTopText = "", string aBottomText = "")
1446 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1447 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1448 clientThread.Start(myParams);
1453 /// Just launch our idle client.
1455 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1457 Thread clientThread = new Thread(SharpDisplayIdleClient.Program.MainWithParams);
1458 SharpDisplayIdleClient.StartParams myParams = new SharpDisplayIdleClient.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1459 clientThread.Start(myParams);
1464 private void buttonStartClient_Click(object sender, EventArgs e)
1469 private void buttonSuspend_Click(object sender, EventArgs e)
1474 private void StartTimer()
1476 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1477 timer.Enabled = true;
1478 UpdateSuspendButton();
1481 private void StopTimer()
1483 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1484 timer.Enabled = false;
1485 UpdateSuspendButton();
1488 private void ToggleTimer()
1490 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1491 timer.Enabled = !timer.Enabled;
1492 UpdateSuspendButton();
1495 private void UpdateSuspendButton()
1499 buttonSuspend.Text = "Run";
1503 buttonSuspend.Text = "Pause";
1508 private void buttonCloseClients_Click(object sender, EventArgs e)
1510 BroadcastCloseEvent();
1513 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1515 //Root node must have at least one child
1516 if (e.Node.Nodes.Count == 0)
1521 //If the selected node is the root node of a client then switch to it
1522 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1523 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1525 //We have a valid session just switch to that client
1526 SetCurrentClient(sessionId,true);
1535 /// <param name="aSessionId"></param>
1536 /// <param name="aCallback"></param>
1537 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1539 if (this.InvokeRequired)
1541 //Not in the proper thread, invoke ourselves
1542 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1543 this.Invoke(d, new object[] { aSessionId, aCallback });
1547 //We are in the proper thread
1548 //Add this session to our collection of clients
1549 ClientData newClient = new ClientData(aSessionId, aCallback);
1550 Program.iMainForm.iClients.Add(aSessionId, newClient);
1551 //Add this session to our UI
1552 UpdateClientTreeViewNode(newClient);
1558 /// Find the client with the highest priority if any.
1560 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1561 public ClientData FindHighestPriorityClient()
1563 ClientData highestPriorityClient = null;
1564 foreach (var client in iClients)
1566 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1568 highestPriorityClient = client.Value;
1572 return highestPriorityClient;
1578 /// <param name="aSessionId"></param>
1579 public void RemoveClientThreadSafe(string aSessionId)
1581 if (this.InvokeRequired)
1583 //Not in the proper thread, invoke ourselves
1584 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1585 this.Invoke(d, new object[] { aSessionId });
1589 //We are in the proper thread
1590 //Remove this session from both client collection and UI tree view
1591 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1593 Program.iMainForm.iClients.Remove(aSessionId);
1594 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1595 //Update recording status too whenever a client is removed
1596 UpdateRecordingNotification();
1599 if (iCurrentClientSessionId == aSessionId)
1601 //The current client is closing
1602 iCurrentClientData = null;
1603 //Find the client with the highest priority and set it as current
1604 ClientData newCurrentClient = FindHighestPriorityClient();
1605 if (newCurrentClient!=null)
1607 SetCurrentClient(newCurrentClient.SessionId, true);
1611 if (iClients.Count == 0)
1613 //Clear our screen when last client disconnects
1618 //We were closing our form
1619 //All clients are now closed
1620 //Just resume our close operation
1631 /// <param name="aSessionId"></param>
1632 /// <param name="aLayout"></param>
1633 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1635 if (this.InvokeRequired)
1637 //Not in the proper thread, invoke ourselves
1638 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1639 this.Invoke(d, new object[] { aSessionId, aLayout });
1643 ClientData client = iClients[aSessionId];
1646 //Don't change a thing if the layout is the same
1647 if (!client.Layout.IsSameAs(aLayout))
1649 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1650 //Set our client layout then
1651 client.Layout = aLayout;
1652 //So that next time we update all our fields at ones
1653 client.HasNewLayout = true;
1654 //Layout has changed clear our fields then
1655 client.Fields.Clear();
1657 UpdateClientTreeViewNode(client);
1661 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1670 /// <param name="aSessionId"></param>
1671 /// <param name="aField"></param>
1672 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1674 if (this.InvokeRequired)
1676 //Not in the proper thread, invoke ourselves
1677 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1678 this.Invoke(d, new object[] { aSessionId, aField });
1682 //We are in the proper thread
1683 //Call the non-thread-safe variant
1684 SetClientField(aSessionId, aField);
1692 /// Set a data field in the given client.
1694 /// <param name="aSessionId"></param>
1695 /// <param name="aField"></param>
1696 private void SetClientField(string aSessionId, DataField aField)
1698 //TODO: should check if the field actually changed?
1700 ClientData client = iClients[aSessionId];
1701 bool layoutChanged = false;
1702 bool contentChanged = true;
1704 //Fetch our field index
1705 int fieldIndex = client.FindSameFieldIndex(aField);
1709 //No corresponding field, just bail out
1713 //Keep our previous field in there
1714 DataField previousField = client.Fields[fieldIndex];
1715 //Just update that field then
1716 client.Fields[fieldIndex] = aField;
1718 if (!aField.IsTableField)
1720 //We are done then if that field is not in our table layout
1724 TableField tableField = (TableField) aField;
1726 if (previousField.IsSameLayout(aField))
1728 //If we are updating a field in our current client we need to update it in our panel
1729 if (aSessionId == iCurrentClientSessionId)
1731 Control ctrl=iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1732 if (aField.IsTextField && ctrl is MarqueeLabel)
1734 TextField textField=(TextField)aField;
1735 //Text field control already in place, just change the text
1736 MarqueeLabel label = (MarqueeLabel)ctrl;
1737 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1738 label.Text = textField.Text;
1739 label.TextAlign = textField.Alignment;
1741 else if (aField.IsBitmapField && ctrl is PictureBox)
1743 BitmapField bitmapField = (BitmapField)aField;
1744 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1745 //Bitmap field control already in place just change the bitmap
1746 PictureBox pictureBox = (PictureBox)ctrl;
1747 pictureBox.Image = bitmapField.Bitmap;
1751 layoutChanged = true;
1757 layoutChanged = true;
1760 //If either content or layout changed we need to update our tree view to reflect the changes
1761 if (contentChanged || layoutChanged)
1763 UpdateClientTreeViewNode(client);
1767 Debug.Print("Layout changed");
1768 //Our layout has changed, if we are already the current client we need to update our panel
1769 if (aSessionId == iCurrentClientSessionId)
1771 //Apply layout and set data fields.
1772 UpdateTableLayoutPanel(iCurrentClientData);
1777 Debug.Print("Layout has not changed.");
1782 Debug.Print("WARNING: content and layout have not changed!");
1785 //When a client field is set we try switching to this client to present the new information to our user
1786 SetCurrentClient(aSessionId);
1792 /// <param name="aTexts"></param>
1793 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1795 if (this.InvokeRequired)
1797 //Not in the proper thread, invoke ourselves
1798 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1799 this.Invoke(d, new object[] { aSessionId, aFields });
1803 ClientData client = iClients[aSessionId];
1805 if (client.HasNewLayout)
1807 //TODO: Assert client.Count == 0
1808 //Our layout was just changed
1809 //Do some special handling to avoid re-creating our panel N times, once for each fields
1810 client.HasNewLayout = false;
1811 //Just set all our fields then
1812 client.Fields.AddRange(aFields);
1813 //Try switch to that client
1814 SetCurrentClient(aSessionId);
1816 //If we are updating the current client update our panel
1817 if (aSessionId == iCurrentClientSessionId)
1819 //Apply layout and set data fields.
1820 UpdateTableLayoutPanel(iCurrentClientData);
1823 UpdateClientTreeViewNode(client);
1827 //Put each our text fields in a label control
1828 foreach (DataField field in aFields)
1830 SetClientField(aSessionId, field);
1839 /// <param name="aSessionId"></param>
1840 /// <param name="aName"></param>
1841 public void SetClientNameThreadSafe(string aSessionId, string aName)
1843 if (this.InvokeRequired)
1845 //Not in the proper thread, invoke ourselves
1846 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1847 this.Invoke(d, new object[] { aSessionId, aName });
1851 //We are in the proper thread
1853 ClientData client = iClients[aSessionId];
1857 client.Name = aName;
1858 //Update our tree-view
1859 UpdateClientTreeViewNode(client);
1865 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1867 if (this.InvokeRequired)
1869 //Not in the proper thread, invoke ourselves
1870 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1871 this.Invoke(d, new object[] { aSessionId, aPriority });
1875 //We are in the proper thread
1877 ClientData client = iClients[aSessionId];
1881 client.Priority = aPriority;
1882 //Update our tree-view
1883 UpdateClientTreeViewNode(client);
1884 //Change our current client as per new priority
1885 ClientData newCurrentClient = FindHighestPriorityClient();
1886 if (newCurrentClient!=null)
1888 SetCurrentClient(newCurrentClient.SessionId);
1897 /// <param name="value"></param>
1898 /// <param name="maxChars"></param>
1899 /// <returns></returns>
1900 public static string Truncate(string value, int maxChars)
1902 return value.Length <= maxChars ? value : value.Substring(0, maxChars-3) + "...";
1906 /// Update our recording notification.
1908 private void UpdateRecordingNotification()
1911 bool activeRecording = false;
1913 RecordingField recField=new RecordingField();
1914 foreach (var client in iClients)
1916 RecordingField rec=(RecordingField)client.Value.FindSameFieldAs(recField);
1917 if (rec!=null && rec.IsActive)
1919 activeRecording = true;
1920 //Don't break cause we are collecting the names/texts.
1921 if (!String.IsNullOrEmpty(rec.Text))
1923 text += (rec.Text + "\n");
1927 //Not text for that recording, use client name instead
1928 text += client.Value.Name + " recording\n";
1934 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1935 iRecordingNotification.Text = Truncate(text,63);
1937 //Change visibility of notification if needed
1938 if (iRecordingNotification.Visible != activeRecording)
1940 iRecordingNotification.Visible = activeRecording;
1941 //Assuming the notification icon is in sync with our display icon
1942 //Take care of our REC icon
1943 if (iDisplay.IsOpen())
1945 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
1953 /// <param name="aClient"></param>
1954 private void UpdateClientTreeViewNode(ClientData aClient)
1956 Debug.Print("UpdateClientTreeViewNode");
1958 if (aClient == null)
1963 //Hook in record icon update too
1964 UpdateRecordingNotification();
1966 TreeNode node = null;
1967 //Check that our client node already exists
1968 //Get our client root node using its key which is our session ID
1969 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
1970 if (nodes.Count()>0)
1972 //We already have a node for that client
1974 //Clear children as we are going to recreate them below
1979 //Client node does not exists create a new one
1980 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1981 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1987 if (!String.IsNullOrEmpty(aClient.Name))
1989 //We have a name, use it as text for our root node
1990 node.Text = aClient.Name;
1991 //Add a child with SessionId
1992 node.Nodes.Add(new TreeNode(aClient.SessionId));
1996 //No name, use session ID instead
1997 node.Text = aClient.SessionId;
2000 //Display client priority
2001 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2003 if (aClient.Fields.Count > 0)
2005 //Create root node for our texts
2006 TreeNode textsRoot = new TreeNode("Fields");
2007 node.Nodes.Add(textsRoot);
2008 //For each text add a new entry
2009 foreach (DataField field in aClient.Fields)
2011 if (field.IsTextField)
2013 TextField textField = (TextField)field;
2014 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2016 else if (field.IsBitmapField)
2018 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2020 else if (field.IsRecordingField)
2022 RecordingField recordingField = (RecordingField)field;
2023 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2033 /// Update our table layout row styles to make sure each rows have similar height
2035 private void UpdateTableLayoutRowStyles()
2037 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2039 rowStyle.SizeType = SizeType.Percent;
2040 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
2045 /// Update our display table layout.
2046 /// Will instanciated every field control as defined by our client.
2047 /// Fields must be specified by rows from the left.
2049 /// <param name="aLayout"></param>
2050 private void UpdateTableLayoutPanel(ClientData aClient)
2052 Debug.Print("UpdateTableLayoutPanel");
2054 if (aClient == null)
2061 TableLayout layout = aClient.Layout;
2063 //First clean our current panel
2064 iTableLayoutPanel.Controls.Clear();
2065 iTableLayoutPanel.RowStyles.Clear();
2066 iTableLayoutPanel.ColumnStyles.Clear();
2067 iTableLayoutPanel.RowCount = 0;
2068 iTableLayoutPanel.ColumnCount = 0;
2070 //Then recreate our rows...
2071 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2073 iTableLayoutPanel.RowCount++;
2077 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2079 iTableLayoutPanel.ColumnCount++;
2083 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2085 //Create our column styles
2086 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2089 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2093 //Create our row styles
2094 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2104 foreach (DataField field in aClient.Fields)
2106 if (!field.IsTableField)
2108 //That field is not taking part in our table layout skip it
2112 TableField tableField = (TableField)field;
2114 //Create a control corresponding to the field specified for that cell
2115 Control control = CreateControlForDataField(tableField);
2117 //Add newly created control to our table layout at the specified row and column
2118 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2119 //Make sure we specify column and row span for that new control
2120 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2121 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2129 /// Check our type of data field and create corresponding control
2131 /// <param name="aField"></param>
2132 private Control CreateControlForDataField(DataField aField)
2134 Control control=null;
2135 if (aField.IsTextField)
2137 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2138 label.AutoEllipsis = true;
2139 label.AutoSize = true;
2140 label.BackColor = System.Drawing.Color.Transparent;
2141 label.Dock = System.Windows.Forms.DockStyle.Fill;
2142 label.Location = new System.Drawing.Point(1, 1);
2143 label.Margin = new System.Windows.Forms.Padding(0);
2144 label.Name = "marqueeLabel" + aField;
2145 label.OwnTimer = false;
2146 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2147 label.Separator = cds.Separator;
2148 label.MinFontSize = cds.MinFontSize;
2149 label.ScaleToFit = cds.ScaleToFit;
2150 //control.Size = new System.Drawing.Size(254, 30);
2151 //control.TabIndex = 2;
2152 label.Font = cds.Font;
2154 TextField field = (TextField)aField;
2155 label.TextAlign = field.Alignment;
2156 label.UseCompatibleTextRendering = true;
2157 label.Text = field.Text;
2161 else if (aField.IsBitmapField)
2163 //Create picture box
2164 PictureBox picture = new PictureBox();
2165 picture.AutoSize = true;
2166 picture.BackColor = System.Drawing.Color.Transparent;
2167 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2168 picture.Location = new System.Drawing.Point(1, 1);
2169 picture.Margin = new System.Windows.Forms.Padding(0);
2170 picture.Name = "pictureBox" + aField;
2172 BitmapField field = (BitmapField)aField;
2173 picture.Image = field.Bitmap;
2177 //TODO: Handle recording field?
2183 /// Called when the user selected a new display type.
2185 /// <param name="sender"></param>
2186 /// <param name="e"></param>
2187 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2189 //Store the selected display type in our settings
2190 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2191 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2192 Properties.Settings.Default.Save();
2194 //Try re-opening the display connection if we were already connected.
2195 //Otherwise just update our status to reflect display type change.
2196 if (iDisplay.IsOpen())
2198 OpenDisplayConnection();
2206 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2208 if (maskedTextBoxTimerInterval.Text != "")
2210 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2214 timer.Interval = interval;
2215 cds.TimerInterval = timer.Interval;
2216 Properties.Settings.Default.Save();
2221 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2223 if (maskedTextBoxMinFontSize.Text != "")
2225 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2227 if (minFontSize > 0)
2229 cds.MinFontSize = minFontSize;
2230 Properties.Settings.Default.Save();
2231 //We need to recreate our layout for that change to take effect
2232 UpdateTableLayoutPanel(iCurrentClientData);
2238 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2240 if (maskedTextBoxScrollingSpeed.Text != "")
2242 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2244 if (scrollingSpeed > 0)
2246 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2247 Properties.Settings.Default.Save();
2248 //We need to recreate our layout for that change to take effect
2249 UpdateTableLayoutPanel(iCurrentClientData);
2254 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2256 cds.Separator = textBoxScrollLoopSeparator.Text;
2257 Properties.Settings.Default.Save();
2259 //Update our text fields
2260 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2262 ctrl.Separator = cds.Separator;
2267 private void buttonPowerOn_Click(object sender, EventArgs e)
2272 private void buttonPowerOff_Click(object sender, EventArgs e)
2274 iDisplay.PowerOff();
2277 private void buttonShowClock_Click(object sender, EventArgs e)
2282 private void buttonHideClock_Click(object sender, EventArgs e)
2287 private void buttonUpdate_Click(object sender, EventArgs e)
2289 InstallUpdateSyncWithInfo();
2297 if (!iDisplay.IsOpen())
2302 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2303 iSkipFrameRendering = true;
2306 iDisplay.SwapBuffers();
2307 //Then show our clock
2308 iDisplay.ShowClock();
2316 if (!iDisplay.IsOpen())
2321 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2322 iSkipFrameRendering = false;
2323 iDisplay.HideClock();
2326 private void InstallUpdateSyncWithInfo()
2328 UpdateCheckInfo info = null;
2330 if (ApplicationDeployment.IsNetworkDeployed)
2332 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2336 info = ad.CheckForDetailedUpdate();
2339 catch (DeploymentDownloadException dde)
2341 MessageBox.Show("The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " + dde.Message);
2344 catch (InvalidDeploymentException ide)
2346 MessageBox.Show("Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " + ide.Message);
2349 catch (InvalidOperationException ioe)
2351 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2355 if (info.UpdateAvailable)
2357 Boolean doUpdate = true;
2359 if (!info.IsUpdateRequired)
2361 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2362 if (!(DialogResult.OK == dr))
2369 // Display a message that the application MUST reboot. Display the minimum required version.
2370 MessageBox.Show("This application has detected a mandatory update from your current " +
2371 "version to version " + info.MinimumRequiredVersion.ToString() +
2372 ". The application will now install the update and restart.",
2373 "Update Available", MessageBoxButtons.OK,
2374 MessageBoxIcon.Information);
2382 MessageBox.Show("The application has been upgraded, and will now restart.");
2383 Application.Restart();
2385 catch (DeploymentDownloadException dde)
2387 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2394 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2403 private void SysTrayHideShow()
2409 WindowState = FormWindowState.Normal;
2414 /// Use to handle minimize events.
2416 /// <param name="sender"></param>
2417 /// <param name="e"></param>
2418 private void MainForm_SizeChanged(object sender, EventArgs e)
2420 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2432 /// <param name="sender"></param>
2433 /// <param name="e"></param>
2434 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2436 //Our table layout size has changed which means our display size has changed.
2437 //We need to re-create our bitmap.
2438 iCreateBitmap = true;
2444 /// <param name="sender"></param>
2445 /// <param name="e"></param>
2446 private void buttonSelectFile_Click(object sender, EventArgs e)
2448 //openFileDialog1.InitialDirectory = "c:\\";
2449 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2450 //openFileDialog.FilterIndex = 1;
2451 openFileDialog.RestoreDirectory = true;
2453 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2455 labelStartFileName.Text = openFileDialog.FileName;
2456 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2457 Properties.Settings.Default.Save();
2464 /// <param name="sender"></param>
2465 /// <param name="e"></param>
2466 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2468 //Save the optical drive the user selected for ejection
2469 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2470 Properties.Settings.Default.Save();
2474 /// Broadcast messages to subscribers.
2476 /// <param name="message"></param>
2477 protected override void WndProc(ref Message aMessage)
2479 if (OnWndProc!=null)
2481 OnWndProc(ref aMessage);
2484 base.WndProc(ref aMessage);
2487 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2489 //Save CEC enabled status
2490 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2491 Properties.Settings.Default.Save();
2496 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2498 //Save CEC HDMI port
2499 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2500 Properties.Settings.Default.CecHdmiPort++;
2501 Properties.Settings.Default.Save();
2506 private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
2508 Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
2509 Properties.Settings.Default.Save();
2514 private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
2516 Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
2517 Properties.Settings.Default.Save();
2525 private void ResetCec()
2527 if (iCecManager==null)
2529 //Thus skipping initial UI setup
2535 if (Properties.Settings.Default.CecEnabled)
2537 iCecManager.Start(Handle, "CEC",
2538 Properties.Settings.Default.CecHdmiPort,
2539 Properties.Settings.Default.CecMonitorOn,
2540 Properties.Settings.Default.CecMonitorOff);
2544 private void ButtonStartIdleClient_Click(object sender, EventArgs e)