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 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 //Update our animations
997 DateTime NewTickTime = DateTime.Now;
999 UpdateNetworkSignal(LastTickTime, NewTickTime);
1001 //Update animation for all our marquees
1002 foreach (Control ctrl in iTableLayoutPanel.Controls)
1004 if (ctrl is MarqueeLabel)
1006 ((MarqueeLabel) ctrl).UpdateAnimation(LastTickTime, NewTickTime);
1010 //Update our display
1011 if (iDisplay.IsOpen())
1013 CheckForRequestResults();
1015 //Check if frame rendering is needed
1016 //Typically used when showing clock
1017 if (!iSkipFrameRendering)
1022 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
1023 PixelFormat.Format32bppArgb);
1024 iCreateBitmap = false;
1026 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
1027 //iBmp.Save("D:\\capture.png");
1029 //Send it to our display
1030 for (int i = 0; i < iBmp.Width; i++)
1032 for (int j = 0; j < iBmp.Height; j++)
1036 //Get our processed pixel coordinates
1037 int x = iScreenX(iBmp, i);
1038 int y = iScreenY(iBmp, j);
1040 uint color = (uint) iBmp.GetPixel(i, j).ToArgb();
1041 //Apply color effects
1042 color = iColorFx(x, y, color);
1044 iDisplay.SetPixel(x, y, color);
1049 iDisplay.SwapBuffers();
1053 //Compute instant FPS
1054 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " +
1055 (1000/timer.Interval).ToString() + " FPS";
1057 LastTickTime = NewTickTime;
1062 /// Attempt to establish connection with our display hardware.
1064 private void OpenDisplayConnection()
1066 CloseDisplayConnection();
1068 if (!iDisplay.Open((MiniDisplay.Type) cds.DisplayType))
1071 toolStripStatusLabelConnect.Text = "Connection error";
1075 private void CloseDisplayConnection()
1077 //Status will be updated upon receiving the closed event
1079 if (iDisplay == null || !iDisplay.IsOpen())
1084 //Do not clear if we gave up on rendering already.
1085 //This means we will keep on displaying clock on MDM166AA for instance.
1086 if (!iSkipFrameRendering)
1089 iDisplay.SwapBuffers();
1092 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1096 private void buttonOpen_Click(object sender, EventArgs e)
1098 OpenDisplayConnection();
1101 private void buttonClose_Click(object sender, EventArgs e)
1103 CloseDisplayConnection();
1106 private void buttonClear_Click(object sender, EventArgs e)
1109 iDisplay.SwapBuffers();
1112 private void buttonFill_Click(object sender, EventArgs e)
1115 iDisplay.SwapBuffers();
1118 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1120 cds.Brightness = trackBarBrightness.Value;
1121 Properties.Settings.Default.Save();
1122 iDisplay.SetBrightness(trackBarBrightness.Value);
1128 /// CDS stands for Current Display Settings
1130 private DisplaySettings cds
1134 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1135 if (settings == null)
1137 settings = new DisplaysSettings();
1139 Properties.Settings.Default.DisplaysSettings = settings;
1142 //Make sure all our settings have been created
1143 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1145 settings.Displays.Add(new DisplaySettings());
1148 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1149 return displaySettings;
1154 /// Check if the given font has a fixed character pitch.
1156 /// <param name="ft"></param>
1157 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1158 public float IsFixedWidth(Font ft)
1160 Graphics g = CreateGraphics();
1161 char[] charSizes = new char[] {'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.'};
1162 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1164 bool fixedWidth = true;
1166 foreach (char c in charSizes)
1167 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width !=
1180 /// Synchronize UI with settings
1182 private void UpdateStatus()
1185 checkBoxShowBorders.Checked = cds.ShowBorders;
1186 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders
1187 ? TableLayoutPanelCellBorderStyle.Single
1188 : TableLayoutPanelCellBorderStyle.None);
1190 //Set the proper font to each of our labels
1191 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1193 ctrl.Font = cds.Font;
1197 //Check if "run on Windows startup" is enabled
1198 checkBoxAutoStart.Checked = iStartupManager.Startup;
1201 iButtonHarmonyConnect.Enabled = Properties.Settings.Default.HarmonyEnabled;
1204 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1206 //Mini Display settings
1207 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1208 checkBoxInverseColors.Checked = cds.InverseColors;
1209 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1210 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1211 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1212 labelMinFontSize.Enabled = cds.ScaleToFit;
1213 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1214 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1215 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1216 timer.Interval = cds.TimerInterval;
1217 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1218 textBoxScrollLoopSeparator.Text = cds.Separator;
1220 SetupPixelDelegates();
1222 if (iDisplay.IsOpen())
1224 //We have a display connection
1225 //Reflect that in our UI
1228 iTableLayoutPanel.Enabled = true;
1229 panelDisplay.Enabled = true;
1231 //Only setup brightness if display is open
1232 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1233 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1234 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1236 //Brightness out of range, this can occur when using auto-detect
1237 //Use max brightness instead
1238 trackBarBrightness.Value = iDisplay.MaxBrightness();
1239 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1243 trackBarBrightness.Value = cds.Brightness;
1244 iDisplay.SetBrightness(cds.Brightness);
1247 //Try compute the steps to something that makes sense
1248 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness())/5);
1249 trackBarBrightness.SmallChange = 1;
1252 buttonFill.Enabled = true;
1253 buttonClear.Enabled = true;
1254 buttonOpen.Enabled = false;
1255 buttonClose.Enabled = true;
1256 trackBarBrightness.Enabled = true;
1257 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1258 //+ " - " + iDisplay.SerialNumber();
1260 if (iDisplay.SupportPowerOnOff())
1262 buttonPowerOn.Enabled = true;
1263 buttonPowerOff.Enabled = true;
1267 buttonPowerOn.Enabled = false;
1268 buttonPowerOff.Enabled = false;
1271 if (iDisplay.SupportClock())
1273 buttonShowClock.Enabled = true;
1274 buttonHideClock.Enabled = true;
1278 buttonShowClock.Enabled = false;
1279 buttonHideClock.Enabled = false;
1283 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1284 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel) > 0;
1286 if (cds.ShowVolumeLabel)
1288 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1292 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1297 //Display connection not available
1298 //Reflect that in our UI
1300 //In debug start our timer even if we don't have a display connection
1303 //In production environment we don't need our timer if no display connection
1306 checkBoxShowVolumeLabel.Enabled = false;
1307 iTableLayoutPanel.Enabled = false;
1308 panelDisplay.Enabled = false;
1309 buttonFill.Enabled = false;
1310 buttonClear.Enabled = false;
1311 buttonOpen.Enabled = true;
1312 buttonClose.Enabled = false;
1313 trackBarBrightness.Enabled = false;
1314 buttonPowerOn.Enabled = false;
1315 buttonPowerOff.Enabled = false;
1316 buttonShowClock.Enabled = false;
1317 buttonHideClock.Enabled = false;
1318 toolStripStatusLabelConnect.Text = "Disconnected";
1319 toolStripStatusLabelPower.Text = "N/A";
1328 /// <param name="sender"></param>
1329 /// <param name="e"></param>
1330 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1332 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1333 Properties.Settings.Default.Save();
1337 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1339 //Save our show borders setting
1340 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked
1341 ? TableLayoutPanelCellBorderStyle.Single
1342 : TableLayoutPanelCellBorderStyle.None);
1343 cds.ShowBorders = checkBoxShowBorders.Checked;
1344 Properties.Settings.Default.Save();
1348 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1350 iStartupManager.Startup = checkBoxAutoStart.Checked;
1354 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1356 //Save our reverse screen setting
1357 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1358 Properties.Settings.Default.Save();
1359 SetupPixelDelegates();
1362 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1364 //Save our inverse colors setting
1365 cds.InverseColors = checkBoxInverseColors.Checked;
1366 Properties.Settings.Default.Save();
1367 SetupPixelDelegates();
1370 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1372 //Save our scale to fit setting
1373 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1374 Properties.Settings.Default.Save();
1376 labelMinFontSize.Enabled = cds.ScaleToFit;
1377 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1380 private void MainForm_Resize(object sender, EventArgs e)
1382 if (WindowState == FormWindowState.Minimized)
1384 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1385 // That's apparently not needed on Windows 10 but we better leave it in place.
1386 iCreateBitmap = true;
1390 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1393 iNetworkManager.Dispose();
1394 CloseDisplayConnection();
1396 e.Cancel = iClosing;
1399 public void StartServer()
1401 iServiceHost = new ServiceHost
1404 new Uri[] {new Uri("net.tcp://localhost:8001/")}
1407 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true),
1409 iServiceHost.Open();
1412 public void StopServer()
1414 if (iClients.Count > 0 && !iClosing)
1418 BroadcastCloseEvent();
1423 MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo,
1424 MessageBoxIcon.Warning) == DialogResult.Yes)
1426 iClosing = false; //We make sure we force close if asked twice
1431 //We removed that as it often lags for some reason
1432 //iServiceHost.Close();
1436 public void BroadcastCloseEvent()
1438 Trace.TraceInformation("BroadcastCloseEvent - start");
1440 var inactiveClients = new List<string>();
1441 foreach (var client in iClients)
1443 //if (client.Key != eventData.ClientName)
1447 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1448 client.Value.Callback.OnCloseOrder( /*eventData*/);
1450 catch (Exception ex)
1452 inactiveClients.Add(client.Key);
1457 if (inactiveClients.Count > 0)
1459 foreach (var client in inactiveClients)
1461 iClients.Remove(client);
1462 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1463 Program.iFormMain.iTreeViewClients.Nodes.Find(client, false)[0]);
1467 if (iClients.Count == 0)
1474 /// Just remove all our fields.
1476 private void ClearLayout()
1478 iTableLayoutPanel.Controls.Clear();
1479 iTableLayoutPanel.RowStyles.Clear();
1480 iTableLayoutPanel.ColumnStyles.Clear();
1481 iCurrentClientData = null;
1485 /// Just launch a demo client.
1487 private void StartNewClient(string aTopText = "", string aBottomText = "")
1489 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1490 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(
1491 new Point(this.Right, this.Top), aTopText, aBottomText);
1492 clientThread.Start(myParams);
1497 /// Just launch our idle client.
1499 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1501 Thread clientThread = new Thread(SharpDisplayClientIdle.Program.MainWithParams);
1502 SharpDisplayClientIdle.StartParams myParams =
1503 new SharpDisplayClientIdle.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1504 clientThread.Start(myParams);
1509 private void buttonStartClient_Click(object sender, EventArgs e)
1514 private void buttonSuspend_Click(object sender, EventArgs e)
1519 private void StartTimer()
1521 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1522 timer.Enabled = true;
1523 UpdateSuspendButton();
1526 private void StopTimer()
1528 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1529 timer.Enabled = false;
1530 UpdateSuspendButton();
1533 private void ToggleTimer()
1535 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1536 timer.Enabled = !timer.Enabled;
1537 UpdateSuspendButton();
1540 private void UpdateSuspendButton()
1544 buttonSuspend.Text = "Run";
1548 buttonSuspend.Text = "Pause";
1553 private void buttonCloseClients_Click(object sender, EventArgs e)
1555 BroadcastCloseEvent();
1558 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1560 //Root node must have at least one child
1561 if (e.Node.Nodes.Count == 0)
1566 //If the selected node is the root node of a client then switch to it
1567 string sessionId = e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1568 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1570 //We have a valid session just switch to that client
1571 SetCurrentClient(sessionId, true);
1580 /// <param name="aSessionId"></param>
1581 /// <param name="aCallback"></param>
1582 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1584 if (this.InvokeRequired)
1586 //Not in the proper thread, invoke ourselves
1587 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1588 this.Invoke(d, new object[] {aSessionId, aCallback});
1592 //We are in the proper thread
1593 //Add this session to our collection of clients
1594 ClientData newClient = new ClientData(aSessionId, aCallback);
1595 Program.iFormMain.iClients.Add(aSessionId, newClient);
1596 //Add this session to our UI
1597 UpdateClientTreeViewNode(newClient);
1603 /// Find the client with the highest priority if any.
1605 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1606 public ClientData FindHighestPriorityClient()
1608 ClientData highestPriorityClient = null;
1609 foreach (var client in iClients)
1611 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1613 highestPriorityClient = client.Value;
1617 return highestPriorityClient;
1623 /// <param name="aSessionId"></param>
1624 public void RemoveClientThreadSafe(string aSessionId)
1626 if (this.InvokeRequired)
1628 //Not in the proper thread, invoke ourselves
1629 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1630 this.Invoke(d, new object[] {aSessionId});
1634 //We are in the proper thread
1635 //Remove this session from both client collection and UI tree view
1636 if (Program.iFormMain.iClients.Keys.Contains(aSessionId))
1638 Program.iFormMain.iClients.Remove(aSessionId);
1639 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1640 Program.iFormMain.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1641 //Update recording status too whenever a client is removed
1642 UpdateRecordingNotification();
1645 if (iCurrentClientSessionId == aSessionId)
1647 //The current client is closing
1648 iCurrentClientData = null;
1649 //Find the client with the highest priority and set it as current
1650 ClientData newCurrentClient = FindHighestPriorityClient();
1651 if (newCurrentClient != null)
1653 SetCurrentClient(newCurrentClient.SessionId, true);
1657 if (iClients.Count == 0)
1659 //Clear our screen when last client disconnects
1664 //We were closing our form
1665 //All clients are now closed
1666 //Just resume our close operation
1677 /// <param name="aSessionId"></param>
1678 /// <param name="aLayout"></param>
1679 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1681 if (this.InvokeRequired)
1683 //Not in the proper thread, invoke ourselves
1684 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1685 this.Invoke(d, new object[] {aSessionId, aLayout});
1689 ClientData client = iClients[aSessionId];
1692 //Don't change a thing if the layout is the same
1693 if (!client.Layout.IsSameAs(aLayout))
1695 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1696 //Set our client layout then
1697 client.Layout = aLayout;
1698 //So that next time we update all our fields at ones
1699 client.HasNewLayout = true;
1700 //Layout has changed clear our fields then
1701 client.Fields.Clear();
1703 UpdateClientTreeViewNode(client);
1707 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1716 /// <param name="aSessionId"></param>
1717 /// <param name="aField"></param>
1718 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1720 if (this.InvokeRequired)
1722 //Not in the proper thread, invoke ourselves
1723 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1724 this.Invoke(d, new object[] {aSessionId, aField});
1728 //We are in the proper thread
1729 //Call the non-thread-safe variant
1730 SetClientField(aSessionId, aField);
1738 /// Set a data field in the given client.
1740 /// <param name="aSessionId"></param>
1741 /// <param name="aField"></param>
1742 private void SetClientField(string aSessionId, DataField aField)
1744 //TODO: should check if the field actually changed?
1746 ClientData client = iClients[aSessionId];
1747 bool layoutChanged = false;
1748 bool contentChanged = true;
1750 //Fetch our field index
1751 int fieldIndex = client.FindSameFieldIndex(aField);
1755 //No corresponding field, just bail out
1759 //Keep our previous field in there
1760 DataField previousField = client.Fields[fieldIndex];
1761 //Just update that field then
1762 client.Fields[fieldIndex] = aField;
1764 if (!aField.IsTableField)
1766 //We are done then if that field is not in our table layout
1770 TableField tableField = (TableField) aField;
1772 if (previousField.IsSameLayout(aField))
1774 //If we are updating a field in our current client we need to update it in our panel
1775 if (aSessionId == iCurrentClientSessionId)
1777 Control ctrl = iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1778 if (aField.IsTextField && ctrl is MarqueeLabel)
1780 TextField textField = (TextField) aField;
1781 //Text field control already in place, just change the text
1782 MarqueeLabel label = (MarqueeLabel) ctrl;
1783 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1784 label.Text = textField.Text;
1785 label.TextAlign = textField.Alignment;
1787 else if (aField.IsBitmapField && ctrl is PictureBox)
1789 BitmapField bitmapField = (BitmapField) aField;
1790 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1791 //Bitmap field control already in place just change the bitmap
1792 PictureBox pictureBox = (PictureBox) ctrl;
1793 pictureBox.Image = bitmapField.Bitmap;
1797 layoutChanged = true;
1803 layoutChanged = true;
1806 //If either content or layout changed we need to update our tree view to reflect the changes
1807 if (contentChanged || layoutChanged)
1809 UpdateClientTreeViewNode(client);
1813 Debug.Print("Layout changed");
1814 //Our layout has changed, if we are already the current client we need to update our panel
1815 if (aSessionId == iCurrentClientSessionId)
1817 //Apply layout and set data fields.
1818 UpdateTableLayoutPanel(iCurrentClientData);
1823 Debug.Print("Layout has not changed.");
1828 Debug.Print("WARNING: content and layout have not changed!");
1831 //When a client field is set we try switching to this client to present the new information to our user
1832 SetCurrentClient(aSessionId);
1838 /// <param name="aTexts"></param>
1839 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1841 if (this.InvokeRequired)
1843 //Not in the proper thread, invoke ourselves
1844 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1845 this.Invoke(d, new object[] {aSessionId, aFields});
1849 ClientData client = iClients[aSessionId];
1851 if (client.HasNewLayout)
1853 //TODO: Assert client.Count == 0
1854 //Our layout was just changed
1855 //Do some special handling to avoid re-creating our panel N times, once for each fields
1856 client.HasNewLayout = false;
1857 //Just set all our fields then
1858 client.Fields.AddRange(aFields);
1859 //Try switch to that client
1860 SetCurrentClient(aSessionId);
1862 //If we are updating the current client update our panel
1863 if (aSessionId == iCurrentClientSessionId)
1865 //Apply layout and set data fields.
1866 UpdateTableLayoutPanel(iCurrentClientData);
1869 UpdateClientTreeViewNode(client);
1873 //Put each our text fields in a label control
1874 foreach (DataField field in aFields)
1876 SetClientField(aSessionId, field);
1885 /// <param name="aSessionId"></param>
1886 /// <param name="aName"></param>
1887 public void SetClientNameThreadSafe(string aSessionId, string aName)
1889 if (this.InvokeRequired)
1891 //Not in the proper thread, invoke ourselves
1892 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1893 this.Invoke(d, new object[] {aSessionId, aName});
1897 //We are in the proper thread
1899 ClientData client = iClients[aSessionId];
1903 client.Name = aName;
1904 //Update our tree-view
1905 UpdateClientTreeViewNode(client);
1911 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1913 if (this.InvokeRequired)
1915 //Not in the proper thread, invoke ourselves
1916 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1917 this.Invoke(d, new object[] {aSessionId, aPriority});
1921 //We are in the proper thread
1923 ClientData client = iClients[aSessionId];
1927 client.Priority = aPriority;
1928 //Update our tree-view
1929 UpdateClientTreeViewNode(client);
1930 //Change our current client as per new priority
1931 ClientData newCurrentClient = FindHighestPriorityClient();
1932 if (newCurrentClient != null)
1934 SetCurrentClient(newCurrentClient.SessionId);
1943 /// <param name="value"></param>
1944 /// <param name="maxChars"></param>
1945 /// <returns></returns>
1946 public static string Truncate(string value, int maxChars)
1948 return value.Length <= maxChars ? value : value.Substring(0, maxChars - 3) + "...";
1952 /// Update our recording notification.
1954 private void UpdateRecordingNotification()
1957 bool activeRecording = false;
1959 RecordingField recField = new RecordingField();
1960 foreach (var client in iClients)
1962 RecordingField rec = (RecordingField) client.Value.FindSameFieldAs(recField);
1963 if (rec != null && rec.IsActive)
1965 activeRecording = true;
1966 //Don't break cause we are collecting the names/texts.
1967 if (!String.IsNullOrEmpty(rec.Text))
1969 text += (rec.Text + "\n");
1973 //Not text for that recording, use client name instead
1974 text += client.Value.Name + " recording\n";
1980 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
1981 iRecordingNotification.Text = Truncate(text, 63);
1983 //Change visibility of notification if needed
1984 if (iRecordingNotification.Visible != activeRecording)
1986 iRecordingNotification.Visible = activeRecording;
1987 //Assuming the notification icon is in sync with our display icon
1988 //Take care of our REC icon
1989 if (iDisplay.IsOpen())
1991 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
1999 /// <param name="aClient"></param>
2000 private void UpdateClientTreeViewNode(ClientData aClient)
2002 Debug.Print("UpdateClientTreeViewNode");
2004 if (aClient == null)
2009 //Hook in record icon update too
2010 UpdateRecordingNotification();
2012 TreeNode node = null;
2013 //Check that our client node already exists
2014 //Get our client root node using its key which is our session ID
2015 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
2016 if (nodes.Count() > 0)
2018 //We already have a node for that client
2020 //Clear children as we are going to recreate them below
2025 //Client node does not exists create a new one
2026 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
2027 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
2033 if (!String.IsNullOrEmpty(aClient.Name))
2035 //We have a name, use it as text for our root node
2036 node.Text = aClient.Name;
2037 //Add a child with SessionId
2038 node.Nodes.Add(new TreeNode(aClient.SessionId));
2042 //No name, use session ID instead
2043 node.Text = aClient.SessionId;
2046 //Display client priority
2047 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2049 if (aClient.Fields.Count > 0)
2051 //Create root node for our texts
2052 TreeNode textsRoot = new TreeNode("Fields");
2053 node.Nodes.Add(textsRoot);
2054 //For each text add a new entry
2055 foreach (DataField field in aClient.Fields)
2057 if (field.IsTextField)
2059 TextField textField = (TextField) field;
2060 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2062 else if (field.IsBitmapField)
2064 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2066 else if (field.IsRecordingField)
2068 RecordingField recordingField = (RecordingField) field;
2069 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2079 /// Update our table layout row styles to make sure each rows have similar height
2081 private void UpdateTableLayoutRowStyles()
2083 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2085 rowStyle.SizeType = SizeType.Percent;
2086 rowStyle.Height = 100/iTableLayoutPanel.RowCount;
2091 /// Update our display table layout.
2092 /// Will instanciated every field control as defined by our client.
2093 /// Fields must be specified by rows from the left.
2095 /// <param name="aLayout"></param>
2096 private void UpdateTableLayoutPanel(ClientData aClient)
2098 Debug.Print("UpdateTableLayoutPanel");
2100 if (aClient == null)
2107 TableLayout layout = aClient.Layout;
2109 //First clean our current panel
2110 iTableLayoutPanel.Controls.Clear();
2111 iTableLayoutPanel.RowStyles.Clear();
2112 iTableLayoutPanel.ColumnStyles.Clear();
2113 iTableLayoutPanel.RowCount = 0;
2114 iTableLayoutPanel.ColumnCount = 0;
2116 //Then recreate our rows...
2117 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2119 iTableLayoutPanel.RowCount++;
2123 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2125 iTableLayoutPanel.ColumnCount++;
2129 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2131 //Create our column styles
2132 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2135 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2139 //Create our row styles
2140 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2150 foreach (DataField field in aClient.Fields)
2152 if (!field.IsTableField)
2154 //That field is not taking part in our table layout skip it
2158 TableField tableField = (TableField) field;
2160 //Create a control corresponding to the field specified for that cell
2161 Control control = CreateControlForDataField(tableField);
2163 //Add newly created control to our table layout at the specified row and column
2164 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2165 //Make sure we specify column and row span for that new control
2166 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2167 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2175 /// Check our type of data field and create corresponding control
2177 /// <param name="aField"></param>
2178 private Control CreateControlForDataField(DataField aField)
2180 Control control = null;
2181 if (aField.IsTextField)
2183 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2184 label.AutoEllipsis = true;
2185 label.AutoSize = true;
2186 label.BackColor = System.Drawing.Color.Transparent;
2187 label.Dock = System.Windows.Forms.DockStyle.Fill;
2188 label.Location = new System.Drawing.Point(1, 1);
2189 label.Margin = new System.Windows.Forms.Padding(0);
2190 label.Name = "marqueeLabel" + aField;
2191 label.OwnTimer = false;
2192 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2193 label.Separator = cds.Separator;
2194 label.MinFontSize = cds.MinFontSize;
2195 label.ScaleToFit = cds.ScaleToFit;
2196 //control.Size = new System.Drawing.Size(254, 30);
2197 //control.TabIndex = 2;
2198 label.Font = cds.Font;
2200 TextField field = (TextField) aField;
2201 label.TextAlign = field.Alignment;
2202 label.UseCompatibleTextRendering = true;
2203 label.Text = field.Text;
2207 else if (aField.IsBitmapField)
2209 //Create picture box
2210 PictureBox picture = new PictureBox();
2211 picture.AutoSize = true;
2212 picture.BackColor = System.Drawing.Color.Transparent;
2213 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2214 picture.Location = new System.Drawing.Point(1, 1);
2215 picture.Margin = new System.Windows.Forms.Padding(0);
2216 picture.Name = "pictureBox" + aField;
2218 BitmapField field = (BitmapField) aField;
2219 picture.Image = field.Bitmap;
2223 //TODO: Handle recording field?
2229 /// Called when the user selected a new display type.
2231 /// <param name="sender"></param>
2232 /// <param name="e"></param>
2233 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2235 //Store the selected display type in our settings
2236 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2237 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2238 Properties.Settings.Default.Save();
2240 //Try re-opening the display connection if we were already connected.
2241 //Otherwise just update our status to reflect display type change.
2242 if (iDisplay.IsOpen())
2244 OpenDisplayConnection();
2252 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2254 if (maskedTextBoxTimerInterval.Text != "")
2256 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2260 timer.Interval = interval;
2261 cds.TimerInterval = timer.Interval;
2262 Properties.Settings.Default.Save();
2267 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2269 if (maskedTextBoxMinFontSize.Text != "")
2271 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2273 if (minFontSize > 0)
2275 cds.MinFontSize = minFontSize;
2276 Properties.Settings.Default.Save();
2277 //We need to recreate our layout for that change to take effect
2278 UpdateTableLayoutPanel(iCurrentClientData);
2284 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2286 if (maskedTextBoxScrollingSpeed.Text != "")
2288 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2290 if (scrollingSpeed > 0)
2292 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2293 Properties.Settings.Default.Save();
2294 //We need to recreate our layout for that change to take effect
2295 UpdateTableLayoutPanel(iCurrentClientData);
2300 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2302 cds.Separator = textBoxScrollLoopSeparator.Text;
2303 Properties.Settings.Default.Save();
2305 //Update our text fields
2306 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2308 ctrl.Separator = cds.Separator;
2313 private void buttonPowerOn_Click(object sender, EventArgs e)
2318 private void buttonPowerOff_Click(object sender, EventArgs e)
2320 iDisplay.PowerOff();
2323 private void buttonShowClock_Click(object sender, EventArgs e)
2328 private void buttonHideClock_Click(object sender, EventArgs e)
2333 private void buttonUpdate_Click(object sender, EventArgs e)
2335 InstallUpdateSyncWithInfo();
2343 if (!iDisplay.IsOpen())
2348 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2349 iSkipFrameRendering = true;
2352 iDisplay.SwapBuffers();
2353 //Then show our clock
2354 iDisplay.ShowClock();
2362 if (!iDisplay.IsOpen())
2367 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2368 iSkipFrameRendering = false;
2369 iDisplay.HideClock();
2372 private void InstallUpdateSyncWithInfo()
2374 UpdateCheckInfo info = null;
2376 if (ApplicationDeployment.IsNetworkDeployed)
2378 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2382 info = ad.CheckForDetailedUpdate();
2385 catch (DeploymentDownloadException dde)
2388 "The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " +
2392 catch (InvalidDeploymentException ide)
2395 "Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " +
2399 catch (InvalidOperationException ioe)
2402 "This application cannot be updated. It is likely not a ClickOnce application. Error: " +
2407 if (info.UpdateAvailable)
2409 Boolean doUpdate = true;
2411 if (!info.IsUpdateRequired)
2414 MessageBox.Show("An update is available. Would you like to update the application now?",
2415 "Update Available", MessageBoxButtons.OKCancel);
2416 if (!(DialogResult.OK == dr))
2423 // Display a message that the application MUST reboot. Display the minimum required version.
2424 MessageBox.Show("This application has detected a mandatory update from your current " +
2425 "version to version " + info.MinimumRequiredVersion.ToString() +
2426 ". The application will now install the update and restart.",
2427 "Update Available", MessageBoxButtons.OK,
2428 MessageBoxIcon.Information);
2436 MessageBox.Show("The application has been upgraded, and will now restart.");
2437 Application.Restart();
2439 catch (DeploymentDownloadException dde)
2442 "Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " +
2450 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2459 private void SysTrayHideShow()
2465 WindowState = FormWindowState.Normal;
2470 /// Use to handle minimize events.
2472 /// <param name="sender"></param>
2473 /// <param name="e"></param>
2474 private void MainForm_SizeChanged(object sender, EventArgs e)
2476 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2488 /// <param name="sender"></param>
2489 /// <param name="e"></param>
2490 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2492 //Our table layout size has changed which means our display size has changed.
2493 //We need to re-create our bitmap.
2494 iCreateBitmap = true;
2498 /// Broadcast messages to subscribers.
2500 /// <param name="message"></param>
2501 protected override void WndProc(ref Message aMessage)
2503 if (OnWndProc != null)
2505 OnWndProc(ref aMessage);
2508 base.WndProc(ref aMessage);
2511 private void iCheckBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2517 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2519 //Save CEC HDMI port
2520 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2521 Properties.Settings.Default.CecHdmiPort++;
2522 Properties.Settings.Default.Save();
2530 private void ResetCec()
2532 if (iCecManager == null)
2534 //Thus skipping initial UI setup
2540 if (Properties.Settings.Default.CecEnabled)
2542 iCecManager.Start(Handle, "CEC",
2543 Properties.Settings.Default.CecHdmiPort);
2552 private async void ResetHarmonyAsync(bool aForceAuth=false)
2554 // ConnectAsync already if we have an existing session cookie
2555 if (Properties.Settings.Default.HarmonyEnabled)
2559 iButtonHarmonyConnect.Enabled = false;
2560 await ConnectHarmonyAsync(aForceAuth);
2562 catch (Exception ex)
2564 Trace.WriteLine("Exception thrown by ConnectHarmonyAsync");
2565 Trace.WriteLine(ex.ToString());
2569 iButtonHarmonyConnect.Enabled = true;
2577 /// <param name="sender"></param>
2578 /// <param name="e"></param>
2579 private void iButtonHarmonyConnect_Click(object sender, EventArgs e)
2581 // User is explicitaly trying to connect
2582 //Reset Harmony Hub connection forcing authentication
2583 ResetHarmonyAsync(true);
2589 /// <param name="sender"></param>
2590 /// <param name="e"></param>
2591 private void iCheckBoxHarmonyEnabled_CheckedChanged(object sender, EventArgs e)
2593 iButtonHarmonyConnect.Enabled = iCheckBoxHarmonyEnabled.Checked;
2599 private void SetupCecLogLevel()
2602 iCecManager.Client.LogLevel = 0;
2604 if (checkBoxCecLogError.Checked)
2605 iCecManager.Client.LogLevel |= (int) CecLogLevel.Error;
2607 if (checkBoxCecLogWarning.Checked)
2608 iCecManager.Client.LogLevel |= (int) CecLogLevel.Warning;
2610 if (checkBoxCecLogNotice.Checked)
2611 iCecManager.Client.LogLevel |= (int) CecLogLevel.Notice;
2613 if (checkBoxCecLogTraffic.Checked)
2614 iCecManager.Client.LogLevel |= (int) CecLogLevel.Traffic;
2616 if (checkBoxCecLogDebug.Checked)
2617 iCecManager.Client.LogLevel |= (int) CecLogLevel.Debug;
2619 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2623 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2628 private void buttonClearLogs_Click(object sender, EventArgs e)
2630 richTextBoxLogs.Clear();
2633 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)
2642 /// <param name="aEvent"></param>
2643 private void SelectEvent(Ear.Event aEvent)
2650 foreach (TreeNode node in iTreeViewEvents.Nodes)
2652 if (node.Tag == aEvent)
2654 iTreeViewEvents.SelectedNode = node;
2655 iTreeViewEvents.Focus();
2663 /// Get the current event based on event tree view selection.
2665 /// <returns></returns>
2666 private Ear.Event CurrentEvent()
2668 //Walk up the tree from the selected node to find our event
2669 TreeNode node = iTreeViewEvents.SelectedNode;
2670 Ear.Event selectedEvent = null;
2671 while (node != null)
2673 if (node.Tag is Ear.Event)
2675 selectedEvent = (Ear.Event) node.Tag;
2681 return selectedEvent;
2685 /// Get the current action based on event tree view selection
2687 /// <returns></returns>
2688 private Ear.Action CurrentAction()
2690 TreeNode node = iTreeViewEvents.SelectedNode;
2691 if (node != null && node.Tag is Ear.Action)
2693 return (Ear.Action) node.Tag;
2702 /// <param name="sender"></param>
2703 /// <param name="e"></param>
2704 private void buttonActionAdd_Click(object sender, EventArgs e)
2706 Ear.Event selectedEvent = CurrentEvent();
2707 if (selectedEvent == null)
2709 //We did not find a corresponding event
2713 FormEditObject<Ear.Action> ea = new FormEditObject<Ear.Action>();
2714 ea.Text = "Add action";
2715 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2716 if (res == DialogResult.OK)
2718 selectedEvent.Actions.Add(ea.Object);
2719 Properties.Settings.Default.Save();
2720 PopulateEventsTreeView();
2727 /// <param name="sender"></param>
2728 /// <param name="e"></param>
2729 private void buttonActionEdit_Click(object sender, EventArgs e)
2731 Ear.Event selectedEvent = CurrentEvent();
2732 Ear.Action selectedAction = CurrentAction();
2733 if (selectedEvent == null || selectedAction == null)
2735 //We did not find a corresponding event
2739 FormEditObject<Ear.Action> ea = new FormEditObject<Ear.Action>();
2740 ea.Text = "Edit action";
2741 ea.Object = selectedAction;
2742 int actionIndex = iTreeViewEvents.SelectedNode.Index;
2743 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2744 if (res == DialogResult.OK)
2747 selectedEvent.Actions[actionIndex]=ea.Object;
2748 //Save and rebuild our event tree view
2749 Properties.Settings.Default.Save();
2750 PopulateEventsTreeView();
2757 /// <param name="sender"></param>
2758 /// <param name="e"></param>
2759 private void buttonActionDelete_Click(object sender, EventArgs e)
2762 Ear.Action action = CurrentAction();
2765 //Must select action node
2769 Properties.Settings.Default.EarManager.RemoveAction(action);
2770 Properties.Settings.Default.Save();
2771 PopulateEventsTreeView();
2777 /// <param name="sender"></param>
2778 /// <param name="e"></param>
2779 private void buttonActionTest_Click(object sender, EventArgs e)
2781 Ear.Action a = CurrentAction();
2786 iTreeViewEvents.Focus();
2792 /// <param name="sender"></param>
2793 /// <param name="e"></param>
2794 private void buttonActionMoveUp_Click(object sender, EventArgs e)
2796 Ear.Action a = CurrentAction();
2798 //Action already at the top of the list
2799 iTreeViewEvents.SelectedNode.Index == 0)
2804 //Swap actions in event's action list
2805 Ear.Event currentEvent = CurrentEvent();
2806 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2807 Ear.Action movingUp = currentEvent.Actions[currentIndex];
2808 Ear.Action movingDown = currentEvent.Actions[currentIndex-1];
2809 currentEvent.Actions[currentIndex] = movingDown;
2810 currentEvent.Actions[currentIndex-1] = movingUp;
2812 //Save and populate our tree again
2813 Properties.Settings.Default.Save();
2814 PopulateEventsTreeView();
2821 /// <param name="sender"></param>
2822 /// <param name="e"></param>
2823 private void buttonActionMoveDown_Click(object sender, EventArgs e)
2825 Ear.Action a = CurrentAction();
2827 //Action already at the bottom of the list
2828 iTreeViewEvents.SelectedNode.Index == iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1)
2833 //Swap actions in event's action list
2834 Ear.Event currentEvent = CurrentEvent();
2835 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2836 Ear.Action movingDown = currentEvent.Actions[currentIndex];
2837 Ear.Action movingUp = currentEvent.Actions[currentIndex + 1];
2838 currentEvent.Actions[currentIndex] = movingUp;
2839 currentEvent.Actions[currentIndex + 1] = movingDown;
2841 //Save and populate our tree again
2842 Properties.Settings.Default.Save();
2843 PopulateEventsTreeView();
2850 /// <param name="sender"></param>
2851 /// <param name="e"></param>
2852 private void buttonEventTest_Click(object sender, EventArgs e)
2854 Ear.Event earEvent = CurrentEvent();
2855 if (earEvent != null)
2862 /// Manages events and actions buttons according to selected item in event tree.
2864 /// <param name="sender"></param>
2865 /// <param name="e"></param>
2866 private void iTreeViewEvents_AfterSelect(object sender, TreeViewEventArgs e)
2874 private void UpdateEventView()
2876 //One can always add an event
2877 buttonEventAdd.Enabled = true;
2879 //Enable buttons according to selected item
2880 buttonActionAdd.Enabled =
2881 buttonEventTest.Enabled =
2882 buttonEventDelete.Enabled =
2883 buttonEventEdit.Enabled =
2884 CurrentEvent() != null;
2886 Ear.Action currentAction = CurrentAction();
2887 //If an action is selected enable the following buttons
2888 buttonActionTest.Enabled =
2889 buttonActionDelete.Enabled =
2890 buttonActionMoveUp.Enabled =
2891 buttonActionMoveDown.Enabled =
2892 buttonActionEdit.Enabled =
2893 currentAction != null;
2895 if (currentAction != null)
2897 //If an action is selected enable move buttons if needed
2898 buttonActionMoveUp.Enabled = iTreeViewEvents.SelectedNode.Index != 0;
2899 buttonActionMoveDown.Enabled = iTreeViewEvents.SelectedNode.Index <
2900 iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1;
2907 /// <param name="sender"></param>
2908 /// <param name="e"></param>
2909 private void buttonEventAdd_Click(object sender, EventArgs e)
2911 FormEditObject<Ear.Event> ea = new FormEditObject<Ear.Event>();
2912 ea.Text = "Add event";
2913 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2914 if (res == DialogResult.OK)
2916 Properties.Settings.Default.EarManager.Events.Add(ea.Object);
2917 Properties.Settings.Default.Save();
2918 PopulateEventsTreeView();
2919 SelectEvent(ea.Object);
2926 /// <param name="sender"></param>
2927 /// <param name="e"></param>
2928 private void buttonEventDelete_Click(object sender, EventArgs e)
2930 Ear.Event currentEvent = CurrentEvent();
2931 if (currentEvent == null)
2933 //Must select action node
2937 Properties.Settings.Default.EarManager.Events.Remove(currentEvent);
2938 Properties.Settings.Default.Save();
2939 PopulateEventsTreeView();
2945 /// <param name="sender"></param>
2946 /// <param name="e"></param>
2947 private void buttonEventEdit_Click(object sender, EventArgs e)
2949 Ear.Event selectedEvent = CurrentEvent();
2950 if (selectedEvent == null)
2952 //We did not find a corresponding event
2956 FormEditObject<Ear.Event> ea = new FormEditObject<Ear.Event>();
2957 ea.Text = "Edit event";
2958 ea.Object = selectedEvent;
2959 int index = iTreeViewEvents.SelectedNode.Index;
2960 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2961 if (res == DialogResult.OK)
2963 //Make sure we keep the same actions as before
2964 ea.Object.Actions = Properties.Settings.Default.EarManager.Events[index].Actions;
2966 Properties.Settings.Default.EarManager.Events[index] = ea.Object;
2967 //Save and rebuild our event tree view
2968 Properties.Settings.Default.Save();
2969 PopulateEventsTreeView();
2976 /// <param name="sender"></param>
2977 /// <param name="e"></param>
2978 private void iTreeViewEvents_Leave(object sender, EventArgs e)
2980 //Make sure our event tree never looses focus
2981 ((TreeView) sender).Focus();
2985 /// Called whenever we loose connection with our HarmonyHub.
2987 /// <param name="aRequestWasCancelled"></param>
2988 private void HarmonyConnectionClosedByServer(object aSender, bool aRequestWasCancelled)
2990 //Try reconnect then
2991 #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
2992 BeginInvoke(new MethodInvoker(delegate () { ResetHarmonyAsync(); }));
2993 #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
2999 /// <returns></returns>
3000 private async Task ConnectHarmonyAsync(bool aForceAuth=false)
3002 if (Program.HarmonyClient != null)
3004 await Program.HarmonyClient.CloseAsync();
3007 //Reset Harmony client & config
3008 Program.HarmonyClient = null;
3009 Program.HarmonyConfig = null;
3011 Trace.WriteLine("Harmony: Connecting... ");
3012 //First create our client and login
3013 //Tip: Set keep-alive to false when testing reconnection process
3014 Program.HarmonyClient = new HarmonyHub.Client(iTextBoxHarmonyHubAddress.Text, true);
3015 Program.HarmonyClient.OnConnectionClosedByServer += HarmonyConnectionClosedByServer;
3017 if (File.Exists("SessionToken") && !aForceAuth)
3019 var sessionToken = File.ReadAllText("SessionToken");
3020 Trace.WriteLine("Harmony: Reusing token: {0}", sessionToken);
3021 await Program.HarmonyClient.TryOpenAsync(sessionToken);
3024 if (!Program.HarmonyClient.IsReady)
3026 //We failed to connect using our token
3028 File.Delete("SessionToken");
3030 //Then try connect using our password
3031 if (string.IsNullOrEmpty(iTextBoxLogitechPassword.Text))
3033 Trace.WriteLine("Harmony: Credentials missing!");
3037 Trace.WriteLine("Harmony: Authenticating with Logitech servers...");
3038 await Program.HarmonyClient.TryOpenAsync(iTextBoxLogitechUserName.Text, iTextBoxLogitechPassword.Text);
3039 File.WriteAllText("SessionToken", Program.HarmonyClient.Token);
3043 Program.HarmonyConfig = await Program.HarmonyClient.GetConfigAsync();
3044 PopulateTreeViewHarmony(Program.HarmonyConfig);
3045 //Make sure harmony command actions are showing device name instead of device id
3046 PopulateEventsTreeView();
3052 /// <param name="aConfig"></param>
3053 private void PopulateTreeViewHarmony(HarmonyHub.Config aConfig)
3055 iTreeViewHarmony.Nodes.Clear();
3057 foreach (HarmonyHub.Device device in aConfig.Devices)
3059 TreeNode deviceNode = iTreeViewHarmony.Nodes.Add(device.Id, $"{device.Label} ({device.DeviceTypeDisplayName}/{device.Model})");
3060 deviceNode.Tag = device;
3062 foreach (HarmonyHub.ControlGroup cg in device.ControlGroups)
3064 TreeNode cgNode = deviceNode.Nodes.Add(cg.Name);
3067 foreach (HarmonyHub.Function f in cg.Functions)
3069 TreeNode fNode = cgNode.Nodes.Add(f.Label);
3075 //treeViewConfig.ExpandAll();
3078 private async void iTreeViewHarmony_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
3080 //Upon function node double click we execute it
3081 var tag = e.Node.Tag as HarmonyHub.Function;
3082 if (tag != null && e.Node.Parent.Parent.Tag is HarmonyHub.Device)
3084 HarmonyHub.Function f = tag;
3085 HarmonyHub.Device d = (HarmonyHub.Device)e.Node.Parent.Parent.Tag;
3087 Trace.WriteLine($"Harmony: Sending {f.Label} to {d.Label}...");
3089 await Program.HarmonyClient.TrySendKeyPressAsync(d.Id, f.Action.Command);