Adding non functional generic EAR HID event.
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 //Populate optical drives
182 PopulateOpticalDrives();
184 //Initial status update
187 //We have a bug when drawing minimized and reusing our bitmap
188 //Though I could not reproduce it on Windows 10
189 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
190 PixelFormat.Format32bppArgb);
191 iCreateBitmap = false;
193 //Minimize our window if desired
194 if (Properties.Settings.Default.StartMinimized)
196 WindowState = FormWindowState.Minimized;
204 /// <param name="sender"></param>
205 /// <param name="e"></param>
206 private void MainForm_Load(object sender, EventArgs e)
208 //Check if we are running a Click Once deployed application
209 if (ApplicationDeployment.IsNetworkDeployed)
211 //This is a proper Click Once installation, fetch and show our version number
212 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
216 //Not a proper Click Once installation, assuming development build then
217 this.Text += " - development";
221 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
222 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
223 UpdateAudioDeviceAndMasterVolumeThreadSafe();
226 iNetworkManager = new NetworkManager();
227 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
228 UpdateNetworkStatus();
231 iCecManager = new ConsumerElectronicControl();
232 OnWndProc += iCecManager.OnWndProc;
239 PopulateEventsTreeView();
241 //Setup notification icon
244 //Setup recording notification
245 SetupRecordingNotification();
247 // To make sure start up with minimize to tray works
248 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
254 //When not debugging we want the screen to be empty until a client takes over
257 //When developing we want at least one client for testing
258 StartNewClient("abcdefghijklmnopqrst-0123456789", "ABCDEFGHIJKLMNOPQRST-0123456789");
261 //Open display connection on start-up if needed
262 if (Properties.Settings.Default.DisplayConnectOnStartup)
264 OpenDisplayConnection();
267 //Start our server so that we can get client requests
270 //Register for HID events
271 RegisterHidDevices();
273 //Start Idle client if needed
274 if (Properties.Settings.Default.StartIdleClient)
281 /// Called when our display is opened.
283 /// <param name="aDisplay"></param>
284 private void OnDisplayOpened(Display aDisplay)
286 //Make sure we resume frame rendering
287 iSkipFrameRendering = false;
289 //Set our screen size now that our display is connected
290 //Our panelDisplay is the container of our tableLayoutPanel
291 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
292 //panelDisplay needs an extra 2 pixels for borders on each sides
293 //tableLayoutPanel will eventually be the exact size of our display
294 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
295 panelDisplay.Size = size;
297 //Our display was just opened, update our UI
299 //Initiate asynchronous request
300 iDisplay.RequestFirmwareRevision();
303 UpdateMasterVolumeThreadSafe();
305 UpdateNetworkStatus();
308 //Testing icon in debug, no arm done if icon not supported
309 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
310 //iDisplay.SetAllIconsStatus(2);
316 /// Populate tree view with events and actions
318 private void PopulateEventsTreeView()
320 //Disable action buttons
321 buttonActionAdd.Enabled = false;
322 buttonActionDelete.Enabled = false;
324 Ear.Event currentEvent = CurrentEvent();
325 Ear.Action currentAction = CurrentAction();
326 TreeNode treeNodeToSelect = null;
329 iTreeViewEvents.Nodes.Clear();
330 //Populate registered events
331 foreach (Ear.Event e in Properties.Settings.Default.EarManager.Events)
333 //Create our event node
334 TreeNode eventNode = iTreeViewEvents.Nodes.Add(e.Brief());
335 eventNode.Tag = e; //For easy access to our event
338 //Dim our nodes if disabled
339 eventNode.ForeColor = Color.DimGray;
342 //Add event description as child node
343 eventNode.Nodes.Add(e.Description).ForeColor = eventNode.ForeColor;
344 //Create child node for actions root
345 TreeNode actionsNodes = eventNode.Nodes.Add("Actions");
346 actionsNodes.ForeColor = eventNode.ForeColor;
348 // Add our actions for that event
349 foreach (Ear.Action a in e.Actions)
351 TreeNode actionNode = actionsNodes.Nodes.Add(a.Brief());
353 actionNode.ForeColor = eventNode.ForeColor;
354 if (a == currentAction)
356 treeNodeToSelect = actionNode;
361 iTreeViewEvents.ExpandAll();
362 SelectEvent(currentEvent);
364 if (treeNodeToSelect != null)
366 iTreeViewEvents.SelectedNode = treeNodeToSelect;
368 else if (iTreeViewEvents.SelectedNode != null && iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) > 0)
370 //Select the last action if any
371 iTreeViewEvents.SelectedNode =
372 iTreeViewEvents.SelectedNode.Nodes[1].Nodes[
373 iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) - 1];
375 else if (iTreeViewEvents.SelectedNode == null && iTreeViewEvents.Nodes.Count > 0)
377 //Still no selected node select the first one then
378 iTreeViewEvents.SelectedNode = iTreeViewEvents.Nodes[0];
386 /// Called when our display is closed.
388 /// <param name="aDisplay"></param>
389 private void OnDisplayClosed(Display aDisplay)
391 //Our display was just closed, update our UI consequently
395 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
397 //Update network status
398 UpdateNetworkStatus();
402 /// Update our Network Status
404 private void UpdateNetworkStatus()
406 if (iDisplay.IsOpen())
408 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet,
409 iNetworkManager.NetworkListManager.IsConnectedToInternet);
410 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
415 int iLastNetworkIconIndex = 0;
416 int iUpdateCountSinceLastNetworkAnimation = 0;
421 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
423 iUpdateCountSinceLastNetworkAnimation++;
424 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation%4;
426 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected &&
427 iUpdateCountSinceLastNetworkAnimation == 0)
429 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
432 //Prevents div by zero and other undefined behavior
435 iLastNetworkIconIndex++;
436 iLastNetworkIconIndex = iLastNetworkIconIndex%(iconCount*2);
437 for (int i = 0; i < iconCount; i++)
439 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) &&
440 !(i == 1 && iLastNetworkIconIndex > 4))
442 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
446 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
455 /// Receive volume change notification and reflect changes on our slider.
457 /// <param name="data"></param>
458 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
460 UpdateMasterVolumeThreadSafe();
464 /// Update master volume when user moves our slider.
466 /// <param name="sender"></param>
467 /// <param name="e"></param>
468 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
470 //Just like Windows Volume Mixer we unmute if the volume is adjusted
471 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
472 //Set volume level according to our volume slider new position
473 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value/100.0f;
478 /// Mute check box changed.
480 /// <param name="sender"></param>
481 /// <param name="e"></param>
482 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
484 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
488 /// Device State Changed
490 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId,
491 [MarshalAs(UnmanagedType.I4)] DeviceState newState)
498 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId)
505 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId)
510 /// Default Device Changed
512 public void OnDefaultDeviceChanged(DataFlow flow, Role role,
513 [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
515 if (role == Role.Multimedia && flow == DataFlow.Render)
517 UpdateAudioDeviceAndMasterVolumeThreadSafe();
522 /// Property Value Changed
524 /// <param name="pwstrDeviceId"></param>
525 /// <param name="key"></param>
526 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key)
534 /// Update master volume indicators based our current system states.
535 /// This typically includes volume levels and mute status.
537 private void UpdateMasterVolumeThreadSafe()
539 if (this.InvokeRequired)
541 //Not in the proper thread, invoke ourselves
542 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
543 this.Invoke(d, new object[] {});
547 //Update volume slider
548 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
549 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar*100);
550 //Update mute checkbox
551 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
553 //If our display connection is open we need to update its icons
554 if (iDisplay.IsOpen())
556 //First take care our our volume level icons
557 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
558 if (volumeIconCount > 0)
560 //Compute current volume level from system level and the number of segments in our display volume bar.
561 //That tells us how many segments in our volume bar needs to be turned on.
562 float currentVolume = volumeLevelScalar*volumeIconCount;
563 int segmentOnCount = Convert.ToInt32(currentVolume);
564 //Check if our segment count was rounded up, this will later be used for half brightness segment
565 bool roundedUp = segmentOnCount > currentVolume;
567 for (int i = 0; i < volumeIconCount; i++)
569 if (i < segmentOnCount)
571 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
572 if (i == segmentOnCount - 1 && roundedUp)
575 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
576 (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1)/2);
581 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
582 iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
587 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
592 //Take care of our mute icon
593 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
601 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
603 if (this.InvokeRequired)
605 //Not in the proper thread, invoke ourselves
606 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
607 this.Invoke(d, new object[] {});
611 //We are in the correct thread just go ahead.
614 //Get our master volume
615 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
617 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
619 //Show our volume in our track bar
620 UpdateMasterVolumeThreadSafe();
622 //Register to get volume modifications
623 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
625 trackBarMasterVolume.Enabled = true;
629 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
630 Debug.WriteLine(ex.ToString());
631 //Something went wrong S/PDIF device ca throw exception I guess
632 trackBarMasterVolume.Enabled = false;
639 private void PopulateDeviceTypes()
641 int count = Display.TypeCount();
643 for (int i = 0; i < count; i++)
645 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type) i));
652 private void PopulateOpticalDrives()
654 //Reset our list of drives
655 comboBoxOpticalDrives.Items.Clear();
656 comboBoxOpticalDrives.Items.Add("None");
658 //Go through each drives on our system and collected the optical ones in our list
659 DriveInfo[] allDrives = DriveInfo.GetDrives();
660 foreach (DriveInfo d in allDrives)
662 Debug.WriteLine("Drive " + d.Name);
663 Debug.WriteLine(" Drive type: {0}", d.DriveType);
665 if (d.DriveType == DriveType.CDRom)
667 //This is an optical drive, add it now
668 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0, 2));
676 /// <returns></returns>
677 public string OpticalDriveToEject()
679 return comboBoxOpticalDrives.SelectedItem.ToString();
687 private void SetupTrayIcon()
689 iNotifyIcon.Icon = GetIcon("vfd.ico");
690 iNotifyIcon.Text = "Sharp Display Manager";
691 iNotifyIcon.Visible = true;
693 //Double click toggles visibility - typically brings up the application
694 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
699 //Adding a context menu, useful to be able to exit the application
700 ContextMenu contextMenu = new ContextMenu();
701 //Context menu item to toggle visibility
702 MenuItem hideShowItem = new MenuItem("Hide/Show");
703 hideShowItem.Click += delegate(object obj, EventArgs args)
707 contextMenu.MenuItems.Add(hideShowItem);
709 //Context menu item separator
710 contextMenu.MenuItems.Add(new MenuItem("-"));
712 //Context menu exit item
713 MenuItem exitItem = new MenuItem("Exit");
714 exitItem.Click += delegate(object obj, EventArgs args)
718 contextMenu.MenuItems.Add(exitItem);
720 iNotifyIcon.ContextMenu = contextMenu;
726 private void SetupRecordingNotification()
728 iRecordingNotification.Icon = GetIcon("record.ico");
729 iRecordingNotification.Text = "No recording";
730 iRecordingNotification.Visible = false;
734 /// Access icons from embedded resources.
736 /// <param name="aName"></param>
737 /// <returns></returns>
738 public static Icon GetIcon(string aName)
740 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
741 foreach (string name in names)
743 //Find a resource name that ends with the given name
744 if (name.EndsWith(aName))
746 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
748 return new Icon(stream);
758 /// Set our current client.
759 /// This will take care of applying our client layout and set data fields.
761 /// <param name="aSessionId"></param>
762 void SetCurrentClient(string aSessionId, bool aForce = false)
764 if (aSessionId == iCurrentClientSessionId)
766 //Given client is already the current one.
767 //Don't bother changing anything then.
771 ClientData requestedClientData = iClients[aSessionId];
773 //Check when was the last time we switched to that client
774 if (iCurrentClientData != null)
776 //Do not switch client if priority of current client is higher
777 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
783 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
784 //TODO: put that hard coded value as a client property
785 //Clients should be able to define how often they can be interrupted
786 //Thus a background client can set this to zero allowing any other client to interrupt at any time
787 //We could also compute this delay by looking at the requests frequencies?
789 requestedClientData.Priority == iCurrentClientData.Priority &&
790 //Time sharing is only if clients have the same priority
791 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
793 //Don't switch clients too often
798 //Set current client ID.
799 iCurrentClientSessionId = aSessionId;
800 //Set the time we last switched to that client
801 iClients[aSessionId].LastSwitchTime = DateTime.Now;
802 //Fetch and set current client data.
803 iCurrentClientData = requestedClientData;
804 //Apply layout and set data fields.
805 UpdateTableLayoutPanel(iCurrentClientData);
808 private void buttonFont_Click(object sender, EventArgs e)
810 //fontDialog.ShowColor = true;
811 //fontDialog.ShowApply = true;
812 fontDialog.ShowEffects = true;
813 fontDialog.Font = cds.Font;
815 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
817 //fontDialog.ShowHelp = true;
819 //fontDlg.MaxSize = 40;
820 //fontDlg.MinSize = 22;
822 //fontDialog.Parent = this;
823 //fontDialog.StartPosition = FormStartPosition.CenterParent;
825 //DlgBox.ShowDialog(fontDialog);
827 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
828 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
830 //Set the fonts to all our labels in our layout
831 foreach (Control ctrl in iTableLayoutPanel.Controls)
833 if (ctrl is MarqueeLabel)
835 ((MarqueeLabel) ctrl).Font = fontDialog.Font;
840 cds.Font = fontDialog.Font;
841 Properties.Settings.Default.Save();
850 void CheckFontHeight()
852 //Show font height and width
853 labelFontHeight.Text = "Font height: " + cds.Font.Height;
854 float charWidth = IsFixedWidth(cds.Font);
855 if (charWidth == 0.0f)
857 labelFontWidth.Visible = false;
861 labelFontWidth.Visible = true;
862 labelFontWidth.Text = "Font width: " + charWidth;
865 MarqueeLabel label = null;
866 //Get the first label control we can find
867 foreach (Control ctrl in iTableLayoutPanel.Controls)
869 if (ctrl is MarqueeLabel)
871 label = (MarqueeLabel) ctrl;
876 //Now check font height and show a warning if needed.
877 if (label != null && label.Font.Height > label.Height)
879 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) +
881 labelWarning.Visible = true;
885 labelWarning.Visible = false;
890 private void buttonCapture_Click(object sender, EventArgs e)
892 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
893 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
894 //Bitmap bmpToSave = new Bitmap(bmp);
895 bmp.Save("D:\\capture.png");
897 ((MarqueeLabel) iTableLayoutPanel.Controls[0]).Text = "Captured";
900 string outputFileName = "d:\\capture.png";
901 using (MemoryStream memory = new MemoryStream())
903 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
905 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
906 byte[] bytes = memory.ToArray();
907 fs.Write(bytes, 0, bytes.Length);
914 private void CheckForRequestResults()
916 if (iDisplay.IsRequestPending())
918 switch (iDisplay.AttemptRequestCompletion())
920 case MiniDisplay.Request.FirmwareRevision:
921 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
922 //Issue next request then
923 iDisplay.RequestPowerSupplyStatus();
926 case MiniDisplay.Request.PowerSupplyStatus:
927 if (iDisplay.PowerSupplyStatus())
929 toolStripStatusLabelPower.Text = "ON";
933 toolStripStatusLabelPower.Text = "OFF";
935 //Issue next request then
936 iDisplay.RequestDeviceId();
939 case MiniDisplay.Request.DeviceId:
940 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
941 //No more request to issue
947 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
949 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
956 public static uint ColorUntouched(int aX, int aY, uint aPixel)
961 public static uint ColorInversed(int aX, int aY, uint aPixel)
966 public static uint ColorChessboard(int aX, int aY, uint aPixel)
968 if ((aX%2 == 0) && (aY%2 == 0))
972 else if ((aX%2 != 0) && (aY%2 != 0))
980 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
982 return aBmp.Width - aX - 1;
985 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
987 return iBmp.Height - aY - 1;
990 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
995 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
1001 /// Select proper pixel delegates according to our current settings.
1003 private void SetupPixelDelegates()
1005 //Select our pixel processing routine
1006 if (cds.InverseColors)
1008 //iColorFx = ColorChessboard;
1009 iColorFx = ColorInversed;
1013 iColorFx = ColorWhiteIsOn;
1016 //Select proper coordinate translation functions
1017 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
1018 if (cds.ReverseScreen)
1020 iScreenX = ScreenReversedX;
1021 iScreenY = ScreenReversedY;
1031 //This is our timer tick responsible to perform our render
1032 private void timer_Tick(object sender, EventArgs e)
1034 //Not ideal cause this has nothing to do with display render
1037 //Update our animations
1038 DateTime NewTickTime = DateTime.Now;
1040 UpdateNetworkSignal(LastTickTime, NewTickTime);
1042 //Update animation for all our marquees
1043 foreach (Control ctrl in iTableLayoutPanel.Controls)
1045 if (ctrl is MarqueeLabel)
1047 ((MarqueeLabel) ctrl).UpdateAnimation(LastTickTime, NewTickTime);
1051 //Update our display
1052 if (iDisplay.IsOpen())
1054 CheckForRequestResults();
1056 //Check if frame rendering is needed
1057 //Typically used when showing clock
1058 if (!iSkipFrameRendering)
1063 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
1064 PixelFormat.Format32bppArgb);
1065 iCreateBitmap = false;
1067 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
1068 //iBmp.Save("D:\\capture.png");
1070 //Send it to our display
1071 for (int i = 0; i < iBmp.Width; i++)
1073 for (int j = 0; j < iBmp.Height; j++)
1077 //Get our processed pixel coordinates
1078 int x = iScreenX(iBmp, i);
1079 int y = iScreenY(iBmp, j);
1081 uint color = (uint) iBmp.GetPixel(i, j).ToArgb();
1082 //Apply color effects
1083 color = iColorFx(x, y, color);
1085 iDisplay.SetPixel(x, y, color);
1090 iDisplay.SwapBuffers();
1094 //Compute instant FPS
1095 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " +
1096 (1000/timer.Interval).ToString() + " FPS";
1098 LastTickTime = NewTickTime;
1103 /// Attempt to establish connection with our display hardware.
1105 private void OpenDisplayConnection()
1107 CloseDisplayConnection();
1109 if (!iDisplay.Open((MiniDisplay.Type) cds.DisplayType))
1112 toolStripStatusLabelConnect.Text = "Connection error";
1116 private void CloseDisplayConnection()
1118 //Status will be updated upon receiving the closed event
1120 if (iDisplay == null || !iDisplay.IsOpen())
1125 //Do not clear if we gave up on rendering already.
1126 //This means we will keep on displaying clock on MDM166AA for instance.
1127 if (!iSkipFrameRendering)
1130 iDisplay.SwapBuffers();
1133 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1137 private void buttonOpen_Click(object sender, EventArgs e)
1139 OpenDisplayConnection();
1142 private void buttonClose_Click(object sender, EventArgs e)
1144 CloseDisplayConnection();
1147 private void buttonClear_Click(object sender, EventArgs e)
1150 iDisplay.SwapBuffers();
1153 private void buttonFill_Click(object sender, EventArgs e)
1156 iDisplay.SwapBuffers();
1159 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1161 cds.Brightness = trackBarBrightness.Value;
1162 Properties.Settings.Default.Save();
1163 iDisplay.SetBrightness(trackBarBrightness.Value);
1169 /// CDS stands for Current Display Settings
1171 private DisplaySettings cds
1175 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1176 if (settings == null)
1178 settings = new DisplaysSettings();
1180 Properties.Settings.Default.DisplaysSettings = settings;
1183 //Make sure all our settings have been created
1184 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1186 settings.Displays.Add(new DisplaySettings());
1189 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1190 return displaySettings;
1195 /// Check if the given font has a fixed character pitch.
1197 /// <param name="ft"></param>
1198 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1199 public float IsFixedWidth(Font ft)
1201 Graphics g = CreateGraphics();
1202 char[] charSizes = new char[] {'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.'};
1203 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1205 bool fixedWidth = true;
1207 foreach (char c in charSizes)
1208 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width !=
1221 /// Synchronize UI with settings
1223 private void UpdateStatus()
1226 checkBoxShowBorders.Checked = cds.ShowBorders;
1227 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders
1228 ? TableLayoutPanelCellBorderStyle.Single
1229 : TableLayoutPanelCellBorderStyle.None);
1231 //Set the proper font to each of our labels
1232 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1234 ctrl.Font = cds.Font;
1238 //Check if "run on Windows startup" is enabled
1239 checkBoxAutoStart.Checked = iStartupManager.Startup;
1241 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1244 //Try find our drive in our drive list
1245 int opticalDriveItemIndex = 0;
1246 bool driveNotFound = true;
1247 string opticalDriveToEject = Properties.Settings.Default.OpticalDriveToEject;
1248 foreach (object item in comboBoxOpticalDrives.Items)
1250 if (opticalDriveToEject == item.ToString())
1252 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1253 driveNotFound = false;
1256 opticalDriveItemIndex++;
1261 //We could not find the drive we had saved.
1262 //Select "None" then.
1263 comboBoxOpticalDrives.SelectedIndex = 0;
1267 iTextBoxHarmonyHubAddress.Text = Properties.Settings.Default.HarmonyHubAddress;
1270 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1272 //Mini Display settings
1273 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1274 checkBoxInverseColors.Checked = cds.InverseColors;
1275 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1276 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1277 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1278 labelMinFontSize.Enabled = cds.ScaleToFit;
1279 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1280 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1281 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1282 timer.Interval = cds.TimerInterval;
1283 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1284 textBoxScrollLoopSeparator.Text = cds.Separator;
1286 SetupPixelDelegates();
1288 if (iDisplay.IsOpen())
1290 //We have a display connection
1291 //Reflect that in our UI
1294 iTableLayoutPanel.Enabled = true;
1295 panelDisplay.Enabled = true;
1297 //Only setup brightness if display is open
1298 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1299 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1300 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1302 //Brightness out of range, this can occur when using auto-detect
1303 //Use max brightness instead
1304 trackBarBrightness.Value = iDisplay.MaxBrightness();
1305 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1309 trackBarBrightness.Value = cds.Brightness;
1310 iDisplay.SetBrightness(cds.Brightness);
1313 //Try compute the steps to something that makes sense
1314 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness())/5);
1315 trackBarBrightness.SmallChange = 1;
1318 buttonFill.Enabled = true;
1319 buttonClear.Enabled = true;
1320 buttonOpen.Enabled = false;
1321 buttonClose.Enabled = true;
1322 trackBarBrightness.Enabled = true;
1323 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1324 //+ " - " + iDisplay.SerialNumber();
1326 if (iDisplay.SupportPowerOnOff())
1328 buttonPowerOn.Enabled = true;
1329 buttonPowerOff.Enabled = true;
1333 buttonPowerOn.Enabled = false;
1334 buttonPowerOff.Enabled = false;
1337 if (iDisplay.SupportClock())
1339 buttonShowClock.Enabled = true;
1340 buttonHideClock.Enabled = true;
1344 buttonShowClock.Enabled = false;
1345 buttonHideClock.Enabled = false;
1349 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1350 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel) > 0;
1352 if (cds.ShowVolumeLabel)
1354 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1358 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1363 //Display connection not available
1364 //Reflect that in our UI
1366 //In debug start our timer even if we don't have a display connection
1369 //In production environment we don't need our timer if no display connection
1372 checkBoxShowVolumeLabel.Enabled = false;
1373 iTableLayoutPanel.Enabled = false;
1374 panelDisplay.Enabled = false;
1375 buttonFill.Enabled = false;
1376 buttonClear.Enabled = false;
1377 buttonOpen.Enabled = true;
1378 buttonClose.Enabled = false;
1379 trackBarBrightness.Enabled = false;
1380 buttonPowerOn.Enabled = false;
1381 buttonPowerOff.Enabled = false;
1382 buttonShowClock.Enabled = false;
1383 buttonHideClock.Enabled = false;
1384 toolStripStatusLabelConnect.Text = "Disconnected";
1385 toolStripStatusLabelPower.Text = "N/A";
1394 /// <param name="sender"></param>
1395 /// <param name="e"></param>
1396 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1398 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1399 Properties.Settings.Default.Save();
1403 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1405 //Save our show borders setting
1406 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked
1407 ? TableLayoutPanelCellBorderStyle.Single
1408 : TableLayoutPanelCellBorderStyle.None);
1409 cds.ShowBorders = checkBoxShowBorders.Checked;
1410 Properties.Settings.Default.Save();
1414 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1416 iStartupManager.Startup = checkBoxAutoStart.Checked;
1420 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1422 //Save our reverse screen setting
1423 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1424 Properties.Settings.Default.Save();
1425 SetupPixelDelegates();
1428 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1430 //Save our inverse colors setting
1431 cds.InverseColors = checkBoxInverseColors.Checked;
1432 Properties.Settings.Default.Save();
1433 SetupPixelDelegates();
1436 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1438 //Save our scale to fit setting
1439 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1440 Properties.Settings.Default.Save();
1442 labelMinFontSize.Enabled = cds.ScaleToFit;
1443 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1446 private void MainForm_Resize(object sender, EventArgs e)
1448 if (WindowState == FormWindowState.Minimized)
1450 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1451 // That's apparently not needed on Windows 10 but we better leave it in place.
1452 iCreateBitmap = true;
1456 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1459 iNetworkManager.Dispose();
1460 CloseDisplayConnection();
1462 e.Cancel = iClosing;
1465 public void StartServer()
1467 iServiceHost = new ServiceHost
1470 new Uri[] {new Uri("net.tcp://localhost:8001/")}
1473 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true),
1475 iServiceHost.Open();
1478 public void StopServer()
1480 if (iClients.Count > 0 && !iClosing)
1484 BroadcastCloseEvent();
1489 MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo,
1490 MessageBoxIcon.Warning) == DialogResult.Yes)
1492 iClosing = false; //We make sure we force close if asked twice
1497 //We removed that as it often lags for some reason
1498 //iServiceHost.Close();
1502 public void BroadcastCloseEvent()
1504 Trace.TraceInformation("BroadcastCloseEvent - start");
1506 var inactiveClients = new List<string>();
1507 foreach (var client in iClients)
1509 //if (client.Key != eventData.ClientName)
1513 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1514 client.Value.Callback.OnCloseOrder( /*eventData*/);
1516 catch (Exception ex)
1518 inactiveClients.Add(client.Key);
1523 if (inactiveClients.Count > 0)
1525 foreach (var client in inactiveClients)
1527 iClients.Remove(client);
1528 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1529 Program.iFormMain.iTreeViewClients.Nodes.Find(client, false)[0]);
1533 if (iClients.Count == 0)
1540 /// Just remove all our fields.
1542 private void ClearLayout()
1544 iTableLayoutPanel.Controls.Clear();
1545 iTableLayoutPanel.RowStyles.Clear();
1546 iTableLayoutPanel.ColumnStyles.Clear();
1547 iCurrentClientData = null;
1551 /// Just launch a demo client.
1553 private void StartNewClient(string aTopText = "", string aBottomText = "")
1555 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1556 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(
1557 new Point(this.Right, this.Top), aTopText, aBottomText);
1558 clientThread.Start(myParams);
1563 /// Just launch our idle client.
1565 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1567 Thread clientThread = new Thread(SharpDisplayClientIdle.Program.MainWithParams);
1568 SharpDisplayClientIdle.StartParams myParams =
1569 new SharpDisplayClientIdle.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1570 clientThread.Start(myParams);
1575 private void buttonStartClient_Click(object sender, EventArgs e)
1580 private void buttonSuspend_Click(object sender, EventArgs e)
1585 private void StartTimer()
1587 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1588 timer.Enabled = true;
1589 UpdateSuspendButton();
1592 private void StopTimer()
1594 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1595 timer.Enabled = false;
1596 UpdateSuspendButton();
1599 private void ToggleTimer()
1601 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1602 timer.Enabled = !timer.Enabled;
1603 UpdateSuspendButton();
1606 private void UpdateSuspendButton()
1610 buttonSuspend.Text = "Run";
1614 buttonSuspend.Text = "Pause";
1619 private void buttonCloseClients_Click(object sender, EventArgs e)
1621 BroadcastCloseEvent();
1624 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1626 //Root node must have at least one child
1627 if (e.Node.Nodes.Count == 0)
1632 //If the selected node is the root node of a client then switch to it
1633 string sessionId = e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1634 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1636 //We have a valid session just switch to that client
1637 SetCurrentClient(sessionId, true);
1646 /// <param name="aSessionId"></param>
1647 /// <param name="aCallback"></param>
1648 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1650 if (this.InvokeRequired)
1652 //Not in the proper thread, invoke ourselves
1653 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1654 this.Invoke(d, new object[] {aSessionId, aCallback});
1658 //We are in the proper thread
1659 //Add this session to our collection of clients
1660 ClientData newClient = new ClientData(aSessionId, aCallback);
1661 Program.iFormMain.iClients.Add(aSessionId, newClient);
1662 //Add this session to our UI
1663 UpdateClientTreeViewNode(newClient);
1669 /// Find the client with the highest priority if any.
1671 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1672 public ClientData FindHighestPriorityClient()
1674 ClientData highestPriorityClient = null;
1675 foreach (var client in iClients)
1677 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1679 highestPriorityClient = client.Value;
1683 return highestPriorityClient;
1689 /// <param name="aSessionId"></param>
1690 public void RemoveClientThreadSafe(string aSessionId)
1692 if (this.InvokeRequired)
1694 //Not in the proper thread, invoke ourselves
1695 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1696 this.Invoke(d, new object[] {aSessionId});
1700 //We are in the proper thread
1701 //Remove this session from both client collection and UI tree view
1702 if (Program.iFormMain.iClients.Keys.Contains(aSessionId))
1704 Program.iFormMain.iClients.Remove(aSessionId);
1705 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1706 Program.iFormMain.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1707 //Update recording status too whenever a client is removed
1708 UpdateRecordingNotification();
1711 if (iCurrentClientSessionId == aSessionId)
1713 //The current client is closing
1714 iCurrentClientData = null;
1715 //Find the client with the highest priority and set it as current
1716 ClientData newCurrentClient = FindHighestPriorityClient();
1717 if (newCurrentClient != null)
1719 SetCurrentClient(newCurrentClient.SessionId, true);
1723 if (iClients.Count == 0)
1725 //Clear our screen when last client disconnects
1730 //We were closing our form
1731 //All clients are now closed
1732 //Just resume our close operation
1743 /// <param name="aSessionId"></param>
1744 /// <param name="aLayout"></param>
1745 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1747 if (this.InvokeRequired)
1749 //Not in the proper thread, invoke ourselves
1750 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1751 this.Invoke(d, new object[] {aSessionId, aLayout});
1755 ClientData client = iClients[aSessionId];
1758 //Don't change a thing if the layout is the same
1759 if (!client.Layout.IsSameAs(aLayout))
1761 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1762 //Set our client layout then
1763 client.Layout = aLayout;
1764 //So that next time we update all our fields at ones
1765 client.HasNewLayout = true;
1766 //Layout has changed clear our fields then
1767 client.Fields.Clear();
1769 UpdateClientTreeViewNode(client);
1773 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1782 /// <param name="aSessionId"></param>
1783 /// <param name="aField"></param>
1784 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1786 if (this.InvokeRequired)
1788 //Not in the proper thread, invoke ourselves
1789 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1790 this.Invoke(d, new object[] {aSessionId, aField});
1794 //We are in the proper thread
1795 //Call the non-thread-safe variant
1796 SetClientField(aSessionId, aField);
1804 /// Set a data field in the given client.
1806 /// <param name="aSessionId"></param>
1807 /// <param name="aField"></param>
1808 private void SetClientField(string aSessionId, DataField aField)
1810 //TODO: should check if the field actually changed?
1812 ClientData client = iClients[aSessionId];
1813 bool layoutChanged = false;
1814 bool contentChanged = true;
1816 //Fetch our field index
1817 int fieldIndex = client.FindSameFieldIndex(aField);
1821 //No corresponding field, just bail out
1825 //Keep our previous field in there
1826 DataField previousField = client.Fields[fieldIndex];
1827 //Just update that field then
1828 client.Fields[fieldIndex] = aField;
1830 if (!aField.IsTableField)
1832 //We are done then if that field is not in our table layout
1836 TableField tableField = (TableField) aField;
1838 if (previousField.IsSameLayout(aField))
1840 //If we are updating a field in our current client we need to update it in our panel
1841 if (aSessionId == iCurrentClientSessionId)
1843 Control ctrl = iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1844 if (aField.IsTextField && ctrl is MarqueeLabel)
1846 TextField textField = (TextField) aField;
1847 //Text field control already in place, just change the text
1848 MarqueeLabel label = (MarqueeLabel) ctrl;
1849 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1850 label.Text = textField.Text;
1851 label.TextAlign = textField.Alignment;
1853 else if (aField.IsBitmapField && ctrl is PictureBox)
1855 BitmapField bitmapField = (BitmapField) aField;
1856 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1857 //Bitmap field control already in place just change the bitmap
1858 PictureBox pictureBox = (PictureBox) ctrl;
1859 pictureBox.Image = bitmapField.Bitmap;
1863 layoutChanged = true;
1869 layoutChanged = true;
1872 //If either content or layout changed we need to update our tree view to reflect the changes
1873 if (contentChanged || layoutChanged)
1875 UpdateClientTreeViewNode(client);
1879 Debug.Print("Layout changed");
1880 //Our layout has changed, if we are already the current client we need to update our panel
1881 if (aSessionId == iCurrentClientSessionId)
1883 //Apply layout and set data fields.
1884 UpdateTableLayoutPanel(iCurrentClientData);
1889 Debug.Print("Layout has not changed.");
1894 Debug.Print("WARNING: content and layout have not changed!");
1897 //When a client field is set we try switching to this client to present the new information to our user
1898 SetCurrentClient(aSessionId);
1904 /// <param name="aTexts"></param>
1905 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1907 if (this.InvokeRequired)
1909 //Not in the proper thread, invoke ourselves
1910 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1911 this.Invoke(d, new object[] {aSessionId, aFields});
1915 ClientData client = iClients[aSessionId];
1917 if (client.HasNewLayout)
1919 //TODO: Assert client.Count == 0
1920 //Our layout was just changed
1921 //Do some special handling to avoid re-creating our panel N times, once for each fields
1922 client.HasNewLayout = false;
1923 //Just set all our fields then
1924 client.Fields.AddRange(aFields);
1925 //Try switch to that client
1926 SetCurrentClient(aSessionId);
1928 //If we are updating the current client update our panel
1929 if (aSessionId == iCurrentClientSessionId)
1931 //Apply layout and set data fields.
1932 UpdateTableLayoutPanel(iCurrentClientData);
1935 UpdateClientTreeViewNode(client);
1939 //Put each our text fields in a label control
1940 foreach (DataField field in aFields)
1942 SetClientField(aSessionId, field);
1951 /// <param name="aSessionId"></param>
1952 /// <param name="aName"></param>
1953 public void SetClientNameThreadSafe(string aSessionId, string aName)
1955 if (this.InvokeRequired)
1957 //Not in the proper thread, invoke ourselves
1958 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1959 this.Invoke(d, new object[] {aSessionId, aName});
1963 //We are in the proper thread
1965 ClientData client = iClients[aSessionId];
1969 client.Name = aName;
1970 //Update our tree-view
1971 UpdateClientTreeViewNode(client);
1977 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
1979 if (this.InvokeRequired)
1981 //Not in the proper thread, invoke ourselves
1982 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
1983 this.Invoke(d, new object[] {aSessionId, aPriority});
1987 //We are in the proper thread
1989 ClientData client = iClients[aSessionId];
1993 client.Priority = aPriority;
1994 //Update our tree-view
1995 UpdateClientTreeViewNode(client);
1996 //Change our current client as per new priority
1997 ClientData newCurrentClient = FindHighestPriorityClient();
1998 if (newCurrentClient != null)
2000 SetCurrentClient(newCurrentClient.SessionId);
2009 /// <param name="value"></param>
2010 /// <param name="maxChars"></param>
2011 /// <returns></returns>
2012 public static string Truncate(string value, int maxChars)
2014 return value.Length <= maxChars ? value : value.Substring(0, maxChars - 3) + "...";
2018 /// Update our recording notification.
2020 private void UpdateRecordingNotification()
2023 bool activeRecording = false;
2025 RecordingField recField = new RecordingField();
2026 foreach (var client in iClients)
2028 RecordingField rec = (RecordingField) client.Value.FindSameFieldAs(recField);
2029 if (rec != null && rec.IsActive)
2031 activeRecording = true;
2032 //Don't break cause we are collecting the names/texts.
2033 if (!String.IsNullOrEmpty(rec.Text))
2035 text += (rec.Text + "\n");
2039 //Not text for that recording, use client name instead
2040 text += client.Value.Name + " recording\n";
2046 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
2047 iRecordingNotification.Text = Truncate(text, 63);
2049 //Change visibility of notification if needed
2050 if (iRecordingNotification.Visible != activeRecording)
2052 iRecordingNotification.Visible = activeRecording;
2053 //Assuming the notification icon is in sync with our display icon
2054 //Take care of our REC icon
2055 if (iDisplay.IsOpen())
2057 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
2065 /// <param name="aClient"></param>
2066 private void UpdateClientTreeViewNode(ClientData aClient)
2068 Debug.Print("UpdateClientTreeViewNode");
2070 if (aClient == null)
2075 //Hook in record icon update too
2076 UpdateRecordingNotification();
2078 TreeNode node = null;
2079 //Check that our client node already exists
2080 //Get our client root node using its key which is our session ID
2081 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
2082 if (nodes.Count() > 0)
2084 //We already have a node for that client
2086 //Clear children as we are going to recreate them below
2091 //Client node does not exists create a new one
2092 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
2093 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
2099 if (!String.IsNullOrEmpty(aClient.Name))
2101 //We have a name, use it as text for our root node
2102 node.Text = aClient.Name;
2103 //Add a child with SessionId
2104 node.Nodes.Add(new TreeNode(aClient.SessionId));
2108 //No name, use session ID instead
2109 node.Text = aClient.SessionId;
2112 //Display client priority
2113 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2115 if (aClient.Fields.Count > 0)
2117 //Create root node for our texts
2118 TreeNode textsRoot = new TreeNode("Fields");
2119 node.Nodes.Add(textsRoot);
2120 //For each text add a new entry
2121 foreach (DataField field in aClient.Fields)
2123 if (field.IsTextField)
2125 TextField textField = (TextField) field;
2126 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2128 else if (field.IsBitmapField)
2130 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2132 else if (field.IsRecordingField)
2134 RecordingField recordingField = (RecordingField) field;
2135 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2145 /// Update our table layout row styles to make sure each rows have similar height
2147 private void UpdateTableLayoutRowStyles()
2149 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2151 rowStyle.SizeType = SizeType.Percent;
2152 rowStyle.Height = 100/iTableLayoutPanel.RowCount;
2157 /// Update our display table layout.
2158 /// Will instanciated every field control as defined by our client.
2159 /// Fields must be specified by rows from the left.
2161 /// <param name="aLayout"></param>
2162 private void UpdateTableLayoutPanel(ClientData aClient)
2164 Debug.Print("UpdateTableLayoutPanel");
2166 if (aClient == null)
2173 TableLayout layout = aClient.Layout;
2175 //First clean our current panel
2176 iTableLayoutPanel.Controls.Clear();
2177 iTableLayoutPanel.RowStyles.Clear();
2178 iTableLayoutPanel.ColumnStyles.Clear();
2179 iTableLayoutPanel.RowCount = 0;
2180 iTableLayoutPanel.ColumnCount = 0;
2182 //Then recreate our rows...
2183 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2185 iTableLayoutPanel.RowCount++;
2189 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2191 iTableLayoutPanel.ColumnCount++;
2195 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2197 //Create our column styles
2198 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2201 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2205 //Create our row styles
2206 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2216 foreach (DataField field in aClient.Fields)
2218 if (!field.IsTableField)
2220 //That field is not taking part in our table layout skip it
2224 TableField tableField = (TableField) field;
2226 //Create a control corresponding to the field specified for that cell
2227 Control control = CreateControlForDataField(tableField);
2229 //Add newly created control to our table layout at the specified row and column
2230 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2231 //Make sure we specify column and row span for that new control
2232 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2233 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2241 /// Check our type of data field and create corresponding control
2243 /// <param name="aField"></param>
2244 private Control CreateControlForDataField(DataField aField)
2246 Control control = null;
2247 if (aField.IsTextField)
2249 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2250 label.AutoEllipsis = true;
2251 label.AutoSize = true;
2252 label.BackColor = System.Drawing.Color.Transparent;
2253 label.Dock = System.Windows.Forms.DockStyle.Fill;
2254 label.Location = new System.Drawing.Point(1, 1);
2255 label.Margin = new System.Windows.Forms.Padding(0);
2256 label.Name = "marqueeLabel" + aField;
2257 label.OwnTimer = false;
2258 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2259 label.Separator = cds.Separator;
2260 label.MinFontSize = cds.MinFontSize;
2261 label.ScaleToFit = cds.ScaleToFit;
2262 //control.Size = new System.Drawing.Size(254, 30);
2263 //control.TabIndex = 2;
2264 label.Font = cds.Font;
2266 TextField field = (TextField) aField;
2267 label.TextAlign = field.Alignment;
2268 label.UseCompatibleTextRendering = true;
2269 label.Text = field.Text;
2273 else if (aField.IsBitmapField)
2275 //Create picture box
2276 PictureBox picture = new PictureBox();
2277 picture.AutoSize = true;
2278 picture.BackColor = System.Drawing.Color.Transparent;
2279 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2280 picture.Location = new System.Drawing.Point(1, 1);
2281 picture.Margin = new System.Windows.Forms.Padding(0);
2282 picture.Name = "pictureBox" + aField;
2284 BitmapField field = (BitmapField) aField;
2285 picture.Image = field.Bitmap;
2289 //TODO: Handle recording field?
2295 /// Called when the user selected a new display type.
2297 /// <param name="sender"></param>
2298 /// <param name="e"></param>
2299 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2301 //Store the selected display type in our settings
2302 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2303 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2304 Properties.Settings.Default.Save();
2306 //Try re-opening the display connection if we were already connected.
2307 //Otherwise just update our status to reflect display type change.
2308 if (iDisplay.IsOpen())
2310 OpenDisplayConnection();
2318 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2320 if (maskedTextBoxTimerInterval.Text != "")
2322 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2326 timer.Interval = interval;
2327 cds.TimerInterval = timer.Interval;
2328 Properties.Settings.Default.Save();
2333 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2335 if (maskedTextBoxMinFontSize.Text != "")
2337 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2339 if (minFontSize > 0)
2341 cds.MinFontSize = minFontSize;
2342 Properties.Settings.Default.Save();
2343 //We need to recreate our layout for that change to take effect
2344 UpdateTableLayoutPanel(iCurrentClientData);
2350 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2352 if (maskedTextBoxScrollingSpeed.Text != "")
2354 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2356 if (scrollingSpeed > 0)
2358 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2359 Properties.Settings.Default.Save();
2360 //We need to recreate our layout for that change to take effect
2361 UpdateTableLayoutPanel(iCurrentClientData);
2366 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2368 cds.Separator = textBoxScrollLoopSeparator.Text;
2369 Properties.Settings.Default.Save();
2371 //Update our text fields
2372 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2374 ctrl.Separator = cds.Separator;
2379 private void buttonPowerOn_Click(object sender, EventArgs e)
2384 private void buttonPowerOff_Click(object sender, EventArgs e)
2386 iDisplay.PowerOff();
2389 private void buttonShowClock_Click(object sender, EventArgs e)
2394 private void buttonHideClock_Click(object sender, EventArgs e)
2399 private void buttonUpdate_Click(object sender, EventArgs e)
2401 InstallUpdateSyncWithInfo();
2409 if (!iDisplay.IsOpen())
2414 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2415 iSkipFrameRendering = true;
2418 iDisplay.SwapBuffers();
2419 //Then show our clock
2420 iDisplay.ShowClock();
2428 if (!iDisplay.IsOpen())
2433 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2434 iSkipFrameRendering = false;
2435 iDisplay.HideClock();
2438 private void InstallUpdateSyncWithInfo()
2440 UpdateCheckInfo info = null;
2442 if (ApplicationDeployment.IsNetworkDeployed)
2444 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2448 info = ad.CheckForDetailedUpdate();
2451 catch (DeploymentDownloadException dde)
2454 "The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " +
2458 catch (InvalidDeploymentException ide)
2461 "Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " +
2465 catch (InvalidOperationException ioe)
2468 "This application cannot be updated. It is likely not a ClickOnce application. Error: " +
2473 if (info.UpdateAvailable)
2475 Boolean doUpdate = true;
2477 if (!info.IsUpdateRequired)
2480 MessageBox.Show("An update is available. Would you like to update the application now?",
2481 "Update Available", MessageBoxButtons.OKCancel);
2482 if (!(DialogResult.OK == dr))
2489 // Display a message that the application MUST reboot. Display the minimum required version.
2490 MessageBox.Show("This application has detected a mandatory update from your current " +
2491 "version to version " + info.MinimumRequiredVersion.ToString() +
2492 ". The application will now install the update and restart.",
2493 "Update Available", MessageBoxButtons.OK,
2494 MessageBoxIcon.Information);
2502 MessageBox.Show("The application has been upgraded, and will now restart.");
2503 Application.Restart();
2505 catch (DeploymentDownloadException dde)
2508 "Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " +
2516 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2525 private void SysTrayHideShow()
2531 WindowState = FormWindowState.Normal;
2536 /// Use to handle minimize events.
2538 /// <param name="sender"></param>
2539 /// <param name="e"></param>
2540 private void MainForm_SizeChanged(object sender, EventArgs e)
2542 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2554 /// <param name="sender"></param>
2555 /// <param name="e"></param>
2556 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2558 //Our table layout size has changed which means our display size has changed.
2559 //We need to re-create our bitmap.
2560 iCreateBitmap = true;
2566 /// <param name="sender"></param>
2567 /// <param name="e"></param>
2568 private void buttonSelectFile_Click(object sender, EventArgs e)
2570 //openFileDialog1.InitialDirectory = "c:\\";
2571 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2572 //openFileDialog.FilterIndex = 1;
2573 openFileDialog.RestoreDirectory = true;
2575 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2577 labelStartFileName.Text = openFileDialog.FileName;
2578 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2579 Properties.Settings.Default.Save();
2586 /// <param name="sender"></param>
2587 /// <param name="e"></param>
2588 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2590 //Save the optical drive the user selected for ejection
2591 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2592 Properties.Settings.Default.Save();
2599 private void LogsUpdate()
2601 if (iWriter != null)
2609 /// Broadcast messages to subscribers.
2611 /// <param name="message"></param>
2612 protected override void WndProc(ref Message aMessage)
2616 if (OnWndProc != null)
2618 OnWndProc(ref aMessage);
2621 base.WndProc(ref aMessage);
2624 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2630 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2632 //Save CEC HDMI port
2633 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2634 Properties.Settings.Default.CecHdmiPort++;
2635 Properties.Settings.Default.Save();
2643 private void ResetCec()
2645 if (iCecManager == null)
2647 //Thus skipping initial UI setup
2653 if (Properties.Settings.Default.CecEnabled)
2655 iCecManager.Start(Handle, "CEC",
2656 Properties.Settings.Default.CecHdmiPort);
2665 private async void ResetHarmony()
2667 // ConnectAsync already if we have an existing session cookie
2668 if (Properties.Settings.Default.HarmonyEnabled && File.Exists("SessionToken"))
2671 iButtonHarmonyConnect.Enabled = false;
2674 await ConnectHarmonyAsync();
2678 iButtonHarmonyConnect.Enabled = true;
2686 private void SetupCecLogLevel()
2689 iCecManager.Client.LogLevel = 0;
2691 if (checkBoxCecLogError.Checked)
2692 iCecManager.Client.LogLevel |= (int) CecLogLevel.Error;
2694 if (checkBoxCecLogWarning.Checked)
2695 iCecManager.Client.LogLevel |= (int) CecLogLevel.Warning;
2697 if (checkBoxCecLogNotice.Checked)
2698 iCecManager.Client.LogLevel |= (int) CecLogLevel.Notice;
2700 if (checkBoxCecLogTraffic.Checked)
2701 iCecManager.Client.LogLevel |= (int) CecLogLevel.Traffic;
2703 if (checkBoxCecLogDebug.Checked)
2704 iCecManager.Client.LogLevel |= (int) CecLogLevel.Debug;
2706 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2710 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2715 private void buttonClearLogs_Click(object sender, EventArgs e)
2717 richTextBoxLogs.Clear();
2720 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)
2729 /// <param name="aEvent"></param>
2730 private void SelectEvent(Ear.Event aEvent)
2737 foreach (TreeNode node in iTreeViewEvents.Nodes)
2739 if (node.Tag == aEvent)
2741 iTreeViewEvents.SelectedNode = node;
2742 iTreeViewEvents.Focus();
2750 /// Get the current event based on event tree view selection.
2752 /// <returns></returns>
2753 private Ear.Event CurrentEvent()
2755 //Walk up the tree from the selected node to find our event
2756 TreeNode node = iTreeViewEvents.SelectedNode;
2757 Ear.Event selectedEvent = null;
2758 while (node != null)
2760 if (node.Tag is Ear.Event)
2762 selectedEvent = (Ear.Event) node.Tag;
2768 return selectedEvent;
2772 /// Get the current action based on event tree view selection
2774 /// <returns></returns>
2775 private Ear.Action CurrentAction()
2777 TreeNode node = iTreeViewEvents.SelectedNode;
2778 if (node != null && node.Tag is Ear.Action)
2780 return (Ear.Action) node.Tag;
2789 /// <param name="sender"></param>
2790 /// <param name="e"></param>
2791 private void buttonActionAdd_Click(object sender, EventArgs e)
2793 Ear.Event selectedEvent = CurrentEvent();
2794 if (selectedEvent == null)
2796 //We did not find a corresponding event
2800 FormEditObject<Ear.Action> ea = new FormEditObject<Ear.Action>();
2801 ea.Text = "Add action";
2802 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2803 if (res == DialogResult.OK)
2805 selectedEvent.Actions.Add(ea.Object);
2806 Properties.Settings.Default.Save();
2807 PopulateEventsTreeView();
2814 /// <param name="sender"></param>
2815 /// <param name="e"></param>
2816 private void buttonActionEdit_Click(object sender, EventArgs e)
2818 Ear.Event selectedEvent = CurrentEvent();
2819 Ear.Action selectedAction = CurrentAction();
2820 if (selectedEvent == null || selectedAction == null)
2822 //We did not find a corresponding event
2826 FormEditObject<Ear.Action> ea = new FormEditObject<Ear.Action>();
2827 ea.Text = "Edit action";
2828 ea.Object = selectedAction;
2829 int actionIndex = iTreeViewEvents.SelectedNode.Index;
2830 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2831 if (res == DialogResult.OK)
2834 selectedEvent.Actions[actionIndex]=ea.Object;
2835 //Save and rebuild our event tree view
2836 Properties.Settings.Default.Save();
2837 PopulateEventsTreeView();
2844 /// <param name="sender"></param>
2845 /// <param name="e"></param>
2846 private void buttonActionDelete_Click(object sender, EventArgs e)
2849 Ear.Action action = CurrentAction();
2852 //Must select action node
2856 Properties.Settings.Default.EarManager.RemoveAction(action);
2857 Properties.Settings.Default.Save();
2858 PopulateEventsTreeView();
2864 /// <param name="sender"></param>
2865 /// <param name="e"></param>
2866 private void buttonActionTest_Click(object sender, EventArgs e)
2868 Ear.Action a = CurrentAction();
2873 iTreeViewEvents.Focus();
2879 /// <param name="sender"></param>
2880 /// <param name="e"></param>
2881 private void buttonActionMoveUp_Click(object sender, EventArgs e)
2883 Ear.Action a = CurrentAction();
2885 //Action already at the top of the list
2886 iTreeViewEvents.SelectedNode.Index == 0)
2891 //Swap actions in event's action list
2892 Ear.Event currentEvent = CurrentEvent();
2893 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2894 Ear.Action movingUp = currentEvent.Actions[currentIndex];
2895 Ear.Action movingDown = currentEvent.Actions[currentIndex-1];
2896 currentEvent.Actions[currentIndex] = movingDown;
2897 currentEvent.Actions[currentIndex-1] = movingUp;
2899 //Save and populate our tree again
2900 Properties.Settings.Default.Save();
2901 PopulateEventsTreeView();
2908 /// <param name="sender"></param>
2909 /// <param name="e"></param>
2910 private void buttonActionMoveDown_Click(object sender, EventArgs e)
2912 Ear.Action a = CurrentAction();
2914 //Action already at the bottom of the list
2915 iTreeViewEvents.SelectedNode.Index == iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1)
2920 //Swap actions in event's action list
2921 Ear.Event currentEvent = CurrentEvent();
2922 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2923 Ear.Action movingDown = currentEvent.Actions[currentIndex];
2924 Ear.Action movingUp = currentEvent.Actions[currentIndex + 1];
2925 currentEvent.Actions[currentIndex] = movingUp;
2926 currentEvent.Actions[currentIndex + 1] = movingDown;
2928 //Save and populate our tree again
2929 Properties.Settings.Default.Save();
2930 PopulateEventsTreeView();
2937 /// <param name="sender"></param>
2938 /// <param name="e"></param>
2939 private void buttonEventTest_Click(object sender, EventArgs e)
2941 Ear.Event earEvent = CurrentEvent();
2942 if (earEvent != null)
2949 /// Manages events and actions buttons according to selected item in event tree.
2951 /// <param name="sender"></param>
2952 /// <param name="e"></param>
2953 private void iTreeViewEvents_AfterSelect(object sender, TreeViewEventArgs e)
2961 private void UpdateEventView()
2963 //One can always add an event
2964 buttonEventAdd.Enabled = true;
2966 //Enable buttons according to selected item
2967 buttonActionAdd.Enabled =
2968 buttonEventTest.Enabled =
2969 buttonEventDelete.Enabled =
2970 buttonEventEdit.Enabled =
2971 CurrentEvent() != null;
2973 Ear.Action currentAction = CurrentAction();
2974 //If an action is selected enable the following buttons
2975 buttonActionTest.Enabled =
2976 buttonActionDelete.Enabled =
2977 buttonActionMoveUp.Enabled =
2978 buttonActionMoveDown.Enabled =
2979 buttonActionEdit.Enabled =
2980 currentAction != null;
2982 if (currentAction != null)
2984 //If an action is selected enable move buttons if needed
2985 buttonActionMoveUp.Enabled = iTreeViewEvents.SelectedNode.Index != 0;
2986 buttonActionMoveDown.Enabled = iTreeViewEvents.SelectedNode.Index <
2987 iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1;
2991 private void buttonEventAdd_Click(object sender, EventArgs e)
2993 FormEditObject<Ear.Event> ea = new FormEditObject<Ear.Event>();
2994 ea.Text = "Add event";
2995 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2996 if (res == DialogResult.OK)
2998 Properties.Settings.Default.EarManager.Events.Add(ea.Object);
2999 Properties.Settings.Default.Save();
3000 PopulateEventsTreeView();
3001 SelectEvent(ea.Object);
3005 private void buttonEventDelete_Click(object sender, EventArgs e)
3007 Ear.Event currentEvent = CurrentEvent();
3008 if (currentEvent == null)
3010 //Must select action node
3014 Properties.Settings.Default.EarManager.Events.Remove(currentEvent);
3015 Properties.Settings.Default.Save();
3016 PopulateEventsTreeView();
3019 private void buttonEventEdit_Click(object sender, EventArgs e)
3021 Ear.Event selectedEvent = CurrentEvent();
3022 if (selectedEvent == null)
3024 //We did not find a corresponding event
3028 FormEditObject<Ear.Event> ea = new FormEditObject<Ear.Event>();
3029 ea.Text = "Edit event";
3030 ea.Object = selectedEvent;
3031 int actionIndex = iTreeViewEvents.SelectedNode.Index;
3032 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
3033 if (res == DialogResult.OK)
3035 //Save and rebuild our event tree view
3036 Properties.Settings.Default.Save();
3037 PopulateEventsTreeView();
3041 private void iTreeViewEvents_Leave(object sender, EventArgs e)
3043 //Make sure our event tree never looses focus
3044 ((TreeView) sender).Focus();
3047 private async void iButtonHarmonyConnect_Click(object sender, EventArgs e)
3050 Properties.Settings.Default.HarmonyHubAddress = iTextBoxHarmonyHubAddress.Text;
3051 Properties.Settings.Default.Save();
3053 iButtonHarmonyConnect.Enabled = false;
3056 await ConnectHarmonyAsync();
3060 iButtonHarmonyConnect.Enabled = true;
3066 private async Task ConnectHarmonyAsync()
3068 if (Program.HarmonyClient != null)
3070 Program.HarmonyClient.Close();
3073 //Reset Harmony client & config
3074 Program.HarmonyClient = null;
3075 Program.HarmonyConfig = null;
3077 Console.WriteLine("Harmony: Connecting... ");
3078 Program.HarmonyClient = new HarmonyHub.Client(iTextBoxHarmonyHubAddress.Text);
3079 //First create our client and login
3080 if (File.Exists("SessionToken"))
3082 var sessionToken = File.ReadAllText("SessionToken");
3083 Console.WriteLine("Harmony: Reusing token: {0}", sessionToken);
3084 Program.HarmonyClient.Open(sessionToken);
3088 if (string.IsNullOrEmpty(iTextBoxLogitechPassword.Text))
3090 Console.WriteLine("Harmony: Credentials missing!");
3094 Console.WriteLine("Harmony: Authenticating with Logitech servers...");
3095 await Program.HarmonyClient.Open(iTextBoxLogitechUserName.Text, iTextBoxLogitechPassword.Text);
3096 File.WriteAllText("SessionToken", Program.HarmonyClient.Token);
3099 Console.WriteLine("Harmony: Fetching Harmony Hub configuration...");
3102 Program.HarmonyConfig = await Program.HarmonyClient.GetConfigAsync();
3103 PopulateTreeViewHarmony(Program.HarmonyConfig);
3105 Console.WriteLine("Harmony: Ready");
3111 /// <param name="aConfig"></param>
3112 private void PopulateTreeViewHarmony(HarmonyHub.Config aConfig)
3114 iTreeViewHarmony.Nodes.Clear();
3116 foreach (HarmonyHub.Device device in aConfig.Devices)
3118 TreeNode deviceNode = iTreeViewHarmony.Nodes.Add(device.Id, $"{device.Label} ({device.DeviceTypeDisplayName}/{device.Model})");
3119 deviceNode.Tag = device;
3121 foreach (HarmonyHub.ControlGroup cg in device.ControlGroups)
3123 TreeNode cgNode = deviceNode.Nodes.Add(cg.Name);
3126 foreach (HarmonyHub.Function f in cg.Functions)
3128 TreeNode fNode = cgNode.Nodes.Add(f.Name);
3134 //treeViewConfig.ExpandAll();
3137 private async void iTreeViewHarmony_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
3139 //Upon function node double click we execute it
3140 var tag = e.Node.Tag as HarmonyHub.Function;
3141 if (tag != null && e.Node.Parent.Parent.Tag is HarmonyHub.Device)
3143 HarmonyHub.Function f = tag;
3144 HarmonyHub.Device d = (HarmonyHub.Device)e.Node.Parent.Parent.Tag;
3146 Console.WriteLine($"Harmony: Sending {f.Name} to {d.Label}...");
3148 await Program.HarmonyClient.SendCommandAsync(d.Id, f.Name);