Reflection functions now working on all loaded assemblies.
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 //public ManagerEventAction iManager = new ManagerEventAction();
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 ManagerEventAction.Current = Properties.Settings.Default.Actions;
135 if (ManagerEventAction.Current == null)
137 //No actions in our settings yet
138 ManagerEventAction.Current = new ManagerEventAction();
139 Properties.Settings.Default.Actions = ManagerEventAction.Current;
143 //We loaded actions from our settings
144 //We need to hook them with corresponding events
145 ManagerEventAction.Current.Init();
147 iSkipFrameRendering = false;
149 iCurrentClientSessionId = "";
150 iCurrentClientData = null;
151 LastTickTime = DateTime.Now;
152 //Instantiate our display and register for events notifications
153 iDisplay = new Display();
154 iDisplay.OnOpened += OnDisplayOpened;
155 iDisplay.OnClosed += OnDisplayClosed;
157 iClients = new Dictionary<string, ClientData>();
158 iStartupManager = new StartupManager();
159 iNotifyIcon = new SharpLib.Notification.Control();
160 iRecordingNotification = new SharpLib.Notification.Control();
162 //Have our designer initialize its controls
163 InitializeComponent();
165 //Redirect console output
166 iWriter = new RichTextBoxTextWriter(richTextBoxLogs);
167 Console.SetOut(iWriter);
169 //Populate device types
170 PopulateDeviceTypes();
172 //Populate optical drives
173 PopulateOpticalDrives();
175 //Initial status update
178 //We have a bug when drawing minimized and reusing our bitmap
179 //Though I could not reproduce it on Windows 10
180 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
181 iCreateBitmap = false;
183 //Minimize our window if desired
184 if (Properties.Settings.Default.StartMinimized)
186 WindowState = FormWindowState.Minimized;
194 /// <param name="sender"></param>
195 /// <param name="e"></param>
196 private void MainForm_Load(object sender, EventArgs e)
198 //Check if we are running a Click Once deployed application
199 if (ApplicationDeployment.IsNetworkDeployed)
201 //This is a proper Click Once installation, fetch and show our version number
202 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
206 //Not a proper Click Once installation, assuming development build then
207 this.Text += " - development";
211 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
212 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
213 UpdateAudioDeviceAndMasterVolumeThreadSafe();
216 iNetworkManager = new NetworkManager();
217 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
218 UpdateNetworkStatus();
221 iCecManager = new ConsumerElectronicControl();
222 OnWndProc += iCecManager.OnWndProc;
228 //Setup notification icon
231 //Setup recording notification
232 SetupRecordingNotification();
234 // To make sure start up with minimize to tray works
235 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
241 //When not debugging we want the screen to be empty until a client takes over
244 //When developing we want at least one client for testing
245 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
248 //Open display connection on start-up if needed
249 if (Properties.Settings.Default.DisplayConnectOnStartup)
251 OpenDisplayConnection();
254 //Start our server so that we can get client requests
257 //Register for HID events
258 RegisterHidDevices();
260 //Start Idle client if needed
261 if (Properties.Settings.Default.StartIdleClient)
268 /// Called when our display is opened.
270 /// <param name="aDisplay"></param>
271 private void OnDisplayOpened(Display aDisplay)
273 //Make sure we resume frame rendering
274 iSkipFrameRendering = false;
276 //Set our screen size now that our display is connected
277 //Our panelDisplay is the container of our tableLayoutPanel
278 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
279 //panelDisplay needs an extra 2 pixels for borders on each sides
280 //tableLayoutPanel will eventually be the exact size of our display
281 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
282 panelDisplay.Size = size;
284 //Our display was just opened, update our UI
286 //Initiate asynchronous request
287 iDisplay.RequestFirmwareRevision();
290 UpdateMasterVolumeThreadSafe();
292 UpdateNetworkStatus();
295 //Testing icon in debug, no arm done if icon not supported
296 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
297 //iDisplay.SetAllIconsStatus(2);
305 private void SetupEvents()
308 iTreeViewEvents.Nodes.Clear();
309 //Populate registered events
310 foreach (string key in ManagerEventAction.Current.Events.Keys)
312 Event e = ManagerEventAction.Current.Events[key];
313 TreeNode eventNode = iTreeViewEvents.Nodes.Add(key,e.Name);
315 eventNode.Nodes.Add(key + ".Description", e.Description);
316 TreeNode actionsNodes = eventNode.Nodes.Add(key + ".Actions", "Actions");
318 foreach (SharpLib.Ear.Action a in e.Actions)
320 actionsNodes.Nodes.Add(a.Name);
327 /// Called when our display is closed.
329 /// <param name="aDisplay"></param>
330 private void OnDisplayClosed(Display aDisplay)
332 //Our display was just closed, update our UI consequently
336 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
338 //Update network status
339 UpdateNetworkStatus();
343 /// Update our Network Status
345 private void UpdateNetworkStatus()
347 if (iDisplay.IsOpen())
349 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
350 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
355 int iLastNetworkIconIndex = 0;
356 int iUpdateCountSinceLastNetworkAnimation = 0;
361 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
363 iUpdateCountSinceLastNetworkAnimation++;
364 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
366 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
368 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
371 //Prevents div by zero and other undefined behavior
374 iLastNetworkIconIndex++;
375 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
376 for (int i=0;i<iconCount;i++)
378 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
380 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
384 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
393 /// Receive volume change notification and reflect changes on our slider.
395 /// <param name="data"></param>
396 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
398 UpdateMasterVolumeThreadSafe();
402 /// Update master volume when user moves our slider.
404 /// <param name="sender"></param>
405 /// <param name="e"></param>
406 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
408 //Just like Windows Volume Mixer we unmute if the volume is adjusted
409 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
410 //Set volume level according to our volume slider new position
411 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
416 /// Mute check box changed.
418 /// <param name="sender"></param>
419 /// <param name="e"></param>
420 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
422 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
426 /// Device State Changed
428 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
433 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
438 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
441 /// Default Device Changed
443 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
445 if (role == Role.Multimedia && flow == DataFlow.Render)
447 UpdateAudioDeviceAndMasterVolumeThreadSafe();
452 /// Property Value Changed
454 /// <param name="pwstrDeviceId"></param>
455 /// <param name="key"></param>
456 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
462 /// Update master volume indicators based our current system states.
463 /// This typically includes volume levels and mute status.
465 private void UpdateMasterVolumeThreadSafe()
467 if (this.InvokeRequired)
469 //Not in the proper thread, invoke ourselves
470 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
471 this.Invoke(d, new object[] { });
475 //Update volume slider
476 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
477 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
478 //Update mute checkbox
479 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
481 //If our display connection is open we need to update its icons
482 if (iDisplay.IsOpen())
484 //First take care our our volume level icons
485 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
486 if (volumeIconCount > 0)
488 //Compute current volume level from system level and the number of segments in our display volume bar.
489 //That tells us how many segments in our volume bar needs to be turned on.
490 float currentVolume = volumeLevelScalar * volumeIconCount;
491 int segmentOnCount = Convert.ToInt32(currentVolume);
492 //Check if our segment count was rounded up, this will later be used for half brightness segment
493 bool roundedUp = segmentOnCount > currentVolume;
495 for (int i = 0; i < volumeIconCount; i++)
497 if (i < segmentOnCount)
499 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
500 if (i == segmentOnCount - 1 && roundedUp)
503 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
508 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
513 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
518 //Take care of our mute icon
519 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
527 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
529 if (this.InvokeRequired)
531 //Not in the proper thread, invoke ourselves
532 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
533 this.Invoke(d, new object[] { });
537 //We are in the correct thread just go ahead.
540 //Get our master volume
541 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
543 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
545 //Show our volume in our track bar
546 UpdateMasterVolumeThreadSafe();
548 //Register to get volume modifications
549 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
551 trackBarMasterVolume.Enabled = true;
555 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
556 Debug.WriteLine(ex.ToString());
557 //Something went wrong S/PDIF device ca throw exception I guess
558 trackBarMasterVolume.Enabled = false;
565 private void PopulateDeviceTypes()
567 int count = Display.TypeCount();
569 for (int i = 0; i < count; i++)
571 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
578 private void PopulateOpticalDrives()
580 //Reset our list of drives
581 comboBoxOpticalDrives.Items.Clear();
582 comboBoxOpticalDrives.Items.Add("None");
584 //Go through each drives on our system and collected the optical ones in our list
585 DriveInfo[] allDrives = DriveInfo.GetDrives();
586 foreach (DriveInfo d in allDrives)
588 Debug.WriteLine("Drive " + d.Name);
589 Debug.WriteLine(" Drive type: {0}", d.DriveType);
591 if (d.DriveType==DriveType.CDRom)
593 //This is an optical drive, add it now
594 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
602 /// <returns></returns>
603 public string OpticalDriveToEject()
605 return comboBoxOpticalDrives.SelectedItem.ToString();
613 private void SetupTrayIcon()
615 iNotifyIcon.Icon = GetIcon("vfd.ico");
616 iNotifyIcon.Text = "Sharp Display Manager";
617 iNotifyIcon.Visible = true;
619 //Double click toggles visibility - typically brings up the application
620 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
625 //Adding a context menu, useful to be able to exit the application
626 ContextMenu contextMenu = new ContextMenu();
627 //Context menu item to toggle visibility
628 MenuItem hideShowItem = new MenuItem("Hide/Show");
629 hideShowItem.Click += delegate(object obj, EventArgs args)
633 contextMenu.MenuItems.Add(hideShowItem);
635 //Context menu item separator
636 contextMenu.MenuItems.Add(new MenuItem("-"));
638 //Context menu exit item
639 MenuItem exitItem = new MenuItem("Exit");
640 exitItem.Click += delegate(object obj, EventArgs args)
644 contextMenu.MenuItems.Add(exitItem);
646 iNotifyIcon.ContextMenu = contextMenu;
652 private void SetupRecordingNotification()
654 iRecordingNotification.Icon = GetIcon("record.ico");
655 iRecordingNotification.Text = "No recording";
656 iRecordingNotification.Visible = false;
660 /// Access icons from embedded resources.
662 /// <param name="aName"></param>
663 /// <returns></returns>
664 public static Icon GetIcon(string aName)
666 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
667 foreach (string name in names)
669 //Find a resource name that ends with the given name
670 if (name.EndsWith(aName))
672 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
674 return new Icon(stream);
684 /// Set our current client.
685 /// This will take care of applying our client layout and set data fields.
687 /// <param name="aSessionId"></param>
688 void SetCurrentClient(string aSessionId, bool aForce=false)
690 if (aSessionId == iCurrentClientSessionId)
692 //Given client is already the current one.
693 //Don't bother changing anything then.
697 ClientData requestedClientData = iClients[aSessionId];
699 //Check when was the last time we switched to that client
700 if (iCurrentClientData != null)
702 //Do not switch client if priority of current client is higher
703 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
709 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
710 //TODO: put that hard coded value as a client property
711 //Clients should be able to define how often they can be interrupted
712 //Thus a background client can set this to zero allowing any other client to interrupt at any time
713 //We could also compute this delay by looking at the requests frequencies?
715 requestedClientData.Priority == iCurrentClientData.Priority && //Time sharing is only if clients have the same priority
716 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
718 //Don't switch clients too often
723 //Set current client ID.
724 iCurrentClientSessionId = aSessionId;
725 //Set the time we last switched to that client
726 iClients[aSessionId].LastSwitchTime = DateTime.Now;
727 //Fetch and set current client data.
728 iCurrentClientData = requestedClientData;
729 //Apply layout and set data fields.
730 UpdateTableLayoutPanel(iCurrentClientData);
733 private void buttonFont_Click(object sender, EventArgs e)
735 //fontDialog.ShowColor = true;
736 //fontDialog.ShowApply = true;
737 fontDialog.ShowEffects = true;
738 fontDialog.Font = cds.Font;
740 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
742 //fontDialog.ShowHelp = true;
744 //fontDlg.MaxSize = 40;
745 //fontDlg.MinSize = 22;
747 //fontDialog.Parent = this;
748 //fontDialog.StartPosition = FormStartPosition.CenterParent;
750 //DlgBox.ShowDialog(fontDialog);
752 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
753 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
755 //Set the fonts to all our labels in our layout
756 foreach (Control ctrl in iTableLayoutPanel.Controls)
758 if (ctrl is MarqueeLabel)
760 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
765 cds.Font = fontDialog.Font;
766 Properties.Settings.Default.Save();
775 void CheckFontHeight()
777 //Show font height and width
778 labelFontHeight.Text = "Font height: " + cds.Font.Height;
779 float charWidth = IsFixedWidth(cds.Font);
780 if (charWidth == 0.0f)
782 labelFontWidth.Visible = false;
786 labelFontWidth.Visible = true;
787 labelFontWidth.Text = "Font width: " + charWidth;
790 MarqueeLabel label = null;
791 //Get the first label control we can find
792 foreach (Control ctrl in iTableLayoutPanel.Controls)
794 if (ctrl is MarqueeLabel)
796 label = (MarqueeLabel)ctrl;
801 //Now check font height and show a warning if needed.
802 if (label != null && label.Font.Height > label.Height)
804 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
805 labelWarning.Visible = true;
809 labelWarning.Visible = false;
814 private void buttonCapture_Click(object sender, EventArgs e)
816 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
817 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
818 //Bitmap bmpToSave = new Bitmap(bmp);
819 bmp.Save("D:\\capture.png");
821 ((MarqueeLabel)iTableLayoutPanel.Controls[0]).Text = "Captured";
824 string outputFileName = "d:\\capture.png";
825 using (MemoryStream memory = new MemoryStream())
827 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
829 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
830 byte[] bytes = memory.ToArray();
831 fs.Write(bytes, 0, bytes.Length);
838 private void CheckForRequestResults()
840 if (iDisplay.IsRequestPending())
842 switch (iDisplay.AttemptRequestCompletion())
844 case MiniDisplay.Request.FirmwareRevision:
845 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
846 //Issue next request then
847 iDisplay.RequestPowerSupplyStatus();
850 case MiniDisplay.Request.PowerSupplyStatus:
851 if (iDisplay.PowerSupplyStatus())
853 toolStripStatusLabelPower.Text = "ON";
857 toolStripStatusLabelPower.Text = "OFF";
859 //Issue next request then
860 iDisplay.RequestDeviceId();
863 case MiniDisplay.Request.DeviceId:
864 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
865 //No more request to issue
871 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
873 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
880 public static uint ColorUntouched(int aX, int aY, uint aPixel)
885 public static uint ColorInversed(int aX, int aY, uint aPixel)
890 public static uint ColorChessboard(int aX, int aY, uint aPixel)
892 if ((aX % 2 == 0) && (aY % 2 == 0))
896 else if ((aX % 2 != 0) && (aY % 2 != 0))
904 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
906 return aBmp.Width - aX - 1;
909 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
911 return iBmp.Height - aY - 1;
914 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
919 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
925 /// Select proper pixel delegates according to our current settings.
927 private void SetupPixelDelegates()
929 //Select our pixel processing routine
930 if (cds.InverseColors)
932 //iColorFx = ColorChessboard;
933 iColorFx = ColorInversed;
937 iColorFx = ColorWhiteIsOn;
940 //Select proper coordinate translation functions
941 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
942 if (cds.ReverseScreen)
944 iScreenX = ScreenReversedX;
945 iScreenY = ScreenReversedY;
955 //This is our timer tick responsible to perform our render
956 private void timer_Tick(object sender, EventArgs e)
958 //Not ideal cause this has nothing to do with display render
961 //Update our animations
962 DateTime NewTickTime = DateTime.Now;
964 UpdateNetworkSignal(LastTickTime, NewTickTime);
966 //Update animation for all our marquees
967 foreach (Control ctrl in iTableLayoutPanel.Controls)
969 if (ctrl is MarqueeLabel)
971 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
976 if (iDisplay.IsOpen())
978 CheckForRequestResults();
980 //Check if frame rendering is needed
981 //Typically used when showing clock
982 if (!iSkipFrameRendering)
987 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
988 iCreateBitmap = false;
990 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
991 //iBmp.Save("D:\\capture.png");
993 //Send it to our display
994 for (int i = 0; i < iBmp.Width; i++)
996 for (int j = 0; j < iBmp.Height; j++)
1000 //Get our processed pixel coordinates
1001 int x = iScreenX(iBmp, i);
1002 int y = iScreenY(iBmp, j);
1004 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
1005 //Apply color effects
1006 color = iColorFx(x, y, color);
1008 iDisplay.SetPixel(x, y, color);
1013 iDisplay.SwapBuffers();
1017 //Compute instant FPS
1018 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
1020 LastTickTime = NewTickTime;
1025 /// Attempt to establish connection with our display hardware.
1027 private void OpenDisplayConnection()
1029 CloseDisplayConnection();
1031 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
1034 toolStripStatusLabelConnect.Text = "Connection error";
1038 private void CloseDisplayConnection()
1040 //Status will be updated upon receiving the closed event
1042 if (iDisplay == null || !iDisplay.IsOpen())
1047 //Do not clear if we gave up on rendering already.
1048 //This means we will keep on displaying clock on MDM166AA for instance.
1049 if (!iSkipFrameRendering)
1052 iDisplay.SwapBuffers();
1055 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1059 private void buttonOpen_Click(object sender, EventArgs e)
1061 OpenDisplayConnection();
1064 private void buttonClose_Click(object sender, EventArgs e)
1066 CloseDisplayConnection();
1069 private void buttonClear_Click(object sender, EventArgs e)
1072 iDisplay.SwapBuffers();
1075 private void buttonFill_Click(object sender, EventArgs e)
1078 iDisplay.SwapBuffers();
1081 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1083 cds.Brightness = trackBarBrightness.Value;
1084 Properties.Settings.Default.Save();
1085 iDisplay.SetBrightness(trackBarBrightness.Value);
1091 /// CDS stands for Current Display Settings
1093 private DisplaySettings cds
1097 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1098 if (settings == null)
1100 settings = new DisplaysSettings();
1102 Properties.Settings.Default.DisplaysSettings = settings;
1105 //Make sure all our settings have been created
1106 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1108 settings.Displays.Add(new DisplaySettings());
1111 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1112 return displaySettings;
1117 /// Check if the given font has a fixed character pitch.
1119 /// <param name="ft"></param>
1120 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1121 public float IsFixedWidth(Font ft)
1123 Graphics g = CreateGraphics();
1124 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1125 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1127 bool fixedWidth = true;
1129 foreach (char c in charSizes)
1130 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1142 /// Synchronize UI with settings
1144 private void UpdateStatus()
1147 checkBoxShowBorders.Checked = cds.ShowBorders;
1148 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1150 //Set the proper font to each of our labels
1151 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1153 ctrl.Font = cds.Font;
1157 //Check if "run on Windows startup" is enabled
1158 checkBoxAutoStart.Checked = iStartupManager.Startup;
1160 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1161 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1162 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1163 iCheckBoxStartIdleClient.Checked = Properties.Settings.Default.StartIdleClient;
1164 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1167 //Try find our drive in our drive list
1168 int opticalDriveItemIndex=0;
1169 bool driveNotFound = true;
1170 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1171 foreach (object item in comboBoxOpticalDrives.Items)
1173 if (opticalDriveToEject == item.ToString())
1175 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1176 driveNotFound = false;
1179 opticalDriveItemIndex++;
1184 //We could not find the drive we had saved.
1185 //Select "None" then.
1186 comboBoxOpticalDrives.SelectedIndex = 0;
1190 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1191 checkBoxCecMonitorOn.Checked = Properties.Settings.Default.CecMonitorOn;
1192 checkBoxCecMonitorOff.Checked = Properties.Settings.Default.CecMonitorOff;
1193 checkBoxCecReconnectToPowerTv.Checked = Properties.Settings.Default.CecReconnectToPowerTv;
1194 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1196 //Mini Display settings
1197 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1198 checkBoxInverseColors.Checked = cds.InverseColors;
1199 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1200 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1201 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1202 labelMinFontSize.Enabled = cds.ScaleToFit;
1203 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1204 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1205 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1206 timer.Interval = cds.TimerInterval;
1207 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1208 textBoxScrollLoopSeparator.Text = cds.Separator;
1210 SetupPixelDelegates();
1212 if (iDisplay.IsOpen())
1214 //We have a display connection
1215 //Reflect that in our UI
1218 iTableLayoutPanel.Enabled = true;
1219 panelDisplay.Enabled = true;
1221 //Only setup brightness if display is open
1222 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1223 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1224 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1226 //Brightness out of range, this can occur when using auto-detect
1227 //Use max brightness instead
1228 trackBarBrightness.Value = iDisplay.MaxBrightness();
1229 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1233 trackBarBrightness.Value = cds.Brightness;
1234 iDisplay.SetBrightness(cds.Brightness);
1237 //Try compute the steps to something that makes sense
1238 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1239 trackBarBrightness.SmallChange = 1;
1242 buttonFill.Enabled = true;
1243 buttonClear.Enabled = true;
1244 buttonOpen.Enabled = false;
1245 buttonClose.Enabled = true;
1246 trackBarBrightness.Enabled = true;
1247 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1248 //+ " - " + iDisplay.SerialNumber();
1250 if (iDisplay.SupportPowerOnOff())
1252 buttonPowerOn.Enabled = true;
1253 buttonPowerOff.Enabled = true;
1257 buttonPowerOn.Enabled = false;
1258 buttonPowerOff.Enabled = false;
1261 if (iDisplay.SupportClock())
1263 buttonShowClock.Enabled = true;
1264 buttonHideClock.Enabled = true;
1268 buttonShowClock.Enabled = false;
1269 buttonHideClock.Enabled = false;
1273 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1274 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1276 if (cds.ShowVolumeLabel)
1278 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1282 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1287 //Display connection not available
1288 //Reflect that in our UI
1290 //In debug start our timer even if we don't have a display connection
1293 //In production environment we don't need our timer if no display connection
1296 checkBoxShowVolumeLabel.Enabled = false;
1297 iTableLayoutPanel.Enabled = false;
1298 panelDisplay.Enabled = false;
1299 buttonFill.Enabled = false;
1300 buttonClear.Enabled = false;
1301 buttonOpen.Enabled = true;
1302 buttonClose.Enabled = false;
1303 trackBarBrightness.Enabled = false;
1304 buttonPowerOn.Enabled = false;
1305 buttonPowerOff.Enabled = false;
1306 buttonShowClock.Enabled = false;
1307 buttonHideClock.Enabled = false;
1308 toolStripStatusLabelConnect.Text = "Disconnected";
1309 toolStripStatusLabelPower.Text = "N/A";
1318 /// <param name="sender"></param>
1319 /// <param name="e"></param>
1320 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1322 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1323 Properties.Settings.Default.Save();
1327 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1329 //Save our show borders setting
1330 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1331 cds.ShowBorders = checkBoxShowBorders.Checked;
1332 Properties.Settings.Default.Save();
1336 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1338 //Save our connect on startup setting
1339 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1340 Properties.Settings.Default.Save();
1343 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1345 //Save our "Minimize to tray" setting
1346 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1347 Properties.Settings.Default.Save();
1351 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1353 //Save our "Start minimized" setting
1354 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1355 Properties.Settings.Default.Save();
1358 private void checkBoxStartIdleClient_CheckedChanged(object sender, EventArgs e)
1360 Properties.Settings.Default.StartIdleClient = iCheckBoxStartIdleClient.Checked;
1361 Properties.Settings.Default.Save();
1364 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1366 iStartupManager.Startup = checkBoxAutoStart.Checked;
1370 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1372 //Save our reverse screen setting
1373 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1374 Properties.Settings.Default.Save();
1375 SetupPixelDelegates();
1378 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1380 //Save our inverse colors setting
1381 cds.InverseColors = checkBoxInverseColors.Checked;
1382 Properties.Settings.Default.Save();
1383 SetupPixelDelegates();
1386 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1388 //Save our scale to fit setting
1389 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1390 Properties.Settings.Default.Save();
1392 labelMinFontSize.Enabled = cds.ScaleToFit;
1393 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1396 private void MainForm_Resize(object sender, EventArgs e)
1398 if (WindowState == FormWindowState.Minimized)
1400 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1401 // That's apparently not needed on Windows 10 but we better leave it in place.
1402 iCreateBitmap = true;
1406 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1409 iNetworkManager.Dispose();
1410 CloseDisplayConnection();
1412 e.Cancel = iClosing;
1415 public void StartServer()
1417 iServiceHost = new ServiceHost
1420 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1423 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1424 iServiceHost.Open();
1427 public void StopServer()
1429 if (iClients.Count > 0 && !iClosing)
1433 BroadcastCloseEvent();
1437 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1439 iClosing = false; //We make sure we force close if asked twice
1444 //We removed that as it often lags for some reason
1445 //iServiceHost.Close();
1449 public void BroadcastCloseEvent()
1451 Trace.TraceInformation("BroadcastCloseEvent - start");
1453 var inactiveClients = new List<string>();
1454 foreach (var client in iClients)
1456 //if (client.Key != eventData.ClientName)
1460 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1461 client.Value.Callback.OnCloseOrder(/*eventData*/);
1463 catch (Exception ex)
1465 inactiveClients.Add(client.Key);
1470 if (inactiveClients.Count > 0)
1472 foreach (var client in inactiveClients)
1474 iClients.Remove(client);
1475 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(client, false)[0]);
1479 if (iClients.Count==0)
1486 /// Just remove all our fields.
1488 private void ClearLayout()
1490 iTableLayoutPanel.Controls.Clear();
1491 iTableLayoutPanel.RowStyles.Clear();
1492 iTableLayoutPanel.ColumnStyles.Clear();
1493 iCurrentClientData = null;
1497 /// Just launch a demo client.
1499 private void StartNewClient(string aTopText = "", string aBottomText = "")
1501 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1502 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1503 clientThread.Start(myParams);
1508 /// Just launch our idle client.
1510 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1512 Thread clientThread = new Thread(SharpDisplayIdleClient.Program.MainWithParams);
1513 SharpDisplayIdleClient.StartParams myParams = new SharpDisplayIdleClient.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1514 clientThread.Start(myParams);
1519 private void buttonStartClient_Click(object sender, EventArgs e)
1524 private void buttonSuspend_Click(object sender, EventArgs e)
1529 private void StartTimer()
1531 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1532 timer.Enabled = true;
1533 UpdateSuspendButton();
1536 private void StopTimer()
1538 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1539 timer.Enabled = false;
1540 UpdateSuspendButton();
1543 private void ToggleTimer()
1545 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1546 timer.Enabled = !timer.Enabled;
1547 UpdateSuspendButton();
1550 private void UpdateSuspendButton()
1554 buttonSuspend.Text = "Run";
1558 buttonSuspend.Text = "Pause";
1563 private void buttonCloseClients_Click(object sender, EventArgs e)
1565 BroadcastCloseEvent();
1568 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1570 //Root node must have at least one child
1571 if (e.Node.Nodes.Count == 0)
1576 //If the selected node is the root node of a client then switch to it
1577 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1578 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1580 //We have a valid session just switch to that client
1581 SetCurrentClient(sessionId,true);
1590 /// <param name="aSessionId"></param>
1591 /// <param name="aCallback"></param>
1592 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1594 if (this.InvokeRequired)
1596 //Not in the proper thread, invoke ourselves
1597 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1598 this.Invoke(d, new object[] { aSessionId, aCallback });
1602 //We are in the proper thread
1603 //Add this session to our collection of clients
1604 ClientData newClient = new ClientData(aSessionId, aCallback);
1605 Program.iMainForm.iClients.Add(aSessionId, newClient);
1606 //Add this session to our UI
1607 UpdateClientTreeViewNode(newClient);
1613 /// Find the client with the highest priority if any.
1615 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1616 public ClientData FindHighestPriorityClient()
1618 ClientData highestPriorityClient = null;
1619 foreach (var client in iClients)
1621 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1623 highestPriorityClient = client.Value;
1627 return highestPriorityClient;
1633 /// <param name="aSessionId"></param>
1634 public void RemoveClientThreadSafe(string aSessionId)
1636 if (this.InvokeRequired)
1638 //Not in the proper thread, invoke ourselves
1639 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1640 this.Invoke(d, new object[] { aSessionId });
1644 //We are in the proper thread
1645 //Remove this session from both client collection and UI tree view
1646 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1648 Program.iMainForm.iClients.Remove(aSessionId);
1649 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1650 //Update recording status too whenever a client is removed
1651 UpdateRecordingNotification();
1654 if (iCurrentClientSessionId == aSessionId)
1656 //The current client is closing
1657 iCurrentClientData = null;
1658 //Find the client with the highest priority and set it as current
1659 ClientData newCurrentClient = FindHighestPriorityClient();
1660 if (newCurrentClient!=null)
1662 SetCurrentClient(newCurrentClient.SessionId, true);
1666 if (iClients.Count == 0)
1668 //Clear our screen when last client disconnects
1673 //We were closing our form
1674 //All clients are now closed
1675 //Just resume our close operation
1686 /// <param name="aSessionId"></param>
1687 /// <param name="aLayout"></param>
1688 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1690 if (this.InvokeRequired)
1692 //Not in the proper thread, invoke ourselves
1693 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1694 this.Invoke(d, new object[] { aSessionId, aLayout });
1698 ClientData client = iClients[aSessionId];
1701 //Don't change a thing if the layout is the same
1702 if (!client.Layout.IsSameAs(aLayout))
1704 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1705 //Set our client layout then
1706 client.Layout = aLayout;
1707 //So that next time we update all our fields at ones
1708 client.HasNewLayout = true;
1709 //Layout has changed clear our fields then
1710 client.Fields.Clear();
1712 UpdateClientTreeViewNode(client);
1716 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1725 /// <param name="aSessionId"></param>
1726 /// <param name="aField"></param>
1727 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1729 if (this.InvokeRequired)
1731 //Not in the proper thread, invoke ourselves
1732 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1733 this.Invoke(d, new object[] { aSessionId, aField });
1737 //We are in the proper thread
1738 //Call the non-thread-safe variant
1739 SetClientField(aSessionId, aField);
1747 /// Set a data field in the given client.
1749 /// <param name="aSessionId"></param>
1750 /// <param name="aField"></param>
1751 private void SetClientField(string aSessionId, DataField aField)
1753 //TODO: should check if the field actually changed?
1755 ClientData client = iClients[aSessionId];
1756 bool layoutChanged = false;
1757 bool contentChanged = true;
1759 //Fetch our field index
1760 int fieldIndex = client.FindSameFieldIndex(aField);
1764 //No corresponding field, just bail out
1768 //Keep our previous field in there
1769 DataField previousField = client.Fields[fieldIndex];
1770 //Just update that field then
1771 client.Fields[fieldIndex] = aField;
1773 if (!aField.IsTableField)
1775 //We are done then if that field is not in our table layout
1779 TableField tableField = (TableField) aField;
1781 if (previousField.IsSameLayout(aField))
1783 //If we are updating a field in our current client we need to update it in our panel
1784 if (aSessionId == iCurrentClientSessionId)
1786 Control ctrl=iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1787 if (aField.IsTextField && ctrl is MarqueeLabel)
1789 TextField textField=(TextField)aField;
1790 //Text field control already in place, just change the text
1791 MarqueeLabel label = (MarqueeLabel)ctrl;
1792 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1793 label.Text = textField.Text;
1794 label.TextAlign = textField.Alignment;
1796 else if (aField.IsBitmapField && ctrl is PictureBox)
1798 BitmapField bitmapField = (BitmapField)aField;
1799 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1800 //Bitmap field control already in place just change the bitmap
1801 PictureBox pictureBox = (PictureBox)ctrl;
1802 pictureBox.Image = bitmapField.Bitmap;
1806 layoutChanged = true;
1812 layoutChanged = true;
1815 //If either content or layout changed we need to update our tree view to reflect the changes
1816 if (contentChanged || layoutChanged)
1818 UpdateClientTreeViewNode(client);
1822 Debug.Print("Layout changed");
1823 //Our layout has changed, if we are already the current client we need to update our panel
1824 if (aSessionId == iCurrentClientSessionId)
1826 //Apply layout and set data fields.
1827 UpdateTableLayoutPanel(iCurrentClientData);
1832 Debug.Print("Layout has not changed.");
1837 Debug.Print("WARNING: content and layout have not changed!");
1840 //When a client field is set we try switching to this client to present the new information to our user
1841 SetCurrentClient(aSessionId);
1847 /// <param name="aTexts"></param>
1848 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1850 if (this.InvokeRequired)
1852 //Not in the proper thread, invoke ourselves
1853 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1854 this.Invoke(d, new object[] { aSessionId, aFields });
1858 ClientData client = iClients[aSessionId];
1860 if (client.HasNewLayout)
1862 //TODO: Assert client.Count == 0
1863 //Our layout was just changed
1864 //Do some special handling to avoid re-creating our panel N times, once for each fields
1865 client.HasNewLayout = false;
1866 //Just set all our fields then
1867 client.Fields.AddRange(aFields);
1868 //Try switch to that client
1869 SetCurrentClient(aSessionId);
1871 //If we are updating the current client update our panel
1872 if (aSessionId == iCurrentClientSessionId)
1874 //Apply layout and set data fields.
1875 UpdateTableLayoutPanel(iCurrentClientData);
1878 UpdateClientTreeViewNode(client);
1882 //Put each our text fields in a label control
1883 foreach (DataField field in aFields)
1885 SetClientField(aSessionId, field);
1894 /// <param name="aSessionId"></param>
1895 /// <param name="aName"></param>
1896 public void SetClientNameThreadSafe(string aSessionId, string aName)
1898 if (this.InvokeRequired)
1900 //Not in the proper thread, invoke ourselves
1901 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1902 this.Invoke(d, new object[] { aSessionId, aName });
1906 //We are in the proper thread
1908 ClientData client = iClients[aSessionId];
1912 client.Name = aName;
1913 //Update our tree-view
1914 UpdateClientTreeViewNode(client);
1920 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1922 if (this.InvokeRequired)
1924 //Not in the proper thread, invoke ourselves
1925 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1926 this.Invoke(d, new object[] { aSessionId, aPriority });
1930 //We are in the proper thread
1932 ClientData client = iClients[aSessionId];
1936 client.Priority = aPriority;
1937 //Update our tree-view
1938 UpdateClientTreeViewNode(client);
1939 //Change our current client as per new priority
1940 ClientData newCurrentClient = FindHighestPriorityClient();
1941 if (newCurrentClient!=null)
1943 SetCurrentClient(newCurrentClient.SessionId);
1952 /// <param name="value"></param>
1953 /// <param name="maxChars"></param>
1954 /// <returns></returns>
1955 public static string Truncate(string value, int maxChars)
1957 return value.Length <= maxChars ? value : value.Substring(0, maxChars-3) + "...";
1961 /// Update our recording notification.
1963 private void UpdateRecordingNotification()
1966 bool activeRecording = false;
1968 RecordingField recField=new RecordingField();
1969 foreach (var client in iClients)
1971 RecordingField rec=(RecordingField)client.Value.FindSameFieldAs(recField);
1972 if (rec!=null && rec.IsActive)
1974 activeRecording = true;
1975 //Don't break cause we are collecting the names/texts.
1976 if (!String.IsNullOrEmpty(rec.Text))
1978 text += (rec.Text + "\n");
1982 //Not text for that recording, use client name instead
1983 text += client.Value.Name + " recording\n";
1989 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1990 iRecordingNotification.Text = Truncate(text,63);
1992 //Change visibility of notification if needed
1993 if (iRecordingNotification.Visible != activeRecording)
1995 iRecordingNotification.Visible = activeRecording;
1996 //Assuming the notification icon is in sync with our display icon
1997 //Take care of our REC icon
1998 if (iDisplay.IsOpen())
2000 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
2008 /// <param name="aClient"></param>
2009 private void UpdateClientTreeViewNode(ClientData aClient)
2011 Debug.Print("UpdateClientTreeViewNode");
2013 if (aClient == null)
2018 //Hook in record icon update too
2019 UpdateRecordingNotification();
2021 TreeNode node = null;
2022 //Check that our client node already exists
2023 //Get our client root node using its key which is our session ID
2024 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
2025 if (nodes.Count()>0)
2027 //We already have a node for that client
2029 //Clear children as we are going to recreate them below
2034 //Client node does not exists create a new one
2035 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
2036 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
2042 if (!String.IsNullOrEmpty(aClient.Name))
2044 //We have a name, use it as text for our root node
2045 node.Text = aClient.Name;
2046 //Add a child with SessionId
2047 node.Nodes.Add(new TreeNode(aClient.SessionId));
2051 //No name, use session ID instead
2052 node.Text = aClient.SessionId;
2055 //Display client priority
2056 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2058 if (aClient.Fields.Count > 0)
2060 //Create root node for our texts
2061 TreeNode textsRoot = new TreeNode("Fields");
2062 node.Nodes.Add(textsRoot);
2063 //For each text add a new entry
2064 foreach (DataField field in aClient.Fields)
2066 if (field.IsTextField)
2068 TextField textField = (TextField)field;
2069 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2071 else if (field.IsBitmapField)
2073 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2075 else if (field.IsRecordingField)
2077 RecordingField recordingField = (RecordingField)field;
2078 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2088 /// Update our table layout row styles to make sure each rows have similar height
2090 private void UpdateTableLayoutRowStyles()
2092 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2094 rowStyle.SizeType = SizeType.Percent;
2095 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
2100 /// Update our display table layout.
2101 /// Will instanciated every field control as defined by our client.
2102 /// Fields must be specified by rows from the left.
2104 /// <param name="aLayout"></param>
2105 private void UpdateTableLayoutPanel(ClientData aClient)
2107 Debug.Print("UpdateTableLayoutPanel");
2109 if (aClient == null)
2116 TableLayout layout = aClient.Layout;
2118 //First clean our current panel
2119 iTableLayoutPanel.Controls.Clear();
2120 iTableLayoutPanel.RowStyles.Clear();
2121 iTableLayoutPanel.ColumnStyles.Clear();
2122 iTableLayoutPanel.RowCount = 0;
2123 iTableLayoutPanel.ColumnCount = 0;
2125 //Then recreate our rows...
2126 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2128 iTableLayoutPanel.RowCount++;
2132 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2134 iTableLayoutPanel.ColumnCount++;
2138 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2140 //Create our column styles
2141 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2144 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2148 //Create our row styles
2149 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2159 foreach (DataField field in aClient.Fields)
2161 if (!field.IsTableField)
2163 //That field is not taking part in our table layout skip it
2167 TableField tableField = (TableField)field;
2169 //Create a control corresponding to the field specified for that cell
2170 Control control = CreateControlForDataField(tableField);
2172 //Add newly created control to our table layout at the specified row and column
2173 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2174 //Make sure we specify column and row span for that new control
2175 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2176 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2184 /// Check our type of data field and create corresponding control
2186 /// <param name="aField"></param>
2187 private Control CreateControlForDataField(DataField aField)
2189 Control control=null;
2190 if (aField.IsTextField)
2192 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2193 label.AutoEllipsis = true;
2194 label.AutoSize = true;
2195 label.BackColor = System.Drawing.Color.Transparent;
2196 label.Dock = System.Windows.Forms.DockStyle.Fill;
2197 label.Location = new System.Drawing.Point(1, 1);
2198 label.Margin = new System.Windows.Forms.Padding(0);
2199 label.Name = "marqueeLabel" + aField;
2200 label.OwnTimer = false;
2201 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2202 label.Separator = cds.Separator;
2203 label.MinFontSize = cds.MinFontSize;
2204 label.ScaleToFit = cds.ScaleToFit;
2205 //control.Size = new System.Drawing.Size(254, 30);
2206 //control.TabIndex = 2;
2207 label.Font = cds.Font;
2209 TextField field = (TextField)aField;
2210 label.TextAlign = field.Alignment;
2211 label.UseCompatibleTextRendering = true;
2212 label.Text = field.Text;
2216 else if (aField.IsBitmapField)
2218 //Create picture box
2219 PictureBox picture = new PictureBox();
2220 picture.AutoSize = true;
2221 picture.BackColor = System.Drawing.Color.Transparent;
2222 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2223 picture.Location = new System.Drawing.Point(1, 1);
2224 picture.Margin = new System.Windows.Forms.Padding(0);
2225 picture.Name = "pictureBox" + aField;
2227 BitmapField field = (BitmapField)aField;
2228 picture.Image = field.Bitmap;
2232 //TODO: Handle recording field?
2238 /// Called when the user selected a new display type.
2240 /// <param name="sender"></param>
2241 /// <param name="e"></param>
2242 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2244 //Store the selected display type in our settings
2245 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2246 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2247 Properties.Settings.Default.Save();
2249 //Try re-opening the display connection if we were already connected.
2250 //Otherwise just update our status to reflect display type change.
2251 if (iDisplay.IsOpen())
2253 OpenDisplayConnection();
2261 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2263 if (maskedTextBoxTimerInterval.Text != "")
2265 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2269 timer.Interval = interval;
2270 cds.TimerInterval = timer.Interval;
2271 Properties.Settings.Default.Save();
2276 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2278 if (maskedTextBoxMinFontSize.Text != "")
2280 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2282 if (minFontSize > 0)
2284 cds.MinFontSize = minFontSize;
2285 Properties.Settings.Default.Save();
2286 //We need to recreate our layout for that change to take effect
2287 UpdateTableLayoutPanel(iCurrentClientData);
2293 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2295 if (maskedTextBoxScrollingSpeed.Text != "")
2297 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2299 if (scrollingSpeed > 0)
2301 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2302 Properties.Settings.Default.Save();
2303 //We need to recreate our layout for that change to take effect
2304 UpdateTableLayoutPanel(iCurrentClientData);
2309 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2311 cds.Separator = textBoxScrollLoopSeparator.Text;
2312 Properties.Settings.Default.Save();
2314 //Update our text fields
2315 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2317 ctrl.Separator = cds.Separator;
2322 private void buttonPowerOn_Click(object sender, EventArgs e)
2327 private void buttonPowerOff_Click(object sender, EventArgs e)
2329 iDisplay.PowerOff();
2332 private void buttonShowClock_Click(object sender, EventArgs e)
2337 private void buttonHideClock_Click(object sender, EventArgs e)
2342 private void buttonUpdate_Click(object sender, EventArgs e)
2344 InstallUpdateSyncWithInfo();
2352 if (!iDisplay.IsOpen())
2357 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2358 iSkipFrameRendering = true;
2361 iDisplay.SwapBuffers();
2362 //Then show our clock
2363 iDisplay.ShowClock();
2371 if (!iDisplay.IsOpen())
2376 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2377 iSkipFrameRendering = false;
2378 iDisplay.HideClock();
2381 private void InstallUpdateSyncWithInfo()
2383 UpdateCheckInfo info = null;
2385 if (ApplicationDeployment.IsNetworkDeployed)
2387 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2391 info = ad.CheckForDetailedUpdate();
2394 catch (DeploymentDownloadException dde)
2396 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);
2399 catch (InvalidDeploymentException ide)
2401 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);
2404 catch (InvalidOperationException ioe)
2406 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2410 if (info.UpdateAvailable)
2412 Boolean doUpdate = true;
2414 if (!info.IsUpdateRequired)
2416 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2417 if (!(DialogResult.OK == dr))
2424 // Display a message that the application MUST reboot. Display the minimum required version.
2425 MessageBox.Show("This application has detected a mandatory update from your current " +
2426 "version to version " + info.MinimumRequiredVersion.ToString() +
2427 ". The application will now install the update and restart.",
2428 "Update Available", MessageBoxButtons.OK,
2429 MessageBoxIcon.Information);
2437 MessageBox.Show("The application has been upgraded, and will now restart.");
2438 Application.Restart();
2440 catch (DeploymentDownloadException dde)
2442 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2449 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2458 private void SysTrayHideShow()
2464 WindowState = FormWindowState.Normal;
2469 /// Use to handle minimize events.
2471 /// <param name="sender"></param>
2472 /// <param name="e"></param>
2473 private void MainForm_SizeChanged(object sender, EventArgs e)
2475 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2487 /// <param name="sender"></param>
2488 /// <param name="e"></param>
2489 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2491 //Our table layout size has changed which means our display size has changed.
2492 //We need to re-create our bitmap.
2493 iCreateBitmap = true;
2499 /// <param name="sender"></param>
2500 /// <param name="e"></param>
2501 private void buttonSelectFile_Click(object sender, EventArgs e)
2503 //openFileDialog1.InitialDirectory = "c:\\";
2504 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2505 //openFileDialog.FilterIndex = 1;
2506 openFileDialog.RestoreDirectory = true;
2508 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2510 labelStartFileName.Text = openFileDialog.FileName;
2511 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2512 Properties.Settings.Default.Save();
2519 /// <param name="sender"></param>
2520 /// <param name="e"></param>
2521 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2523 //Save the optical drive the user selected for ejection
2524 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2525 Properties.Settings.Default.Save();
2532 private void LogsUpdate()
2534 if (iWriter != null)
2542 /// Broadcast messages to subscribers.
2544 /// <param name="message"></param>
2545 protected override void WndProc(ref Message aMessage)
2549 if (OnWndProc!=null)
2551 OnWndProc(ref aMessage);
2554 base.WndProc(ref aMessage);
2557 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2559 //Save CEC enabled status
2560 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2561 Properties.Settings.Default.Save();
2566 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2568 //Save CEC HDMI port
2569 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2570 Properties.Settings.Default.CecHdmiPort++;
2571 Properties.Settings.Default.Save();
2576 private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
2578 Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
2579 Properties.Settings.Default.Save();
2584 private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
2586 Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
2587 Properties.Settings.Default.Save();
2592 private void checkBoxCecReconnectToPowerTv_CheckedChanged(object sender, EventArgs e)
2594 Properties.Settings.Default.CecReconnectToPowerTv = checkBoxCecReconnectToPowerTv.Checked;
2595 Properties.Settings.Default.Save();
2603 private void ResetCec()
2605 if (iCecManager==null)
2607 //Thus skipping initial UI setup
2613 if (Properties.Settings.Default.CecEnabled)
2615 iCecManager.Start(Handle, "CEC",
2616 Properties.Settings.Default.CecHdmiPort,
2617 Properties.Settings.Default.CecMonitorOn,
2618 Properties.Settings.Default.CecMonitorOff,
2619 Properties.Settings.Default.CecReconnectToPowerTv);
2628 private void SetupCecLogLevel()
2631 iCecManager.Client.LogLevel = 0;
2633 if (checkBoxCecLogError.Checked)
2634 iCecManager.Client.LogLevel |= (int)CecLogLevel.Error;
2636 if (checkBoxCecLogWarning.Checked)
2637 iCecManager.Client.LogLevel |= (int)CecLogLevel.Warning;
2639 if (checkBoxCecLogNotice.Checked)
2640 iCecManager.Client.LogLevel |= (int)CecLogLevel.Notice;
2642 if (checkBoxCecLogTraffic.Checked)
2643 iCecManager.Client.LogLevel |= (int)CecLogLevel.Traffic;
2645 if (checkBoxCecLogDebug.Checked)
2646 iCecManager.Client.LogLevel |= (int)CecLogLevel.Debug;
2648 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2652 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2657 private void buttonClearLogs_Click(object sender, EventArgs e)
2659 richTextBoxLogs.Clear();
2662 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)
2667 private void buttonAddAction_Click(object sender, EventArgs e)
2669 if (iTreeViewEvents.SelectedNode==null)
2674 Event earEvent = (Event)iTreeViewEvents.SelectedNode.Tag;
2675 if (earEvent == null)
2677 //Must select event node
2681 FormEditAction ea = new FormEditAction();
2682 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2683 if (res == DialogResult.OK)
2685 earEvent.Actions.Add(ea.Action);
2686 Properties.Settings.Default.Actions = ManagerEventAction.Current;
2687 Properties.Settings.Default.Save();