Prototype for event and action framework.
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;
45 using SharpDisplayClient;
47 using MiniDisplayInterop;
48 using SharpLib.Display;
51 namespace SharpDisplayManager
54 public delegate uint ColorProcessingDelegate(int aX, int aY, uint aPixel);
55 public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
56 //Delegates are used for our thread safe method
57 public delegate void AddClientDelegate(string aSessionId, ICallback aCallback);
58 public delegate void RemoveClientDelegate(string aSessionId);
59 public delegate void SetFieldDelegate(string SessionId, DataField aField);
60 public delegate void SetFieldsDelegate(string SessionId, System.Collections.Generic.IList<DataField> aFields);
61 public delegate void SetLayoutDelegate(string SessionId, TableLayout aLayout);
62 public delegate void SetClientNameDelegate(string aSessionId, string aName);
63 public delegate void SetClientPriorityDelegate(string aSessionId, uint aPriority);
64 public delegate void PlainUpdateDelegate();
65 public delegate void WndProcDelegate(ref Message aMessage);
68 /// Our Display manager main form
70 [System.ComponentModel.DesignerCategory("Form")]
71 public partial class MainForm : MainFormHid, IMMNotificationClient
73 private Manager iManager = new Manager();
74 DateTime LastTickTime;
76 System.Drawing.Bitmap iBmp;
77 bool iCreateBitmap; //Workaround render to bitmap issues when minimized
78 ServiceHost iServiceHost;
79 // Our collection of clients sorted by session id.
80 public Dictionary<string, ClientData> iClients;
81 // The name of the client which informations are currently displayed.
82 public string iCurrentClientSessionId;
83 ClientData iCurrentClientData;
87 public bool iSkipFrameRendering;
88 //Function pointer for pixel color filtering
89 ColorProcessingDelegate iColorFx;
90 //Function pointer for pixel X coordinate intercept
91 CoordinateTranslationDelegate iScreenX;
92 //Function pointer for pixel Y coordinate intercept
93 CoordinateTranslationDelegate iScreenY;
95 private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
96 private MMDevice iMultiMediaDevice;
98 private NetworkManager iNetworkManager;
101 /// CEC - Consumer Electronic Control.
102 /// Notably used to turn TV on and off as Windows broadcast monitor on and off notifications.
104 private ConsumerElectronicControl iCecManager;
107 /// Manage run when Windows startup option
109 private StartupManager iStartupManager;
112 /// System notification icon used to hide our application from the task bar.
114 private SharpLib.Notification.Control iNotifyIcon;
117 /// System recording notification icon.
119 private SharpLib.Notification.Control iRecordingNotification;
124 RichTextBoxTextWriter iWriter;
128 /// Allow user to receive window messages;
130 public event WndProcDelegate OnWndProc;
134 iSkipFrameRendering = false;
136 iCurrentClientSessionId = "";
137 iCurrentClientData = null;
138 LastTickTime = DateTime.Now;
139 //Instantiate our display and register for events notifications
140 iDisplay = new Display();
141 iDisplay.OnOpened += OnDisplayOpened;
142 iDisplay.OnClosed += OnDisplayClosed;
144 iClients = new Dictionary<string, ClientData>();
145 iStartupManager = new StartupManager();
146 iNotifyIcon = new SharpLib.Notification.Control();
147 iRecordingNotification = new SharpLib.Notification.Control();
149 //Have our designer initialize its controls
150 InitializeComponent();
152 //Redirect console output
153 iWriter = new RichTextBoxTextWriter(richTextBoxLogs);
154 Console.SetOut(iWriter);
156 //Populate device types
157 PopulateDeviceTypes();
159 //Populate optical drives
160 PopulateOpticalDrives();
162 //Initial status update
165 //We have a bug when drawing minimized and reusing our bitmap
166 //Though I could not reproduce it on Windows 10
167 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
168 iCreateBitmap = false;
170 //Minimize our window if desired
171 if (Properties.Settings.Default.StartMinimized)
173 WindowState = FormWindowState.Minimized;
181 /// <param name="sender"></param>
182 /// <param name="e"></param>
183 private void MainForm_Load(object sender, EventArgs e)
185 //Check if we are running a Click Once deployed application
186 if (ApplicationDeployment.IsNetworkDeployed)
188 //This is a proper Click Once installation, fetch and show our version number
189 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
193 //Not a proper Click Once installation, assuming development build then
194 this.Text += " - development";
198 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
199 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
200 UpdateAudioDeviceAndMasterVolumeThreadSafe();
203 iNetworkManager = new NetworkManager();
204 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
205 UpdateNetworkStatus();
208 iCecManager = new ConsumerElectronicControl();
209 OnWndProc += iCecManager.OnWndProc;
213 //Setup notification icon
216 //Setup recording notification
217 SetupRecordingNotification();
219 // To make sure start up with minimize to tray works
220 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
226 //When not debugging we want the screen to be empty until a client takes over
229 //When developing we want at least one client for testing
230 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
233 //Open display connection on start-up if needed
234 if (Properties.Settings.Default.DisplayConnectOnStartup)
236 OpenDisplayConnection();
239 //Start our server so that we can get client requests
242 //Register for HID events
243 RegisterHidDevices();
245 //Start Idle client if needed
246 if (Properties.Settings.Default.StartIdleClient)
253 /// Called when our display is opened.
255 /// <param name="aDisplay"></param>
256 private void OnDisplayOpened(Display aDisplay)
258 //Make sure we resume frame rendering
259 iSkipFrameRendering = false;
261 //Set our screen size now that our display is connected
262 //Our panelDisplay is the container of our tableLayoutPanel
263 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
264 //panelDisplay needs an extra 2 pixels for borders on each sides
265 //tableLayoutPanel will eventually be the exact size of our display
266 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
267 panelDisplay.Size = size;
269 //Our display was just opened, update our UI
271 //Initiate asynchronous request
272 iDisplay.RequestFirmwareRevision();
275 UpdateMasterVolumeThreadSafe();
277 UpdateNetworkStatus();
280 //Testing icon in debug, no arm done if icon not supported
281 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
282 //iDisplay.SetAllIconsStatus(2);
288 /// Called when our display is closed.
290 /// <param name="aDisplay"></param>
291 private void OnDisplayClosed(Display aDisplay)
293 //Our display was just closed, update our UI consequently
297 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
299 //Update network status
300 UpdateNetworkStatus();
304 /// Update our Network Status
306 private void UpdateNetworkStatus()
308 if (iDisplay.IsOpen())
310 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
311 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
316 int iLastNetworkIconIndex = 0;
317 int iUpdateCountSinceLastNetworkAnimation = 0;
322 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
324 iUpdateCountSinceLastNetworkAnimation++;
325 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
327 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
329 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
332 //Prevents div by zero and other undefined behavior
335 iLastNetworkIconIndex++;
336 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
337 for (int i=0;i<iconCount;i++)
339 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
341 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
345 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
354 /// Receive volume change notification and reflect changes on our slider.
356 /// <param name="data"></param>
357 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
359 UpdateMasterVolumeThreadSafe();
363 /// Update master volume when user moves our slider.
365 /// <param name="sender"></param>
366 /// <param name="e"></param>
367 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
369 //Just like Windows Volume Mixer we unmute if the volume is adjusted
370 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
371 //Set volume level according to our volume slider new position
372 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
377 /// Mute check box changed.
379 /// <param name="sender"></param>
380 /// <param name="e"></param>
381 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
383 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
387 /// Device State Changed
389 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
394 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
399 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
402 /// Default Device Changed
404 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
406 if (role == Role.Multimedia && flow == DataFlow.Render)
408 UpdateAudioDeviceAndMasterVolumeThreadSafe();
413 /// Property Value Changed
415 /// <param name="pwstrDeviceId"></param>
416 /// <param name="key"></param>
417 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
423 /// Update master volume indicators based our current system states.
424 /// This typically includes volume levels and mute status.
426 private void UpdateMasterVolumeThreadSafe()
428 if (this.InvokeRequired)
430 //Not in the proper thread, invoke ourselves
431 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
432 this.Invoke(d, new object[] { });
436 //Update volume slider
437 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
438 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
439 //Update mute checkbox
440 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
442 //If our display connection is open we need to update its icons
443 if (iDisplay.IsOpen())
445 //First take care our our volume level icons
446 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
447 if (volumeIconCount > 0)
449 //Compute current volume level from system level and the number of segments in our display volume bar.
450 //That tells us how many segments in our volume bar needs to be turned on.
451 float currentVolume = volumeLevelScalar * volumeIconCount;
452 int segmentOnCount = Convert.ToInt32(currentVolume);
453 //Check if our segment count was rounded up, this will later be used for half brightness segment
454 bool roundedUp = segmentOnCount > currentVolume;
456 for (int i = 0; i < volumeIconCount; i++)
458 if (i < segmentOnCount)
460 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
461 if (i == segmentOnCount - 1 && roundedUp)
464 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
469 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
474 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
479 //Take care of our mute icon
480 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
488 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
490 if (this.InvokeRequired)
492 //Not in the proper thread, invoke ourselves
493 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
494 this.Invoke(d, new object[] { });
498 //We are in the correct thread just go ahead.
501 //Get our master volume
502 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
504 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
506 //Show our volume in our track bar
507 UpdateMasterVolumeThreadSafe();
509 //Register to get volume modifications
510 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
512 trackBarMasterVolume.Enabled = true;
516 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
517 Debug.WriteLine(ex.ToString());
518 //Something went wrong S/PDIF device ca throw exception I guess
519 trackBarMasterVolume.Enabled = false;
526 private void PopulateDeviceTypes()
528 int count = Display.TypeCount();
530 for (int i = 0; i < count; i++)
532 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
539 private void PopulateOpticalDrives()
541 //Reset our list of drives
542 comboBoxOpticalDrives.Items.Clear();
543 comboBoxOpticalDrives.Items.Add("None");
545 //Go through each drives on our system and collected the optical ones in our list
546 DriveInfo[] allDrives = DriveInfo.GetDrives();
547 foreach (DriveInfo d in allDrives)
549 Debug.WriteLine("Drive " + d.Name);
550 Debug.WriteLine(" Drive type: {0}", d.DriveType);
552 if (d.DriveType==DriveType.CDRom)
554 //This is an optical drive, add it now
555 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
563 /// <returns></returns>
564 public string OpticalDriveToEject()
566 return comboBoxOpticalDrives.SelectedItem.ToString();
574 private void SetupTrayIcon()
576 iNotifyIcon.Icon = GetIcon("vfd.ico");
577 iNotifyIcon.Text = "Sharp Display Manager";
578 iNotifyIcon.Visible = true;
580 //Double click toggles visibility - typically brings up the application
581 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
586 //Adding a context menu, useful to be able to exit the application
587 ContextMenu contextMenu = new ContextMenu();
588 //Context menu item to toggle visibility
589 MenuItem hideShowItem = new MenuItem("Hide/Show");
590 hideShowItem.Click += delegate(object obj, EventArgs args)
594 contextMenu.MenuItems.Add(hideShowItem);
596 //Context menu item separator
597 contextMenu.MenuItems.Add(new MenuItem("-"));
599 //Context menu exit item
600 MenuItem exitItem = new MenuItem("Exit");
601 exitItem.Click += delegate(object obj, EventArgs args)
605 contextMenu.MenuItems.Add(exitItem);
607 iNotifyIcon.ContextMenu = contextMenu;
613 private void SetupRecordingNotification()
615 iRecordingNotification.Icon = GetIcon("record.ico");
616 iRecordingNotification.Text = "No recording";
617 iRecordingNotification.Visible = false;
621 /// Access icons from embedded resources.
623 /// <param name="aName"></param>
624 /// <returns></returns>
625 public static Icon GetIcon(string aName)
627 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
628 foreach (string name in names)
630 //Find a resource name that ends with the given name
631 if (name.EndsWith(aName))
633 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
635 return new Icon(stream);
645 /// Set our current client.
646 /// This will take care of applying our client layout and set data fields.
648 /// <param name="aSessionId"></param>
649 void SetCurrentClient(string aSessionId, bool aForce=false)
651 if (aSessionId == iCurrentClientSessionId)
653 //Given client is already the current one.
654 //Don't bother changing anything then.
658 ClientData requestedClientData = iClients[aSessionId];
660 //Check when was the last time we switched to that client
661 if (iCurrentClientData != null)
663 //Do not switch client if priority of current client is higher
664 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
670 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
671 //TODO: put that hard coded value as a client property
672 //Clients should be able to define how often they can be interrupted
673 //Thus a background client can set this to zero allowing any other client to interrupt at any time
674 //We could also compute this delay by looking at the requests frequencies?
676 requestedClientData.Priority == iCurrentClientData.Priority && //Time sharing is only if clients have the same priority
677 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
679 //Don't switch clients too often
684 //Set current client ID.
685 iCurrentClientSessionId = aSessionId;
686 //Set the time we last switched to that client
687 iClients[aSessionId].LastSwitchTime = DateTime.Now;
688 //Fetch and set current client data.
689 iCurrentClientData = requestedClientData;
690 //Apply layout and set data fields.
691 UpdateTableLayoutPanel(iCurrentClientData);
694 private void buttonFont_Click(object sender, EventArgs e)
696 //fontDialog.ShowColor = true;
697 //fontDialog.ShowApply = true;
698 fontDialog.ShowEffects = true;
699 fontDialog.Font = cds.Font;
701 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
703 //fontDialog.ShowHelp = true;
705 //fontDlg.MaxSize = 40;
706 //fontDlg.MinSize = 22;
708 //fontDialog.Parent = this;
709 //fontDialog.StartPosition = FormStartPosition.CenterParent;
711 //DlgBox.ShowDialog(fontDialog);
713 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
714 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
716 //Set the fonts to all our labels in our layout
717 foreach (Control ctrl in iTableLayoutPanel.Controls)
719 if (ctrl is MarqueeLabel)
721 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
726 cds.Font = fontDialog.Font;
727 Properties.Settings.Default.Save();
736 void CheckFontHeight()
738 //Show font height and width
739 labelFontHeight.Text = "Font height: " + cds.Font.Height;
740 float charWidth = IsFixedWidth(cds.Font);
741 if (charWidth == 0.0f)
743 labelFontWidth.Visible = false;
747 labelFontWidth.Visible = true;
748 labelFontWidth.Text = "Font width: " + charWidth;
751 MarqueeLabel label = null;
752 //Get the first label control we can find
753 foreach (Control ctrl in iTableLayoutPanel.Controls)
755 if (ctrl is MarqueeLabel)
757 label = (MarqueeLabel)ctrl;
762 //Now check font height and show a warning if needed.
763 if (label != null && label.Font.Height > label.Height)
765 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
766 labelWarning.Visible = true;
770 labelWarning.Visible = false;
775 private void buttonCapture_Click(object sender, EventArgs e)
777 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
778 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
779 //Bitmap bmpToSave = new Bitmap(bmp);
780 bmp.Save("D:\\capture.png");
782 ((MarqueeLabel)iTableLayoutPanel.Controls[0]).Text = "Captured";
785 string outputFileName = "d:\\capture.png";
786 using (MemoryStream memory = new MemoryStream())
788 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
790 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
791 byte[] bytes = memory.ToArray();
792 fs.Write(bytes, 0, bytes.Length);
799 private void CheckForRequestResults()
801 if (iDisplay.IsRequestPending())
803 switch (iDisplay.AttemptRequestCompletion())
805 case MiniDisplay.Request.FirmwareRevision:
806 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
807 //Issue next request then
808 iDisplay.RequestPowerSupplyStatus();
811 case MiniDisplay.Request.PowerSupplyStatus:
812 if (iDisplay.PowerSupplyStatus())
814 toolStripStatusLabelPower.Text = "ON";
818 toolStripStatusLabelPower.Text = "OFF";
820 //Issue next request then
821 iDisplay.RequestDeviceId();
824 case MiniDisplay.Request.DeviceId:
825 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
826 //No more request to issue
832 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
834 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
841 public static uint ColorUntouched(int aX, int aY, uint aPixel)
846 public static uint ColorInversed(int aX, int aY, uint aPixel)
851 public static uint ColorChessboard(int aX, int aY, uint aPixel)
853 if ((aX % 2 == 0) && (aY % 2 == 0))
857 else if ((aX % 2 != 0) && (aY % 2 != 0))
865 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
867 return aBmp.Width - aX - 1;
870 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
872 return iBmp.Height - aY - 1;
875 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
880 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
886 /// Select proper pixel delegates according to our current settings.
888 private void SetupPixelDelegates()
890 //Select our pixel processing routine
891 if (cds.InverseColors)
893 //iColorFx = ColorChessboard;
894 iColorFx = ColorInversed;
898 iColorFx = ColorWhiteIsOn;
901 //Select proper coordinate translation functions
902 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
903 if (cds.ReverseScreen)
905 iScreenX = ScreenReversedX;
906 iScreenY = ScreenReversedY;
916 //This is our timer tick responsible to perform our render
917 private void timer_Tick(object sender, EventArgs e)
919 //Not ideal cause this has nothing to do with display render
922 //Update our animations
923 DateTime NewTickTime = DateTime.Now;
925 UpdateNetworkSignal(LastTickTime, NewTickTime);
927 //Update animation for all our marquees
928 foreach (Control ctrl in iTableLayoutPanel.Controls)
930 if (ctrl is MarqueeLabel)
932 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
937 if (iDisplay.IsOpen())
939 CheckForRequestResults();
941 //Check if frame rendering is needed
942 //Typically used when showing clock
943 if (!iSkipFrameRendering)
948 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
949 iCreateBitmap = false;
951 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
952 //iBmp.Save("D:\\capture.png");
954 //Send it to our display
955 for (int i = 0; i < iBmp.Width; i++)
957 for (int j = 0; j < iBmp.Height; j++)
961 //Get our processed pixel coordinates
962 int x = iScreenX(iBmp, i);
963 int y = iScreenY(iBmp, j);
965 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
966 //Apply color effects
967 color = iColorFx(x, y, color);
969 iDisplay.SetPixel(x, y, color);
974 iDisplay.SwapBuffers();
978 //Compute instant FPS
979 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
981 LastTickTime = NewTickTime;
986 /// Attempt to establish connection with our display hardware.
988 private void OpenDisplayConnection()
990 CloseDisplayConnection();
992 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
995 toolStripStatusLabelConnect.Text = "Connection error";
999 private void CloseDisplayConnection()
1001 //Status will be updated upon receiving the closed event
1003 if (iDisplay == null || !iDisplay.IsOpen())
1008 //Do not clear if we gave up on rendering already.
1009 //This means we will keep on displaying clock on MDM166AA for instance.
1010 if (!iSkipFrameRendering)
1013 iDisplay.SwapBuffers();
1016 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1020 private void buttonOpen_Click(object sender, EventArgs e)
1022 OpenDisplayConnection();
1025 private void buttonClose_Click(object sender, EventArgs e)
1027 CloseDisplayConnection();
1030 private void buttonClear_Click(object sender, EventArgs e)
1033 iDisplay.SwapBuffers();
1036 private void buttonFill_Click(object sender, EventArgs e)
1039 iDisplay.SwapBuffers();
1042 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1044 cds.Brightness = trackBarBrightness.Value;
1045 Properties.Settings.Default.Save();
1046 iDisplay.SetBrightness(trackBarBrightness.Value);
1052 /// CDS stands for Current Display Settings
1054 private DisplaySettings cds
1058 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1059 if (settings == null)
1061 settings = new DisplaysSettings();
1063 Properties.Settings.Default.DisplaysSettings = settings;
1066 //Make sure all our settings have been created
1067 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1069 settings.Displays.Add(new DisplaySettings());
1072 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1073 return displaySettings;
1078 /// Check if the given font has a fixed character pitch.
1080 /// <param name="ft"></param>
1081 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1082 public float IsFixedWidth(Font ft)
1084 Graphics g = CreateGraphics();
1085 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1086 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1088 bool fixedWidth = true;
1090 foreach (char c in charSizes)
1091 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1103 /// Synchronize UI with settings
1105 private void UpdateStatus()
1108 checkBoxShowBorders.Checked = cds.ShowBorders;
1109 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1111 //Set the proper font to each of our labels
1112 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1114 ctrl.Font = cds.Font;
1118 //Check if "run on Windows startup" is enabled
1119 checkBoxAutoStart.Checked = iStartupManager.Startup;
1121 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1122 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1123 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1124 iCheckBoxStartIdleClient.Checked = Properties.Settings.Default.StartIdleClient;
1125 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1128 //Try find our drive in our drive list
1129 int opticalDriveItemIndex=0;
1130 bool driveNotFound = true;
1131 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1132 foreach (object item in comboBoxOpticalDrives.Items)
1134 if (opticalDriveToEject == item.ToString())
1136 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1137 driveNotFound = false;
1140 opticalDriveItemIndex++;
1145 //We could not find the drive we had saved.
1146 //Select "None" then.
1147 comboBoxOpticalDrives.SelectedIndex = 0;
1151 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1152 checkBoxCecMonitorOn.Checked = Properties.Settings.Default.CecMonitorOn;
1153 checkBoxCecMonitorOff.Checked = Properties.Settings.Default.CecMonitorOff;
1154 checkBoxCecReconnectToPowerTv.Checked = Properties.Settings.Default.CecReconnectToPowerTv;
1155 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1157 //Mini Display settings
1158 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1159 checkBoxInverseColors.Checked = cds.InverseColors;
1160 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1161 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1162 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1163 labelMinFontSize.Enabled = cds.ScaleToFit;
1164 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1165 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1166 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1167 timer.Interval = cds.TimerInterval;
1168 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1169 textBoxScrollLoopSeparator.Text = cds.Separator;
1171 SetupPixelDelegates();
1173 if (iDisplay.IsOpen())
1175 //We have a display connection
1176 //Reflect that in our UI
1179 iTableLayoutPanel.Enabled = true;
1180 panelDisplay.Enabled = true;
1182 //Only setup brightness if display is open
1183 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1184 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1185 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1187 //Brightness out of range, this can occur when using auto-detect
1188 //Use max brightness instead
1189 trackBarBrightness.Value = iDisplay.MaxBrightness();
1190 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1194 trackBarBrightness.Value = cds.Brightness;
1195 iDisplay.SetBrightness(cds.Brightness);
1198 //Try compute the steps to something that makes sense
1199 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1200 trackBarBrightness.SmallChange = 1;
1203 buttonFill.Enabled = true;
1204 buttonClear.Enabled = true;
1205 buttonOpen.Enabled = false;
1206 buttonClose.Enabled = true;
1207 trackBarBrightness.Enabled = true;
1208 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1209 //+ " - " + iDisplay.SerialNumber();
1211 if (iDisplay.SupportPowerOnOff())
1213 buttonPowerOn.Enabled = true;
1214 buttonPowerOff.Enabled = true;
1218 buttonPowerOn.Enabled = false;
1219 buttonPowerOff.Enabled = false;
1222 if (iDisplay.SupportClock())
1224 buttonShowClock.Enabled = true;
1225 buttonHideClock.Enabled = true;
1229 buttonShowClock.Enabled = false;
1230 buttonHideClock.Enabled = false;
1234 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1235 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1237 if (cds.ShowVolumeLabel)
1239 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1243 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1248 //Display connection not available
1249 //Reflect that in our UI
1251 //In debug start our timer even if we don't have a display connection
1254 //In production environment we don't need our timer if no display connection
1257 checkBoxShowVolumeLabel.Enabled = false;
1258 iTableLayoutPanel.Enabled = false;
1259 panelDisplay.Enabled = false;
1260 buttonFill.Enabled = false;
1261 buttonClear.Enabled = false;
1262 buttonOpen.Enabled = true;
1263 buttonClose.Enabled = false;
1264 trackBarBrightness.Enabled = false;
1265 buttonPowerOn.Enabled = false;
1266 buttonPowerOff.Enabled = false;
1267 buttonShowClock.Enabled = false;
1268 buttonHideClock.Enabled = false;
1269 toolStripStatusLabelConnect.Text = "Disconnected";
1270 toolStripStatusLabelPower.Text = "N/A";
1279 /// <param name="sender"></param>
1280 /// <param name="e"></param>
1281 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1283 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1284 Properties.Settings.Default.Save();
1288 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1290 //Save our show borders setting
1291 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1292 cds.ShowBorders = checkBoxShowBorders.Checked;
1293 Properties.Settings.Default.Save();
1297 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1299 //Save our connect on startup setting
1300 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1301 Properties.Settings.Default.Save();
1304 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1306 //Save our "Minimize to tray" setting
1307 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1308 Properties.Settings.Default.Save();
1312 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1314 //Save our "Start minimized" setting
1315 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1316 Properties.Settings.Default.Save();
1319 private void checkBoxStartIdleClient_CheckedChanged(object sender, EventArgs e)
1321 Properties.Settings.Default.StartIdleClient = iCheckBoxStartIdleClient.Checked;
1322 Properties.Settings.Default.Save();
1325 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1327 iStartupManager.Startup = checkBoxAutoStart.Checked;
1331 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1333 //Save our reverse screen setting
1334 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1335 Properties.Settings.Default.Save();
1336 SetupPixelDelegates();
1339 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1341 //Save our inverse colors setting
1342 cds.InverseColors = checkBoxInverseColors.Checked;
1343 Properties.Settings.Default.Save();
1344 SetupPixelDelegates();
1347 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1349 //Save our scale to fit setting
1350 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1351 Properties.Settings.Default.Save();
1353 labelMinFontSize.Enabled = cds.ScaleToFit;
1354 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1357 private void MainForm_Resize(object sender, EventArgs e)
1359 if (WindowState == FormWindowState.Minimized)
1361 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1362 // That's apparently not needed on Windows 10 but we better leave it in place.
1363 iCreateBitmap = true;
1367 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1370 iNetworkManager.Dispose();
1371 CloseDisplayConnection();
1373 e.Cancel = iClosing;
1376 public void StartServer()
1378 iServiceHost = new ServiceHost
1381 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1384 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1385 iServiceHost.Open();
1388 public void StopServer()
1390 if (iClients.Count > 0 && !iClosing)
1394 BroadcastCloseEvent();
1398 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1400 iClosing = false; //We make sure we force close if asked twice
1405 //We removed that as it often lags for some reason
1406 //iServiceHost.Close();
1410 public void BroadcastCloseEvent()
1412 Trace.TraceInformation("BroadcastCloseEvent - start");
1414 var inactiveClients = new List<string>();
1415 foreach (var client in iClients)
1417 //if (client.Key != eventData.ClientName)
1421 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1422 client.Value.Callback.OnCloseOrder(/*eventData*/);
1424 catch (Exception ex)
1426 inactiveClients.Add(client.Key);
1431 if (inactiveClients.Count > 0)
1433 foreach (var client in inactiveClients)
1435 iClients.Remove(client);
1436 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(client, false)[0]);
1440 if (iClients.Count==0)
1447 /// Just remove all our fields.
1449 private void ClearLayout()
1451 iTableLayoutPanel.Controls.Clear();
1452 iTableLayoutPanel.RowStyles.Clear();
1453 iTableLayoutPanel.ColumnStyles.Clear();
1454 iCurrentClientData = null;
1458 /// Just launch a demo client.
1460 private void StartNewClient(string aTopText = "", string aBottomText = "")
1462 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1463 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1464 clientThread.Start(myParams);
1469 /// Just launch our idle client.
1471 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1473 Thread clientThread = new Thread(SharpDisplayIdleClient.Program.MainWithParams);
1474 SharpDisplayIdleClient.StartParams myParams = new SharpDisplayIdleClient.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1475 clientThread.Start(myParams);
1480 private void buttonStartClient_Click(object sender, EventArgs e)
1485 private void buttonSuspend_Click(object sender, EventArgs e)
1490 private void StartTimer()
1492 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1493 timer.Enabled = true;
1494 UpdateSuspendButton();
1497 private void StopTimer()
1499 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1500 timer.Enabled = false;
1501 UpdateSuspendButton();
1504 private void ToggleTimer()
1506 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1507 timer.Enabled = !timer.Enabled;
1508 UpdateSuspendButton();
1511 private void UpdateSuspendButton()
1515 buttonSuspend.Text = "Run";
1519 buttonSuspend.Text = "Pause";
1524 private void buttonCloseClients_Click(object sender, EventArgs e)
1526 BroadcastCloseEvent();
1529 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1531 //Root node must have at least one child
1532 if (e.Node.Nodes.Count == 0)
1537 //If the selected node is the root node of a client then switch to it
1538 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1539 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1541 //We have a valid session just switch to that client
1542 SetCurrentClient(sessionId,true);
1551 /// <param name="aSessionId"></param>
1552 /// <param name="aCallback"></param>
1553 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1555 if (this.InvokeRequired)
1557 //Not in the proper thread, invoke ourselves
1558 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1559 this.Invoke(d, new object[] { aSessionId, aCallback });
1563 //We are in the proper thread
1564 //Add this session to our collection of clients
1565 ClientData newClient = new ClientData(aSessionId, aCallback);
1566 Program.iMainForm.iClients.Add(aSessionId, newClient);
1567 //Add this session to our UI
1568 UpdateClientTreeViewNode(newClient);
1574 /// Find the client with the highest priority if any.
1576 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1577 public ClientData FindHighestPriorityClient()
1579 ClientData highestPriorityClient = null;
1580 foreach (var client in iClients)
1582 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1584 highestPriorityClient = client.Value;
1588 return highestPriorityClient;
1594 /// <param name="aSessionId"></param>
1595 public void RemoveClientThreadSafe(string aSessionId)
1597 if (this.InvokeRequired)
1599 //Not in the proper thread, invoke ourselves
1600 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1601 this.Invoke(d, new object[] { aSessionId });
1605 //We are in the proper thread
1606 //Remove this session from both client collection and UI tree view
1607 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1609 Program.iMainForm.iClients.Remove(aSessionId);
1610 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1611 //Update recording status too whenever a client is removed
1612 UpdateRecordingNotification();
1615 if (iCurrentClientSessionId == aSessionId)
1617 //The current client is closing
1618 iCurrentClientData = null;
1619 //Find the client with the highest priority and set it as current
1620 ClientData newCurrentClient = FindHighestPriorityClient();
1621 if (newCurrentClient!=null)
1623 SetCurrentClient(newCurrentClient.SessionId, true);
1627 if (iClients.Count == 0)
1629 //Clear our screen when last client disconnects
1634 //We were closing our form
1635 //All clients are now closed
1636 //Just resume our close operation
1647 /// <param name="aSessionId"></param>
1648 /// <param name="aLayout"></param>
1649 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1651 if (this.InvokeRequired)
1653 //Not in the proper thread, invoke ourselves
1654 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1655 this.Invoke(d, new object[] { aSessionId, aLayout });
1659 ClientData client = iClients[aSessionId];
1662 //Don't change a thing if the layout is the same
1663 if (!client.Layout.IsSameAs(aLayout))
1665 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1666 //Set our client layout then
1667 client.Layout = aLayout;
1668 //So that next time we update all our fields at ones
1669 client.HasNewLayout = true;
1670 //Layout has changed clear our fields then
1671 client.Fields.Clear();
1673 UpdateClientTreeViewNode(client);
1677 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1686 /// <param name="aSessionId"></param>
1687 /// <param name="aField"></param>
1688 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1690 if (this.InvokeRequired)
1692 //Not in the proper thread, invoke ourselves
1693 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1694 this.Invoke(d, new object[] { aSessionId, aField });
1698 //We are in the proper thread
1699 //Call the non-thread-safe variant
1700 SetClientField(aSessionId, aField);
1708 /// Set a data field in the given client.
1710 /// <param name="aSessionId"></param>
1711 /// <param name="aField"></param>
1712 private void SetClientField(string aSessionId, DataField aField)
1714 //TODO: should check if the field actually changed?
1716 ClientData client = iClients[aSessionId];
1717 bool layoutChanged = false;
1718 bool contentChanged = true;
1720 //Fetch our field index
1721 int fieldIndex = client.FindSameFieldIndex(aField);
1725 //No corresponding field, just bail out
1729 //Keep our previous field in there
1730 DataField previousField = client.Fields[fieldIndex];
1731 //Just update that field then
1732 client.Fields[fieldIndex] = aField;
1734 if (!aField.IsTableField)
1736 //We are done then if that field is not in our table layout
1740 TableField tableField = (TableField) aField;
1742 if (previousField.IsSameLayout(aField))
1744 //If we are updating a field in our current client we need to update it in our panel
1745 if (aSessionId == iCurrentClientSessionId)
1747 Control ctrl=iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1748 if (aField.IsTextField && ctrl is MarqueeLabel)
1750 TextField textField=(TextField)aField;
1751 //Text field control already in place, just change the text
1752 MarqueeLabel label = (MarqueeLabel)ctrl;
1753 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1754 label.Text = textField.Text;
1755 label.TextAlign = textField.Alignment;
1757 else if (aField.IsBitmapField && ctrl is PictureBox)
1759 BitmapField bitmapField = (BitmapField)aField;
1760 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1761 //Bitmap field control already in place just change the bitmap
1762 PictureBox pictureBox = (PictureBox)ctrl;
1763 pictureBox.Image = bitmapField.Bitmap;
1767 layoutChanged = true;
1773 layoutChanged = true;
1776 //If either content or layout changed we need to update our tree view to reflect the changes
1777 if (contentChanged || layoutChanged)
1779 UpdateClientTreeViewNode(client);
1783 Debug.Print("Layout changed");
1784 //Our layout has changed, if we are already the current client we need to update our panel
1785 if (aSessionId == iCurrentClientSessionId)
1787 //Apply layout and set data fields.
1788 UpdateTableLayoutPanel(iCurrentClientData);
1793 Debug.Print("Layout has not changed.");
1798 Debug.Print("WARNING: content and layout have not changed!");
1801 //When a client field is set we try switching to this client to present the new information to our user
1802 SetCurrentClient(aSessionId);
1808 /// <param name="aTexts"></param>
1809 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1811 if (this.InvokeRequired)
1813 //Not in the proper thread, invoke ourselves
1814 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1815 this.Invoke(d, new object[] { aSessionId, aFields });
1819 ClientData client = iClients[aSessionId];
1821 if (client.HasNewLayout)
1823 //TODO: Assert client.Count == 0
1824 //Our layout was just changed
1825 //Do some special handling to avoid re-creating our panel N times, once for each fields
1826 client.HasNewLayout = false;
1827 //Just set all our fields then
1828 client.Fields.AddRange(aFields);
1829 //Try switch to that client
1830 SetCurrentClient(aSessionId);
1832 //If we are updating the current client update our panel
1833 if (aSessionId == iCurrentClientSessionId)
1835 //Apply layout and set data fields.
1836 UpdateTableLayoutPanel(iCurrentClientData);
1839 UpdateClientTreeViewNode(client);
1843 //Put each our text fields in a label control
1844 foreach (DataField field in aFields)
1846 SetClientField(aSessionId, field);
1855 /// <param name="aSessionId"></param>
1856 /// <param name="aName"></param>
1857 public void SetClientNameThreadSafe(string aSessionId, string aName)
1859 if (this.InvokeRequired)
1861 //Not in the proper thread, invoke ourselves
1862 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1863 this.Invoke(d, new object[] { aSessionId, aName });
1867 //We are in the proper thread
1869 ClientData client = iClients[aSessionId];
1873 client.Name = aName;
1874 //Update our tree-view
1875 UpdateClientTreeViewNode(client);
1881 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1883 if (this.InvokeRequired)
1885 //Not in the proper thread, invoke ourselves
1886 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1887 this.Invoke(d, new object[] { aSessionId, aPriority });
1891 //We are in the proper thread
1893 ClientData client = iClients[aSessionId];
1897 client.Priority = aPriority;
1898 //Update our tree-view
1899 UpdateClientTreeViewNode(client);
1900 //Change our current client as per new priority
1901 ClientData newCurrentClient = FindHighestPriorityClient();
1902 if (newCurrentClient!=null)
1904 SetCurrentClient(newCurrentClient.SessionId);
1913 /// <param name="value"></param>
1914 /// <param name="maxChars"></param>
1915 /// <returns></returns>
1916 public static string Truncate(string value, int maxChars)
1918 return value.Length <= maxChars ? value : value.Substring(0, maxChars-3) + "...";
1922 /// Update our recording notification.
1924 private void UpdateRecordingNotification()
1927 bool activeRecording = false;
1929 RecordingField recField=new RecordingField();
1930 foreach (var client in iClients)
1932 RecordingField rec=(RecordingField)client.Value.FindSameFieldAs(recField);
1933 if (rec!=null && rec.IsActive)
1935 activeRecording = true;
1936 //Don't break cause we are collecting the names/texts.
1937 if (!String.IsNullOrEmpty(rec.Text))
1939 text += (rec.Text + "\n");
1943 //Not text for that recording, use client name instead
1944 text += client.Value.Name + " recording\n";
1950 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1951 iRecordingNotification.Text = Truncate(text,63);
1953 //Change visibility of notification if needed
1954 if (iRecordingNotification.Visible != activeRecording)
1956 iRecordingNotification.Visible = activeRecording;
1957 //Assuming the notification icon is in sync with our display icon
1958 //Take care of our REC icon
1959 if (iDisplay.IsOpen())
1961 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
1969 /// <param name="aClient"></param>
1970 private void UpdateClientTreeViewNode(ClientData aClient)
1972 Debug.Print("UpdateClientTreeViewNode");
1974 if (aClient == null)
1979 //Hook in record icon update too
1980 UpdateRecordingNotification();
1982 TreeNode node = null;
1983 //Check that our client node already exists
1984 //Get our client root node using its key which is our session ID
1985 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
1986 if (nodes.Count()>0)
1988 //We already have a node for that client
1990 //Clear children as we are going to recreate them below
1995 //Client node does not exists create a new one
1996 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
1997 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
2003 if (!String.IsNullOrEmpty(aClient.Name))
2005 //We have a name, use it as text for our root node
2006 node.Text = aClient.Name;
2007 //Add a child with SessionId
2008 node.Nodes.Add(new TreeNode(aClient.SessionId));
2012 //No name, use session ID instead
2013 node.Text = aClient.SessionId;
2016 //Display client priority
2017 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2019 if (aClient.Fields.Count > 0)
2021 //Create root node for our texts
2022 TreeNode textsRoot = new TreeNode("Fields");
2023 node.Nodes.Add(textsRoot);
2024 //For each text add a new entry
2025 foreach (DataField field in aClient.Fields)
2027 if (field.IsTextField)
2029 TextField textField = (TextField)field;
2030 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2032 else if (field.IsBitmapField)
2034 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2036 else if (field.IsRecordingField)
2038 RecordingField recordingField = (RecordingField)field;
2039 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2049 /// Update our table layout row styles to make sure each rows have similar height
2051 private void UpdateTableLayoutRowStyles()
2053 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2055 rowStyle.SizeType = SizeType.Percent;
2056 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
2061 /// Update our display table layout.
2062 /// Will instanciated every field control as defined by our client.
2063 /// Fields must be specified by rows from the left.
2065 /// <param name="aLayout"></param>
2066 private void UpdateTableLayoutPanel(ClientData aClient)
2068 Debug.Print("UpdateTableLayoutPanel");
2070 if (aClient == null)
2077 TableLayout layout = aClient.Layout;
2079 //First clean our current panel
2080 iTableLayoutPanel.Controls.Clear();
2081 iTableLayoutPanel.RowStyles.Clear();
2082 iTableLayoutPanel.ColumnStyles.Clear();
2083 iTableLayoutPanel.RowCount = 0;
2084 iTableLayoutPanel.ColumnCount = 0;
2086 //Then recreate our rows...
2087 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2089 iTableLayoutPanel.RowCount++;
2093 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2095 iTableLayoutPanel.ColumnCount++;
2099 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2101 //Create our column styles
2102 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2105 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2109 //Create our row styles
2110 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2120 foreach (DataField field in aClient.Fields)
2122 if (!field.IsTableField)
2124 //That field is not taking part in our table layout skip it
2128 TableField tableField = (TableField)field;
2130 //Create a control corresponding to the field specified for that cell
2131 Control control = CreateControlForDataField(tableField);
2133 //Add newly created control to our table layout at the specified row and column
2134 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2135 //Make sure we specify column and row span for that new control
2136 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2137 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2145 /// Check our type of data field and create corresponding control
2147 /// <param name="aField"></param>
2148 private Control CreateControlForDataField(DataField aField)
2150 Control control=null;
2151 if (aField.IsTextField)
2153 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2154 label.AutoEllipsis = true;
2155 label.AutoSize = true;
2156 label.BackColor = System.Drawing.Color.Transparent;
2157 label.Dock = System.Windows.Forms.DockStyle.Fill;
2158 label.Location = new System.Drawing.Point(1, 1);
2159 label.Margin = new System.Windows.Forms.Padding(0);
2160 label.Name = "marqueeLabel" + aField;
2161 label.OwnTimer = false;
2162 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2163 label.Separator = cds.Separator;
2164 label.MinFontSize = cds.MinFontSize;
2165 label.ScaleToFit = cds.ScaleToFit;
2166 //control.Size = new System.Drawing.Size(254, 30);
2167 //control.TabIndex = 2;
2168 label.Font = cds.Font;
2170 TextField field = (TextField)aField;
2171 label.TextAlign = field.Alignment;
2172 label.UseCompatibleTextRendering = true;
2173 label.Text = field.Text;
2177 else if (aField.IsBitmapField)
2179 //Create picture box
2180 PictureBox picture = new PictureBox();
2181 picture.AutoSize = true;
2182 picture.BackColor = System.Drawing.Color.Transparent;
2183 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2184 picture.Location = new System.Drawing.Point(1, 1);
2185 picture.Margin = new System.Windows.Forms.Padding(0);
2186 picture.Name = "pictureBox" + aField;
2188 BitmapField field = (BitmapField)aField;
2189 picture.Image = field.Bitmap;
2193 //TODO: Handle recording field?
2199 /// Called when the user selected a new display type.
2201 /// <param name="sender"></param>
2202 /// <param name="e"></param>
2203 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2205 //Store the selected display type in our settings
2206 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2207 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2208 Properties.Settings.Default.Save();
2210 //Try re-opening the display connection if we were already connected.
2211 //Otherwise just update our status to reflect display type change.
2212 if (iDisplay.IsOpen())
2214 OpenDisplayConnection();
2222 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2224 if (maskedTextBoxTimerInterval.Text != "")
2226 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2230 timer.Interval = interval;
2231 cds.TimerInterval = timer.Interval;
2232 Properties.Settings.Default.Save();
2237 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2239 if (maskedTextBoxMinFontSize.Text != "")
2241 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2243 if (minFontSize > 0)
2245 cds.MinFontSize = minFontSize;
2246 Properties.Settings.Default.Save();
2247 //We need to recreate our layout for that change to take effect
2248 UpdateTableLayoutPanel(iCurrentClientData);
2254 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2256 if (maskedTextBoxScrollingSpeed.Text != "")
2258 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2260 if (scrollingSpeed > 0)
2262 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2263 Properties.Settings.Default.Save();
2264 //We need to recreate our layout for that change to take effect
2265 UpdateTableLayoutPanel(iCurrentClientData);
2270 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2272 cds.Separator = textBoxScrollLoopSeparator.Text;
2273 Properties.Settings.Default.Save();
2275 //Update our text fields
2276 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2278 ctrl.Separator = cds.Separator;
2283 private void buttonPowerOn_Click(object sender, EventArgs e)
2288 private void buttonPowerOff_Click(object sender, EventArgs e)
2290 iDisplay.PowerOff();
2293 private void buttonShowClock_Click(object sender, EventArgs e)
2298 private void buttonHideClock_Click(object sender, EventArgs e)
2303 private void buttonUpdate_Click(object sender, EventArgs e)
2305 InstallUpdateSyncWithInfo();
2313 if (!iDisplay.IsOpen())
2318 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2319 iSkipFrameRendering = true;
2322 iDisplay.SwapBuffers();
2323 //Then show our clock
2324 iDisplay.ShowClock();
2332 if (!iDisplay.IsOpen())
2337 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2338 iSkipFrameRendering = false;
2339 iDisplay.HideClock();
2342 private void InstallUpdateSyncWithInfo()
2344 UpdateCheckInfo info = null;
2346 if (ApplicationDeployment.IsNetworkDeployed)
2348 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2352 info = ad.CheckForDetailedUpdate();
2355 catch (DeploymentDownloadException dde)
2357 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);
2360 catch (InvalidDeploymentException ide)
2362 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);
2365 catch (InvalidOperationException ioe)
2367 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2371 if (info.UpdateAvailable)
2373 Boolean doUpdate = true;
2375 if (!info.IsUpdateRequired)
2377 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2378 if (!(DialogResult.OK == dr))
2385 // Display a message that the application MUST reboot. Display the minimum required version.
2386 MessageBox.Show("This application has detected a mandatory update from your current " +
2387 "version to version " + info.MinimumRequiredVersion.ToString() +
2388 ". The application will now install the update and restart.",
2389 "Update Available", MessageBoxButtons.OK,
2390 MessageBoxIcon.Information);
2398 MessageBox.Show("The application has been upgraded, and will now restart.");
2399 Application.Restart();
2401 catch (DeploymentDownloadException dde)
2403 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2410 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2419 private void SysTrayHideShow()
2425 WindowState = FormWindowState.Normal;
2430 /// Use to handle minimize events.
2432 /// <param name="sender"></param>
2433 /// <param name="e"></param>
2434 private void MainForm_SizeChanged(object sender, EventArgs e)
2436 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2448 /// <param name="sender"></param>
2449 /// <param name="e"></param>
2450 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2452 //Our table layout size has changed which means our display size has changed.
2453 //We need to re-create our bitmap.
2454 iCreateBitmap = true;
2460 /// <param name="sender"></param>
2461 /// <param name="e"></param>
2462 private void buttonSelectFile_Click(object sender, EventArgs e)
2464 //openFileDialog1.InitialDirectory = "c:\\";
2465 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2466 //openFileDialog.FilterIndex = 1;
2467 openFileDialog.RestoreDirectory = true;
2469 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2471 labelStartFileName.Text = openFileDialog.FileName;
2472 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2473 Properties.Settings.Default.Save();
2480 /// <param name="sender"></param>
2481 /// <param name="e"></param>
2482 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2484 //Save the optical drive the user selected for ejection
2485 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2486 Properties.Settings.Default.Save();
2493 private void LogsUpdate()
2495 if (iWriter != null)
2503 /// Broadcast messages to subscribers.
2505 /// <param name="message"></param>
2506 protected override void WndProc(ref Message aMessage)
2510 if (OnWndProc!=null)
2512 OnWndProc(ref aMessage);
2515 base.WndProc(ref aMessage);
2518 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2520 //Save CEC enabled status
2521 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2522 Properties.Settings.Default.Save();
2527 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2529 //Save CEC HDMI port
2530 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2531 Properties.Settings.Default.CecHdmiPort++;
2532 Properties.Settings.Default.Save();
2537 private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
2539 Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
2540 Properties.Settings.Default.Save();
2545 private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
2547 Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
2548 Properties.Settings.Default.Save();
2553 private void checkBoxCecReconnectToPowerTv_CheckedChanged(object sender, EventArgs e)
2555 Properties.Settings.Default.CecReconnectToPowerTv = checkBoxCecReconnectToPowerTv.Checked;
2556 Properties.Settings.Default.Save();
2564 private void ResetCec()
2566 if (iCecManager==null)
2568 //Thus skipping initial UI setup
2574 if (Properties.Settings.Default.CecEnabled)
2576 iCecManager.Start(Handle, "CEC",
2577 Properties.Settings.Default.CecHdmiPort,
2578 Properties.Settings.Default.CecMonitorOn,
2579 Properties.Settings.Default.CecMonitorOff,
2580 Properties.Settings.Default.CecReconnectToPowerTv);
2589 private void SetupCecLogLevel()
2592 iCecManager.Client.LogLevel = 0;
2594 if (checkBoxCecLogError.Checked)
2595 iCecManager.Client.LogLevel |= (int)CecLogLevel.Error;
2597 if (checkBoxCecLogWarning.Checked)
2598 iCecManager.Client.LogLevel |= (int)CecLogLevel.Warning;
2600 if (checkBoxCecLogNotice.Checked)
2601 iCecManager.Client.LogLevel |= (int)CecLogLevel.Notice;
2603 if (checkBoxCecLogTraffic.Checked)
2604 iCecManager.Client.LogLevel |= (int)CecLogLevel.Traffic;
2606 if (checkBoxCecLogDebug.Checked)
2607 iCecManager.Client.LogLevel |= (int)CecLogLevel.Debug;
2609 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2613 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2618 private void buttonClearLogs_Click(object sender, EventArgs e)
2620 richTextBoxLogs.Clear();
2623 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)