Adding Harmony tab.
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;
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 ManagerEventAction iManager = new ManagerEventAction();
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 ManagerEventAction.Current = Properties.Settings.Default.Events;
145 if (ManagerEventAction.Current == null)
147 //No actions in our settings yet
148 ManagerEventAction.Current = new ManagerEventAction();
149 Properties.Settings.Default.Events = ManagerEventAction.Current;
153 //We loaded actions from our settings
154 //We need to hook them with corresponding events
155 ManagerEventAction.Current.Init();
157 iSkipFrameRendering = false;
159 iCurrentClientSessionId = "";
160 iCurrentClientData = null;
161 LastTickTime = DateTime.Now;
162 //Instantiate our display and register for events notifications
163 iDisplay = new Display();
164 iDisplay.OnOpened += OnDisplayOpened;
165 iDisplay.OnClosed += OnDisplayClosed;
167 iClients = new Dictionary<string, ClientData>();
168 iStartupManager = new StartupManager();
169 iNotifyIcon = new SharpLib.Notification.Control();
170 iRecordingNotification = new SharpLib.Notification.Control();
172 //Have our designer initialize its controls
173 InitializeComponent();
175 //Redirect console output
176 iWriter = new RichTextBoxTextWriter(richTextBoxLogs);
177 Console.SetOut(iWriter);
179 //Populate device types
180 PopulateDeviceTypes();
182 //Populate optical drives
183 PopulateOpticalDrives();
185 //Initial status update
188 //We have a bug when drawing minimized and reusing our bitmap
189 //Though I could not reproduce it on Windows 10
190 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
191 PixelFormat.Format32bppArgb);
192 iCreateBitmap = false;
194 //Minimize our window if desired
195 if (Properties.Settings.Default.StartMinimized)
197 WindowState = FormWindowState.Minimized;
205 /// <param name="sender"></param>
206 /// <param name="e"></param>
207 private void MainForm_Load(object sender, EventArgs e)
209 //Check if we are running a Click Once deployed application
210 if (ApplicationDeployment.IsNetworkDeployed)
212 //This is a proper Click Once installation, fetch and show our version number
213 this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
217 //Not a proper Click Once installation, assuming development build then
218 this.Text += " - development";
222 iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
223 iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);
224 UpdateAudioDeviceAndMasterVolumeThreadSafe();
227 iNetworkManager = new NetworkManager();
228 iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
229 UpdateNetworkStatus();
232 iCecManager = new ConsumerElectronicControl();
233 OnWndProc += iCecManager.OnWndProc;
240 PopulateEventsTreeView();
242 //Setup notification icon
245 //Setup recording notification
246 SetupRecordingNotification();
248 // To make sure start up with minimize to tray works
249 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
255 //When not debugging we want the screen to be empty until a client takes over
258 //When developing we want at least one client for testing
259 StartNewClient("abcdefghijklmnopqrst-0123456789", "ABCDEFGHIJKLMNOPQRST-0123456789");
262 //Open display connection on start-up if needed
263 if (Properties.Settings.Default.DisplayConnectOnStartup)
265 OpenDisplayConnection();
268 //Start our server so that we can get client requests
271 //Register for HID events
272 RegisterHidDevices();
274 //Start Idle client if needed
275 if (Properties.Settings.Default.StartIdleClient)
282 /// Called when our display is opened.
284 /// <param name="aDisplay"></param>
285 private void OnDisplayOpened(Display aDisplay)
287 //Make sure we resume frame rendering
288 iSkipFrameRendering = false;
290 //Set our screen size now that our display is connected
291 //Our panelDisplay is the container of our tableLayoutPanel
292 //tableLayoutPanel will resize itself to fit the client size of our panelDisplay
293 //panelDisplay needs an extra 2 pixels for borders on each sides
294 //tableLayoutPanel will eventually be the exact size of our display
295 Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
296 panelDisplay.Size = size;
298 //Our display was just opened, update our UI
300 //Initiate asynchronous request
301 iDisplay.RequestFirmwareRevision();
304 UpdateMasterVolumeThreadSafe();
306 UpdateNetworkStatus();
309 //Testing icon in debug, no arm done if icon not supported
310 //iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
311 //iDisplay.SetAllIconsStatus(2);
317 /// Populate tree view with events and actions
319 private void PopulateEventsTreeView()
321 //Disable action buttons
322 buttonActionAdd.Enabled = false;
323 buttonActionDelete.Enabled = false;
325 Event currentEvent = CurrentEvent();
326 SharpLib.Ear.Action currentAction = CurrentAction();
327 TreeNode treeNodeToSelect = null;
330 iTreeViewEvents.Nodes.Clear();
331 //Populate registered events
332 foreach (SharpLib.Ear.Event e in ManagerEventAction.Current.Events)
334 //Create our event node
335 TreeNode eventNode = iTreeViewEvents.Nodes.Add(e.Name);
336 eventNode.Tag = e; //For easy access to our event
339 //Dim our nodes if disabled
340 eventNode.ForeColor = Color.DimGray;
343 //Add event description as child node
344 eventNode.Nodes.Add(e.Description).ForeColor = eventNode.ForeColor;
345 //Create child node for actions root
346 TreeNode actionsNodes = eventNode.Nodes.Add("Actions");
347 actionsNodes.ForeColor = eventNode.ForeColor;
349 // Add our actions for that event
350 foreach (SharpLib.Ear.Action a in e.Actions)
352 TreeNode actionNode = actionsNodes.Nodes.Add(a.Brief());
354 actionNode.ForeColor = eventNode.ForeColor;
355 if (a == currentAction)
357 treeNodeToSelect = actionNode;
362 iTreeViewEvents.ExpandAll();
363 SelectEvent(currentEvent);
365 if (treeNodeToSelect != null)
367 iTreeViewEvents.SelectedNode = treeNodeToSelect;
369 else if (iTreeViewEvents.SelectedNode != null && iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) > 0)
371 //Select the last action if any
372 iTreeViewEvents.SelectedNode =
373 iTreeViewEvents.SelectedNode.Nodes[1].Nodes[
374 iTreeViewEvents.SelectedNode.Nodes[1].GetNodeCount(false) - 1];
376 else if (iTreeViewEvents.SelectedNode == null && iTreeViewEvents.Nodes.Count > 0)
378 //Still no selected node select the first one then
379 iTreeViewEvents.SelectedNode = iTreeViewEvents.Nodes[0];
387 /// Called when our display is closed.
389 /// <param name="aDisplay"></param>
390 private void OnDisplayClosed(Display aDisplay)
392 //Our display was just closed, update our UI consequently
396 public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
398 //Update network status
399 UpdateNetworkStatus();
403 /// Update our Network Status
405 private void UpdateNetworkStatus()
407 if (iDisplay.IsOpen())
409 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet,
410 iNetworkManager.NetworkListManager.IsConnectedToInternet);
411 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
416 int iLastNetworkIconIndex = 0;
417 int iUpdateCountSinceLastNetworkAnimation = 0;
422 private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
424 iUpdateCountSinceLastNetworkAnimation++;
425 iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation%4;
427 if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected &&
428 iUpdateCountSinceLastNetworkAnimation == 0)
430 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
433 //Prevents div by zero and other undefined behavior
436 iLastNetworkIconIndex++;
437 iLastNetworkIconIndex = iLastNetworkIconIndex%(iconCount*2);
438 for (int i = 0; i < iconCount; i++)
440 if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) &&
441 !(i == 1 && iLastNetworkIconIndex > 4))
443 iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
447 iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
456 /// Receive volume change notification and reflect changes on our slider.
458 /// <param name="data"></param>
459 public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
461 UpdateMasterVolumeThreadSafe();
465 /// Update master volume when user moves our slider.
467 /// <param name="sender"></param>
468 /// <param name="e"></param>
469 private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
471 //Just like Windows Volume Mixer we unmute if the volume is adjusted
472 iMultiMediaDevice.AudioEndpointVolume.Mute = false;
473 //Set volume level according to our volume slider new position
474 iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value/100.0f;
479 /// Mute check box changed.
481 /// <param name="sender"></param>
482 /// <param name="e"></param>
483 private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
485 iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
489 /// Device State Changed
491 public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId,
492 [MarshalAs(UnmanagedType.I4)] DeviceState newState)
499 public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId)
506 public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId)
511 /// Default Device Changed
513 public void OnDefaultDeviceChanged(DataFlow flow, Role role,
514 [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
516 if (role == Role.Multimedia && flow == DataFlow.Render)
518 UpdateAudioDeviceAndMasterVolumeThreadSafe();
523 /// Property Value Changed
525 /// <param name="pwstrDeviceId"></param>
526 /// <param name="key"></param>
527 public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key)
535 /// Update master volume indicators based our current system states.
536 /// This typically includes volume levels and mute status.
538 private void UpdateMasterVolumeThreadSafe()
540 if (this.InvokeRequired)
542 //Not in the proper thread, invoke ourselves
543 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
544 this.Invoke(d, new object[] {});
548 //Update volume slider
549 float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
550 trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar*100);
551 //Update mute checkbox
552 checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
554 //If our display connection is open we need to update its icons
555 if (iDisplay.IsOpen())
557 //First take care our our volume level icons
558 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
559 if (volumeIconCount > 0)
561 //Compute current volume level from system level and the number of segments in our display volume bar.
562 //That tells us how many segments in our volume bar needs to be turned on.
563 float currentVolume = volumeLevelScalar*volumeIconCount;
564 int segmentOnCount = Convert.ToInt32(currentVolume);
565 //Check if our segment count was rounded up, this will later be used for half brightness segment
566 bool roundedUp = segmentOnCount > currentVolume;
568 for (int i = 0; i < volumeIconCount; i++)
570 if (i < segmentOnCount)
572 //If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
573 if (i == segmentOnCount - 1 && roundedUp)
576 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
577 (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1)/2);
582 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i,
583 iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
588 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
593 //Take care of our mute icon
594 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
602 private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
604 if (this.InvokeRequired)
606 //Not in the proper thread, invoke ourselves
607 PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
608 this.Invoke(d, new object[] {});
612 //We are in the correct thread just go ahead.
615 //Get our master volume
616 iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
618 labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
620 //Show our volume in our track bar
621 UpdateMasterVolumeThreadSafe();
623 //Register to get volume modifications
624 iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
626 trackBarMasterVolume.Enabled = true;
630 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
631 Debug.WriteLine(ex.ToString());
632 //Something went wrong S/PDIF device ca throw exception I guess
633 trackBarMasterVolume.Enabled = false;
640 private void PopulateDeviceTypes()
642 int count = Display.TypeCount();
644 for (int i = 0; i < count; i++)
646 comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type) i));
653 private void PopulateOpticalDrives()
655 //Reset our list of drives
656 comboBoxOpticalDrives.Items.Clear();
657 comboBoxOpticalDrives.Items.Add("None");
659 //Go through each drives on our system and collected the optical ones in our list
660 DriveInfo[] allDrives = DriveInfo.GetDrives();
661 foreach (DriveInfo d in allDrives)
663 Debug.WriteLine("Drive " + d.Name);
664 Debug.WriteLine(" Drive type: {0}", d.DriveType);
666 if (d.DriveType == DriveType.CDRom)
668 //This is an optical drive, add it now
669 comboBoxOpticalDrives.Items.Add(d.Name.Substring(0, 2));
677 /// <returns></returns>
678 public string OpticalDriveToEject()
680 return comboBoxOpticalDrives.SelectedItem.ToString();
688 private void SetupTrayIcon()
690 iNotifyIcon.Icon = GetIcon("vfd.ico");
691 iNotifyIcon.Text = "Sharp Display Manager";
692 iNotifyIcon.Visible = true;
694 //Double click toggles visibility - typically brings up the application
695 iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
700 //Adding a context menu, useful to be able to exit the application
701 ContextMenu contextMenu = new ContextMenu();
702 //Context menu item to toggle visibility
703 MenuItem hideShowItem = new MenuItem("Hide/Show");
704 hideShowItem.Click += delegate(object obj, EventArgs args)
708 contextMenu.MenuItems.Add(hideShowItem);
710 //Context menu item separator
711 contextMenu.MenuItems.Add(new MenuItem("-"));
713 //Context menu exit item
714 MenuItem exitItem = new MenuItem("Exit");
715 exitItem.Click += delegate(object obj, EventArgs args)
719 contextMenu.MenuItems.Add(exitItem);
721 iNotifyIcon.ContextMenu = contextMenu;
727 private void SetupRecordingNotification()
729 iRecordingNotification.Icon = GetIcon("record.ico");
730 iRecordingNotification.Text = "No recording";
731 iRecordingNotification.Visible = false;
735 /// Access icons from embedded resources.
737 /// <param name="aName"></param>
738 /// <returns></returns>
739 public static Icon GetIcon(string aName)
741 string[] names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
742 foreach (string name in names)
744 //Find a resource name that ends with the given name
745 if (name.EndsWith(aName))
747 using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
749 return new Icon(stream);
759 /// Set our current client.
760 /// This will take care of applying our client layout and set data fields.
762 /// <param name="aSessionId"></param>
763 void SetCurrentClient(string aSessionId, bool aForce = false)
765 if (aSessionId == iCurrentClientSessionId)
767 //Given client is already the current one.
768 //Don't bother changing anything then.
772 ClientData requestedClientData = iClients[aSessionId];
774 //Check when was the last time we switched to that client
775 if (iCurrentClientData != null)
777 //Do not switch client if priority of current client is higher
778 if (!aForce && requestedClientData.Priority < iCurrentClientData.Priority)
784 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
785 //TODO: put that hard coded value as a client property
786 //Clients should be able to define how often they can be interrupted
787 //Thus a background client can set this to zero allowing any other client to interrupt at any time
788 //We could also compute this delay by looking at the requests frequencies?
790 requestedClientData.Priority == iCurrentClientData.Priority &&
791 //Time sharing is only if clients have the same priority
792 (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
794 //Don't switch clients too often
799 //Set current client ID.
800 iCurrentClientSessionId = aSessionId;
801 //Set the time we last switched to that client
802 iClients[aSessionId].LastSwitchTime = DateTime.Now;
803 //Fetch and set current client data.
804 iCurrentClientData = requestedClientData;
805 //Apply layout and set data fields.
806 UpdateTableLayoutPanel(iCurrentClientData);
809 private void buttonFont_Click(object sender, EventArgs e)
811 //fontDialog.ShowColor = true;
812 //fontDialog.ShowApply = true;
813 fontDialog.ShowEffects = true;
814 fontDialog.Font = cds.Font;
816 fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
818 //fontDialog.ShowHelp = true;
820 //fontDlg.MaxSize = 40;
821 //fontDlg.MinSize = 22;
823 //fontDialog.Parent = this;
824 //fontDialog.StartPosition = FormStartPosition.CenterParent;
826 //DlgBox.ShowDialog(fontDialog);
828 //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
829 if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
831 //Set the fonts to all our labels in our layout
832 foreach (Control ctrl in iTableLayoutPanel.Controls)
834 if (ctrl is MarqueeLabel)
836 ((MarqueeLabel) ctrl).Font = fontDialog.Font;
841 cds.Font = fontDialog.Font;
842 Properties.Settings.Default.Save();
851 void CheckFontHeight()
853 //Show font height and width
854 labelFontHeight.Text = "Font height: " + cds.Font.Height;
855 float charWidth = IsFixedWidth(cds.Font);
856 if (charWidth == 0.0f)
858 labelFontWidth.Visible = false;
862 labelFontWidth.Visible = true;
863 labelFontWidth.Text = "Font width: " + charWidth;
866 MarqueeLabel label = null;
867 //Get the first label control we can find
868 foreach (Control ctrl in iTableLayoutPanel.Controls)
870 if (ctrl is MarqueeLabel)
872 label = (MarqueeLabel) ctrl;
877 //Now check font height and show a warning if needed.
878 if (label != null && label.Font.Height > label.Height)
880 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) +
882 labelWarning.Visible = true;
886 labelWarning.Visible = false;
891 private void buttonCapture_Click(object sender, EventArgs e)
893 System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height);
894 iTableLayoutPanel.DrawToBitmap(bmp, iTableLayoutPanel.ClientRectangle);
895 //Bitmap bmpToSave = new Bitmap(bmp);
896 bmp.Save("D:\\capture.png");
898 ((MarqueeLabel) iTableLayoutPanel.Controls[0]).Text = "Captured";
901 string outputFileName = "d:\\capture.png";
902 using (MemoryStream memory = new MemoryStream())
904 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
906 bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
907 byte[] bytes = memory.ToArray();
908 fs.Write(bytes, 0, bytes.Length);
915 private void CheckForRequestResults()
917 if (iDisplay.IsRequestPending())
919 switch (iDisplay.AttemptRequestCompletion())
921 case MiniDisplay.Request.FirmwareRevision:
922 toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
923 //Issue next request then
924 iDisplay.RequestPowerSupplyStatus();
927 case MiniDisplay.Request.PowerSupplyStatus:
928 if (iDisplay.PowerSupplyStatus())
930 toolStripStatusLabelPower.Text = "ON";
934 toolStripStatusLabelPower.Text = "OFF";
936 //Issue next request then
937 iDisplay.RequestDeviceId();
940 case MiniDisplay.Request.DeviceId:
941 toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
942 //No more request to issue
948 public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
950 if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
957 public static uint ColorUntouched(int aX, int aY, uint aPixel)
962 public static uint ColorInversed(int aX, int aY, uint aPixel)
967 public static uint ColorChessboard(int aX, int aY, uint aPixel)
969 if ((aX%2 == 0) && (aY%2 == 0))
973 else if ((aX%2 != 0) && (aY%2 != 0))
981 public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
983 return aBmp.Width - aX - 1;
986 public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
988 return iBmp.Height - aY - 1;
991 public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
996 public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
1002 /// Select proper pixel delegates according to our current settings.
1004 private void SetupPixelDelegates()
1006 //Select our pixel processing routine
1007 if (cds.InverseColors)
1009 //iColorFx = ColorChessboard;
1010 iColorFx = ColorInversed;
1014 iColorFx = ColorWhiteIsOn;
1017 //Select proper coordinate translation functions
1018 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
1019 if (cds.ReverseScreen)
1021 iScreenX = ScreenReversedX;
1022 iScreenY = ScreenReversedY;
1032 //This is our timer tick responsible to perform our render
1033 private void timer_Tick(object sender, EventArgs e)
1035 //Not ideal cause this has nothing to do with display render
1038 //Update our animations
1039 DateTime NewTickTime = DateTime.Now;
1041 UpdateNetworkSignal(LastTickTime, NewTickTime);
1043 //Update animation for all our marquees
1044 foreach (Control ctrl in iTableLayoutPanel.Controls)
1046 if (ctrl is MarqueeLabel)
1048 ((MarqueeLabel) ctrl).UpdateAnimation(LastTickTime, NewTickTime);
1052 //Update our display
1053 if (iDisplay.IsOpen())
1055 CheckForRequestResults();
1057 //Check if frame rendering is needed
1058 //Typically used when showing clock
1059 if (!iSkipFrameRendering)
1064 iBmp = new System.Drawing.Bitmap(iTableLayoutPanel.Width, iTableLayoutPanel.Height,
1065 PixelFormat.Format32bppArgb);
1066 iCreateBitmap = false;
1068 iTableLayoutPanel.DrawToBitmap(iBmp, iTableLayoutPanel.ClientRectangle);
1069 //iBmp.Save("D:\\capture.png");
1071 //Send it to our display
1072 for (int i = 0; i < iBmp.Width; i++)
1074 for (int j = 0; j < iBmp.Height; j++)
1078 //Get our processed pixel coordinates
1079 int x = iScreenX(iBmp, i);
1080 int y = iScreenY(iBmp, j);
1082 uint color = (uint) iBmp.GetPixel(i, j).ToArgb();
1083 //Apply color effects
1084 color = iColorFx(x, y, color);
1086 iDisplay.SetPixel(x, y, color);
1091 iDisplay.SwapBuffers();
1095 //Compute instant FPS
1096 toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " +
1097 (1000/timer.Interval).ToString() + " FPS";
1099 LastTickTime = NewTickTime;
1104 /// Attempt to establish connection with our display hardware.
1106 private void OpenDisplayConnection()
1108 CloseDisplayConnection();
1110 if (!iDisplay.Open((MiniDisplay.Type) cds.DisplayType))
1113 toolStripStatusLabelConnect.Text = "Connection error";
1117 private void CloseDisplayConnection()
1119 //Status will be updated upon receiving the closed event
1121 if (iDisplay == null || !iDisplay.IsOpen())
1126 //Do not clear if we gave up on rendering already.
1127 //This means we will keep on displaying clock on MDM166AA for instance.
1128 if (!iSkipFrameRendering)
1131 iDisplay.SwapBuffers();
1134 iDisplay.SetAllIconsStatus(0); //Turn off all icons
1138 private void buttonOpen_Click(object sender, EventArgs e)
1140 OpenDisplayConnection();
1143 private void buttonClose_Click(object sender, EventArgs e)
1145 CloseDisplayConnection();
1148 private void buttonClear_Click(object sender, EventArgs e)
1151 iDisplay.SwapBuffers();
1154 private void buttonFill_Click(object sender, EventArgs e)
1157 iDisplay.SwapBuffers();
1160 private void trackBarBrightness_Scroll(object sender, EventArgs e)
1162 cds.Brightness = trackBarBrightness.Value;
1163 Properties.Settings.Default.Save();
1164 iDisplay.SetBrightness(trackBarBrightness.Value);
1170 /// CDS stands for Current Display Settings
1172 private DisplaySettings cds
1176 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
1177 if (settings == null)
1179 settings = new DisplaysSettings();
1181 Properties.Settings.Default.DisplaysSettings = settings;
1184 //Make sure all our settings have been created
1185 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
1187 settings.Displays.Add(new DisplaySettings());
1190 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
1191 return displaySettings;
1196 /// Check if the given font has a fixed character pitch.
1198 /// <param name="ft"></param>
1199 /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
1200 public float IsFixedWidth(Font ft)
1202 Graphics g = CreateGraphics();
1203 char[] charSizes = new char[] {'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.'};
1204 float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
1206 bool fixedWidth = true;
1208 foreach (char c in charSizes)
1209 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width !=
1222 /// Synchronize UI with settings
1224 private void UpdateStatus()
1227 checkBoxShowBorders.Checked = cds.ShowBorders;
1228 iTableLayoutPanel.CellBorderStyle = (cds.ShowBorders
1229 ? TableLayoutPanelCellBorderStyle.Single
1230 : TableLayoutPanelCellBorderStyle.None);
1232 //Set the proper font to each of our labels
1233 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
1235 ctrl.Font = cds.Font;
1239 //Check if "run on Windows startup" is enabled
1240 checkBoxAutoStart.Checked = iStartupManager.Startup;
1242 checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
1243 checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
1244 checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
1245 iCheckBoxStartIdleClient.Checked = Properties.Settings.Default.StartIdleClient;
1246 labelStartFileName.Text = Properties.Settings.Default.StartFileName;
1249 //Try find our drive in our drive list
1250 int opticalDriveItemIndex = 0;
1251 bool driveNotFound = true;
1252 string opticalDriveToEject = Properties.Settings.Default.OpticalDriveToEject;
1253 foreach (object item in comboBoxOpticalDrives.Items)
1255 if (opticalDriveToEject == item.ToString())
1257 comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
1258 driveNotFound = false;
1261 opticalDriveItemIndex++;
1266 //We could not find the drive we had saved.
1267 //Select "None" then.
1268 comboBoxOpticalDrives.SelectedIndex = 0;
1272 iCheckBoxHarmonyEnabled.Checked = Properties.Settings.Default.HarmonyEnabled;
1273 iTextBoxHarmonyHubAddress.Text = Properties.Settings.Default.HarmonyHubAddress;
1276 checkBoxCecEnabled.Checked = Properties.Settings.Default.CecEnabled;
1277 comboBoxHdmiPort.SelectedIndex = Properties.Settings.Default.CecHdmiPort - 1;
1279 //Mini Display settings
1280 checkBoxReverseScreen.Checked = cds.ReverseScreen;
1281 checkBoxInverseColors.Checked = cds.InverseColors;
1282 checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
1283 checkBoxScaleToFit.Checked = cds.ScaleToFit;
1284 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1285 labelMinFontSize.Enabled = cds.ScaleToFit;
1286 maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
1287 maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
1288 comboBoxDisplayType.SelectedIndex = cds.DisplayType;
1289 timer.Interval = cds.TimerInterval;
1290 maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
1291 textBoxScrollLoopSeparator.Text = cds.Separator;
1293 SetupPixelDelegates();
1295 if (iDisplay.IsOpen())
1297 //We have a display connection
1298 //Reflect that in our UI
1301 iTableLayoutPanel.Enabled = true;
1302 panelDisplay.Enabled = true;
1304 //Only setup brightness if display is open
1305 trackBarBrightness.Minimum = iDisplay.MinBrightness();
1306 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
1307 if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
1309 //Brightness out of range, this can occur when using auto-detect
1310 //Use max brightness instead
1311 trackBarBrightness.Value = iDisplay.MaxBrightness();
1312 iDisplay.SetBrightness(iDisplay.MaxBrightness());
1316 trackBarBrightness.Value = cds.Brightness;
1317 iDisplay.SetBrightness(cds.Brightness);
1320 //Try compute the steps to something that makes sense
1321 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness())/5);
1322 trackBarBrightness.SmallChange = 1;
1325 buttonFill.Enabled = true;
1326 buttonClear.Enabled = true;
1327 buttonOpen.Enabled = false;
1328 buttonClose.Enabled = true;
1329 trackBarBrightness.Enabled = true;
1330 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
1331 //+ " - " + iDisplay.SerialNumber();
1333 if (iDisplay.SupportPowerOnOff())
1335 buttonPowerOn.Enabled = true;
1336 buttonPowerOff.Enabled = true;
1340 buttonPowerOn.Enabled = false;
1341 buttonPowerOff.Enabled = false;
1344 if (iDisplay.SupportClock())
1346 buttonShowClock.Enabled = true;
1347 buttonHideClock.Enabled = true;
1351 buttonShowClock.Enabled = false;
1352 buttonHideClock.Enabled = false;
1356 //Check if Volume Label is supported. To date only MDM166AA supports that crap :)
1357 checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel) > 0;
1359 if (cds.ShowVolumeLabel)
1361 iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
1365 iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
1370 //Display connection not available
1371 //Reflect that in our UI
1373 //In debug start our timer even if we don't have a display connection
1376 //In production environment we don't need our timer if no display connection
1379 checkBoxShowVolumeLabel.Enabled = false;
1380 iTableLayoutPanel.Enabled = false;
1381 panelDisplay.Enabled = false;
1382 buttonFill.Enabled = false;
1383 buttonClear.Enabled = false;
1384 buttonOpen.Enabled = true;
1385 buttonClose.Enabled = false;
1386 trackBarBrightness.Enabled = false;
1387 buttonPowerOn.Enabled = false;
1388 buttonPowerOff.Enabled = false;
1389 buttonShowClock.Enabled = false;
1390 buttonHideClock.Enabled = false;
1391 toolStripStatusLabelConnect.Text = "Disconnected";
1392 toolStripStatusLabelPower.Text = "N/A";
1401 /// <param name="sender"></param>
1402 /// <param name="e"></param>
1403 private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
1405 cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
1406 Properties.Settings.Default.Save();
1410 private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
1412 //Save our show borders setting
1413 iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked
1414 ? TableLayoutPanelCellBorderStyle.Single
1415 : TableLayoutPanelCellBorderStyle.None);
1416 cds.ShowBorders = checkBoxShowBorders.Checked;
1417 Properties.Settings.Default.Save();
1421 private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
1423 //Save our connect on startup setting
1424 Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
1425 Properties.Settings.Default.Save();
1428 private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
1430 //Save our "Minimize to tray" setting
1431 Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
1432 Properties.Settings.Default.Save();
1436 private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
1438 //Save our "Start minimized" setting
1439 Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
1440 Properties.Settings.Default.Save();
1443 private void checkBoxStartIdleClient_CheckedChanged(object sender, EventArgs e)
1445 Properties.Settings.Default.StartIdleClient = iCheckBoxStartIdleClient.Checked;
1446 Properties.Settings.Default.Save();
1449 private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
1451 iStartupManager.Startup = checkBoxAutoStart.Checked;
1455 private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
1457 //Save our reverse screen setting
1458 cds.ReverseScreen = checkBoxReverseScreen.Checked;
1459 Properties.Settings.Default.Save();
1460 SetupPixelDelegates();
1463 private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
1465 //Save our inverse colors setting
1466 cds.InverseColors = checkBoxInverseColors.Checked;
1467 Properties.Settings.Default.Save();
1468 SetupPixelDelegates();
1471 private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
1473 //Save our scale to fit setting
1474 cds.ScaleToFit = checkBoxScaleToFit.Checked;
1475 Properties.Settings.Default.Save();
1477 labelMinFontSize.Enabled = cds.ScaleToFit;
1478 maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
1481 private void MainForm_Resize(object sender, EventArgs e)
1483 if (WindowState == FormWindowState.Minimized)
1485 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
1486 // That's apparently not needed on Windows 10 but we better leave it in place.
1487 iCreateBitmap = true;
1491 private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
1494 iNetworkManager.Dispose();
1495 CloseDisplayConnection();
1497 e.Cancel = iClosing;
1500 public void StartServer()
1502 iServiceHost = new ServiceHost
1505 new Uri[] {new Uri("net.tcp://localhost:8001/")}
1508 iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true),
1510 iServiceHost.Open();
1513 public void StopServer()
1515 if (iClients.Count > 0 && !iClosing)
1519 BroadcastCloseEvent();
1524 MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo,
1525 MessageBoxIcon.Warning) == DialogResult.Yes)
1527 iClosing = false; //We make sure we force close if asked twice
1532 //We removed that as it often lags for some reason
1533 //iServiceHost.Close();
1537 public void BroadcastCloseEvent()
1539 Trace.TraceInformation("BroadcastCloseEvent - start");
1541 var inactiveClients = new List<string>();
1542 foreach (var client in iClients)
1544 //if (client.Key != eventData.ClientName)
1548 Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
1549 client.Value.Callback.OnCloseOrder( /*eventData*/);
1551 catch (Exception ex)
1553 inactiveClients.Add(client.Key);
1558 if (inactiveClients.Count > 0)
1560 foreach (var client in inactiveClients)
1562 iClients.Remove(client);
1563 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1564 Program.iFormMain.iTreeViewClients.Nodes.Find(client, false)[0]);
1568 if (iClients.Count == 0)
1575 /// Just remove all our fields.
1577 private void ClearLayout()
1579 iTableLayoutPanel.Controls.Clear();
1580 iTableLayoutPanel.RowStyles.Clear();
1581 iTableLayoutPanel.ColumnStyles.Clear();
1582 iCurrentClientData = null;
1586 /// Just launch a demo client.
1588 private void StartNewClient(string aTopText = "", string aBottomText = "")
1590 Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
1591 SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(
1592 new Point(this.Right, this.Top), aTopText, aBottomText);
1593 clientThread.Start(myParams);
1598 /// Just launch our idle client.
1600 private void StartIdleClient(string aTopText = "", string aBottomText = "")
1602 Thread clientThread = new Thread(SharpDisplayClientIdle.Program.MainWithParams);
1603 SharpDisplayClientIdle.StartParams myParams =
1604 new SharpDisplayClientIdle.StartParams(new Point(this.Right, this.Top), aTopText, aBottomText);
1605 clientThread.Start(myParams);
1610 private void buttonStartClient_Click(object sender, EventArgs e)
1615 private void buttonSuspend_Click(object sender, EventArgs e)
1620 private void StartTimer()
1622 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1623 timer.Enabled = true;
1624 UpdateSuspendButton();
1627 private void StopTimer()
1629 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1630 timer.Enabled = false;
1631 UpdateSuspendButton();
1634 private void ToggleTimer()
1636 LastTickTime = DateTime.Now; //Reset timer to prevent jump
1637 timer.Enabled = !timer.Enabled;
1638 UpdateSuspendButton();
1641 private void UpdateSuspendButton()
1645 buttonSuspend.Text = "Run";
1649 buttonSuspend.Text = "Pause";
1654 private void buttonCloseClients_Click(object sender, EventArgs e)
1656 BroadcastCloseEvent();
1659 private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
1661 //Root node must have at least one child
1662 if (e.Node.Nodes.Count == 0)
1667 //If the selected node is the root node of a client then switch to it
1668 string sessionId = e.Node.Nodes[0].Text; //First child of a root node is the sessionId
1669 if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
1671 //We have a valid session just switch to that client
1672 SetCurrentClient(sessionId, true);
1681 /// <param name="aSessionId"></param>
1682 /// <param name="aCallback"></param>
1683 public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
1685 if (this.InvokeRequired)
1687 //Not in the proper thread, invoke ourselves
1688 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
1689 this.Invoke(d, new object[] {aSessionId, aCallback});
1693 //We are in the proper thread
1694 //Add this session to our collection of clients
1695 ClientData newClient = new ClientData(aSessionId, aCallback);
1696 Program.iFormMain.iClients.Add(aSessionId, newClient);
1697 //Add this session to our UI
1698 UpdateClientTreeViewNode(newClient);
1704 /// Find the client with the highest priority if any.
1706 /// <returns>Our highest priority client or null if not a single client is connected.</returns>
1707 public ClientData FindHighestPriorityClient()
1709 ClientData highestPriorityClient = null;
1710 foreach (var client in iClients)
1712 if (highestPriorityClient == null || client.Value.Priority > highestPriorityClient.Priority)
1714 highestPriorityClient = client.Value;
1718 return highestPriorityClient;
1724 /// <param name="aSessionId"></param>
1725 public void RemoveClientThreadSafe(string aSessionId)
1727 if (this.InvokeRequired)
1729 //Not in the proper thread, invoke ourselves
1730 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
1731 this.Invoke(d, new object[] {aSessionId});
1735 //We are in the proper thread
1736 //Remove this session from both client collection and UI tree view
1737 if (Program.iFormMain.iClients.Keys.Contains(aSessionId))
1739 Program.iFormMain.iClients.Remove(aSessionId);
1740 Program.iFormMain.iTreeViewClients.Nodes.Remove(
1741 Program.iFormMain.iTreeViewClients.Nodes.Find(aSessionId, false)[0]);
1742 //Update recording status too whenever a client is removed
1743 UpdateRecordingNotification();
1746 if (iCurrentClientSessionId == aSessionId)
1748 //The current client is closing
1749 iCurrentClientData = null;
1750 //Find the client with the highest priority and set it as current
1751 ClientData newCurrentClient = FindHighestPriorityClient();
1752 if (newCurrentClient != null)
1754 SetCurrentClient(newCurrentClient.SessionId, true);
1758 if (iClients.Count == 0)
1760 //Clear our screen when last client disconnects
1765 //We were closing our form
1766 //All clients are now closed
1767 //Just resume our close operation
1778 /// <param name="aSessionId"></param>
1779 /// <param name="aLayout"></param>
1780 public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
1782 if (this.InvokeRequired)
1784 //Not in the proper thread, invoke ourselves
1785 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
1786 this.Invoke(d, new object[] {aSessionId, aLayout});
1790 ClientData client = iClients[aSessionId];
1793 //Don't change a thing if the layout is the same
1794 if (!client.Layout.IsSameAs(aLayout))
1796 Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
1797 //Set our client layout then
1798 client.Layout = aLayout;
1799 //So that next time we update all our fields at ones
1800 client.HasNewLayout = true;
1801 //Layout has changed clear our fields then
1802 client.Fields.Clear();
1804 UpdateClientTreeViewNode(client);
1808 Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
1817 /// <param name="aSessionId"></param>
1818 /// <param name="aField"></param>
1819 public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
1821 if (this.InvokeRequired)
1823 //Not in the proper thread, invoke ourselves
1824 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
1825 this.Invoke(d, new object[] {aSessionId, aField});
1829 //We are in the proper thread
1830 //Call the non-thread-safe variant
1831 SetClientField(aSessionId, aField);
1839 /// Set a data field in the given client.
1841 /// <param name="aSessionId"></param>
1842 /// <param name="aField"></param>
1843 private void SetClientField(string aSessionId, DataField aField)
1845 //TODO: should check if the field actually changed?
1847 ClientData client = iClients[aSessionId];
1848 bool layoutChanged = false;
1849 bool contentChanged = true;
1851 //Fetch our field index
1852 int fieldIndex = client.FindSameFieldIndex(aField);
1856 //No corresponding field, just bail out
1860 //Keep our previous field in there
1861 DataField previousField = client.Fields[fieldIndex];
1862 //Just update that field then
1863 client.Fields[fieldIndex] = aField;
1865 if (!aField.IsTableField)
1867 //We are done then if that field is not in our table layout
1871 TableField tableField = (TableField) aField;
1873 if (previousField.IsSameLayout(aField))
1875 //If we are updating a field in our current client we need to update it in our panel
1876 if (aSessionId == iCurrentClientSessionId)
1878 Control ctrl = iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
1879 if (aField.IsTextField && ctrl is MarqueeLabel)
1881 TextField textField = (TextField) aField;
1882 //Text field control already in place, just change the text
1883 MarqueeLabel label = (MarqueeLabel) ctrl;
1884 contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
1885 label.Text = textField.Text;
1886 label.TextAlign = textField.Alignment;
1888 else if (aField.IsBitmapField && ctrl is PictureBox)
1890 BitmapField bitmapField = (BitmapField) aField;
1891 contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
1892 //Bitmap field control already in place just change the bitmap
1893 PictureBox pictureBox = (PictureBox) ctrl;
1894 pictureBox.Image = bitmapField.Bitmap;
1898 layoutChanged = true;
1904 layoutChanged = true;
1907 //If either content or layout changed we need to update our tree view to reflect the changes
1908 if (contentChanged || layoutChanged)
1910 UpdateClientTreeViewNode(client);
1914 Debug.Print("Layout changed");
1915 //Our layout has changed, if we are already the current client we need to update our panel
1916 if (aSessionId == iCurrentClientSessionId)
1918 //Apply layout and set data fields.
1919 UpdateTableLayoutPanel(iCurrentClientData);
1924 Debug.Print("Layout has not changed.");
1929 Debug.Print("WARNING: content and layout have not changed!");
1932 //When a client field is set we try switching to this client to present the new information to our user
1933 SetCurrentClient(aSessionId);
1939 /// <param name="aTexts"></param>
1940 public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
1942 if (this.InvokeRequired)
1944 //Not in the proper thread, invoke ourselves
1945 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
1946 this.Invoke(d, new object[] {aSessionId, aFields});
1950 ClientData client = iClients[aSessionId];
1952 if (client.HasNewLayout)
1954 //TODO: Assert client.Count == 0
1955 //Our layout was just changed
1956 //Do some special handling to avoid re-creating our panel N times, once for each fields
1957 client.HasNewLayout = false;
1958 //Just set all our fields then
1959 client.Fields.AddRange(aFields);
1960 //Try switch to that client
1961 SetCurrentClient(aSessionId);
1963 //If we are updating the current client update our panel
1964 if (aSessionId == iCurrentClientSessionId)
1966 //Apply layout and set data fields.
1967 UpdateTableLayoutPanel(iCurrentClientData);
1970 UpdateClientTreeViewNode(client);
1974 //Put each our text fields in a label control
1975 foreach (DataField field in aFields)
1977 SetClientField(aSessionId, field);
1986 /// <param name="aSessionId"></param>
1987 /// <param name="aName"></param>
1988 public void SetClientNameThreadSafe(string aSessionId, string aName)
1990 if (this.InvokeRequired)
1992 //Not in the proper thread, invoke ourselves
1993 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
1994 this.Invoke(d, new object[] {aSessionId, aName});
1998 //We are in the proper thread
2000 ClientData client = iClients[aSessionId];
2004 client.Name = aName;
2005 //Update our tree-view
2006 UpdateClientTreeViewNode(client);
2012 public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
2014 if (this.InvokeRequired)
2016 //Not in the proper thread, invoke ourselves
2017 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
2018 this.Invoke(d, new object[] {aSessionId, aPriority});
2022 //We are in the proper thread
2024 ClientData client = iClients[aSessionId];
2028 client.Priority = aPriority;
2029 //Update our tree-view
2030 UpdateClientTreeViewNode(client);
2031 //Change our current client as per new priority
2032 ClientData newCurrentClient = FindHighestPriorityClient();
2033 if (newCurrentClient != null)
2035 SetCurrentClient(newCurrentClient.SessionId);
2044 /// <param name="value"></param>
2045 /// <param name="maxChars"></param>
2046 /// <returns></returns>
2047 public static string Truncate(string value, int maxChars)
2049 return value.Length <= maxChars ? value : value.Substring(0, maxChars - 3) + "...";
2053 /// Update our recording notification.
2055 private void UpdateRecordingNotification()
2058 bool activeRecording = false;
2060 RecordingField recField = new RecordingField();
2061 foreach (var client in iClients)
2063 RecordingField rec = (RecordingField) client.Value.FindSameFieldAs(recField);
2064 if (rec != null && rec.IsActive)
2066 activeRecording = true;
2067 //Don't break cause we are collecting the names/texts.
2068 if (!String.IsNullOrEmpty(rec.Text))
2070 text += (rec.Text + "\n");
2074 //Not text for that recording, use client name instead
2075 text += client.Value.Name + " recording\n";
2081 //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
2082 iRecordingNotification.Text = Truncate(text, 63);
2084 //Change visibility of notification if needed
2085 if (iRecordingNotification.Visible != activeRecording)
2087 iRecordingNotification.Visible = activeRecording;
2088 //Assuming the notification icon is in sync with our display icon
2089 //Take care of our REC icon
2090 if (iDisplay.IsOpen())
2092 iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
2100 /// <param name="aClient"></param>
2101 private void UpdateClientTreeViewNode(ClientData aClient)
2103 Debug.Print("UpdateClientTreeViewNode");
2105 if (aClient == null)
2110 //Hook in record icon update too
2111 UpdateRecordingNotification();
2113 TreeNode node = null;
2114 //Check that our client node already exists
2115 //Get our client root node using its key which is our session ID
2116 TreeNode[] nodes = iTreeViewClients.Nodes.Find(aClient.SessionId, false);
2117 if (nodes.Count() > 0)
2119 //We already have a node for that client
2121 //Clear children as we are going to recreate them below
2126 //Client node does not exists create a new one
2127 iTreeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
2128 node = iTreeViewClients.Nodes.Find(aClient.SessionId, false)[0];
2134 if (!String.IsNullOrEmpty(aClient.Name))
2136 //We have a name, use it as text for our root node
2137 node.Text = aClient.Name;
2138 //Add a child with SessionId
2139 node.Nodes.Add(new TreeNode(aClient.SessionId));
2143 //No name, use session ID instead
2144 node.Text = aClient.SessionId;
2147 //Display client priority
2148 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
2150 if (aClient.Fields.Count > 0)
2152 //Create root node for our texts
2153 TreeNode textsRoot = new TreeNode("Fields");
2154 node.Nodes.Add(textsRoot);
2155 //For each text add a new entry
2156 foreach (DataField field in aClient.Fields)
2158 if (field.IsTextField)
2160 TextField textField = (TextField) field;
2161 textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
2163 else if (field.IsBitmapField)
2165 textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
2167 else if (field.IsRecordingField)
2169 RecordingField recordingField = (RecordingField) field;
2170 textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
2180 /// Update our table layout row styles to make sure each rows have similar height
2182 private void UpdateTableLayoutRowStyles()
2184 foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
2186 rowStyle.SizeType = SizeType.Percent;
2187 rowStyle.Height = 100/iTableLayoutPanel.RowCount;
2192 /// Update our display table layout.
2193 /// Will instanciated every field control as defined by our client.
2194 /// Fields must be specified by rows from the left.
2196 /// <param name="aLayout"></param>
2197 private void UpdateTableLayoutPanel(ClientData aClient)
2199 Debug.Print("UpdateTableLayoutPanel");
2201 if (aClient == null)
2208 TableLayout layout = aClient.Layout;
2210 //First clean our current panel
2211 iTableLayoutPanel.Controls.Clear();
2212 iTableLayoutPanel.RowStyles.Clear();
2213 iTableLayoutPanel.ColumnStyles.Clear();
2214 iTableLayoutPanel.RowCount = 0;
2215 iTableLayoutPanel.ColumnCount = 0;
2217 //Then recreate our rows...
2218 while (iTableLayoutPanel.RowCount < layout.Rows.Count)
2220 iTableLayoutPanel.RowCount++;
2224 while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
2226 iTableLayoutPanel.ColumnCount++;
2230 for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
2232 //Create our column styles
2233 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
2236 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
2240 //Create our row styles
2241 this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
2251 foreach (DataField field in aClient.Fields)
2253 if (!field.IsTableField)
2255 //That field is not taking part in our table layout skip it
2259 TableField tableField = (TableField) field;
2261 //Create a control corresponding to the field specified for that cell
2262 Control control = CreateControlForDataField(tableField);
2264 //Add newly created control to our table layout at the specified row and column
2265 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
2266 //Make sure we specify column and row span for that new control
2267 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
2268 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
2276 /// Check our type of data field and create corresponding control
2278 /// <param name="aField"></param>
2279 private Control CreateControlForDataField(DataField aField)
2281 Control control = null;
2282 if (aField.IsTextField)
2284 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
2285 label.AutoEllipsis = true;
2286 label.AutoSize = true;
2287 label.BackColor = System.Drawing.Color.Transparent;
2288 label.Dock = System.Windows.Forms.DockStyle.Fill;
2289 label.Location = new System.Drawing.Point(1, 1);
2290 label.Margin = new System.Windows.Forms.Padding(0);
2291 label.Name = "marqueeLabel" + aField;
2292 label.OwnTimer = false;
2293 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
2294 label.Separator = cds.Separator;
2295 label.MinFontSize = cds.MinFontSize;
2296 label.ScaleToFit = cds.ScaleToFit;
2297 //control.Size = new System.Drawing.Size(254, 30);
2298 //control.TabIndex = 2;
2299 label.Font = cds.Font;
2301 TextField field = (TextField) aField;
2302 label.TextAlign = field.Alignment;
2303 label.UseCompatibleTextRendering = true;
2304 label.Text = field.Text;
2308 else if (aField.IsBitmapField)
2310 //Create picture box
2311 PictureBox picture = new PictureBox();
2312 picture.AutoSize = true;
2313 picture.BackColor = System.Drawing.Color.Transparent;
2314 picture.Dock = System.Windows.Forms.DockStyle.Fill;
2315 picture.Location = new System.Drawing.Point(1, 1);
2316 picture.Margin = new System.Windows.Forms.Padding(0);
2317 picture.Name = "pictureBox" + aField;
2319 BitmapField field = (BitmapField) aField;
2320 picture.Image = field.Bitmap;
2324 //TODO: Handle recording field?
2330 /// Called when the user selected a new display type.
2332 /// <param name="sender"></param>
2333 /// <param name="e"></param>
2334 private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
2336 //Store the selected display type in our settings
2337 Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
2338 cds.DisplayType = comboBoxDisplayType.SelectedIndex;
2339 Properties.Settings.Default.Save();
2341 //Try re-opening the display connection if we were already connected.
2342 //Otherwise just update our status to reflect display type change.
2343 if (iDisplay.IsOpen())
2345 OpenDisplayConnection();
2353 private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
2355 if (maskedTextBoxTimerInterval.Text != "")
2357 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
2361 timer.Interval = interval;
2362 cds.TimerInterval = timer.Interval;
2363 Properties.Settings.Default.Save();
2368 private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
2370 if (maskedTextBoxMinFontSize.Text != "")
2372 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
2374 if (minFontSize > 0)
2376 cds.MinFontSize = minFontSize;
2377 Properties.Settings.Default.Save();
2378 //We need to recreate our layout for that change to take effect
2379 UpdateTableLayoutPanel(iCurrentClientData);
2385 private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
2387 if (maskedTextBoxScrollingSpeed.Text != "")
2389 int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
2391 if (scrollingSpeed > 0)
2393 cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
2394 Properties.Settings.Default.Save();
2395 //We need to recreate our layout for that change to take effect
2396 UpdateTableLayoutPanel(iCurrentClientData);
2401 private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
2403 cds.Separator = textBoxScrollLoopSeparator.Text;
2404 Properties.Settings.Default.Save();
2406 //Update our text fields
2407 foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
2409 ctrl.Separator = cds.Separator;
2414 private void buttonPowerOn_Click(object sender, EventArgs e)
2419 private void buttonPowerOff_Click(object sender, EventArgs e)
2421 iDisplay.PowerOff();
2424 private void buttonShowClock_Click(object sender, EventArgs e)
2429 private void buttonHideClock_Click(object sender, EventArgs e)
2434 private void buttonUpdate_Click(object sender, EventArgs e)
2436 InstallUpdateSyncWithInfo();
2444 if (!iDisplay.IsOpen())
2449 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2450 iSkipFrameRendering = true;
2453 iDisplay.SwapBuffers();
2454 //Then show our clock
2455 iDisplay.ShowClock();
2463 if (!iDisplay.IsOpen())
2468 //Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
2469 iSkipFrameRendering = false;
2470 iDisplay.HideClock();
2473 private void InstallUpdateSyncWithInfo()
2475 UpdateCheckInfo info = null;
2477 if (ApplicationDeployment.IsNetworkDeployed)
2479 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
2483 info = ad.CheckForDetailedUpdate();
2486 catch (DeploymentDownloadException dde)
2489 "The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " +
2493 catch (InvalidDeploymentException ide)
2496 "Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " +
2500 catch (InvalidOperationException ioe)
2503 "This application cannot be updated. It is likely not a ClickOnce application. Error: " +
2508 if (info.UpdateAvailable)
2510 Boolean doUpdate = true;
2512 if (!info.IsUpdateRequired)
2515 MessageBox.Show("An update is available. Would you like to update the application now?",
2516 "Update Available", MessageBoxButtons.OKCancel);
2517 if (!(DialogResult.OK == dr))
2524 // Display a message that the application MUST reboot. Display the minimum required version.
2525 MessageBox.Show("This application has detected a mandatory update from your current " +
2526 "version to version " + info.MinimumRequiredVersion.ToString() +
2527 ". The application will now install the update and restart.",
2528 "Update Available", MessageBoxButtons.OK,
2529 MessageBoxIcon.Information);
2537 MessageBox.Show("The application has been upgraded, and will now restart.");
2538 Application.Restart();
2540 catch (DeploymentDownloadException dde)
2543 "Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " +
2551 MessageBox.Show("You are already running the latest version.", "Application up-to-date");
2560 private void SysTrayHideShow()
2566 WindowState = FormWindowState.Normal;
2571 /// Use to handle minimize events.
2573 /// <param name="sender"></param>
2574 /// <param name="e"></param>
2575 private void MainForm_SizeChanged(object sender, EventArgs e)
2577 if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
2589 /// <param name="sender"></param>
2590 /// <param name="e"></param>
2591 private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
2593 //Our table layout size has changed which means our display size has changed.
2594 //We need to re-create our bitmap.
2595 iCreateBitmap = true;
2601 /// <param name="sender"></param>
2602 /// <param name="e"></param>
2603 private void buttonSelectFile_Click(object sender, EventArgs e)
2605 //openFileDialog1.InitialDirectory = "c:\\";
2606 //openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
2607 //openFileDialog.FilterIndex = 1;
2608 openFileDialog.RestoreDirectory = true;
2610 if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
2612 labelStartFileName.Text = openFileDialog.FileName;
2613 Properties.Settings.Default.StartFileName = openFileDialog.FileName;
2614 Properties.Settings.Default.Save();
2621 /// <param name="sender"></param>
2622 /// <param name="e"></param>
2623 private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
2625 //Save the optical drive the user selected for ejection
2626 Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
2627 Properties.Settings.Default.Save();
2634 private void LogsUpdate()
2636 if (iWriter != null)
2644 /// Broadcast messages to subscribers.
2646 /// <param name="message"></param>
2647 protected override void WndProc(ref Message aMessage)
2651 if (OnWndProc != null)
2653 OnWndProc(ref aMessage);
2656 base.WndProc(ref aMessage);
2659 private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
2661 //Save CEC enabled status
2662 Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
2663 Properties.Settings.Default.Save();
2668 private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
2670 //Save CEC HDMI port
2671 Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
2672 Properties.Settings.Default.CecHdmiPort++;
2673 Properties.Settings.Default.Save();
2681 private void ResetCec()
2683 if (iCecManager == null)
2685 //Thus skipping initial UI setup
2691 if (Properties.Settings.Default.CecEnabled)
2693 iCecManager.Start(Handle, "CEC",
2694 Properties.Settings.Default.CecHdmiPort);
2703 private async void ResetHarmony()
2705 // ConnectAsync already if we have an existing session cookie
2706 if (Properties.Settings.Default.HarmonyEnabled && File.Exists("SessionToken"))
2709 iButtonHarmonyConnect.Enabled = false;
2712 await ConnectHarmonyAsync();
2716 iButtonHarmonyConnect.Enabled = true;
2724 private void SetupCecLogLevel()
2727 iCecManager.Client.LogLevel = 0;
2729 if (checkBoxCecLogError.Checked)
2730 iCecManager.Client.LogLevel |= (int) CecLogLevel.Error;
2732 if (checkBoxCecLogWarning.Checked)
2733 iCecManager.Client.LogLevel |= (int) CecLogLevel.Warning;
2735 if (checkBoxCecLogNotice.Checked)
2736 iCecManager.Client.LogLevel |= (int) CecLogLevel.Notice;
2738 if (checkBoxCecLogTraffic.Checked)
2739 iCecManager.Client.LogLevel |= (int) CecLogLevel.Traffic;
2741 if (checkBoxCecLogDebug.Checked)
2742 iCecManager.Client.LogLevel |= (int) CecLogLevel.Debug;
2744 iCecManager.Client.FilterOutPollLogs = checkBoxCecLogNoPoll.Checked;
2748 private void ButtonStartIdleClient_Click(object sender, EventArgs e)
2753 private void buttonClearLogs_Click(object sender, EventArgs e)
2755 richTextBoxLogs.Clear();
2758 private void checkBoxCecLogs_CheckedChanged(object sender, EventArgs e)
2767 /// <param name="aEvent"></param>
2768 private void SelectEvent(Event aEvent)
2775 foreach (TreeNode node in iTreeViewEvents.Nodes)
2777 if (node.Tag == aEvent)
2779 iTreeViewEvents.SelectedNode = node;
2780 iTreeViewEvents.Focus();
2788 /// Get the current event based on event tree view selection.
2790 /// <returns></returns>
2791 private Event CurrentEvent()
2793 //Walk up the tree from the selected node to find our event
2794 TreeNode node = iTreeViewEvents.SelectedNode;
2795 Event selectedEvent = null;
2796 while (node != null)
2798 if (node.Tag is Event)
2800 selectedEvent = (Event) node.Tag;
2806 return selectedEvent;
2810 /// Get the current action based on event tree view selection
2812 /// <returns></returns>
2813 private SharpLib.Ear.Action CurrentAction()
2815 TreeNode node = iTreeViewEvents.SelectedNode;
2816 if (node != null && node.Tag is SharpLib.Ear.Action)
2818 return (SharpLib.Ear.Action) node.Tag;
2827 /// <param name="sender"></param>
2828 /// <param name="e"></param>
2829 private void buttonActionAdd_Click(object sender, EventArgs e)
2831 Event selectedEvent = CurrentEvent();
2832 if (selectedEvent == null)
2834 //We did not find a corresponding event
2838 FormEditObject<SharpLib.Ear.Action> ea = new FormEditObject<SharpLib.Ear.Action>();
2839 ea.Text = "Add action";
2840 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2841 if (res == DialogResult.OK)
2843 selectedEvent.Actions.Add(ea.Object);
2844 Properties.Settings.Default.Events = ManagerEventAction.Current;
2845 Properties.Settings.Default.Save();
2846 PopulateEventsTreeView();
2853 /// <param name="sender"></param>
2854 /// <param name="e"></param>
2855 private void buttonActionEdit_Click(object sender, EventArgs e)
2857 Event selectedEvent = CurrentEvent();
2858 SharpLib.Ear.Action selectedAction = CurrentAction();
2859 if (selectedEvent == null || selectedAction == null)
2861 //We did not find a corresponding event
2865 FormEditObject<SharpLib.Ear.Action> ea = new FormEditObject<SharpLib.Ear.Action>();
2866 ea.Text = "Edit action";
2867 ea.Object = selectedAction;
2868 int actionIndex = iTreeViewEvents.SelectedNode.Index;
2869 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
2870 if (res == DialogResult.OK)
2873 selectedEvent.Actions[actionIndex]=ea.Object;
2874 //Save and rebuild our event tree view
2875 Properties.Settings.Default.Events = ManagerEventAction.Current;
2876 Properties.Settings.Default.Save();
2877 PopulateEventsTreeView();
2884 /// <param name="sender"></param>
2885 /// <param name="e"></param>
2886 private void buttonActionDelete_Click(object sender, EventArgs e)
2889 SharpLib.Ear.Action action = CurrentAction();
2892 //Must select action node
2896 ManagerEventAction.Current.RemoveAction(action);
2897 Properties.Settings.Default.Events = ManagerEventAction.Current;
2898 Properties.Settings.Default.Save();
2899 PopulateEventsTreeView();
2905 /// <param name="sender"></param>
2906 /// <param name="e"></param>
2907 private void buttonActionTest_Click(object sender, EventArgs e)
2909 SharpLib.Ear.Action a = CurrentAction();
2914 iTreeViewEvents.Focus();
2920 /// <param name="sender"></param>
2921 /// <param name="e"></param>
2922 private void buttonActionMoveUp_Click(object sender, EventArgs e)
2924 SharpLib.Ear.Action a = CurrentAction();
2926 //Action already at the top of the list
2927 iTreeViewEvents.SelectedNode.Index == 0)
2932 //Swap actions in event's action list
2933 Event currentEvent = CurrentEvent();
2934 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2935 SharpLib.Ear.Action movingUp = currentEvent.Actions[currentIndex];
2936 SharpLib.Ear.Action movingDown = currentEvent.Actions[currentIndex-1];
2937 currentEvent.Actions[currentIndex] = movingDown;
2938 currentEvent.Actions[currentIndex-1] = movingUp;
2940 //Save and populate our tree again
2941 Properties.Settings.Default.Events = ManagerEventAction.Current;
2942 Properties.Settings.Default.Save();
2943 PopulateEventsTreeView();
2950 /// <param name="sender"></param>
2951 /// <param name="e"></param>
2952 private void buttonActionMoveDown_Click(object sender, EventArgs e)
2954 SharpLib.Ear.Action a = CurrentAction();
2956 //Action already at the bottom of the list
2957 iTreeViewEvents.SelectedNode.Index == iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1)
2962 //Swap actions in event's action list
2963 Event currentEvent = CurrentEvent();
2964 int currentIndex = iTreeViewEvents.SelectedNode.Index;
2965 SharpLib.Ear.Action movingDown = currentEvent.Actions[currentIndex];
2966 SharpLib.Ear.Action movingUp = currentEvent.Actions[currentIndex + 1];
2967 currentEvent.Actions[currentIndex] = movingUp;
2968 currentEvent.Actions[currentIndex + 1] = movingDown;
2970 //Save and populate our tree again
2971 Properties.Settings.Default.Events = ManagerEventAction.Current;
2972 Properties.Settings.Default.Save();
2973 PopulateEventsTreeView();
2980 /// <param name="sender"></param>
2981 /// <param name="e"></param>
2982 private void buttonEventTest_Click(object sender, EventArgs e)
2984 Event earEvent = CurrentEvent();
2985 if (earEvent != null)
2992 /// Manages events and actions buttons according to selected item in event tree.
2994 /// <param name="sender"></param>
2995 /// <param name="e"></param>
2996 private void iTreeViewEvents_AfterSelect(object sender, TreeViewEventArgs e)
3004 private void UpdateEventView()
3006 //One can always add an event
3007 buttonEventAdd.Enabled = true;
3009 //Enable buttons according to selected item
3010 buttonActionAdd.Enabled =
3011 buttonEventTest.Enabled =
3012 buttonEventDelete.Enabled =
3013 buttonEventEdit.Enabled =
3014 CurrentEvent() != null;
3016 SharpLib.Ear.Action currentAction = CurrentAction();
3017 //If an action is selected enable the following buttons
3018 buttonActionTest.Enabled =
3019 buttonActionDelete.Enabled =
3020 buttonActionMoveUp.Enabled =
3021 buttonActionMoveDown.Enabled =
3022 buttonActionEdit.Enabled =
3023 currentAction != null;
3025 if (currentAction != null)
3027 //If an action is selected enable move buttons if needed
3028 buttonActionMoveUp.Enabled = iTreeViewEvents.SelectedNode.Index != 0;
3029 buttonActionMoveDown.Enabled = iTreeViewEvents.SelectedNode.Index <
3030 iTreeViewEvents.SelectedNode.Parent.Nodes.Count - 1;
3034 private void buttonEventAdd_Click(object sender, EventArgs e)
3036 FormEditObject<SharpLib.Ear.Event> ea = new FormEditObject<SharpLib.Ear.Event>();
3037 ea.Text = "Add event";
3038 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
3039 if (res == DialogResult.OK)
3041 ManagerEventAction.Current.Events.Add(ea.Object);
3042 Properties.Settings.Default.Events = ManagerEventAction.Current;
3043 Properties.Settings.Default.Save();
3044 PopulateEventsTreeView();
3045 SelectEvent(ea.Object);
3049 private void buttonEventDelete_Click(object sender, EventArgs e)
3051 SharpLib.Ear.Event currentEvent = CurrentEvent();
3052 if (currentEvent == null)
3054 //Must select action node
3058 ManagerEventAction.Current.Events.Remove(currentEvent);
3059 Properties.Settings.Default.Events = ManagerEventAction.Current;
3060 Properties.Settings.Default.Save();
3061 PopulateEventsTreeView();
3064 private void buttonEventEdit_Click(object sender, EventArgs e)
3066 Event selectedEvent = CurrentEvent();
3067 if (selectedEvent == null)
3069 //We did not find a corresponding event
3073 FormEditObject<SharpLib.Ear.Event> ea = new FormEditObject<SharpLib.Ear.Event>();
3074 ea.Text = "Edit event";
3075 ea.Object = selectedEvent;
3076 int actionIndex = iTreeViewEvents.SelectedNode.Index;
3077 DialogResult res = CodeProject.Dialog.DlgBox.ShowDialog(ea);
3078 if (res == DialogResult.OK)
3080 //Save and rebuild our event tree view
3081 Properties.Settings.Default.Events = ManagerEventAction.Current;
3082 Properties.Settings.Default.Save();
3083 PopulateEventsTreeView();
3087 private void iTreeViewEvents_Leave(object sender, EventArgs e)
3089 //Make sure our event tree never looses focus
3090 ((TreeView) sender).Focus();
3093 private async void iButtonHarmonyConnect_Click(object sender, EventArgs e)
3096 Properties.Settings.Default.HarmonyHubAddress = iTextBoxHarmonyHubAddress.Text;
3097 Properties.Settings.Default.Save();
3099 iButtonHarmonyConnect.Enabled = false;
3102 await ConnectHarmonyAsync();
3106 iButtonHarmonyConnect.Enabled = true;
3112 private async Task ConnectHarmonyAsync()
3114 Console.WriteLine("Harmony: Connecting... ");
3115 //First create our client and login
3116 if (File.Exists("SessionToken"))
3118 var sessionToken = File.ReadAllText("SessionToken");
3119 Console.WriteLine("Harmony: Reusing token: {0}", sessionToken);
3120 Program.HarmonyClient = HarmonyHub.HarmonyClient.Create(iTextBoxHarmonyHubAddress.Text, sessionToken);
3124 if (string.IsNullOrEmpty(iTextBoxLogitechPassword.Text))
3126 Console.WriteLine("Harmony: Credentials missing!");
3130 Console.WriteLine("Harmony: Authenticating with Logitech servers...");
3131 Program.HarmonyClient = await HarmonyHub.HarmonyClient.Create(iTextBoxHarmonyHubAddress.Text, iTextBoxLogitechUserName.Text, iTextBoxLogitechPassword.Text);
3132 File.WriteAllText("SessionToken", Program.HarmonyClient.Token);
3135 Console.WriteLine("Harmony: Fetching Harmony Hub configuration...");
3138 var harmonyConfig = await Program.HarmonyClient.GetConfigAsync();
3139 PopulateTreeViewHarmony(harmonyConfig);
3141 Console.WriteLine("Harmony: Ready");
3147 /// <param name="aConfig"></param>
3148 private void PopulateTreeViewHarmony(HarmonyHub.Entities.Response.Config aConfig)
3150 iTreeViewHarmony.Nodes.Clear();
3152 foreach (HarmonyHub.Entities.Response.Device device in aConfig.Devices)
3154 TreeNode deviceNode = iTreeViewHarmony.Nodes.Add(device.Id, $"{device.Label} ({device.DeviceTypeDisplayName}/{device.Model})");
3155 deviceNode.Tag = device;
3157 foreach (HarmonyHub.Entities.Response.ControlGroup cg in device.ControlGroups)
3159 TreeNode cgNode = deviceNode.Nodes.Add(cg.Name);
3162 foreach (HarmonyHub.Entities.Response.Function f in cg.Functions)
3164 TreeNode fNode = cgNode.Nodes.Add(f.Name);
3170 //treeViewConfig.ExpandAll();
3173 private void iCheckBoxHarmonyEnabled_CheckedChanged(object sender, EventArgs e)
3175 Properties.Settings.Default.HarmonyEnabled = iCheckBoxHarmonyEnabled.Checked;
3176 Properties.Settings.Default.Save();
3179 private async void iTreeViewHarmony_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
3181 //Upon function node double click we execute it
3182 var tag = e.Node.Tag as HarmonyHub.Entities.Response.Function;
3183 if (tag != null && e.Node.Parent.Parent.Tag is HarmonyHub.Entities.Response.Device)
3185 HarmonyHub.Entities.Response.Function f = tag;
3186 HarmonyHub.Entities.Response.Device d = (HarmonyHub.Entities.Response.Device)e.Node.Parent.Parent.Tag;
3188 Console.WriteLine($"Harmony: Sending {f.Name} to {d.Label}...");
3190 await Program.HarmonyClient.SendCommandAsync(d.Id, f.Name);