EAR: Actions now support multiple iterations.
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 //Use color from event unless our action itself is disabled
364 actionNode.ForeColor = a.Enabled? eventNode.ForeColor: Color.DimGray;
365 if (a == currentAction)
367 treeNodeToSelect = actionNode;
372 iTreeViewEvents.ExpandAll();
373 SelectEvent(currentEvent);
375 if (treeNodeToSelect != null)
377 iTreeViewEvents.SelectedNode = treeNodeToSelect;
379 else if (iTreeViewEvents.SelectedNode != null && iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) > 0)
381 //Select the last action if any
382 iTreeViewEvents.SelectedNode =
383 iTreeViewEvents.SelectedNode.Nodes[1].Nodes[
384 iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) - 1];
386 else if (iTreeViewEvents.SelectedNode == null && iTreeViewEvents.Nodes.Count > 0)
388 //Still no selected node select the first one then
389 iTreeViewEvents.SelectedNode = iTreeViewEvents.Nodes[0];
397 /// Called when our display is closed.
399 /// <param name="aDisplay"></param>
400 private void OnDisplayClosed(Display aDisplay)
402 //Our display was just closed, update our UI consequently
406 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
408 //Update network status
409 UpdateNetworkStatus();
413 /// Update our Network Status
415 private void UpdateNetworkStatus()
417 if (iDisplay.IsOpen())
419 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet,
420 iNetworkManager.NetworkListManager.IsConnectedToInternet);
421 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
426 int iLastNetworkIconIndex = 0;
427 int iUpdateCountSinceLastNetworkAnimation = 0;
432 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
434 iUpdateCountSinceLastNetworkAnimation++;
435 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation%4;
437 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected &&
438 iUpdateCountSinceLastNetworkAnimation == 0)
440 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
443 //Prevents div by zero and other undefined behavior
446 iLastNetworkIconIndex++;
447 iLastNetworkIconIndex = iLastNetworkIconIndex%(iconCount*2);
448 for (int i = 0; i < iconCount; i++)
450 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) &&
451 !(i == 1 && iLastNetworkIconIndex > 4))
453 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
457 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
466 /// Receive volume change notification and reflect changes on our slider.
468 /// <param name="data"></param>
469 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
471 UpdateMasterVolumeThreadSafe();
475 /// Update master volume when user moves our slider.
477 /// <param name="sender"></param>
478 /// <param name="e"></param>
479 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
481 //Just like Windows Volume Mixer we unmute if the volume is adjusted
482 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
483 //Set volume level according to our volume slider new position
484 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value/100.0f;
489 /// Mute check box changed.
491 /// <param name="sender"></param>
492 /// <param name="e"></param>
493 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
495 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
499 /// Device State Changed
501 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId,
502 [MarshalAs(UnmanagedType.I4)] DeviceState newState)
509 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId)
516 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId)
521 /// Default Device Changed
523 public void OnDefaultDeviceChanged(DataFlow flow, Role role,
524 [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
526 if (role == Role.Multimedia && flow == DataFlow.Render)
528 UpdateAudioDeviceAndMasterVolumeThreadSafe();
533 /// Property Value Changed
535 /// <param name="pwstrDeviceId"></param>
536 /// <param name="key"></param>
537 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key)
545 /// Update master volume indicators based our current system states.
546 /// This typically includes volume levels and mute status.
548 private void UpdateMasterVolumeThreadSafe()
550 if (this.InvokeRequired)
552 //Not in the proper thread, invoke ourselves
553 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
554 this.Invoke(d, new object[] {});
558 //Update volume slider
559 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
560 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar*100);
561 //Update mute checkbox
562 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
564 //If our display connection is open we need to update its icons
565 if (iDisplay.IsOpen())
567 //First take care our our volume level icons
568 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
569 if (volumeIconCount > 0)
571 //Compute current volume level from system level and the number of segments in our display volume bar.
572 //That tells us how many segments in our volume bar needs to be turned on.
573 float currentVolume = volumeLevelScalar*volumeIconCount;
574 int segmentOnCount = Convert.ToInt32(currentVolume);
575 //Check if our segment count was rounded up, this will later be used for half brightness segment
576 bool roundedUp = segmentOnCount > currentVolume;
578 for (int i = 0; i < volumeIconCount; i++)
580 if (i < segmentOnCount)
582 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
583 if (i == segmentOnCount - 1 && roundedUp)
586 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
587 (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1)/2);
592 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
593 iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
598 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
603 //Take care of our mute icon
604 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
612 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
614 if (this.InvokeRequired)
616 //Not in the proper thread, invoke ourselves
617 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
618 this.Invoke(d, new object[] {});
622 //We are in the correct thread just go ahead.
625 //Get our master volume
626 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
628 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
630 //Show our volume in our track bar
631 UpdateMasterVolumeThreadSafe();
633 //Register to get volume modifications
634 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
636 trackBarMasterVolume.Enabled = true;
640 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
641 Debug.WriteLine(ex.ToString());
642 //Something went wrong S/PDIF device ca throw exception I guess
643 trackBarMasterVolume.Enabled = false;
650 private void PopulateDeviceTypes()
652 int count = Display.TypeCount();
654 for (int i = 0; i < count; i++)
656 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type) i));
663 private void SetupTrayIcon()
665 iNotifyIcon.Icon = GetIcon("vfd.ico");
666 iNotifyIcon.Text = "Sharp Display Manager";
667 iNotifyIcon.Visible = true;
669 //Double click toggles visibility - typically brings up the application
670 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
675 //Adding a context menu, useful to be able to exit the application
676 ContextMenu contextMenu = new ContextMenu();
677 //Context menu item to toggle visibility
678 MenuItem hideShowItem = new MenuItem("Hide/Show");
679 hideShowItem.Click += delegate(object obj, EventArgs args)
683 contextMenu.MenuItems.Add(hideShowItem);
685 //Context menu item separator
686 contextMenu.MenuItems.Add(new MenuItem("-"));
688 //Context menu exit item
689 MenuItem exitItem = new MenuItem("Exit");
690 exitItem.Click += delegate(object obj, EventArgs args)
694 contextMenu.MenuItems.Add(exitItem);
696 iNotifyIcon.ContextMenu = contextMenu;
702 private void SetupRecordingNotification()
704 iRecordingNotification.Icon = GetIcon("record.ico");
705 iRecordingNotification.Text = "No recording";
706 iRecordingNotification.Visible = false;
710 /// Access icons from embedded resources.
712 /// <param name="aName"></param>
713 /// <returns></returns>
714 public static Icon GetIcon(string aName)
716 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
717 foreach (string name in names)
719 //Find a resource name that ends with the given name
720 if (name.EndsWith(aName))
722 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
724 return new Icon(stream);
734 /// Set our current client.
735 /// This will take care of applying our client layout and set data fields.
737 /// <param name="aSessionId"></param>
738 void SetCurrentClient(string aSessionId, bool aForce = false)
740 if (aSessionId == iCurrentClientSessionId)
742 //Given client is already the current one.
743 //Don't bother changing anything then.
747 ClientData requestedClientData = iClients[aSessionId];
749 //Check when was the last time we switched to that client
750 if (iCurrentClientData != null)
752 //Do not switch client if priority of current client is higher
753 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
759 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
760 //TODO: put that hard coded value as a client property
761 //Clients should be able to define how often they can be interrupted
762 //Thus a background client can set this to zero allowing any other client to interrupt at any time
763 //We could also compute this delay by looking at the requests frequencies?
765 requestedClientData.Priority == iCurrentClientData.Priority &&
766 //Time sharing is only if clients have the same priority
767 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
769 //Don't switch clients too often
774 //Set current client ID.
775 iCurrentClientSessionId = aSessionId;
776 //Set the time we last switched to that client
777 iClients[aSessionId].LastSwitchTime = DateTime.Now;
778 //Fetch and set current client data.
779 iCurrentClientData = requestedClientData;
780 //Apply layout and set data fields.
781 UpdateTableLayoutPanel(iCurrentClientData);
784 private void buttonFont_Click(object sender, EventArgs e)
786 //fontDialog.ShowColor = true;
787 //fontDialog.ShowApply = true;
788 fontDialog.ShowEffects = true;
789 fontDialog.Font = cds.Font;
791 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
793 //fontDialog.ShowHelp = true;
795 //fontDlg.MaxSize = 40;
796 //fontDlg.MinSize = 22;
798 //fontDialog.Parent = this;
799 //fontDialog.StartPosition = FormStartPosition.CenterParent;
801 //DlgBox.ShowDialog(fontDialog);
803 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
804 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
806 //Set the fonts to all our labels in our layout
807 foreach (Control ctrl in iTableLayoutPanel.Controls)
809 if (ctrl is MarqueeLabel)
811 ((MarqueeLabel) ctrl).Font = fontDialog.Font;
816 cds.Font = fontDialog.Font;
817 Properties.Settings.Default.Save();
826 void CheckFontHeight()
828 //Show font height and width
829 labelFontHeight.Text = "Font height: " + cds.Font.Height;
830 float charWidth = IsFixedWidth(cds.Font);
831 if (charWidth == 0.0f)
833 labelFontWidth.Visible = false;
837 labelFontWidth.Visible = true;
838 labelFontWidth.Text = "Font width: " + charWidth;
841 MarqueeLabel label = null;
842 //Get the first label control we can find
843 foreach (Control ctrl in iTableLayoutPanel.Controls)
845 if (ctrl is MarqueeLabel)
847 label = (MarqueeLabel) ctrl;
852 //Now check font height and show a warning if needed.
853 if (label != null && label.Font.Height > label.Height)
855 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) +
857 labelWarning.Visible = true;
861 labelWarning.Visible = false;
866 private void buttonCapture_Click(object sender, EventArgs e)
868 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
869 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
870 //Bitmap bmpToSave = new Bitmap(bmp);
871 bmp.Save("D:\\capture.png");
873 ((MarqueeLabel) iTableLayoutPanel.Controls[0]).Text = "Captured";
876 string outputFileName = "d:\\capture.png";
877 using (MemoryStream memory = new MemoryStream())
879 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
881 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
882 byte[] bytes = memory.ToArray();
883 fs.Write(bytes, 0, bytes.Length);
890 private void CheckForRequestResults()
892 if (iDisplay.IsRequestPending())
894 switch (iDisplay.AttemptRequestCompletion())
896 case MiniDisplay.Request.FirmwareRevision:
897 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
898 //Issue next request then
899 iDisplay.RequestPowerSupplyStatus();
902 case MiniDisplay.Request.PowerSupplyStatus:
903 if (iDisplay.PowerSupplyStatus())
905 toolStripStatusLabelPower.Text = "ON";
909 toolStripStatusLabelPower.Text = "OFF";
911 //Issue next request then
912 iDisplay.RequestDeviceId();
915 case MiniDisplay.Request.DeviceId:
916 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
917 //No more request to issue
923 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
925 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
932 public static uint ColorUntouched(int aX, int aY, uint aPixel)
937 public static uint ColorInversed(int aX, int aY, uint aPixel)
942 public static uint ColorChessboard(int aX, int aY, uint aPixel)
944 if ((aX%2 == 0) && (aY%2 == 0))
948 else if ((aX%2 != 0) && (aY%2 != 0))
956 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
958 return aBmp.Width - aX - 1;
961 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
963 return iBmp.Height - aY - 1;
966 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
971 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
977 /// Select proper pixel delegates according to our current settings.
979 private void SetupPixelDelegates()
981 //Select our pixel processing routine
982 if (cds.InverseColors)
984 //iColorFx = ColorChessboard;
985 iColorFx = ColorInversed;
989 iColorFx = ColorWhiteIsOn;
992 //Select proper coordinate translation functions
993 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
994 if (cds.ReverseScreen)
996 iScreenX = ScreenReversedX;
997 iScreenY = ScreenReversedY;
1007 //This is our timer tick responsible to perform our render
1008 private void timer_Tick(object sender, EventArgs e)
1010 //Update our animations
1011 DateTime NewTickTime = DateTime.Now;
1013 UpdateNetworkSignal(LastTickTime, NewTickTime);
1015 //Update animation for all our marquees
1016 foreach (Control ctrl in iTableLayoutPanel.Controls)
1018 if (ctrl is MarqueeLabel)
1020 ((MarqueeLabel) ctrl).UpdateAnimation(LastTickTime, NewTickTime);
1024 //Update our display
1025 if (iDisplay.IsOpen())
1027 CheckForRequestResults();
1029 //Check if frame rendering is needed
1030 //Typically used when showing clock
1031 if (!iSkipFrameRendering)
1036 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
1037 PixelFormat.Format32bppArgb);
1038 iCreateBitmap = false;
1040 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
1041 //iBmp.Save("D:\\capture.png");
1043 //Send it to our display
1044 for (int i = 0; i < iBmp.Width; i++)
1046 for (int j = 0; j < iBmp.Height; j++)
1050 //Get our processed pixel coordinates
1051 int x = iScreenX(iBmp, i);
1052 int y = iScreenY(iBmp, j);
1054 uint color = (uint) iBmp.GetPixel(i, j).ToArgb();
1055 //Apply color effects
1056 color = iColorFx(x, y, color);
1058 iDisplay.SetPixel(x, y, color);
1063 iDisplay.SwapBuffers();
1067 //Compute instant FPS
1068 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " +
1069 (1000/timer.Interval).ToString() + " FPS";
1071 LastTickTime = NewTickTime;
1076 /// Attempt to establish connection with our display hardware.
1078 private void OpenDisplayConnection()
1080 CloseDisplayConnection();
1082 if (!iDisplay.Open((MiniDisplay.Type) cds.DisplayType))
1085 toolStripStatusLabelConnect.Text = "Connection error";
1089 private void CloseDisplayConnection()
1091 //Status will be updated upon receiving the closed event
1093 if (iDisplay == null || !iDisplay.IsOpen())
1098 //Do not clear if we gave up on rendering already.
1099 //This means we will keep on displaying clock on MDM166AA for instance.
1100 if (!iSkipFrameRendering)
1103 iDisplay.SwapBuffers();
1106 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1110 private void buttonOpen_Click(object sender, EventArgs e)
1112 OpenDisplayConnection();
1115 private void buttonClose_Click(object sender, EventArgs e)
1117 CloseDisplayConnection();
1120 private void buttonClear_Click(object sender, EventArgs e)
1123 iDisplay.SwapBuffers();
1126 private void buttonFill_Click(object sender, EventArgs e)
1129 iDisplay.SwapBuffers();
1132 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1134 cds.Brightness = trackBarBrightness.Value;
1135 Properties.Settings.Default.Save();
1136 iDisplay.SetBrightness(trackBarBrightness.Value);
1142 /// CDS stands for Current Display Settings
1144 private DisplaySettings cds
1148 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1149 if (settings == null)
1151 settings = new DisplaysSettings();
1153 Properties.Settings.Default.DisplaysSettings = settings;
1156 //Make sure all our settings have been created
1157 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1159 settings.Displays.Add(new DisplaySettings());
1162 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1163 return displaySettings;
1168 /// Check if the given font has a fixed character pitch.
1170 /// <param name="ft"></param>
1171 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1172 public float IsFixedWidth(Font ft)
1174 Graphics g = CreateGraphics();
1175 char[] charSizes = new char[] {'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.'};
1176 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1178 bool fixedWidth = true;
1180 foreach (char c in charSizes)
1181 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width !=
1194 /// Synchronize UI with settings
1196 private void UpdateStatus()
1199 checkBoxShowBorders.Checked = cds.ShowBorders;
1200 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders
1201 ? TableLayoutPanelCellBorderStyle.Single
1202 : TableLayoutPanelCellBorderStyle.None);
1204 //Set the proper font to each of our labels
1205 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1207 ctrl.Font = cds.Font;
1211 //Check if "run on Windows startup" is enabled
1212 checkBoxAutoStart.Checked = iStartupManager.Startup;
1215 iButtonHarmonyConnect.Enabled = Properties.Settings.Default.HarmonyEnabled;
1218 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1220 //Mini Display settings
1221 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1222 checkBoxInverseColors.Checked = cds.InverseColors;
1223 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1224 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1225 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1226 labelMinFontSize.Enabled = cds.ScaleToFit;
1227 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1228 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1229 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1230 timer.Interval = cds.TimerInterval;
1231 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1232 textBoxScrollLoopSeparator.Text = cds.Separator;
1234 SetupPixelDelegates();
1236 if (iDisplay.IsOpen())
1238 //We have a display connection
1239 //Reflect that in our UI
1242 iTableLayoutPanel.Enabled = true;
1243 panelDisplay.Enabled = true;
1245 //Only setup brightness if display is open
1246 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1247 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1248 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1250 //Brightness out of range, this can occur when using auto-detect
1251 //Use max brightness instead
1252 trackBarBrightness.Value = iDisplay.MaxBrightness();
1253 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1257 trackBarBrightness.Value = cds.Brightness;
1258 iDisplay.SetBrightness(cds.Brightness);
1261 //Try compute the steps to something that makes sense
1262 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness())/5);
1263 trackBarBrightness.SmallChange = 1;
1266 buttonFill.Enabled = true;
1267 buttonClear.Enabled = true;
1268 buttonOpen.Enabled = false;
1269 buttonClose.Enabled = true;
1270 trackBarBrightness.Enabled = true;
1271 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1272 //+ " - " + iDisplay.SerialNumber();
1274 if (iDisplay.SupportPowerOnOff())
1276 buttonPowerOn.Enabled = true;
1277 buttonPowerOff.Enabled = true;
1281 buttonPowerOn.Enabled = false;
1282 buttonPowerOff.Enabled = false;
1285 if (iDisplay.SupportClock())
1287 buttonShowClock.Enabled = true;
1288 buttonHideClock.Enabled = true;
1292 buttonShowClock.Enabled = false;
1293 buttonHideClock.Enabled = false;
1297 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1298 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel) > 0;
1300 if (cds.ShowVolumeLabel)
1302 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1306 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1311 //Display connection not available
1312 //Reflect that in our UI
1314 //In debug start our timer even if we don't have a display connection
1317 //In production environment we don't need our timer if no display connection
1320 checkBoxShowVolumeLabel.Enabled = false;
1321 iTableLayoutPanel.Enabled = false;
1322 panelDisplay.Enabled = false;
1323 buttonFill.Enabled = false;
1324 buttonClear.Enabled = false;
1325 buttonOpen.Enabled = true;
1326 buttonClose.Enabled = false;
1327 trackBarBrightness.Enabled = false;
1328 buttonPowerOn.Enabled = false;
1329 buttonPowerOff.Enabled = false;
1330 buttonShowClock.Enabled = false;
1331 buttonHideClock.Enabled = false;
1332 toolStripStatusLabelConnect.Text = "Disconnected";
1333 toolStripStatusLabelPower.Text = "N/A";
1342 /// <param name="sender"></param>
1343 /// <param name="e"></param>
1344 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1346 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1347 Properties.Settings.Default.Save();
1351 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1353 //Save our show borders setting
1354 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked
1355 ? TableLayoutPanelCellBorderStyle.Single
1356 : TableLayoutPanelCellBorderStyle.None);
1357 cds.ShowBorders = checkBoxShowBorders.Checked;
1358 Properties.Settings.Default.Save();
1362 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1364 iStartupManager.Startup = checkBoxAutoStart.Checked;
1368 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1370 //Save our reverse screen setting
1371 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1372 Properties.Settings.Default.Save();
1373 SetupPixelDelegates();
1376 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1378 //Save our inverse colors setting
1379 cds.InverseColors = checkBoxInverseColors.Checked;
1380 Properties.Settings.Default.Save();
1381 SetupPixelDelegates();
1384 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1386 //Save our scale to fit setting
1387 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1388 Properties.Settings.Default.Save();
1390 labelMinFontSize.Enabled = cds.ScaleToFit;
1391 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1394 private void MainForm_Resize(object sender, EventArgs e)
1396 if (WindowState == FormWindowState.Minimized)
1398 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1399 // That's apparently not needed on Windows 10 but we better leave it in place.
1400 iCreateBitmap = true;
1404 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1407 iNetworkManager.Dispose();
1408 CloseDisplayConnection();
1410 e.Cancel = iClosing;
1413 public void StartServer()
1415 iServiceHost = new ServiceHost
1418 new Uri[] {new Uri("net.tcp://localhost:8001/")}
1421 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true),
1423 iServiceHost.Open();
1426 public void StopServer()
1428 if (iClients.Count > 0 && !iClosing)
1432 BroadcastCloseEvent();
1437 MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo,
1438 MessageBoxIcon.Warning) == DialogResult.Yes)
1440 iClosing = false; //We make sure we force close if asked twice
1445 //We removed that as it often lags for some reason
1446 //iServiceHost.Close();
1450 public void BroadcastCloseEvent()
1452 Trace.TraceInformation("BroadcastCloseEvent - start");
1454 var inactiveClients = new List<string>();
1455 foreach (var client in iClients)
1457 //if (client.Key != eventData.ClientName)
1461 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1462 client.Value.Callback.OnCloseOrder( /*eventData*/);
1464 catch (Exception ex)
1466 inactiveClients.Add(client.Key);
1471 if (inactiveClients.Count > 0)
1473 foreach (var client in inactiveClients)
1475 iClients.Remove(client);
1476 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1477 Program.iFormMain.iTreeViewClients.Nodes.Find(client, false)[0]);
1481 if (iClients.Count == 0)
1488 /// Just remove all our fields.
1490 private void ClearLayout()
1492 iTableLayoutPanel.Controls.Clear();
1493 iTableLayoutPanel.RowStyles.Clear();
1494 iTableLayoutPanel.ColumnStyles.Clear();
1495 iCurrentClientData = null;
1499 /// Just launch a demo client.
1501 private void StartNewClient(string aTopText = "", string aBottomText = "")
1503 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1504 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(
1505 new Point(this.Right, this.Top), aTopText, aBottomText);
1506 clientThread.Start(myParams);
1511 /// Just launch our idle client.
1513 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1515 Thread clientThread = new Thread(SharpDisplayClientIdle.Program.MainWithParams);
1516 SharpDisplayClientIdle.StartParams myParams =
1517 new SharpDisplayClientIdle.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1518 clientThread.Start(myParams);
1523 private void buttonStartClient_Click(object sender, EventArgs e)
1528 private void buttonSuspend_Click(object sender, EventArgs e)
1533 private void StartTimer()
1535 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1536 timer.Enabled = true;
1537 UpdateSuspendButton();
1540 private void StopTimer()
1542 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1543 timer.Enabled = false;
1544 UpdateSuspendButton();
1547 private void ToggleTimer()
1549 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1550 timer.Enabled = !timer.Enabled;
1551 UpdateSuspendButton();
1554 private void UpdateSuspendButton()
1558 buttonSuspend.Text = "Run";
1562 buttonSuspend.Text = "Pause";
1567 private void buttonCloseClients_Click(object sender, EventArgs e)
1569 BroadcastCloseEvent();
1572 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1574 //Root node must have at least one child
1575 if (e.Node.Nodes.Count == 0)
1580 //If the selected node is the root node of a client then switch to it
1581 string sessionId = e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1582 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1584 //We have a valid session just switch to that client
1585 SetCurrentClient(sessionId, true);
1594 /// <param name="aSessionId"></param>
1595 /// <param name="aCallback"></param>
1596 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1598 if (this.InvokeRequired)
1600 //Not in the proper thread, invoke ourselves
1601 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1602 this.Invoke(d, new object[] {aSessionId, aCallback});
1606 //We are in the proper thread
1607 //Add this session to our collection of clients
1608 ClientData newClient = new ClientData(aSessionId, aCallback);
1609 Program.iFormMain.iClients.Add(aSessionId, newClient);
1610 //Add this session to our UI
1611 UpdateClientTreeViewNode(newClient);
1617 /// Find the client with the highest priority if any.
1619 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1620 public ClientData FindHighestPriorityClient()
1622 ClientData highestPriorityClient = null;
1623 foreach (var client in iClients)
1625 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1627 highestPriorityClient = client.Value;
1631 return highestPriorityClient;
1637 /// <param name="aSessionId"></param>
1638 public void RemoveClientThreadSafe(string aSessionId)
1640 if (this.InvokeRequired)
1642 //Not in the proper thread, invoke ourselves
1643 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1644 this.Invoke(d, new object[] {aSessionId});
1648 //We are in the proper thread
1649 //Remove this session from both client collection and UI tree view
1650 if (Program.iFormMain.iClients.Keys.Contains(aSessionId))
1652 Program.iFormMain.iClients.Remove(aSessionId);
1653 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1654 Program.iFormMain.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1655 //Update recording status too whenever a client is removed
1656 UpdateRecordingNotification();
1659 if (iCurrentClientSessionId == aSessionId)
1661 //The current client is closing
1662 iCurrentClientData = null;
1663 //Find the client with the highest priority and set it as current
1664 ClientData newCurrentClient = FindHighestPriorityClient();
1665 if (newCurrentClient != null)
1667 SetCurrentClient(newCurrentClient.SessionId, true);
1671 if (iClients.Count == 0)
1673 //Clear our screen when last client disconnects
1678 //We were closing our form
1679 //All clients are now closed
1680 //Just resume our close operation
1691 /// <param name="aSessionId"></param>
1692 /// <param name="aLayout"></param>
1693 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1695 if (this.InvokeRequired)
1697 //Not in the proper thread, invoke ourselves
1698 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1699 this.Invoke(d, new object[] {aSessionId, aLayout});
1703 ClientData client = iClients[aSessionId];
1706 //Don't change a thing if the layout is the same
1707 if (!client.Layout.IsSameAs(aLayout))
1709 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1710 //Set our client layout then
1711 client.Layout = aLayout;
1712 //So that next time we update all our fields at ones
1713 client.HasNewLayout = true;
1714 //Layout has changed clear our fields then
1715 client.Fields.Clear();
1717 UpdateClientTreeViewNode(client);
1721 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1730 /// <param name="aSessionId"></param>
1731 /// <param name="aField"></param>
1732 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1734 if (this.InvokeRequired)
1736 //Not in the proper thread, invoke ourselves
1737 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1738 this.Invoke(d, new object[] {aSessionId, aField});
1742 //We are in the proper thread
1743 //Call the non-thread-safe variant
1744 SetClientField(aSessionId, aField);
1752 /// Set a data field in the given client.
1754 /// <param name="aSessionId"></param>
1755 /// <param name="aField"></param>
1756 private void SetClientField(string aSessionId, DataField aField)
1758 //TODO: should check if the field actually changed?
1760 ClientData client = iClients[aSessionId];
1761 bool layoutChanged = false;
1762 bool contentChanged = true;
1764 //Fetch our field index
1765 int fieldIndex = client.FindSameFieldIndex(aField);
1769 //No corresponding field, just bail out
1773 //Keep our previous field in there
1774 DataField previousField = client.Fields[fieldIndex];
1775 //Just update that field then
1776 client.Fields[fieldIndex] = aField;
1778 if (!aField.IsTableField)
1780 //We are done then if that field is not in our table layout
1784 TableField tableField = (TableField) aField;
1786 if (previousField.IsSameLayout(aField))
1788 //If we are updating a field in our current client we need to update it in our panel
1789 if (aSessionId == iCurrentClientSessionId)
1791 Control ctrl = iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1792 if (aField.IsTextField && ctrl is MarqueeLabel)
1794 TextField textField = (TextField) aField;
1795 //Text field control already in place, just change the text
1796 MarqueeLabel label = (MarqueeLabel) ctrl;
1797 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1798 label.Text = textField.Text;
1799 label.TextAlign = textField.Alignment;
1801 else if (aField.IsBitmapField && ctrl is PictureBox)
1803 BitmapField bitmapField = (BitmapField) aField;
1804 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1805 //Bitmap field control already in place just change the bitmap
1806 PictureBox pictureBox = (PictureBox) ctrl;
1807 pictureBox.Image = bitmapField.Bitmap;
1811 layoutChanged = true;
1817 layoutChanged = true;
1820 //If either content or layout changed we need to update our tree view to reflect the changes
1821 if (contentChanged || layoutChanged)
1823 UpdateClientTreeViewNode(client);
1827 Debug.Print("Layout changed");
1828 //Our layout has changed, if we are already the current client we need to update our panel
1829 if (aSessionId == iCurrentClientSessionId)
1831 //Apply layout and set data fields.
1832 UpdateTableLayoutPanel(iCurrentClientData);
1837 Debug.Print("Layout has not changed.");
1842 Debug.Print("WARNING: content and layout have not changed!");
1845 //When a client field is set we try switching to this client to present the new information to our user
1846 SetCurrentClient(aSessionId);
1852 /// <param name="aTexts"></param>
1853 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1855 if (this.InvokeRequired)
1857 //Not in the proper thread, invoke ourselves
1858 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1859 this.Invoke(d, new object[] {aSessionId, aFields});
1863 ClientData client = iClients[aSessionId];
1865 if (client.HasNewLayout)
1867 //TODO: Assert client.Count == 0
1868 //Our layout was just changed
1869 //Do some special handling to avoid re-creating our panel N times, once for each fields
1870 client.HasNewLayout = false;
1871 //Just set all our fields then
1872 client.Fields.AddRange(aFields);
1873 //Try switch to that client
1874 SetCurrentClient(aSessionId);
1876 //If we are updating the current client update our panel
1877 if (aSessionId == iCurrentClientSessionId)
1879 //Apply layout and set data fields.
1880 UpdateTableLayoutPanel(iCurrentClientData);
1883 UpdateClientTreeViewNode(client);
1887 //Put each our text fields in a label control
1888 foreach (DataField field in aFields)
1890 SetClientField(aSessionId, field);
1899 /// <param name="aSessionId"></param>
1900 /// <param name="aName"></param>
1901 public void SetClientNameThreadSafe(string aSessionId, string aName)
1903 if (this.InvokeRequired)
1905 //Not in the proper thread, invoke ourselves
1906 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1907 this.Invoke(d, new object[] {aSessionId, aName});
1911 //We are in the proper thread
1913 ClientData client = iClients[aSessionId];
1917 client.Name = aName;
1918 //Update our tree-view
1919 UpdateClientTreeViewNode(client);
1925 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1927 if (this.InvokeRequired)
1929 //Not in the proper thread, invoke ourselves
1930 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1931 this.Invoke(d, new object[] {aSessionId, aPriority});
1935 //We are in the proper thread
1937 ClientData client = iClients[aSessionId];
1941 client.Priority = aPriority;
1942 //Update our tree-view
1943 UpdateClientTreeViewNode(client);
1944 //Change our current client as per new priority
1945 ClientData newCurrentClient = FindHighestPriorityClient();
1946 if (newCurrentClient != null)
1948 SetCurrentClient(newCurrentClient.SessionId);
1957 /// <param name="value"></param>
1958 /// <param name="maxChars"></param>
1959 /// <returns></returns>
1960 public static string Truncate(string value, int maxChars)
1962 return value.Length <= maxChars ? value : value.Substring(0, maxChars - 3) + "...";
1966 /// Update our recording notification.
1968 private void UpdateRecordingNotification()
1971 bool activeRecording = false;
1973 RecordingField recField = new RecordingField();
1974 foreach (var client in iClients)
1976 RecordingField rec = (RecordingField) client.Value.FindSameFieldAs(recField);
1977 if (rec != null && rec.IsActive)
1979 activeRecording = true;
1980 //Don't break cause we are collecting the names/texts.
1981 if (!String.IsNullOrEmpty(rec.Text))
1983 text += (rec.Text + "\n");
1987 //Not text for that recording, use client name instead
1988 text += client.Value.Name + " recording\n";
1994 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1995 iRecordingNotification.Text = Truncate(text, 63);
1997 //Change visibility of notification if needed
1998 if (iRecordingNotification.Visible != activeRecording)
2000 iRecordingNotification.Visible = activeRecording;
2001 //Assuming the notification icon is in sync with our display icon
2002 //Take care of our REC icon
2003 if (iDisplay.IsOpen())
2005 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
2013 /// <param name="aClient"></param>
2014 private void UpdateClientTreeViewNode(ClientData aClient)
2016 Debug.Print("UpdateClientTreeViewNode");
2018 if (aClient == null)
2023 //Hook in record icon update too
2024 UpdateRecordingNotification();
2026 TreeNode node = null;
2027 //Check that our client node already exists
2028 //Get our client root node using its key which is our session ID
2029 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
2030 if (nodes.Count() > 0)
2032 //We already have a node for that client
2034 //Clear children as we are going to recreate them below
2039 //Client node does not exists create a new one
2040 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
2041 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
2047 if (!String.IsNullOrEmpty(aClient.Name))
2049 //We have a name, use it as text for our root node
2050 node.Text = aClient.Name;
2051 //Add a child with SessionId
2052 node.Nodes.Add(new TreeNode(aClient.SessionId));
2056 //No name, use session ID instead
2057 node.Text = aClient.SessionId;
2060 //Display client priority
2061 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2063 if (aClient.Fields.Count > 0)
2065 //Create root node for our texts
2066 TreeNode textsRoot = new TreeNode("Fields");
2067 node.Nodes.Add(textsRoot);
2068 //For each text add a new entry
2069 foreach (DataField field in aClient.Fields)
2071 if (field.IsTextField)
2073 TextField textField = (TextField) field;
2074 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2076 else if (field.IsBitmapField)
2078 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2080 else if (field.IsRecordingField)
2082 RecordingField recordingField = (RecordingField) field;
2083 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2093 /// Update our table layout row styles to make sure each rows have similar height
2095 private void UpdateTableLayoutRowStyles()
2097 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2099 rowStyle.SizeType = SizeType.Percent;
2100 rowStyle.Height = 100/iTableLayoutPanel.RowCount;
2105 /// Update our display table layout.
2106 /// Will instanciated every field control as defined by our client.
2107 /// Fields must be specified by rows from the left.
2109 /// <param name="aLayout"></param>
2110 private void UpdateTableLayoutPanel(ClientData aClient)
2112 Debug.Print("UpdateTableLayoutPanel");
2114 if (aClient == null)
2121 TableLayout layout = aClient.Layout;
2123 //First clean our current panel
2124 iTableLayoutPanel.Controls.Clear();
2125 iTableLayoutPanel.RowStyles.Clear();
2126 iTableLayoutPanel.ColumnStyles.Clear();
2127 iTableLayoutPanel.RowCount = 0;
2128 iTableLayoutPanel.ColumnCount = 0;
2130 //Then recreate our rows...
2131 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2133 iTableLayoutPanel.RowCount++;
2137 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2139 iTableLayoutPanel.ColumnCount++;
2143 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2145 //Create our column styles
2146 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2149 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2153 //Create our row styles
2154 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2164 foreach (DataField field in aClient.Fields)
2166 if (!field.IsTableField)
2168 //That field is not taking part in our table layout skip it
2172 TableField tableField = (TableField) field;
2174 //Create a control corresponding to the field specified for that cell
2175 Control control = CreateControlForDataField(tableField);
2177 //Add newly created control to our table layout at the specified row and column
2178 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2179 //Make sure we specify column and row span for that new control
2180 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2181 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2189 /// Check our type of data field and create corresponding control
2191 /// <param name="aField"></param>
2192 private Control CreateControlForDataField(DataField aField)
2194 Control control = null;
2195 if (aField.IsTextField)
2197 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2198 label.AutoEllipsis = true;
2199 label.AutoSize = true;
2200 label.BackColor = System.Drawing.Color.Transparent;
2201 label.Dock = System.Windows.Forms.DockStyle.Fill;
2202 label.Location = new System.Drawing.Point(1, 1);
2203 label.Margin = new System.Windows.Forms.Padding(0);
2204 label.Name = "marqueeLabel" + aField;
2205 label.OwnTimer = false;
2206 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2207 label.Separator = cds.Separator;
2208 label.MinFontSize = cds.MinFontSize;
2209 label.ScaleToFit = cds.ScaleToFit;
2210 //control.Size = new System.Drawing.Size(254, 30);
2211 //control.TabIndex = 2;
2212 label.Font = cds.Font;
2214 TextField field = (TextField) aField;
2215 label.TextAlign = field.Alignment;
2216 label.UseCompatibleTextRendering = true;
2217 label.Text = field.Text;
2221 else if (aField.IsBitmapField)
2223 //Create picture box
2224 PictureBox picture = new PictureBox();
2225 picture.AutoSize = true;
2226 picture.BackColor = System.Drawing.Color.Transparent;
2227 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2228 picture.Location = new System.Drawing.Point(1, 1);
2229 picture.Margin = new System.Windows.Forms.Padding(0);
2230 picture.Name = "pictureBox" + aField;
2232 BitmapField field = (BitmapField) aField;
2233 picture.Image = field.Bitmap;
2237 //TODO: Handle recording field?
2243 /// Called when the user selected a new display type.
2245 /// <param name="sender"></param>
2246 /// <param name="e"></param>
2247 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2249 //Store the selected display type in our settings
2250 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2251 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2252 Properties.Settings.Default.Save();
2254 //Try re-opening the display connection if we were already connected.
2255 //Otherwise just update our status to reflect display type change.
2256 if (iDisplay.IsOpen())
2258 OpenDisplayConnection();
2266 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2268 if (maskedTextBoxTimerInterval.Text != "")
2270 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2274 timer.Interval = interval;
2275 cds.TimerInterval = timer.Interval;
2276 Properties.Settings.Default.Save();
2281 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2283 if (maskedTextBoxMinFontSize.Text != "")
2285 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2287 if (minFontSize > 0)
2289 cds.MinFontSize = minFontSize;
2290 Properties.Settings.Default.Save();
2291 //We need to recreate our layout for that change to take effect
2292 UpdateTableLayoutPanel(iCurrentClientData);
2298 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2300 if (maskedTextBoxScrollingSpeed.Text != "")
2302 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2304 if (scrollingSpeed > 0)
2306 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2307 Properties.Settings.Default.Save();
2308 //We need to recreate our layout for that change to take effect
2309 UpdateTableLayoutPanel(iCurrentClientData);
2314 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2316 cds.Separator = textBoxScrollLoopSeparator.Text;
2317 Properties.Settings.Default.Save();
2319 //Update our text fields
2320 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2322 ctrl.Separator = cds.Separator;
2327 private void buttonPowerOn_Click(object sender, EventArgs e)
2332 private void buttonPowerOff_Click(object sender, EventArgs e)
2334 iDisplay.PowerOff();
2337 private void buttonShowClock_Click(object sender, EventArgs e)
2342 private void buttonHideClock_Click(object sender, EventArgs e)
2347 private void buttonUpdate_Click(object sender, EventArgs e)
2349 InstallUpdateSyncWithInfo();
2357 if (!iDisplay.IsOpen())
2362 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2363 iSkipFrameRendering = true;
2366 iDisplay.SwapBuffers();
2367 //Then show our clock
2368 iDisplay.ShowClock();
2376 if (!iDisplay.IsOpen())
2381 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2382 iSkipFrameRendering = false;
2383 iDisplay.HideClock();
2386 private void InstallUpdateSyncWithInfo()
2388 UpdateCheckInfo info = null;
2390 if (ApplicationDeployment.IsNetworkDeployed)
2392 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2396 info = ad.CheckForDetailedUpdate();
2399 catch (DeploymentDownloadException dde)
2402 "The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " +
2406 catch (InvalidDeploymentException ide)
2409 "Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " +
2413 catch (InvalidOperationException ioe)
2416 "This application cannot be updated. It is likely not a ClickOnce application. Error: " +
2421 if (info.UpdateAvailable)
2423 Boolean doUpdate = true;
2425 if (!info.IsUpdateRequired)
2428 MessageBox.Show("An update is available. Would you like to update the application now?",
2429 "Update Available", MessageBoxButtons.OKCancel);
2430 if (!(DialogResult.OK == dr))
2437 // Display a message that the application MUST reboot. Display the minimum required version.
2438 MessageBox.Show("This application has detected a mandatory update from your current " +
2439 "version to version " + info.MinimumRequiredVersion.ToString() +
2440 ". The application will now install the update and restart.",
2441 "Update Available", MessageBoxButtons.OK,
2442 MessageBoxIcon.Information);
2450 MessageBox.Show("The application has been upgraded, and will now restart.");
2451 Application.Restart();
2453 catch (DeploymentDownloadException dde)
2456 "Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " +
2464 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2473 private void SysTrayHideShow()
2479 WindowState = FormWindowState.Normal;
2484 /// Use to handle minimize events.
2486 /// <param name="sender"></param>
2487 /// <param name="e"></param>
2488 private void MainForm_SizeChanged(object sender, EventArgs e)
2490 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2502 /// <param name="sender"></param>
2503 /// <param name="e"></param>
2504 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2506 //Our table layout size has changed which means our display size has changed.
2507 //We need to re-create our bitmap.
2508 iCreateBitmap = true;
2512 /// Broadcast messages to subscribers.
2514 /// <param name="message"></param>
2515 protected override void WndProc(ref Message aMessage)
2517 if (OnWndProc != null)
2519 OnWndProc(ref aMessage);
2522 base.WndProc(ref aMessage);
2525 private void iCheckBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2531 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2533 //Save CEC HDMI port
2534 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2535 Properties.Settings.Default.CecHdmiPort++;
2536 Properties.Settings.Default.Save();
2544 private void ResetCec()
2546 if (iCecManager == null)
2548 //Thus skipping initial UI setup
2554 if (Properties.Settings.Default.CecEnabled)
2556 iCecManager.Start(Handle, "CEC",
2557 Properties.Settings.Default.CecHdmiPort);
2566 private async void ResetHarmonyAsync(bool aForceAuth=false)
2568 // ConnectAsync already if we have an existing session cookie
2569 if (Properties.Settings.Default.HarmonyEnabled)
2573 iButtonHarmonyConnect.Enabled = false;
2574 await ConnectHarmonyAsync(aForceAuth);
2576 catch (Exception ex)
2578 Trace.WriteLine("Exception thrown by ConnectHarmonyAsync");
2579 Trace.WriteLine(ex.ToString());
2583 iButtonHarmonyConnect.Enabled = true;
2591 /// <param name="sender"></param>
2592 /// <param name="e"></param>
2593 private void iButtonHarmonyConnect_Click(object sender, EventArgs e)
2595 // User is explicitaly trying to connect
2596 //Reset Harmony Hub connection forcing authentication
2597 ResetHarmonyAsync(true);
2603 /// <param name="sender"></param>
2604 /// <param name="e"></param>
2605 private void iCheckBoxHarmonyEnabled_CheckedChanged(object sender, EventArgs e)
2607 iButtonHarmonyConnect.Enabled = iCheckBoxHarmonyEnabled.Checked;
2613 private void SetupCecLogLevel()
2616 iCecManager.Client.LogLevel = 0;
2618 if (checkBoxCecLogError.Checked)
2619 iCecManager.Client.LogLevel |= (int) CecLogLevel.Error;
2621 if (checkBoxCecLogWarning.Checked)
2622 iCecManager.Client.LogLevel |= (int) CecLogLevel.Warning;
2624 if (checkBoxCecLogNotice.Checked)
2625 iCecManager.Client.LogLevel |= (int) CecLogLevel.Notice;
2627 if (checkBoxCecLogTraffic.Checked)
2628 iCecManager.Client.LogLevel |= (int) CecLogLevel.Traffic;
2630 if (checkBoxCecLogDebug.Checked)
2631 iCecManager.Client.LogLevel |= (int) CecLogLevel.Debug;
2633 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2637 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2642 private void buttonClearLogs_Click(object sender, EventArgs e)
2644 richTextBoxLogs.Clear();
2647 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)
2656 /// <param name="aEvent"></param>
2657 private void SelectEvent(Ear.Event aEvent)
2664 foreach (TreeNode node in iTreeViewEvents.Nodes)
2666 if (node.Tag == aEvent)
2668 iTreeViewEvents.SelectedNode = node;
2669 iTreeViewEvents.Focus();
2677 /// Get the current event based on event tree view selection.
2679 /// <returns></returns>
2680 private Ear.Event CurrentEvent()
2682 //Walk up the tree from the selected node to find our event
2683 TreeNode node = iTreeViewEvents.SelectedNode;
2684 Ear.Event selectedEvent = null;
2685 while (node != null)
2687 if (node.Tag is Ear.Event)
2689 selectedEvent = (Ear.Event) node.Tag;
2695 return selectedEvent;
2699 /// Get the current action based on event tree view selection
2701 /// <returns></returns>
2702 private Ear.Action CurrentAction()
2704 TreeNode node = iTreeViewEvents.SelectedNode;
2705 if (node != null && node.Tag is Ear.Action)
2707 return (Ear.Action) node.Tag;
2716 /// <param name="sender"></param>
2717 /// <param name="e"></param>
2718 private void buttonActionAdd_Click(object sender, EventArgs e)
2720 Ear.Event selectedEvent = CurrentEvent();
2721 if (selectedEvent == null)
2723 //We did not find a corresponding event
2727 FormEditObject<Ear.Action> ea = new FormEditObject<Ear.Action>();
2728 ea.Text = "Add action";
2729 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2730 if (res == DialogResult.OK)
2732 selectedEvent.Actions.Add(ea.Object);
2733 Properties.Settings.Default.Save();
2734 PopulateTreeViewEvents();
2741 /// <param name="sender"></param>
2742 /// <param name="e"></param>
2743 private void buttonActionEdit_Click(object sender, EventArgs e)
2745 Ear.Event selectedEvent = CurrentEvent();
2746 Ear.Action selectedAction = CurrentAction();
2747 if (selectedEvent == null || selectedAction == null)
2749 //We did not find a corresponding event
2753 FormEditObject<Ear.Action> ea = new FormEditObject<Ear.Action>();
2754 ea.Text = "Edit action";
2755 ea.Object = selectedAction;
2756 int actionIndex = iTreeViewEvents.SelectedNode.Index;
2757 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2758 if (res == DialogResult.OK)
2761 selectedEvent.Actions[actionIndex]=ea.Object;
2762 //Save and rebuild our event tree view
2763 Properties.Settings.Default.Save();
2764 PopulateTreeViewEvents();
2771 /// <param name="sender"></param>
2772 /// <param name="e"></param>
2773 private void buttonActionDelete_Click(object sender, EventArgs e)
2776 Ear.Action action = CurrentAction();
2779 //Must select action node
2783 Properties.Settings.Default.EarManager.RemoveAction(action);
2784 Properties.Settings.Default.Save();
2785 PopulateTreeViewEvents();
2791 /// <param name="sender"></param>
2792 /// <param name="e"></param>
2793 private void buttonActionTest_Click(object sender, EventArgs e)
2795 Ear.Action a = CurrentAction();
2800 iTreeViewEvents.Focus();
2806 /// <param name="sender"></param>
2807 /// <param name="e"></param>
2808 private void buttonActionMoveUp_Click(object sender, EventArgs e)
2810 Ear.Action a = CurrentAction();
2812 //Action already at the top of the list
2813 iTreeViewEvents.SelectedNode.Index == 0)
2818 //Swap actions in event's action list
2819 Ear.Event currentEvent = CurrentEvent();
2820 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2821 Ear.Action movingUp = currentEvent.Actions[currentIndex];
2822 Ear.Action movingDown = currentEvent.Actions[currentIndex-1];
2823 currentEvent.Actions[currentIndex] = movingDown;
2824 currentEvent.Actions[currentIndex-1] = movingUp;
2826 //Save and populate our tree again
2827 Properties.Settings.Default.Save();
2828 PopulateTreeViewEvents();
2835 /// <param name="sender"></param>
2836 /// <param name="e"></param>
2837 private void buttonActionMoveDown_Click(object sender, EventArgs e)
2839 Ear.Action a = CurrentAction();
2841 //Action already at the bottom of the list
2842 iTreeViewEvents.SelectedNode.Index == iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1)
2847 //Swap actions in event's action list
2848 Ear.Event currentEvent = CurrentEvent();
2849 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2850 Ear.Action movingDown = currentEvent.Actions[currentIndex];
2851 Ear.Action movingUp = currentEvent.Actions[currentIndex + 1];
2852 currentEvent.Actions[currentIndex] = movingUp;
2853 currentEvent.Actions[currentIndex + 1] = movingDown;
2855 //Save and populate our tree again
2856 Properties.Settings.Default.Save();
2857 PopulateTreeViewEvents();
2864 /// <param name="sender"></param>
2865 /// <param name="e"></param>
2866 private void buttonEventTest_Click(object sender, EventArgs e)
2868 Ear.Event earEvent = CurrentEvent();
2869 if (earEvent != null)
2876 /// Manages events and actions buttons according to selected item in event tree.
2878 /// <param name="sender"></param>
2879 /// <param name="e"></param>
2880 private void iTreeViewEvents_AfterSelect(object sender, TreeViewEventArgs e)
2888 private void UpdateEventView()
2890 //One can always add an event
2891 buttonEventAdd.Enabled = true;
2893 //Enable buttons according to selected item
2894 buttonActionAdd.Enabled =
2895 buttonEventTest.Enabled =
2896 buttonEventDelete.Enabled =
2897 buttonEventEdit.Enabled =
2898 CurrentEvent() != null;
2900 Ear.Action currentAction = CurrentAction();
2901 //If an action is selected enable the following buttons
2902 buttonActionTest.Enabled =
2903 buttonActionDelete.Enabled =
2904 buttonActionMoveUp.Enabled =
2905 buttonActionMoveDown.Enabled =
2906 buttonActionEdit.Enabled =
2907 currentAction != null;
2909 if (currentAction != null)
2911 //If an action is selected enable move buttons if needed
2912 buttonActionMoveUp.Enabled = iTreeViewEvents.SelectedNode.Index != 0;
2913 buttonActionMoveDown.Enabled = iTreeViewEvents.SelectedNode.Index <
2914 iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1;
2921 /// <param name="sender"></param>
2922 /// <param name="e"></param>
2923 private void buttonEventAdd_Click(object sender, EventArgs e)
2925 FormEditObject<Ear.Event> ea = new FormEditObject<Ear.Event>();
2926 ea.Text = "Add event";
2927 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2928 if (res == DialogResult.OK)
2930 Properties.Settings.Default.EarManager.Events.Add(ea.Object);
2931 Properties.Settings.Default.Save();
2932 PopulateTreeViewEvents();
2933 SelectEvent(ea.Object);
2940 /// <param name="sender"></param>
2941 /// <param name="e"></param>
2942 private void buttonEventDelete_Click(object sender, EventArgs e)
2944 Ear.Event currentEvent = CurrentEvent();
2945 if (currentEvent == null)
2947 //Must select action node
2951 Properties.Settings.Default.EarManager.Events.Remove(currentEvent);
2952 Properties.Settings.Default.Save();
2953 PopulateTreeViewEvents();
2959 /// <param name="sender"></param>
2960 /// <param name="e"></param>
2961 private void buttonEventEdit_Click(object sender, EventArgs e)
2963 Ear.Event selectedEvent = CurrentEvent();
2964 if (selectedEvent == null)
2966 //We did not find a corresponding event
2970 FormEditObject<Ear.Event> ea = new FormEditObject<Ear.Event>();
2971 ea.Text = "Edit event";
2972 ea.Object = selectedEvent;
2973 int index = iTreeViewEvents.SelectedNode.Index;
2974 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2975 if (res == DialogResult.OK)
2977 //Make sure we keep the same actions as before
2978 ea.Object.Actions = Properties.Settings.Default.EarManager.Events[index].Actions;
2980 Properties.Settings.Default.EarManager.Events[index] = ea.Object;
2981 //Save and rebuild our event tree view
2982 Properties.Settings.Default.Save();
2983 PopulateTreeViewEvents();
2990 /// <param name="sender"></param>
2991 /// <param name="e"></param>
2992 private void iTreeViewEvents_Leave(object sender, EventArgs e)
2994 //Make sure our event tree never looses focus
2995 ((TreeView) sender).Focus();
2999 /// Called whenever we loose connection with our HarmonyHub.
3001 /// <param name="aRequestWasCancelled"></param>
3002 private void HarmonyConnectionClosedByServer(object aSender, bool aRequestWasCancelled)
3004 //Try reconnect then
3005 #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
3006 BeginInvoke(new MethodInvoker(delegate () { ResetHarmonyAsync(); }));
3007 #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
3013 /// <returns></returns>
3014 private async Task ConnectHarmonyAsync(bool aForceAuth=false)
3016 if (Program.HarmonyClient != null)
3018 await Program.HarmonyClient.CloseAsync();
3021 //Reset Harmony client & config
3022 Program.HarmonyClient = null;
3023 Program.HarmonyConfig = null;
3025 Trace.WriteLine("Harmony: Connecting... ");
3026 //First create our client and login
3027 //Tip: Set keep-alive to false when testing reconnection process
3028 Program.HarmonyClient = new HarmonyHub.Client(iTextBoxHarmonyHubAddress.Text, true);
3029 Program.HarmonyClient.OnConnectionClosedByServer += HarmonyConnectionClosedByServer;
3031 if (File.Exists("SessionToken") && !aForceAuth)
3033 var sessionToken = File.ReadAllText("SessionToken");
3034 Trace.WriteLine("Harmony: Reusing token: {0}", sessionToken);
3035 await Program.HarmonyClient.TryOpenAsync(sessionToken);
3038 if (!Program.HarmonyClient.IsReady)
3040 //We failed to connect using our token
3042 File.Delete("SessionToken");
3044 //Then try connect using our password
3045 if (string.IsNullOrEmpty(iTextBoxLogitechPassword.Text))
3047 Trace.WriteLine("Harmony: Credentials missing!");
3051 Trace.WriteLine("Harmony: Authenticating with Logitech servers...");
3052 await Program.HarmonyClient.TryOpenAsync(iTextBoxLogitechUserName.Text, iTextBoxLogitechPassword.Text);
3053 File.WriteAllText("SessionToken", Program.HarmonyClient.Token);
3057 Program.HarmonyConfig = await Program.HarmonyClient.GetConfigAsync();
3058 PopulateTreeViewHarmony(Program.HarmonyConfig);
3059 //Make sure harmony command actions are showing device name instead of device id
3060 PopulateTreeViewEvents();
3066 /// <param name="aConfig"></param>
3067 private void PopulateTreeViewHarmony(HarmonyHub.Config aConfig)
3069 iTreeViewHarmony.Nodes.Clear();
3071 foreach (HarmonyHub.Device device in aConfig.Devices)
3073 TreeNode deviceNode = iTreeViewHarmony.Nodes.Add(device.Id, $"{device.Label} ({device.DeviceTypeDisplayName}/{device.Model})");
3074 deviceNode.Tag = device;
3076 foreach (HarmonyHub.ControlGroup cg in device.ControlGroups)
3078 TreeNode cgNode = deviceNode.Nodes.Add(cg.Name);
3081 foreach (HarmonyHub.Function f in cg.Functions)
3083 TreeNode fNode = cgNode.Nodes.Add(f.Label);
3089 //treeViewConfig.ExpandAll();
3092 private async void iTreeViewHarmony_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
3094 //Upon function node double click we execute it
3095 var tag = e.Node.Tag as HarmonyHub.Function;
3096 if (tag != null && e.Node.Parent.Parent.Tag is HarmonyHub.Device)
3098 HarmonyHub.Function f = tag;
3099 HarmonyHub.Device d = (HarmonyHub.Device)e.Node.Parent.Parent.Tag;
3101 Trace.WriteLine($"Harmony: Sending {f.Label} to {d.Label}...");
3103 await Program.HarmonyClient.TrySendKeyPressAsync(d.Id, f.Action.Command);