Adding a bunch of CEC actions.
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 // Add our actions for that event
319 foreach (SharpLib.Ear.Action a in e.Actions)
321 TreeNode actionNode = actionsNodes.Nodes.Add(a.Name);
326 iTreeViewEvents.ExpandAll();
331 /// Called when our display is closed.
333 /// <param name="aDisplay"></param>
334 private void OnDisplayClosed(Display aDisplay)
336 //Our display was just closed, update our UI consequently
340 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
342 //Update network status
343 UpdateNetworkStatus();
347 /// Update our Network Status
349 private void UpdateNetworkStatus()
351 if (iDisplay.IsOpen())
353 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
354 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
359 int iLastNetworkIconIndex = 0;
360 int iUpdateCountSinceLastNetworkAnimation = 0;
365 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
367 iUpdateCountSinceLastNetworkAnimation++;
368 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
370 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
372 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
375 //Prevents div by zero and other undefined behavior
378 iLastNetworkIconIndex++;
379 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
380 for (int i=0;i<iconCount;i++)
382 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
384 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
388 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
397 /// Receive volume change notification and reflect changes on our slider.
399 /// <param name="data"></param>
400 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
402 UpdateMasterVolumeThreadSafe();
406 /// Update master volume when user moves our slider.
408 /// <param name="sender"></param>
409 /// <param name="e"></param>
410 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
412 //Just like Windows Volume Mixer we unmute if the volume is adjusted
413 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
414 //Set volume level according to our volume slider new position
415 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
420 /// Mute check box changed.
422 /// <param name="sender"></param>
423 /// <param name="e"></param>
424 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
426 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
430 /// Device State Changed
432 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
437 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
442 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
445 /// Default Device Changed
447 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
449 if (role == Role.Multimedia && flow == DataFlow.Render)
451 UpdateAudioDeviceAndMasterVolumeThreadSafe();
456 /// Property Value Changed
458 /// <param name="pwstrDeviceId"></param>
459 /// <param name="key"></param>
460 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
466 /// Update master volume indicators based our current system states.
467 /// This typically includes volume levels and mute status.
469 private void UpdateMasterVolumeThreadSafe()
471 if (this.InvokeRequired)
473 //Not in the proper thread, invoke ourselves
474 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
475 this.Invoke(d, new object[] { });
479 //Update volume slider
480 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
481 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
482 //Update mute checkbox
483 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
485 //If our display connection is open we need to update its icons
486 if (iDisplay.IsOpen())
488 //First take care our our volume level icons
489 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
490 if (volumeIconCount > 0)
492 //Compute current volume level from system level and the number of segments in our display volume bar.
493 //That tells us how many segments in our volume bar needs to be turned on.
494 float currentVolume = volumeLevelScalar * volumeIconCount;
495 int segmentOnCount = Convert.ToInt32(currentVolume);
496 //Check if our segment count was rounded up, this will later be used for half brightness segment
497 bool roundedUp = segmentOnCount > currentVolume;
499 for (int i = 0; i < volumeIconCount; i++)
501 if (i < segmentOnCount)
503 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
504 if (i == segmentOnCount - 1 && roundedUp)
507 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
512 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
517 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
522 //Take care of our mute icon
523 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
531 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
533 if (this.InvokeRequired)
535 //Not in the proper thread, invoke ourselves
536 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
537 this.Invoke(d, new object[] { });
541 //We are in the correct thread just go ahead.
544 //Get our master volume
545 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
547 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
549 //Show our volume in our track bar
550 UpdateMasterVolumeThreadSafe();
552 //Register to get volume modifications
553 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
555 trackBarMasterVolume.Enabled = true;
559 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
560 Debug.WriteLine(ex.ToString());
561 //Something went wrong S/PDIF device ca throw exception I guess
562 trackBarMasterVolume.Enabled = false;
569 private void PopulateDeviceTypes()
571 int count = Display.TypeCount();
573 for (int i = 0; i < count; i++)
575 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
582 private void PopulateOpticalDrives()
584 //Reset our list of drives
585 comboBoxOpticalDrives.Items.Clear();
586 comboBoxOpticalDrives.Items.Add("None");
588 //Go through each drives on our system and collected the optical ones in our list
589 DriveInfo[] allDrives = DriveInfo.GetDrives();
590 foreach (DriveInfo d in allDrives)
592 Debug.WriteLine("Drive " + d.Name);
593 Debug.WriteLine(" Drive type: {0}", d.DriveType);
595 if (d.DriveType==DriveType.CDRom)
597 //This is an optical drive, add it now
598 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
606 /// <returns></returns>
607 public string OpticalDriveToEject()
609 return comboBoxOpticalDrives.SelectedItem.ToString();
617 private void SetupTrayIcon()
619 iNotifyIcon.Icon = GetIcon("vfd.ico");
620 iNotifyIcon.Text = "Sharp Display Manager";
621 iNotifyIcon.Visible = true;
623 //Double click toggles visibility - typically brings up the application
624 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
629 //Adding a context menu, useful to be able to exit the application
630 ContextMenu contextMenu = new ContextMenu();
631 //Context menu item to toggle visibility
632 MenuItem hideShowItem = new MenuItem("Hide/Show");
633 hideShowItem.Click += delegate(object obj, EventArgs args)
637 contextMenu.MenuItems.Add(hideShowItem);
639 //Context menu item separator
640 contextMenu.MenuItems.Add(new MenuItem("-"));
642 //Context menu exit item
643 MenuItem exitItem = new MenuItem("Exit");
644 exitItem.Click += delegate(object obj, EventArgs args)
648 contextMenu.MenuItems.Add(exitItem);
650 iNotifyIcon.ContextMenu = contextMenu;
656 private void SetupRecordingNotification()
658 iRecordingNotification.Icon = GetIcon("record.ico");
659 iRecordingNotification.Text = "No recording";
660 iRecordingNotification.Visible = false;
664 /// Access icons from embedded resources.
666 /// <param name="aName"></param>
667 /// <returns></returns>
668 public static Icon GetIcon(string aName)
670 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
671 foreach (string name in names)
673 //Find a resource name that ends with the given name
674 if (name.EndsWith(aName))
676 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
678 return new Icon(stream);
688 /// Set our current client.
689 /// This will take care of applying our client layout and set data fields.
691 /// <param name="aSessionId"></param>
692 void SetCurrentClient(string aSessionId, bool aForce=false)
694 if (aSessionId == iCurrentClientSessionId)
696 //Given client is already the current one.
697 //Don't bother changing anything then.
701 ClientData requestedClientData = iClients[aSessionId];
703 //Check when was the last time we switched to that client
704 if (iCurrentClientData != null)
706 //Do not switch client if priority of current client is higher
707 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
713 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
714 //TODO: put that hard coded value as a client property
715 //Clients should be able to define how often they can be interrupted
716 //Thus a background client can set this to zero allowing any other client to interrupt at any time
717 //We could also compute this delay by looking at the requests frequencies?
719 requestedClientData.Priority == iCurrentClientData.Priority && //Time sharing is only if clients have the same priority
720 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
722 //Don't switch clients too often
727 //Set current client ID.
728 iCurrentClientSessionId = aSessionId;
729 //Set the time we last switched to that client
730 iClients[aSessionId].LastSwitchTime = DateTime.Now;
731 //Fetch and set current client data.
732 iCurrentClientData = requestedClientData;
733 //Apply layout and set data fields.
734 UpdateTableLayoutPanel(iCurrentClientData);
737 private void buttonFont_Click(object sender, EventArgs e)
739 //fontDialog.ShowColor = true;
740 //fontDialog.ShowApply = true;
741 fontDialog.ShowEffects = true;
742 fontDialog.Font = cds.Font;
744 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
746 //fontDialog.ShowHelp = true;
748 //fontDlg.MaxSize = 40;
749 //fontDlg.MinSize = 22;
751 //fontDialog.Parent = this;
752 //fontDialog.StartPosition = FormStartPosition.CenterParent;
754 //DlgBox.ShowDialog(fontDialog);
756 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
757 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
759 //Set the fonts to all our labels in our layout
760 foreach (Control ctrl in iTableLayoutPanel.Controls)
762 if (ctrl is MarqueeLabel)
764 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
769 cds.Font = fontDialog.Font;
770 Properties.Settings.Default.Save();
779 void CheckFontHeight()
781 //Show font height and width
782 labelFontHeight.Text = "Font height: " + cds.Font.Height;
783 float charWidth = IsFixedWidth(cds.Font);
784 if (charWidth == 0.0f)
786 labelFontWidth.Visible = false;
790 labelFontWidth.Visible = true;
791 labelFontWidth.Text = "Font width: " + charWidth;
794 MarqueeLabel label = null;
795 //Get the first label control we can find
796 foreach (Control ctrl in iTableLayoutPanel.Controls)
798 if (ctrl is MarqueeLabel)
800 label = (MarqueeLabel)ctrl;
805 //Now check font height and show a warning if needed.
806 if (label != null && label.Font.Height > label.Height)
808 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
809 labelWarning.Visible = true;
813 labelWarning.Visible = false;
818 private void buttonCapture_Click(object sender, EventArgs e)
820 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
821 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
822 //Bitmap bmpToSave = new Bitmap(bmp);
823 bmp.Save("D:\\capture.png");
825 ((MarqueeLabel)iTableLayoutPanel.Controls[0]).Text = "Captured";
828 string outputFileName = "d:\\capture.png";
829 using (MemoryStream memory = new MemoryStream())
831 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
833 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
834 byte[] bytes = memory.ToArray();
835 fs.Write(bytes, 0, bytes.Length);
842 private void CheckForRequestResults()
844 if (iDisplay.IsRequestPending())
846 switch (iDisplay.AttemptRequestCompletion())
848 case MiniDisplay.Request.FirmwareRevision:
849 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
850 //Issue next request then
851 iDisplay.RequestPowerSupplyStatus();
854 case MiniDisplay.Request.PowerSupplyStatus:
855 if (iDisplay.PowerSupplyStatus())
857 toolStripStatusLabelPower.Text = "ON";
861 toolStripStatusLabelPower.Text = "OFF";
863 //Issue next request then
864 iDisplay.RequestDeviceId();
867 case MiniDisplay.Request.DeviceId:
868 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
869 //No more request to issue
875 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
877 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
884 public static uint ColorUntouched(int aX, int aY, uint aPixel)
889 public static uint ColorInversed(int aX, int aY, uint aPixel)
894 public static uint ColorChessboard(int aX, int aY, uint aPixel)
896 if ((aX % 2 == 0) && (aY % 2 == 0))
900 else if ((aX % 2 != 0) && (aY % 2 != 0))
908 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
910 return aBmp.Width - aX - 1;
913 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
915 return iBmp.Height - aY - 1;
918 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
923 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
929 /// Select proper pixel delegates according to our current settings.
931 private void SetupPixelDelegates()
933 //Select our pixel processing routine
934 if (cds.InverseColors)
936 //iColorFx = ColorChessboard;
937 iColorFx = ColorInversed;
941 iColorFx = ColorWhiteIsOn;
944 //Select proper coordinate translation functions
945 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
946 if (cds.ReverseScreen)
948 iScreenX = ScreenReversedX;
949 iScreenY = ScreenReversedY;
959 //This is our timer tick responsible to perform our render
960 private void timer_Tick(object sender, EventArgs e)
962 //Not ideal cause this has nothing to do with display render
965 //Update our animations
966 DateTime NewTickTime = DateTime.Now;
968 UpdateNetworkSignal(LastTickTime, NewTickTime);
970 //Update animation for all our marquees
971 foreach (Control ctrl in iTableLayoutPanel.Controls)
973 if (ctrl is MarqueeLabel)
975 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
980 if (iDisplay.IsOpen())
982 CheckForRequestResults();
984 //Check if frame rendering is needed
985 //Typically used when showing clock
986 if (!iSkipFrameRendering)
991 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
992 iCreateBitmap = false;
994 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
995 //iBmp.Save("D:\\capture.png");
997 //Send it to our display
998 for (int i = 0; i < iBmp.Width; i++)
1000 for (int j = 0; j < iBmp.Height; j++)
1004 //Get our processed pixel coordinates
1005 int x = iScreenX(iBmp, i);
1006 int y = iScreenY(iBmp, j);
1008 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
1009 //Apply color effects
1010 color = iColorFx(x, y, color);
1012 iDisplay.SetPixel(x, y, color);
1017 iDisplay.SwapBuffers();
1021 //Compute instant FPS
1022 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
1024 LastTickTime = NewTickTime;
1029 /// Attempt to establish connection with our display hardware.
1031 private void OpenDisplayConnection()
1033 CloseDisplayConnection();
1035 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
1038 toolStripStatusLabelConnect.Text = "Connection error";
1042 private void CloseDisplayConnection()
1044 //Status will be updated upon receiving the closed event
1046 if (iDisplay == null || !iDisplay.IsOpen())
1051 //Do not clear if we gave up on rendering already.
1052 //This means we will keep on displaying clock on MDM166AA for instance.
1053 if (!iSkipFrameRendering)
1056 iDisplay.SwapBuffers();
1059 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1063 private void buttonOpen_Click(object sender, EventArgs e)
1065 OpenDisplayConnection();
1068 private void buttonClose_Click(object sender, EventArgs e)
1070 CloseDisplayConnection();
1073 private void buttonClear_Click(object sender, EventArgs e)
1076 iDisplay.SwapBuffers();
1079 private void buttonFill_Click(object sender, EventArgs e)
1082 iDisplay.SwapBuffers();
1085 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1087 cds.Brightness = trackBarBrightness.Value;
1088 Properties.Settings.Default.Save();
1089 iDisplay.SetBrightness(trackBarBrightness.Value);
1095 /// CDS stands for Current Display Settings
1097 private DisplaySettings cds
1101 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1102 if (settings == null)
1104 settings = new DisplaysSettings();
1106 Properties.Settings.Default.DisplaysSettings = settings;
1109 //Make sure all our settings have been created
1110 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1112 settings.Displays.Add(new DisplaySettings());
1115 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1116 return displaySettings;
1121 /// Check if the given font has a fixed character pitch.
1123 /// <param name="ft"></param>
1124 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1125 public float IsFixedWidth(Font ft)
1127 Graphics g = CreateGraphics();
1128 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1129 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1131 bool fixedWidth = true;
1133 foreach (char c in charSizes)
1134 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1146 /// Synchronize UI with settings
1148 private void UpdateStatus()
1151 checkBoxShowBorders.Checked = cds.ShowBorders;
1152 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1154 //Set the proper font to each of our labels
1155 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1157 ctrl.Font = cds.Font;
1161 //Check if "run on Windows startup" is enabled
1162 checkBoxAutoStart.Checked = iStartupManager.Startup;
1164 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1165 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1166 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1167 iCheckBoxStartIdleClient.Checked = Properties.Settings.Default.StartIdleClient;
1168 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1171 //Try find our drive in our drive list
1172 int opticalDriveItemIndex=0;
1173 bool driveNotFound = true;
1174 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1175 foreach (object item in comboBoxOpticalDrives.Items)
1177 if (opticalDriveToEject == item.ToString())
1179 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1180 driveNotFound = false;
1183 opticalDriveItemIndex++;
1188 //We could not find the drive we had saved.
1189 //Select "None" then.
1190 comboBoxOpticalDrives.SelectedIndex = 0;
1194 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1195 checkBoxCecMonitorOn.Checked = Properties.Settings.Default.CecMonitorOn;
1196 checkBoxCecMonitorOff.Checked = Properties.Settings.Default.CecMonitorOff;
1197 checkBoxCecReconnectToPowerTv.Checked = Properties.Settings.Default.CecReconnectToPowerTv;
1198 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1200 //Mini Display settings
1201 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1202 checkBoxInverseColors.Checked = cds.InverseColors;
1203 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1204 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1205 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1206 labelMinFontSize.Enabled = cds.ScaleToFit;
1207 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1208 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1209 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1210 timer.Interval = cds.TimerInterval;
1211 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1212 textBoxScrollLoopSeparator.Text = cds.Separator;
1214 SetupPixelDelegates();
1216 if (iDisplay.IsOpen())
1218 //We have a display connection
1219 //Reflect that in our UI
1222 iTableLayoutPanel.Enabled = true;
1223 panelDisplay.Enabled = true;
1225 //Only setup brightness if display is open
1226 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1227 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1228 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1230 //Brightness out of range, this can occur when using auto-detect
1231 //Use max brightness instead
1232 trackBarBrightness.Value = iDisplay.MaxBrightness();
1233 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1237 trackBarBrightness.Value = cds.Brightness;
1238 iDisplay.SetBrightness(cds.Brightness);
1241 //Try compute the steps to something that makes sense
1242 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1243 trackBarBrightness.SmallChange = 1;
1246 buttonFill.Enabled = true;
1247 buttonClear.Enabled = true;
1248 buttonOpen.Enabled = false;
1249 buttonClose.Enabled = true;
1250 trackBarBrightness.Enabled = true;
1251 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1252 //+ " - " + iDisplay.SerialNumber();
1254 if (iDisplay.SupportPowerOnOff())
1256 buttonPowerOn.Enabled = true;
1257 buttonPowerOff.Enabled = true;
1261 buttonPowerOn.Enabled = false;
1262 buttonPowerOff.Enabled = false;
1265 if (iDisplay.SupportClock())
1267 buttonShowClock.Enabled = true;
1268 buttonHideClock.Enabled = true;
1272 buttonShowClock.Enabled = false;
1273 buttonHideClock.Enabled = false;
1277 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1278 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1280 if (cds.ShowVolumeLabel)
1282 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1286 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1291 //Display connection not available
1292 //Reflect that in our UI
1294 //In debug start our timer even if we don't have a display connection
1297 //In production environment we don't need our timer if no display connection
1300 checkBoxShowVolumeLabel.Enabled = false;
1301 iTableLayoutPanel.Enabled = false;
1302 panelDisplay.Enabled = false;
1303 buttonFill.Enabled = false;
1304 buttonClear.Enabled = false;
1305 buttonOpen.Enabled = true;
1306 buttonClose.Enabled = false;
1307 trackBarBrightness.Enabled = false;
1308 buttonPowerOn.Enabled = false;
1309 buttonPowerOff.Enabled = false;
1310 buttonShowClock.Enabled = false;
1311 buttonHideClock.Enabled = false;
1312 toolStripStatusLabelConnect.Text = "Disconnected";
1313 toolStripStatusLabelPower.Text = "N/A";
1322 /// <param name="sender"></param>
1323 /// <param name="e"></param>
1324 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1326 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1327 Properties.Settings.Default.Save();
1331 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1333 //Save our show borders setting
1334 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1335 cds.ShowBorders = checkBoxShowBorders.Checked;
1336 Properties.Settings.Default.Save();
1340 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1342 //Save our connect on startup setting
1343 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1344 Properties.Settings.Default.Save();
1347 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1349 //Save our "Minimize to tray" setting
1350 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1351 Properties.Settings.Default.Save();
1355 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1357 //Save our "Start minimized" setting
1358 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1359 Properties.Settings.Default.Save();
1362 private void checkBoxStartIdleClient_CheckedChanged(object sender, EventArgs e)
1364 Properties.Settings.Default.StartIdleClient = iCheckBoxStartIdleClient.Checked;
1365 Properties.Settings.Default.Save();
1368 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1370 iStartupManager.Startup = checkBoxAutoStart.Checked;
1374 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1376 //Save our reverse screen setting
1377 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1378 Properties.Settings.Default.Save();
1379 SetupPixelDelegates();
1382 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1384 //Save our inverse colors setting
1385 cds.InverseColors = checkBoxInverseColors.Checked;
1386 Properties.Settings.Default.Save();
1387 SetupPixelDelegates();
1390 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1392 //Save our scale to fit setting
1393 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1394 Properties.Settings.Default.Save();
1396 labelMinFontSize.Enabled = cds.ScaleToFit;
1397 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1400 private void MainForm_Resize(object sender, EventArgs e)
1402 if (WindowState == FormWindowState.Minimized)
1404 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1405 // That's apparently not needed on Windows 10 but we better leave it in place.
1406 iCreateBitmap = true;
1410 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1413 iNetworkManager.Dispose();
1414 CloseDisplayConnection();
1416 e.Cancel = iClosing;
1419 public void StartServer()
1421 iServiceHost = new ServiceHost
1424 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1427 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1428 iServiceHost.Open();
1431 public void StopServer()
1433 if (iClients.Count > 0 && !iClosing)
1437 BroadcastCloseEvent();
1441 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1443 iClosing = false; //We make sure we force close if asked twice
1448 //We removed that as it often lags for some reason
1449 //iServiceHost.Close();
1453 public void BroadcastCloseEvent()
1455 Trace.TraceInformation("BroadcastCloseEvent - start");
1457 var inactiveClients = new List<string>();
1458 foreach (var client in iClients)
1460 //if (client.Key != eventData.ClientName)
1464 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1465 client.Value.Callback.OnCloseOrder(/*eventData*/);
1467 catch (Exception ex)
1469 inactiveClients.Add(client.Key);
1474 if (inactiveClients.Count > 0)
1476 foreach (var client in inactiveClients)
1478 iClients.Remove(client);
1479 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(client, false)[0]);
1483 if (iClients.Count==0)
1490 /// Just remove all our fields.
1492 private void ClearLayout()
1494 iTableLayoutPanel.Controls.Clear();
1495 iTableLayoutPanel.RowStyles.Clear();
1496 iTableLayoutPanel.ColumnStyles.Clear();
1497 iCurrentClientData = null;
1501 /// Just launch a demo client.
1503 private void StartNewClient(string aTopText = "", string aBottomText = "")
1505 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1506 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1507 clientThread.Start(myParams);
1512 /// Just launch our idle client.
1514 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1516 Thread clientThread = new Thread(SharpDisplayIdleClient.Program.MainWithParams);
1517 SharpDisplayIdleClient.StartParams myParams = new SharpDisplayIdleClient.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1518 clientThread.Start(myParams);
1523 private void buttonStartClient_Click(object sender, EventArgs e)
1528 private void buttonSuspend_Click(object sender, EventArgs e)
1533 private void StartTimer()
1535 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1536 timer.Enabled = true;
1537 UpdateSuspendButton();
1540 private void StopTimer()
1542 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1543 timer.Enabled = false;
1544 UpdateSuspendButton();
1547 private void ToggleTimer()
1549 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1550 timer.Enabled = !timer.Enabled;
1551 UpdateSuspendButton();
1554 private void UpdateSuspendButton()
1558 buttonSuspend.Text = "Run";
1562 buttonSuspend.Text = "Pause";
1567 private void buttonCloseClients_Click(object sender, EventArgs e)
1569 BroadcastCloseEvent();
1572 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1574 //Root node must have at least one child
1575 if (e.Node.Nodes.Count == 0)
1580 //If the selected node is the root node of a client then switch to it
1581 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1582 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1584 //We have a valid session just switch to that client
1585 SetCurrentClient(sessionId,true);
1594 /// <param name="aSessionId"></param>
1595 /// <param name="aCallback"></param>
1596 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1598 if (this.InvokeRequired)
1600 //Not in the proper thread, invoke ourselves
1601 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1602 this.Invoke(d, new object[] { aSessionId, aCallback });
1606 //We are in the proper thread
1607 //Add this session to our collection of clients
1608 ClientData newClient = new ClientData(aSessionId, aCallback);
1609 Program.iMainForm.iClients.Add(aSessionId, newClient);
1610 //Add this session to our UI
1611 UpdateClientTreeViewNode(newClient);
1617 /// Find the client with the highest priority if any.
1619 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1620 public ClientData FindHighestPriorityClient()
1622 ClientData highestPriorityClient = null;
1623 foreach (var client in iClients)
1625 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1627 highestPriorityClient = client.Value;
1631 return highestPriorityClient;
1637 /// <param name="aSessionId"></param>
1638 public void RemoveClientThreadSafe(string aSessionId)
1640 if (this.InvokeRequired)
1642 //Not in the proper thread, invoke ourselves
1643 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1644 this.Invoke(d, new object[] { aSessionId });
1648 //We are in the proper thread
1649 //Remove this session from both client collection and UI tree view
1650 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1652 Program.iMainForm.iClients.Remove(aSessionId);
1653 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1654 //Update recording status too whenever a client is removed
1655 UpdateRecordingNotification();
1658 if (iCurrentClientSessionId == aSessionId)
1660 //The current client is closing
1661 iCurrentClientData = null;
1662 //Find the client with the highest priority and set it as current
1663 ClientData newCurrentClient = FindHighestPriorityClient();
1664 if (newCurrentClient!=null)
1666 SetCurrentClient(newCurrentClient.SessionId, true);
1670 if (iClients.Count == 0)
1672 //Clear our screen when last client disconnects
1677 //We were closing our form
1678 //All clients are now closed
1679 //Just resume our close operation
1690 /// <param name="aSessionId"></param>
1691 /// <param name="aLayout"></param>
1692 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1694 if (this.InvokeRequired)
1696 //Not in the proper thread, invoke ourselves
1697 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1698 this.Invoke(d, new object[] { aSessionId, aLayout });
1702 ClientData client = iClients[aSessionId];
1705 //Don't change a thing if the layout is the same
1706 if (!client.Layout.IsSameAs(aLayout))
1708 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1709 //Set our client layout then
1710 client.Layout = aLayout;
1711 //So that next time we update all our fields at ones
1712 client.HasNewLayout = true;
1713 //Layout has changed clear our fields then
1714 client.Fields.Clear();
1716 UpdateClientTreeViewNode(client);
1720 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1729 /// <param name="aSessionId"></param>
1730 /// <param name="aField"></param>
1731 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1733 if (this.InvokeRequired)
1735 //Not in the proper thread, invoke ourselves
1736 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1737 this.Invoke(d, new object[] { aSessionId, aField });
1741 //We are in the proper thread
1742 //Call the non-thread-safe variant
1743 SetClientField(aSessionId, aField);
1751 /// Set a data field in the given client.
1753 /// <param name="aSessionId"></param>
1754 /// <param name="aField"></param>
1755 private void SetClientField(string aSessionId, DataField aField)
1757 //TODO: should check if the field actually changed?
1759 ClientData client = iClients[aSessionId];
1760 bool layoutChanged = false;
1761 bool contentChanged = true;
1763 //Fetch our field index
1764 int fieldIndex = client.FindSameFieldIndex(aField);
1768 //No corresponding field, just bail out
1772 //Keep our previous field in there
1773 DataField previousField = client.Fields[fieldIndex];
1774 //Just update that field then
1775 client.Fields[fieldIndex] = aField;
1777 if (!aField.IsTableField)
1779 //We are done then if that field is not in our table layout
1783 TableField tableField = (TableField) aField;
1785 if (previousField.IsSameLayout(aField))
1787 //If we are updating a field in our current client we need to update it in our panel
1788 if (aSessionId == iCurrentClientSessionId)
1790 Control ctrl=iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1791 if (aField.IsTextField && ctrl is MarqueeLabel)
1793 TextField textField=(TextField)aField;
1794 //Text field control already in place, just change the text
1795 MarqueeLabel label = (MarqueeLabel)ctrl;
1796 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1797 label.Text = textField.Text;
1798 label.TextAlign = textField.Alignment;
1800 else if (aField.IsBitmapField && ctrl is PictureBox)
1802 BitmapField bitmapField = (BitmapField)aField;
1803 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1804 //Bitmap field control already in place just change the bitmap
1805 PictureBox pictureBox = (PictureBox)ctrl;
1806 pictureBox.Image = bitmapField.Bitmap;
1810 layoutChanged = true;
1816 layoutChanged = true;
1819 //If either content or layout changed we need to update our tree view to reflect the changes
1820 if (contentChanged || layoutChanged)
1822 UpdateClientTreeViewNode(client);
1826 Debug.Print("Layout changed");
1827 //Our layout has changed, if we are already the current client we need to update our panel
1828 if (aSessionId == iCurrentClientSessionId)
1830 //Apply layout and set data fields.
1831 UpdateTableLayoutPanel(iCurrentClientData);
1836 Debug.Print("Layout has not changed.");
1841 Debug.Print("WARNING: content and layout have not changed!");
1844 //When a client field is set we try switching to this client to present the new information to our user
1845 SetCurrentClient(aSessionId);
1851 /// <param name="aTexts"></param>
1852 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1854 if (this.InvokeRequired)
1856 //Not in the proper thread, invoke ourselves
1857 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1858 this.Invoke(d, new object[] { aSessionId, aFields });
1862 ClientData client = iClients[aSessionId];
1864 if (client.HasNewLayout)
1866 //TODO: Assert client.Count == 0
1867 //Our layout was just changed
1868 //Do some special handling to avoid re-creating our panel N times, once for each fields
1869 client.HasNewLayout = false;
1870 //Just set all our fields then
1871 client.Fields.AddRange(aFields);
1872 //Try switch to that client
1873 SetCurrentClient(aSessionId);
1875 //If we are updating the current client update our panel
1876 if (aSessionId == iCurrentClientSessionId)
1878 //Apply layout and set data fields.
1879 UpdateTableLayoutPanel(iCurrentClientData);
1882 UpdateClientTreeViewNode(client);
1886 //Put each our text fields in a label control
1887 foreach (DataField field in aFields)
1889 SetClientField(aSessionId, field);
1898 /// <param name="aSessionId"></param>
1899 /// <param name="aName"></param>
1900 public void SetClientNameThreadSafe(string aSessionId, string aName)
1902 if (this.InvokeRequired)
1904 //Not in the proper thread, invoke ourselves
1905 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1906 this.Invoke(d, new object[] { aSessionId, aName });
1910 //We are in the proper thread
1912 ClientData client = iClients[aSessionId];
1916 client.Name = aName;
1917 //Update our tree-view
1918 UpdateClientTreeViewNode(client);
1924 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1926 if (this.InvokeRequired)
1928 //Not in the proper thread, invoke ourselves
1929 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1930 this.Invoke(d, new object[] { aSessionId, aPriority });
1934 //We are in the proper thread
1936 ClientData client = iClients[aSessionId];
1940 client.Priority = aPriority;
1941 //Update our tree-view
1942 UpdateClientTreeViewNode(client);
1943 //Change our current client as per new priority
1944 ClientData newCurrentClient = FindHighestPriorityClient();
1945 if (newCurrentClient!=null)
1947 SetCurrentClient(newCurrentClient.SessionId);
1956 /// <param name="value"></param>
1957 /// <param name="maxChars"></param>
1958 /// <returns></returns>
1959 public static string Truncate(string value, int maxChars)
1961 return value.Length <= maxChars ? value : value.Substring(0, maxChars-3) + "...";
1965 /// Update our recording notification.
1967 private void UpdateRecordingNotification()
1970 bool activeRecording = false;
1972 RecordingField recField=new RecordingField();
1973 foreach (var client in iClients)
1975 RecordingField rec=(RecordingField)client.Value.FindSameFieldAs(recField);
1976 if (rec!=null && rec.IsActive)
1978 activeRecording = true;
1979 //Don't break cause we are collecting the names/texts.
1980 if (!String.IsNullOrEmpty(rec.Text))
1982 text += (rec.Text + "\n");
1986 //Not text for that recording, use client name instead
1987 text += client.Value.Name + " recording\n";
1993 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1994 iRecordingNotification.Text = Truncate(text,63);
1996 //Change visibility of notification if needed
1997 if (iRecordingNotification.Visible != activeRecording)
1999 iRecordingNotification.Visible = activeRecording;
2000 //Assuming the notification icon is in sync with our display icon
2001 //Take care of our REC icon
2002 if (iDisplay.IsOpen())
2004 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
2012 /// <param name="aClient"></param>
2013 private void UpdateClientTreeViewNode(ClientData aClient)
2015 Debug.Print("UpdateClientTreeViewNode");
2017 if (aClient == null)
2022 //Hook in record icon update too
2023 UpdateRecordingNotification();
2025 TreeNode node = null;
2026 //Check that our client node already exists
2027 //Get our client root node using its key which is our session ID
2028 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
2029 if (nodes.Count()>0)
2031 //We already have a node for that client
2033 //Clear children as we are going to recreate them below
2038 //Client node does not exists create a new one
2039 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
2040 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
2046 if (!String.IsNullOrEmpty(aClient.Name))
2048 //We have a name, use it as text for our root node
2049 node.Text = aClient.Name;
2050 //Add a child with SessionId
2051 node.Nodes.Add(new TreeNode(aClient.SessionId));
2055 //No name, use session ID instead
2056 node.Text = aClient.SessionId;
2059 //Display client priority
2060 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2062 if (aClient.Fields.Count > 0)
2064 //Create root node for our texts
2065 TreeNode textsRoot = new TreeNode("Fields");
2066 node.Nodes.Add(textsRoot);
2067 //For each text add a new entry
2068 foreach (DataField field in aClient.Fields)
2070 if (field.IsTextField)
2072 TextField textField = (TextField)field;
2073 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2075 else if (field.IsBitmapField)
2077 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2079 else if (field.IsRecordingField)
2081 RecordingField recordingField = (RecordingField)field;
2082 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2092 /// Update our table layout row styles to make sure each rows have similar height
2094 private void UpdateTableLayoutRowStyles()
2096 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2098 rowStyle.SizeType = SizeType.Percent;
2099 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
2104 /// Update our display table layout.
2105 /// Will instanciated every field control as defined by our client.
2106 /// Fields must be specified by rows from the left.
2108 /// <param name="aLayout"></param>
2109 private void UpdateTableLayoutPanel(ClientData aClient)
2111 Debug.Print("UpdateTableLayoutPanel");
2113 if (aClient == null)
2120 TableLayout layout = aClient.Layout;
2122 //First clean our current panel
2123 iTableLayoutPanel.Controls.Clear();
2124 iTableLayoutPanel.RowStyles.Clear();
2125 iTableLayoutPanel.ColumnStyles.Clear();
2126 iTableLayoutPanel.RowCount = 0;
2127 iTableLayoutPanel.ColumnCount = 0;
2129 //Then recreate our rows...
2130 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2132 iTableLayoutPanel.RowCount++;
2136 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2138 iTableLayoutPanel.ColumnCount++;
2142 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2144 //Create our column styles
2145 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2148 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2152 //Create our row styles
2153 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2163 foreach (DataField field in aClient.Fields)
2165 if (!field.IsTableField)
2167 //That field is not taking part in our table layout skip it
2171 TableField tableField = (TableField)field;
2173 //Create a control corresponding to the field specified for that cell
2174 Control control = CreateControlForDataField(tableField);
2176 //Add newly created control to our table layout at the specified row and column
2177 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2178 //Make sure we specify column and row span for that new control
2179 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2180 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2188 /// Check our type of data field and create corresponding control
2190 /// <param name="aField"></param>
2191 private Control CreateControlForDataField(DataField aField)
2193 Control control=null;
2194 if (aField.IsTextField)
2196 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2197 label.AutoEllipsis = true;
2198 label.AutoSize = true;
2199 label.BackColor = System.Drawing.Color.Transparent;
2200 label.Dock = System.Windows.Forms.DockStyle.Fill;
2201 label.Location = new System.Drawing.Point(1, 1);
2202 label.Margin = new System.Windows.Forms.Padding(0);
2203 label.Name = "marqueeLabel" + aField;
2204 label.OwnTimer = false;
2205 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2206 label.Separator = cds.Separator;
2207 label.MinFontSize = cds.MinFontSize;
2208 label.ScaleToFit = cds.ScaleToFit;
2209 //control.Size = new System.Drawing.Size(254, 30);
2210 //control.TabIndex = 2;
2211 label.Font = cds.Font;
2213 TextField field = (TextField)aField;
2214 label.TextAlign = field.Alignment;
2215 label.UseCompatibleTextRendering = true;
2216 label.Text = field.Text;
2220 else if (aField.IsBitmapField)
2222 //Create picture box
2223 PictureBox picture = new PictureBox();
2224 picture.AutoSize = true;
2225 picture.BackColor = System.Drawing.Color.Transparent;
2226 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2227 picture.Location = new System.Drawing.Point(1, 1);
2228 picture.Margin = new System.Windows.Forms.Padding(0);
2229 picture.Name = "pictureBox" + aField;
2231 BitmapField field = (BitmapField)aField;
2232 picture.Image = field.Bitmap;
2236 //TODO: Handle recording field?
2242 /// Called when the user selected a new display type.
2244 /// <param name="sender"></param>
2245 /// <param name="e"></param>
2246 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2248 //Store the selected display type in our settings
2249 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2250 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2251 Properties.Settings.Default.Save();
2253 //Try re-opening the display connection if we were already connected.
2254 //Otherwise just update our status to reflect display type change.
2255 if (iDisplay.IsOpen())
2257 OpenDisplayConnection();
2265 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2267 if (maskedTextBoxTimerInterval.Text != "")
2269 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2273 timer.Interval = interval;
2274 cds.TimerInterval = timer.Interval;
2275 Properties.Settings.Default.Save();
2280 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2282 if (maskedTextBoxMinFontSize.Text != "")
2284 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2286 if (minFontSize > 0)
2288 cds.MinFontSize = minFontSize;
2289 Properties.Settings.Default.Save();
2290 //We need to recreate our layout for that change to take effect
2291 UpdateTableLayoutPanel(iCurrentClientData);
2297 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2299 if (maskedTextBoxScrollingSpeed.Text != "")
2301 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2303 if (scrollingSpeed > 0)
2305 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2306 Properties.Settings.Default.Save();
2307 //We need to recreate our layout for that change to take effect
2308 UpdateTableLayoutPanel(iCurrentClientData);
2313 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2315 cds.Separator = textBoxScrollLoopSeparator.Text;
2316 Properties.Settings.Default.Save();
2318 //Update our text fields
2319 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2321 ctrl.Separator = cds.Separator;
2326 private void buttonPowerOn_Click(object sender, EventArgs e)
2331 private void buttonPowerOff_Click(object sender, EventArgs e)
2333 iDisplay.PowerOff();
2336 private void buttonShowClock_Click(object sender, EventArgs e)
2341 private void buttonHideClock_Click(object sender, EventArgs e)
2346 private void buttonUpdate_Click(object sender, EventArgs e)
2348 InstallUpdateSyncWithInfo();
2356 if (!iDisplay.IsOpen())
2361 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2362 iSkipFrameRendering = true;
2365 iDisplay.SwapBuffers();
2366 //Then show our clock
2367 iDisplay.ShowClock();
2375 if (!iDisplay.IsOpen())
2380 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2381 iSkipFrameRendering = false;
2382 iDisplay.HideClock();
2385 private void InstallUpdateSyncWithInfo()
2387 UpdateCheckInfo info = null;
2389 if (ApplicationDeployment.IsNetworkDeployed)
2391 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2395 info = ad.CheckForDetailedUpdate();
2398 catch (DeploymentDownloadException dde)
2400 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);
2403 catch (InvalidDeploymentException ide)
2405 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);
2408 catch (InvalidOperationException ioe)
2410 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2414 if (info.UpdateAvailable)
2416 Boolean doUpdate = true;
2418 if (!info.IsUpdateRequired)
2420 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2421 if (!(DialogResult.OK == dr))
2428 // Display a message that the application MUST reboot. Display the minimum required version.
2429 MessageBox.Show("This application has detected a mandatory update from your current " +
2430 "version to version " + info.MinimumRequiredVersion.ToString() +
2431 ". The application will now install the update and restart.",
2432 "Update Available", MessageBoxButtons.OK,
2433 MessageBoxIcon.Information);
2441 MessageBox.Show("The application has been upgraded, and will now restart.");
2442 Application.Restart();
2444 catch (DeploymentDownloadException dde)
2446 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2453 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2462 private void SysTrayHideShow()
2468 WindowState = FormWindowState.Normal;
2473 /// Use to handle minimize events.
2475 /// <param name="sender"></param>
2476 /// <param name="e"></param>
2477 private void MainForm_SizeChanged(object sender, EventArgs e)
2479 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2491 /// <param name="sender"></param>
2492 /// <param name="e"></param>
2493 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2495 //Our table layout size has changed which means our display size has changed.
2496 //We need to re-create our bitmap.
2497 iCreateBitmap = true;
2503 /// <param name="sender"></param>
2504 /// <param name="e"></param>
2505 private void buttonSelectFile_Click(object sender, EventArgs e)
2507 //openFileDialog1.InitialDirectory = "c:\\";
2508 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2509 //openFileDialog.FilterIndex = 1;
2510 openFileDialog.RestoreDirectory = true;
2512 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2514 labelStartFileName.Text = openFileDialog.FileName;
2515 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2516 Properties.Settings.Default.Save();
2523 /// <param name="sender"></param>
2524 /// <param name="e"></param>
2525 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2527 //Save the optical drive the user selected for ejection
2528 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2529 Properties.Settings.Default.Save();
2536 private void LogsUpdate()
2538 if (iWriter != null)
2546 /// Broadcast messages to subscribers.
2548 /// <param name="message"></param>
2549 protected override void WndProc(ref Message aMessage)
2553 if (OnWndProc!=null)
2555 OnWndProc(ref aMessage);
2558 base.WndProc(ref aMessage);
2561 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2563 //Save CEC enabled status
2564 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2565 Properties.Settings.Default.Save();
2570 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2572 //Save CEC HDMI port
2573 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2574 Properties.Settings.Default.CecHdmiPort++;
2575 Properties.Settings.Default.Save();
2580 private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
2582 Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
2583 Properties.Settings.Default.Save();
2588 private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
2590 Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
2591 Properties.Settings.Default.Save();
2596 private void checkBoxCecReconnectToPowerTv_CheckedChanged(object sender, EventArgs e)
2598 Properties.Settings.Default.CecReconnectToPowerTv = checkBoxCecReconnectToPowerTv.Checked;
2599 Properties.Settings.Default.Save();
2607 private void ResetCec()
2609 if (iCecManager==null)
2611 //Thus skipping initial UI setup
2617 if (Properties.Settings.Default.CecEnabled)
2619 iCecManager.Start(Handle, "CEC",
2620 Properties.Settings.Default.CecHdmiPort,
2621 Properties.Settings.Default.CecMonitorOn,
2622 Properties.Settings.Default.CecMonitorOff,
2623 Properties.Settings.Default.CecReconnectToPowerTv);
2632 private void SetupCecLogLevel()
2635 iCecManager.Client.LogLevel = 0;
2637 if (checkBoxCecLogError.Checked)
2638 iCecManager.Client.LogLevel |= (int)CecLogLevel.Error;
2640 if (checkBoxCecLogWarning.Checked)
2641 iCecManager.Client.LogLevel |= (int)CecLogLevel.Warning;
2643 if (checkBoxCecLogNotice.Checked)
2644 iCecManager.Client.LogLevel |= (int)CecLogLevel.Notice;
2646 if (checkBoxCecLogTraffic.Checked)
2647 iCecManager.Client.LogLevel |= (int)CecLogLevel.Traffic;
2649 if (checkBoxCecLogDebug.Checked)
2650 iCecManager.Client.LogLevel |= (int)CecLogLevel.Debug;
2652 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2656 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2661 private void buttonClearLogs_Click(object sender, EventArgs e)
2663 richTextBoxLogs.Clear();
2666 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)
2671 private void buttonAddAction_Click(object sender, EventArgs e)
2673 if (iTreeViewEvents.SelectedNode==null
2674 || !(iTreeViewEvents.SelectedNode.Tag is Event))
2679 Event earEvent = (Event)iTreeViewEvents.SelectedNode.Tag;
2680 if (earEvent == null)
2682 //Must select event node
2686 FormEditAction ea = new FormEditAction();
2687 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2688 if (res == DialogResult.OK)
2690 earEvent.Actions.Add(ea.Action);
2691 Properties.Settings.Default.Actions = ManagerEventAction.Current;
2692 Properties.Settings.Default.Save();
2697 private void buttonDeleteAction_Click(object sender, EventArgs e)
2699 if (iTreeViewEvents.SelectedNode == null
2700 || !(iTreeViewEvents.SelectedNode.Tag is SharpLib.Ear.Action))
2705 SharpLib.Ear.Action action = (SharpLib.Ear.Action)iTreeViewEvents.SelectedNode.Tag;
2708 //Must select action node
2712 ManagerEventAction.Current.RemoveAction(action);
2713 Properties.Settings.Default.Actions = ManagerEventAction.Current;
2714 Properties.Settings.Default.Save();