Published v0.8.0.0.
2 // Copyright (C) 2014-2015 Stéphane Lenclud.
4 // This file is part of SharpDisplayManager.
6 // SharpDisplayManager is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
11 // SharpDisplayManager is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with SharpDisplayManager. If not, see <http://www.gnu.org/licenses/>.
21 using System.Collections.Generic;
22 using System.ComponentModel;
27 using System.Threading.Tasks;
28 using System.Windows.Forms;
30 using CodeProject.Dialog;
31 using System.Drawing.Imaging;
32 using System.ServiceModel;
33 using System.Threading;
34 using System.Diagnostics;
35 using System.Deployment.Application;
36 using System.Reflection;
38 using NAudio.CoreAudioApi;
39 using NAudio.CoreAudioApi.Interfaces;
40 using System.Runtime.InteropServices;
44 using SharpDisplayClient;
46 using MiniDisplayInterop;
47 using SharpLib.Display;
49 namespace SharpDisplayManager
52 public delegate uint ColorProcessingDelegate(int aX, int aY, uint aPixel);
53 public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
54 //Delegates are used for our thread safe method
55 public delegate void AddClientDelegate(string aSessionId, ICallback aCallback);
56 public delegate void RemoveClientDelegate(string aSessionId);
57 public delegate void SetFieldDelegate(string SessionId, DataField aField);
58 public delegate void SetFieldsDelegate(string SessionId, System.Collections.Generic.IList<DataField> aFields);
59 public delegate void SetLayoutDelegate(string SessionId, TableLayout aLayout);
60 public delegate void SetClientNameDelegate(string aSessionId, string aName);
61 public delegate void PlainUpdateDelegate();
62 public delegate void WndProcDelegate(ref Message aMessage);
65 /// Our Display manager main form
67 [System.ComponentModel.DesignerCategory("Form")]
68 public partial class MainForm : MainFormHid, IMMNotificationClient
70 DateTime LastTickTime;
72 System.Drawing.Bitmap iBmp;
73 bool iCreateBitmap; //Workaround render to bitmap issues when minimized
74 ServiceHost iServiceHost;
75 // Our collection of clients sorted by session id.
76 public Dictionary<string, ClientData> iClients;
77 // The name of the client which informations are currently displayed.
78 public string iCurrentClientSessionId;
79 ClientData iCurrentClientData;
83 public bool iSkipFrameRendering;
84 //Function pointer for pixel color filtering
85 ColorProcessingDelegate iColorFx;
86 //Function pointer for pixel X coordinate intercept
87 CoordinateTranslationDelegate iScreenX;
88 //Function pointer for pixel Y coordinate intercept
89 CoordinateTranslationDelegate iScreenY;
91 private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
92 private MMDevice iMultiMediaDevice;
94 private NetworkManager iNetworkManager;
97 /// CEC - Consumer Electronic Control.
98 /// Notably used to turn TV on and off as Windows broadcast monitor on and off notifications.
100 private ConsumerElectronicControl iCecManager;
103 /// Manage run when Windows startup option
105 private StartupManager iStartupManager;
108 /// System notification icon used to hide our application from the task bar.
110 private SharpLib.Notification.Control iNotifyIcon;
113 /// System recording notifcation icon.
115 private SharpLib.Notification.Control iRecordingNotification;
119 /// Allow user to receive window messages;
121 public event WndProcDelegate OnWndProc;
125 iSkipFrameRendering = false;
127 iCurrentClientSessionId = "";
128 iCurrentClientData = null;
129 LastTickTime = DateTime.Now;
130 //Instantiate our display and register for events notifications
131 iDisplay = new Display();
132 iDisplay.OnOpened += OnDisplayOpened;
133 iDisplay.OnClosed += OnDisplayClosed;
135 iClients = new Dictionary<string, ClientData>();
136 iStartupManager = new StartupManager();
137 iNotifyIcon = new SharpLib.Notification.Control();
138 iRecordingNotification = new SharpLib.Notification.Control();
140 //Have our designer initialize its controls
141 InitializeComponent();
143 //Populate device types
144 PopulateDeviceTypes();
146 //Populate optical drives
147 PopulateOpticalDrives();
149 //Initial status update
152 //We have a bug when drawing minimized and reusing our bitmap
153 //Though I could not reproduce it on Windows 10
154 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
155 iCreateBitmap = false;
157 //Minimize our window if desired
158 if (Properties.Settings.Default.StartMinimized)
160 WindowState = FormWindowState.Minimized;
168 /// <param name="sender"></param>
169 /// <param name="e"></param>
170 private void MainForm_Load(object sender, EventArgs e)
172 //Check if we are running a Click Once deployed application
173 if (ApplicationDeployment.IsNetworkDeployed)
175 //This is a proper Click Once installation, fetch and show our version number
176 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
180 //Not a proper Click Once installation, assuming development build then
181 this.Text += " - development";
185 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
186 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
187 UpdateAudioDeviceAndMasterVolumeThreadSafe();
190 iNetworkManager = new NetworkManager();
191 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
192 UpdateNetworkStatus();
195 iCecManager = new ConsumerElectronicControl();
196 OnWndProc += iCecManager.OnWndProc;
200 //Setup notification icon
203 //Setup recording notification
204 SetupRecordingNotification();
206 // To make sure start up with minimize to tray works
207 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
213 //When not debugging we want the screen to be empty until a client takes over
216 //When developing we want at least one client for testing
217 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
220 //Open display connection on start-up if needed
221 if (Properties.Settings.Default.DisplayConnectOnStartup)
223 OpenDisplayConnection();
226 //Start our server so that we can get client requests
229 //Register for HID events
230 RegisterHidDevices();
234 /// Called when our display is opened.
236 /// <param name="aDisplay"></param>
237 private void OnDisplayOpened(Display aDisplay)
239 //Make sure we resume frame rendering
240 iSkipFrameRendering = false;
242 //Set our screen size now that our display is connected
243 //Our panelDisplay is the container of our tableLayoutPanel
244 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
245 //panelDisplay needs an extra 2 pixels for borders on each sides
246 //tableLayoutPanel will eventually be the exact size of our display
247 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
248 panelDisplay.Size = size;
250 //Our display was just opened, update our UI
252 //Initiate asynchronous request
253 iDisplay.RequestFirmwareRevision();
256 UpdateMasterVolumeThreadSafe();
258 UpdateNetworkStatus();
261 //Testing icon in debug, no arm done if icon not supported
262 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
263 //iDisplay.SetAllIconsStatus(2);
269 /// Called when our display is closed.
271 /// <param name="aDisplay"></param>
272 private void OnDisplayClosed(Display aDisplay)
274 //Our display was just closed, update our UI consequently
278 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
280 //Update network status
281 UpdateNetworkStatus();
285 /// Update our Network Status
287 private void UpdateNetworkStatus()
289 if (iDisplay.IsOpen())
291 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
292 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
297 int iLastNetworkIconIndex = 0;
298 int iUpdateCountSinceLastNetworkAnimation = 0;
303 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
305 iUpdateCountSinceLastNetworkAnimation++;
306 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
308 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
310 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
313 //Prevents div by zero and other undefined behavior
316 iLastNetworkIconIndex++;
317 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
318 for (int i=0;i<iconCount;i++)
320 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
322 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
326 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
335 /// Receive volume change notification and reflect changes on our slider.
337 /// <param name="data"></param>
338 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
340 UpdateMasterVolumeThreadSafe();
344 /// Update master volume when user moves our slider.
346 /// <param name="sender"></param>
347 /// <param name="e"></param>
348 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
350 //Just like Windows Volume Mixer we unmute if the volume is adjusted
351 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
352 //Set volume level according to our volume slider new position
353 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
358 /// Mute check box changed.
360 /// <param name="sender"></param>
361 /// <param name="e"></param>
362 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
364 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
368 /// Device State Changed
370 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
375 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
380 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
383 /// Default Device Changed
385 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
387 if (role == Role.Multimedia && flow == DataFlow.Render)
389 UpdateAudioDeviceAndMasterVolumeThreadSafe();
394 /// Property Value Changed
396 /// <param name="pwstrDeviceId"></param>
397 /// <param name="key"></param>
398 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
404 /// Update master volume indicators based our current system states.
405 /// This typically includes volume levels and mute status.
407 private void UpdateMasterVolumeThreadSafe()
409 if (this.InvokeRequired)
411 //Not in the proper thread, invoke ourselves
412 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
413 this.Invoke(d, new object[] { });
417 //Update volume slider
418 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
419 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
420 //Update mute checkbox
421 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
423 //If our display connection is open we need to update its icons
424 if (iDisplay.IsOpen())
426 //First take care our our volume level icons
427 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
428 if (volumeIconCount > 0)
430 //Compute current volume level from system level and the number of segments in our display volume bar.
431 //That tells us how many segments in our volume bar needs to be turned on.
432 float currentVolume = volumeLevelScalar * volumeIconCount;
433 int segmentOnCount = Convert.ToInt32(currentVolume);
434 //Check if our segment count was rounded up, this will later be used for half brightness segment
435 bool roundedUp = segmentOnCount > currentVolume;
437 for (int i = 0; i < volumeIconCount; i++)
439 if (i < segmentOnCount)
441 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
442 if (i == segmentOnCount - 1 && roundedUp)
445 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
450 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
455 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
460 //Take care our our mute icon
461 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
469 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
471 if (this.InvokeRequired)
473 //Not in the proper thread, invoke ourselves
474 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
475 this.Invoke(d, new object[] { });
479 //We are in the correct thread just go ahead.
482 //Get our master volume
483 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
485 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
487 //Show our volume in our track bar
488 UpdateMasterVolumeThreadSafe();
490 //Register to get volume modifications
491 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
493 trackBarMasterVolume.Enabled = true;
497 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
498 Debug.WriteLine(ex.ToString());
499 //Something went wrong S/PDIF device ca throw exception I guess
500 trackBarMasterVolume.Enabled = false;
507 private void PopulateDeviceTypes()
509 int count = Display.TypeCount();
511 for (int i = 0; i < count; i++)
513 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
520 private void PopulateOpticalDrives()
522 //Reset our list of drives
523 comboBoxOpticalDrives.Items.Clear();
524 comboBoxOpticalDrives.Items.Add("None");
526 //Go through each drives on our system and collected the optical ones in our list
527 DriveInfo[] allDrives = DriveInfo.GetDrives();
528 foreach (DriveInfo d in allDrives)
530 Debug.WriteLine("Drive " + d.Name);
531 Debug.WriteLine(" Drive type: {0}", d.DriveType);
533 if (d.DriveType==DriveType.CDRom)
535 //This is an optical drive, add it now
536 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
544 /// <returns></returns>
545 public string OpticalDriveToEject()
547 return comboBoxOpticalDrives.SelectedItem.ToString();
555 private void SetupTrayIcon()
557 iNotifyIcon.Icon = GetIcon("vfd.ico");
558 iNotifyIcon.Text = "Sharp Display Manager";
559 iNotifyIcon.Visible = true;
561 //Double click toggles visibility - typically brings up the application
562 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
567 //Adding a context menu, useful to be able to exit the application
568 ContextMenu contextMenu = new ContextMenu();
569 //Context menu item to toggle visibility
570 MenuItem hideShowItem = new MenuItem("Hide/Show");
571 hideShowItem.Click += delegate(object obj, EventArgs args)
575 contextMenu.MenuItems.Add(hideShowItem);
577 //Context menu item separator
578 contextMenu.MenuItems.Add(new MenuItem("-"));
580 //Context menu exit item
581 MenuItem exitItem = new MenuItem("Exit");
582 exitItem.Click += delegate(object obj, EventArgs args)
586 contextMenu.MenuItems.Add(exitItem);
588 iNotifyIcon.ContextMenu = contextMenu;
594 private void SetupRecordingNotification()
596 iRecordingNotification.Icon = GetIcon("record.ico");
597 iRecordingNotification.Text = "No recording";
598 iRecordingNotification.Visible = false;
602 /// Access icons from embedded resources.
604 /// <param name="aName"></param>
605 /// <returns></returns>
606 public static Icon GetIcon(string aName)
608 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
609 foreach (string name in names)
611 //Find a resource name that ends with the given name
612 if (name.EndsWith(aName))
614 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
616 return new Icon(stream);
626 /// Set our current client.
627 /// This will take care of applying our client layout and set data fields.
629 /// <param name="aSessionId"></param>
630 void SetCurrentClient(string aSessionId, bool aForce=false)
632 if (aSessionId == iCurrentClientSessionId)
634 //Given client is already the current one.
635 //Don't bother changing anything then.
640 //Check when was the last time we switched to that client
641 if (iCurrentClientData != null)
643 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
644 //TODO: put that hard coded value as a client property
645 //Clients should be able to define how often they can be interrupted
646 //Thus a background client can set this to zero allowing any other client to interrupt at any time
647 //We could also compute this delay by looking at the requests frequencies?
648 if (!aForce && (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
650 //Don't switch clients too often
655 //Set current client ID.
656 iCurrentClientSessionId = aSessionId;
657 //Set the time we last switched to that client
658 iClients[aSessionId].LastSwitchTime = DateTime.Now;
659 //Fetch and set current client data.
660 iCurrentClientData = iClients[aSessionId];
661 //Apply layout and set data fields.
662 UpdateTableLayoutPanel(iCurrentClientData);
665 private void buttonFont_Click(object sender, EventArgs e)
667 //fontDialog.ShowColor = true;
668 //fontDialog.ShowApply = true;
669 fontDialog.ShowEffects = true;
670 fontDialog.Font = cds.Font;
672 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
674 //fontDialog.ShowHelp = true;
676 //fontDlg.MaxSize = 40;
677 //fontDlg.MinSize = 22;
679 //fontDialog.Parent = this;
680 //fontDialog.StartPosition = FormStartPosition.CenterParent;
682 //DlgBox.ShowDialog(fontDialog);
684 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
685 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
687 //Set the fonts to all our labels in our layout
688 foreach (Control ctrl in iTableLayoutPanel.Controls)
690 if (ctrl is MarqueeLabel)
692 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
697 cds.Font = fontDialog.Font;
698 Properties.Settings.Default.Save();
707 void CheckFontHeight()
709 //Show font height and width
710 labelFontHeight.Text = "Font height: " + cds.Font.Height;
711 float charWidth = IsFixedWidth(cds.Font);
712 if (charWidth == 0.0f)
714 labelFontWidth.Visible = false;
718 labelFontWidth.Visible = true;
719 labelFontWidth.Text = "Font width: " + charWidth;
722 MarqueeLabel label = null;
723 //Get the first label control we can find
724 foreach (Control ctrl in iTableLayoutPanel.Controls)
726 if (ctrl is MarqueeLabel)
728 label = (MarqueeLabel)ctrl;
733 //Now check font height and show a warning if needed.
734 if (label != null && label.Font.Height > label.Height)
736 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
737 labelWarning.Visible = true;
741 labelWarning.Visible = false;
746 private void buttonCapture_Click(object sender, EventArgs e)
748 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
749 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
750 //Bitmap bmpToSave = new Bitmap(bmp);
751 bmp.Save("D:\\capture.png");
753 ((MarqueeLabel)iTableLayoutPanel.Controls[0]).Text = "Captured";
756 string outputFileName = "d:\\capture.png";
757 using (MemoryStream memory = new MemoryStream())
759 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
761 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
762 byte[] bytes = memory.ToArray();
763 fs.Write(bytes, 0, bytes.Length);
770 private void CheckForRequestResults()
772 if (iDisplay.IsRequestPending())
774 switch (iDisplay.AttemptRequestCompletion())
776 case MiniDisplay.Request.FirmwareRevision:
777 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
778 //Issue next request then
779 iDisplay.RequestPowerSupplyStatus();
782 case MiniDisplay.Request.PowerSupplyStatus:
783 if (iDisplay.PowerSupplyStatus())
785 toolStripStatusLabelPower.Text = "ON";
789 toolStripStatusLabelPower.Text = "OFF";
791 //Issue next request then
792 iDisplay.RequestDeviceId();
795 case MiniDisplay.Request.DeviceId:
796 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
797 //No more request to issue
803 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
805 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
812 public static uint ColorUntouched(int aX, int aY, uint aPixel)
817 public static uint ColorInversed(int aX, int aY, uint aPixel)
822 public static uint ColorChessboard(int aX, int aY, uint aPixel)
824 if ((aX % 2 == 0) && (aY % 2 == 0))
828 else if ((aX % 2 != 0) && (aY % 2 != 0))
836 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
838 return aBmp.Width - aX - 1;
841 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
843 return iBmp.Height - aY - 1;
846 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
851 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
857 /// Select proper pixel delegates according to our current settings.
859 private void SetupPixelDelegates()
861 //Select our pixel processing routine
862 if (cds.InverseColors)
864 //iColorFx = ColorChessboard;
865 iColorFx = ColorInversed;
869 iColorFx = ColorWhiteIsOn;
872 //Select proper coordinate translation functions
873 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
874 if (cds.ReverseScreen)
876 iScreenX = ScreenReversedX;
877 iScreenY = ScreenReversedY;
887 //This is our timer tick responsible to perform our render
888 private void timer_Tick(object sender, EventArgs e)
890 //Update our animations
891 DateTime NewTickTime = DateTime.Now;
893 UpdateNetworkSignal(LastTickTime, NewTickTime);
895 //Update animation for all our marquees
896 foreach (Control ctrl in iTableLayoutPanel.Controls)
898 if (ctrl is MarqueeLabel)
900 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
905 if (iDisplay.IsOpen())
907 CheckForRequestResults();
909 //Check if frame rendering is needed
910 //Typically used when showing clock
911 if (!iSkipFrameRendering)
916 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
917 iCreateBitmap = false;
919 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
920 //iBmp.Save("D:\\capture.png");
922 //Send it to our display
923 for (int i = 0; i < iBmp.Width; i++)
925 for (int j = 0; j < iBmp.Height; j++)
929 //Get our processed pixel coordinates
930 int x = iScreenX(iBmp, i);
931 int y = iScreenY(iBmp, j);
933 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
934 //Apply color effects
935 color = iColorFx(x, y, color);
937 iDisplay.SetPixel(x, y, color);
942 iDisplay.SwapBuffers();
946 //Compute instant FPS
947 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
949 LastTickTime = NewTickTime;
954 /// Attempt to establish connection with our display hardware.
956 private void OpenDisplayConnection()
958 CloseDisplayConnection();
960 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
963 toolStripStatusLabelConnect.Text = "Connection error";
967 private void CloseDisplayConnection()
969 //Status will be updated upon receiving the closed event
971 if (iDisplay == null || !iDisplay.IsOpen())
976 //Do not clear if we gave up on rendering already.
977 //This means we will keep on displaying clock on MDM166AA for instance.
978 if (!iSkipFrameRendering)
981 iDisplay.SwapBuffers();
984 iDisplay.SetAllIconsStatus(0); //Turn off all icons
988 private void buttonOpen_Click(object sender, EventArgs e)
990 OpenDisplayConnection();
993 private void buttonClose_Click(object sender, EventArgs e)
995 CloseDisplayConnection();
998 private void buttonClear_Click(object sender, EventArgs e)
1001 iDisplay.SwapBuffers();
1004 private void buttonFill_Click(object sender, EventArgs e)
1007 iDisplay.SwapBuffers();
1010 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1012 cds.Brightness = trackBarBrightness.Value;
1013 Properties.Settings.Default.Save();
1014 iDisplay.SetBrightness(trackBarBrightness.Value);
1020 /// CDS stands for Current Display Settings
1022 private DisplaySettings cds
1026 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1027 if (settings == null)
1029 settings = new DisplaysSettings();
1031 Properties.Settings.Default.DisplaysSettings = settings;
1034 //Make sure all our settings have been created
1035 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1037 settings.Displays.Add(new DisplaySettings());
1040 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1041 return displaySettings;
1046 /// Check if the given font has a fixed character pitch.
1048 /// <param name="ft"></param>
1049 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1050 public float IsFixedWidth(Font ft)
1052 Graphics g = CreateGraphics();
1053 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1054 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1056 bool fixedWidth = true;
1058 foreach (char c in charSizes)
1059 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1071 /// Synchronize UI with settings
1073 private void UpdateStatus()
1076 checkBoxShowBorders.Checked = cds.ShowBorders;
1077 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1079 //Set the proper font to each of our labels
1080 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1082 ctrl.Font = cds.Font;
1086 //Check if "run on Windows startup" is enabled
1087 checkBoxAutoStart.Checked = iStartupManager.Startup;
1089 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1090 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1091 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1092 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1094 //Try find our drive in our drive list
1095 int opticalDriveItemIndex=0;
1096 bool driveNotFound = true;
1097 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1098 foreach (object item in comboBoxOpticalDrives.Items)
1100 if (opticalDriveToEject == item.ToString())
1102 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1103 driveNotFound = false;
1106 opticalDriveItemIndex++;
1111 //We could not find the drive we had saved.
1112 //Select "None" then.
1113 comboBoxOpticalDrives.SelectedIndex = 0;
1117 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1118 checkBoxCecMonitorOn.Checked = Properties.Settings.Default.CecMonitorOn;
1119 checkBoxCecMonitorOff.Checked = Properties.Settings.Default.CecMonitorOff;
1120 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1122 //Mini Display settings
1123 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1124 checkBoxInverseColors.Checked = cds.InverseColors;
1125 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1126 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1127 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1128 labelMinFontSize.Enabled = cds.ScaleToFit;
1129 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1130 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1131 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1132 timer.Interval = cds.TimerInterval;
1133 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1134 textBoxScrollLoopSeparator.Text = cds.Separator;
1136 SetupPixelDelegates();
1138 if (iDisplay.IsOpen())
1140 //We have a display connection
1141 //Reflect that in our UI
1143 iTableLayoutPanel.Enabled = true;
1144 panelDisplay.Enabled = true;
1146 //Only setup brightness if display is open
1147 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1148 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1149 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1151 //Brightness out of range, this can occur when using auto-detect
1152 //Use max brightness instead
1153 trackBarBrightness.Value = iDisplay.MaxBrightness();
1154 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1158 trackBarBrightness.Value = cds.Brightness;
1159 iDisplay.SetBrightness(cds.Brightness);
1162 //Try compute the steps to something that makes sense
1163 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1164 trackBarBrightness.SmallChange = 1;
1167 buttonFill.Enabled = true;
1168 buttonClear.Enabled = true;
1169 buttonOpen.Enabled = false;
1170 buttonClose.Enabled = true;
1171 trackBarBrightness.Enabled = true;
1172 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1173 //+ " - " + iDisplay.SerialNumber();
1175 if (iDisplay.SupportPowerOnOff())
1177 buttonPowerOn.Enabled = true;
1178 buttonPowerOff.Enabled = true;
1182 buttonPowerOn.Enabled = false;
1183 buttonPowerOff.Enabled = false;
1186 if (iDisplay.SupportClock())
1188 buttonShowClock.Enabled = true;
1189 buttonHideClock.Enabled = true;
1193 buttonShowClock.Enabled = false;
1194 buttonHideClock.Enabled = false;
1198 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1199 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1201 if (cds.ShowVolumeLabel)
1203 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1207 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1212 //Display is connection not available
1213 //Reflect that in our UI
1214 checkBoxShowVolumeLabel.Enabled = false;
1215 iTableLayoutPanel.Enabled = false;
1216 panelDisplay.Enabled = false;
1217 buttonFill.Enabled = false;
1218 buttonClear.Enabled = false;
1219 buttonOpen.Enabled = true;
1220 buttonClose.Enabled = false;
1221 trackBarBrightness.Enabled = false;
1222 buttonPowerOn.Enabled = false;
1223 buttonPowerOff.Enabled = false;
1224 buttonShowClock.Enabled = false;
1225 buttonHideClock.Enabled = false;
1226 toolStripStatusLabelConnect.Text = "Disconnected";
1227 toolStripStatusLabelPower.Text = "N/A";
1236 /// <param name="sender"></param>
1237 /// <param name="e"></param>
1238 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1240 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1241 Properties.Settings.Default.Save();
1245 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1247 //Save our show borders setting
1248 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1249 cds.ShowBorders = checkBoxShowBorders.Checked;
1250 Properties.Settings.Default.Save();
1254 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1256 //Save our connect on startup setting
1257 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1258 Properties.Settings.Default.Save();
1261 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1263 //Save our "Minimize to tray" setting
1264 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1265 Properties.Settings.Default.Save();
1269 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1271 //Save our "Start minimized" setting
1272 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1273 Properties.Settings.Default.Save();
1276 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1278 iStartupManager.Startup = checkBoxAutoStart.Checked;
1282 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1284 //Save our reverse screen setting
1285 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1286 Properties.Settings.Default.Save();
1287 SetupPixelDelegates();
1290 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1292 //Save our inverse colors setting
1293 cds.InverseColors = checkBoxInverseColors.Checked;
1294 Properties.Settings.Default.Save();
1295 SetupPixelDelegates();
1298 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1300 //Save our scale to fit setting
1301 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1302 Properties.Settings.Default.Save();
1304 labelMinFontSize.Enabled = cds.ScaleToFit;
1305 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1308 private void MainForm_Resize(object sender, EventArgs e)
1310 if (WindowState == FormWindowState.Minimized)
1312 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1313 // That's apparently not needed on Windows 10 but we better leave it in place.
1314 iCreateBitmap = true;
1318 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1321 iNetworkManager.Dispose();
1322 CloseDisplayConnection();
1324 e.Cancel = iClosing;
1327 public void StartServer()
1329 iServiceHost = new ServiceHost
1332 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1335 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1336 iServiceHost.Open();
1339 public void StopServer()
1341 if (iClients.Count > 0 && !iClosing)
1345 BroadcastCloseEvent();
1349 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1351 iClosing = false; //We make sure we force close if asked twice
1356 //We removed that as it often lags for some reason
1357 //iServiceHost.Close();
1361 public void BroadcastCloseEvent()
1363 Trace.TraceInformation("BroadcastCloseEvent - start");
1365 var inactiveClients = new List<string>();
1366 foreach (var client in iClients)
1368 //if (client.Key != eventData.ClientName)
1372 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1373 client.Value.Callback.OnCloseOrder(/*eventData*/);
1375 catch (Exception ex)
1377 inactiveClients.Add(client.Key);
1382 if (inactiveClients.Count > 0)
1384 foreach (var client in inactiveClients)
1386 iClients.Remove(client);
1387 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
1391 if (iClients.Count==0)
1398 /// Just remove all our fields.
1400 private void ClearLayout()
1402 iTableLayoutPanel.Controls.Clear();
1403 iTableLayoutPanel.RowStyles.Clear();
1404 iTableLayoutPanel.ColumnStyles.Clear();
1405 iCurrentClientData = null;
1409 /// Just launch a demo client.
1411 private void StartNewClient(string aTopText = "", string aBottomText = "")
1413 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1414 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1415 clientThread.Start(myParams);
1419 private void buttonStartClient_Click(object sender, EventArgs e)
1424 private void buttonSuspend_Click(object sender, EventArgs e)
1426 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1427 timer.Enabled = !timer.Enabled;
1430 buttonSuspend.Text = "Run";
1434 buttonSuspend.Text = "Pause";
1438 private void buttonCloseClients_Click(object sender, EventArgs e)
1440 BroadcastCloseEvent();
1443 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1445 //Root node must have at least one child
1446 if (e.Node.Nodes.Count == 0)
1451 //If the selected node is the root node of a client then switch to it
1452 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1453 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1455 //We have a valid session just switch to that client
1456 SetCurrentClient(sessionId,true);
1465 /// <param name="aSessionId"></param>
1466 /// <param name="aCallback"></param>
1467 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1469 if (this.InvokeRequired)
1471 //Not in the proper thread, invoke ourselves
1472 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1473 this.Invoke(d, new object[] { aSessionId, aCallback });
1477 //We are in the proper thread
1478 //Add this session to our collection of clients
1479 ClientData newClient = new ClientData(aSessionId, aCallback);
1480 Program.iMainForm.iClients.Add(aSessionId, newClient);
1481 //Add this session to our UI
1482 UpdateClientTreeViewNode(newClient);
1489 /// <param name="aSessionId"></param>
1490 public void RemoveClientThreadSafe(string aSessionId)
1492 if (this.InvokeRequired)
1494 //Not in the proper thread, invoke ourselves
1495 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1496 this.Invoke(d, new object[] { aSessionId });
1500 //We are in the proper thread
1501 //Remove this session from both client collection and UI tree view
1502 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1504 Program.iMainForm.iClients.Remove(aSessionId);
1505 Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
1508 if (iClients.Count == 0)
1510 //Clear our screen when last client disconnects
1515 //We were closing our form
1516 //All clients are now closed
1517 //Just resume our close operation
1528 /// <param name="aSessionId"></param>
1529 /// <param name="aLayout"></param>
1530 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1532 if (this.InvokeRequired)
1534 //Not in the proper thread, invoke ourselves
1535 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1536 this.Invoke(d, new object[] { aSessionId, aLayout });
1540 ClientData client = iClients[aSessionId];
1543 //Don't change a thing if the layout is the same
1544 if (!client.Layout.IsSameAs(aLayout))
1546 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1547 //Set our client layout then
1548 client.Layout = aLayout;
1549 //So that next time we update all our fields at ones
1550 client.HasNewLayout = true;
1551 //Layout has changed clear our fields then
1552 client.Fields.Clear();
1554 UpdateClientTreeViewNode(client);
1558 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1567 /// <param name="aSessionId"></param>
1568 /// <param name="aField"></param>
1569 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1571 if (this.InvokeRequired)
1573 //Not in the proper thread, invoke ourselves
1574 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1575 this.Invoke(d, new object[] { aSessionId, aField });
1579 //We are in the proper thread
1580 //Call the non-thread-safe variant
1581 SetClientField(aSessionId, aField);
1589 /// Set a data field in the given client.
1591 /// <param name="aSessionId"></param>
1592 /// <param name="aField"></param>
1593 private void SetClientField(string aSessionId, DataField aField)
1595 //TODO: should check if the field actually changed?
1597 ClientData client = iClients[aSessionId];
1598 bool layoutChanged = false;
1599 bool contentChanged = true;
1601 //Fetch our field index
1602 int fieldIndex = client.FindSameFieldIndex(aField);
1606 //No corresponding field, just bail out
1610 //Keep our previous field in there
1611 DataField previousField = client.Fields[fieldIndex];
1612 //Just update that field then
1613 client.Fields[fieldIndex] = aField;
1615 if (!aField.IsTableField)
1617 //We are done then if that field is not in our table layout
1621 TableField tableField = (TableField) aField;
1623 if (previousField.IsSameLayout(aField))
1625 //If we are updating a field in our current client we need to update it in our panel
1626 if (aSessionId == iCurrentClientSessionId)
1628 Control ctrl=iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1629 if (aField.IsTextField && ctrl is MarqueeLabel)
1631 TextField textField=(TextField)aField;
1632 //Text field control already in place, just change the text
1633 MarqueeLabel label = (MarqueeLabel)ctrl;
1634 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1635 label.Text = textField.Text;
1636 label.TextAlign = textField.Alignment;
1638 else if (aField.IsBitmapField && ctrl is PictureBox)
1640 BitmapField bitmapField = (BitmapField)aField;
1641 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1642 //Bitmap field control already in place just change the bitmap
1643 PictureBox pictureBox = (PictureBox)ctrl;
1644 pictureBox.Image = bitmapField.Bitmap;
1648 layoutChanged = true;
1654 layoutChanged = true;
1657 //If either content or layout changed we need to update our tree view to reflect the changes
1658 if (contentChanged || layoutChanged)
1660 UpdateClientTreeViewNode(client);
1664 Debug.Print("Layout changed");
1665 //Our layout has changed, if we are already the current client we need to update our panel
1666 if (aSessionId == iCurrentClientSessionId)
1668 //Apply layout and set data fields.
1669 UpdateTableLayoutPanel(iCurrentClientData);
1674 Debug.Print("Layout has not changed.");
1679 Debug.Print("WARNING: content and layout have not changed!");
1682 //When a client field is set we try switching to this client to present the new information to our user
1683 SetCurrentClient(aSessionId);
1689 /// <param name="aTexts"></param>
1690 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1692 if (this.InvokeRequired)
1694 //Not in the proper thread, invoke ourselves
1695 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1696 this.Invoke(d, new object[] { aSessionId, aFields });
1700 ClientData client = iClients[aSessionId];
1702 if (client.HasNewLayout)
1704 //TODO: Assert client.Count == 0
1705 //Our layout was just changed
1706 //Do some special handling to avoid re-creating our panel N times, once for each fields
1707 client.HasNewLayout = false;
1708 //Just set all our fields then
1709 client.Fields.AddRange(aFields);
1710 //Try switch to that client
1711 SetCurrentClient(aSessionId);
1713 //If we are updating the current client update our panel
1714 if (aSessionId == iCurrentClientSessionId)
1716 //Apply layout and set data fields.
1717 UpdateTableLayoutPanel(iCurrentClientData);
1720 UpdateClientTreeViewNode(client);
1724 //Put each our text fields in a label control
1725 foreach (DataField field in aFields)
1727 SetClientField(aSessionId, field);
1736 /// <param name="aSessionId"></param>
1737 /// <param name="aName"></param>
1738 public void SetClientNameThreadSafe(string aSessionId, string aName)
1740 if (this.InvokeRequired)
1742 //Not in the proper thread, invoke ourselves
1743 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1744 this.Invoke(d, new object[] { aSessionId, aName });
1748 //We are in the proper thread
1750 ClientData client = iClients[aSessionId];
1754 client.Name = aName;
1755 //Update our tree-view
1756 UpdateClientTreeViewNode(client);
1762 /// Update our recording notification.
1764 private void UpdateRecordingNotification()
1767 bool activeRecording = false;
1769 RecordingField recField=new RecordingField();
1770 foreach (var client in iClients)
1772 RecordingField rec=(RecordingField)client.Value.FindSameFieldAs(recField);
1773 if (rec!=null && rec.IsActive)
1775 activeRecording = true;
1776 //Don't break cause we are collecting the names.
1777 text += client.Value.Name + " recording\n";
1781 //Change visibility of notification if needed
1782 if (iRecordingNotification.Visible != activeRecording)
1784 iRecordingNotification.Text = text;
1785 iRecordingNotification.Visible = activeRecording;
1793 /// <param name="aClient"></param>
1794 private void UpdateClientTreeViewNode(ClientData aClient)
1796 Debug.Print("UpdateClientTreeViewNode");
1798 if (aClient == null)
1803 //Hook in record icon update too
1804 UpdateRecordingNotification();
1806 TreeNode node = null;
1807 //Check that our client node already exists
1808 //Get our client root node using its key which is our session ID
1809 TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
1810 if (nodes.Count()>0)
1812 //We already have a node for that client
1814 //Clear children as we are going to recreate them below
1819 //Client node does not exists create a new one
1820 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1821 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1827 if (aClient.Name != "")
1829 //We have a name, us it as text for our root node
1830 node.Text = aClient.Name;
1831 //Add a child with SessionId
1832 node.Nodes.Add(new TreeNode(aClient.SessionId));
1836 //No name, use session ID instead
1837 node.Text = aClient.SessionId;
1840 if (aClient.Fields.Count > 0)
1842 //Create root node for our texts
1843 TreeNode textsRoot = new TreeNode("Fields");
1844 node.Nodes.Add(textsRoot);
1845 //For each text add a new entry
1846 foreach (DataField field in aClient.Fields)
1848 if (field.IsTextField)
1850 TextField textField = (TextField)field;
1851 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
1853 else if (field.IsBitmapField)
1855 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
1857 else if (field.IsRecordingField)
1859 RecordingField recordingField = (RecordingField)field;
1860 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
1870 /// Update our table layout row styles to make sure each rows have similar height
1872 private void UpdateTableLayoutRowStyles()
1874 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
1876 rowStyle.SizeType = SizeType.Percent;
1877 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
1882 /// Update our display table layout.
1883 /// Will instanciated every field control as defined by our client.
1884 /// Fields must be specified by rows from the left.
1886 /// <param name="aLayout"></param>
1887 private void UpdateTableLayoutPanel(ClientData aClient)
1889 Debug.Print("UpdateTableLayoutPanel");
1891 if (aClient == null)
1898 TableLayout layout = aClient.Layout;
1900 //First clean our current panel
1901 iTableLayoutPanel.Controls.Clear();
1902 iTableLayoutPanel.RowStyles.Clear();
1903 iTableLayoutPanel.ColumnStyles.Clear();
1904 iTableLayoutPanel.RowCount = 0;
1905 iTableLayoutPanel.ColumnCount = 0;
1907 //Then recreate our rows...
1908 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
1910 iTableLayoutPanel.RowCount++;
1914 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
1916 iTableLayoutPanel.ColumnCount++;
1920 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
1922 //Create our column styles
1923 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
1926 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
1930 //Create our row styles
1931 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
1941 foreach (DataField field in aClient.Fields)
1943 if (!field.IsTableField)
1945 //That field is not taking part in our table layout skip it
1949 TableField tableField = (TableField)field;
1951 //Create a control corresponding to the field specified for that cell
1952 Control control = CreateControlForDataField(tableField);
1954 //Add newly created control to our table layout at the specified row and column
1955 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
1956 //Make sure we specify column and row span for that new control
1957 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
1958 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
1966 /// Check our type of data field and create corresponding control
1968 /// <param name="aField"></param>
1969 private Control CreateControlForDataField(DataField aField)
1971 Control control=null;
1972 if (aField.IsTextField)
1974 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
1975 label.AutoEllipsis = true;
1976 label.AutoSize = true;
1977 label.BackColor = System.Drawing.Color.Transparent;
1978 label.Dock = System.Windows.Forms.DockStyle.Fill;
1979 label.Location = new System.Drawing.Point(1, 1);
1980 label.Margin = new System.Windows.Forms.Padding(0);
1981 label.Name = "marqueeLabel" + aField;
1982 label.OwnTimer = false;
1983 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
1984 label.Separator = cds.Separator;
1985 label.MinFontSize = cds.MinFontSize;
1986 label.ScaleToFit = cds.ScaleToFit;
1987 //control.Size = new System.Drawing.Size(254, 30);
1988 //control.TabIndex = 2;
1989 label.Font = cds.Font;
1991 TextField field = (TextField)aField;
1992 label.TextAlign = field.Alignment;
1993 label.UseCompatibleTextRendering = true;
1994 label.Text = field.Text;
1998 else if (aField.IsBitmapField)
2000 //Create picture box
2001 PictureBox picture = new PictureBox();
2002 picture.AutoSize = true;
2003 picture.BackColor = System.Drawing.Color.Transparent;
2004 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2005 picture.Location = new System.Drawing.Point(1, 1);
2006 picture.Margin = new System.Windows.Forms.Padding(0);
2007 picture.Name = "pictureBox" + aField;
2009 BitmapField field = (BitmapField)aField;
2010 picture.Image = field.Bitmap;
2014 //TODO: Handle recording field?
2020 /// Called when the user selected a new display type.
2022 /// <param name="sender"></param>
2023 /// <param name="e"></param>
2024 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2026 //Store the selected display type in our settings
2027 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2028 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2029 Properties.Settings.Default.Save();
2031 //Try re-opening the display connection if we were already connected.
2032 //Otherwise just update our status to reflect display type change.
2033 if (iDisplay.IsOpen())
2035 OpenDisplayConnection();
2043 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2045 if (maskedTextBoxTimerInterval.Text != "")
2047 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2051 timer.Interval = interval;
2052 cds.TimerInterval = timer.Interval;
2053 Properties.Settings.Default.Save();
2058 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2060 if (maskedTextBoxMinFontSize.Text != "")
2062 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2064 if (minFontSize > 0)
2066 cds.MinFontSize = minFontSize;
2067 Properties.Settings.Default.Save();
2068 //We need to recreate our layout for that change to take effect
2069 UpdateTableLayoutPanel(iCurrentClientData);
2075 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2077 if (maskedTextBoxScrollingSpeed.Text != "")
2079 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2081 if (scrollingSpeed > 0)
2083 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2084 Properties.Settings.Default.Save();
2085 //We need to recreate our layout for that change to take effect
2086 UpdateTableLayoutPanel(iCurrentClientData);
2091 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2093 cds.Separator = textBoxScrollLoopSeparator.Text;
2094 Properties.Settings.Default.Save();
2096 //Update our text fields
2097 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2099 ctrl.Separator = cds.Separator;
2104 private void buttonPowerOn_Click(object sender, EventArgs e)
2109 private void buttonPowerOff_Click(object sender, EventArgs e)
2111 iDisplay.PowerOff();
2114 private void buttonShowClock_Click(object sender, EventArgs e)
2119 private void buttonHideClock_Click(object sender, EventArgs e)
2124 private void buttonUpdate_Click(object sender, EventArgs e)
2126 InstallUpdateSyncWithInfo();
2134 if (!iDisplay.IsOpen())
2139 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2140 iSkipFrameRendering = true;
2143 iDisplay.SwapBuffers();
2144 //Then show our clock
2145 iDisplay.ShowClock();
2153 if (!iDisplay.IsOpen())
2158 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2159 iSkipFrameRendering = false;
2160 iDisplay.HideClock();
2163 private void InstallUpdateSyncWithInfo()
2165 UpdateCheckInfo info = null;
2167 if (ApplicationDeployment.IsNetworkDeployed)
2169 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2173 info = ad.CheckForDetailedUpdate();
2176 catch (DeploymentDownloadException dde)
2178 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);
2181 catch (InvalidDeploymentException ide)
2183 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);
2186 catch (InvalidOperationException ioe)
2188 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2192 if (info.UpdateAvailable)
2194 Boolean doUpdate = true;
2196 if (!info.IsUpdateRequired)
2198 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2199 if (!(DialogResult.OK == dr))
2206 // Display a message that the app MUST reboot. Display the minimum required version.
2207 MessageBox.Show("This application has detected a mandatory update from your current " +
2208 "version to version " + info.MinimumRequiredVersion.ToString() +
2209 ". The application will now install the update and restart.",
2210 "Update Available", MessageBoxButtons.OK,
2211 MessageBoxIcon.Information);
2219 MessageBox.Show("The application has been upgraded, and will now restart.");
2220 Application.Restart();
2222 catch (DeploymentDownloadException dde)
2224 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2231 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2240 private void SysTrayHideShow()
2246 WindowState = FormWindowState.Normal;
2251 /// Use to handle minimize events.
2253 /// <param name="sender"></param>
2254 /// <param name="e"></param>
2255 private void MainForm_SizeChanged(object sender, EventArgs e)
2257 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2269 /// <param name="sender"></param>
2270 /// <param name="e"></param>
2271 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2273 //Our table layout size has changed which means our display size has changed.
2274 //We need to re-create our bitmap.
2275 iCreateBitmap = true;
2281 /// <param name="sender"></param>
2282 /// <param name="e"></param>
2283 private void buttonSelectFile_Click(object sender, EventArgs e)
2285 //openFileDialog1.InitialDirectory = "c:\\";
2286 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2287 //openFileDialog.FilterIndex = 1;
2288 openFileDialog.RestoreDirectory = true;
2290 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2292 labelStartFileName.Text = openFileDialog.FileName;
2293 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2294 Properties.Settings.Default.Save();
2301 /// <param name="sender"></param>
2302 /// <param name="e"></param>
2303 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2305 //Save the optical drive the user selected for ejection
2306 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2307 Properties.Settings.Default.Save();
2311 /// Broadcast messages to subscribers.
2313 /// <param name="message"></param>
2314 protected override void WndProc(ref Message aMessage)
2316 if (OnWndProc!=null)
2318 OnWndProc(ref aMessage);
2321 base.WndProc(ref aMessage);
2324 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2326 //Save CEC enabled status
2327 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2328 Properties.Settings.Default.Save();
2333 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2335 //Save CEC HDMI port
2336 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2337 Properties.Settings.Default.CecHdmiPort++;
2338 Properties.Settings.Default.Save();
2343 private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
2345 Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
2346 Properties.Settings.Default.Save();
2351 private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
2353 Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
2354 Properties.Settings.Default.Save();
2362 private void ResetCec()
2364 if (iCecManager==null)
2366 //Thus skipping initial UI setup
2372 if (Properties.Settings.Default.CecEnabled)
2374 iCecManager.Start(Handle, "CEC",
2375 Properties.Settings.Default.CecHdmiPort,
2376 Properties.Settings.Default.CecMonitorOn,
2377 Properties.Settings.Default.CecMonitorOff);