2 // Copyright (C) 2014-2015 Stéphane Lenclud.
4 // This file is part of SharpDisplayManager.
6 // SharpDisplayManager is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
11 // SharpDisplayManager is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with SharpDisplayManager. If not, see <http://www.gnu.org/licenses/>.
21 using System.Collections.Generic;
22 using System.ComponentModel;
27 using System.Threading.Tasks;
28 using System.Windows.Forms;
30 using CodeProject.Dialog;
31 using System.Drawing.Imaging;
32 using System.ServiceModel;
33 using System.Threading;
34 using System.Diagnostics;
35 using System.Deployment.Application;
36 using System.Reflection;
38 using NAudio.CoreAudioApi;
39 using NAudio.CoreAudioApi.Interfaces;
40 using System.Runtime.InteropServices;
44 using SharpDisplayClient;
46 using MiniDisplayInterop;
47 using SharpLib.Display;
49 namespace SharpDisplayManager
52 public delegate uint ColorProcessingDelegate(int aX, int aY, uint aPixel);
53 public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
54 //Delegates are used for our thread safe method
55 public delegate void AddClientDelegate(string aSessionId, ICallback aCallback);
56 public delegate void RemoveClientDelegate(string aSessionId);
57 public delegate void SetFieldDelegate(string SessionId, DataField aField);
58 public delegate void SetFieldsDelegate(string SessionId, System.Collections.Generic.IList<DataField> aFields);
59 public delegate void SetLayoutDelegate(string SessionId, TableLayout aLayout);
60 public delegate void SetClientNameDelegate(string aSessionId, string aName);
61 public delegate void SetClientPriorityDelegate(string aSessionId, uint aPriority);
62 public delegate void PlainUpdateDelegate();
63 public delegate void WndProcDelegate(ref Message aMessage);
66 /// Our Display manager main form
68 [System.ComponentModel.DesignerCategory("Form")]
69 public partial class MainForm : MainFormHid, IMMNotificationClient
71 DateTime LastTickTime;
73 System.Drawing.Bitmap iBmp;
74 bool iCreateBitmap; //Workaround render to bitmap issues when minimized
75 ServiceHost iServiceHost;
76 // Our collection of clients sorted by session id.
77 public Dictionary<string, ClientData> iClients;
78 // The name of the client which informations are currently displayed.
79 public string iCurrentClientSessionId;
80 ClientData iCurrentClientData;
84 public bool iSkipFrameRendering;
85 //Function pointer for pixel color filtering
86 ColorProcessingDelegate iColorFx;
87 //Function pointer for pixel X coordinate intercept
88 CoordinateTranslationDelegate iScreenX;
89 //Function pointer for pixel Y coordinate intercept
90 CoordinateTranslationDelegate iScreenY;
92 private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
93 private MMDevice iMultiMediaDevice;
95 private NetworkManager iNetworkManager;
98 /// CEC - Consumer Electronic Control.
99 /// Notably used to turn TV on and off as Windows broadcast monitor on and off notifications.
101 private ConsumerElectronicControl iCecManager;
104 /// Manage run when Windows startup option
106 private StartupManager iStartupManager;
109 /// System notification icon used to hide our application from the task bar.
111 private SharpLib.Notification.Control iNotifyIcon;
114 /// System recording notification icon.
116 private SharpLib.Notification.Control iRecordingNotification;
121 RichTextBoxTextWriter iWriter;
125 /// Allow user to receive window messages;
127 public event WndProcDelegate OnWndProc;
131 iSkipFrameRendering = false;
133 iCurrentClientSessionId = "";
134 iCurrentClientData = null;
135 LastTickTime = DateTime.Now;
136 //Instantiate our display and register for events notifications
137 iDisplay = new Display();
138 iDisplay.OnOpened += OnDisplayOpened;
139 iDisplay.OnClosed += OnDisplayClosed;
141 iClients = new Dictionary<string, ClientData>();
142 iStartupManager = new StartupManager();
143 iNotifyIcon = new SharpLib.Notification.Control();
144 iRecordingNotification = new SharpLib.Notification.Control();
146 //Have our designer initialize its controls
147 InitializeComponent();
149 //Redirect console output
150 iWriter = new RichTextBoxTextWriter(richTextBoxLogs);
151 Console.SetOut(iWriter);
153 //Populate device types
154 PopulateDeviceTypes();
156 //Populate optical drives
157 PopulateOpticalDrives();
159 //Initial status update
162 //We have a bug when drawing minimized and reusing our bitmap
163 //Though I could not reproduce it on Windows 10
164 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
165 iCreateBitmap = false;
167 //Minimize our window if desired
168 if (Properties.Settings.Default.StartMinimized)
170 WindowState = FormWindowState.Minimized;
178 /// <param name="sender"></param>
179 /// <param name="e"></param>
180 private void MainForm_Load(object sender, EventArgs e)
182 //Check if we are running a Click Once deployed application
183 if (ApplicationDeployment.IsNetworkDeployed)
185 //This is a proper Click Once installation, fetch and show our version number
186 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
190 //Not a proper Click Once installation, assuming development build then
191 this.Text += " - development";
195 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
196 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
197 UpdateAudioDeviceAndMasterVolumeThreadSafe();
200 iNetworkManager = new NetworkManager();
201 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
202 UpdateNetworkStatus();
205 iCecManager = new ConsumerElectronicControl();
206 OnWndProc += iCecManager.OnWndProc;
210 //Setup notification icon
213 //Setup recording notification
214 SetupRecordingNotification();
216 // To make sure start up with minimize to tray works
217 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
223 //When not debugging we want the screen to be empty until a client takes over
226 //When developing we want at least one client for testing
227 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
230 //Open display connection on start-up if needed
231 if (Properties.Settings.Default.DisplayConnectOnStartup)
233 OpenDisplayConnection();
236 //Start our server so that we can get client requests
239 //Register for HID events
240 RegisterHidDevices();
242 //Start Idle client if needed
243 if (Properties.Settings.Default.StartIdleClient)
250 /// Called when our display is opened.
252 /// <param name="aDisplay"></param>
253 private void OnDisplayOpened(Display aDisplay)
255 //Make sure we resume frame rendering
256 iSkipFrameRendering = false;
258 //Set our screen size now that our display is connected
259 //Our panelDisplay is the container of our tableLayoutPanel
260 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
261 //panelDisplay needs an extra 2 pixels for borders on each sides
262 //tableLayoutPanel will eventually be the exact size of our display
263 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
264 panelDisplay.Size = size;
266 //Our display was just opened, update our UI
268 //Initiate asynchronous request
269 iDisplay.RequestFirmwareRevision();
272 UpdateMasterVolumeThreadSafe();
274 UpdateNetworkStatus();
277 //Testing icon in debug, no arm done if icon not supported
278 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
279 //iDisplay.SetAllIconsStatus(2);
285 /// Called when our display is closed.
287 /// <param name="aDisplay"></param>
288 private void OnDisplayClosed(Display aDisplay)
290 //Our display was just closed, update our UI consequently
294 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
296 //Update network status
297 UpdateNetworkStatus();
301 /// Update our Network Status
303 private void UpdateNetworkStatus()
305 if (iDisplay.IsOpen())
307 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
308 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
313 int iLastNetworkIconIndex = 0;
314 int iUpdateCountSinceLastNetworkAnimation = 0;
319 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
321 iUpdateCountSinceLastNetworkAnimation++;
322 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
324 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
326 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
329 //Prevents div by zero and other undefined behavior
332 iLastNetworkIconIndex++;
333 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
334 for (int i=0;i<iconCount;i++)
336 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
338 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
342 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
351 /// Receive volume change notification and reflect changes on our slider.
353 /// <param name="data"></param>
354 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
356 UpdateMasterVolumeThreadSafe();
360 /// Update master volume when user moves our slider.
362 /// <param name="sender"></param>
363 /// <param name="e"></param>
364 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
366 //Just like Windows Volume Mixer we unmute if the volume is adjusted
367 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
368 //Set volume level according to our volume slider new position
369 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
374 /// Mute check box changed.
376 /// <param name="sender"></param>
377 /// <param name="e"></param>
378 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
380 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
384 /// Device State Changed
386 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
391 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
396 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
399 /// Default Device Changed
401 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
403 if (role == Role.Multimedia && flow == DataFlow.Render)
405 UpdateAudioDeviceAndMasterVolumeThreadSafe();
410 /// Property Value Changed
412 /// <param name="pwstrDeviceId"></param>
413 /// <param name="key"></param>
414 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
420 /// Update master volume indicators based our current system states.
421 /// This typically includes volume levels and mute status.
423 private void UpdateMasterVolumeThreadSafe()
425 if (this.InvokeRequired)
427 //Not in the proper thread, invoke ourselves
428 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
429 this.Invoke(d, new object[] { });
433 //Update volume slider
434 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
435 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
436 //Update mute checkbox
437 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
439 //If our display connection is open we need to update its icons
440 if (iDisplay.IsOpen())
442 //First take care our our volume level icons
443 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
444 if (volumeIconCount > 0)
446 //Compute current volume level from system level and the number of segments in our display volume bar.
447 //That tells us how many segments in our volume bar needs to be turned on.
448 float currentVolume = volumeLevelScalar * volumeIconCount;
449 int segmentOnCount = Convert.ToInt32(currentVolume);
450 //Check if our segment count was rounded up, this will later be used for half brightness segment
451 bool roundedUp = segmentOnCount > currentVolume;
453 for (int i = 0; i < volumeIconCount; i++)
455 if (i < segmentOnCount)
457 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
458 if (i == segmentOnCount - 1 && roundedUp)
461 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
466 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
471 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
476 //Take care of our mute icon
477 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
485 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
487 if (this.InvokeRequired)
489 //Not in the proper thread, invoke ourselves
490 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
491 this.Invoke(d, new object[] { });
495 //We are in the correct thread just go ahead.
498 //Get our master volume
499 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
501 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
503 //Show our volume in our track bar
504 UpdateMasterVolumeThreadSafe();
506 //Register to get volume modifications
507 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
509 trackBarMasterVolume.Enabled = true;
513 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
514 Debug.WriteLine(ex.ToString());
515 //Something went wrong S/PDIF device ca throw exception I guess
516 trackBarMasterVolume.Enabled = false;
523 private void PopulateDeviceTypes()
525 int count = Display.TypeCount();
527 for (int i = 0; i < count; i++)
529 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
536 private void PopulateOpticalDrives()
538 //Reset our list of drives
539 comboBoxOpticalDrives.Items.Clear();
540 comboBoxOpticalDrives.Items.Add("None");
542 //Go through each drives on our system and collected the optical ones in our list
543 DriveInfo[] allDrives = DriveInfo.GetDrives();
544 foreach (DriveInfo d in allDrives)
546 Debug.WriteLine("Drive " + d.Name);
547 Debug.WriteLine(" Drive type: {0}", d.DriveType);
549 if (d.DriveType==DriveType.CDRom)
551 //This is an optical drive, add it now
552 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
560 /// <returns></returns>
561 public string OpticalDriveToEject()
563 return comboBoxOpticalDrives.SelectedItem.ToString();
571 private void SetupTrayIcon()
573 iNotifyIcon.Icon = GetIcon("vfd.ico");
574 iNotifyIcon.Text = "Sharp Display Manager";
575 iNotifyIcon.Visible = true;
577 //Double click toggles visibility - typically brings up the application
578 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
583 //Adding a context menu, useful to be able to exit the application
584 ContextMenu contextMenu = new ContextMenu();
585 //Context menu item to toggle visibility
586 MenuItem hideShowItem = new MenuItem("Hide/Show");
587 hideShowItem.Click += delegate(object obj, EventArgs args)
591 contextMenu.MenuItems.Add(hideShowItem);
593 //Context menu item separator
594 contextMenu.MenuItems.Add(new MenuItem("-"));
596 //Context menu exit item
597 MenuItem exitItem = new MenuItem("Exit");
598 exitItem.Click += delegate(object obj, EventArgs args)
602 contextMenu.MenuItems.Add(exitItem);
604 iNotifyIcon.ContextMenu = contextMenu;
610 private void SetupRecordingNotification()
612 iRecordingNotification.Icon = GetIcon("record.ico");
613 iRecordingNotification.Text = "No recording";
614 iRecordingNotification.Visible = false;
618 /// Access icons from embedded resources.
620 /// <param name="aName"></param>
621 /// <returns></returns>
622 public static Icon GetIcon(string aName)
624 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
625 foreach (string name in names)
627 //Find a resource name that ends with the given name
628 if (name.EndsWith(aName))
630 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
632 return new Icon(stream);
642 /// Set our current client.
643 /// This will take care of applying our client layout and set data fields.
645 /// <param name="aSessionId"></param>
646 void SetCurrentClient(string aSessionId, bool aForce=false)
648 if (aSessionId == iCurrentClientSessionId)
650 //Given client is already the current one.
651 //Don't bother changing anything then.
655 ClientData requestedClientData = iClients[aSessionId];
657 //Check when was the last time we switched to that client
658 if (iCurrentClientData != null)
660 //Do not switch client if priority of current client is higher
661 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
667 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
668 //TODO: put that hard coded value as a client property
669 //Clients should be able to define how often they can be interrupted
670 //Thus a background client can set this to zero allowing any other client to interrupt at any time
671 //We could also compute this delay by looking at the requests frequencies?
673 requestedClientData.Priority == iCurrentClientData.Priority && //Time sharing is only if clients have the same priority
674 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
676 //Don't switch clients too often
681 //Set current client ID.
682 iCurrentClientSessionId = aSessionId;
683 //Set the time we last switched to that client
684 iClients[aSessionId].LastSwitchTime = DateTime.Now;
685 //Fetch and set current client data.
686 iCurrentClientData = requestedClientData;
687 //Apply layout and set data fields.
688 UpdateTableLayoutPanel(iCurrentClientData);
691 private void buttonFont_Click(object sender, EventArgs e)
693 //fontDialog.ShowColor = true;
694 //fontDialog.ShowApply = true;
695 fontDialog.ShowEffects = true;
696 fontDialog.Font = cds.Font;
698 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
700 //fontDialog.ShowHelp = true;
702 //fontDlg.MaxSize = 40;
703 //fontDlg.MinSize = 22;
705 //fontDialog.Parent = this;
706 //fontDialog.StartPosition = FormStartPosition.CenterParent;
708 //DlgBox.ShowDialog(fontDialog);
710 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
711 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
713 //Set the fonts to all our labels in our layout
714 foreach (Control ctrl in iTableLayoutPanel.Controls)
716 if (ctrl is MarqueeLabel)
718 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
723 cds.Font = fontDialog.Font;
724 Properties.Settings.Default.Save();
733 void CheckFontHeight()
735 //Show font height and width
736 labelFontHeight.Text = "Font height: " + cds.Font.Height;
737 float charWidth = IsFixedWidth(cds.Font);
738 if (charWidth == 0.0f)
740 labelFontWidth.Visible = false;
744 labelFontWidth.Visible = true;
745 labelFontWidth.Text = "Font width: " + charWidth;
748 MarqueeLabel label = null;
749 //Get the first label control we can find
750 foreach (Control ctrl in iTableLayoutPanel.Controls)
752 if (ctrl is MarqueeLabel)
754 label = (MarqueeLabel)ctrl;
759 //Now check font height and show a warning if needed.
760 if (label != null && label.Font.Height > label.Height)
762 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
763 labelWarning.Visible = true;
767 labelWarning.Visible = false;
772 private void buttonCapture_Click(object sender, EventArgs e)
774 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
775 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
776 //Bitmap bmpToSave = new Bitmap(bmp);
777 bmp.Save("D:\\capture.png");
779 ((MarqueeLabel)iTableLayoutPanel.Controls[0]).Text = "Captured";
782 string outputFileName = "d:\\capture.png";
783 using (MemoryStream memory = new MemoryStream())
785 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
787 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
788 byte[] bytes = memory.ToArray();
789 fs.Write(bytes, 0, bytes.Length);
796 private void CheckForRequestResults()
798 if (iDisplay.IsRequestPending())
800 switch (iDisplay.AttemptRequestCompletion())
802 case MiniDisplay.Request.FirmwareRevision:
803 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
804 //Issue next request then
805 iDisplay.RequestPowerSupplyStatus();
808 case MiniDisplay.Request.PowerSupplyStatus:
809 if (iDisplay.PowerSupplyStatus())
811 toolStripStatusLabelPower.Text = "ON";
815 toolStripStatusLabelPower.Text = "OFF";
817 //Issue next request then
818 iDisplay.RequestDeviceId();
821 case MiniDisplay.Request.DeviceId:
822 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
823 //No more request to issue
829 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
831 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
838 public static uint ColorUntouched(int aX, int aY, uint aPixel)
843 public static uint ColorInversed(int aX, int aY, uint aPixel)
848 public static uint ColorChessboard(int aX, int aY, uint aPixel)
850 if ((aX % 2 == 0) && (aY % 2 == 0))
854 else if ((aX % 2 != 0) && (aY % 2 != 0))
862 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
864 return aBmp.Width - aX - 1;
867 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
869 return iBmp.Height - aY - 1;
872 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
877 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
883 /// Select proper pixel delegates according to our current settings.
885 private void SetupPixelDelegates()
887 //Select our pixel processing routine
888 if (cds.InverseColors)
890 //iColorFx = ColorChessboard;
891 iColorFx = ColorInversed;
895 iColorFx = ColorWhiteIsOn;
898 //Select proper coordinate translation functions
899 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
900 if (cds.ReverseScreen)
902 iScreenX = ScreenReversedX;
903 iScreenY = ScreenReversedY;
913 //This is our timer tick responsible to perform our render
914 private void timer_Tick(object sender, EventArgs e)
916 //Update our animations
917 DateTime NewTickTime = DateTime.Now;
919 UpdateNetworkSignal(LastTickTime, NewTickTime);
921 //Update animation for all our marquees
922 foreach (Control ctrl in iTableLayoutPanel.Controls)
924 if (ctrl is MarqueeLabel)
926 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
931 if (iDisplay.IsOpen())
933 CheckForRequestResults();
935 //Check if frame rendering is needed
936 //Typically used when showing clock
937 if (!iSkipFrameRendering)
942 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
943 iCreateBitmap = false;
945 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
946 //iBmp.Save("D:\\capture.png");
948 //Send it to our display
949 for (int i = 0; i < iBmp.Width; i++)
951 for (int j = 0; j < iBmp.Height; j++)
955 //Get our processed pixel coordinates
956 int x = iScreenX(iBmp, i);
957 int y = iScreenY(iBmp, j);
959 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
960 //Apply color effects
961 color = iColorFx(x, y, color);
963 iDisplay.SetPixel(x, y, color);
968 iDisplay.SwapBuffers();
972 //Compute instant FPS
973 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
975 LastTickTime = NewTickTime;
980 /// Attempt to establish connection with our display hardware.
982 private void OpenDisplayConnection()
984 CloseDisplayConnection();
986 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
989 toolStripStatusLabelConnect.Text = "Connection error";
993 private void CloseDisplayConnection()
995 //Status will be updated upon receiving the closed event
997 if (iDisplay == null || !iDisplay.IsOpen())
1002 //Do not clear if we gave up on rendering already.
1003 //This means we will keep on displaying clock on MDM166AA for instance.
1004 if (!iSkipFrameRendering)
1007 iDisplay.SwapBuffers();
1010 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1014 private void buttonOpen_Click(object sender, EventArgs e)
1016 OpenDisplayConnection();
1019 private void buttonClose_Click(object sender, EventArgs e)
1021 CloseDisplayConnection();
1024 private void buttonClear_Click(object sender, EventArgs e)
1027 iDisplay.SwapBuffers();
1030 private void buttonFill_Click(object sender, EventArgs e)
1033 iDisplay.SwapBuffers();
1036 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1038 cds.Brightness = trackBarBrightness.Value;
1039 Properties.Settings.Default.Save();
1040 iDisplay.SetBrightness(trackBarBrightness.Value);
1046 /// CDS stands for Current Display Settings
1048 private DisplaySettings cds
1052 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1053 if (settings == null)
1055 settings = new DisplaysSettings();
1057 Properties.Settings.Default.DisplaysSettings = settings;
1060 //Make sure all our settings have been created
1061 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1063 settings.Displays.Add(new DisplaySettings());
1066 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1067 return displaySettings;
1072 /// Check if the given font has a fixed character pitch.
1074 /// <param name="ft"></param>
1075 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1076 public float IsFixedWidth(Font ft)
1078 Graphics g = CreateGraphics();
1079 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1080 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1082 bool fixedWidth = true;
1084 foreach (char c in charSizes)
1085 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1097 /// Synchronize UI with settings
1099 private void UpdateStatus()
1102 checkBoxShowBorders.Checked = cds.ShowBorders;
1103 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1105 //Set the proper font to each of our labels
1106 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1108 ctrl.Font = cds.Font;
1112 //Check if "run on Windows startup" is enabled
1113 checkBoxAutoStart.Checked = iStartupManager.Startup;
1115 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1116 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1117 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1118 iCheckBoxStartIdleClient.Checked = Properties.Settings.Default.StartIdleClient;
1119 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1122 //Try find our drive in our drive list
1123 int opticalDriveItemIndex=0;
1124 bool driveNotFound = true;
1125 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1126 foreach (object item in comboBoxOpticalDrives.Items)
1128 if (opticalDriveToEject == item.ToString())
1130 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1131 driveNotFound = false;
1134 opticalDriveItemIndex++;
1139 //We could not find the drive we had saved.
1140 //Select "None" then.
1141 comboBoxOpticalDrives.SelectedIndex = 0;
1145 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1146 checkBoxCecMonitorOn.Checked = Properties.Settings.Default.CecMonitorOn;
1147 checkBoxCecMonitorOff.Checked = Properties.Settings.Default.CecMonitorOff;
1148 checkBoxCecReconnectToPowerTv.Checked = Properties.Settings.Default.CecReconnectToPowerTv;
1149 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1151 //Mini Display settings
1152 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1153 checkBoxInverseColors.Checked = cds.InverseColors;
1154 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1155 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1156 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1157 labelMinFontSize.Enabled = cds.ScaleToFit;
1158 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1159 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1160 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1161 timer.Interval = cds.TimerInterval;
1162 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1163 textBoxScrollLoopSeparator.Text = cds.Separator;
1165 SetupPixelDelegates();
1167 if (iDisplay.IsOpen())
1169 //We have a display connection
1170 //Reflect that in our UI
1173 iTableLayoutPanel.Enabled = true;
1174 panelDisplay.Enabled = true;
1176 //Only setup brightness if display is open
1177 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1178 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1179 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1181 //Brightness out of range, this can occur when using auto-detect
1182 //Use max brightness instead
1183 trackBarBrightness.Value = iDisplay.MaxBrightness();
1184 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1188 trackBarBrightness.Value = cds.Brightness;
1189 iDisplay.SetBrightness(cds.Brightness);
1192 //Try compute the steps to something that makes sense
1193 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1194 trackBarBrightness.SmallChange = 1;
1197 buttonFill.Enabled = true;
1198 buttonClear.Enabled = true;
1199 buttonOpen.Enabled = false;
1200 buttonClose.Enabled = true;
1201 trackBarBrightness.Enabled = true;
1202 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1203 //+ " - " + iDisplay.SerialNumber();
1205 if (iDisplay.SupportPowerOnOff())
1207 buttonPowerOn.Enabled = true;
1208 buttonPowerOff.Enabled = true;
1212 buttonPowerOn.Enabled = false;
1213 buttonPowerOff.Enabled = false;
1216 if (iDisplay.SupportClock())
1218 buttonShowClock.Enabled = true;
1219 buttonHideClock.Enabled = true;
1223 buttonShowClock.Enabled = false;
1224 buttonHideClock.Enabled = false;
1228 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1229 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1231 if (cds.ShowVolumeLabel)
1233 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1237 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1242 //Display connection not available
1243 //Reflect that in our UI
1245 //In debug start our timer even if we don't have a display connection
1248 //In production environment we don't need our timer if no display connection
1251 checkBoxShowVolumeLabel.Enabled = false;
1252 iTableLayoutPanel.Enabled = false;
1253 panelDisplay.Enabled = false;
1254 buttonFill.Enabled = false;
1255 buttonClear.Enabled = false;
1256 buttonOpen.Enabled = true;
1257 buttonClose.Enabled = false;
1258 trackBarBrightness.Enabled = false;
1259 buttonPowerOn.Enabled = false;
1260 buttonPowerOff.Enabled = false;
1261 buttonShowClock.Enabled = false;
1262 buttonHideClock.Enabled = false;
1263 toolStripStatusLabelConnect.Text = "Disconnected";
1264 toolStripStatusLabelPower.Text = "N/A";
1273 /// <param name="sender"></param>
1274 /// <param name="e"></param>
1275 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1277 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1278 Properties.Settings.Default.Save();
1282 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1284 //Save our show borders setting
1285 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1286 cds.ShowBorders = checkBoxShowBorders.Checked;
1287 Properties.Settings.Default.Save();
1291 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1293 //Save our connect on startup setting
1294 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1295 Properties.Settings.Default.Save();
1298 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1300 //Save our "Minimize to tray" setting
1301 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1302 Properties.Settings.Default.Save();
1306 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1308 //Save our "Start minimized" setting
1309 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1310 Properties.Settings.Default.Save();
1313 private void checkBoxStartIdleClient_CheckedChanged(object sender, EventArgs e)
1315 Properties.Settings.Default.StartIdleClient = iCheckBoxStartIdleClient.Checked;
1316 Properties.Settings.Default.Save();
1319 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1321 iStartupManager.Startup = checkBoxAutoStart.Checked;
1325 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1327 //Save our reverse screen setting
1328 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1329 Properties.Settings.Default.Save();
1330 SetupPixelDelegates();
1333 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1335 //Save our inverse colors setting
1336 cds.InverseColors = checkBoxInverseColors.Checked;
1337 Properties.Settings.Default.Save();
1338 SetupPixelDelegates();
1341 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1343 //Save our scale to fit setting
1344 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1345 Properties.Settings.Default.Save();
1347 labelMinFontSize.Enabled = cds.ScaleToFit;
1348 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1351 private void MainForm_Resize(object sender, EventArgs e)
1353 if (WindowState == FormWindowState.Minimized)
1355 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1356 // That's apparently not needed on Windows 10 but we better leave it in place.
1357 iCreateBitmap = true;
1361 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1364 iNetworkManager.Dispose();
1365 CloseDisplayConnection();
1367 e.Cancel = iClosing;
1370 public void StartServer()
1372 iServiceHost = new ServiceHost
1375 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1378 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1379 iServiceHost.Open();
1382 public void StopServer()
1384 if (iClients.Count > 0 && !iClosing)
1388 BroadcastCloseEvent();
1392 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1394 iClosing = false; //We make sure we force close if asked twice
1399 //We removed that as it often lags for some reason
1400 //iServiceHost.Close();
1404 public void BroadcastCloseEvent()
1406 Trace.TraceInformation("BroadcastCloseEvent - start");
1408 var inactiveClients = new List<string>();
1409 foreach (var client in iClients)
1411 //if (client.Key != eventData.ClientName)
1415 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1416 client.Value.Callback.OnCloseOrder(/*eventData*/);
1418 catch (Exception ex)
1420 inactiveClients.Add(client.Key);
1425 if (inactiveClients.Count > 0)
1427 foreach (var client in inactiveClients)
1429 iClients.Remove(client);
1430 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(client, false)[0]);
1434 if (iClients.Count==0)
1441 /// Just remove all our fields.
1443 private void ClearLayout()
1445 iTableLayoutPanel.Controls.Clear();
1446 iTableLayoutPanel.RowStyles.Clear();
1447 iTableLayoutPanel.ColumnStyles.Clear();
1448 iCurrentClientData = null;
1452 /// Just launch a demo client.
1454 private void StartNewClient(string aTopText = "", string aBottomText = "")
1456 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1457 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1458 clientThread.Start(myParams);
1463 /// Just launch our idle client.
1465 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1467 Thread clientThread = new Thread(SharpDisplayIdleClient.Program.MainWithParams);
1468 SharpDisplayIdleClient.StartParams myParams = new SharpDisplayIdleClient.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1469 clientThread.Start(myParams);
1474 private void buttonStartClient_Click(object sender, EventArgs e)
1479 private void buttonSuspend_Click(object sender, EventArgs e)
1484 private void StartTimer()
1486 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1487 timer.Enabled = true;
1488 UpdateSuspendButton();
1491 private void StopTimer()
1493 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1494 timer.Enabled = false;
1495 UpdateSuspendButton();
1498 private void ToggleTimer()
1500 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1501 timer.Enabled = !timer.Enabled;
1502 UpdateSuspendButton();
1505 private void UpdateSuspendButton()
1509 buttonSuspend.Text = "Run";
1513 buttonSuspend.Text = "Pause";
1518 private void buttonCloseClients_Click(object sender, EventArgs e)
1520 BroadcastCloseEvent();
1523 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1525 //Root node must have at least one child
1526 if (e.Node.Nodes.Count == 0)
1531 //If the selected node is the root node of a client then switch to it
1532 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1533 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1535 //We have a valid session just switch to that client
1536 SetCurrentClient(sessionId,true);
1545 /// <param name="aSessionId"></param>
1546 /// <param name="aCallback"></param>
1547 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1549 if (this.InvokeRequired)
1551 //Not in the proper thread, invoke ourselves
1552 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1553 this.Invoke(d, new object[] { aSessionId, aCallback });
1557 //We are in the proper thread
1558 //Add this session to our collection of clients
1559 ClientData newClient = new ClientData(aSessionId, aCallback);
1560 Program.iMainForm.iClients.Add(aSessionId, newClient);
1561 //Add this session to our UI
1562 UpdateClientTreeViewNode(newClient);
1568 /// Find the client with the highest priority if any.
1570 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1571 public ClientData FindHighestPriorityClient()
1573 ClientData highestPriorityClient = null;
1574 foreach (var client in iClients)
1576 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1578 highestPriorityClient = client.Value;
1582 return highestPriorityClient;
1588 /// <param name="aSessionId"></param>
1589 public void RemoveClientThreadSafe(string aSessionId)
1591 if (this.InvokeRequired)
1593 //Not in the proper thread, invoke ourselves
1594 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1595 this.Invoke(d, new object[] { aSessionId });
1599 //We are in the proper thread
1600 //Remove this session from both client collection and UI tree view
1601 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1603 Program.iMainForm.iClients.Remove(aSessionId);
1604 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1605 //Update recording status too whenever a client is removed
1606 UpdateRecordingNotification();
1609 if (iCurrentClientSessionId == aSessionId)
1611 //The current client is closing
1612 iCurrentClientData = null;
1613 //Find the client with the highest priority and set it as current
1614 ClientData newCurrentClient = FindHighestPriorityClient();
1615 if (newCurrentClient!=null)
1617 SetCurrentClient(newCurrentClient.SessionId, true);
1621 if (iClients.Count == 0)
1623 //Clear our screen when last client disconnects
1628 //We were closing our form
1629 //All clients are now closed
1630 //Just resume our close operation
1641 /// <param name="aSessionId"></param>
1642 /// <param name="aLayout"></param>
1643 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1645 if (this.InvokeRequired)
1647 //Not in the proper thread, invoke ourselves
1648 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1649 this.Invoke(d, new object[] { aSessionId, aLayout });
1653 ClientData client = iClients[aSessionId];
1656 //Don't change a thing if the layout is the same
1657 if (!client.Layout.IsSameAs(aLayout))
1659 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1660 //Set our client layout then
1661 client.Layout = aLayout;
1662 //So that next time we update all our fields at ones
1663 client.HasNewLayout = true;
1664 //Layout has changed clear our fields then
1665 client.Fields.Clear();
1667 UpdateClientTreeViewNode(client);
1671 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1680 /// <param name="aSessionId"></param>
1681 /// <param name="aField"></param>
1682 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1684 if (this.InvokeRequired)
1686 //Not in the proper thread, invoke ourselves
1687 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1688 this.Invoke(d, new object[] { aSessionId, aField });
1692 //We are in the proper thread
1693 //Call the non-thread-safe variant
1694 SetClientField(aSessionId, aField);
1702 /// Set a data field in the given client.
1704 /// <param name="aSessionId"></param>
1705 /// <param name="aField"></param>
1706 private void SetClientField(string aSessionId, DataField aField)
1708 //TODO: should check if the field actually changed?
1710 ClientData client = iClients[aSessionId];
1711 bool layoutChanged = false;
1712 bool contentChanged = true;
1714 //Fetch our field index
1715 int fieldIndex = client.FindSameFieldIndex(aField);
1719 //No corresponding field, just bail out
1723 //Keep our previous field in there
1724 DataField previousField = client.Fields[fieldIndex];
1725 //Just update that field then
1726 client.Fields[fieldIndex] = aField;
1728 if (!aField.IsTableField)
1730 //We are done then if that field is not in our table layout
1734 TableField tableField = (TableField) aField;
1736 if (previousField.IsSameLayout(aField))
1738 //If we are updating a field in our current client we need to update it in our panel
1739 if (aSessionId == iCurrentClientSessionId)
1741 Control ctrl=iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1742 if (aField.IsTextField && ctrl is MarqueeLabel)
1744 TextField textField=(TextField)aField;
1745 //Text field control already in place, just change the text
1746 MarqueeLabel label = (MarqueeLabel)ctrl;
1747 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1748 label.Text = textField.Text;
1749 label.TextAlign = textField.Alignment;
1751 else if (aField.IsBitmapField && ctrl is PictureBox)
1753 BitmapField bitmapField = (BitmapField)aField;
1754 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1755 //Bitmap field control already in place just change the bitmap
1756 PictureBox pictureBox = (PictureBox)ctrl;
1757 pictureBox.Image = bitmapField.Bitmap;
1761 layoutChanged = true;
1767 layoutChanged = true;
1770 //If either content or layout changed we need to update our tree view to reflect the changes
1771 if (contentChanged || layoutChanged)
1773 UpdateClientTreeViewNode(client);
1777 Debug.Print("Layout changed");
1778 //Our layout has changed, if we are already the current client we need to update our panel
1779 if (aSessionId == iCurrentClientSessionId)
1781 //Apply layout and set data fields.
1782 UpdateTableLayoutPanel(iCurrentClientData);
1787 Debug.Print("Layout has not changed.");
1792 Debug.Print("WARNING: content and layout have not changed!");
1795 //When a client field is set we try switching to this client to present the new information to our user
1796 SetCurrentClient(aSessionId);
1802 /// <param name="aTexts"></param>
1803 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1805 if (this.InvokeRequired)
1807 //Not in the proper thread, invoke ourselves
1808 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1809 this.Invoke(d, new object[] { aSessionId, aFields });
1813 ClientData client = iClients[aSessionId];
1815 if (client.HasNewLayout)
1817 //TODO: Assert client.Count == 0
1818 //Our layout was just changed
1819 //Do some special handling to avoid re-creating our panel N times, once for each fields
1820 client.HasNewLayout = false;
1821 //Just set all our fields then
1822 client.Fields.AddRange(aFields);
1823 //Try switch to that client
1824 SetCurrentClient(aSessionId);
1826 //If we are updating the current client update our panel
1827 if (aSessionId == iCurrentClientSessionId)
1829 //Apply layout and set data fields.
1830 UpdateTableLayoutPanel(iCurrentClientData);
1833 UpdateClientTreeViewNode(client);
1837 //Put each our text fields in a label control
1838 foreach (DataField field in aFields)
1840 SetClientField(aSessionId, field);
1849 /// <param name="aSessionId"></param>
1850 /// <param name="aName"></param>
1851 public void SetClientNameThreadSafe(string aSessionId, string aName)
1853 if (this.InvokeRequired)
1855 //Not in the proper thread, invoke ourselves
1856 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1857 this.Invoke(d, new object[] { aSessionId, aName });
1861 //We are in the proper thread
1863 ClientData client = iClients[aSessionId];
1867 client.Name = aName;
1868 //Update our tree-view
1869 UpdateClientTreeViewNode(client);
1875 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1877 if (this.InvokeRequired)
1879 //Not in the proper thread, invoke ourselves
1880 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1881 this.Invoke(d, new object[] { aSessionId, aPriority });
1885 //We are in the proper thread
1887 ClientData client = iClients[aSessionId];
1891 client.Priority = aPriority;
1892 //Update our tree-view
1893 UpdateClientTreeViewNode(client);
1894 //Change our current client as per new priority
1895 ClientData newCurrentClient = FindHighestPriorityClient();
1896 if (newCurrentClient!=null)
1898 SetCurrentClient(newCurrentClient.SessionId);
1907 /// <param name="value"></param>
1908 /// <param name="maxChars"></param>
1909 /// <returns></returns>
1910 public static string Truncate(string value, int maxChars)
1912 return value.Length <= maxChars ? value : value.Substring(0, maxChars-3) + "...";
1916 /// Update our recording notification.
1918 private void UpdateRecordingNotification()
1921 bool activeRecording = false;
1923 RecordingField recField=new RecordingField();
1924 foreach (var client in iClients)
1926 RecordingField rec=(RecordingField)client.Value.FindSameFieldAs(recField);
1927 if (rec!=null && rec.IsActive)
1929 activeRecording = true;
1930 //Don't break cause we are collecting the names/texts.
1931 if (!String.IsNullOrEmpty(rec.Text))
1933 text += (rec.Text + "\n");
1937 //Not text for that recording, use client name instead
1938 text += client.Value.Name + " recording\n";
1944 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1945 iRecordingNotification.Text = Truncate(text,63);
1947 //Change visibility of notification if needed
1948 if (iRecordingNotification.Visible != activeRecording)
1950 iRecordingNotification.Visible = activeRecording;
1951 //Assuming the notification icon is in sync with our display icon
1952 //Take care of our REC icon
1953 if (iDisplay.IsOpen())
1955 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
1963 /// <param name="aClient"></param>
1964 private void UpdateClientTreeViewNode(ClientData aClient)
1966 Debug.Print("UpdateClientTreeViewNode");
1968 if (aClient == null)
1973 //Hook in record icon update too
1974 UpdateRecordingNotification();
1976 TreeNode node = null;
1977 //Check that our client node already exists
1978 //Get our client root node using its key which is our session ID
1979 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
1980 if (nodes.Count()>0)
1982 //We already have a node for that client
1984 //Clear children as we are going to recreate them below
1989 //Client node does not exists create a new one
1990 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1991 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
1997 if (!String.IsNullOrEmpty(aClient.Name))
1999 //We have a name, use it as text for our root node
2000 node.Text = aClient.Name;
2001 //Add a child with SessionId
2002 node.Nodes.Add(new TreeNode(aClient.SessionId));
2006 //No name, use session ID instead
2007 node.Text = aClient.SessionId;
2010 //Display client priority
2011 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2013 if (aClient.Fields.Count > 0)
2015 //Create root node for our texts
2016 TreeNode textsRoot = new TreeNode("Fields");
2017 node.Nodes.Add(textsRoot);
2018 //For each text add a new entry
2019 foreach (DataField field in aClient.Fields)
2021 if (field.IsTextField)
2023 TextField textField = (TextField)field;
2024 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2026 else if (field.IsBitmapField)
2028 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2030 else if (field.IsRecordingField)
2032 RecordingField recordingField = (RecordingField)field;
2033 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2043 /// Update our table layout row styles to make sure each rows have similar height
2045 private void UpdateTableLayoutRowStyles()
2047 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2049 rowStyle.SizeType = SizeType.Percent;
2050 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
2055 /// Update our display table layout.
2056 /// Will instanciated every field control as defined by our client.
2057 /// Fields must be specified by rows from the left.
2059 /// <param name="aLayout"></param>
2060 private void UpdateTableLayoutPanel(ClientData aClient)
2062 Debug.Print("UpdateTableLayoutPanel");
2064 if (aClient == null)
2071 TableLayout layout = aClient.Layout;
2073 //First clean our current panel
2074 iTableLayoutPanel.Controls.Clear();
2075 iTableLayoutPanel.RowStyles.Clear();
2076 iTableLayoutPanel.ColumnStyles.Clear();
2077 iTableLayoutPanel.RowCount = 0;
2078 iTableLayoutPanel.ColumnCount = 0;
2080 //Then recreate our rows...
2081 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2083 iTableLayoutPanel.RowCount++;
2087 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2089 iTableLayoutPanel.ColumnCount++;
2093 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2095 //Create our column styles
2096 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2099 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2103 //Create our row styles
2104 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2114 foreach (DataField field in aClient.Fields)
2116 if (!field.IsTableField)
2118 //That field is not taking part in our table layout skip it
2122 TableField tableField = (TableField)field;
2124 //Create a control corresponding to the field specified for that cell
2125 Control control = CreateControlForDataField(tableField);
2127 //Add newly created control to our table layout at the specified row and column
2128 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2129 //Make sure we specify column and row span for that new control
2130 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2131 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2139 /// Check our type of data field and create corresponding control
2141 /// <param name="aField"></param>
2142 private Control CreateControlForDataField(DataField aField)
2144 Control control=null;
2145 if (aField.IsTextField)
2147 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2148 label.AutoEllipsis = true;
2149 label.AutoSize = true;
2150 label.BackColor = System.Drawing.Color.Transparent;
2151 label.Dock = System.Windows.Forms.DockStyle.Fill;
2152 label.Location = new System.Drawing.Point(1, 1);
2153 label.Margin = new System.Windows.Forms.Padding(0);
2154 label.Name = "marqueeLabel" + aField;
2155 label.OwnTimer = false;
2156 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2157 label.Separator = cds.Separator;
2158 label.MinFontSize = cds.MinFontSize;
2159 label.ScaleToFit = cds.ScaleToFit;
2160 //control.Size = new System.Drawing.Size(254, 30);
2161 //control.TabIndex = 2;
2162 label.Font = cds.Font;
2164 TextField field = (TextField)aField;
2165 label.TextAlign = field.Alignment;
2166 label.UseCompatibleTextRendering = true;
2167 label.Text = field.Text;
2171 else if (aField.IsBitmapField)
2173 //Create picture box
2174 PictureBox picture = new PictureBox();
2175 picture.AutoSize = true;
2176 picture.BackColor = System.Drawing.Color.Transparent;
2177 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2178 picture.Location = new System.Drawing.Point(1, 1);
2179 picture.Margin = new System.Windows.Forms.Padding(0);
2180 picture.Name = "pictureBox" + aField;
2182 BitmapField field = (BitmapField)aField;
2183 picture.Image = field.Bitmap;
2187 //TODO: Handle recording field?
2193 /// Called when the user selected a new display type.
2195 /// <param name="sender"></param>
2196 /// <param name="e"></param>
2197 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2199 //Store the selected display type in our settings
2200 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2201 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2202 Properties.Settings.Default.Save();
2204 //Try re-opening the display connection if we were already connected.
2205 //Otherwise just update our status to reflect display type change.
2206 if (iDisplay.IsOpen())
2208 OpenDisplayConnection();
2216 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2218 if (maskedTextBoxTimerInterval.Text != "")
2220 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2224 timer.Interval = interval;
2225 cds.TimerInterval = timer.Interval;
2226 Properties.Settings.Default.Save();
2231 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2233 if (maskedTextBoxMinFontSize.Text != "")
2235 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2237 if (minFontSize > 0)
2239 cds.MinFontSize = minFontSize;
2240 Properties.Settings.Default.Save();
2241 //We need to recreate our layout for that change to take effect
2242 UpdateTableLayoutPanel(iCurrentClientData);
2248 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2250 if (maskedTextBoxScrollingSpeed.Text != "")
2252 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2254 if (scrollingSpeed > 0)
2256 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2257 Properties.Settings.Default.Save();
2258 //We need to recreate our layout for that change to take effect
2259 UpdateTableLayoutPanel(iCurrentClientData);
2264 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2266 cds.Separator = textBoxScrollLoopSeparator.Text;
2267 Properties.Settings.Default.Save();
2269 //Update our text fields
2270 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2272 ctrl.Separator = cds.Separator;
2277 private void buttonPowerOn_Click(object sender, EventArgs e)
2282 private void buttonPowerOff_Click(object sender, EventArgs e)
2284 iDisplay.PowerOff();
2287 private void buttonShowClock_Click(object sender, EventArgs e)
2292 private void buttonHideClock_Click(object sender, EventArgs e)
2297 private void buttonUpdate_Click(object sender, EventArgs e)
2299 InstallUpdateSyncWithInfo();
2307 if (!iDisplay.IsOpen())
2312 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2313 iSkipFrameRendering = true;
2316 iDisplay.SwapBuffers();
2317 //Then show our clock
2318 iDisplay.ShowClock();
2326 if (!iDisplay.IsOpen())
2331 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2332 iSkipFrameRendering = false;
2333 iDisplay.HideClock();
2336 private void InstallUpdateSyncWithInfo()
2338 UpdateCheckInfo info = null;
2340 if (ApplicationDeployment.IsNetworkDeployed)
2342 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2346 info = ad.CheckForDetailedUpdate();
2349 catch (DeploymentDownloadException dde)
2351 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);
2354 catch (InvalidDeploymentException ide)
2356 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);
2359 catch (InvalidOperationException ioe)
2361 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2365 if (info.UpdateAvailable)
2367 Boolean doUpdate = true;
2369 if (!info.IsUpdateRequired)
2371 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2372 if (!(DialogResult.OK == dr))
2379 // Display a message that the application MUST reboot. Display the minimum required version.
2380 MessageBox.Show("This application has detected a mandatory update from your current " +
2381 "version to version " + info.MinimumRequiredVersion.ToString() +
2382 ". The application will now install the update and restart.",
2383 "Update Available", MessageBoxButtons.OK,
2384 MessageBoxIcon.Information);
2392 MessageBox.Show("The application has been upgraded, and will now restart.");
2393 Application.Restart();
2395 catch (DeploymentDownloadException dde)
2397 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2404 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2413 private void SysTrayHideShow()
2419 WindowState = FormWindowState.Normal;
2424 /// Use to handle minimize events.
2426 /// <param name="sender"></param>
2427 /// <param name="e"></param>
2428 private void MainForm_SizeChanged(object sender, EventArgs e)
2430 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2442 /// <param name="sender"></param>
2443 /// <param name="e"></param>
2444 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2446 //Our table layout size has changed which means our display size has changed.
2447 //We need to re-create our bitmap.
2448 iCreateBitmap = true;
2454 /// <param name="sender"></param>
2455 /// <param name="e"></param>
2456 private void buttonSelectFile_Click(object sender, EventArgs e)
2458 //openFileDialog1.InitialDirectory = "c:\\";
2459 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2460 //openFileDialog.FilterIndex = 1;
2461 openFileDialog.RestoreDirectory = true;
2463 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2465 labelStartFileName.Text = openFileDialog.FileName;
2466 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2467 Properties.Settings.Default.Save();
2474 /// <param name="sender"></param>
2475 /// <param name="e"></param>
2476 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2478 //Save the optical drive the user selected for ejection
2479 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2480 Properties.Settings.Default.Save();
2484 /// Broadcast messages to subscribers.
2486 /// <param name="message"></param>
2487 protected override void WndProc(ref Message aMessage)
2489 if (iWriter != null)
2491 iWriter.FlushAccumulator();
2494 if (OnWndProc!=null)
2496 OnWndProc(ref aMessage);
2499 base.WndProc(ref aMessage);
2502 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2504 //Save CEC enabled status
2505 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2506 Properties.Settings.Default.Save();
2511 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2513 //Save CEC HDMI port
2514 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2515 Properties.Settings.Default.CecHdmiPort++;
2516 Properties.Settings.Default.Save();
2521 private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
2523 Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
2524 Properties.Settings.Default.Save();
2529 private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
2531 Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
2532 Properties.Settings.Default.Save();
2537 private void checkBoxCecReconnectToPowerTv_CheckedChanged(object sender, EventArgs e)
2539 Properties.Settings.Default.CecReconnectToPowerTv = checkBoxCecReconnectToPowerTv.Checked;
2540 Properties.Settings.Default.Save();
2548 private void ResetCec()
2550 if (iCecManager==null)
2552 //Thus skipping initial UI setup
2558 if (Properties.Settings.Default.CecEnabled)
2560 iCecManager.Start(Handle, "CEC",
2561 Properties.Settings.Default.CecHdmiPort,
2562 Properties.Settings.Default.CecMonitorOn,
2563 Properties.Settings.Default.CecMonitorOff,
2564 Properties.Settings.Default.CecReconnectToPowerTv);
2568 private void ButtonStartIdleClient_Click(object sender, EventArgs e)