Generic HID event with key recognition functional.
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 RichTextBoxTextWriter 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 RichTextBoxTextWriter(richTextBoxLogs);
176 Console.SetOut(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 PopulateEventsTreeView();
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 PopulateEventsTreeView()
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 TreeNode eventNode = iTreeViewEvents.Nodes.Add(e.Brief());
332 eventNode.Tag = e; //For easy access to our event
335 //Dim our nodes if disabled
336 eventNode.ForeColor = Color.DimGray;
339 //Add event description as child node
340 eventNode.Nodes.Add(e.Description).ForeColor = eventNode.ForeColor;
341 //Create child node for actions root
342 TreeNode actionsNodes = eventNode.Nodes.Add("Actions");
343 actionsNodes.ForeColor = eventNode.ForeColor;
345 // Add our actions for that event
346 foreach (Ear.Action a in e.Actions)
348 TreeNode actionNode = actionsNodes.Nodes.Add(a.Brief());
350 actionNode.ForeColor = eventNode.ForeColor;
351 if (a == currentAction)
353 treeNodeToSelect = actionNode;
358 iTreeViewEvents.ExpandAll();
359 SelectEvent(currentEvent);
361 if (treeNodeToSelect != null)
363 iTreeViewEvents.SelectedNode = treeNodeToSelect;
365 else if (iTreeViewEvents.SelectedNode != null && iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) > 0)
367 //Select the last action if any
368 iTreeViewEvents.SelectedNode =
369 iTreeViewEvents.SelectedNode.Nodes[1].Nodes[
370 iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) - 1];
372 else if (iTreeViewEvents.SelectedNode == null && iTreeViewEvents.Nodes.Count > 0)
374 //Still no selected node select the first one then
375 iTreeViewEvents.SelectedNode = iTreeViewEvents.Nodes[0];
383 /// Called when our display is closed.
385 /// <param name="aDisplay"></param>
386 private void OnDisplayClosed(Display aDisplay)
388 //Our display was just closed, update our UI consequently
392 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
394 //Update network status
395 UpdateNetworkStatus();
399 /// Update our Network Status
401 private void UpdateNetworkStatus()
403 if (iDisplay.IsOpen())
405 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet,
406 iNetworkManager.NetworkListManager.IsConnectedToInternet);
407 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
412 int iLastNetworkIconIndex = 0;
413 int iUpdateCountSinceLastNetworkAnimation = 0;
418 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
420 iUpdateCountSinceLastNetworkAnimation++;
421 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation%4;
423 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected &&
424 iUpdateCountSinceLastNetworkAnimation == 0)
426 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
429 //Prevents div by zero and other undefined behavior
432 iLastNetworkIconIndex++;
433 iLastNetworkIconIndex = iLastNetworkIconIndex%(iconCount*2);
434 for (int i = 0; i < iconCount; i++)
436 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) &&
437 !(i == 1 && iLastNetworkIconIndex > 4))
439 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
443 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
452 /// Receive volume change notification and reflect changes on our slider.
454 /// <param name="data"></param>
455 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
457 UpdateMasterVolumeThreadSafe();
461 /// Update master volume when user moves our slider.
463 /// <param name="sender"></param>
464 /// <param name="e"></param>
465 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
467 //Just like Windows Volume Mixer we unmute if the volume is adjusted
468 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
469 //Set volume level according to our volume slider new position
470 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value/100.0f;
475 /// Mute check box changed.
477 /// <param name="sender"></param>
478 /// <param name="e"></param>
479 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
481 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
485 /// Device State Changed
487 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId,
488 [MarshalAs(UnmanagedType.I4)] DeviceState newState)
495 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId)
502 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId)
507 /// Default Device Changed
509 public void OnDefaultDeviceChanged(DataFlow flow, Role role,
510 [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
512 if (role == Role.Multimedia && flow == DataFlow.Render)
514 UpdateAudioDeviceAndMasterVolumeThreadSafe();
519 /// Property Value Changed
521 /// <param name="pwstrDeviceId"></param>
522 /// <param name="key"></param>
523 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key)
531 /// Update master volume indicators based our current system states.
532 /// This typically includes volume levels and mute status.
534 private void UpdateMasterVolumeThreadSafe()
536 if (this.InvokeRequired)
538 //Not in the proper thread, invoke ourselves
539 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
540 this.Invoke(d, new object[] {});
544 //Update volume slider
545 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
546 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar*100);
547 //Update mute checkbox
548 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
550 //If our display connection is open we need to update its icons
551 if (iDisplay.IsOpen())
553 //First take care our our volume level icons
554 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
555 if (volumeIconCount > 0)
557 //Compute current volume level from system level and the number of segments in our display volume bar.
558 //That tells us how many segments in our volume bar needs to be turned on.
559 float currentVolume = volumeLevelScalar*volumeIconCount;
560 int segmentOnCount = Convert.ToInt32(currentVolume);
561 //Check if our segment count was rounded up, this will later be used for half brightness segment
562 bool roundedUp = segmentOnCount > currentVolume;
564 for (int i = 0; i < volumeIconCount; i++)
566 if (i < segmentOnCount)
568 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
569 if (i == segmentOnCount - 1 && roundedUp)
572 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
573 (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1)/2);
578 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
579 iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
584 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
589 //Take care of our mute icon
590 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
598 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
600 if (this.InvokeRequired)
602 //Not in the proper thread, invoke ourselves
603 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
604 this.Invoke(d, new object[] {});
608 //We are in the correct thread just go ahead.
611 //Get our master volume
612 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
614 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
616 //Show our volume in our track bar
617 UpdateMasterVolumeThreadSafe();
619 //Register to get volume modifications
620 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
622 trackBarMasterVolume.Enabled = true;
626 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
627 Debug.WriteLine(ex.ToString());
628 //Something went wrong S/PDIF device ca throw exception I guess
629 trackBarMasterVolume.Enabled = false;
636 private void PopulateDeviceTypes()
638 int count = Display.TypeCount();
640 for (int i = 0; i < count; i++)
642 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type) i));
649 private void SetupTrayIcon()
651 iNotifyIcon.Icon = GetIcon("vfd.ico");
652 iNotifyIcon.Text = "Sharp Display Manager";
653 iNotifyIcon.Visible = true;
655 //Double click toggles visibility - typically brings up the application
656 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
661 //Adding a context menu, useful to be able to exit the application
662 ContextMenu contextMenu = new ContextMenu();
663 //Context menu item to toggle visibility
664 MenuItem hideShowItem = new MenuItem("Hide/Show");
665 hideShowItem.Click += delegate(object obj, EventArgs args)
669 contextMenu.MenuItems.Add(hideShowItem);
671 //Context menu item separator
672 contextMenu.MenuItems.Add(new MenuItem("-"));
674 //Context menu exit item
675 MenuItem exitItem = new MenuItem("Exit");
676 exitItem.Click += delegate(object obj, EventArgs args)
680 contextMenu.MenuItems.Add(exitItem);
682 iNotifyIcon.ContextMenu = contextMenu;
688 private void SetupRecordingNotification()
690 iRecordingNotification.Icon = GetIcon("record.ico");
691 iRecordingNotification.Text = "No recording";
692 iRecordingNotification.Visible = false;
696 /// Access icons from embedded resources.
698 /// <param name="aName"></param>
699 /// <returns></returns>
700 public static Icon GetIcon(string aName)
702 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
703 foreach (string name in names)
705 //Find a resource name that ends with the given name
706 if (name.EndsWith(aName))
708 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
710 return new Icon(stream);
720 /// Set our current client.
721 /// This will take care of applying our client layout and set data fields.
723 /// <param name="aSessionId"></param>
724 void SetCurrentClient(string aSessionId, bool aForce = false)
726 if (aSessionId == iCurrentClientSessionId)
728 //Given client is already the current one.
729 //Don't bother changing anything then.
733 ClientData requestedClientData = iClients[aSessionId];
735 //Check when was the last time we switched to that client
736 if (iCurrentClientData != null)
738 //Do not switch client if priority of current client is higher
739 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
745 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
746 //TODO: put that hard coded value as a client property
747 //Clients should be able to define how often they can be interrupted
748 //Thus a background client can set this to zero allowing any other client to interrupt at any time
749 //We could also compute this delay by looking at the requests frequencies?
751 requestedClientData.Priority == iCurrentClientData.Priority &&
752 //Time sharing is only if clients have the same priority
753 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
755 //Don't switch clients too often
760 //Set current client ID.
761 iCurrentClientSessionId = aSessionId;
762 //Set the time we last switched to that client
763 iClients[aSessionId].LastSwitchTime = DateTime.Now;
764 //Fetch and set current client data.
765 iCurrentClientData = requestedClientData;
766 //Apply layout and set data fields.
767 UpdateTableLayoutPanel(iCurrentClientData);
770 private void buttonFont_Click(object sender, EventArgs e)
772 //fontDialog.ShowColor = true;
773 //fontDialog.ShowApply = true;
774 fontDialog.ShowEffects = true;
775 fontDialog.Font = cds.Font;
777 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
779 //fontDialog.ShowHelp = true;
781 //fontDlg.MaxSize = 40;
782 //fontDlg.MinSize = 22;
784 //fontDialog.Parent = this;
785 //fontDialog.StartPosition = FormStartPosition.CenterParent;
787 //DlgBox.ShowDialog(fontDialog);
789 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
790 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
792 //Set the fonts to all our labels in our layout
793 foreach (Control ctrl in iTableLayoutPanel.Controls)
795 if (ctrl is MarqueeLabel)
797 ((MarqueeLabel) ctrl).Font = fontDialog.Font;
802 cds.Font = fontDialog.Font;
803 Properties.Settings.Default.Save();
812 void CheckFontHeight()
814 //Show font height and width
815 labelFontHeight.Text = "Font height: " + cds.Font.Height;
816 float charWidth = IsFixedWidth(cds.Font);
817 if (charWidth == 0.0f)
819 labelFontWidth.Visible = false;
823 labelFontWidth.Visible = true;
824 labelFontWidth.Text = "Font width: " + charWidth;
827 MarqueeLabel label = null;
828 //Get the first label control we can find
829 foreach (Control ctrl in iTableLayoutPanel.Controls)
831 if (ctrl is MarqueeLabel)
833 label = (MarqueeLabel) ctrl;
838 //Now check font height and show a warning if needed.
839 if (label != null && label.Font.Height > label.Height)
841 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) +
843 labelWarning.Visible = true;
847 labelWarning.Visible = false;
852 private void buttonCapture_Click(object sender, EventArgs e)
854 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
855 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
856 //Bitmap bmpToSave = new Bitmap(bmp);
857 bmp.Save("D:\\capture.png");
859 ((MarqueeLabel) iTableLayoutPanel.Controls[0]).Text = "Captured";
862 string outputFileName = "d:\\capture.png";
863 using (MemoryStream memory = new MemoryStream())
865 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
867 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
868 byte[] bytes = memory.ToArray();
869 fs.Write(bytes, 0, bytes.Length);
876 private void CheckForRequestResults()
878 if (iDisplay.IsRequestPending())
880 switch (iDisplay.AttemptRequestCompletion())
882 case MiniDisplay.Request.FirmwareRevision:
883 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
884 //Issue next request then
885 iDisplay.RequestPowerSupplyStatus();
888 case MiniDisplay.Request.PowerSupplyStatus:
889 if (iDisplay.PowerSupplyStatus())
891 toolStripStatusLabelPower.Text = "ON";
895 toolStripStatusLabelPower.Text = "OFF";
897 //Issue next request then
898 iDisplay.RequestDeviceId();
901 case MiniDisplay.Request.DeviceId:
902 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
903 //No more request to issue
909 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
911 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
918 public static uint ColorUntouched(int aX, int aY, uint aPixel)
923 public static uint ColorInversed(int aX, int aY, uint aPixel)
928 public static uint ColorChessboard(int aX, int aY, uint aPixel)
930 if ((aX%2 == 0) && (aY%2 == 0))
934 else if ((aX%2 != 0) && (aY%2 != 0))
942 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
944 return aBmp.Width - aX - 1;
947 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
949 return iBmp.Height - aY - 1;
952 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
957 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
963 /// Select proper pixel delegates according to our current settings.
965 private void SetupPixelDelegates()
967 //Select our pixel processing routine
968 if (cds.InverseColors)
970 //iColorFx = ColorChessboard;
971 iColorFx = ColorInversed;
975 iColorFx = ColorWhiteIsOn;
978 //Select proper coordinate translation functions
979 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
980 if (cds.ReverseScreen)
982 iScreenX = ScreenReversedX;
983 iScreenY = ScreenReversedY;
993 //This is our timer tick responsible to perform our render
994 private void timer_Tick(object sender, EventArgs e)
996 //Not ideal cause this has nothing to do with display render
999 //Update our animations
1000 DateTime NewTickTime = DateTime.Now;
1002 UpdateNetworkSignal(LastTickTime, NewTickTime);
1004 //Update animation for all our marquees
1005 foreach (Control ctrl in iTableLayoutPanel.Controls)
1007 if (ctrl is MarqueeLabel)
1009 ((MarqueeLabel) ctrl).UpdateAnimation(LastTickTime, NewTickTime);
1013 //Update our display
1014 if (iDisplay.IsOpen())
1016 CheckForRequestResults();
1018 //Check if frame rendering is needed
1019 //Typically used when showing clock
1020 if (!iSkipFrameRendering)
1025 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
1026 PixelFormat.Format32bppArgb);
1027 iCreateBitmap = false;
1029 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
1030 //iBmp.Save("D:\\capture.png");
1032 //Send it to our display
1033 for (int i = 0; i < iBmp.Width; i++)
1035 for (int j = 0; j < iBmp.Height; j++)
1039 //Get our processed pixel coordinates
1040 int x = iScreenX(iBmp, i);
1041 int y = iScreenY(iBmp, j);
1043 uint color = (uint) iBmp.GetPixel(i, j).ToArgb();
1044 //Apply color effects
1045 color = iColorFx(x, y, color);
1047 iDisplay.SetPixel(x, y, color);
1052 iDisplay.SwapBuffers();
1056 //Compute instant FPS
1057 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " +
1058 (1000/timer.Interval).ToString() + " FPS";
1060 LastTickTime = NewTickTime;
1065 /// Attempt to establish connection with our display hardware.
1067 private void OpenDisplayConnection()
1069 CloseDisplayConnection();
1071 if (!iDisplay.Open((MiniDisplay.Type) cds.DisplayType))
1074 toolStripStatusLabelConnect.Text = "Connection error";
1078 private void CloseDisplayConnection()
1080 //Status will be updated upon receiving the closed event
1082 if (iDisplay == null || !iDisplay.IsOpen())
1087 //Do not clear if we gave up on rendering already.
1088 //This means we will keep on displaying clock on MDM166AA for instance.
1089 if (!iSkipFrameRendering)
1092 iDisplay.SwapBuffers();
1095 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1099 private void buttonOpen_Click(object sender, EventArgs e)
1101 OpenDisplayConnection();
1104 private void buttonClose_Click(object sender, EventArgs e)
1106 CloseDisplayConnection();
1109 private void buttonClear_Click(object sender, EventArgs e)
1112 iDisplay.SwapBuffers();
1115 private void buttonFill_Click(object sender, EventArgs e)
1118 iDisplay.SwapBuffers();
1121 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1123 cds.Brightness = trackBarBrightness.Value;
1124 Properties.Settings.Default.Save();
1125 iDisplay.SetBrightness(trackBarBrightness.Value);
1131 /// CDS stands for Current Display Settings
1133 private DisplaySettings cds
1137 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1138 if (settings == null)
1140 settings = new DisplaysSettings();
1142 Properties.Settings.Default.DisplaysSettings = settings;
1145 //Make sure all our settings have been created
1146 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1148 settings.Displays.Add(new DisplaySettings());
1151 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1152 return displaySettings;
1157 /// Check if the given font has a fixed character pitch.
1159 /// <param name="ft"></param>
1160 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1161 public float IsFixedWidth(Font ft)
1163 Graphics g = CreateGraphics();
1164 char[] charSizes = new char[] {'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.'};
1165 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1167 bool fixedWidth = true;
1169 foreach (char c in charSizes)
1170 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width !=
1183 /// Synchronize UI with settings
1185 private void UpdateStatus()
1188 checkBoxShowBorders.Checked = cds.ShowBorders;
1189 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders
1190 ? TableLayoutPanelCellBorderStyle.Single
1191 : TableLayoutPanelCellBorderStyle.None);
1193 //Set the proper font to each of our labels
1194 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1196 ctrl.Font = cds.Font;
1200 //Check if "run on Windows startup" is enabled
1201 checkBoxAutoStart.Checked = iStartupManager.Startup;
1204 iTextBoxHarmonyHubAddress.Text = Properties.Settings.Default.HarmonyHubAddress;
1207 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1209 //Mini Display settings
1210 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1211 checkBoxInverseColors.Checked = cds.InverseColors;
1212 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1213 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1214 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1215 labelMinFontSize.Enabled = cds.ScaleToFit;
1216 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1217 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1218 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1219 timer.Interval = cds.TimerInterval;
1220 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1221 textBoxScrollLoopSeparator.Text = cds.Separator;
1223 SetupPixelDelegates();
1225 if (iDisplay.IsOpen())
1227 //We have a display connection
1228 //Reflect that in our UI
1231 iTableLayoutPanel.Enabled = true;
1232 panelDisplay.Enabled = true;
1234 //Only setup brightness if display is open
1235 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1236 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1237 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1239 //Brightness out of range, this can occur when using auto-detect
1240 //Use max brightness instead
1241 trackBarBrightness.Value = iDisplay.MaxBrightness();
1242 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1246 trackBarBrightness.Value = cds.Brightness;
1247 iDisplay.SetBrightness(cds.Brightness);
1250 //Try compute the steps to something that makes sense
1251 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness())/5);
1252 trackBarBrightness.SmallChange = 1;
1255 buttonFill.Enabled = true;
1256 buttonClear.Enabled = true;
1257 buttonOpen.Enabled = false;
1258 buttonClose.Enabled = true;
1259 trackBarBrightness.Enabled = true;
1260 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1261 //+ " - " + iDisplay.SerialNumber();
1263 if (iDisplay.SupportPowerOnOff())
1265 buttonPowerOn.Enabled = true;
1266 buttonPowerOff.Enabled = true;
1270 buttonPowerOn.Enabled = false;
1271 buttonPowerOff.Enabled = false;
1274 if (iDisplay.SupportClock())
1276 buttonShowClock.Enabled = true;
1277 buttonHideClock.Enabled = true;
1281 buttonShowClock.Enabled = false;
1282 buttonHideClock.Enabled = false;
1286 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1287 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel) > 0;
1289 if (cds.ShowVolumeLabel)
1291 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1295 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1300 //Display connection not available
1301 //Reflect that in our UI
1303 //In debug start our timer even if we don't have a display connection
1306 //In production environment we don't need our timer if no display connection
1309 checkBoxShowVolumeLabel.Enabled = false;
1310 iTableLayoutPanel.Enabled = false;
1311 panelDisplay.Enabled = false;
1312 buttonFill.Enabled = false;
1313 buttonClear.Enabled = false;
1314 buttonOpen.Enabled = true;
1315 buttonClose.Enabled = false;
1316 trackBarBrightness.Enabled = false;
1317 buttonPowerOn.Enabled = false;
1318 buttonPowerOff.Enabled = false;
1319 buttonShowClock.Enabled = false;
1320 buttonHideClock.Enabled = false;
1321 toolStripStatusLabelConnect.Text = "Disconnected";
1322 toolStripStatusLabelPower.Text = "N/A";
1331 /// <param name="sender"></param>
1332 /// <param name="e"></param>
1333 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1335 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1336 Properties.Settings.Default.Save();
1340 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1342 //Save our show borders setting
1343 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked
1344 ? TableLayoutPanelCellBorderStyle.Single
1345 : TableLayoutPanelCellBorderStyle.None);
1346 cds.ShowBorders = checkBoxShowBorders.Checked;
1347 Properties.Settings.Default.Save();
1351 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1353 iStartupManager.Startup = checkBoxAutoStart.Checked;
1357 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1359 //Save our reverse screen setting
1360 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1361 Properties.Settings.Default.Save();
1362 SetupPixelDelegates();
1365 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1367 //Save our inverse colors setting
1368 cds.InverseColors = checkBoxInverseColors.Checked;
1369 Properties.Settings.Default.Save();
1370 SetupPixelDelegates();
1373 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1375 //Save our scale to fit setting
1376 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1377 Properties.Settings.Default.Save();
1379 labelMinFontSize.Enabled = cds.ScaleToFit;
1380 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1383 private void MainForm_Resize(object sender, EventArgs e)
1385 if (WindowState == FormWindowState.Minimized)
1387 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1388 // That's apparently not needed on Windows 10 but we better leave it in place.
1389 iCreateBitmap = true;
1393 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1396 iNetworkManager.Dispose();
1397 CloseDisplayConnection();
1399 e.Cancel = iClosing;
1402 public void StartServer()
1404 iServiceHost = new ServiceHost
1407 new Uri[] {new Uri("net.tcp://localhost:8001/")}
1410 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true),
1412 iServiceHost.Open();
1415 public void StopServer()
1417 if (iClients.Count > 0 && !iClosing)
1421 BroadcastCloseEvent();
1426 MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo,
1427 MessageBoxIcon.Warning) == DialogResult.Yes)
1429 iClosing = false; //We make sure we force close if asked twice
1434 //We removed that as it often lags for some reason
1435 //iServiceHost.Close();
1439 public void BroadcastCloseEvent()
1441 Trace.TraceInformation("BroadcastCloseEvent - start");
1443 var inactiveClients = new List<string>();
1444 foreach (var client in iClients)
1446 //if (client.Key != eventData.ClientName)
1450 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1451 client.Value.Callback.OnCloseOrder( /*eventData*/);
1453 catch (Exception ex)
1455 inactiveClients.Add(client.Key);
1460 if (inactiveClients.Count > 0)
1462 foreach (var client in inactiveClients)
1464 iClients.Remove(client);
1465 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1466 Program.iFormMain.iTreeViewClients.Nodes.Find(client, false)[0]);
1470 if (iClients.Count == 0)
1477 /// Just remove all our fields.
1479 private void ClearLayout()
1481 iTableLayoutPanel.Controls.Clear();
1482 iTableLayoutPanel.RowStyles.Clear();
1483 iTableLayoutPanel.ColumnStyles.Clear();
1484 iCurrentClientData = null;
1488 /// Just launch a demo client.
1490 private void StartNewClient(string aTopText = "", string aBottomText = "")
1492 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1493 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(
1494 new Point(this.Right, this.Top), aTopText, aBottomText);
1495 clientThread.Start(myParams);
1500 /// Just launch our idle client.
1502 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1504 Thread clientThread = new Thread(SharpDisplayClientIdle.Program.MainWithParams);
1505 SharpDisplayClientIdle.StartParams myParams =
1506 new SharpDisplayClientIdle.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1507 clientThread.Start(myParams);
1512 private void buttonStartClient_Click(object sender, EventArgs e)
1517 private void buttonSuspend_Click(object sender, EventArgs e)
1522 private void StartTimer()
1524 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1525 timer.Enabled = true;
1526 UpdateSuspendButton();
1529 private void StopTimer()
1531 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1532 timer.Enabled = false;
1533 UpdateSuspendButton();
1536 private void ToggleTimer()
1538 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1539 timer.Enabled = !timer.Enabled;
1540 UpdateSuspendButton();
1543 private void UpdateSuspendButton()
1547 buttonSuspend.Text = "Run";
1551 buttonSuspend.Text = "Pause";
1556 private void buttonCloseClients_Click(object sender, EventArgs e)
1558 BroadcastCloseEvent();
1561 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1563 //Root node must have at least one child
1564 if (e.Node.Nodes.Count == 0)
1569 //If the selected node is the root node of a client then switch to it
1570 string sessionId = e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1571 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1573 //We have a valid session just switch to that client
1574 SetCurrentClient(sessionId, true);
1583 /// <param name="aSessionId"></param>
1584 /// <param name="aCallback"></param>
1585 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1587 if (this.InvokeRequired)
1589 //Not in the proper thread, invoke ourselves
1590 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1591 this.Invoke(d, new object[] {aSessionId, aCallback});
1595 //We are in the proper thread
1596 //Add this session to our collection of clients
1597 ClientData newClient = new ClientData(aSessionId, aCallback);
1598 Program.iFormMain.iClients.Add(aSessionId, newClient);
1599 //Add this session to our UI
1600 UpdateClientTreeViewNode(newClient);
1606 /// Find the client with the highest priority if any.
1608 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1609 public ClientData FindHighestPriorityClient()
1611 ClientData highestPriorityClient = null;
1612 foreach (var client in iClients)
1614 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1616 highestPriorityClient = client.Value;
1620 return highestPriorityClient;
1626 /// <param name="aSessionId"></param>
1627 public void RemoveClientThreadSafe(string aSessionId)
1629 if (this.InvokeRequired)
1631 //Not in the proper thread, invoke ourselves
1632 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1633 this.Invoke(d, new object[] {aSessionId});
1637 //We are in the proper thread
1638 //Remove this session from both client collection and UI tree view
1639 if (Program.iFormMain.iClients.Keys.Contains(aSessionId))
1641 Program.iFormMain.iClients.Remove(aSessionId);
1642 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1643 Program.iFormMain.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1644 //Update recording status too whenever a client is removed
1645 UpdateRecordingNotification();
1648 if (iCurrentClientSessionId == aSessionId)
1650 //The current client is closing
1651 iCurrentClientData = null;
1652 //Find the client with the highest priority and set it as current
1653 ClientData newCurrentClient = FindHighestPriorityClient();
1654 if (newCurrentClient != null)
1656 SetCurrentClient(newCurrentClient.SessionId, true);
1660 if (iClients.Count == 0)
1662 //Clear our screen when last client disconnects
1667 //We were closing our form
1668 //All clients are now closed
1669 //Just resume our close operation
1680 /// <param name="aSessionId"></param>
1681 /// <param name="aLayout"></param>
1682 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1684 if (this.InvokeRequired)
1686 //Not in the proper thread, invoke ourselves
1687 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1688 this.Invoke(d, new object[] {aSessionId, aLayout});
1692 ClientData client = iClients[aSessionId];
1695 //Don't change a thing if the layout is the same
1696 if (!client.Layout.IsSameAs(aLayout))
1698 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1699 //Set our client layout then
1700 client.Layout = aLayout;
1701 //So that next time we update all our fields at ones
1702 client.HasNewLayout = true;
1703 //Layout has changed clear our fields then
1704 client.Fields.Clear();
1706 UpdateClientTreeViewNode(client);
1710 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1719 /// <param name="aSessionId"></param>
1720 /// <param name="aField"></param>
1721 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1723 if (this.InvokeRequired)
1725 //Not in the proper thread, invoke ourselves
1726 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1727 this.Invoke(d, new object[] {aSessionId, aField});
1731 //We are in the proper thread
1732 //Call the non-thread-safe variant
1733 SetClientField(aSessionId, aField);
1741 /// Set a data field in the given client.
1743 /// <param name="aSessionId"></param>
1744 /// <param name="aField"></param>
1745 private void SetClientField(string aSessionId, DataField aField)
1747 //TODO: should check if the field actually changed?
1749 ClientData client = iClients[aSessionId];
1750 bool layoutChanged = false;
1751 bool contentChanged = true;
1753 //Fetch our field index
1754 int fieldIndex = client.FindSameFieldIndex(aField);
1758 //No corresponding field, just bail out
1762 //Keep our previous field in there
1763 DataField previousField = client.Fields[fieldIndex];
1764 //Just update that field then
1765 client.Fields[fieldIndex] = aField;
1767 if (!aField.IsTableField)
1769 //We are done then if that field is not in our table layout
1773 TableField tableField = (TableField) aField;
1775 if (previousField.IsSameLayout(aField))
1777 //If we are updating a field in our current client we need to update it in our panel
1778 if (aSessionId == iCurrentClientSessionId)
1780 Control ctrl = iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1781 if (aField.IsTextField && ctrl is MarqueeLabel)
1783 TextField textField = (TextField) aField;
1784 //Text field control already in place, just change the text
1785 MarqueeLabel label = (MarqueeLabel) ctrl;
1786 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1787 label.Text = textField.Text;
1788 label.TextAlign = textField.Alignment;
1790 else if (aField.IsBitmapField && ctrl is PictureBox)
1792 BitmapField bitmapField = (BitmapField) aField;
1793 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1794 //Bitmap field control already in place just change the bitmap
1795 PictureBox pictureBox = (PictureBox) ctrl;
1796 pictureBox.Image = bitmapField.Bitmap;
1800 layoutChanged = true;
1806 layoutChanged = true;
1809 //If either content or layout changed we need to update our tree view to reflect the changes
1810 if (contentChanged || layoutChanged)
1812 UpdateClientTreeViewNode(client);
1816 Debug.Print("Layout changed");
1817 //Our layout has changed, if we are already the current client we need to update our panel
1818 if (aSessionId == iCurrentClientSessionId)
1820 //Apply layout and set data fields.
1821 UpdateTableLayoutPanel(iCurrentClientData);
1826 Debug.Print("Layout has not changed.");
1831 Debug.Print("WARNING: content and layout have not changed!");
1834 //When a client field is set we try switching to this client to present the new information to our user
1835 SetCurrentClient(aSessionId);
1841 /// <param name="aTexts"></param>
1842 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1844 if (this.InvokeRequired)
1846 //Not in the proper thread, invoke ourselves
1847 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1848 this.Invoke(d, new object[] {aSessionId, aFields});
1852 ClientData client = iClients[aSessionId];
1854 if (client.HasNewLayout)
1856 //TODO: Assert client.Count == 0
1857 //Our layout was just changed
1858 //Do some special handling to avoid re-creating our panel N times, once for each fields
1859 client.HasNewLayout = false;
1860 //Just set all our fields then
1861 client.Fields.AddRange(aFields);
1862 //Try switch to that client
1863 SetCurrentClient(aSessionId);
1865 //If we are updating the current client update our panel
1866 if (aSessionId == iCurrentClientSessionId)
1868 //Apply layout and set data fields.
1869 UpdateTableLayoutPanel(iCurrentClientData);
1872 UpdateClientTreeViewNode(client);
1876 //Put each our text fields in a label control
1877 foreach (DataField field in aFields)
1879 SetClientField(aSessionId, field);
1888 /// <param name="aSessionId"></param>
1889 /// <param name="aName"></param>
1890 public void SetClientNameThreadSafe(string aSessionId, string aName)
1892 if (this.InvokeRequired)
1894 //Not in the proper thread, invoke ourselves
1895 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1896 this.Invoke(d, new object[] {aSessionId, aName});
1900 //We are in the proper thread
1902 ClientData client = iClients[aSessionId];
1906 client.Name = aName;
1907 //Update our tree-view
1908 UpdateClientTreeViewNode(client);
1914 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1916 if (this.InvokeRequired)
1918 //Not in the proper thread, invoke ourselves
1919 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1920 this.Invoke(d, new object[] {aSessionId, aPriority});
1924 //We are in the proper thread
1926 ClientData client = iClients[aSessionId];
1930 client.Priority = aPriority;
1931 //Update our tree-view
1932 UpdateClientTreeViewNode(client);
1933 //Change our current client as per new priority
1934 ClientData newCurrentClient = FindHighestPriorityClient();
1935 if (newCurrentClient != null)
1937 SetCurrentClient(newCurrentClient.SessionId);
1946 /// <param name="value"></param>
1947 /// <param name="maxChars"></param>
1948 /// <returns></returns>
1949 public static string Truncate(string value, int maxChars)
1951 return value.Length <= maxChars ? value : value.Substring(0, maxChars - 3) + "...";
1955 /// Update our recording notification.
1957 private void UpdateRecordingNotification()
1960 bool activeRecording = false;
1962 RecordingField recField = new RecordingField();
1963 foreach (var client in iClients)
1965 RecordingField rec = (RecordingField) client.Value.FindSameFieldAs(recField);
1966 if (rec != null && rec.IsActive)
1968 activeRecording = true;
1969 //Don't break cause we are collecting the names/texts.
1970 if (!String.IsNullOrEmpty(rec.Text))
1972 text += (rec.Text + "\n");
1976 //Not text for that recording, use client name instead
1977 text += client.Value.Name + " recording\n";
1983 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1984 iRecordingNotification.Text = Truncate(text, 63);
1986 //Change visibility of notification if needed
1987 if (iRecordingNotification.Visible != activeRecording)
1989 iRecordingNotification.Visible = activeRecording;
1990 //Assuming the notification icon is in sync with our display icon
1991 //Take care of our REC icon
1992 if (iDisplay.IsOpen())
1994 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
2002 /// <param name="aClient"></param>
2003 private void UpdateClientTreeViewNode(ClientData aClient)
2005 Debug.Print("UpdateClientTreeViewNode");
2007 if (aClient == null)
2012 //Hook in record icon update too
2013 UpdateRecordingNotification();
2015 TreeNode node = null;
2016 //Check that our client node already exists
2017 //Get our client root node using its key which is our session ID
2018 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
2019 if (nodes.Count() > 0)
2021 //We already have a node for that client
2023 //Clear children as we are going to recreate them below
2028 //Client node does not exists create a new one
2029 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
2030 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
2036 if (!String.IsNullOrEmpty(aClient.Name))
2038 //We have a name, use it as text for our root node
2039 node.Text = aClient.Name;
2040 //Add a child with SessionId
2041 node.Nodes.Add(new TreeNode(aClient.SessionId));
2045 //No name, use session ID instead
2046 node.Text = aClient.SessionId;
2049 //Display client priority
2050 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2052 if (aClient.Fields.Count > 0)
2054 //Create root node for our texts
2055 TreeNode textsRoot = new TreeNode("Fields");
2056 node.Nodes.Add(textsRoot);
2057 //For each text add a new entry
2058 foreach (DataField field in aClient.Fields)
2060 if (field.IsTextField)
2062 TextField textField = (TextField) field;
2063 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2065 else if (field.IsBitmapField)
2067 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2069 else if (field.IsRecordingField)
2071 RecordingField recordingField = (RecordingField) field;
2072 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2082 /// Update our table layout row styles to make sure each rows have similar height
2084 private void UpdateTableLayoutRowStyles()
2086 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2088 rowStyle.SizeType = SizeType.Percent;
2089 rowStyle.Height = 100/iTableLayoutPanel.RowCount;
2094 /// Update our display table layout.
2095 /// Will instanciated every field control as defined by our client.
2096 /// Fields must be specified by rows from the left.
2098 /// <param name="aLayout"></param>
2099 private void UpdateTableLayoutPanel(ClientData aClient)
2101 Debug.Print("UpdateTableLayoutPanel");
2103 if (aClient == null)
2110 TableLayout layout = aClient.Layout;
2112 //First clean our current panel
2113 iTableLayoutPanel.Controls.Clear();
2114 iTableLayoutPanel.RowStyles.Clear();
2115 iTableLayoutPanel.ColumnStyles.Clear();
2116 iTableLayoutPanel.RowCount = 0;
2117 iTableLayoutPanel.ColumnCount = 0;
2119 //Then recreate our rows...
2120 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2122 iTableLayoutPanel.RowCount++;
2126 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2128 iTableLayoutPanel.ColumnCount++;
2132 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2134 //Create our column styles
2135 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2138 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2142 //Create our row styles
2143 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2153 foreach (DataField field in aClient.Fields)
2155 if (!field.IsTableField)
2157 //That field is not taking part in our table layout skip it
2161 TableField tableField = (TableField) field;
2163 //Create a control corresponding to the field specified for that cell
2164 Control control = CreateControlForDataField(tableField);
2166 //Add newly created control to our table layout at the specified row and column
2167 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2168 //Make sure we specify column and row span for that new control
2169 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2170 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2178 /// Check our type of data field and create corresponding control
2180 /// <param name="aField"></param>
2181 private Control CreateControlForDataField(DataField aField)
2183 Control control = null;
2184 if (aField.IsTextField)
2186 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2187 label.AutoEllipsis = true;
2188 label.AutoSize = true;
2189 label.BackColor = System.Drawing.Color.Transparent;
2190 label.Dock = System.Windows.Forms.DockStyle.Fill;
2191 label.Location = new System.Drawing.Point(1, 1);
2192 label.Margin = new System.Windows.Forms.Padding(0);
2193 label.Name = "marqueeLabel" + aField;
2194 label.OwnTimer = false;
2195 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2196 label.Separator = cds.Separator;
2197 label.MinFontSize = cds.MinFontSize;
2198 label.ScaleToFit = cds.ScaleToFit;
2199 //control.Size = new System.Drawing.Size(254, 30);
2200 //control.TabIndex = 2;
2201 label.Font = cds.Font;
2203 TextField field = (TextField) aField;
2204 label.TextAlign = field.Alignment;
2205 label.UseCompatibleTextRendering = true;
2206 label.Text = field.Text;
2210 else if (aField.IsBitmapField)
2212 //Create picture box
2213 PictureBox picture = new PictureBox();
2214 picture.AutoSize = true;
2215 picture.BackColor = System.Drawing.Color.Transparent;
2216 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2217 picture.Location = new System.Drawing.Point(1, 1);
2218 picture.Margin = new System.Windows.Forms.Padding(0);
2219 picture.Name = "pictureBox" + aField;
2221 BitmapField field = (BitmapField) aField;
2222 picture.Image = field.Bitmap;
2226 //TODO: Handle recording field?
2232 /// Called when the user selected a new display type.
2234 /// <param name="sender"></param>
2235 /// <param name="e"></param>
2236 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2238 //Store the selected display type in our settings
2239 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2240 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2241 Properties.Settings.Default.Save();
2243 //Try re-opening the display connection if we were already connected.
2244 //Otherwise just update our status to reflect display type change.
2245 if (iDisplay.IsOpen())
2247 OpenDisplayConnection();
2255 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2257 if (maskedTextBoxTimerInterval.Text != "")
2259 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2263 timer.Interval = interval;
2264 cds.TimerInterval = timer.Interval;
2265 Properties.Settings.Default.Save();
2270 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2272 if (maskedTextBoxMinFontSize.Text != "")
2274 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2276 if (minFontSize > 0)
2278 cds.MinFontSize = minFontSize;
2279 Properties.Settings.Default.Save();
2280 //We need to recreate our layout for that change to take effect
2281 UpdateTableLayoutPanel(iCurrentClientData);
2287 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2289 if (maskedTextBoxScrollingSpeed.Text != "")
2291 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2293 if (scrollingSpeed > 0)
2295 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2296 Properties.Settings.Default.Save();
2297 //We need to recreate our layout for that change to take effect
2298 UpdateTableLayoutPanel(iCurrentClientData);
2303 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2305 cds.Separator = textBoxScrollLoopSeparator.Text;
2306 Properties.Settings.Default.Save();
2308 //Update our text fields
2309 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2311 ctrl.Separator = cds.Separator;
2316 private void buttonPowerOn_Click(object sender, EventArgs e)
2321 private void buttonPowerOff_Click(object sender, EventArgs e)
2323 iDisplay.PowerOff();
2326 private void buttonShowClock_Click(object sender, EventArgs e)
2331 private void buttonHideClock_Click(object sender, EventArgs e)
2336 private void buttonUpdate_Click(object sender, EventArgs e)
2338 InstallUpdateSyncWithInfo();
2346 if (!iDisplay.IsOpen())
2351 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2352 iSkipFrameRendering = true;
2355 iDisplay.SwapBuffers();
2356 //Then show our clock
2357 iDisplay.ShowClock();
2365 if (!iDisplay.IsOpen())
2370 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2371 iSkipFrameRendering = false;
2372 iDisplay.HideClock();
2375 private void InstallUpdateSyncWithInfo()
2377 UpdateCheckInfo info = null;
2379 if (ApplicationDeployment.IsNetworkDeployed)
2381 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2385 info = ad.CheckForDetailedUpdate();
2388 catch (DeploymentDownloadException dde)
2391 "The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " +
2395 catch (InvalidDeploymentException ide)
2398 "Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " +
2402 catch (InvalidOperationException ioe)
2405 "This application cannot be updated. It is likely not a ClickOnce application. Error: " +
2410 if (info.UpdateAvailable)
2412 Boolean doUpdate = true;
2414 if (!info.IsUpdateRequired)
2417 MessageBox.Show("An update is available. Would you like to update the application now?",
2418 "Update Available", MessageBoxButtons.OKCancel);
2419 if (!(DialogResult.OK == dr))
2426 // Display a message that the application MUST reboot. Display the minimum required version.
2427 MessageBox.Show("This application has detected a mandatory update from your current " +
2428 "version to version " + info.MinimumRequiredVersion.ToString() +
2429 ". The application will now install the update and restart.",
2430 "Update Available", MessageBoxButtons.OK,
2431 MessageBoxIcon.Information);
2439 MessageBox.Show("The application has been upgraded, and will now restart.");
2440 Application.Restart();
2442 catch (DeploymentDownloadException dde)
2445 "Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " +
2453 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2462 private void SysTrayHideShow()
2468 WindowState = FormWindowState.Normal;
2473 /// Use to handle minimize events.
2475 /// <param name="sender"></param>
2476 /// <param name="e"></param>
2477 private void MainForm_SizeChanged(object sender, EventArgs e)
2479 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2491 /// <param name="sender"></param>
2492 /// <param name="e"></param>
2493 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2495 //Our table layout size has changed which means our display size has changed.
2496 //We need to re-create our bitmap.
2497 iCreateBitmap = true;
2503 private void LogsUpdate()
2505 if (iWriter != null)
2513 /// Broadcast messages to subscribers.
2515 /// <param name="message"></param>
2516 protected override void WndProc(ref Message aMessage)
2520 if (OnWndProc != null)
2522 OnWndProc(ref aMessage);
2525 base.WndProc(ref aMessage);
2528 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2534 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2536 //Save CEC HDMI port
2537 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2538 Properties.Settings.Default.CecHdmiPort++;
2539 Properties.Settings.Default.Save();
2547 private void ResetCec()
2549 if (iCecManager == null)
2551 //Thus skipping initial UI setup
2557 if (Properties.Settings.Default.CecEnabled)
2559 iCecManager.Start(Handle, "CEC",
2560 Properties.Settings.Default.CecHdmiPort);
2569 private async void ResetHarmony()
2571 // ConnectAsync already if we have an existing session cookie
2572 if (Properties.Settings.Default.HarmonyEnabled && File.Exists("SessionToken"))
2575 iButtonHarmonyConnect.Enabled = false;
2578 await ConnectHarmonyAsync();
2582 iButtonHarmonyConnect.Enabled = true;
2590 private void SetupCecLogLevel()
2593 iCecManager.Client.LogLevel = 0;
2595 if (checkBoxCecLogError.Checked)
2596 iCecManager.Client.LogLevel |= (int) CecLogLevel.Error;
2598 if (checkBoxCecLogWarning.Checked)
2599 iCecManager.Client.LogLevel |= (int) CecLogLevel.Warning;
2601 if (checkBoxCecLogNotice.Checked)
2602 iCecManager.Client.LogLevel |= (int) CecLogLevel.Notice;
2604 if (checkBoxCecLogTraffic.Checked)
2605 iCecManager.Client.LogLevel |= (int) CecLogLevel.Traffic;
2607 if (checkBoxCecLogDebug.Checked)
2608 iCecManager.Client.LogLevel |= (int) CecLogLevel.Debug;
2610 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2614 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2619 private void buttonClearLogs_Click(object sender, EventArgs e)
2621 richTextBoxLogs.Clear();
2624 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)
2633 /// <param name="aEvent"></param>
2634 private void SelectEvent(Ear.Event aEvent)
2641 foreach (TreeNode node in iTreeViewEvents.Nodes)
2643 if (node.Tag == aEvent)
2645 iTreeViewEvents.SelectedNode = node;
2646 iTreeViewEvents.Focus();
2654 /// Get the current event based on event tree view selection.
2656 /// <returns></returns>
2657 private Ear.Event CurrentEvent()
2659 //Walk up the tree from the selected node to find our event
2660 TreeNode node = iTreeViewEvents.SelectedNode;
2661 Ear.Event selectedEvent = null;
2662 while (node != null)
2664 if (node.Tag is Ear.Event)
2666 selectedEvent = (Ear.Event) node.Tag;
2672 return selectedEvent;
2676 /// Get the current action based on event tree view selection
2678 /// <returns></returns>
2679 private Ear.Action CurrentAction()
2681 TreeNode node = iTreeViewEvents.SelectedNode;
2682 if (node != null && node.Tag is Ear.Action)
2684 return (Ear.Action) node.Tag;
2693 /// <param name="sender"></param>
2694 /// <param name="e"></param>
2695 private void buttonActionAdd_Click(object sender, EventArgs e)
2697 Ear.Event selectedEvent = CurrentEvent();
2698 if (selectedEvent == null)
2700 //We did not find a corresponding event
2704 FormEditObject<Ear.Action> ea = new FormEditObject<Ear.Action>();
2705 ea.Text = "Add action";
2706 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2707 if (res == DialogResult.OK)
2709 selectedEvent.Actions.Add(ea.Object);
2710 Properties.Settings.Default.Save();
2711 PopulateEventsTreeView();
2718 /// <param name="sender"></param>
2719 /// <param name="e"></param>
2720 private void buttonActionEdit_Click(object sender, EventArgs e)
2722 Ear.Event selectedEvent = CurrentEvent();
2723 Ear.Action selectedAction = CurrentAction();
2724 if (selectedEvent == null || selectedAction == null)
2726 //We did not find a corresponding event
2730 FormEditObject<Ear.Action> ea = new FormEditObject<Ear.Action>();
2731 ea.Text = "Edit action";
2732 ea.Object = selectedAction;
2733 int actionIndex = iTreeViewEvents.SelectedNode.Index;
2734 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2735 if (res == DialogResult.OK)
2738 selectedEvent.Actions[actionIndex]=ea.Object;
2739 //Save and rebuild our event tree view
2740 Properties.Settings.Default.Save();
2741 PopulateEventsTreeView();
2748 /// <param name="sender"></param>
2749 /// <param name="e"></param>
2750 private void buttonActionDelete_Click(object sender, EventArgs e)
2753 Ear.Action action = CurrentAction();
2756 //Must select action node
2760 Properties.Settings.Default.EarManager.RemoveAction(action);
2761 Properties.Settings.Default.Save();
2762 PopulateEventsTreeView();
2768 /// <param name="sender"></param>
2769 /// <param name="e"></param>
2770 private void buttonActionTest_Click(object sender, EventArgs e)
2772 Ear.Action a = CurrentAction();
2777 iTreeViewEvents.Focus();
2783 /// <param name="sender"></param>
2784 /// <param name="e"></param>
2785 private void buttonActionMoveUp_Click(object sender, EventArgs e)
2787 Ear.Action a = CurrentAction();
2789 //Action already at the top of the list
2790 iTreeViewEvents.SelectedNode.Index == 0)
2795 //Swap actions in event's action list
2796 Ear.Event currentEvent = CurrentEvent();
2797 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2798 Ear.Action movingUp = currentEvent.Actions[currentIndex];
2799 Ear.Action movingDown = currentEvent.Actions[currentIndex-1];
2800 currentEvent.Actions[currentIndex] = movingDown;
2801 currentEvent.Actions[currentIndex-1] = movingUp;
2803 //Save and populate our tree again
2804 Properties.Settings.Default.Save();
2805 PopulateEventsTreeView();
2812 /// <param name="sender"></param>
2813 /// <param name="e"></param>
2814 private void buttonActionMoveDown_Click(object sender, EventArgs e)
2816 Ear.Action a = CurrentAction();
2818 //Action already at the bottom of the list
2819 iTreeViewEvents.SelectedNode.Index == iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1)
2824 //Swap actions in event's action list
2825 Ear.Event currentEvent = CurrentEvent();
2826 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2827 Ear.Action movingDown = currentEvent.Actions[currentIndex];
2828 Ear.Action movingUp = currentEvent.Actions[currentIndex + 1];
2829 currentEvent.Actions[currentIndex] = movingUp;
2830 currentEvent.Actions[currentIndex + 1] = movingDown;
2832 //Save and populate our tree again
2833 Properties.Settings.Default.Save();
2834 PopulateEventsTreeView();
2841 /// <param name="sender"></param>
2842 /// <param name="e"></param>
2843 private void buttonEventTest_Click(object sender, EventArgs e)
2845 Ear.Event earEvent = CurrentEvent();
2846 if (earEvent != null)
2853 /// Manages events and actions buttons according to selected item in event tree.
2855 /// <param name="sender"></param>
2856 /// <param name="e"></param>
2857 private void iTreeViewEvents_AfterSelect(object sender, TreeViewEventArgs e)
2865 private void UpdateEventView()
2867 //One can always add an event
2868 buttonEventAdd.Enabled = true;
2870 //Enable buttons according to selected item
2871 buttonActionAdd.Enabled =
2872 buttonEventTest.Enabled =
2873 buttonEventDelete.Enabled =
2874 buttonEventEdit.Enabled =
2875 CurrentEvent() != null;
2877 Ear.Action currentAction = CurrentAction();
2878 //If an action is selected enable the following buttons
2879 buttonActionTest.Enabled =
2880 buttonActionDelete.Enabled =
2881 buttonActionMoveUp.Enabled =
2882 buttonActionMoveDown.Enabled =
2883 buttonActionEdit.Enabled =
2884 currentAction != null;
2886 if (currentAction != null)
2888 //If an action is selected enable move buttons if needed
2889 buttonActionMoveUp.Enabled = iTreeViewEvents.SelectedNode.Index != 0;
2890 buttonActionMoveDown.Enabled = iTreeViewEvents.SelectedNode.Index <
2891 iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1;
2895 private void buttonEventAdd_Click(object sender, EventArgs e)
2897 FormEditObject<Ear.Event> ea = new FormEditObject<Ear.Event>();
2898 ea.Text = "Add event";
2899 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2900 if (res == DialogResult.OK)
2902 Properties.Settings.Default.EarManager.Events.Add(ea.Object);
2903 Properties.Settings.Default.Save();
2904 PopulateEventsTreeView();
2905 SelectEvent(ea.Object);
2909 private void buttonEventDelete_Click(object sender, EventArgs e)
2911 Ear.Event currentEvent = CurrentEvent();
2912 if (currentEvent == null)
2914 //Must select action node
2918 Properties.Settings.Default.EarManager.Events.Remove(currentEvent);
2919 Properties.Settings.Default.Save();
2920 PopulateEventsTreeView();
2923 private void buttonEventEdit_Click(object sender, EventArgs e)
2925 Ear.Event selectedEvent = CurrentEvent();
2926 if (selectedEvent == null)
2928 //We did not find a corresponding event
2932 FormEditObject<Ear.Event> ea = new FormEditObject<Ear.Event>();
2933 ea.Text = "Edit event";
2934 ea.Object = selectedEvent;
2935 int actionIndex = iTreeViewEvents.SelectedNode.Index;
2936 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2937 if (res == DialogResult.OK)
2939 //Save and rebuild our event tree view
2940 Properties.Settings.Default.Save();
2941 PopulateEventsTreeView();
2945 private void iTreeViewEvents_Leave(object sender, EventArgs e)
2947 //Make sure our event tree never looses focus
2948 ((TreeView) sender).Focus();
2951 private async void iButtonHarmonyConnect_Click(object sender, EventArgs e)
2954 Properties.Settings.Default.HarmonyHubAddress = iTextBoxHarmonyHubAddress.Text;
2955 Properties.Settings.Default.Save();
2957 iButtonHarmonyConnect.Enabled = false;
2960 await ConnectHarmonyAsync();
2964 iButtonHarmonyConnect.Enabled = true;
2970 private async Task ConnectHarmonyAsync()
2972 if (Program.HarmonyClient != null)
2974 Program.HarmonyClient.Close();
2977 //Reset Harmony client & config
2978 Program.HarmonyClient = null;
2979 Program.HarmonyConfig = null;
2981 Console.WriteLine("Harmony: Connecting... ");
2982 Program.HarmonyClient = new HarmonyHub.Client(iTextBoxHarmonyHubAddress.Text);
2983 //First create our client and login
2984 if (File.Exists("SessionToken"))
2986 var sessionToken = File.ReadAllText("SessionToken");
2987 Console.WriteLine("Harmony: Reusing token: {0}", sessionToken);
2988 Program.HarmonyClient.Open(sessionToken);
2992 if (string.IsNullOrEmpty(iTextBoxLogitechPassword.Text))
2994 Console.WriteLine("Harmony: Credentials missing!");
2998 Console.WriteLine("Harmony: Authenticating with Logitech servers...");
2999 await Program.HarmonyClient.Open(iTextBoxLogitechUserName.Text, iTextBoxLogitechPassword.Text);
3000 File.WriteAllText("SessionToken", Program.HarmonyClient.Token);
3003 Console.WriteLine("Harmony: Fetching Harmony Hub configuration...");
3006 Program.HarmonyConfig = await Program.HarmonyClient.GetConfigAsync();
3007 PopulateTreeViewHarmony(Program.HarmonyConfig);
3009 Console.WriteLine("Harmony: Ready");
3015 /// <param name="aConfig"></param>
3016 private void PopulateTreeViewHarmony(HarmonyHub.Config aConfig)
3018 iTreeViewHarmony.Nodes.Clear();
3020 foreach (HarmonyHub.Device device in aConfig.Devices)
3022 TreeNode deviceNode = iTreeViewHarmony.Nodes.Add(device.Id, $"{device.Label} ({device.DeviceTypeDisplayName}/{device.Model})");
3023 deviceNode.Tag = device;
3025 foreach (HarmonyHub.ControlGroup cg in device.ControlGroups)
3027 TreeNode cgNode = deviceNode.Nodes.Add(cg.Name);
3030 foreach (HarmonyHub.Function f in cg.Functions)
3032 TreeNode fNode = cgNode.Nodes.Add(f.Name);
3038 //treeViewConfig.ExpandAll();
3041 private async void iTreeViewHarmony_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
3043 //Upon function node double click we execute it
3044 var tag = e.Node.Tag as HarmonyHub.Function;
3045 if (tag != null && e.Node.Parent.Parent.Tag is HarmonyHub.Device)
3047 HarmonyHub.Function f = tag;
3048 HarmonyHub.Device d = (HarmonyHub.Device)e.Node.Parent.Parent.Tag;
3050 Console.WriteLine($"Harmony: Sending {f.Name} to {d.Label}...");
3052 await Program.HarmonyClient.SendCommandAsync(d.Id, f.Name);