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;
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();
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;
98 /// Manage run when Windows startup option
100 private StartupManager iStartupManager;
103 /// System tray icon.
105 private NotifyIconAdv iNotifyIcon;
109 iSkipFrameRendering = false;
111 iCurrentClientSessionId = "";
112 iCurrentClientData = null;
113 LastTickTime = DateTime.Now;
114 //Instantiate our display and register for events notifications
115 iDisplay = new Display();
116 iDisplay.OnOpened += OnDisplayOpened;
117 iDisplay.OnClosed += OnDisplayClosed;
119 iClients = new Dictionary<string, ClientData>();
120 iStartupManager = new StartupManager();
121 iNotifyIcon = new NotifyIconAdv();
123 //Have our designer initialize its controls
124 InitializeComponent();
126 //Populate device types
127 PopulateDeviceTypes();
129 //Populate optical drives
130 PopulateOpticalDrives();
132 //Initial status update
135 //We have a bug when drawing minimized and reusing our bitmap
136 //Though I could not reproduce it on Windows 10
137 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
138 iCreateBitmap = false;
140 //Minimize our window if desired
141 if (Properties.Settings.Default.StartMinimized)
143 WindowState = FormWindowState.Minimized;
151 /// <param name="sender"></param>
152 /// <param name="e"></param>
153 private void MainForm_Load(object sender, EventArgs e)
155 //Check if we are running a Click Once deployed application
156 if (ApplicationDeployment.IsNetworkDeployed)
158 //This is a proper Click Once installation, fetch and show our version number
159 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
163 //Not a proper Click Once installation, assuming development build then
164 this.Text += " - development";
168 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
169 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
170 UpdateAudioDeviceAndMasterVolumeThreadSafe();
173 iNetworkManager = new NetworkManager();
174 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
175 UpdateNetworkStatus();
177 //Setup notification icon
180 // To make sure start up with minimize to tray works
181 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
187 //When not debugging we want the screen to be empty until a client takes over
190 //When developing we want at least one client for testing
191 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
194 //Open display connection on start-up if needed
195 if (Properties.Settings.Default.DisplayConnectOnStartup)
197 OpenDisplayConnection();
200 //Start our server so that we can get client requests
203 //Register for HID events
204 RegisterHidDevices();
208 /// Called when our display is opened.
210 /// <param name="aDisplay"></param>
211 private void OnDisplayOpened(Display aDisplay)
213 //Make sure we resume frame rendering
214 iSkipFrameRendering = false;
216 //Set our screen size now that our display is connected
217 //Our panelDisplay is the container of our tableLayoutPanel
218 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
219 //panelDisplay needs an extra 2 pixels for borders on each sides
220 //tableLayoutPanel will eventually be the exact size of our display
221 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
222 panelDisplay.Size = size;
224 //Our display was just opened, update our UI
226 //Initiate asynchronous request
227 iDisplay.RequestFirmwareRevision();
230 UpdateMasterVolumeThreadSafe();
232 UpdateNetworkStatus();
235 //Testing icon in debug, no arm done if icon not supported
236 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
237 //iDisplay.SetAllIconsStatus(2);
243 /// Called when our display is closed.
245 /// <param name="aDisplay"></param>
246 private void OnDisplayClosed(Display aDisplay)
248 //Our display was just closed, update our UI consequently
252 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
254 //Update network status
255 UpdateNetworkStatus();
259 /// Update our Network Status
261 private void UpdateNetworkStatus()
263 if (iDisplay.IsOpen())
265 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
266 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
271 int iLastNetworkIconIndex = 0;
272 int iUpdateCountSinceLastNetworkAnimation = 0;
277 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
279 iUpdateCountSinceLastNetworkAnimation++;
280 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
282 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
284 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
287 //Prevents div by zero and other undefined behavior
290 iLastNetworkIconIndex++;
291 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
292 for (int i=0;i<iconCount;i++)
294 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
296 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
300 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
309 /// Receive volume change notification and reflect changes on our slider.
311 /// <param name="data"></param>
312 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
314 UpdateMasterVolumeThreadSafe();
318 /// Update master volume when user moves our slider.
320 /// <param name="sender"></param>
321 /// <param name="e"></param>
322 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
324 //Just like Windows Volume Mixer we unmute if the volume is adjusted
325 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
326 //Set volume level according to our volume slider new position
327 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
332 /// Mute check box changed.
334 /// <param name="sender"></param>
335 /// <param name="e"></param>
336 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
338 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
342 /// Device State Changed
344 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
349 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
354 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
357 /// Default Device Changed
359 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
361 if (role == Role.Multimedia && flow == DataFlow.Render)
363 UpdateAudioDeviceAndMasterVolumeThreadSafe();
368 /// Property Value Changed
370 /// <param name="pwstrDeviceId"></param>
371 /// <param name="key"></param>
372 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
378 /// Update master volume indicators based our current system states.
379 /// This typically includes volume levels and mute status.
381 private void UpdateMasterVolumeThreadSafe()
383 if (this.InvokeRequired)
385 //Not in the proper thread, invoke ourselves
386 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
387 this.Invoke(d, new object[] { });
391 //Update volume slider
392 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
393 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
394 //Update mute checkbox
395 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
397 //If our display connection is open we need to update its icons
398 if (iDisplay.IsOpen())
400 //First take care our our volume level icons
401 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
402 if (volumeIconCount > 0)
404 //Compute current volume level from system level and the number of segments in our display volume bar.
405 //That tells us how many segments in our volume bar needs to be turned on.
406 float currentVolume = volumeLevelScalar * volumeIconCount;
407 int segmentOnCount = Convert.ToInt32(currentVolume);
408 //Check if our segment count was rounded up, this will later be used for half brightness segment
409 bool roundedUp = segmentOnCount > currentVolume;
411 for (int i = 0; i < volumeIconCount; i++)
413 if (i < segmentOnCount)
415 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
416 if (i == segmentOnCount - 1 && roundedUp)
419 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
424 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
429 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
434 //Take care our our mute icon
435 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
443 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
445 if (this.InvokeRequired)
447 //Not in the proper thread, invoke ourselves
448 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
449 this.Invoke(d, new object[] { });
453 //We are in the correct thread just go ahead.
456 //Get our master volume
457 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
459 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
461 //Show our volume in our track bar
462 UpdateMasterVolumeThreadSafe();
464 //Register to get volume modifications
465 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
467 trackBarMasterVolume.Enabled = true;
471 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
472 Debug.WriteLine(ex.ToString());
473 //Something went wrong S/PDIF device ca throw exception I guess
474 trackBarMasterVolume.Enabled = false;
481 private void PopulateDeviceTypes()
483 int count = Display.TypeCount();
485 for (int i = 0; i < count; i++)
487 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
494 private void PopulateOpticalDrives()
496 //Reset our list of drives
497 comboBoxOpticalDrives.Items.Clear();
498 comboBoxOpticalDrives.Items.Add("None");
500 //Go through each drives on our system and collected the optical ones in our list
501 DriveInfo[] allDrives = DriveInfo.GetDrives();
502 foreach (DriveInfo d in allDrives)
504 Debug.WriteLine("Drive " + d.Name);
505 Debug.WriteLine(" Drive type: {0}", d.DriveType);
507 if (d.DriveType==DriveType.CDRom)
509 //This is an optical drive, add it now
510 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
518 /// <returns></returns>
519 public string OpticalDriveToEject()
521 return comboBoxOpticalDrives.SelectedItem.ToString();
529 private void SetupTrayIcon()
531 iNotifyIcon.Icon = GetIcon("vfd.ico");
532 iNotifyIcon.Text = "Sharp Display Manager";
533 iNotifyIcon.Visible = true;
535 //Double click toggles visibility - typically brings up the application
536 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
541 //Adding a context menu, useful to be able to exit the application
542 ContextMenu contextMenu = new ContextMenu();
543 //Context menu item to toggle visibility
544 MenuItem hideShowItem = new MenuItem("Hide/Show");
545 hideShowItem.Click += delegate(object obj, EventArgs args)
549 contextMenu.MenuItems.Add(hideShowItem);
551 //Context menu item separator
552 contextMenu.MenuItems.Add(new MenuItem("-"));
554 //Context menu exit item
555 MenuItem exitItem = new MenuItem("Exit");
556 exitItem.Click += delegate(object obj, EventArgs args)
560 contextMenu.MenuItems.Add(exitItem);
562 iNotifyIcon.ContextMenu = contextMenu;
566 /// Access icons from embedded resources.
568 /// <param name="name"></param>
569 /// <returns></returns>
570 public static Icon GetIcon(string name)
572 name = "SharpDisplayManager.Resources." + name;
575 Assembly.GetExecutingAssembly().GetManifestResourceNames();
576 for (int i = 0; i < names.Length; i++)
578 if (names[i].Replace('\\', '.') == name)
580 using (Stream stream = Assembly.GetExecutingAssembly().
581 GetManifestResourceStream(names[i]))
583 return new Icon(stream);
593 /// Set our current client.
594 /// This will take care of applying our client layout and set data fields.
596 /// <param name="aSessionId"></param>
597 void SetCurrentClient(string aSessionId, bool aForce=false)
599 if (aSessionId == iCurrentClientSessionId)
601 //Given client is already the current one.
602 //Don't bother changing anything then.
607 //Check when was the last time we switched to that client
608 if (iCurrentClientData != null)
610 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
611 //TODO: put that hard coded value as a client property
612 //Clients should be able to define how often they can be interrupted
613 //Thus a background client can set this to zero allowing any other client to interrupt at any time
614 //We could also compute this delay by looking at the requests frequencies?
615 if (!aForce && (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
617 //Don't switch clients too often
622 //Set current client ID.
623 iCurrentClientSessionId = aSessionId;
624 //Set the time we last switched to that client
625 iClients[aSessionId].LastSwitchTime = DateTime.Now;
626 //Fetch and set current client data.
627 iCurrentClientData = iClients[aSessionId];
628 //Apply layout and set data fields.
629 UpdateTableLayoutPanel(iCurrentClientData);
632 private void buttonFont_Click(object sender, EventArgs e)
634 //fontDialog.ShowColor = true;
635 //fontDialog.ShowApply = true;
636 fontDialog.ShowEffects = true;
637 fontDialog.Font = cds.Font;
639 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
641 //fontDialog.ShowHelp = true;
643 //fontDlg.MaxSize = 40;
644 //fontDlg.MinSize = 22;
646 //fontDialog.Parent = this;
647 //fontDialog.StartPosition = FormStartPosition.CenterParent;
649 //DlgBox.ShowDialog(fontDialog);
651 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
652 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
654 //Set the fonts to all our labels in our layout
655 foreach (Control ctrl in tableLayoutPanel.Controls)
657 if (ctrl is MarqueeLabel)
659 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
664 cds.Font = fontDialog.Font;
665 Properties.Settings.Default.Save();
674 void CheckFontHeight()
676 //Show font height and width
677 labelFontHeight.Text = "Font height: " + cds.Font.Height;
678 float charWidth = IsFixedWidth(cds.Font);
679 if (charWidth == 0.0f)
681 labelFontWidth.Visible = false;
685 labelFontWidth.Visible = true;
686 labelFontWidth.Text = "Font width: " + charWidth;
689 MarqueeLabel label = null;
690 //Get the first label control we can find
691 foreach (Control ctrl in tableLayoutPanel.Controls)
693 if (ctrl is MarqueeLabel)
695 label = (MarqueeLabel)ctrl;
700 //Now check font height and show a warning if needed.
701 if (label != null && label.Font.Height > label.Height)
703 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
704 labelWarning.Visible = true;
708 labelWarning.Visible = false;
713 private void buttonCapture_Click(object sender, EventArgs e)
715 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
716 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
717 //Bitmap bmpToSave = new Bitmap(bmp);
718 bmp.Save("D:\\capture.png");
720 ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
723 string outputFileName = "d:\\capture.png";
724 using (MemoryStream memory = new MemoryStream())
726 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
728 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
729 byte[] bytes = memory.ToArray();
730 fs.Write(bytes, 0, bytes.Length);
737 private void CheckForRequestResults()
739 if (iDisplay.IsRequestPending())
741 switch (iDisplay.AttemptRequestCompletion())
743 case MiniDisplay.Request.FirmwareRevision:
744 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
745 //Issue next request then
746 iDisplay.RequestPowerSupplyStatus();
749 case MiniDisplay.Request.PowerSupplyStatus:
750 if (iDisplay.PowerSupplyStatus())
752 toolStripStatusLabelPower.Text = "ON";
756 toolStripStatusLabelPower.Text = "OFF";
758 //Issue next request then
759 iDisplay.RequestDeviceId();
762 case MiniDisplay.Request.DeviceId:
763 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
764 //No more request to issue
770 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
772 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
779 public static uint ColorUntouched(int aX, int aY, uint aPixel)
784 public static uint ColorInversed(int aX, int aY, uint aPixel)
789 public static uint ColorChessboard(int aX, int aY, uint aPixel)
791 if ((aX % 2 == 0) && (aY % 2 == 0))
795 else if ((aX % 2 != 0) && (aY % 2 != 0))
803 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
805 return aBmp.Width - aX - 1;
808 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
810 return iBmp.Height - aY - 1;
813 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
818 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
824 /// Select proper pixel delegates according to our current settings.
826 private void SetupPixelDelegates()
828 //Select our pixel processing routine
829 if (cds.InverseColors)
831 //iColorFx = ColorChessboard;
832 iColorFx = ColorInversed;
836 iColorFx = ColorWhiteIsOn;
839 //Select proper coordinate translation functions
840 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
841 if (cds.ReverseScreen)
843 iScreenX = ScreenReversedX;
844 iScreenY = ScreenReversedY;
854 //This is our timer tick responsible to perform our render
855 private void timer_Tick(object sender, EventArgs e)
857 //Update our animations
858 DateTime NewTickTime = DateTime.Now;
860 UpdateNetworkSignal(LastTickTime, NewTickTime);
862 //Update animation for all our marquees
863 foreach (Control ctrl in tableLayoutPanel.Controls)
865 if (ctrl is MarqueeLabel)
867 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
872 if (iDisplay.IsOpen())
874 CheckForRequestResults();
876 //Check if frame rendering is needed
877 //Typically used when showing clock
878 if (!iSkipFrameRendering)
883 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
884 iCreateBitmap = false;
886 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
887 //iBmp.Save("D:\\capture.png");
889 //Send it to our display
890 for (int i = 0; i < iBmp.Width; i++)
892 for (int j = 0; j < iBmp.Height; j++)
896 //Get our processed pixel coordinates
897 int x = iScreenX(iBmp, i);
898 int y = iScreenY(iBmp, j);
900 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
901 //Apply color effects
902 color = iColorFx(x, y, color);
904 iDisplay.SetPixel(x, y, color);
909 iDisplay.SwapBuffers();
913 //Compute instant FPS
914 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
916 LastTickTime = NewTickTime;
921 /// Attempt to establish connection with our display hardware.
923 private void OpenDisplayConnection()
925 CloseDisplayConnection();
927 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
930 toolStripStatusLabelConnect.Text = "Connection error";
934 private void CloseDisplayConnection()
936 //Status will be updated upon receiving the closed event
938 if (iDisplay == null || !iDisplay.IsOpen())
943 //Do not clear if we gave up on rendering already.
944 //This means we will keep on displaying clock on MDM166AA for instance.
945 if (!iSkipFrameRendering)
948 iDisplay.SwapBuffers();
951 iDisplay.SetAllIconsStatus(0); //Turn off all icons
955 private void buttonOpen_Click(object sender, EventArgs e)
957 OpenDisplayConnection();
960 private void buttonClose_Click(object sender, EventArgs e)
962 CloseDisplayConnection();
965 private void buttonClear_Click(object sender, EventArgs e)
968 iDisplay.SwapBuffers();
971 private void buttonFill_Click(object sender, EventArgs e)
974 iDisplay.SwapBuffers();
977 private void trackBarBrightness_Scroll(object sender, EventArgs e)
979 cds.Brightness = trackBarBrightness.Value;
980 Properties.Settings.Default.Save();
981 iDisplay.SetBrightness(trackBarBrightness.Value);
987 /// CDS stands for Current Display Settings
989 private DisplaySettings cds
993 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
994 if (settings == null)
996 settings = new DisplaysSettings();
998 Properties.Settings.Default.DisplaysSettings = settings;
1001 //Make sure all our settings have been created
1002 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1004 settings.Displays.Add(new DisplaySettings());
1007 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1008 return displaySettings;
1013 /// Check if the given font has a fixed character pitch.
1015 /// <param name="ft"></param>
1016 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1017 public float IsFixedWidth(Font ft)
1019 Graphics g = CreateGraphics();
1020 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1021 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1023 bool fixedWidth = true;
1025 foreach (char c in charSizes)
1026 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1038 /// Synchronize UI with settings
1040 private void UpdateStatus()
1043 checkBoxShowBorders.Checked = cds.ShowBorders;
1044 tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1046 //Set the proper font to each of our labels
1047 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1049 ctrl.Font = cds.Font;
1053 //Check if "run on Windows startup" is enabled
1054 checkBoxAutoStart.Checked = iStartupManager.Startup;
1056 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1057 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1058 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1059 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1061 //Try find our drive in our drive list
1062 int opticalDriveItemIndex=0;
1063 bool driveNotFound = true;
1064 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1065 foreach (object item in comboBoxOpticalDrives.Items)
1067 if (opticalDriveToEject == item.ToString())
1069 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1070 driveNotFound = false;
1073 opticalDriveItemIndex++;
1078 //We could not find the drive we had saved.
1079 //Select "None" then.
1080 comboBoxOpticalDrives.SelectedIndex = 0;
1085 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1086 checkBoxInverseColors.Checked = cds.InverseColors;
1087 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1088 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1089 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1090 labelMinFontSize.Enabled = cds.ScaleToFit;
1091 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1092 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1093 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1094 timer.Interval = cds.TimerInterval;
1095 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1096 textBoxScrollLoopSeparator.Text = cds.Separator;
1098 SetupPixelDelegates();
1100 if (iDisplay.IsOpen())
1102 //We have a display connection
1103 //Reflect that in our UI
1105 tableLayoutPanel.Enabled = true;
1106 panelDisplay.Enabled = true;
1108 //Only setup brightness if display is open
1109 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1110 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1111 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1113 //Brightness out of range, this can occur when using auto-detect
1114 //Use max brightness instead
1115 trackBarBrightness.Value = iDisplay.MaxBrightness();
1116 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1120 trackBarBrightness.Value = cds.Brightness;
1121 iDisplay.SetBrightness(cds.Brightness);
1124 //Try compute the steps to something that makes sense
1125 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1126 trackBarBrightness.SmallChange = 1;
1129 buttonFill.Enabled = true;
1130 buttonClear.Enabled = true;
1131 buttonOpen.Enabled = false;
1132 buttonClose.Enabled = true;
1133 trackBarBrightness.Enabled = true;
1134 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1135 //+ " - " + iDisplay.SerialNumber();
1137 if (iDisplay.SupportPowerOnOff())
1139 buttonPowerOn.Enabled = true;
1140 buttonPowerOff.Enabled = true;
1144 buttonPowerOn.Enabled = false;
1145 buttonPowerOff.Enabled = false;
1148 if (iDisplay.SupportClock())
1150 buttonShowClock.Enabled = true;
1151 buttonHideClock.Enabled = true;
1155 buttonShowClock.Enabled = false;
1156 buttonHideClock.Enabled = false;
1160 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1161 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1163 if (cds.ShowVolumeLabel)
1165 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1169 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1174 //Display is connection not available
1175 //Reflect that in our UI
1176 checkBoxShowVolumeLabel.Enabled = false;
1177 tableLayoutPanel.Enabled = false;
1178 panelDisplay.Enabled = false;
1179 buttonFill.Enabled = false;
1180 buttonClear.Enabled = false;
1181 buttonOpen.Enabled = true;
1182 buttonClose.Enabled = false;
1183 trackBarBrightness.Enabled = false;
1184 buttonPowerOn.Enabled = false;
1185 buttonPowerOff.Enabled = false;
1186 buttonShowClock.Enabled = false;
1187 buttonHideClock.Enabled = false;
1188 toolStripStatusLabelConnect.Text = "Disconnected";
1189 toolStripStatusLabelPower.Text = "N/A";
1198 /// <param name="sender"></param>
1199 /// <param name="e"></param>
1200 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1202 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1203 Properties.Settings.Default.Save();
1207 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1209 //Save our show borders setting
1210 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1211 cds.ShowBorders = checkBoxShowBorders.Checked;
1212 Properties.Settings.Default.Save();
1216 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1218 //Save our connect on startup setting
1219 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1220 Properties.Settings.Default.Save();
1223 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1225 //Save our "Minimize to tray" setting
1226 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1227 Properties.Settings.Default.Save();
1231 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1233 //Save our "Start minimized" setting
1234 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1235 Properties.Settings.Default.Save();
1238 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1240 iStartupManager.Startup = checkBoxAutoStart.Checked;
1244 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1246 //Save our reverse screen setting
1247 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1248 Properties.Settings.Default.Save();
1249 SetupPixelDelegates();
1252 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1254 //Save our inverse colors setting
1255 cds.InverseColors = checkBoxInverseColors.Checked;
1256 Properties.Settings.Default.Save();
1257 SetupPixelDelegates();
1260 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1262 //Save our scale to fit setting
1263 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1264 Properties.Settings.Default.Save();
1266 labelMinFontSize.Enabled = cds.ScaleToFit;
1267 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1270 private void MainForm_Resize(object sender, EventArgs e)
1272 if (WindowState == FormWindowState.Minimized)
1274 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1275 // That's apparently not needed on Windows 10 but we better leave it in place.
1276 iCreateBitmap = true;
1280 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1282 iNetworkManager.Dispose();
1283 CloseDisplayConnection();
1285 e.Cancel = iClosing;
1288 public void StartServer()
1290 iServiceHost = new ServiceHost
1293 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1296 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1297 iServiceHost.Open();
1300 public void StopServer()
1302 if (iClients.Count > 0 && !iClosing)
1306 BroadcastCloseEvent();
1310 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1312 iClosing = false; //We make sure we force close if asked twice
1317 //We removed that as it often lags for some reason
1318 //iServiceHost.Close();
1322 public void BroadcastCloseEvent()
1324 Trace.TraceInformation("BroadcastCloseEvent - start");
1326 var inactiveClients = new List<string>();
1327 foreach (var client in iClients)
1329 //if (client.Key != eventData.ClientName)
1333 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1334 client.Value.Callback.OnCloseOrder(/*eventData*/);
1336 catch (Exception ex)
1338 inactiveClients.Add(client.Key);
1343 if (inactiveClients.Count > 0)
1345 foreach (var client in inactiveClients)
1347 iClients.Remove(client);
1348 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
1352 if (iClients.Count==0)
1359 /// Just remove all our fields.
1361 private void ClearLayout()
1363 tableLayoutPanel.Controls.Clear();
1364 tableLayoutPanel.RowStyles.Clear();
1365 tableLayoutPanel.ColumnStyles.Clear();
1366 iCurrentClientData = null;
1370 /// Just launch a demo client.
1372 private void StartNewClient(string aTopText = "", string aBottomText = "")
1374 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1375 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1376 clientThread.Start(myParams);
1380 private void buttonStartClient_Click(object sender, EventArgs e)
1385 private void buttonSuspend_Click(object sender, EventArgs e)
1387 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1388 timer.Enabled = !timer.Enabled;
1391 buttonSuspend.Text = "Run";
1395 buttonSuspend.Text = "Pause";
1399 private void buttonCloseClients_Click(object sender, EventArgs e)
1401 BroadcastCloseEvent();
1404 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1406 //Root node must have at least one child
1407 if (e.Node.Nodes.Count == 0)
1412 //If the selected node is the root node of a client then switch to it
1413 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1414 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1416 //We have a valid session just switch to that client
1417 SetCurrentClient(sessionId,true);
1426 /// <param name="aSessionId"></param>
1427 /// <param name="aCallback"></param>
1428 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1430 if (this.InvokeRequired)
1432 //Not in the proper thread, invoke ourselves
1433 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1434 this.Invoke(d, new object[] { aSessionId, aCallback });
1438 //We are in the proper thread
1439 //Add this session to our collection of clients
1440 ClientData newClient = new ClientData(aSessionId, aCallback);
1441 Program.iMainForm.iClients.Add(aSessionId, newClient);
1442 //Add this session to our UI
1443 UpdateClientTreeViewNode(newClient);
1450 /// <param name="aSessionId"></param>
1451 public void RemoveClientThreadSafe(string aSessionId)
1453 if (this.InvokeRequired)
1455 //Not in the proper thread, invoke ourselves
1456 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1457 this.Invoke(d, new object[] { aSessionId });
1461 //We are in the proper thread
1462 //Remove this session from both client collection and UI tree view
1463 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1465 Program.iMainForm.iClients.Remove(aSessionId);
1466 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
1469 if (iClients.Count == 0)
1471 //Clear our screen when last client disconnects
1476 //We were closing our form
1477 //All clients are now closed
1478 //Just resume our close operation
1489 /// <param name="aSessionId"></param>
1490 /// <param name="aLayout"></param>
1491 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1493 if (this.InvokeRequired)
1495 //Not in the proper thread, invoke ourselves
1496 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1497 this.Invoke(d, new object[] { aSessionId, aLayout });
1501 ClientData client = iClients[aSessionId];
1504 //Don't change a thing if the layout is the same
1505 if (!client.Layout.IsSameAs(aLayout))
1507 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1508 //Set our client layout then
1509 client.Layout = aLayout;
1510 //Layout has changed clear our fields then
1511 client.Fields.Clear();
1513 UpdateClientTreeViewNode(client);
1517 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1526 /// <param name="aSessionId"></param>
1527 /// <param name="aField"></param>
1528 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1530 if (this.InvokeRequired)
1532 //Not in the proper thread, invoke ourselves
1533 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1534 this.Invoke(d, new object[] { aSessionId, aField });
1538 //We are in the proper thread
1539 //Call the non-thread-safe variant
1540 SetClientField(aSessionId, aField);
1547 /// <param name="aSessionId"></param>
1548 /// <param name="aField"></param>
1549 private void SetClientField(string aSessionId, DataField aField)
1551 //TODO: should check if the field actually changed?
1553 ClientData client = iClients[aSessionId];
1554 bool layoutChanged = false;
1555 bool contentChanged = true;
1557 //Make sure all our fields are in place
1558 while (client.Fields.Count < (aField.Index + 1))
1560 //Add a data field with proper index
1561 client.Fields.Add(new DataField(client.Fields.Count));
1562 layoutChanged = true;
1565 //Now that we know our fields are in place just update that one
1566 client.Fields[aField.Index] = aField;
1568 if (client.Fields[aField.Index].IsSameLayout(aField))
1570 //If we are updating a field in our current client we need to update it in our panel
1571 if (aSessionId == iCurrentClientSessionId)
1573 if (aField.IsText && aField.Index < tableLayoutPanel.Controls.Count && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
1575 //Text field control already in place, just change the text
1576 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
1577 contentChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
1578 label.Text = aField.Text;
1579 label.TextAlign = aField.Alignment;
1581 else if (aField.IsBitmap && aField.Index < tableLayoutPanel.Controls.Count && tableLayoutPanel.Controls[aField.Index] is PictureBox)
1583 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1584 //Bitmap field control already in place just change the bitmap
1585 PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
1586 pictureBox.Image = aField.Bitmap;
1590 layoutChanged = true;
1596 layoutChanged = true;
1599 //If either content or layout changed we need to update our tree view to reflect the changes
1600 if (contentChanged || layoutChanged)
1602 UpdateClientTreeViewNode(client);
1606 Debug.Print("Layout changed");
1607 //Our layout has changed, if we are already the current client we need to update our panel
1608 if (aSessionId == iCurrentClientSessionId)
1610 //Apply layout and set data fields.
1611 UpdateTableLayoutPanel(iCurrentClientData);
1616 Debug.Print("Layout has not changed.");
1621 Debug.Print("WARNING: content and layout have not changed!");
1624 //When a client field is set we try switching to this client to present the new information to our user
1625 SetCurrentClient(aSessionId);
1631 /// <param name="aTexts"></param>
1632 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1634 if (this.InvokeRequired)
1636 //Not in the proper thread, invoke ourselves
1637 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1638 this.Invoke(d, new object[] { aSessionId, aFields });
1642 //Put each our text fields in a label control
1643 foreach (DataField field in aFields)
1645 SetClientField(aSessionId, field);
1653 /// <param name="aSessionId"></param>
1654 /// <param name="aName"></param>
1655 public void SetClientNameThreadSafe(string aSessionId, string aName)
1657 if (this.InvokeRequired)
1659 //Not in the proper thread, invoke ourselves
1660 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1661 this.Invoke(d, new object[] { aSessionId, aName });
1665 //We are in the proper thread
1667 ClientData client = iClients[aSessionId];
1671 client.Name = aName;
1672 //Update our tree-view
1673 UpdateClientTreeViewNode(client);
1681 /// <param name="aClient"></param>
1682 private void UpdateClientTreeViewNode(ClientData aClient)
1684 Debug.Print("UpdateClientTreeViewNode");
1686 if (aClient == null)
1691 TreeNode node = null;
1692 //Check that our client node already exists
1693 //Get our client root node using its key which is our session ID
1694 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1695 if (nodes.Count()>0)
1697 //We already have a node for that client
1699 //Clear children as we are going to recreate them below
1704 //Client node does not exists create a new one
1705 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1706 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1712 if (aClient.Name != "")
1714 //We have a name, us it as text for our root node
1715 node.Text = aClient.Name;
1716 //Add a child with SessionId
1717 node.Nodes.Add(new TreeNode(aClient.SessionId));
1721 //No name, use session ID instead
1722 node.Text = aClient.SessionId;
1725 if (aClient.Fields.Count > 0)
1727 //Create root node for our texts
1728 TreeNode textsRoot = new TreeNode("Fields");
1729 node.Nodes.Add(textsRoot);
1730 //For each text add a new entry
1731 foreach (DataField field in aClient.Fields)
1733 if (!field.IsBitmap)
1735 DataField textField = (DataField)field;
1736 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1740 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1750 /// Update our table layout row styles to make sure each rows have similar height
1752 private void UpdateTableLayoutRowStyles()
1754 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1756 rowStyle.SizeType = SizeType.Percent;
1757 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1762 /// Update our display table layout.
1764 /// <param name="aLayout"></param>
1765 private void UpdateTableLayoutPanel(ClientData aClient)
1767 Debug.Print("UpdateClientTreeViewNode");
1769 if (aClient == null)
1776 TableLayout layout = aClient.Layout;
1779 //First clean our current panel
1780 tableLayoutPanel.Controls.Clear();
1781 tableLayoutPanel.RowStyles.Clear();
1782 tableLayoutPanel.ColumnStyles.Clear();
1783 tableLayoutPanel.RowCount = 0;
1784 tableLayoutPanel.ColumnCount = 0;
1786 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1788 tableLayoutPanel.RowCount++;
1791 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1793 tableLayoutPanel.ColumnCount++;
1796 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1798 //Create our column styles
1799 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1801 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1805 //Create our row styles
1806 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1809 //Check if we already have a control
1810 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1811 if (existingControl!=null)
1813 //We already have a control in that cell as a results of row/col spanning
1814 //Move on to next cell then
1820 //Check if a client field already exists for that cell
1821 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1823 //No client field specified, create a text field by default
1824 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1827 //Create a control corresponding to the field specified for that cell
1828 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1829 Control control = CreateControlForDataField(field);
1831 //Add newly created control to our table layout at the specified row and column
1832 tableLayoutPanel.Controls.Add(control, i, j);
1833 //Make sure we specify row and column span for that new control
1834 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1835 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1840 while (aClient.Fields.Count > fieldCount)
1842 //We have too much fields for this layout
1843 //Just discard them until we get there
1844 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1851 /// Check our type of data field and create corresponding control
1853 /// <param name="aField"></param>
1854 private Control CreateControlForDataField(DataField aField)
1856 Control control=null;
1857 if (!aField.IsBitmap)
1859 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1860 label.AutoEllipsis = true;
1861 label.AutoSize = true;
1862 label.BackColor = System.Drawing.Color.Transparent;
1863 label.Dock = System.Windows.Forms.DockStyle.Fill;
1864 label.Location = new System.Drawing.Point(1, 1);
1865 label.Margin = new System.Windows.Forms.Padding(0);
1866 label.Name = "marqueeLabel" + aField.Index;
1867 label.OwnTimer = false;
1868 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
1869 label.Separator = cds.Separator;
1870 label.MinFontSize = cds.MinFontSize;
1871 label.ScaleToFit = cds.ScaleToFit;
1872 //control.Size = new System.Drawing.Size(254, 30);
1873 //control.TabIndex = 2;
1874 label.Font = cds.Font;
1876 label.TextAlign = aField.Alignment;
1877 label.UseCompatibleTextRendering = true;
1878 label.Text = aField.Text;
1884 //Create picture box
1885 PictureBox picture = new PictureBox();
1886 picture.AutoSize = true;
1887 picture.BackColor = System.Drawing.Color.Transparent;
1888 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1889 picture.Location = new System.Drawing.Point(1, 1);
1890 picture.Margin = new System.Windows.Forms.Padding(0);
1891 picture.Name = "pictureBox" + aField;
1893 picture.Image = aField.Bitmap;
1902 /// Called when the user selected a new display type.
1904 /// <param name="sender"></param>
1905 /// <param name="e"></param>
1906 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1908 //Store the selected display type in our settings
1909 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1910 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1911 Properties.Settings.Default.Save();
1913 //Try re-opening the display connection if we were already connected.
1914 //Otherwise just update our status to reflect display type change.
1915 if (iDisplay.IsOpen())
1917 OpenDisplayConnection();
1925 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1927 if (maskedTextBoxTimerInterval.Text != "")
1929 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1933 timer.Interval = interval;
1934 cds.TimerInterval = timer.Interval;
1935 Properties.Settings.Default.Save();
1940 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
1942 if (maskedTextBoxMinFontSize.Text != "")
1944 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
1946 if (minFontSize > 0)
1948 cds.MinFontSize = minFontSize;
1949 Properties.Settings.Default.Save();
1950 //We need to recreate our layout for that change to take effect
1951 UpdateTableLayoutPanel(iCurrentClientData);
1957 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
1959 if (maskedTextBoxScrollingSpeed.Text != "")
1961 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
1963 if (scrollingSpeed > 0)
1965 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
1966 Properties.Settings.Default.Save();
1967 //We need to recreate our layout for that change to take effect
1968 UpdateTableLayoutPanel(iCurrentClientData);
1973 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
1975 cds.Separator = textBoxScrollLoopSeparator.Text;
1976 Properties.Settings.Default.Save();
1978 //Update our text fields
1979 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1981 ctrl.Separator = cds.Separator;
1986 private void buttonPowerOn_Click(object sender, EventArgs e)
1991 private void buttonPowerOff_Click(object sender, EventArgs e)
1993 iDisplay.PowerOff();
1996 private void buttonShowClock_Click(object sender, EventArgs e)
2001 private void buttonHideClock_Click(object sender, EventArgs e)
2006 private void buttonUpdate_Click(object sender, EventArgs e)
2008 InstallUpdateSyncWithInfo();
2016 if (!iDisplay.IsOpen())
2021 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2022 iSkipFrameRendering = true;
2025 iDisplay.SwapBuffers();
2026 //Then show our clock
2027 iDisplay.ShowClock();
2035 if (!iDisplay.IsOpen())
2040 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2041 iSkipFrameRendering = false;
2042 iDisplay.HideClock();
2045 private void InstallUpdateSyncWithInfo()
2047 UpdateCheckInfo info = null;
2049 if (ApplicationDeployment.IsNetworkDeployed)
2051 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2055 info = ad.CheckForDetailedUpdate();
2058 catch (DeploymentDownloadException dde)
2060 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);
2063 catch (InvalidDeploymentException ide)
2065 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);
2068 catch (InvalidOperationException ioe)
2070 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2074 if (info.UpdateAvailable)
2076 Boolean doUpdate = true;
2078 if (!info.IsUpdateRequired)
2080 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2081 if (!(DialogResult.OK == dr))
2088 // Display a message that the app MUST reboot. Display the minimum required version.
2089 MessageBox.Show("This application has detected a mandatory update from your current " +
2090 "version to version " + info.MinimumRequiredVersion.ToString() +
2091 ". The application will now install the update and restart.",
2092 "Update Available", MessageBoxButtons.OK,
2093 MessageBoxIcon.Information);
2101 MessageBox.Show("The application has been upgraded, and will now restart.");
2102 Application.Restart();
2104 catch (DeploymentDownloadException dde)
2106 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2113 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2122 private void SysTrayHideShow()
2128 WindowState = FormWindowState.Normal;
2133 /// Use to handle minimize events.
2135 /// <param name="sender"></param>
2136 /// <param name="e"></param>
2137 private void MainForm_SizeChanged(object sender, EventArgs e)
2139 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2151 /// <param name="sender"></param>
2152 /// <param name="e"></param>
2153 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2155 //Our table layout size has changed which means our display size has changed.
2156 //We need to re-create our bitmap.
2157 iCreateBitmap = true;
2163 /// <param name="sender"></param>
2164 /// <param name="e"></param>
2165 private void buttonSelectFile_Click(object sender, EventArgs e)
2167 //openFileDialog1.InitialDirectory = "c:\\";
2168 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2169 //openFileDialog.FilterIndex = 1;
2170 openFileDialog.RestoreDirectory = true;
2172 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2174 labelStartFileName.Text = openFileDialog.FileName;
2175 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2176 Properties.Settings.Default.Save();
2183 /// <param name="sender"></param>
2184 /// <param name="e"></param>
2185 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2187 //Save the optical drive the user selected for ejection
2188 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2189 Properties.Settings.Default.Save();