Adding Events tab.
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 EventActionManager iManager = new EventActionManager();
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 EventActionManager.Current = iManager;
135 iSkipFrameRendering = false;
137 iCurrentClientSessionId = "";
138 iCurrentClientData = null;
139 LastTickTime = DateTime.Now;
140 //Instantiate our display and register for events notifications
141 iDisplay = new Display();
142 iDisplay.OnOpened += OnDisplayOpened;
143 iDisplay.OnClosed += OnDisplayClosed;
145 iClients = new Dictionary<string, ClientData>();
146 iStartupManager = new StartupManager();
147 iNotifyIcon = new SharpLib.Notification.Control();
148 iRecordingNotification = new SharpLib.Notification.Control();
150 //Have our designer initialize its controls
151 InitializeComponent();
153 //Redirect console output
154 iWriter = new RichTextBoxTextWriter(richTextBoxLogs);
155 Console.SetOut(iWriter);
157 //Populate device types
158 PopulateDeviceTypes();
160 //Populate optical drives
161 PopulateOpticalDrives();
163 //Initial status update
166 //We have a bug when drawing minimized and reusing our bitmap
167 //Though I could not reproduce it on Windows 10
168 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
169 iCreateBitmap = false;
171 //Minimize our window if desired
172 if (Properties.Settings.Default.StartMinimized)
174 WindowState = FormWindowState.Minimized;
182 /// <param name="sender"></param>
183 /// <param name="e"></param>
184 private void MainForm_Load(object sender, EventArgs e)
186 //Check if we are running a Click Once deployed application
187 if (ApplicationDeployment.IsNetworkDeployed)
189 //This is a proper Click Once installation, fetch and show our version number
190 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
194 //Not a proper Click Once installation, assuming development build then
195 this.Text += " - development";
199 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
200 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
201 UpdateAudioDeviceAndMasterVolumeThreadSafe();
204 iNetworkManager = new NetworkManager();
205 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
206 UpdateNetworkStatus();
209 iCecManager = new ConsumerElectronicControl();
210 OnWndProc += iCecManager.OnWndProc;
216 //Setup notification icon
219 //Setup recording notification
220 SetupRecordingNotification();
222 // To make sure start up with minimize to tray works
223 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
229 //When not debugging we want the screen to be empty until a client takes over
232 //When developing we want at least one client for testing
233 StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
236 //Open display connection on start-up if needed
237 if (Properties.Settings.Default.DisplayConnectOnStartup)
239 OpenDisplayConnection();
242 //Start our server so that we can get client requests
245 //Register for HID events
246 RegisterHidDevices();
248 //Start Idle client if needed
249 if (Properties.Settings.Default.StartIdleClient)
256 /// Called when our display is opened.
258 /// <param name="aDisplay"></param>
259 private void OnDisplayOpened(Display aDisplay)
261 //Make sure we resume frame rendering
262 iSkipFrameRendering = false;
264 //Set our screen size now that our display is connected
265 //Our panelDisplay is the container of our tableLayoutPanel
266 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
267 //panelDisplay needs an extra 2 pixels for borders on each sides
268 //tableLayoutPanel will eventually be the exact size of our display
269 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
270 panelDisplay.Size = size;
272 //Our display was just opened, update our UI
274 //Initiate asynchronous request
275 iDisplay.RequestFirmwareRevision();
278 UpdateMasterVolumeThreadSafe();
280 UpdateNetworkStatus();
283 //Testing icon in debug, no arm done if icon not supported
284 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
285 //iDisplay.SetAllIconsStatus(2);
293 private void SetupEvents()
296 iTreeViewEvents.Nodes.Clear();
297 //Populate registered events
298 foreach (string key in EventActionManager.Current.Events.Keys)
300 Event e = EventActionManager.Current.Events[key];
301 TreeNode eventNode = iTreeViewEvents.Nodes.Add(key,e.Name);
303 eventNode.Nodes.Add(key + ".Description", e.Description);
304 TreeNode actionsNodes = eventNode.Nodes.Add(key + ".Actions", "Actions");
306 foreach (SharpLib.Ear.Action a in e.Actions)
308 actionsNodes.Nodes.Add(a.Name);
315 /// Called when our display is closed.
317 /// <param name="aDisplay"></param>
318 private void OnDisplayClosed(Display aDisplay)
320 //Our display was just closed, update our UI consequently
324 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
326 //Update network status
327 UpdateNetworkStatus();
331 /// Update our Network Status
333 private void UpdateNetworkStatus()
335 if (iDisplay.IsOpen())
337 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
338 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
343 int iLastNetworkIconIndex = 0;
344 int iUpdateCountSinceLastNetworkAnimation = 0;
349 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
351 iUpdateCountSinceLastNetworkAnimation++;
352 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
354 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
356 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
359 //Prevents div by zero and other undefined behavior
362 iLastNetworkIconIndex++;
363 iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
364 for (int i=0;i<iconCount;i++)
366 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
368 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
372 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
381 /// Receive volume change notification and reflect changes on our slider.
383 /// <param name="data"></param>
384 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
386 UpdateMasterVolumeThreadSafe();
390 /// Update master volume when user moves our slider.
392 /// <param name="sender"></param>
393 /// <param name="e"></param>
394 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
396 //Just like Windows Volume Mixer we unmute if the volume is adjusted
397 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
398 //Set volume level according to our volume slider new position
399 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
404 /// Mute check box changed.
406 /// <param name="sender"></param>
407 /// <param name="e"></param>
408 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
410 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
414 /// Device State Changed
416 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
421 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
426 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
429 /// Default Device Changed
431 public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
433 if (role == Role.Multimedia && flow == DataFlow.Render)
435 UpdateAudioDeviceAndMasterVolumeThreadSafe();
440 /// Property Value Changed
442 /// <param name="pwstrDeviceId"></param>
443 /// <param name="key"></param>
444 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
450 /// Update master volume indicators based our current system states.
451 /// This typically includes volume levels and mute status.
453 private void UpdateMasterVolumeThreadSafe()
455 if (this.InvokeRequired)
457 //Not in the proper thread, invoke ourselves
458 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
459 this.Invoke(d, new object[] { });
463 //Update volume slider
464 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
465 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
466 //Update mute checkbox
467 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
469 //If our display connection is open we need to update its icons
470 if (iDisplay.IsOpen())
472 //First take care our our volume level icons
473 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
474 if (volumeIconCount > 0)
476 //Compute current volume level from system level and the number of segments in our display volume bar.
477 //That tells us how many segments in our volume bar needs to be turned on.
478 float currentVolume = volumeLevelScalar * volumeIconCount;
479 int segmentOnCount = Convert.ToInt32(currentVolume);
480 //Check if our segment count was rounded up, this will later be used for half brightness segment
481 bool roundedUp = segmentOnCount > currentVolume;
483 for (int i = 0; i < volumeIconCount; i++)
485 if (i < segmentOnCount)
487 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
488 if (i == segmentOnCount - 1 && roundedUp)
491 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
496 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
501 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
506 //Take care of our mute icon
507 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
515 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
517 if (this.InvokeRequired)
519 //Not in the proper thread, invoke ourselves
520 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
521 this.Invoke(d, new object[] { });
525 //We are in the correct thread just go ahead.
528 //Get our master volume
529 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
531 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
533 //Show our volume in our track bar
534 UpdateMasterVolumeThreadSafe();
536 //Register to get volume modifications
537 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
539 trackBarMasterVolume.Enabled = true;
543 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
544 Debug.WriteLine(ex.ToString());
545 //Something went wrong S/PDIF device ca throw exception I guess
546 trackBarMasterVolume.Enabled = false;
553 private void PopulateDeviceTypes()
555 int count = Display.TypeCount();
557 for (int i = 0; i < count; i++)
559 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
566 private void PopulateOpticalDrives()
568 //Reset our list of drives
569 comboBoxOpticalDrives.Items.Clear();
570 comboBoxOpticalDrives.Items.Add("None");
572 //Go through each drives on our system and collected the optical ones in our list
573 DriveInfo[] allDrives = DriveInfo.GetDrives();
574 foreach (DriveInfo d in allDrives)
576 Debug.WriteLine("Drive " + d.Name);
577 Debug.WriteLine(" Drive type: {0}", d.DriveType);
579 if (d.DriveType==DriveType.CDRom)
581 //This is an optical drive, add it now
582 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
590 /// <returns></returns>
591 public string OpticalDriveToEject()
593 return comboBoxOpticalDrives.SelectedItem.ToString();
601 private void SetupTrayIcon()
603 iNotifyIcon.Icon = GetIcon("vfd.ico");
604 iNotifyIcon.Text = "Sharp Display Manager";
605 iNotifyIcon.Visible = true;
607 //Double click toggles visibility - typically brings up the application
608 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
613 //Adding a context menu, useful to be able to exit the application
614 ContextMenu contextMenu = new ContextMenu();
615 //Context menu item to toggle visibility
616 MenuItem hideShowItem = new MenuItem("Hide/Show");
617 hideShowItem.Click += delegate(object obj, EventArgs args)
621 contextMenu.MenuItems.Add(hideShowItem);
623 //Context menu item separator
624 contextMenu.MenuItems.Add(new MenuItem("-"));
626 //Context menu exit item
627 MenuItem exitItem = new MenuItem("Exit");
628 exitItem.Click += delegate(object obj, EventArgs args)
632 contextMenu.MenuItems.Add(exitItem);
634 iNotifyIcon.ContextMenu = contextMenu;
640 private void SetupRecordingNotification()
642 iRecordingNotification.Icon = GetIcon("record.ico");
643 iRecordingNotification.Text = "No recording";
644 iRecordingNotification.Visible = false;
648 /// Access icons from embedded resources.
650 /// <param name="aName"></param>
651 /// <returns></returns>
652 public static Icon GetIcon(string aName)
654 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
655 foreach (string name in names)
657 //Find a resource name that ends with the given name
658 if (name.EndsWith(aName))
660 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
662 return new Icon(stream);
672 /// Set our current client.
673 /// This will take care of applying our client layout and set data fields.
675 /// <param name="aSessionId"></param>
676 void SetCurrentClient(string aSessionId, bool aForce=false)
678 if (aSessionId == iCurrentClientSessionId)
680 //Given client is already the current one.
681 //Don't bother changing anything then.
685 ClientData requestedClientData = iClients[aSessionId];
687 //Check when was the last time we switched to that client
688 if (iCurrentClientData != null)
690 //Do not switch client if priority of current client is higher
691 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
697 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
698 //TODO: put that hard coded value as a client property
699 //Clients should be able to define how often they can be interrupted
700 //Thus a background client can set this to zero allowing any other client to interrupt at any time
701 //We could also compute this delay by looking at the requests frequencies?
703 requestedClientData.Priority == iCurrentClientData.Priority && //Time sharing is only if clients have the same priority
704 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
706 //Don't switch clients too often
711 //Set current client ID.
712 iCurrentClientSessionId = aSessionId;
713 //Set the time we last switched to that client
714 iClients[aSessionId].LastSwitchTime = DateTime.Now;
715 //Fetch and set current client data.
716 iCurrentClientData = requestedClientData;
717 //Apply layout and set data fields.
718 UpdateTableLayoutPanel(iCurrentClientData);
721 private void buttonFont_Click(object sender, EventArgs e)
723 //fontDialog.ShowColor = true;
724 //fontDialog.ShowApply = true;
725 fontDialog.ShowEffects = true;
726 fontDialog.Font = cds.Font;
728 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
730 //fontDialog.ShowHelp = true;
732 //fontDlg.MaxSize = 40;
733 //fontDlg.MinSize = 22;
735 //fontDialog.Parent = this;
736 //fontDialog.StartPosition = FormStartPosition.CenterParent;
738 //DlgBox.ShowDialog(fontDialog);
740 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
741 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
743 //Set the fonts to all our labels in our layout
744 foreach (Control ctrl in iTableLayoutPanel.Controls)
746 if (ctrl is MarqueeLabel)
748 ((MarqueeLabel)ctrl).Font = fontDialog.Font;
753 cds.Font = fontDialog.Font;
754 Properties.Settings.Default.Save();
763 void CheckFontHeight()
765 //Show font height and width
766 labelFontHeight.Text = "Font height: " + cds.Font.Height;
767 float charWidth = IsFixedWidth(cds.Font);
768 if (charWidth == 0.0f)
770 labelFontWidth.Visible = false;
774 labelFontWidth.Visible = true;
775 labelFontWidth.Text = "Font width: " + charWidth;
778 MarqueeLabel label = null;
779 //Get the first label control we can find
780 foreach (Control ctrl in iTableLayoutPanel.Controls)
782 if (ctrl is MarqueeLabel)
784 label = (MarqueeLabel)ctrl;
789 //Now check font height and show a warning if needed.
790 if (label != null && label.Font.Height > label.Height)
792 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
793 labelWarning.Visible = true;
797 labelWarning.Visible = false;
802 private void buttonCapture_Click(object sender, EventArgs e)
804 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
805 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
806 //Bitmap bmpToSave = new Bitmap(bmp);
807 bmp.Save("D:\\capture.png");
809 ((MarqueeLabel)iTableLayoutPanel.Controls[0]).Text = "Captured";
812 string outputFileName = "d:\\capture.png";
813 using (MemoryStream memory = new MemoryStream())
815 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
817 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
818 byte[] bytes = memory.ToArray();
819 fs.Write(bytes, 0, bytes.Length);
826 private void CheckForRequestResults()
828 if (iDisplay.IsRequestPending())
830 switch (iDisplay.AttemptRequestCompletion())
832 case MiniDisplay.Request.FirmwareRevision:
833 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
834 //Issue next request then
835 iDisplay.RequestPowerSupplyStatus();
838 case MiniDisplay.Request.PowerSupplyStatus:
839 if (iDisplay.PowerSupplyStatus())
841 toolStripStatusLabelPower.Text = "ON";
845 toolStripStatusLabelPower.Text = "OFF";
847 //Issue next request then
848 iDisplay.RequestDeviceId();
851 case MiniDisplay.Request.DeviceId:
852 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
853 //No more request to issue
859 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
861 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
868 public static uint ColorUntouched(int aX, int aY, uint aPixel)
873 public static uint ColorInversed(int aX, int aY, uint aPixel)
878 public static uint ColorChessboard(int aX, int aY, uint aPixel)
880 if ((aX % 2 == 0) && (aY % 2 == 0))
884 else if ((aX % 2 != 0) && (aY % 2 != 0))
892 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
894 return aBmp.Width - aX - 1;
897 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
899 return iBmp.Height - aY - 1;
902 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
907 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
913 /// Select proper pixel delegates according to our current settings.
915 private void SetupPixelDelegates()
917 //Select our pixel processing routine
918 if (cds.InverseColors)
920 //iColorFx = ColorChessboard;
921 iColorFx = ColorInversed;
925 iColorFx = ColorWhiteIsOn;
928 //Select proper coordinate translation functions
929 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
930 if (cds.ReverseScreen)
932 iScreenX = ScreenReversedX;
933 iScreenY = ScreenReversedY;
943 //This is our timer tick responsible to perform our render
944 private void timer_Tick(object sender, EventArgs e)
946 //Not ideal cause this has nothing to do with display render
949 //Update our animations
950 DateTime NewTickTime = DateTime.Now;
952 UpdateNetworkSignal(LastTickTime, NewTickTime);
954 //Update animation for all our marquees
955 foreach (Control ctrl in iTableLayoutPanel.Controls)
957 if (ctrl is MarqueeLabel)
959 ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
964 if (iDisplay.IsOpen())
966 CheckForRequestResults();
968 //Check if frame rendering is needed
969 //Typically used when showing clock
970 if (!iSkipFrameRendering)
975 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height, PixelFormat.Format32bppArgb);
976 iCreateBitmap = false;
978 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
979 //iBmp.Save("D:\\capture.png");
981 //Send it to our display
982 for (int i = 0; i < iBmp.Width; i++)
984 for (int j = 0; j < iBmp.Height; j++)
988 //Get our processed pixel coordinates
989 int x = iScreenX(iBmp, i);
990 int y = iScreenY(iBmp, j);
992 uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
993 //Apply color effects
994 color = iColorFx(x, y, color);
996 iDisplay.SetPixel(x, y, color);
1001 iDisplay.SwapBuffers();
1005 //Compute instant FPS
1006 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
1008 LastTickTime = NewTickTime;
1013 /// Attempt to establish connection with our display hardware.
1015 private void OpenDisplayConnection()
1017 CloseDisplayConnection();
1019 if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
1022 toolStripStatusLabelConnect.Text = "Connection error";
1026 private void CloseDisplayConnection()
1028 //Status will be updated upon receiving the closed event
1030 if (iDisplay == null || !iDisplay.IsOpen())
1035 //Do not clear if we gave up on rendering already.
1036 //This means we will keep on displaying clock on MDM166AA for instance.
1037 if (!iSkipFrameRendering)
1040 iDisplay.SwapBuffers();
1043 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1047 private void buttonOpen_Click(object sender, EventArgs e)
1049 OpenDisplayConnection();
1052 private void buttonClose_Click(object sender, EventArgs e)
1054 CloseDisplayConnection();
1057 private void buttonClear_Click(object sender, EventArgs e)
1060 iDisplay.SwapBuffers();
1063 private void buttonFill_Click(object sender, EventArgs e)
1066 iDisplay.SwapBuffers();
1069 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1071 cds.Brightness = trackBarBrightness.Value;
1072 Properties.Settings.Default.Save();
1073 iDisplay.SetBrightness(trackBarBrightness.Value);
1079 /// CDS stands for Current Display Settings
1081 private DisplaySettings cds
1085 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1086 if (settings == null)
1088 settings = new DisplaysSettings();
1090 Properties.Settings.Default.DisplaysSettings = settings;
1093 //Make sure all our settings have been created
1094 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1096 settings.Displays.Add(new DisplaySettings());
1099 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1100 return displaySettings;
1105 /// Check if the given font has a fixed character pitch.
1107 /// <param name="ft"></param>
1108 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1109 public float IsFixedWidth(Font ft)
1111 Graphics g = CreateGraphics();
1112 char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
1113 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1115 bool fixedWidth = true;
1117 foreach (char c in charSizes)
1118 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
1130 /// Synchronize UI with settings
1132 private void UpdateStatus()
1135 checkBoxShowBorders.Checked = cds.ShowBorders;
1136 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1138 //Set the proper font to each of our labels
1139 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1141 ctrl.Font = cds.Font;
1145 //Check if "run on Windows startup" is enabled
1146 checkBoxAutoStart.Checked = iStartupManager.Startup;
1148 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1149 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1150 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1151 iCheckBoxStartIdleClient.Checked = Properties.Settings.Default.StartIdleClient;
1152 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1155 //Try find our drive in our drive list
1156 int opticalDriveItemIndex=0;
1157 bool driveNotFound = true;
1158 string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
1159 foreach (object item in comboBoxOpticalDrives.Items)
1161 if (opticalDriveToEject == item.ToString())
1163 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1164 driveNotFound = false;
1167 opticalDriveItemIndex++;
1172 //We could not find the drive we had saved.
1173 //Select "None" then.
1174 comboBoxOpticalDrives.SelectedIndex = 0;
1178 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1179 checkBoxCecMonitorOn.Checked = Properties.Settings.Default.CecMonitorOn;
1180 checkBoxCecMonitorOff.Checked = Properties.Settings.Default.CecMonitorOff;
1181 checkBoxCecReconnectToPowerTv.Checked = Properties.Settings.Default.CecReconnectToPowerTv;
1182 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1184 //Mini Display settings
1185 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1186 checkBoxInverseColors.Checked = cds.InverseColors;
1187 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1188 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1189 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1190 labelMinFontSize.Enabled = cds.ScaleToFit;
1191 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1192 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1193 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1194 timer.Interval = cds.TimerInterval;
1195 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1196 textBoxScrollLoopSeparator.Text = cds.Separator;
1198 SetupPixelDelegates();
1200 if (iDisplay.IsOpen())
1202 //We have a display connection
1203 //Reflect that in our UI
1206 iTableLayoutPanel.Enabled = true;
1207 panelDisplay.Enabled = true;
1209 //Only setup brightness if display is open
1210 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1211 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1212 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1214 //Brightness out of range, this can occur when using auto-detect
1215 //Use max brightness instead
1216 trackBarBrightness.Value = iDisplay.MaxBrightness();
1217 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1221 trackBarBrightness.Value = cds.Brightness;
1222 iDisplay.SetBrightness(cds.Brightness);
1225 //Try compute the steps to something that makes sense
1226 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
1227 trackBarBrightness.SmallChange = 1;
1230 buttonFill.Enabled = true;
1231 buttonClear.Enabled = true;
1232 buttonOpen.Enabled = false;
1233 buttonClose.Enabled = true;
1234 trackBarBrightness.Enabled = true;
1235 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1236 //+ " - " + iDisplay.SerialNumber();
1238 if (iDisplay.SupportPowerOnOff())
1240 buttonPowerOn.Enabled = true;
1241 buttonPowerOff.Enabled = true;
1245 buttonPowerOn.Enabled = false;
1246 buttonPowerOff.Enabled = false;
1249 if (iDisplay.SupportClock())
1251 buttonShowClock.Enabled = true;
1252 buttonHideClock.Enabled = true;
1256 buttonShowClock.Enabled = false;
1257 buttonHideClock.Enabled = false;
1261 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1262 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
1264 if (cds.ShowVolumeLabel)
1266 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1270 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1275 //Display connection not available
1276 //Reflect that in our UI
1278 //In debug start our timer even if we don't have a display connection
1281 //In production environment we don't need our timer if no display connection
1284 checkBoxShowVolumeLabel.Enabled = false;
1285 iTableLayoutPanel.Enabled = false;
1286 panelDisplay.Enabled = false;
1287 buttonFill.Enabled = false;
1288 buttonClear.Enabled = false;
1289 buttonOpen.Enabled = true;
1290 buttonClose.Enabled = false;
1291 trackBarBrightness.Enabled = false;
1292 buttonPowerOn.Enabled = false;
1293 buttonPowerOff.Enabled = false;
1294 buttonShowClock.Enabled = false;
1295 buttonHideClock.Enabled = false;
1296 toolStripStatusLabelConnect.Text = "Disconnected";
1297 toolStripStatusLabelPower.Text = "N/A";
1306 /// <param name="sender"></param>
1307 /// <param name="e"></param>
1308 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1310 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1311 Properties.Settings.Default.Save();
1315 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1317 //Save our show borders setting
1318 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
1319 cds.ShowBorders = checkBoxShowBorders.Checked;
1320 Properties.Settings.Default.Save();
1324 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1326 //Save our connect on startup setting
1327 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1328 Properties.Settings.Default.Save();
1331 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1333 //Save our "Minimize to tray" setting
1334 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1335 Properties.Settings.Default.Save();
1339 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1341 //Save our "Start minimized" setting
1342 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1343 Properties.Settings.Default.Save();
1346 private void checkBoxStartIdleClient_CheckedChanged(object sender, EventArgs e)
1348 Properties.Settings.Default.StartIdleClient = iCheckBoxStartIdleClient.Checked;
1349 Properties.Settings.Default.Save();
1352 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1354 iStartupManager.Startup = checkBoxAutoStart.Checked;
1358 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1360 //Save our reverse screen setting
1361 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1362 Properties.Settings.Default.Save();
1363 SetupPixelDelegates();
1366 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1368 //Save our inverse colors setting
1369 cds.InverseColors = checkBoxInverseColors.Checked;
1370 Properties.Settings.Default.Save();
1371 SetupPixelDelegates();
1374 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1376 //Save our scale to fit setting
1377 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1378 Properties.Settings.Default.Save();
1380 labelMinFontSize.Enabled = cds.ScaleToFit;
1381 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1384 private void MainForm_Resize(object sender, EventArgs e)
1386 if (WindowState == FormWindowState.Minimized)
1388 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1389 // That's apparently not needed on Windows 10 but we better leave it in place.
1390 iCreateBitmap = true;
1394 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1397 iNetworkManager.Dispose();
1398 CloseDisplayConnection();
1400 e.Cancel = iClosing;
1403 public void StartServer()
1405 iServiceHost = new ServiceHost
1408 new Uri[] { new Uri("net.tcp://localhost:8001/") }
1411 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
1412 iServiceHost.Open();
1415 public void StopServer()
1417 if (iClients.Count > 0 && !iClosing)
1421 BroadcastCloseEvent();
1425 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
1427 iClosing = false; //We make sure we force close if asked twice
1432 //We removed that as it often lags for some reason
1433 //iServiceHost.Close();
1437 public void BroadcastCloseEvent()
1439 Trace.TraceInformation("BroadcastCloseEvent - start");
1441 var inactiveClients = new List<string>();
1442 foreach (var client in iClients)
1444 //if (client.Key != eventData.ClientName)
1448 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1449 client.Value.Callback.OnCloseOrder(/*eventData*/);
1451 catch (Exception ex)
1453 inactiveClients.Add(client.Key);
1458 if (inactiveClients.Count > 0)
1460 foreach (var client in inactiveClients)
1462 iClients.Remove(client);
1463 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(client, false)[0]);
1467 if (iClients.Count==0)
1474 /// Just remove all our fields.
1476 private void ClearLayout()
1478 iTableLayoutPanel.Controls.Clear();
1479 iTableLayoutPanel.RowStyles.Clear();
1480 iTableLayoutPanel.ColumnStyles.Clear();
1481 iCurrentClientData = null;
1485 /// Just launch a demo client.
1487 private void StartNewClient(string aTopText = "", string aBottomText = "")
1489 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1490 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
1491 clientThread.Start(myParams);
1496 /// Just launch our idle client.
1498 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1500 Thread clientThread = new Thread(SharpDisplayIdleClient.Program.MainWithParams);
1501 SharpDisplayIdleClient.StartParams myParams = new SharpDisplayIdleClient.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1502 clientThread.Start(myParams);
1507 private void buttonStartClient_Click(object sender, EventArgs e)
1512 private void buttonSuspend_Click(object sender, EventArgs e)
1517 private void StartTimer()
1519 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1520 timer.Enabled = true;
1521 UpdateSuspendButton();
1524 private void StopTimer()
1526 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1527 timer.Enabled = false;
1528 UpdateSuspendButton();
1531 private void ToggleTimer()
1533 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1534 timer.Enabled = !timer.Enabled;
1535 UpdateSuspendButton();
1538 private void UpdateSuspendButton()
1542 buttonSuspend.Text = "Run";
1546 buttonSuspend.Text = "Pause";
1551 private void buttonCloseClients_Click(object sender, EventArgs e)
1553 BroadcastCloseEvent();
1556 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1558 //Root node must have at least one child
1559 if (e.Node.Nodes.Count == 0)
1564 //If the selected node is the root node of a client then switch to it
1565 string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1566 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1568 //We have a valid session just switch to that client
1569 SetCurrentClient(sessionId,true);
1578 /// <param name="aSessionId"></param>
1579 /// <param name="aCallback"></param>
1580 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1582 if (this.InvokeRequired)
1584 //Not in the proper thread, invoke ourselves
1585 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1586 this.Invoke(d, new object[] { aSessionId, aCallback });
1590 //We are in the proper thread
1591 //Add this session to our collection of clients
1592 ClientData newClient = new ClientData(aSessionId, aCallback);
1593 Program.iMainForm.iClients.Add(aSessionId, newClient);
1594 //Add this session to our UI
1595 UpdateClientTreeViewNode(newClient);
1601 /// Find the client with the highest priority if any.
1603 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1604 public ClientData FindHighestPriorityClient()
1606 ClientData highestPriorityClient = null;
1607 foreach (var client in iClients)
1609 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1611 highestPriorityClient = client.Value;
1615 return highestPriorityClient;
1621 /// <param name="aSessionId"></param>
1622 public void RemoveClientThreadSafe(string aSessionId)
1624 if (this.InvokeRequired)
1626 //Not in the proper thread, invoke ourselves
1627 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1628 this.Invoke(d, new object[] { aSessionId });
1632 //We are in the proper thread
1633 //Remove this session from both client collection and UI tree view
1634 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
1636 Program.iMainForm.iClients.Remove(aSessionId);
1637 Program.iMainForm.iTreeViewClients.Nodes.Remove(Program.iMainForm.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1638 //Update recording status too whenever a client is removed
1639 UpdateRecordingNotification();
1642 if (iCurrentClientSessionId == aSessionId)
1644 //The current client is closing
1645 iCurrentClientData = null;
1646 //Find the client with the highest priority and set it as current
1647 ClientData newCurrentClient = FindHighestPriorityClient();
1648 if (newCurrentClient!=null)
1650 SetCurrentClient(newCurrentClient.SessionId, true);
1654 if (iClients.Count == 0)
1656 //Clear our screen when last client disconnects
1661 //We were closing our form
1662 //All clients are now closed
1663 //Just resume our close operation
1674 /// <param name="aSessionId"></param>
1675 /// <param name="aLayout"></param>
1676 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1678 if (this.InvokeRequired)
1680 //Not in the proper thread, invoke ourselves
1681 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1682 this.Invoke(d, new object[] { aSessionId, aLayout });
1686 ClientData client = iClients[aSessionId];
1689 //Don't change a thing if the layout is the same
1690 if (!client.Layout.IsSameAs(aLayout))
1692 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1693 //Set our client layout then
1694 client.Layout = aLayout;
1695 //So that next time we update all our fields at ones
1696 client.HasNewLayout = true;
1697 //Layout has changed clear our fields then
1698 client.Fields.Clear();
1700 UpdateClientTreeViewNode(client);
1704 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1713 /// <param name="aSessionId"></param>
1714 /// <param name="aField"></param>
1715 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1717 if (this.InvokeRequired)
1719 //Not in the proper thread, invoke ourselves
1720 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1721 this.Invoke(d, new object[] { aSessionId, aField });
1725 //We are in the proper thread
1726 //Call the non-thread-safe variant
1727 SetClientField(aSessionId, aField);
1735 /// Set a data field in the given client.
1737 /// <param name="aSessionId"></param>
1738 /// <param name="aField"></param>
1739 private void SetClientField(string aSessionId, DataField aField)
1741 //TODO: should check if the field actually changed?
1743 ClientData client = iClients[aSessionId];
1744 bool layoutChanged = false;
1745 bool contentChanged = true;
1747 //Fetch our field index
1748 int fieldIndex = client.FindSameFieldIndex(aField);
1752 //No corresponding field, just bail out
1756 //Keep our previous field in there
1757 DataField previousField = client.Fields[fieldIndex];
1758 //Just update that field then
1759 client.Fields[fieldIndex] = aField;
1761 if (!aField.IsTableField)
1763 //We are done then if that field is not in our table layout
1767 TableField tableField = (TableField) aField;
1769 if (previousField.IsSameLayout(aField))
1771 //If we are updating a field in our current client we need to update it in our panel
1772 if (aSessionId == iCurrentClientSessionId)
1774 Control ctrl=iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1775 if (aField.IsTextField && ctrl is MarqueeLabel)
1777 TextField textField=(TextField)aField;
1778 //Text field control already in place, just change the text
1779 MarqueeLabel label = (MarqueeLabel)ctrl;
1780 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1781 label.Text = textField.Text;
1782 label.TextAlign = textField.Alignment;
1784 else if (aField.IsBitmapField && ctrl is PictureBox)
1786 BitmapField bitmapField = (BitmapField)aField;
1787 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1788 //Bitmap field control already in place just change the bitmap
1789 PictureBox pictureBox = (PictureBox)ctrl;
1790 pictureBox.Image = bitmapField.Bitmap;
1794 layoutChanged = true;
1800 layoutChanged = true;
1803 //If either content or layout changed we need to update our tree view to reflect the changes
1804 if (contentChanged || layoutChanged)
1806 UpdateClientTreeViewNode(client);
1810 Debug.Print("Layout changed");
1811 //Our layout has changed, if we are already the current client we need to update our panel
1812 if (aSessionId == iCurrentClientSessionId)
1814 //Apply layout and set data fields.
1815 UpdateTableLayoutPanel(iCurrentClientData);
1820 Debug.Print("Layout has not changed.");
1825 Debug.Print("WARNING: content and layout have not changed!");
1828 //When a client field is set we try switching to this client to present the new information to our user
1829 SetCurrentClient(aSessionId);
1835 /// <param name="aTexts"></param>
1836 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1838 if (this.InvokeRequired)
1840 //Not in the proper thread, invoke ourselves
1841 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1842 this.Invoke(d, new object[] { aSessionId, aFields });
1846 ClientData client = iClients[aSessionId];
1848 if (client.HasNewLayout)
1850 //TODO: Assert client.Count == 0
1851 //Our layout was just changed
1852 //Do some special handling to avoid re-creating our panel N times, once for each fields
1853 client.HasNewLayout = false;
1854 //Just set all our fields then
1855 client.Fields.AddRange(aFields);
1856 //Try switch to that client
1857 SetCurrentClient(aSessionId);
1859 //If we are updating the current client update our panel
1860 if (aSessionId == iCurrentClientSessionId)
1862 //Apply layout and set data fields.
1863 UpdateTableLayoutPanel(iCurrentClientData);
1866 UpdateClientTreeViewNode(client);
1870 //Put each our text fields in a label control
1871 foreach (DataField field in aFields)
1873 SetClientField(aSessionId, field);
1882 /// <param name="aSessionId"></param>
1883 /// <param name="aName"></param>
1884 public void SetClientNameThreadSafe(string aSessionId, string aName)
1886 if (this.InvokeRequired)
1888 //Not in the proper thread, invoke ourselves
1889 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1890 this.Invoke(d, new object[] { aSessionId, aName });
1894 //We are in the proper thread
1896 ClientData client = iClients[aSessionId];
1900 client.Name = aName;
1901 //Update our tree-view
1902 UpdateClientTreeViewNode(client);
1908 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1910 if (this.InvokeRequired)
1912 //Not in the proper thread, invoke ourselves
1913 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1914 this.Invoke(d, new object[] { aSessionId, aPriority });
1918 //We are in the proper thread
1920 ClientData client = iClients[aSessionId];
1924 client.Priority = aPriority;
1925 //Update our tree-view
1926 UpdateClientTreeViewNode(client);
1927 //Change our current client as per new priority
1928 ClientData newCurrentClient = FindHighestPriorityClient();
1929 if (newCurrentClient!=null)
1931 SetCurrentClient(newCurrentClient.SessionId);
1940 /// <param name="value"></param>
1941 /// <param name="maxChars"></param>
1942 /// <returns></returns>
1943 public static string Truncate(string value, int maxChars)
1945 return value.Length <= maxChars ? value : value.Substring(0, maxChars-3) + "...";
1949 /// Update our recording notification.
1951 private void UpdateRecordingNotification()
1954 bool activeRecording = false;
1956 RecordingField recField=new RecordingField();
1957 foreach (var client in iClients)
1959 RecordingField rec=(RecordingField)client.Value.FindSameFieldAs(recField);
1960 if (rec!=null && rec.IsActive)
1962 activeRecording = true;
1963 //Don't break cause we are collecting the names/texts.
1964 if (!String.IsNullOrEmpty(rec.Text))
1966 text += (rec.Text + "\n");
1970 //Not text for that recording, use client name instead
1971 text += client.Value.Name + " recording\n";
1977 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1978 iRecordingNotification.Text = Truncate(text,63);
1980 //Change visibility of notification if needed
1981 if (iRecordingNotification.Visible != activeRecording)
1983 iRecordingNotification.Visible = activeRecording;
1984 //Assuming the notification icon is in sync with our display icon
1985 //Take care of our REC icon
1986 if (iDisplay.IsOpen())
1988 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
1996 /// <param name="aClient"></param>
1997 private void UpdateClientTreeViewNode(ClientData aClient)
1999 Debug.Print("UpdateClientTreeViewNode");
2001 if (aClient == null)
2006 //Hook in record icon update too
2007 UpdateRecordingNotification();
2009 TreeNode node = null;
2010 //Check that our client node already exists
2011 //Get our client root node using its key which is our session ID
2012 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
2013 if (nodes.Count()>0)
2015 //We already have a node for that client
2017 //Clear children as we are going to recreate them below
2022 //Client node does not exists create a new one
2023 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
2024 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
2030 if (!String.IsNullOrEmpty(aClient.Name))
2032 //We have a name, use it as text for our root node
2033 node.Text = aClient.Name;
2034 //Add a child with SessionId
2035 node.Nodes.Add(new TreeNode(aClient.SessionId));
2039 //No name, use session ID instead
2040 node.Text = aClient.SessionId;
2043 //Display client priority
2044 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2046 if (aClient.Fields.Count > 0)
2048 //Create root node for our texts
2049 TreeNode textsRoot = new TreeNode("Fields");
2050 node.Nodes.Add(textsRoot);
2051 //For each text add a new entry
2052 foreach (DataField field in aClient.Fields)
2054 if (field.IsTextField)
2056 TextField textField = (TextField)field;
2057 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2059 else if (field.IsBitmapField)
2061 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2063 else if (field.IsRecordingField)
2065 RecordingField recordingField = (RecordingField)field;
2066 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2076 /// Update our table layout row styles to make sure each rows have similar height
2078 private void UpdateTableLayoutRowStyles()
2080 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2082 rowStyle.SizeType = SizeType.Percent;
2083 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
2088 /// Update our display table layout.
2089 /// Will instanciated every field control as defined by our client.
2090 /// Fields must be specified by rows from the left.
2092 /// <param name="aLayout"></param>
2093 private void UpdateTableLayoutPanel(ClientData aClient)
2095 Debug.Print("UpdateTableLayoutPanel");
2097 if (aClient == null)
2104 TableLayout layout = aClient.Layout;
2106 //First clean our current panel
2107 iTableLayoutPanel.Controls.Clear();
2108 iTableLayoutPanel.RowStyles.Clear();
2109 iTableLayoutPanel.ColumnStyles.Clear();
2110 iTableLayoutPanel.RowCount = 0;
2111 iTableLayoutPanel.ColumnCount = 0;
2113 //Then recreate our rows...
2114 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2116 iTableLayoutPanel.RowCount++;
2120 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2122 iTableLayoutPanel.ColumnCount++;
2126 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2128 //Create our column styles
2129 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2132 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2136 //Create our row styles
2137 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2147 foreach (DataField field in aClient.Fields)
2149 if (!field.IsTableField)
2151 //That field is not taking part in our table layout skip it
2155 TableField tableField = (TableField)field;
2157 //Create a control corresponding to the field specified for that cell
2158 Control control = CreateControlForDataField(tableField);
2160 //Add newly created control to our table layout at the specified row and column
2161 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2162 //Make sure we specify column and row span for that new control
2163 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2164 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2172 /// Check our type of data field and create corresponding control
2174 /// <param name="aField"></param>
2175 private Control CreateControlForDataField(DataField aField)
2177 Control control=null;
2178 if (aField.IsTextField)
2180 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2181 label.AutoEllipsis = true;
2182 label.AutoSize = true;
2183 label.BackColor = System.Drawing.Color.Transparent;
2184 label.Dock = System.Windows.Forms.DockStyle.Fill;
2185 label.Location = new System.Drawing.Point(1, 1);
2186 label.Margin = new System.Windows.Forms.Padding(0);
2187 label.Name = "marqueeLabel" + aField;
2188 label.OwnTimer = false;
2189 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2190 label.Separator = cds.Separator;
2191 label.MinFontSize = cds.MinFontSize;
2192 label.ScaleToFit = cds.ScaleToFit;
2193 //control.Size = new System.Drawing.Size(254, 30);
2194 //control.TabIndex = 2;
2195 label.Font = cds.Font;
2197 TextField field = (TextField)aField;
2198 label.TextAlign = field.Alignment;
2199 label.UseCompatibleTextRendering = true;
2200 label.Text = field.Text;
2204 else if (aField.IsBitmapField)
2206 //Create picture box
2207 PictureBox picture = new PictureBox();
2208 picture.AutoSize = true;
2209 picture.BackColor = System.Drawing.Color.Transparent;
2210 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2211 picture.Location = new System.Drawing.Point(1, 1);
2212 picture.Margin = new System.Windows.Forms.Padding(0);
2213 picture.Name = "pictureBox" + aField;
2215 BitmapField field = (BitmapField)aField;
2216 picture.Image = field.Bitmap;
2220 //TODO: Handle recording field?
2226 /// Called when the user selected a new display type.
2228 /// <param name="sender"></param>
2229 /// <param name="e"></param>
2230 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2232 //Store the selected display type in our settings
2233 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2234 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2235 Properties.Settings.Default.Save();
2237 //Try re-opening the display connection if we were already connected.
2238 //Otherwise just update our status to reflect display type change.
2239 if (iDisplay.IsOpen())
2241 OpenDisplayConnection();
2249 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2251 if (maskedTextBoxTimerInterval.Text != "")
2253 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2257 timer.Interval = interval;
2258 cds.TimerInterval = timer.Interval;
2259 Properties.Settings.Default.Save();
2264 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2266 if (maskedTextBoxMinFontSize.Text != "")
2268 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2270 if (minFontSize > 0)
2272 cds.MinFontSize = minFontSize;
2273 Properties.Settings.Default.Save();
2274 //We need to recreate our layout for that change to take effect
2275 UpdateTableLayoutPanel(iCurrentClientData);
2281 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2283 if (maskedTextBoxScrollingSpeed.Text != "")
2285 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2287 if (scrollingSpeed > 0)
2289 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2290 Properties.Settings.Default.Save();
2291 //We need to recreate our layout for that change to take effect
2292 UpdateTableLayoutPanel(iCurrentClientData);
2297 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2299 cds.Separator = textBoxScrollLoopSeparator.Text;
2300 Properties.Settings.Default.Save();
2302 //Update our text fields
2303 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2305 ctrl.Separator = cds.Separator;
2310 private void buttonPowerOn_Click(object sender, EventArgs e)
2315 private void buttonPowerOff_Click(object sender, EventArgs e)
2317 iDisplay.PowerOff();
2320 private void buttonShowClock_Click(object sender, EventArgs e)
2325 private void buttonHideClock_Click(object sender, EventArgs e)
2330 private void buttonUpdate_Click(object sender, EventArgs e)
2332 InstallUpdateSyncWithInfo();
2340 if (!iDisplay.IsOpen())
2345 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2346 iSkipFrameRendering = true;
2349 iDisplay.SwapBuffers();
2350 //Then show our clock
2351 iDisplay.ShowClock();
2359 if (!iDisplay.IsOpen())
2364 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2365 iSkipFrameRendering = false;
2366 iDisplay.HideClock();
2369 private void InstallUpdateSyncWithInfo()
2371 UpdateCheckInfo info = null;
2373 if (ApplicationDeployment.IsNetworkDeployed)
2375 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2379 info = ad.CheckForDetailedUpdate();
2382 catch (DeploymentDownloadException dde)
2384 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);
2387 catch (InvalidDeploymentException ide)
2389 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);
2392 catch (InvalidOperationException ioe)
2394 MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
2398 if (info.UpdateAvailable)
2400 Boolean doUpdate = true;
2402 if (!info.IsUpdateRequired)
2404 DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
2405 if (!(DialogResult.OK == dr))
2412 // Display a message that the application MUST reboot. Display the minimum required version.
2413 MessageBox.Show("This application has detected a mandatory update from your current " +
2414 "version to version " + info.MinimumRequiredVersion.ToString() +
2415 ". The application will now install the update and restart.",
2416 "Update Available", MessageBoxButtons.OK,
2417 MessageBoxIcon.Information);
2425 MessageBox.Show("The application has been upgraded, and will now restart.");
2426 Application.Restart();
2428 catch (DeploymentDownloadException dde)
2430 MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
2437 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2446 private void SysTrayHideShow()
2452 WindowState = FormWindowState.Normal;
2457 /// Use to handle minimize events.
2459 /// <param name="sender"></param>
2460 /// <param name="e"></param>
2461 private void MainForm_SizeChanged(object sender, EventArgs e)
2463 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2475 /// <param name="sender"></param>
2476 /// <param name="e"></param>
2477 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2479 //Our table layout size has changed which means our display size has changed.
2480 //We need to re-create our bitmap.
2481 iCreateBitmap = true;
2487 /// <param name="sender"></param>
2488 /// <param name="e"></param>
2489 private void buttonSelectFile_Click(object sender, EventArgs e)
2491 //openFileDialog1.InitialDirectory = "c:\\";
2492 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2493 //openFileDialog.FilterIndex = 1;
2494 openFileDialog.RestoreDirectory = true;
2496 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2498 labelStartFileName.Text = openFileDialog.FileName;
2499 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2500 Properties.Settings.Default.Save();
2507 /// <param name="sender"></param>
2508 /// <param name="e"></param>
2509 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2511 //Save the optical drive the user selected for ejection
2512 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2513 Properties.Settings.Default.Save();
2520 private void LogsUpdate()
2522 if (iWriter != null)
2530 /// Broadcast messages to subscribers.
2532 /// <param name="message"></param>
2533 protected override void WndProc(ref Message aMessage)
2537 if (OnWndProc!=null)
2539 OnWndProc(ref aMessage);
2542 base.WndProc(ref aMessage);
2545 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2547 //Save CEC enabled status
2548 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2549 Properties.Settings.Default.Save();
2554 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2556 //Save CEC HDMI port
2557 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2558 Properties.Settings.Default.CecHdmiPort++;
2559 Properties.Settings.Default.Save();
2564 private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
2566 Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
2567 Properties.Settings.Default.Save();
2572 private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
2574 Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
2575 Properties.Settings.Default.Save();
2580 private void checkBoxCecReconnectToPowerTv_CheckedChanged(object sender, EventArgs e)
2582 Properties.Settings.Default.CecReconnectToPowerTv = checkBoxCecReconnectToPowerTv.Checked;
2583 Properties.Settings.Default.Save();
2591 private void ResetCec()
2593 if (iCecManager==null)
2595 //Thus skipping initial UI setup
2601 if (Properties.Settings.Default.CecEnabled)
2603 iCecManager.Start(Handle, "CEC",
2604 Properties.Settings.Default.CecHdmiPort,
2605 Properties.Settings.Default.CecMonitorOn,
2606 Properties.Settings.Default.CecMonitorOff,
2607 Properties.Settings.Default.CecReconnectToPowerTv);
2616 private void SetupCecLogLevel()
2619 iCecManager.Client.LogLevel = 0;
2621 if (checkBoxCecLogError.Checked)
2622 iCecManager.Client.LogLevel |= (int)CecLogLevel.Error;
2624 if (checkBoxCecLogWarning.Checked)
2625 iCecManager.Client.LogLevel |= (int)CecLogLevel.Warning;
2627 if (checkBoxCecLogNotice.Checked)
2628 iCecManager.Client.LogLevel |= (int)CecLogLevel.Notice;
2630 if (checkBoxCecLogTraffic.Checked)
2631 iCecManager.Client.LogLevel |= (int)CecLogLevel.Traffic;
2633 if (checkBoxCecLogDebug.Checked)
2634 iCecManager.Client.LogLevel |= (int)CecLogLevel.Debug;
2636 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2640 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2645 private void buttonClearLogs_Click(object sender, EventArgs e)
2647 richTextBoxLogs.Clear();
2650 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)
2655 private void buttonAddAction_Click(object sender, EventArgs e)
2657 Event ear = (Event)iTreeViewEvents.SelectedNode.Tag;
2660 //Must select event node
2664 FormEditAction ea = new FormEditAction();
2665 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2666 if (res == DialogResult.OK)
2668 ear.Actions.Add(ea.Action);