Improving layout change workflow efficiency.
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 PlainUpdateDelegate();
62 public delegate void WndProcDelegate(ref Message aMessage);
65 /// Our Display manager main form
67 [System.ComponentModel.DesignerCategory("Form")]
68 public partial class MainForm : MainFormHid, IMMNotificationClient
70 DateTime LastTickTime;
72 System.Drawing.Bitmap iBmp;
73 bool iCreateBitmap; //Workaround render to bitmap issues when minimized
74 ServiceHost iServiceHost;
75 // Our collection of clients sorted by session id.
76 public Dictionary<string, ClientData> iClients;
77 // The name of the client which informations are currently displayed.
78 public string iCurrentClientSessionId;
79 ClientData iCurrentClientData;
83 public bool iSkipFrameRendering;
84 //Function pointer for pixel color filtering
85 ColorProcessingDelegate iColorFx;
86 //Function pointer for pixel X coordinate intercept
87 CoordinateTranslationDelegate iScreenX;
88 //Function pointer for pixel Y coordinate intercept
89 CoordinateTranslationDelegate iScreenY;
91 private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
92 private MMDevice iMultiMediaDevice;
94 private NetworkManager iNetworkManager;
97 /// CEC - Consumer Electronic Control.
98 /// Notably used to turn TV on and off as Windows broadcast monitor on and off notifications.
100 private ConsumerElectronicControl iCecManager;
103 /// Manage run when Windows startup option
105 private StartupManager iStartupManager;
108 /// System tray icon.
110 private SharpLib.Notification.Control iNotifyIcon;
113 /// Allow user to receive window messages;
115 public event WndProcDelegate OnWndProc;
119 iSkipFrameRendering = false;
121 iCurrentClientSessionId = "";
122 iCurrentClientData = null;
123 LastTickTime = DateTime.Now;
124 //Instantiate our display and register for events notifications
125 iDisplay = new Display();
126 iDisplay.OnOpened += OnDisplayOpened;
127 iDisplay.OnClosed += OnDisplayClosed;
129 iClients = new Dictionary<string, ClientData>();
130 iStartupManager = new StartupManager();
131 iNotifyIcon = new SharpLib.Notification.Control();
133 //Have our designer initialize its controls
134 InitializeComponent();
136 //Populate device types
137 PopulateDeviceTypes();
139 //Populate optical drives
140 PopulateOpticalDrives();
142 //Initial status update
145 //We have a bug when drawing minimized and reusing our bitmap
146 //Though I could not reproduce it on Windows 10
147 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
148 iCreateBitmap = false;
150 //Minimize our window if desired
151 if (Properties.Settings.Default.StartMinimized)
153 WindowState = FormWindowState.Minimized;
161 /// <param name="sender"></param>
162 /// <param name="e"></param>
163 private void MainForm_Load(object sender, EventArgs e)
165 //Check if we are running a Click Once deployed application
166 if (ApplicationDeployment.IsNetworkDeployed)
168 //This is a proper Click Once installation, fetch and show our version number
169 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
173 //Not a proper Click Once installation, assuming development build then
174 this.Text += " - development";
178 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
179 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
180 UpdateAudioDeviceAndMasterVolumeThreadSafe();
183 iNetworkManager = new NetworkManager();
184 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
185 UpdateNetworkStatus();
188 iCecManager = new ConsumerElectronicControl();
189 OnWndProc += iCecManager.OnWndProc;
193 //Setup notification icon
196 // To make sure start up with minimize to tray works
197 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
203 //When not debugging we want the screen to be empty until a client takes over
206 //When developing we want at least one client for testing
207 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
210 //Open display connection on start-up if needed
211 if (Properties.Settings.Default.DisplayConnectOnStartup)
213 OpenDisplayConnection();
216 //Start our server so that we can get client requests
219 //Register for HID events
220 RegisterHidDevices();
224 /// Called when our display is opened.
226 /// <param name="aDisplay"></param>
227 private void OnDisplayOpened(Display aDisplay)
229 //Make sure we resume frame rendering
230 iSkipFrameRendering = false;
232 //Set our screen size now that our display is connected
233 //Our panelDisplay is the container of our tableLayoutPanel
234 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
235 //panelDisplay needs an extra 2 pixels for borders on each sides
236 //tableLayoutPanel will eventually be the exact size of our display
237 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
238 panelDisplay.Size = size;
240 //Our display was just opened, update our UI
242 //Initiate asynchronous request
243 iDisplay.RequestFirmwareRevision();
246 UpdateMasterVolumeThreadSafe();
248 UpdateNetworkStatus();
251 //Testing icon in debug, no arm done if icon not supported
252 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
253 //iDisplay.SetAllIconsStatus(2);
259 /// Called when our display is closed.
261 /// <param name="aDisplay"></param>
262 private void OnDisplayClosed(Display aDisplay)
264 //Our display was just closed, update our UI consequently
268 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
270 //Update network status
271 UpdateNetworkStatus();
275 /// Update our Network Status
277 private void UpdateNetworkStatus()
279 if (iDisplay.IsOpen())
281 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
282 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
287 int iLastNetworkIconIndex = 0;
288 int iUpdateCountSinceLastNetworkAnimation = 0;
293 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
295 iUpdateCountSinceLastNetworkAnimation++;
296 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
298 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
300 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
303 //Prevents div by zero and other undefined behavior
306 iLastNetworkIconIndex++;
307 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
308 for (int i=0;i<iconCount;i++)
310 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
312 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
316 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
325 /// Receive volume change notification and reflect changes on our slider.
327 /// <param name="data"></param>
328 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
330 UpdateMasterVolumeThreadSafe();
334 /// Update master volume when user moves our slider.
336 /// <param name="sender"></param>
337 /// <param name="e"></param>
338 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
340 //Just like Windows Volume Mixer we unmute if the volume is adjusted
341 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
342 //Set volume level according to our volume slider new position
343 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
348 /// Mute check box changed.
350 /// <param name="sender"></param>
351 /// <param name="e"></param>
352 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
354 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
358 /// Device State Changed
360 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
365 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
370 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
373 /// Default Device Changed
375 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
377 if (role == Role.Multimedia && flow == DataFlow.Render)
379 UpdateAudioDeviceAndMasterVolumeThreadSafe();
384 /// Property Value Changed
386 /// <param name="pwstrDeviceId"></param>
387 /// <param name="key"></param>
388 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
394 /// Update master volume indicators based our current system states.
395 /// This typically includes volume levels and mute status.
397 private void UpdateMasterVolumeThreadSafe()
399 if (this.InvokeRequired)
401 //Not in the proper thread, invoke ourselves
402 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
403 this.Invoke(d, new object[] { });
407 //Update volume slider
408 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
409 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
410 //Update mute checkbox
411 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
413 //If our display connection is open we need to update its icons
414 if (iDisplay.IsOpen())
416 //First take care our our volume level icons
417 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
418 if (volumeIconCount > 0)
420 //Compute current volume level from system level and the number of segments in our display volume bar.
421 //That tells us how many segments in our volume bar needs to be turned on.
422 float currentVolume = volumeLevelScalar * volumeIconCount;
423 int segmentOnCount = Convert.ToInt32(currentVolume);
424 //Check if our segment count was rounded up, this will later be used for half brightness segment
425 bool roundedUp = segmentOnCount > currentVolume;
427 for (int i = 0; i < volumeIconCount; i++)
429 if (i < segmentOnCount)
431 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
432 if (i == segmentOnCount - 1 && roundedUp)
435 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
440 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
445 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
450 //Take care our our mute icon
451 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
459 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
461 if (this.InvokeRequired)
463 //Not in the proper thread, invoke ourselves
464 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
465 this.Invoke(d, new object[] { });
469 //We are in the correct thread just go ahead.
472 //Get our master volume
473 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
475 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
477 //Show our volume in our track bar
478 UpdateMasterVolumeThreadSafe();
480 //Register to get volume modifications
481 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
483 trackBarMasterVolume.Enabled = true;
487 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
488 Debug.WriteLine(ex.ToString());
489 //Something went wrong S/PDIF device ca throw exception I guess
490 trackBarMasterVolume.Enabled = false;
497 private void PopulateDeviceTypes()
499 int count = Display.TypeCount();
501 for (int i = 0; i < count; i++)
503 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
510 private void PopulateOpticalDrives()
512 //Reset our list of drives
513 comboBoxOpticalDrives.Items.Clear();
514 comboBoxOpticalDrives.Items.Add("None");
516 //Go through each drives on our system and collected the optical ones in our list
517 DriveInfo[] allDrives = DriveInfo.GetDrives();
518 foreach (DriveInfo d in allDrives)
520 Debug.WriteLine("Drive " + d.Name);
521 Debug.WriteLine(" Drive type: {0}", d.DriveType);
523 if (d.DriveType==DriveType.CDRom)
525 //This is an optical drive, add it now
526 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
534 /// <returns></returns>
535 public string OpticalDriveToEject()
537 return comboBoxOpticalDrives.SelectedItem.ToString();
545 private void SetupTrayIcon()
547 iNotifyIcon.Icon = GetIcon("vfd.ico");
548 iNotifyIcon.Text = "Sharp Display Manager";
549 iNotifyIcon.Visible = true;
551 //Double click toggles visibility - typically brings up the application
552 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
557 //Adding a context menu, useful to be able to exit the application
558 ContextMenu contextMenu = new ContextMenu();
559 //Context menu item to toggle visibility
560 MenuItem hideShowItem = new MenuItem("Hide/Show");
561 hideShowItem.Click += delegate(object obj, EventArgs args)
565 contextMenu.MenuItems.Add(hideShowItem);
567 //Context menu item separator
568 contextMenu.MenuItems.Add(new MenuItem("-"));
570 //Context menu exit item
571 MenuItem exitItem = new MenuItem("Exit");
572 exitItem.Click += delegate(object obj, EventArgs args)
576 contextMenu.MenuItems.Add(exitItem);
578 iNotifyIcon.ContextMenu = contextMenu;
582 /// Access icons from embedded resources.
584 /// <param name="name"></param>
585 /// <returns></returns>
586 public static Icon GetIcon(string name)
588 name = "SharpDisplayManager.Resources." + name;
591 Assembly.GetExecutingAssembly().GetManifestResourceNames();
592 for (int i = 0; i < names.Length; i++)
594 if (names[i].Replace('\\', '.') == name)
596 using (Stream stream = Assembly.GetExecutingAssembly().
597 GetManifestResourceStream(names[i]))
599 return new Icon(stream);
609 /// Set our current client.
610 /// This will take care of applying our client layout and set data fields.
612 /// <param name="aSessionId"></param>
613 void SetCurrentClient(string aSessionId, bool aForce=false)
615 if (aSessionId == iCurrentClientSessionId)
617 //Given client is already the current one.
618 //Don't bother changing anything then.
623 //Check when was the last time we switched to that client
624 if (iCurrentClientData != null)
626 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
627 //TODO: put that hard coded value as a client property
628 //Clients should be able to define how often they can be interrupted
629 //Thus a background client can set this to zero allowing any other client to interrupt at any time
630 //We could also compute this delay by looking at the requests frequencies?
631 if (!aForce && (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
633 //Don't switch clients too often
638 //Set current client ID.
639 iCurrentClientSessionId = aSessionId;
640 //Set the time we last switched to that client
641 iClients[aSessionId].LastSwitchTime = DateTime.Now;
642 //Fetch and set current client data.
643 iCurrentClientData = iClients[aSessionId];
644 //Apply layout and set data fields.
645 UpdateTableLayoutPanel(iCurrentClientData);
648 private void buttonFont_Click(object sender, EventArgs e)
650 //fontDialog.ShowColor = true;
651 //fontDialog.ShowApply = true;
652 fontDialog.ShowEffects = true;
653 fontDialog.Font = cds.Font;
655 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
657 //fontDialog.ShowHelp = true;
659 //fontDlg.MaxSize = 40;
660 //fontDlg.MinSize = 22;
662 //fontDialog.Parent = this;
663 //fontDialog.StartPosition = FormStartPosition.CenterParent;
665 //DlgBox.ShowDialog(fontDialog);
667 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
668 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
670 //Set the fonts to all our labels in our layout
671 foreach (Control ctrl in iTableLayoutPanel.Controls)
673 if (ctrl is MarqueeLabel)
675 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
680 cds.Font = fontDialog.Font;
681 Properties.Settings.Default.Save();
690 void CheckFontHeight()
692 //Show font height and width
693 labelFontHeight.Text = "Font height: " + cds.Font.Height;
694 float charWidth = IsFixedWidth(cds.Font);
695 if (charWidth == 0.0f)
697 labelFontWidth.Visible = false;
701 labelFontWidth.Visible = true;
702 labelFontWidth.Text = "Font width: " + charWidth;
705 MarqueeLabel label = null;
706 //Get the first label control we can find
707 foreach (Control ctrl in iTableLayoutPanel.Controls)
709 if (ctrl is MarqueeLabel)
711 label = (MarqueeLabel)ctrl;
716 //Now check font height and show a warning if needed.
717 if (label != null && label.Font.Height > label.Height)
719 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
720 labelWarning.Visible = true;
724 labelWarning.Visible = false;
729 private void buttonCapture_Click(object sender, EventArgs e)
731 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
732 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
733 //Bitmap bmpToSave = new Bitmap(bmp);
734 bmp.Save("D:\\capture.png");
736 ((MarqueeLabel)iTableLayoutPanel.Controls[0]).Text = "Captured";
739 string outputFileName = "d:\\capture.png";
740 using (MemoryStream memory = new MemoryStream())
742 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
744 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
745 byte[] bytes = memory.ToArray();
746 fs.Write(bytes, 0, bytes.Length);
753 private void CheckForRequestResults()
755 if (iDisplay.IsRequestPending())
757 switch (iDisplay.AttemptRequestCompletion())
759 case MiniDisplay.Request.FirmwareRevision:
760 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
761 //Issue next request then
762 iDisplay.RequestPowerSupplyStatus();
765 case MiniDisplay.Request.PowerSupplyStatus:
766 if (iDisplay.PowerSupplyStatus())
768 toolStripStatusLabelPower.Text = "ON";
772 toolStripStatusLabelPower.Text = "OFF";
774 //Issue next request then
775 iDisplay.RequestDeviceId();
778 case MiniDisplay.Request.DeviceId:
779 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
780 //No more request to issue
786 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
788 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
795 public static uint ColorUntouched(int aX, int aY, uint aPixel)
800 public static uint ColorInversed(int aX, int aY, uint aPixel)
805 public static uint ColorChessboard(int aX, int aY, uint aPixel)
807 if ((aX % 2 == 0) && (aY % 2 == 0))
811 else if ((aX % 2 != 0) && (aY % 2 != 0))
819 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
821 return aBmp.Width - aX - 1;
824 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
826 return iBmp.Height - aY - 1;
829 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
834 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
840 /// Select proper pixel delegates according to our current settings.
842 private void SetupPixelDelegates()
844 //Select our pixel processing routine
845 if (cds.InverseColors)
847 //iColorFx = ColorChessboard;
848 iColorFx = ColorInversed;
852 iColorFx = ColorWhiteIsOn;
855 //Select proper coordinate translation functions
856 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
857 if (cds.ReverseScreen)
859 iScreenX = ScreenReversedX;
860 iScreenY = ScreenReversedY;
870 //This is our timer tick responsible to perform our render
871 private void timer_Tick(object sender, EventArgs e)
873 //Update our animations
874 DateTime NewTickTime = DateTime.Now;
876 UpdateNetworkSignal(LastTickTime, NewTickTime);
878 //Update animation for all our marquees
879 foreach (Control ctrl in iTableLayoutPanel.Controls)
881 if (ctrl is MarqueeLabel)
883 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
888 if (iDisplay.IsOpen())
890 CheckForRequestResults();
892 //Check if frame rendering is needed
893 //Typically used when showing clock
894 if (!iSkipFrameRendering)
899 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
900 iCreateBitmap = false;
902 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
903 //iBmp.Save("D:\\capture.png");
905 //Send it to our display
906 for (int i = 0; i < iBmp.Width; i++)
908 for (int j = 0; j < iBmp.Height; j++)
912 //Get our processed pixel coordinates
913 int x = iScreenX(iBmp, i);
914 int y = iScreenY(iBmp, j);
916 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
917 //Apply color effects
918 color = iColorFx(x, y, color);
920 iDisplay.SetPixel(x, y, color);
925 iDisplay.SwapBuffers();
929 //Compute instant FPS
930 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
932 LastTickTime = NewTickTime;
937 /// Attempt to establish connection with our display hardware.
939 private void OpenDisplayConnection()
941 CloseDisplayConnection();
943 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
946 toolStripStatusLabelConnect.Text = "Connection error";
950 private void CloseDisplayConnection()
952 //Status will be updated upon receiving the closed event
954 if (iDisplay == null || !iDisplay.IsOpen())
959 //Do not clear if we gave up on rendering already.
960 //This means we will keep on displaying clock on MDM166AA for instance.
961 if (!iSkipFrameRendering)
964 iDisplay.SwapBuffers();
967 iDisplay.SetAllIconsStatus(0); //Turn off all icons
971 private void buttonOpen_Click(object sender, EventArgs e)
973 OpenDisplayConnection();
976 private void buttonClose_Click(object sender, EventArgs e)
978 CloseDisplayConnection();
981 private void buttonClear_Click(object sender, EventArgs e)
984 iDisplay.SwapBuffers();
987 private void buttonFill_Click(object sender, EventArgs e)
990 iDisplay.SwapBuffers();
993 private void trackBarBrightness_Scroll(object sender, EventArgs e)
995 cds.Brightness = trackBarBrightness.Value;
996 Properties.Settings.Default.Save();
997 iDisplay.SetBrightness(trackBarBrightness.Value);
1003 /// CDS stands for Current Display Settings
1005 private DisplaySettings cds
1009 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1010 if (settings == null)
1012 settings = new DisplaysSettings();
1014 Properties.Settings.Default.DisplaysSettings = settings;
1017 //Make sure all our settings have been created
1018 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1020 settings.Displays.Add(new DisplaySettings());
1023 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1024 return displaySettings;
1029 /// Check if the given font has a fixed character pitch.
1031 /// <param name="ft"></param>
1032 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1033 public float IsFixedWidth(Font ft)
1035 Graphics g = CreateGraphics();
1036 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1037 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1039 bool fixedWidth = true;
1041 foreach (char c in charSizes)
1042 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1054 /// Synchronize UI with settings
1056 private void UpdateStatus()
1059 checkBoxShowBorders.Checked = cds.ShowBorders;
1060 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1062 //Set the proper font to each of our labels
1063 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1065 ctrl.Font = cds.Font;
1069 //Check if "run on Windows startup" is enabled
1070 checkBoxAutoStart.Checked = iStartupManager.Startup;
1072 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1073 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1074 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1075 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1077 //Try find our drive in our drive list
1078 int opticalDriveItemIndex=0;
1079 bool driveNotFound = true;
1080 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1081 foreach (object item in comboBoxOpticalDrives.Items)
1083 if (opticalDriveToEject == item.ToString())
1085 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1086 driveNotFound = false;
1089 opticalDriveItemIndex++;
1094 //We could not find the drive we had saved.
1095 //Select "None" then.
1096 comboBoxOpticalDrives.SelectedIndex = 0;
1100 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1101 checkBoxCecMonitorOn.Checked = Properties.Settings.Default.CecMonitorOn;
1102 checkBoxCecMonitorOff.Checked = Properties.Settings.Default.CecMonitorOff;
1103 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1105 //Mini Display settings
1106 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1107 checkBoxInverseColors.Checked = cds.InverseColors;
1108 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1109 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1110 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1111 labelMinFontSize.Enabled = cds.ScaleToFit;
1112 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1113 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1114 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1115 timer.Interval = cds.TimerInterval;
1116 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1117 textBoxScrollLoopSeparator.Text = cds.Separator;
1119 SetupPixelDelegates();
1121 if (iDisplay.IsOpen())
1123 //We have a display connection
1124 //Reflect that in our UI
1126 iTableLayoutPanel.Enabled = true;
1127 panelDisplay.Enabled = true;
1129 //Only setup brightness if display is open
1130 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1131 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1132 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1134 //Brightness out of range, this can occur when using auto-detect
1135 //Use max brightness instead
1136 trackBarBrightness.Value = iDisplay.MaxBrightness();
1137 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1141 trackBarBrightness.Value = cds.Brightness;
1142 iDisplay.SetBrightness(cds.Brightness);
1145 //Try compute the steps to something that makes sense
1146 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1147 trackBarBrightness.SmallChange = 1;
1150 buttonFill.Enabled = true;
1151 buttonClear.Enabled = true;
1152 buttonOpen.Enabled = false;
1153 buttonClose.Enabled = true;
1154 trackBarBrightness.Enabled = true;
1155 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1156 //+ " - " + iDisplay.SerialNumber();
1158 if (iDisplay.SupportPowerOnOff())
1160 buttonPowerOn.Enabled = true;
1161 buttonPowerOff.Enabled = true;
1165 buttonPowerOn.Enabled = false;
1166 buttonPowerOff.Enabled = false;
1169 if (iDisplay.SupportClock())
1171 buttonShowClock.Enabled = true;
1172 buttonHideClock.Enabled = true;
1176 buttonShowClock.Enabled = false;
1177 buttonHideClock.Enabled = false;
1181 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1182 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1184 if (cds.ShowVolumeLabel)
1186 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1190 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1195 //Display is connection not available
1196 //Reflect that in our UI
1197 checkBoxShowVolumeLabel.Enabled = false;
1198 iTableLayoutPanel.Enabled = false;
1199 panelDisplay.Enabled = false;
1200 buttonFill.Enabled = false;
1201 buttonClear.Enabled = false;
1202 buttonOpen.Enabled = true;
1203 buttonClose.Enabled = false;
1204 trackBarBrightness.Enabled = false;
1205 buttonPowerOn.Enabled = false;
1206 buttonPowerOff.Enabled = false;
1207 buttonShowClock.Enabled = false;
1208 buttonHideClock.Enabled = false;
1209 toolStripStatusLabelConnect.Text = "Disconnected";
1210 toolStripStatusLabelPower.Text = "N/A";
1219 /// <param name="sender"></param>
1220 /// <param name="e"></param>
1221 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1223 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1224 Properties.Settings.Default.Save();
1228 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1230 //Save our show borders setting
1231 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1232 cds.ShowBorders = checkBoxShowBorders.Checked;
1233 Properties.Settings.Default.Save();
1237 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1239 //Save our connect on startup setting
1240 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1241 Properties.Settings.Default.Save();
1244 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1246 //Save our "Minimize to tray" setting
1247 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1248 Properties.Settings.Default.Save();
1252 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1254 //Save our "Start minimized" setting
1255 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1256 Properties.Settings.Default.Save();
1259 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1261 iStartupManager.Startup = checkBoxAutoStart.Checked;
1265 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1267 //Save our reverse screen setting
1268 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1269 Properties.Settings.Default.Save();
1270 SetupPixelDelegates();
1273 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1275 //Save our inverse colors setting
1276 cds.InverseColors = checkBoxInverseColors.Checked;
1277 Properties.Settings.Default.Save();
1278 SetupPixelDelegates();
1281 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1283 //Save our scale to fit setting
1284 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1285 Properties.Settings.Default.Save();
1287 labelMinFontSize.Enabled = cds.ScaleToFit;
1288 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1291 private void MainForm_Resize(object sender, EventArgs e)
1293 if (WindowState == FormWindowState.Minimized)
1295 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1296 // That's apparently not needed on Windows 10 but we better leave it in place.
1297 iCreateBitmap = true;
1301 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1304 iNetworkManager.Dispose();
1305 CloseDisplayConnection();
1307 e.Cancel = iClosing;
1310 public void StartServer()
1312 iServiceHost = new ServiceHost
1315 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1318 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1319 iServiceHost.Open();
1322 public void StopServer()
1324 if (iClients.Count > 0 && !iClosing)
1328 BroadcastCloseEvent();
1332 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1334 iClosing = false; //We make sure we force close if asked twice
1339 //We removed that as it often lags for some reason
1340 //iServiceHost.Close();
1344 public void BroadcastCloseEvent()
1346 Trace.TraceInformation("BroadcastCloseEvent - start");
1348 var inactiveClients = new List<string>();
1349 foreach (var client in iClients)
1351 //if (client.Key != eventData.ClientName)
1355 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1356 client.Value.Callback.OnCloseOrder(/*eventData*/);
1358 catch (Exception ex)
1360 inactiveClients.Add(client.Key);
1365 if (inactiveClients.Count > 0)
1367 foreach (var client in inactiveClients)
1369 iClients.Remove(client);
1370 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
1374 if (iClients.Count==0)
1381 /// Just remove all our fields.
1383 private void ClearLayout()
1385 iTableLayoutPanel.Controls.Clear();
1386 iTableLayoutPanel.RowStyles.Clear();
1387 iTableLayoutPanel.ColumnStyles.Clear();
1388 iCurrentClientData = null;
1392 /// Just launch a demo client.
1394 private void StartNewClient(string aTopText = "", string aBottomText = "")
1396 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1397 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1398 clientThread.Start(myParams);
1402 private void buttonStartClient_Click(object sender, EventArgs e)
1407 private void buttonSuspend_Click(object sender, EventArgs e)
1409 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1410 timer.Enabled = !timer.Enabled;
1413 buttonSuspend.Text = "Run";
1417 buttonSuspend.Text = "Pause";
1421 private void buttonCloseClients_Click(object sender, EventArgs e)
1423 BroadcastCloseEvent();
1426 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1428 //Root node must have at least one child
1429 if (e.Node.Nodes.Count == 0)
1434 //If the selected node is the root node of a client then switch to it
1435 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1436 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1438 //We have a valid session just switch to that client
1439 SetCurrentClient(sessionId,true);
1448 /// <param name="aSessionId"></param>
1449 /// <param name="aCallback"></param>
1450 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1452 if (this.InvokeRequired)
1454 //Not in the proper thread, invoke ourselves
1455 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1456 this.Invoke(d, new object[] { aSessionId, aCallback });
1460 //We are in the proper thread
1461 //Add this session to our collection of clients
1462 ClientData newClient = new ClientData(aSessionId, aCallback);
1463 Program.iMainForm.iClients.Add(aSessionId, newClient);
1464 //Add this session to our UI
1465 UpdateClientTreeViewNode(newClient);
1472 /// <param name="aSessionId"></param>
1473 public void RemoveClientThreadSafe(string aSessionId)
1475 if (this.InvokeRequired)
1477 //Not in the proper thread, invoke ourselves
1478 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1479 this.Invoke(d, new object[] { aSessionId });
1483 //We are in the proper thread
1484 //Remove this session from both client collection and UI tree view
1485 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1487 Program.iMainForm.iClients.Remove(aSessionId);
1488 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
1491 if (iClients.Count == 0)
1493 //Clear our screen when last client disconnects
1498 //We were closing our form
1499 //All clients are now closed
1500 //Just resume our close operation
1511 /// <param name="aSessionId"></param>
1512 /// <param name="aLayout"></param>
1513 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1515 if (this.InvokeRequired)
1517 //Not in the proper thread, invoke ourselves
1518 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1519 this.Invoke(d, new object[] { aSessionId, aLayout });
1523 ClientData client = iClients[aSessionId];
1526 //Don't change a thing if the layout is the same
1527 if (!client.Layout.IsSameAs(aLayout))
1529 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1530 //Set our client layout then
1531 client.Layout = aLayout;
1532 //So that next time we update all our fields at ones
1533 client.HasNewLayout = true;
1534 //Layout has changed clear our fields then
1535 client.Fields.Clear();
1537 UpdateClientTreeViewNode(client);
1541 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1550 /// <param name="aSessionId"></param>
1551 /// <param name="aField"></param>
1552 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1554 if (this.InvokeRequired)
1556 //Not in the proper thread, invoke ourselves
1557 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1558 this.Invoke(d, new object[] { aSessionId, aField });
1562 //We are in the proper thread
1563 //Call the non-thread-safe variant
1564 SetClientField(aSessionId, aField);
1569 /// Set a data field in the given client.
1571 /// <param name="aSessionId"></param>
1572 /// <param name="aField"></param>
1573 private void SetClientField(string aSessionId, DataField aField)
1575 //TODO: should check if the field actually changed?
1577 ClientData client = iClients[aSessionId];
1578 bool layoutChanged = false;
1579 bool contentChanged = true;
1581 //Make sure all our fields are in place
1582 while (client.Fields.Count < (aField.Index + 1))
1584 //Add a data field with proper index
1585 client.Fields.Add(new TextField(client.Fields.Count));
1586 layoutChanged = true;
1589 //Keep our previous field in there
1590 DataField previousField = client.Fields[aField.Index];
1591 //Now that we know our fields are in place just update that one
1592 client.Fields[aField.Index] = aField;
1595 if (previousField.IsSameLayout(aField))
1597 //If we are updating a field in our current client we need to update it in our panel
1598 if (aSessionId == iCurrentClientSessionId)
1600 if (aField.IsTextField && aField.Index < iTableLayoutPanel.Controls.Count && iTableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
1602 TextField textField=(TextField)aField;
1603 //Text field control already in place, just change the text
1604 MarqueeLabel label = (MarqueeLabel)iTableLayoutPanel.Controls[aField.Index];
1605 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1606 label.Text = textField.Text;
1607 label.TextAlign = textField.Alignment;
1609 else if (aField.IsBitmapField && aField.Index < iTableLayoutPanel.Controls.Count && iTableLayoutPanel.Controls[aField.Index] is PictureBox)
1611 BitmapField bitmapField = (BitmapField)aField;
1612 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1613 //Bitmap field control already in place just change the bitmap
1614 PictureBox pictureBox = (PictureBox)iTableLayoutPanel.Controls[aField.Index];
1615 pictureBox.Image = bitmapField.Bitmap;
1619 layoutChanged = true;
1625 layoutChanged = true;
1628 //If either content or layout changed we need to update our tree view to reflect the changes
1629 if (contentChanged || layoutChanged)
1631 UpdateClientTreeViewNode(client);
1635 Debug.Print("Layout changed");
1636 //Our layout has changed, if we are already the current client we need to update our panel
1637 if (aSessionId == iCurrentClientSessionId)
1639 //Apply layout and set data fields.
1640 UpdateTableLayoutPanel(iCurrentClientData);
1645 Debug.Print("Layout has not changed.");
1650 Debug.Print("WARNING: content and layout have not changed!");
1653 //When a client field is set we try switching to this client to present the new information to our user
1654 SetCurrentClient(aSessionId);
1660 /// <param name="aTexts"></param>
1661 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1663 if (this.InvokeRequired)
1665 //Not in the proper thread, invoke ourselves
1666 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1667 this.Invoke(d, new object[] { aSessionId, aFields });
1671 ClientData client = iClients[aSessionId];
1673 if (client.HasNewLayout)
1675 //TODO: Assert client.Count == 0
1676 //Our layout was just changed
1677 //Do some special handling to avoid re-creating our panel N times, once for each fields
1678 client.HasNewLayout = false;
1679 //Just set all our fields then
1680 client.Fields.AddRange(aFields);
1681 //Try switch to that client
1682 SetCurrentClient(aSessionId);
1684 //If we are updating the current client update our panel
1685 if (aSessionId == iCurrentClientSessionId)
1687 //Apply layout and set data fields.
1688 UpdateTableLayoutPanel(iCurrentClientData);
1693 //Put each our text fields in a label control
1694 foreach (DataField field in aFields)
1696 SetClientField(aSessionId, field);
1705 /// <param name="aSessionId"></param>
1706 /// <param name="aName"></param>
1707 public void SetClientNameThreadSafe(string aSessionId, string aName)
1709 if (this.InvokeRequired)
1711 //Not in the proper thread, invoke ourselves
1712 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1713 this.Invoke(d, new object[] { aSessionId, aName });
1717 //We are in the proper thread
1719 ClientData client = iClients[aSessionId];
1723 client.Name = aName;
1724 //Update our tree-view
1725 UpdateClientTreeViewNode(client);
1733 /// <param name="aClient"></param>
1734 private void UpdateClientTreeViewNode(ClientData aClient)
1736 Debug.Print("UpdateClientTreeViewNode");
1738 if (aClient == null)
1743 TreeNode node = null;
1744 //Check that our client node already exists
1745 //Get our client root node using its key which is our session ID
1746 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1747 if (nodes.Count()>0)
1749 //We already have a node for that client
1751 //Clear children as we are going to recreate them below
1756 //Client node does not exists create a new one
1757 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1758 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1764 if (aClient.Name != "")
1766 //We have a name, us it as text for our root node
1767 node.Text = aClient.Name;
1768 //Add a child with SessionId
1769 node.Nodes.Add(new TreeNode(aClient.SessionId));
1773 //No name, use session ID instead
1774 node.Text = aClient.SessionId;
1777 if (aClient.Fields.Count > 0)
1779 //Create root node for our texts
1780 TreeNode textsRoot = new TreeNode("Fields");
1781 node.Nodes.Add(textsRoot);
1782 //For each text add a new entry
1783 foreach (DataField field in aClient.Fields)
1785 if (field.IsTextField)
1787 TextField textField = (TextField)field;
1788 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1790 else if (field.IsBitmapField)
1792 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1794 else if (field.IsRecordingField)
1796 textsRoot.Nodes.Add(new TreeNode("[Recording]"));
1806 /// Update our table layout row styles to make sure each rows have similar height
1808 private void UpdateTableLayoutRowStyles()
1810 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
1812 rowStyle.SizeType = SizeType.Percent;
1813 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
1818 /// Update our display table layout.
1819 /// Will instanciated every field control as defined by our client.
1820 /// Fields must be specified by rows from the left.
1822 /// <param name="aLayout"></param>
1823 private void UpdateTableLayoutPanel(ClientData aClient)
1825 Debug.Print("UpdateTableLayoutPanel");
1827 if (aClient == null)
1834 TableLayout layout = aClient.Layout;
1837 //First clean our current panel
1838 iTableLayoutPanel.Controls.Clear();
1839 iTableLayoutPanel.RowStyles.Clear();
1840 iTableLayoutPanel.ColumnStyles.Clear();
1841 iTableLayoutPanel.RowCount = 0;
1842 iTableLayoutPanel.ColumnCount = 0;
1844 //Then recreate our rows...
1845 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
1847 iTableLayoutPanel.RowCount++;
1851 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
1853 iTableLayoutPanel.ColumnCount++;
1857 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
1859 //Create our column styles
1860 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1863 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
1867 //Create our row styles
1868 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1871 //Check if we already have a control
1872 Control existingControl = iTableLayoutPanel.GetControlFromPosition(i,j);
1873 if (existingControl!=null)
1875 //We already have a control in that cell as a results of row/col spanning
1876 //Move on to next cell then
1882 //Check if a client field already exists for that cell
1883 if (aClient.Fields.Count <= iTableLayoutPanel.Controls.Count)
1885 //No client field specified, create a text field by default
1886 aClient.Fields.Add(new TextField(aClient.Fields.Count));
1890 DataField field = aClient.Fields[iTableLayoutPanel.Controls.Count];
1891 if (!field.IsTableField)
1893 //That field is not taking part in our table layout then
1894 //We should be ok I guess
1898 TableField tableField = (TableField)field;
1900 //Create a control corresponding to the field specified for that cell
1901 Control control = CreateControlForDataField(tableField);
1903 //Add newly created control to our table layout at the specified row and column
1904 iTableLayoutPanel.Controls.Add(control, i, j);
1905 //Make sure we specify row and column span for that new control
1906 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
1907 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
1912 while (aClient.Fields.Count > fieldCount)
1914 //We have too much fields for this layout
1915 //Just discard them until we get there
1916 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1923 /// Check our type of data field and create corresponding control
1925 /// <param name="aField"></param>
1926 private Control CreateControlForDataField(DataField aField)
1928 Control control=null;
1929 if (aField.IsTextField)
1931 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1932 label.AutoEllipsis = true;
1933 label.AutoSize = true;
1934 label.BackColor = System.Drawing.Color.Transparent;
1935 label.Dock = System.Windows.Forms.DockStyle.Fill;
1936 label.Location = new System.Drawing.Point(1, 1);
1937 label.Margin = new System.Windows.Forms.Padding(0);
1938 label.Name = "marqueeLabel" + aField.Index;
1939 label.OwnTimer = false;
1940 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
1941 label.Separator = cds.Separator;
1942 label.MinFontSize = cds.MinFontSize;
1943 label.ScaleToFit = cds.ScaleToFit;
1944 //control.Size = new System.Drawing.Size(254, 30);
1945 //control.TabIndex = 2;
1946 label.Font = cds.Font;
1948 TextField field = (TextField)aField;
1949 label.TextAlign = field.Alignment;
1950 label.UseCompatibleTextRendering = true;
1951 label.Text = field.Text;
1955 else if (aField.IsBitmapField)
1957 //Create picture box
1958 PictureBox picture = new PictureBox();
1959 picture.AutoSize = true;
1960 picture.BackColor = System.Drawing.Color.Transparent;
1961 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1962 picture.Location = new System.Drawing.Point(1, 1);
1963 picture.Margin = new System.Windows.Forms.Padding(0);
1964 picture.Name = "pictureBox" + aField;
1966 BitmapField field = (BitmapField)aField;
1967 picture.Image = field.Bitmap;
1971 //TODO: Handle recording field?
1977 /// Called when the user selected a new display type.
1979 /// <param name="sender"></param>
1980 /// <param name="e"></param>
1981 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1983 //Store the selected display type in our settings
1984 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1985 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1986 Properties.Settings.Default.Save();
1988 //Try re-opening the display connection if we were already connected.
1989 //Otherwise just update our status to reflect display type change.
1990 if (iDisplay.IsOpen())
1992 OpenDisplayConnection();
2000 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2002 if (maskedTextBoxTimerInterval.Text != "")
2004 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2008 timer.Interval = interval;
2009 cds.TimerInterval = timer.Interval;
2010 Properties.Settings.Default.Save();
2015 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2017 if (maskedTextBoxMinFontSize.Text != "")
2019 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2021 if (minFontSize > 0)
2023 cds.MinFontSize = minFontSize;
2024 Properties.Settings.Default.Save();
2025 //We need to recreate our layout for that change to take effect
2026 UpdateTableLayoutPanel(iCurrentClientData);
2032 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2034 if (maskedTextBoxScrollingSpeed.Text != "")
2036 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2038 if (scrollingSpeed > 0)
2040 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2041 Properties.Settings.Default.Save();
2042 //We need to recreate our layout for that change to take effect
2043 UpdateTableLayoutPanel(iCurrentClientData);
2048 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2050 cds.Separator = textBoxScrollLoopSeparator.Text;
2051 Properties.Settings.Default.Save();
2053 //Update our text fields
2054 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2056 ctrl.Separator = cds.Separator;
2061 private void buttonPowerOn_Click(object sender, EventArgs e)
2066 private void buttonPowerOff_Click(object sender, EventArgs e)
2068 iDisplay.PowerOff();
2071 private void buttonShowClock_Click(object sender, EventArgs e)
2076 private void buttonHideClock_Click(object sender, EventArgs e)
2081 private void buttonUpdate_Click(object sender, EventArgs e)
2083 InstallUpdateSyncWithInfo();
2091 if (!iDisplay.IsOpen())
2096 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2097 iSkipFrameRendering = true;
2100 iDisplay.SwapBuffers();
2101 //Then show our clock
2102 iDisplay.ShowClock();
2110 if (!iDisplay.IsOpen())
2115 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2116 iSkipFrameRendering = false;
2117 iDisplay.HideClock();
2120 private void InstallUpdateSyncWithInfo()
2122 UpdateCheckInfo info = null;
2124 if (ApplicationDeployment.IsNetworkDeployed)
2126 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2130 info = ad.CheckForDetailedUpdate();
2133 catch (DeploymentDownloadException dde)
2135 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);
2138 catch (InvalidDeploymentException ide)
2140 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);
2143 catch (InvalidOperationException ioe)
2145 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2149 if (info.UpdateAvailable)
2151 Boolean doUpdate = true;
2153 if (!info.IsUpdateRequired)
2155 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2156 if (!(DialogResult.OK == dr))
2163 // Display a message that the app MUST reboot. Display the minimum required version.
2164 MessageBox.Show("This application has detected a mandatory update from your current " +
2165 "version to version " + info.MinimumRequiredVersion.ToString() +
2166 ". The application will now install the update and restart.",
2167 "Update Available", MessageBoxButtons.OK,
2168 MessageBoxIcon.Information);
2176 MessageBox.Show("The application has been upgraded, and will now restart.");
2177 Application.Restart();
2179 catch (DeploymentDownloadException dde)
2181 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2188 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2197 private void SysTrayHideShow()
2203 WindowState = FormWindowState.Normal;
2208 /// Use to handle minimize events.
2210 /// <param name="sender"></param>
2211 /// <param name="e"></param>
2212 private void MainForm_SizeChanged(object sender, EventArgs e)
2214 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2226 /// <param name="sender"></param>
2227 /// <param name="e"></param>
2228 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2230 //Our table layout size has changed which means our display size has changed.
2231 //We need to re-create our bitmap.
2232 iCreateBitmap = true;
2238 /// <param name="sender"></param>
2239 /// <param name="e"></param>
2240 private void buttonSelectFile_Click(object sender, EventArgs e)
2242 //openFileDialog1.InitialDirectory = "c:\\";
2243 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2244 //openFileDialog.FilterIndex = 1;
2245 openFileDialog.RestoreDirectory = true;
2247 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2249 labelStartFileName.Text = openFileDialog.FileName;
2250 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2251 Properties.Settings.Default.Save();
2258 /// <param name="sender"></param>
2259 /// <param name="e"></param>
2260 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2262 //Save the optical drive the user selected for ejection
2263 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2264 Properties.Settings.Default.Save();
2268 /// Broadcast messages to subscribers.
2270 /// <param name="message"></param>
2271 protected override void WndProc(ref Message aMessage)
2273 if (OnWndProc!=null)
2275 OnWndProc(ref aMessage);
2278 base.WndProc(ref aMessage);
2281 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2283 //Save CEC enabled status
2284 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2285 Properties.Settings.Default.Save();
2290 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2292 //Save CEC HDMI port
2293 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2294 Properties.Settings.Default.CecHdmiPort++;
2295 Properties.Settings.Default.Save();
2300 private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
2302 Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
2303 Properties.Settings.Default.Save();
2308 private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
2310 Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
2311 Properties.Settings.Default.Save();
2319 private void ResetCec()
2321 if (iCecManager==null)
2323 //Thus skipping initial UI setup
2329 if (Properties.Settings.Default.CecEnabled)
2331 iCecManager.Start(Handle, "CEC",
2332 Properties.Settings.Default.CecHdmiPort,
2333 Properties.Settings.Default.CecMonitorOn,
2334 Properties.Settings.Default.CecMonitorOff);