Adding Delay action to replace our Sleep action.
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;
49 using Ear = SharpLib.Ear;
51 namespace SharpDisplayManager
54 public delegate uint ColorProcessingDelegate(int aX, int aY, uint aPixel);
56 public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
58 //Delegates are used for our thread safe method
59 public delegate void AddClientDelegate(string aSessionId, ICallback aCallback);
61 public delegate void RemoveClientDelegate(string aSessionId);
63 public delegate void SetFieldDelegate(string SessionId, DataField aField);
65 public delegate void SetFieldsDelegate(string SessionId, System.Collections.Generic.IList<DataField> aFields);
67 public delegate void SetLayoutDelegate(string SessionId, TableLayout aLayout);
69 public delegate void SetClientNameDelegate(string aSessionId, string aName);
71 public delegate void SetClientPriorityDelegate(string aSessionId, uint aPriority);
73 public delegate void PlainUpdateDelegate();
75 public delegate void WndProcDelegate(ref Message aMessage);
78 /// Our Display manager main form
80 [System.ComponentModel.DesignerCategory("Form")]
81 public partial class FormMain : FormMainHid, IMMNotificationClient
83 //public Manager iManager = new Manager();
84 DateTime LastTickTime;
86 System.Drawing.Bitmap iBmp;
87 bool iCreateBitmap; //Workaround render to bitmap issues when minimized
88 ServiceHost iServiceHost;
89 // Our collection of clients sorted by session id.
90 public Dictionary<string, ClientData> iClients;
91 // The name of the client which informations are currently displayed.
92 public string iCurrentClientSessionId;
93 ClientData iCurrentClientData;
97 public bool iSkipFrameRendering;
98 //Function pointer for pixel color filtering
99 ColorProcessingDelegate iColorFx;
100 //Function pointer for pixel X coordinate intercept
101 CoordinateTranslationDelegate iScreenX;
102 //Function pointer for pixel Y coordinate intercept
103 CoordinateTranslationDelegate iScreenY;
105 private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
106 private MMDevice iMultiMediaDevice;
108 private NetworkManager iNetworkManager;
111 /// CEC - Consumer Electronic Control.
112 /// Notably used to turn TV on and off as Windows broadcast monitor on and off notifications.
114 private ConsumerElectronicControl iCecManager;
117 /// Manage run when Windows startup option
119 private StartupManager iStartupManager;
122 /// System notification icon used to hide our application from the task bar.
124 private SharpLib.Notification.Control iNotifyIcon;
127 /// System recording notification icon.
129 private SharpLib.Notification.Control iRecordingNotification;
134 RichTextBoxTraceListener iWriter;
138 /// Allow user to receive window messages;
140 public event WndProcDelegate OnWndProc;
144 if (Properties.Settings.Default.EarManager == null)
146 //No actions in our settings yet
147 Properties.Settings.Default.EarManager = new EarManager();
151 // We loaded events and actions from our settings
152 // Internalizer apparently skips constructor so we need to initialize it here
153 // Though I reckon that should only be needed when loading an empty EAR manager I guess.
154 Properties.Settings.Default.EarManager.Construct();
156 iSkipFrameRendering = false;
158 iCurrentClientSessionId = "";
159 iCurrentClientData = null;
160 LastTickTime = DateTime.Now;
161 //Instantiate our display and register for events notifications
162 iDisplay = new Display();
163 iDisplay.OnOpened += OnDisplayOpened;
164 iDisplay.OnClosed += OnDisplayClosed;
166 iClients = new Dictionary<string, ClientData>();
167 iStartupManager = new StartupManager();
168 iNotifyIcon = new SharpLib.Notification.Control();
169 iRecordingNotification = new SharpLib.Notification.Control();
171 //Have our designer initialize its controls
172 InitializeComponent();
174 //Redirect console output
175 iWriter = new RichTextBoxTraceListener(richTextBoxLogs);
176 Trace.Listeners.Add(iWriter);
178 //Populate device types
179 PopulateDeviceTypes();
181 //Initial status update
184 //We have a bug when drawing minimized and reusing our bitmap
185 //Though I could not reproduce it on Windows 10
186 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
187 PixelFormat.Format32bppArgb);
188 iCreateBitmap = false;
190 //Minimize our window if desired
191 if (Properties.Settings.Default.StartMinimized)
193 WindowState = FormWindowState.Minimized;
201 /// <param name="sender"></param>
202 /// <param name="e"></param>
203 private void MainForm_Load(object sender, EventArgs e)
205 //Check if we are running a Click Once deployed application
206 if (ApplicationDeployment.IsNetworkDeployed)
208 //This is a proper Click Once installation, fetch and show our version number
209 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
213 //Not a proper Click Once installation, assuming development build then
214 this.Text += " - development";
218 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
219 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
220 UpdateAudioDeviceAndMasterVolumeThreadSafe();
223 iNetworkManager = new NetworkManager();
224 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
225 UpdateNetworkStatus();
228 iCecManager = new ConsumerElectronicControl();
229 OnWndProc += iCecManager.OnWndProc;
236 PopulateTreeViewEvents();
238 //Setup notification icon
241 //Setup recording notification
242 SetupRecordingNotification();
244 // To make sure start up with minimize to tray works
245 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
251 //When not debugging we want the screen to be empty until a client takes over
254 //When developing we want at least one client for testing
255 StartNewClient("abcdefghijklmnopqrst-0123456789", "ABCDEFGHIJKLMNOPQRST-0123456789");
258 //Open display connection on start-up if needed
259 if (Properties.Settings.Default.DisplayConnectOnStartup)
261 OpenDisplayConnection();
264 //Start our server so that we can get client requests
267 //Register for HID events
268 RegisterHidDevices();
270 //Start Idle client if needed
271 if (Properties.Settings.Default.StartIdleClient)
278 /// Called when our display is opened.
280 /// <param name="aDisplay"></param>
281 private void OnDisplayOpened(Display aDisplay)
283 //Make sure we resume frame rendering
284 iSkipFrameRendering = false;
286 //Set our screen size now that our display is connected
287 //Our panelDisplay is the container of our tableLayoutPanel
288 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
289 //panelDisplay needs an extra 2 pixels for borders on each sides
290 //tableLayoutPanel will eventually be the exact size of our display
291 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
292 panelDisplay.Size = size;
294 //Our display was just opened, update our UI
296 //Initiate asynchronous request
297 iDisplay.RequestFirmwareRevision();
300 UpdateMasterVolumeThreadSafe();
302 UpdateNetworkStatus();
305 //Testing icon in debug, no arm done if icon not supported
306 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
307 //iDisplay.SetAllIconsStatus(2);
313 /// Populate tree view with events and actions
315 private void PopulateTreeViewEvents()
317 //Disable action buttons
318 buttonActionAdd.Enabled = false;
319 buttonActionDelete.Enabled = false;
321 Ear.Event currentEvent = CurrentEvent();
322 Ear.Action currentAction = CurrentAction();
323 TreeNode treeNodeToSelect = null;
326 iTreeViewEvents.Nodes.Clear();
327 //Populate registered events
328 foreach (Ear.Event e in Properties.Settings.Default.EarManager.Events)
330 //Create our event node
331 //Work out the name of our node
332 string eventNodeName = "";
333 if (!string.IsNullOrEmpty(e.Name))
335 //That event has a proper name, use it then
336 eventNodeName = $"{e.Name} - {e.Brief()}";
340 //Unnamed events just use brief
341 eventNodeName = e.Brief();
344 TreeNode eventNode = iTreeViewEvents.Nodes.Add(eventNodeName);
345 eventNode.Tag = e; //For easy access to our event
348 //Dim our nodes if disabled
349 eventNode.ForeColor = Color.DimGray;
352 //Add event description as child node
353 eventNode.Nodes.Add(e.AttributeDescription).ForeColor = eventNode.ForeColor;
354 //Create child node for actions root
355 TreeNode actionsNodes = eventNode.Nodes.Add("Actions");
356 actionsNodes.ForeColor = eventNode.ForeColor;
358 // Add our actions for that event
359 foreach (Ear.Action a in e.Actions)
361 TreeNode actionNode = actionsNodes.Nodes.Add(a.Brief());
363 actionNode.ForeColor = eventNode.ForeColor;
364 if (a == currentAction)
366 treeNodeToSelect = actionNode;
371 iTreeViewEvents.ExpandAll();
372 SelectEvent(currentEvent);
374 if (treeNodeToSelect != null)
376 iTreeViewEvents.SelectedNode = treeNodeToSelect;
378 else if (iTreeViewEvents.SelectedNode != null && iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) > 0)
380 //Select the last action if any
381 iTreeViewEvents.SelectedNode =
382 iTreeViewEvents.SelectedNode.Nodes[1].Nodes[
383 iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) - 1];
385 else if (iTreeViewEvents.SelectedNode == null && iTreeViewEvents.Nodes.Count > 0)
387 //Still no selected node select the first one then
388 iTreeViewEvents.SelectedNode = iTreeViewEvents.Nodes[0];
396 /// Called when our display is closed.
398 /// <param name="aDisplay"></param>
399 private void OnDisplayClosed(Display aDisplay)
401 //Our display was just closed, update our UI consequently
405 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
407 //Update network status
408 UpdateNetworkStatus();
412 /// Update our Network Status
414 private void UpdateNetworkStatus()
416 if (iDisplay.IsOpen())
418 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet,
419 iNetworkManager.NetworkListManager.IsConnectedToInternet);
420 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
425 int iLastNetworkIconIndex = 0;
426 int iUpdateCountSinceLastNetworkAnimation = 0;
431 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
433 iUpdateCountSinceLastNetworkAnimation++;
434 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation%4;
436 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected &&
437 iUpdateCountSinceLastNetworkAnimation == 0)
439 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
442 //Prevents div by zero and other undefined behavior
445 iLastNetworkIconIndex++;
446 iLastNetworkIconIndex = iLastNetworkIconIndex%(iconCount*2);
447 for (int i = 0; i < iconCount; i++)
449 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) &&
450 !(i == 1 && iLastNetworkIconIndex > 4))
452 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
456 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
465 /// Receive volume change notification and reflect changes on our slider.
467 /// <param name="data"></param>
468 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
470 UpdateMasterVolumeThreadSafe();
474 /// Update master volume when user moves our slider.
476 /// <param name="sender"></param>
477 /// <param name="e"></param>
478 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
480 //Just like Windows Volume Mixer we unmute if the volume is adjusted
481 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
482 //Set volume level according to our volume slider new position
483 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value/100.0f;
488 /// Mute check box changed.
490 /// <param name="sender"></param>
491 /// <param name="e"></param>
492 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
494 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
498 /// Device State Changed
500 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId,
501 [MarshalAs(UnmanagedType.I4)] DeviceState newState)
508 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId)
515 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId)
520 /// Default Device Changed
522 public void OnDefaultDeviceChanged(DataFlow flow, Role role,
523 [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
525 if (role == Role.Multimedia && flow == DataFlow.Render)
527 UpdateAudioDeviceAndMasterVolumeThreadSafe();
532 /// Property Value Changed
534 /// <param name="pwstrDeviceId"></param>
535 /// <param name="key"></param>
536 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key)
544 /// Update master volume indicators based our current system states.
545 /// This typically includes volume levels and mute status.
547 private void UpdateMasterVolumeThreadSafe()
549 if (this.InvokeRequired)
551 //Not in the proper thread, invoke ourselves
552 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
553 this.Invoke(d, new object[] {});
557 //Update volume slider
558 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
559 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar*100);
560 //Update mute checkbox
561 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
563 //If our display connection is open we need to update its icons
564 if (iDisplay.IsOpen())
566 //First take care our our volume level icons
567 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
568 if (volumeIconCount > 0)
570 //Compute current volume level from system level and the number of segments in our display volume bar.
571 //That tells us how many segments in our volume bar needs to be turned on.
572 float currentVolume = volumeLevelScalar*volumeIconCount;
573 int segmentOnCount = Convert.ToInt32(currentVolume);
574 //Check if our segment count was rounded up, this will later be used for half brightness segment
575 bool roundedUp = segmentOnCount > currentVolume;
577 for (int i = 0; i < volumeIconCount; i++)
579 if (i < segmentOnCount)
581 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
582 if (i == segmentOnCount - 1 && roundedUp)
585 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
586 (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1)/2);
591 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
592 iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
597 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
602 //Take care of our mute icon
603 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
611 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
613 if (this.InvokeRequired)
615 //Not in the proper thread, invoke ourselves
616 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
617 this.Invoke(d, new object[] {});
621 //We are in the correct thread just go ahead.
624 //Get our master volume
625 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
627 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
629 //Show our volume in our track bar
630 UpdateMasterVolumeThreadSafe();
632 //Register to get volume modifications
633 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
635 trackBarMasterVolume.Enabled = true;
639 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
640 Debug.WriteLine(ex.ToString());
641 //Something went wrong S/PDIF device ca throw exception I guess
642 trackBarMasterVolume.Enabled = false;
649 private void PopulateDeviceTypes()
651 int count = Display.TypeCount();
653 for (int i = 0; i < count; i++)
655 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type) i));
662 private void SetupTrayIcon()
664 iNotifyIcon.Icon = GetIcon("vfd.ico");
665 iNotifyIcon.Text = "Sharp Display Manager";
666 iNotifyIcon.Visible = true;
668 //Double click toggles visibility - typically brings up the application
669 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
674 //Adding a context menu, useful to be able to exit the application
675 ContextMenu contextMenu = new ContextMenu();
676 //Context menu item to toggle visibility
677 MenuItem hideShowItem = new MenuItem("Hide/Show");
678 hideShowItem.Click += delegate(object obj, EventArgs args)
682 contextMenu.MenuItems.Add(hideShowItem);
684 //Context menu item separator
685 contextMenu.MenuItems.Add(new MenuItem("-"));
687 //Context menu exit item
688 MenuItem exitItem = new MenuItem("Exit");
689 exitItem.Click += delegate(object obj, EventArgs args)
693 contextMenu.MenuItems.Add(exitItem);
695 iNotifyIcon.ContextMenu = contextMenu;
701 private void SetupRecordingNotification()
703 iRecordingNotification.Icon = GetIcon("record.ico");
704 iRecordingNotification.Text = "No recording";
705 iRecordingNotification.Visible = false;
709 /// Access icons from embedded resources.
711 /// <param name="aName"></param>
712 /// <returns></returns>
713 public static Icon GetIcon(string aName)
715 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
716 foreach (string name in names)
718 //Find a resource name that ends with the given name
719 if (name.EndsWith(aName))
721 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
723 return new Icon(stream);
733 /// Set our current client.
734 /// This will take care of applying our client layout and set data fields.
736 /// <param name="aSessionId"></param>
737 void SetCurrentClient(string aSessionId, bool aForce = false)
739 if (aSessionId == iCurrentClientSessionId)
741 //Given client is already the current one.
742 //Don't bother changing anything then.
746 ClientData requestedClientData = iClients[aSessionId];
748 //Check when was the last time we switched to that client
749 if (iCurrentClientData != null)
751 //Do not switch client if priority of current client is higher
752 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
758 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
759 //TODO: put that hard coded value as a client property
760 //Clients should be able to define how often they can be interrupted
761 //Thus a background client can set this to zero allowing any other client to interrupt at any time
762 //We could also compute this delay by looking at the requests frequencies?
764 requestedClientData.Priority == iCurrentClientData.Priority &&
765 //Time sharing is only if clients have the same priority
766 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
768 //Don't switch clients too often
773 //Set current client ID.
774 iCurrentClientSessionId = aSessionId;
775 //Set the time we last switched to that client
776 iClients[aSessionId].LastSwitchTime = DateTime.Now;
777 //Fetch and set current client data.
778 iCurrentClientData = requestedClientData;
779 //Apply layout and set data fields.
780 UpdateTableLayoutPanel(iCurrentClientData);
783 private void buttonFont_Click(object sender, EventArgs e)
785 //fontDialog.ShowColor = true;
786 //fontDialog.ShowApply = true;
787 fontDialog.ShowEffects = true;
788 fontDialog.Font = cds.Font;
790 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
792 //fontDialog.ShowHelp = true;
794 //fontDlg.MaxSize = 40;
795 //fontDlg.MinSize = 22;
797 //fontDialog.Parent = this;
798 //fontDialog.StartPosition = FormStartPosition.CenterParent;
800 //DlgBox.ShowDialog(fontDialog);
802 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
803 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
805 //Set the fonts to all our labels in our layout
806 foreach (Control ctrl in iTableLayoutPanel.Controls)
808 if (ctrl is MarqueeLabel)
810 ((MarqueeLabel) ctrl).Font = fontDialog.Font;
815 cds.Font = fontDialog.Font;
816 Properties.Settings.Default.Save();
825 void CheckFontHeight()
827 //Show font height and width
828 labelFontHeight.Text = "Font height: " + cds.Font.Height;
829 float charWidth = IsFixedWidth(cds.Font);
830 if (charWidth == 0.0f)
832 labelFontWidth.Visible = false;
836 labelFontWidth.Visible = true;
837 labelFontWidth.Text = "Font width: " + charWidth;
840 MarqueeLabel label = null;
841 //Get the first label control we can find
842 foreach (Control ctrl in iTableLayoutPanel.Controls)
844 if (ctrl is MarqueeLabel)
846 label = (MarqueeLabel) ctrl;
851 //Now check font height and show a warning if needed.
852 if (label != null && label.Font.Height > label.Height)
854 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) +
856 labelWarning.Visible = true;
860 labelWarning.Visible = false;
865 private void buttonCapture_Click(object sender, EventArgs e)
867 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
868 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
869 //Bitmap bmpToSave = new Bitmap(bmp);
870 bmp.Save("D:\\capture.png");
872 ((MarqueeLabel) iTableLayoutPanel.Controls[0]).Text = "Captured";
875 string outputFileName = "d:\\capture.png";
876 using (MemoryStream memory = new MemoryStream())
878 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
880 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
881 byte[] bytes = memory.ToArray();
882 fs.Write(bytes, 0, bytes.Length);
889 private void CheckForRequestResults()
891 if (iDisplay.IsRequestPending())
893 switch (iDisplay.AttemptRequestCompletion())
895 case MiniDisplay.Request.FirmwareRevision:
896 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
897 //Issue next request then
898 iDisplay.RequestPowerSupplyStatus();
901 case MiniDisplay.Request.PowerSupplyStatus:
902 if (iDisplay.PowerSupplyStatus())
904 toolStripStatusLabelPower.Text = "ON";
908 toolStripStatusLabelPower.Text = "OFF";
910 //Issue next request then
911 iDisplay.RequestDeviceId();
914 case MiniDisplay.Request.DeviceId:
915 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
916 //No more request to issue
922 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
924 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
931 public static uint ColorUntouched(int aX, int aY, uint aPixel)
936 public static uint ColorInversed(int aX, int aY, uint aPixel)
941 public static uint ColorChessboard(int aX, int aY, uint aPixel)
943 if ((aX%2 == 0) && (aY%2 == 0))
947 else if ((aX%2 != 0) && (aY%2 != 0))
955 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
957 return aBmp.Width - aX - 1;
960 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
962 return iBmp.Height - aY - 1;
965 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
970 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
976 /// Select proper pixel delegates according to our current settings.
978 private void SetupPixelDelegates()
980 //Select our pixel processing routine
981 if (cds.InverseColors)
983 //iColorFx = ColorChessboard;
984 iColorFx = ColorInversed;
988 iColorFx = ColorWhiteIsOn;
991 //Select proper coordinate translation functions
992 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
993 if (cds.ReverseScreen)
995 iScreenX = ScreenReversedX;
996 iScreenY = ScreenReversedY;
1006 //This is our timer tick responsible to perform our render
1007 private void timer_Tick(object sender, EventArgs e)
1009 //Update our animations
1010 DateTime NewTickTime = DateTime.Now;
1012 UpdateNetworkSignal(LastTickTime, NewTickTime);
1014 //Update animation for all our marquees
1015 foreach (Control ctrl in iTableLayoutPanel.Controls)
1017 if (ctrl is MarqueeLabel)
1019 ((MarqueeLabel) ctrl).UpdateAnimation(LastTickTime, NewTickTime);
1023 //Update our display
1024 if (iDisplay.IsOpen())
1026 CheckForRequestResults();
1028 //Check if frame rendering is needed
1029 //Typically used when showing clock
1030 if (!iSkipFrameRendering)
1035 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
1036 PixelFormat.Format32bppArgb);
1037 iCreateBitmap = false;
1039 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
1040 //iBmp.Save("D:\\capture.png");
1042 //Send it to our display
1043 for (int i = 0; i < iBmp.Width; i++)
1045 for (int j = 0; j < iBmp.Height; j++)
1049 //Get our processed pixel coordinates
1050 int x = iScreenX(iBmp, i);
1051 int y = iScreenY(iBmp, j);
1053 uint color = (uint) iBmp.GetPixel(i, j).ToArgb();
1054 //Apply color effects
1055 color = iColorFx(x, y, color);
1057 iDisplay.SetPixel(x, y, color);
1062 iDisplay.SwapBuffers();
1066 //Compute instant FPS
1067 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " +
1068 (1000/timer.Interval).ToString() + " FPS";
1070 LastTickTime = NewTickTime;
1075 /// Attempt to establish connection with our display hardware.
1077 private void OpenDisplayConnection()
1079 CloseDisplayConnection();
1081 if (!iDisplay.Open((MiniDisplay.Type) cds.DisplayType))
1084 toolStripStatusLabelConnect.Text = "Connection error";
1088 private void CloseDisplayConnection()
1090 //Status will be updated upon receiving the closed event
1092 if (iDisplay == null || !iDisplay.IsOpen())
1097 //Do not clear if we gave up on rendering already.
1098 //This means we will keep on displaying clock on MDM166AA for instance.
1099 if (!iSkipFrameRendering)
1102 iDisplay.SwapBuffers();
1105 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1109 private void buttonOpen_Click(object sender, EventArgs e)
1111 OpenDisplayConnection();
1114 private void buttonClose_Click(object sender, EventArgs e)
1116 CloseDisplayConnection();
1119 private void buttonClear_Click(object sender, EventArgs e)
1122 iDisplay.SwapBuffers();
1125 private void buttonFill_Click(object sender, EventArgs e)
1128 iDisplay.SwapBuffers();
1131 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1133 cds.Brightness = trackBarBrightness.Value;
1134 Properties.Settings.Default.Save();
1135 iDisplay.SetBrightness(trackBarBrightness.Value);
1141 /// CDS stands for Current Display Settings
1143 private DisplaySettings cds
1147 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1148 if (settings == null)
1150 settings = new DisplaysSettings();
1152 Properties.Settings.Default.DisplaysSettings = settings;
1155 //Make sure all our settings have been created
1156 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1158 settings.Displays.Add(new DisplaySettings());
1161 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1162 return displaySettings;
1167 /// Check if the given font has a fixed character pitch.
1169 /// <param name="ft"></param>
1170 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1171 public float IsFixedWidth(Font ft)
1173 Graphics g = CreateGraphics();
1174 char[] charSizes = new char[] {'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.'};
1175 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1177 bool fixedWidth = true;
1179 foreach (char c in charSizes)
1180 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width !=
1193 /// Synchronize UI with settings
1195 private void UpdateStatus()
1198 checkBoxShowBorders.Checked = cds.ShowBorders;
1199 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders
1200 ? TableLayoutPanelCellBorderStyle.Single
1201 : TableLayoutPanelCellBorderStyle.None);
1203 //Set the proper font to each of our labels
1204 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1206 ctrl.Font = cds.Font;
1210 //Check if "run on Windows startup" is enabled
1211 checkBoxAutoStart.Checked = iStartupManager.Startup;
1214 iButtonHarmonyConnect.Enabled = Properties.Settings.Default.HarmonyEnabled;
1217 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1219 //Mini Display settings
1220 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1221 checkBoxInverseColors.Checked = cds.InverseColors;
1222 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1223 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1224 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1225 labelMinFontSize.Enabled = cds.ScaleToFit;
1226 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1227 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1228 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1229 timer.Interval = cds.TimerInterval;
1230 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1231 textBoxScrollLoopSeparator.Text = cds.Separator;
1233 SetupPixelDelegates();
1235 if (iDisplay.IsOpen())
1237 //We have a display connection
1238 //Reflect that in our UI
1241 iTableLayoutPanel.Enabled = true;
1242 panelDisplay.Enabled = true;
1244 //Only setup brightness if display is open
1245 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1246 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1247 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1249 //Brightness out of range, this can occur when using auto-detect
1250 //Use max brightness instead
1251 trackBarBrightness.Value = iDisplay.MaxBrightness();
1252 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1256 trackBarBrightness.Value = cds.Brightness;
1257 iDisplay.SetBrightness(cds.Brightness);
1260 //Try compute the steps to something that makes sense
1261 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness())/5);
1262 trackBarBrightness.SmallChange = 1;
1265 buttonFill.Enabled = true;
1266 buttonClear.Enabled = true;
1267 buttonOpen.Enabled = false;
1268 buttonClose.Enabled = true;
1269 trackBarBrightness.Enabled = true;
1270 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1271 //+ " - " + iDisplay.SerialNumber();
1273 if (iDisplay.SupportPowerOnOff())
1275 buttonPowerOn.Enabled = true;
1276 buttonPowerOff.Enabled = true;
1280 buttonPowerOn.Enabled = false;
1281 buttonPowerOff.Enabled = false;
1284 if (iDisplay.SupportClock())
1286 buttonShowClock.Enabled = true;
1287 buttonHideClock.Enabled = true;
1291 buttonShowClock.Enabled = false;
1292 buttonHideClock.Enabled = false;
1296 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1297 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel) > 0;
1299 if (cds.ShowVolumeLabel)
1301 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1305 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1310 //Display connection not available
1311 //Reflect that in our UI
1313 //In debug start our timer even if we don't have a display connection
1316 //In production environment we don't need our timer if no display connection
1319 checkBoxShowVolumeLabel.Enabled = false;
1320 iTableLayoutPanel.Enabled = false;
1321 panelDisplay.Enabled = false;
1322 buttonFill.Enabled = false;
1323 buttonClear.Enabled = false;
1324 buttonOpen.Enabled = true;
1325 buttonClose.Enabled = false;
1326 trackBarBrightness.Enabled = false;
1327 buttonPowerOn.Enabled = false;
1328 buttonPowerOff.Enabled = false;
1329 buttonShowClock.Enabled = false;
1330 buttonHideClock.Enabled = false;
1331 toolStripStatusLabelConnect.Text = "Disconnected";
1332 toolStripStatusLabelPower.Text = "N/A";
1341 /// <param name="sender"></param>
1342 /// <param name="e"></param>
1343 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1345 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1346 Properties.Settings.Default.Save();
1350 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1352 //Save our show borders setting
1353 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked
1354 ? TableLayoutPanelCellBorderStyle.Single
1355 : TableLayoutPanelCellBorderStyle.None);
1356 cds.ShowBorders = checkBoxShowBorders.Checked;
1357 Properties.Settings.Default.Save();
1361 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1363 iStartupManager.Startup = checkBoxAutoStart.Checked;
1367 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1369 //Save our reverse screen setting
1370 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1371 Properties.Settings.Default.Save();
1372 SetupPixelDelegates();
1375 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1377 //Save our inverse colors setting
1378 cds.InverseColors = checkBoxInverseColors.Checked;
1379 Properties.Settings.Default.Save();
1380 SetupPixelDelegates();
1383 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1385 //Save our scale to fit setting
1386 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1387 Properties.Settings.Default.Save();
1389 labelMinFontSize.Enabled = cds.ScaleToFit;
1390 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1393 private void MainForm_Resize(object sender, EventArgs e)
1395 if (WindowState == FormWindowState.Minimized)
1397 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1398 // That's apparently not needed on Windows 10 but we better leave it in place.
1399 iCreateBitmap = true;
1403 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1406 iNetworkManager.Dispose();
1407 CloseDisplayConnection();
1409 e.Cancel = iClosing;
1412 public void StartServer()
1414 iServiceHost = new ServiceHost
1417 new Uri[] {new Uri("net.tcp://localhost:8001/")}
1420 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true),
1422 iServiceHost.Open();
1425 public void StopServer()
1427 if (iClients.Count > 0 && !iClosing)
1431 BroadcastCloseEvent();
1436 MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo,
1437 MessageBoxIcon.Warning) == DialogResult.Yes)
1439 iClosing = false; //We make sure we force close if asked twice
1444 //We removed that as it often lags for some reason
1445 //iServiceHost.Close();
1449 public void BroadcastCloseEvent()
1451 Trace.TraceInformation("BroadcastCloseEvent - start");
1453 var inactiveClients = new List<string>();
1454 foreach (var client in iClients)
1456 //if (client.Key != eventData.ClientName)
1460 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1461 client.Value.Callback.OnCloseOrder( /*eventData*/);
1463 catch (Exception ex)
1465 inactiveClients.Add(client.Key);
1470 if (inactiveClients.Count > 0)
1472 foreach (var client in inactiveClients)
1474 iClients.Remove(client);
1475 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1476 Program.iFormMain.iTreeViewClients.Nodes.Find(client, false)[0]);
1480 if (iClients.Count == 0)
1487 /// Just remove all our fields.
1489 private void ClearLayout()
1491 iTableLayoutPanel.Controls.Clear();
1492 iTableLayoutPanel.RowStyles.Clear();
1493 iTableLayoutPanel.ColumnStyles.Clear();
1494 iCurrentClientData = null;
1498 /// Just launch a demo client.
1500 private void StartNewClient(string aTopText = "", string aBottomText = "")
1502 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1503 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(
1504 new Point(this.Right, this.Top), aTopText, aBottomText);
1505 clientThread.Start(myParams);
1510 /// Just launch our idle client.
1512 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1514 Thread clientThread = new Thread(SharpDisplayClientIdle.Program.MainWithParams);
1515 SharpDisplayClientIdle.StartParams myParams =
1516 new SharpDisplayClientIdle.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1517 clientThread.Start(myParams);
1522 private void buttonStartClient_Click(object sender, EventArgs e)
1527 private void buttonSuspend_Click(object sender, EventArgs e)
1532 private void StartTimer()
1534 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1535 timer.Enabled = true;
1536 UpdateSuspendButton();
1539 private void StopTimer()
1541 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1542 timer.Enabled = false;
1543 UpdateSuspendButton();
1546 private void ToggleTimer()
1548 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1549 timer.Enabled = !timer.Enabled;
1550 UpdateSuspendButton();
1553 private void UpdateSuspendButton()
1557 buttonSuspend.Text = "Run";
1561 buttonSuspend.Text = "Pause";
1566 private void buttonCloseClients_Click(object sender, EventArgs e)
1568 BroadcastCloseEvent();
1571 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1573 //Root node must have at least one child
1574 if (e.Node.Nodes.Count == 0)
1579 //If the selected node is the root node of a client then switch to it
1580 string sessionId = e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1581 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1583 //We have a valid session just switch to that client
1584 SetCurrentClient(sessionId, true);
1593 /// <param name="aSessionId"></param>
1594 /// <param name="aCallback"></param>
1595 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1597 if (this.InvokeRequired)
1599 //Not in the proper thread, invoke ourselves
1600 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1601 this.Invoke(d, new object[] {aSessionId, aCallback});
1605 //We are in the proper thread
1606 //Add this session to our collection of clients
1607 ClientData newClient = new ClientData(aSessionId, aCallback);
1608 Program.iFormMain.iClients.Add(aSessionId, newClient);
1609 //Add this session to our UI
1610 UpdateClientTreeViewNode(newClient);
1616 /// Find the client with the highest priority if any.
1618 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1619 public ClientData FindHighestPriorityClient()
1621 ClientData highestPriorityClient = null;
1622 foreach (var client in iClients)
1624 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1626 highestPriorityClient = client.Value;
1630 return highestPriorityClient;
1636 /// <param name="aSessionId"></param>
1637 public void RemoveClientThreadSafe(string aSessionId)
1639 if (this.InvokeRequired)
1641 //Not in the proper thread, invoke ourselves
1642 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1643 this.Invoke(d, new object[] {aSessionId});
1647 //We are in the proper thread
1648 //Remove this session from both client collection and UI tree view
1649 if (Program.iFormMain.iClients.Keys.Contains(aSessionId))
1651 Program.iFormMain.iClients.Remove(aSessionId);
1652 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1653 Program.iFormMain.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)
2401 "The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " +
2405 catch (InvalidDeploymentException ide)
2408 "Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " +
2412 catch (InvalidOperationException ioe)
2415 "This application cannot be updated. It is likely not a ClickOnce application. Error: " +
2420 if (info.UpdateAvailable)
2422 Boolean doUpdate = true;
2424 if (!info.IsUpdateRequired)
2427 MessageBox.Show("An update is available. Would you like to update the application now?",
2428 "Update Available", MessageBoxButtons.OKCancel);
2429 if (!(DialogResult.OK == dr))
2436 // Display a message that the application MUST reboot. Display the minimum required version.
2437 MessageBox.Show("This application has detected a mandatory update from your current " +
2438 "version to version " + info.MinimumRequiredVersion.ToString() +
2439 ". The application will now install the update and restart.",
2440 "Update Available", MessageBoxButtons.OK,
2441 MessageBoxIcon.Information);
2449 MessageBox.Show("The application has been upgraded, and will now restart.");
2450 Application.Restart();
2452 catch (DeploymentDownloadException dde)
2455 "Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " +
2463 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2472 private void SysTrayHideShow()
2478 WindowState = FormWindowState.Normal;
2483 /// Use to handle minimize events.
2485 /// <param name="sender"></param>
2486 /// <param name="e"></param>
2487 private void MainForm_SizeChanged(object sender, EventArgs e)
2489 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2501 /// <param name="sender"></param>
2502 /// <param name="e"></param>
2503 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2505 //Our table layout size has changed which means our display size has changed.
2506 //We need to re-create our bitmap.
2507 iCreateBitmap = true;
2511 /// Broadcast messages to subscribers.
2513 /// <param name="message"></param>
2514 protected override void WndProc(ref Message aMessage)
2516 if (OnWndProc != null)
2518 OnWndProc(ref aMessage);
2521 base.WndProc(ref aMessage);
2524 private void iCheckBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2530 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2532 //Save CEC HDMI port
2533 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2534 Properties.Settings.Default.CecHdmiPort++;
2535 Properties.Settings.Default.Save();
2543 private void ResetCec()
2545 if (iCecManager == null)
2547 //Thus skipping initial UI setup
2553 if (Properties.Settings.Default.CecEnabled)
2555 iCecManager.Start(Handle, "CEC",
2556 Properties.Settings.Default.CecHdmiPort);
2565 private async void ResetHarmonyAsync(bool aForceAuth=false)
2567 // ConnectAsync already if we have an existing session cookie
2568 if (Properties.Settings.Default.HarmonyEnabled)
2572 iButtonHarmonyConnect.Enabled = false;
2573 await ConnectHarmonyAsync(aForceAuth);
2575 catch (Exception ex)
2577 Trace.WriteLine("Exception thrown by ConnectHarmonyAsync");
2578 Trace.WriteLine(ex.ToString());
2582 iButtonHarmonyConnect.Enabled = true;
2590 /// <param name="sender"></param>
2591 /// <param name="e"></param>
2592 private void iButtonHarmonyConnect_Click(object sender, EventArgs e)
2594 // User is explicitaly trying to connect
2595 //Reset Harmony Hub connection forcing authentication
2596 ResetHarmonyAsync(true);
2602 /// <param name="sender"></param>
2603 /// <param name="e"></param>
2604 private void iCheckBoxHarmonyEnabled_CheckedChanged(object sender, EventArgs e)
2606 iButtonHarmonyConnect.Enabled = iCheckBoxHarmonyEnabled.Checked;
2612 private void SetupCecLogLevel()
2615 iCecManager.Client.LogLevel = 0;
2617 if (checkBoxCecLogError.Checked)
2618 iCecManager.Client.LogLevel |= (int) CecLogLevel.Error;
2620 if (checkBoxCecLogWarning.Checked)
2621 iCecManager.Client.LogLevel |= (int) CecLogLevel.Warning;
2623 if (checkBoxCecLogNotice.Checked)
2624 iCecManager.Client.LogLevel |= (int) CecLogLevel.Notice;
2626 if (checkBoxCecLogTraffic.Checked)
2627 iCecManager.Client.LogLevel |= (int) CecLogLevel.Traffic;
2629 if (checkBoxCecLogDebug.Checked)
2630 iCecManager.Client.LogLevel |= (int) CecLogLevel.Debug;
2632 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2636 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2641 private void buttonClearLogs_Click(object sender, EventArgs e)
2643 richTextBoxLogs.Clear();
2646 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)
2655 /// <param name="aEvent"></param>
2656 private void SelectEvent(Ear.Event aEvent)
2663 foreach (TreeNode node in iTreeViewEvents.Nodes)
2665 if (node.Tag == aEvent)
2667 iTreeViewEvents.SelectedNode = node;
2668 iTreeViewEvents.Focus();
2676 /// Get the current event based on event tree view selection.
2678 /// <returns></returns>
2679 private Ear.Event CurrentEvent()
2681 //Walk up the tree from the selected node to find our event
2682 TreeNode node = iTreeViewEvents.SelectedNode;
2683 Ear.Event selectedEvent = null;
2684 while (node != null)
2686 if (node.Tag is Ear.Event)
2688 selectedEvent = (Ear.Event) node.Tag;
2694 return selectedEvent;
2698 /// Get the current action based on event tree view selection
2700 /// <returns></returns>
2701 private Ear.Action CurrentAction()
2703 TreeNode node = iTreeViewEvents.SelectedNode;
2704 if (node != null && node.Tag is Ear.Action)
2706 return (Ear.Action) node.Tag;
2715 /// <param name="sender"></param>
2716 /// <param name="e"></param>
2717 private void buttonActionAdd_Click(object sender, EventArgs e)
2719 Ear.Event selectedEvent = CurrentEvent();
2720 if (selectedEvent == null)
2722 //We did not find a corresponding event
2726 FormEditObject<Ear.Action> ea = new FormEditObject<Ear.Action>();
2727 ea.Text = "Add action";
2728 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2729 if (res == DialogResult.OK)
2731 selectedEvent.Actions.Add(ea.Object);
2732 Properties.Settings.Default.Save();
2733 PopulateTreeViewEvents();
2740 /// <param name="sender"></param>
2741 /// <param name="e"></param>
2742 private void buttonActionEdit_Click(object sender, EventArgs e)
2744 Ear.Event selectedEvent = CurrentEvent();
2745 Ear.Action selectedAction = CurrentAction();
2746 if (selectedEvent == null || selectedAction == null)
2748 //We did not find a corresponding event
2752 FormEditObject<Ear.Action> ea = new FormEditObject<Ear.Action>();
2753 ea.Text = "Edit action";
2754 ea.Object = selectedAction;
2755 int actionIndex = iTreeViewEvents.SelectedNode.Index;
2756 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2757 if (res == DialogResult.OK)
2760 selectedEvent.Actions[actionIndex]=ea.Object;
2761 //Save and rebuild our event tree view
2762 Properties.Settings.Default.Save();
2763 PopulateTreeViewEvents();
2770 /// <param name="sender"></param>
2771 /// <param name="e"></param>
2772 private void buttonActionDelete_Click(object sender, EventArgs e)
2775 Ear.Action action = CurrentAction();
2778 //Must select action node
2782 Properties.Settings.Default.EarManager.RemoveAction(action);
2783 Properties.Settings.Default.Save();
2784 PopulateTreeViewEvents();
2790 /// <param name="sender"></param>
2791 /// <param name="e"></param>
2792 private void buttonActionTest_Click(object sender, EventArgs e)
2794 Ear.Action a = CurrentAction();
2799 iTreeViewEvents.Focus();
2805 /// <param name="sender"></param>
2806 /// <param name="e"></param>
2807 private void buttonActionMoveUp_Click(object sender, EventArgs e)
2809 Ear.Action a = CurrentAction();
2811 //Action already at the top of the list
2812 iTreeViewEvents.SelectedNode.Index == 0)
2817 //Swap actions in event's action list
2818 Ear.Event currentEvent = CurrentEvent();
2819 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2820 Ear.Action movingUp = currentEvent.Actions[currentIndex];
2821 Ear.Action movingDown = currentEvent.Actions[currentIndex-1];
2822 currentEvent.Actions[currentIndex] = movingDown;
2823 currentEvent.Actions[currentIndex-1] = movingUp;
2825 //Save and populate our tree again
2826 Properties.Settings.Default.Save();
2827 PopulateTreeViewEvents();
2834 /// <param name="sender"></param>
2835 /// <param name="e"></param>
2836 private void buttonActionMoveDown_Click(object sender, EventArgs e)
2838 Ear.Action a = CurrentAction();
2840 //Action already at the bottom of the list
2841 iTreeViewEvents.SelectedNode.Index == iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1)
2846 //Swap actions in event's action list
2847 Ear.Event currentEvent = CurrentEvent();
2848 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2849 Ear.Action movingDown = currentEvent.Actions[currentIndex];
2850 Ear.Action movingUp = currentEvent.Actions[currentIndex + 1];
2851 currentEvent.Actions[currentIndex] = movingUp;
2852 currentEvent.Actions[currentIndex + 1] = movingDown;
2854 //Save and populate our tree again
2855 Properties.Settings.Default.Save();
2856 PopulateTreeViewEvents();
2863 /// <param name="sender"></param>
2864 /// <param name="e"></param>
2865 private void buttonEventTest_Click(object sender, EventArgs e)
2867 Ear.Event earEvent = CurrentEvent();
2868 if (earEvent != null)
2875 /// Manages events and actions buttons according to selected item in event tree.
2877 /// <param name="sender"></param>
2878 /// <param name="e"></param>
2879 private void iTreeViewEvents_AfterSelect(object sender, TreeViewEventArgs e)
2887 private void UpdateEventView()
2889 //One can always add an event
2890 buttonEventAdd.Enabled = true;
2892 //Enable buttons according to selected item
2893 buttonActionAdd.Enabled =
2894 buttonEventTest.Enabled =
2895 buttonEventDelete.Enabled =
2896 buttonEventEdit.Enabled =
2897 CurrentEvent() != null;
2899 Ear.Action currentAction = CurrentAction();
2900 //If an action is selected enable the following buttons
2901 buttonActionTest.Enabled =
2902 buttonActionDelete.Enabled =
2903 buttonActionMoveUp.Enabled =
2904 buttonActionMoveDown.Enabled =
2905 buttonActionEdit.Enabled =
2906 currentAction != null;
2908 if (currentAction != null)
2910 //If an action is selected enable move buttons if needed
2911 buttonActionMoveUp.Enabled = iTreeViewEvents.SelectedNode.Index != 0;
2912 buttonActionMoveDown.Enabled = iTreeViewEvents.SelectedNode.Index <
2913 iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1;
2920 /// <param name="sender"></param>
2921 /// <param name="e"></param>
2922 private void buttonEventAdd_Click(object sender, EventArgs e)
2924 FormEditObject<Ear.Event> ea = new FormEditObject<Ear.Event>();
2925 ea.Text = "Add event";
2926 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2927 if (res == DialogResult.OK)
2929 Properties.Settings.Default.EarManager.Events.Add(ea.Object);
2930 Properties.Settings.Default.Save();
2931 PopulateTreeViewEvents();
2932 SelectEvent(ea.Object);
2939 /// <param name="sender"></param>
2940 /// <param name="e"></param>
2941 private void buttonEventDelete_Click(object sender, EventArgs e)
2943 Ear.Event currentEvent = CurrentEvent();
2944 if (currentEvent == null)
2946 //Must select action node
2950 Properties.Settings.Default.EarManager.Events.Remove(currentEvent);
2951 Properties.Settings.Default.Save();
2952 PopulateTreeViewEvents();
2958 /// <param name="sender"></param>
2959 /// <param name="e"></param>
2960 private void buttonEventEdit_Click(object sender, EventArgs e)
2962 Ear.Event selectedEvent = CurrentEvent();
2963 if (selectedEvent == null)
2965 //We did not find a corresponding event
2969 FormEditObject<Ear.Event> ea = new FormEditObject<Ear.Event>();
2970 ea.Text = "Edit event";
2971 ea.Object = selectedEvent;
2972 int index = iTreeViewEvents.SelectedNode.Index;
2973 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2974 if (res == DialogResult.OK)
2976 //Make sure we keep the same actions as before
2977 ea.Object.Actions = Properties.Settings.Default.EarManager.Events[index].Actions;
2979 Properties.Settings.Default.EarManager.Events[index] = ea.Object;
2980 //Save and rebuild our event tree view
2981 Properties.Settings.Default.Save();
2982 PopulateTreeViewEvents();
2989 /// <param name="sender"></param>
2990 /// <param name="e"></param>
2991 private void iTreeViewEvents_Leave(object sender, EventArgs e)
2993 //Make sure our event tree never looses focus
2994 ((TreeView) sender).Focus();
2998 /// Called whenever we loose connection with our HarmonyHub.
3000 /// <param name="aRequestWasCancelled"></param>
3001 private void HarmonyConnectionClosedByServer(object aSender, bool aRequestWasCancelled)
3003 //Try reconnect then
3004 #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
3005 BeginInvoke(new MethodInvoker(delegate () { ResetHarmonyAsync(); }));
3006 #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
3012 /// <returns></returns>
3013 private async Task ConnectHarmonyAsync(bool aForceAuth=false)
3015 if (Program.HarmonyClient != null)
3017 await Program.HarmonyClient.CloseAsync();
3020 //Reset Harmony client & config
3021 Program.HarmonyClient = null;
3022 Program.HarmonyConfig = null;
3024 Trace.WriteLine("Harmony: Connecting... ");
3025 //First create our client and login
3026 //Tip: Set keep-alive to false when testing reconnection process
3027 Program.HarmonyClient = new HarmonyHub.Client(iTextBoxHarmonyHubAddress.Text, true);
3028 Program.HarmonyClient.OnConnectionClosedByServer += HarmonyConnectionClosedByServer;
3030 if (File.Exists("SessionToken") && !aForceAuth)
3032 var sessionToken = File.ReadAllText("SessionToken");
3033 Trace.WriteLine("Harmony: Reusing token: {0}", sessionToken);
3034 await Program.HarmonyClient.TryOpenAsync(sessionToken);
3037 if (!Program.HarmonyClient.IsReady)
3039 //We failed to connect using our token
3041 File.Delete("SessionToken");
3043 //Then try connect using our password
3044 if (string.IsNullOrEmpty(iTextBoxLogitechPassword.Text))
3046 Trace.WriteLine("Harmony: Credentials missing!");
3050 Trace.WriteLine("Harmony: Authenticating with Logitech servers...");
3051 await Program.HarmonyClient.TryOpenAsync(iTextBoxLogitechUserName.Text, iTextBoxLogitechPassword.Text);
3052 File.WriteAllText("SessionToken", Program.HarmonyClient.Token);
3056 Program.HarmonyConfig = await Program.HarmonyClient.GetConfigAsync();
3057 PopulateTreeViewHarmony(Program.HarmonyConfig);
3058 //Make sure harmony command actions are showing device name instead of device id
3059 PopulateTreeViewEvents();
3065 /// <param name="aConfig"></param>
3066 private void PopulateTreeViewHarmony(HarmonyHub.Config aConfig)
3068 iTreeViewHarmony.Nodes.Clear();
3070 foreach (HarmonyHub.Device device in aConfig.Devices)
3072 TreeNode deviceNode = iTreeViewHarmony.Nodes.Add(device.Id, $"{device.Label} ({device.DeviceTypeDisplayName}/{device.Model})");
3073 deviceNode.Tag = device;
3075 foreach (HarmonyHub.ControlGroup cg in device.ControlGroups)
3077 TreeNode cgNode = deviceNode.Nodes.Add(cg.Name);
3080 foreach (HarmonyHub.Function f in cg.Functions)
3082 TreeNode fNode = cgNode.Nodes.Add(f.Label);
3088 //treeViewConfig.ExpandAll();
3091 private async void iTreeViewHarmony_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
3093 //Upon function node double click we execute it
3094 var tag = e.Node.Tag as HarmonyHub.Function;
3095 if (tag != null && e.Node.Parent.Parent.Tag is HarmonyHub.Device)
3097 HarmonyHub.Function f = tag;
3098 HarmonyHub.Device d = (HarmonyHub.Device)e.Node.Parent.Parent.Tag;
3100 Trace.WriteLine($"Harmony: Sending {f.Label} to {d.Label}...");
3102 await Program.HarmonyClient.TrySendKeyPressAsync(d.Id, f.Action.Command);