Better CEC architecture.
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();
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 NotifyIconAdv 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 NotifyIconAdv();
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(tableLayoutPanel.Width, tableLayoutPanel.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;
190 iCecManager.Start(Handle,"CEC", 2);
192 //Setup notification icon
195 // To make sure start up with minimize to tray works
196 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
202 //When not debugging we want the screen to be empty until a client takes over
205 //When developing we want at least one client for testing
206 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
209 //Open display connection on start-up if needed
210 if (Properties.Settings.Default.DisplayConnectOnStartup)
212 OpenDisplayConnection();
215 //Start our server so that we can get client requests
218 //Register for HID events
219 RegisterHidDevices();
223 /// Called when our display is opened.
225 /// <param name="aDisplay"></param>
226 private void OnDisplayOpened(Display aDisplay)
228 //Make sure we resume frame rendering
229 iSkipFrameRendering = false;
231 //Set our screen size now that our display is connected
232 //Our panelDisplay is the container of our tableLayoutPanel
233 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
234 //panelDisplay needs an extra 2 pixels for borders on each sides
235 //tableLayoutPanel will eventually be the exact size of our display
236 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
237 panelDisplay.Size = size;
239 //Our display was just opened, update our UI
241 //Initiate asynchronous request
242 iDisplay.RequestFirmwareRevision();
245 UpdateMasterVolumeThreadSafe();
247 UpdateNetworkStatus();
250 //Testing icon in debug, no arm done if icon not supported
251 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
252 //iDisplay.SetAllIconsStatus(2);
258 /// Called when our display is closed.
260 /// <param name="aDisplay"></param>
261 private void OnDisplayClosed(Display aDisplay)
263 //Our display was just closed, update our UI consequently
267 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
269 //Update network status
270 UpdateNetworkStatus();
274 /// Update our Network Status
276 private void UpdateNetworkStatus()
278 if (iDisplay.IsOpen())
280 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
281 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
286 int iLastNetworkIconIndex = 0;
287 int iUpdateCountSinceLastNetworkAnimation = 0;
292 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
294 iUpdateCountSinceLastNetworkAnimation++;
295 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
297 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
299 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
302 //Prevents div by zero and other undefined behavior
305 iLastNetworkIconIndex++;
306 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
307 for (int i=0;i<iconCount;i++)
309 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
311 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
315 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
324 /// Receive volume change notification and reflect changes on our slider.
326 /// <param name="data"></param>
327 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
329 UpdateMasterVolumeThreadSafe();
333 /// Update master volume when user moves our slider.
335 /// <param name="sender"></param>
336 /// <param name="e"></param>
337 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
339 //Just like Windows Volume Mixer we unmute if the volume is adjusted
340 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
341 //Set volume level according to our volume slider new position
342 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
347 /// Mute check box changed.
349 /// <param name="sender"></param>
350 /// <param name="e"></param>
351 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
353 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
357 /// Device State Changed
359 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
364 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
369 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
372 /// Default Device Changed
374 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
376 if (role == Role.Multimedia && flow == DataFlow.Render)
378 UpdateAudioDeviceAndMasterVolumeThreadSafe();
383 /// Property Value Changed
385 /// <param name="pwstrDeviceId"></param>
386 /// <param name="key"></param>
387 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
393 /// Update master volume indicators based our current system states.
394 /// This typically includes volume levels and mute status.
396 private void UpdateMasterVolumeThreadSafe()
398 if (this.InvokeRequired)
400 //Not in the proper thread, invoke ourselves
401 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
402 this.Invoke(d, new object[] { });
406 //Update volume slider
407 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
408 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
409 //Update mute checkbox
410 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
412 //If our display connection is open we need to update its icons
413 if (iDisplay.IsOpen())
415 //First take care our our volume level icons
416 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
417 if (volumeIconCount > 0)
419 //Compute current volume level from system level and the number of segments in our display volume bar.
420 //That tells us how many segments in our volume bar needs to be turned on.
421 float currentVolume = volumeLevelScalar * volumeIconCount;
422 int segmentOnCount = Convert.ToInt32(currentVolume);
423 //Check if our segment count was rounded up, this will later be used for half brightness segment
424 bool roundedUp = segmentOnCount > currentVolume;
426 for (int i = 0; i < volumeIconCount; i++)
428 if (i < segmentOnCount)
430 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
431 if (i == segmentOnCount - 1 && roundedUp)
434 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
439 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
444 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
449 //Take care our our mute icon
450 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
458 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
460 if (this.InvokeRequired)
462 //Not in the proper thread, invoke ourselves
463 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
464 this.Invoke(d, new object[] { });
468 //We are in the correct thread just go ahead.
471 //Get our master volume
472 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
474 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
476 //Show our volume in our track bar
477 UpdateMasterVolumeThreadSafe();
479 //Register to get volume modifications
480 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
482 trackBarMasterVolume.Enabled = true;
486 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
487 Debug.WriteLine(ex.ToString());
488 //Something went wrong S/PDIF device ca throw exception I guess
489 trackBarMasterVolume.Enabled = false;
496 private void PopulateDeviceTypes()
498 int count = Display.TypeCount();
500 for (int i = 0; i < count; i++)
502 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
509 private void PopulateOpticalDrives()
511 //Reset our list of drives
512 comboBoxOpticalDrives.Items.Clear();
513 comboBoxOpticalDrives.Items.Add("None");
515 //Go through each drives on our system and collected the optical ones in our list
516 DriveInfo[] allDrives = DriveInfo.GetDrives();
517 foreach (DriveInfo d in allDrives)
519 Debug.WriteLine("Drive " + d.Name);
520 Debug.WriteLine(" Drive type: {0}", d.DriveType);
522 if (d.DriveType==DriveType.CDRom)
524 //This is an optical drive, add it now
525 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
533 /// <returns></returns>
534 public string OpticalDriveToEject()
536 return comboBoxOpticalDrives.SelectedItem.ToString();
544 private void SetupTrayIcon()
546 iNotifyIcon.Icon = GetIcon("vfd.ico");
547 iNotifyIcon.Text = "Sharp Display Manager";
548 iNotifyIcon.Visible = true;
550 //Double click toggles visibility - typically brings up the application
551 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
556 //Adding a context menu, useful to be able to exit the application
557 ContextMenu contextMenu = new ContextMenu();
558 //Context menu item to toggle visibility
559 MenuItem hideShowItem = new MenuItem("Hide/Show");
560 hideShowItem.Click += delegate(object obj, EventArgs args)
564 contextMenu.MenuItems.Add(hideShowItem);
566 //Context menu item separator
567 contextMenu.MenuItems.Add(new MenuItem("-"));
569 //Context menu exit item
570 MenuItem exitItem = new MenuItem("Exit");
571 exitItem.Click += delegate(object obj, EventArgs args)
575 contextMenu.MenuItems.Add(exitItem);
577 iNotifyIcon.ContextMenu = contextMenu;
581 /// Access icons from embedded resources.
583 /// <param name="name"></param>
584 /// <returns></returns>
585 public static Icon GetIcon(string name)
587 name = "SharpDisplayManager.Resources." + name;
590 Assembly.GetExecutingAssembly().GetManifestResourceNames();
591 for (int i = 0; i < names.Length; i++)
593 if (names[i].Replace('\\', '.') == name)
595 using (Stream stream = Assembly.GetExecutingAssembly().
596 GetManifestResourceStream(names[i]))
598 return new Icon(stream);
608 /// Set our current client.
609 /// This will take care of applying our client layout and set data fields.
611 /// <param name="aSessionId"></param>
612 void SetCurrentClient(string aSessionId, bool aForce=false)
614 if (aSessionId == iCurrentClientSessionId)
616 //Given client is already the current one.
617 //Don't bother changing anything then.
622 //Check when was the last time we switched to that client
623 if (iCurrentClientData != null)
625 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
626 //TODO: put that hard coded value as a client property
627 //Clients should be able to define how often they can be interrupted
628 //Thus a background client can set this to zero allowing any other client to interrupt at any time
629 //We could also compute this delay by looking at the requests frequencies?
630 if (!aForce && (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
632 //Don't switch clients too often
637 //Set current client ID.
638 iCurrentClientSessionId = aSessionId;
639 //Set the time we last switched to that client
640 iClients[aSessionId].LastSwitchTime = DateTime.Now;
641 //Fetch and set current client data.
642 iCurrentClientData = iClients[aSessionId];
643 //Apply layout and set data fields.
644 UpdateTableLayoutPanel(iCurrentClientData);
647 private void buttonFont_Click(object sender, EventArgs e)
649 //fontDialog.ShowColor = true;
650 //fontDialog.ShowApply = true;
651 fontDialog.ShowEffects = true;
652 fontDialog.Font = cds.Font;
654 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
656 //fontDialog.ShowHelp = true;
658 //fontDlg.MaxSize = 40;
659 //fontDlg.MinSize = 22;
661 //fontDialog.Parent = this;
662 //fontDialog.StartPosition = FormStartPosition.CenterParent;
664 //DlgBox.ShowDialog(fontDialog);
666 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
667 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
669 //Set the fonts to all our labels in our layout
670 foreach (Control ctrl in tableLayoutPanel.Controls)
672 if (ctrl is MarqueeLabel)
674 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
679 cds.Font = fontDialog.Font;
680 Properties.Settings.Default.Save();
689 void CheckFontHeight()
691 //Show font height and width
692 labelFontHeight.Text = "Font height: " + cds.Font.Height;
693 float charWidth = IsFixedWidth(cds.Font);
694 if (charWidth == 0.0f)
696 labelFontWidth.Visible = false;
700 labelFontWidth.Visible = true;
701 labelFontWidth.Text = "Font width: " + charWidth;
704 MarqueeLabel label = null;
705 //Get the first label control we can find
706 foreach (Control ctrl in tableLayoutPanel.Controls)
708 if (ctrl is MarqueeLabel)
710 label = (MarqueeLabel)ctrl;
715 //Now check font height and show a warning if needed.
716 if (label != null && label.Font.Height > label.Height)
718 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
719 labelWarning.Visible = true;
723 labelWarning.Visible = false;
728 private void buttonCapture_Click(object sender, EventArgs e)
730 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
731 tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
732 //Bitmap bmpToSave = new Bitmap(bmp);
733 bmp.Save("D:\\capture.png");
735 ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
738 string outputFileName = "d:\\capture.png";
739 using (MemoryStream memory = new MemoryStream())
741 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
743 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
744 byte[] bytes = memory.ToArray();
745 fs.Write(bytes, 0, bytes.Length);
752 private void CheckForRequestResults()
754 if (iDisplay.IsRequestPending())
756 switch (iDisplay.AttemptRequestCompletion())
758 case MiniDisplay.Request.FirmwareRevision:
759 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
760 //Issue next request then
761 iDisplay.RequestPowerSupplyStatus();
764 case MiniDisplay.Request.PowerSupplyStatus:
765 if (iDisplay.PowerSupplyStatus())
767 toolStripStatusLabelPower.Text = "ON";
771 toolStripStatusLabelPower.Text = "OFF";
773 //Issue next request then
774 iDisplay.RequestDeviceId();
777 case MiniDisplay.Request.DeviceId:
778 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
779 //No more request to issue
785 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
787 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
794 public static uint ColorUntouched(int aX, int aY, uint aPixel)
799 public static uint ColorInversed(int aX, int aY, uint aPixel)
804 public static uint ColorChessboard(int aX, int aY, uint aPixel)
806 if ((aX % 2 == 0) && (aY % 2 == 0))
810 else if ((aX % 2 != 0) && (aY % 2 != 0))
818 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
820 return aBmp.Width - aX - 1;
823 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
825 return iBmp.Height - aY - 1;
828 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
833 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
839 /// Select proper pixel delegates according to our current settings.
841 private void SetupPixelDelegates()
843 //Select our pixel processing routine
844 if (cds.InverseColors)
846 //iColorFx = ColorChessboard;
847 iColorFx = ColorInversed;
851 iColorFx = ColorWhiteIsOn;
854 //Select proper coordinate translation functions
855 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
856 if (cds.ReverseScreen)
858 iScreenX = ScreenReversedX;
859 iScreenY = ScreenReversedY;
869 //This is our timer tick responsible to perform our render
870 private void timer_Tick(object sender, EventArgs e)
872 //Update our animations
873 DateTime NewTickTime = DateTime.Now;
875 UpdateNetworkSignal(LastTickTime, NewTickTime);
877 //Update animation for all our marquees
878 foreach (Control ctrl in tableLayoutPanel.Controls)
880 if (ctrl is MarqueeLabel)
882 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
887 if (iDisplay.IsOpen())
889 CheckForRequestResults();
891 //Check if frame rendering is needed
892 //Typically used when showing clock
893 if (!iSkipFrameRendering)
898 iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
899 iCreateBitmap = false;
901 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
902 //iBmp.Save("D:\\capture.png");
904 //Send it to our display
905 for (int i = 0; i < iBmp.Width; i++)
907 for (int j = 0; j < iBmp.Height; j++)
911 //Get our processed pixel coordinates
912 int x = iScreenX(iBmp, i);
913 int y = iScreenY(iBmp, j);
915 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
916 //Apply color effects
917 color = iColorFx(x, y, color);
919 iDisplay.SetPixel(x, y, color);
924 iDisplay.SwapBuffers();
928 //Compute instant FPS
929 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
931 LastTickTime = NewTickTime;
936 /// Attempt to establish connection with our display hardware.
938 private void OpenDisplayConnection()
940 CloseDisplayConnection();
942 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
945 toolStripStatusLabelConnect.Text = "Connection error";
949 private void CloseDisplayConnection()
951 //Status will be updated upon receiving the closed event
953 if (iDisplay == null || !iDisplay.IsOpen())
958 //Do not clear if we gave up on rendering already.
959 //This means we will keep on displaying clock on MDM166AA for instance.
960 if (!iSkipFrameRendering)
963 iDisplay.SwapBuffers();
966 iDisplay.SetAllIconsStatus(0); //Turn off all icons
970 private void buttonOpen_Click(object sender, EventArgs e)
972 OpenDisplayConnection();
975 private void buttonClose_Click(object sender, EventArgs e)
977 CloseDisplayConnection();
980 private void buttonClear_Click(object sender, EventArgs e)
983 iDisplay.SwapBuffers();
986 private void buttonFill_Click(object sender, EventArgs e)
989 iDisplay.SwapBuffers();
992 private void trackBarBrightness_Scroll(object sender, EventArgs e)
994 cds.Brightness = trackBarBrightness.Value;
995 Properties.Settings.Default.Save();
996 iDisplay.SetBrightness(trackBarBrightness.Value);
1002 /// CDS stands for Current Display Settings
1004 private DisplaySettings cds
1008 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1009 if (settings == null)
1011 settings = new DisplaysSettings();
1013 Properties.Settings.Default.DisplaysSettings = settings;
1016 //Make sure all our settings have been created
1017 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1019 settings.Displays.Add(new DisplaySettings());
1022 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1023 return displaySettings;
1028 /// Check if the given font has a fixed character pitch.
1030 /// <param name="ft"></param>
1031 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1032 public float IsFixedWidth(Font ft)
1034 Graphics g = CreateGraphics();
1035 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1036 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1038 bool fixedWidth = true;
1040 foreach (char c in charSizes)
1041 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1053 /// Synchronize UI with settings
1055 private void UpdateStatus()
1058 checkBoxShowBorders.Checked = cds.ShowBorders;
1059 tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1061 //Set the proper font to each of our labels
1062 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1064 ctrl.Font = cds.Font;
1068 //Check if "run on Windows startup" is enabled
1069 checkBoxAutoStart.Checked = iStartupManager.Startup;
1071 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1072 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1073 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1074 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1076 //Try find our drive in our drive list
1077 int opticalDriveItemIndex=0;
1078 bool driveNotFound = true;
1079 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1080 foreach (object item in comboBoxOpticalDrives.Items)
1082 if (opticalDriveToEject == item.ToString())
1084 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1085 driveNotFound = false;
1088 opticalDriveItemIndex++;
1093 //We could not find the drive we had saved.
1094 //Select "None" then.
1095 comboBoxOpticalDrives.SelectedIndex = 0;
1100 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1101 checkBoxInverseColors.Checked = cds.InverseColors;
1102 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1103 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1104 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1105 labelMinFontSize.Enabled = cds.ScaleToFit;
1106 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1107 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1108 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1109 timer.Interval = cds.TimerInterval;
1110 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1111 textBoxScrollLoopSeparator.Text = cds.Separator;
1113 SetupPixelDelegates();
1115 if (iDisplay.IsOpen())
1117 //We have a display connection
1118 //Reflect that in our UI
1120 tableLayoutPanel.Enabled = true;
1121 panelDisplay.Enabled = true;
1123 //Only setup brightness if display is open
1124 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1125 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1126 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1128 //Brightness out of range, this can occur when using auto-detect
1129 //Use max brightness instead
1130 trackBarBrightness.Value = iDisplay.MaxBrightness();
1131 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1135 trackBarBrightness.Value = cds.Brightness;
1136 iDisplay.SetBrightness(cds.Brightness);
1139 //Try compute the steps to something that makes sense
1140 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1141 trackBarBrightness.SmallChange = 1;
1144 buttonFill.Enabled = true;
1145 buttonClear.Enabled = true;
1146 buttonOpen.Enabled = false;
1147 buttonClose.Enabled = true;
1148 trackBarBrightness.Enabled = true;
1149 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1150 //+ " - " + iDisplay.SerialNumber();
1152 if (iDisplay.SupportPowerOnOff())
1154 buttonPowerOn.Enabled = true;
1155 buttonPowerOff.Enabled = true;
1159 buttonPowerOn.Enabled = false;
1160 buttonPowerOff.Enabled = false;
1163 if (iDisplay.SupportClock())
1165 buttonShowClock.Enabled = true;
1166 buttonHideClock.Enabled = true;
1170 buttonShowClock.Enabled = false;
1171 buttonHideClock.Enabled = false;
1175 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1176 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1178 if (cds.ShowVolumeLabel)
1180 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1184 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1189 //Display is connection not available
1190 //Reflect that in our UI
1191 checkBoxShowVolumeLabel.Enabled = false;
1192 tableLayoutPanel.Enabled = false;
1193 panelDisplay.Enabled = false;
1194 buttonFill.Enabled = false;
1195 buttonClear.Enabled = false;
1196 buttonOpen.Enabled = true;
1197 buttonClose.Enabled = false;
1198 trackBarBrightness.Enabled = false;
1199 buttonPowerOn.Enabled = false;
1200 buttonPowerOff.Enabled = false;
1201 buttonShowClock.Enabled = false;
1202 buttonHideClock.Enabled = false;
1203 toolStripStatusLabelConnect.Text = "Disconnected";
1204 toolStripStatusLabelPower.Text = "N/A";
1213 /// <param name="sender"></param>
1214 /// <param name="e"></param>
1215 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1217 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1218 Properties.Settings.Default.Save();
1222 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1224 //Save our show borders setting
1225 tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1226 cds.ShowBorders = checkBoxShowBorders.Checked;
1227 Properties.Settings.Default.Save();
1231 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1233 //Save our connect on startup setting
1234 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1235 Properties.Settings.Default.Save();
1238 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1240 //Save our "Minimize to tray" setting
1241 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1242 Properties.Settings.Default.Save();
1246 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1248 //Save our "Start minimized" setting
1249 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1250 Properties.Settings.Default.Save();
1253 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1255 iStartupManager.Startup = checkBoxAutoStart.Checked;
1259 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1261 //Save our reverse screen setting
1262 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1263 Properties.Settings.Default.Save();
1264 SetupPixelDelegates();
1267 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1269 //Save our inverse colors setting
1270 cds.InverseColors = checkBoxInverseColors.Checked;
1271 Properties.Settings.Default.Save();
1272 SetupPixelDelegates();
1275 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1277 //Save our scale to fit setting
1278 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1279 Properties.Settings.Default.Save();
1281 labelMinFontSize.Enabled = cds.ScaleToFit;
1282 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1285 private void MainForm_Resize(object sender, EventArgs e)
1287 if (WindowState == FormWindowState.Minimized)
1289 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1290 // That's apparently not needed on Windows 10 but we better leave it in place.
1291 iCreateBitmap = true;
1295 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1298 iNetworkManager.Dispose();
1299 CloseDisplayConnection();
1301 e.Cancel = iClosing;
1304 public void StartServer()
1306 iServiceHost = new ServiceHost
1309 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1312 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1313 iServiceHost.Open();
1316 public void StopServer()
1318 if (iClients.Count > 0 && !iClosing)
1322 BroadcastCloseEvent();
1326 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1328 iClosing = false; //We make sure we force close if asked twice
1333 //We removed that as it often lags for some reason
1334 //iServiceHost.Close();
1338 public void BroadcastCloseEvent()
1340 Trace.TraceInformation("BroadcastCloseEvent - start");
1342 var inactiveClients = new List<string>();
1343 foreach (var client in iClients)
1345 //if (client.Key != eventData.ClientName)
1349 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1350 client.Value.Callback.OnCloseOrder(/*eventData*/);
1352 catch (Exception ex)
1354 inactiveClients.Add(client.Key);
1359 if (inactiveClients.Count > 0)
1361 foreach (var client in inactiveClients)
1363 iClients.Remove(client);
1364 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
1368 if (iClients.Count==0)
1375 /// Just remove all our fields.
1377 private void ClearLayout()
1379 tableLayoutPanel.Controls.Clear();
1380 tableLayoutPanel.RowStyles.Clear();
1381 tableLayoutPanel.ColumnStyles.Clear();
1382 iCurrentClientData = null;
1386 /// Just launch a demo client.
1388 private void StartNewClient(string aTopText = "", string aBottomText = "")
1390 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1391 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1392 clientThread.Start(myParams);
1396 private void buttonStartClient_Click(object sender, EventArgs e)
1401 private void buttonSuspend_Click(object sender, EventArgs e)
1403 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1404 timer.Enabled = !timer.Enabled;
1407 buttonSuspend.Text = "Run";
1411 buttonSuspend.Text = "Pause";
1415 private void buttonCloseClients_Click(object sender, EventArgs e)
1417 BroadcastCloseEvent();
1420 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1422 //Root node must have at least one child
1423 if (e.Node.Nodes.Count == 0)
1428 //If the selected node is the root node of a client then switch to it
1429 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1430 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1432 //We have a valid session just switch to that client
1433 SetCurrentClient(sessionId,true);
1442 /// <param name="aSessionId"></param>
1443 /// <param name="aCallback"></param>
1444 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1446 if (this.InvokeRequired)
1448 //Not in the proper thread, invoke ourselves
1449 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1450 this.Invoke(d, new object[] { aSessionId, aCallback });
1454 //We are in the proper thread
1455 //Add this session to our collection of clients
1456 ClientData newClient = new ClientData(aSessionId, aCallback);
1457 Program.iMainForm.iClients.Add(aSessionId, newClient);
1458 //Add this session to our UI
1459 UpdateClientTreeViewNode(newClient);
1466 /// <param name="aSessionId"></param>
1467 public void RemoveClientThreadSafe(string aSessionId)
1469 if (this.InvokeRequired)
1471 //Not in the proper thread, invoke ourselves
1472 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1473 this.Invoke(d, new object[] { aSessionId });
1477 //We are in the proper thread
1478 //Remove this session from both client collection and UI tree view
1479 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1481 Program.iMainForm.iClients.Remove(aSessionId);
1482 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
1485 if (iClients.Count == 0)
1487 //Clear our screen when last client disconnects
1492 //We were closing our form
1493 //All clients are now closed
1494 //Just resume our close operation
1505 /// <param name="aSessionId"></param>
1506 /// <param name="aLayout"></param>
1507 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1509 if (this.InvokeRequired)
1511 //Not in the proper thread, invoke ourselves
1512 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1513 this.Invoke(d, new object[] { aSessionId, aLayout });
1517 ClientData client = iClients[aSessionId];
1520 //Don't change a thing if the layout is the same
1521 if (!client.Layout.IsSameAs(aLayout))
1523 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1524 //Set our client layout then
1525 client.Layout = aLayout;
1526 //Layout has changed clear our fields then
1527 client.Fields.Clear();
1529 UpdateClientTreeViewNode(client);
1533 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1542 /// <param name="aSessionId"></param>
1543 /// <param name="aField"></param>
1544 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1546 if (this.InvokeRequired)
1548 //Not in the proper thread, invoke ourselves
1549 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1550 this.Invoke(d, new object[] { aSessionId, aField });
1554 //We are in the proper thread
1555 //Call the non-thread-safe variant
1556 SetClientField(aSessionId, aField);
1563 /// <param name="aSessionId"></param>
1564 /// <param name="aField"></param>
1565 private void SetClientField(string aSessionId, DataField aField)
1567 //TODO: should check if the field actually changed?
1569 ClientData client = iClients[aSessionId];
1570 bool layoutChanged = false;
1571 bool contentChanged = true;
1573 //Make sure all our fields are in place
1574 while (client.Fields.Count < (aField.Index + 1))
1576 //Add a data field with proper index
1577 client.Fields.Add(new DataField(client.Fields.Count));
1578 layoutChanged = true;
1581 //Now that we know our fields are in place just update that one
1582 client.Fields[aField.Index] = aField;
1584 if (client.Fields[aField.Index].IsSameLayout(aField))
1586 //If we are updating a field in our current client we need to update it in our panel
1587 if (aSessionId == iCurrentClientSessionId)
1589 if (aField.IsText && aField.Index < tableLayoutPanel.Controls.Count && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
1591 //Text field control already in place, just change the text
1592 MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
1593 contentChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
1594 label.Text = aField.Text;
1595 label.TextAlign = aField.Alignment;
1597 else if (aField.IsBitmap && aField.Index < tableLayoutPanel.Controls.Count && tableLayoutPanel.Controls[aField.Index] is PictureBox)
1599 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1600 //Bitmap field control already in place just change the bitmap
1601 PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
1602 pictureBox.Image = aField.Bitmap;
1606 layoutChanged = true;
1612 layoutChanged = true;
1615 //If either content or layout changed we need to update our tree view to reflect the changes
1616 if (contentChanged || layoutChanged)
1618 UpdateClientTreeViewNode(client);
1622 Debug.Print("Layout changed");
1623 //Our layout has changed, if we are already the current client we need to update our panel
1624 if (aSessionId == iCurrentClientSessionId)
1626 //Apply layout and set data fields.
1627 UpdateTableLayoutPanel(iCurrentClientData);
1632 Debug.Print("Layout has not changed.");
1637 Debug.Print("WARNING: content and layout have not changed!");
1640 //When a client field is set we try switching to this client to present the new information to our user
1641 SetCurrentClient(aSessionId);
1647 /// <param name="aTexts"></param>
1648 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1650 if (this.InvokeRequired)
1652 //Not in the proper thread, invoke ourselves
1653 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1654 this.Invoke(d, new object[] { aSessionId, aFields });
1658 //Put each our text fields in a label control
1659 foreach (DataField field in aFields)
1661 SetClientField(aSessionId, field);
1669 /// <param name="aSessionId"></param>
1670 /// <param name="aName"></param>
1671 public void SetClientNameThreadSafe(string aSessionId, string aName)
1673 if (this.InvokeRequired)
1675 //Not in the proper thread, invoke ourselves
1676 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1677 this.Invoke(d, new object[] { aSessionId, aName });
1681 //We are in the proper thread
1683 ClientData client = iClients[aSessionId];
1687 client.Name = aName;
1688 //Update our tree-view
1689 UpdateClientTreeViewNode(client);
1697 /// <param name="aClient"></param>
1698 private void UpdateClientTreeViewNode(ClientData aClient)
1700 Debug.Print("UpdateClientTreeViewNode");
1702 if (aClient == null)
1707 TreeNode node = null;
1708 //Check that our client node already exists
1709 //Get our client root node using its key which is our session ID
1710 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1711 if (nodes.Count()>0)
1713 //We already have a node for that client
1715 //Clear children as we are going to recreate them below
1720 //Client node does not exists create a new one
1721 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1722 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1728 if (aClient.Name != "")
1730 //We have a name, us it as text for our root node
1731 node.Text = aClient.Name;
1732 //Add a child with SessionId
1733 node.Nodes.Add(new TreeNode(aClient.SessionId));
1737 //No name, use session ID instead
1738 node.Text = aClient.SessionId;
1741 if (aClient.Fields.Count > 0)
1743 //Create root node for our texts
1744 TreeNode textsRoot = new TreeNode("Fields");
1745 node.Nodes.Add(textsRoot);
1746 //For each text add a new entry
1747 foreach (DataField field in aClient.Fields)
1749 if (!field.IsBitmap)
1751 DataField textField = (DataField)field;
1752 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1756 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1766 /// Update our table layout row styles to make sure each rows have similar height
1768 private void UpdateTableLayoutRowStyles()
1770 foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
1772 rowStyle.SizeType = SizeType.Percent;
1773 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
1778 /// Update our display table layout.
1780 /// <param name="aLayout"></param>
1781 private void UpdateTableLayoutPanel(ClientData aClient)
1783 Debug.Print("UpdateClientTreeViewNode");
1785 if (aClient == null)
1792 TableLayout layout = aClient.Layout;
1795 //First clean our current panel
1796 tableLayoutPanel.Controls.Clear();
1797 tableLayoutPanel.RowStyles.Clear();
1798 tableLayoutPanel.ColumnStyles.Clear();
1799 tableLayoutPanel.RowCount = 0;
1800 tableLayoutPanel.ColumnCount = 0;
1802 while (tableLayoutPanel.RowCount < layout.Rows.Count)
1804 tableLayoutPanel.RowCount++;
1807 while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
1809 tableLayoutPanel.ColumnCount++;
1812 for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
1814 //Create our column styles
1815 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1817 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
1821 //Create our row styles
1822 this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1825 //Check if we already have a control
1826 Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
1827 if (existingControl!=null)
1829 //We already have a control in that cell as a results of row/col spanning
1830 //Move on to next cell then
1836 //Check if a client field already exists for that cell
1837 if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
1839 //No client field specified, create a text field by default
1840 aClient.Fields.Add(new DataField(aClient.Fields.Count));
1843 //Create a control corresponding to the field specified for that cell
1844 DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
1845 Control control = CreateControlForDataField(field);
1847 //Add newly created control to our table layout at the specified row and column
1848 tableLayoutPanel.Controls.Add(control, i, j);
1849 //Make sure we specify row and column span for that new control
1850 tableLayoutPanel.SetRowSpan(control,field.RowSpan);
1851 tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
1856 while (aClient.Fields.Count > fieldCount)
1858 //We have too much fields for this layout
1859 //Just discard them until we get there
1860 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
1867 /// Check our type of data field and create corresponding control
1869 /// <param name="aField"></param>
1870 private Control CreateControlForDataField(DataField aField)
1872 Control control=null;
1873 if (!aField.IsBitmap)
1875 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1876 label.AutoEllipsis = true;
1877 label.AutoSize = true;
1878 label.BackColor = System.Drawing.Color.Transparent;
1879 label.Dock = System.Windows.Forms.DockStyle.Fill;
1880 label.Location = new System.Drawing.Point(1, 1);
1881 label.Margin = new System.Windows.Forms.Padding(0);
1882 label.Name = "marqueeLabel" + aField.Index;
1883 label.OwnTimer = false;
1884 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
1885 label.Separator = cds.Separator;
1886 label.MinFontSize = cds.MinFontSize;
1887 label.ScaleToFit = cds.ScaleToFit;
1888 //control.Size = new System.Drawing.Size(254, 30);
1889 //control.TabIndex = 2;
1890 label.Font = cds.Font;
1892 label.TextAlign = aField.Alignment;
1893 label.UseCompatibleTextRendering = true;
1894 label.Text = aField.Text;
1900 //Create picture box
1901 PictureBox picture = new PictureBox();
1902 picture.AutoSize = true;
1903 picture.BackColor = System.Drawing.Color.Transparent;
1904 picture.Dock = System.Windows.Forms.DockStyle.Fill;
1905 picture.Location = new System.Drawing.Point(1, 1);
1906 picture.Margin = new System.Windows.Forms.Padding(0);
1907 picture.Name = "pictureBox" + aField;
1909 picture.Image = aField.Bitmap;
1918 /// Called when the user selected a new display type.
1920 /// <param name="sender"></param>
1921 /// <param name="e"></param>
1922 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
1924 //Store the selected display type in our settings
1925 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
1926 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
1927 Properties.Settings.Default.Save();
1929 //Try re-opening the display connection if we were already connected.
1930 //Otherwise just update our status to reflect display type change.
1931 if (iDisplay.IsOpen())
1933 OpenDisplayConnection();
1941 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
1943 if (maskedTextBoxTimerInterval.Text != "")
1945 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
1949 timer.Interval = interval;
1950 cds.TimerInterval = timer.Interval;
1951 Properties.Settings.Default.Save();
1956 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
1958 if (maskedTextBoxMinFontSize.Text != "")
1960 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
1962 if (minFontSize > 0)
1964 cds.MinFontSize = minFontSize;
1965 Properties.Settings.Default.Save();
1966 //We need to recreate our layout for that change to take effect
1967 UpdateTableLayoutPanel(iCurrentClientData);
1973 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
1975 if (maskedTextBoxScrollingSpeed.Text != "")
1977 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
1979 if (scrollingSpeed > 0)
1981 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
1982 Properties.Settings.Default.Save();
1983 //We need to recreate our layout for that change to take effect
1984 UpdateTableLayoutPanel(iCurrentClientData);
1989 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
1991 cds.Separator = textBoxScrollLoopSeparator.Text;
1992 Properties.Settings.Default.Save();
1994 //Update our text fields
1995 foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
1997 ctrl.Separator = cds.Separator;
2002 private void buttonPowerOn_Click(object sender, EventArgs e)
2007 private void buttonPowerOff_Click(object sender, EventArgs e)
2009 iDisplay.PowerOff();
2012 private void buttonShowClock_Click(object sender, EventArgs e)
2017 private void buttonHideClock_Click(object sender, EventArgs e)
2022 private void buttonUpdate_Click(object sender, EventArgs e)
2024 InstallUpdateSyncWithInfo();
2032 if (!iDisplay.IsOpen())
2037 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2038 iSkipFrameRendering = true;
2041 iDisplay.SwapBuffers();
2042 //Then show our clock
2043 iDisplay.ShowClock();
2051 if (!iDisplay.IsOpen())
2056 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2057 iSkipFrameRendering = false;
2058 iDisplay.HideClock();
2061 private void InstallUpdateSyncWithInfo()
2063 UpdateCheckInfo info = null;
2065 if (ApplicationDeployment.IsNetworkDeployed)
2067 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2071 info = ad.CheckForDetailedUpdate();
2074 catch (DeploymentDownloadException dde)
2076 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);
2079 catch (InvalidDeploymentException ide)
2081 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);
2084 catch (InvalidOperationException ioe)
2086 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2090 if (info.UpdateAvailable)
2092 Boolean doUpdate = true;
2094 if (!info.IsUpdateRequired)
2096 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2097 if (!(DialogResult.OK == dr))
2104 // Display a message that the app MUST reboot. Display the minimum required version.
2105 MessageBox.Show("This application has detected a mandatory update from your current " +
2106 "version to version " + info.MinimumRequiredVersion.ToString() +
2107 ". The application will now install the update and restart.",
2108 "Update Available", MessageBoxButtons.OK,
2109 MessageBoxIcon.Information);
2117 MessageBox.Show("The application has been upgraded, and will now restart.");
2118 Application.Restart();
2120 catch (DeploymentDownloadException dde)
2122 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2129 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2138 private void SysTrayHideShow()
2144 WindowState = FormWindowState.Normal;
2149 /// Use to handle minimize events.
2151 /// <param name="sender"></param>
2152 /// <param name="e"></param>
2153 private void MainForm_SizeChanged(object sender, EventArgs e)
2155 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2167 /// <param name="sender"></param>
2168 /// <param name="e"></param>
2169 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2171 //Our table layout size has changed which means our display size has changed.
2172 //We need to re-create our bitmap.
2173 iCreateBitmap = true;
2179 /// <param name="sender"></param>
2180 /// <param name="e"></param>
2181 private void buttonSelectFile_Click(object sender, EventArgs e)
2183 //openFileDialog1.InitialDirectory = "c:\\";
2184 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2185 //openFileDialog.FilterIndex = 1;
2186 openFileDialog.RestoreDirectory = true;
2188 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2190 labelStartFileName.Text = openFileDialog.FileName;
2191 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2192 Properties.Settings.Default.Save();
2199 /// <param name="sender"></param>
2200 /// <param name="e"></param>
2201 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2203 //Save the optical drive the user selected for ejection
2204 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2205 Properties.Settings.Default.Save();
2209 /// Broadcast messages to subscribers.
2211 /// <param name="message"></param>
2212 protected override void WndProc(ref Message aMessage)
2214 if (OnWndProc!=null)
2216 OnWndProc(ref aMessage);
2219 base.WndProc(ref aMessage);