# HG changeset patch # User StephaneLenclud # Date 1420151749 -3600 # Node ID af40a92d480decae549861b21c0ecbc66bea7cfa # Parent 0cb7b9f6a6f8f30b354e1f3b75a9dce607b40e0f SharpDisplay: Migrating to new robust client scheme. diff -r 0cb7b9f6a6f8 -r af40a92d480d GUI/SharpDisplay.cs --- a/GUI/SharpDisplay.cs Mon Sep 22 21:59:11 2014 +0200 +++ b/GUI/SharpDisplay.cs Thu Jan 01 23:35:49 2015 +0100 @@ -25,19 +25,19 @@ namespace OpenHardwareMonitor.GUI { - public class SharpDisplay : ICallback, IDisposable + public class SharpDisplay : IDisposable { private IComputer computer; private PersistentSettings settings; private UnitManager unitManager; private List iSensors = new List(); - private global::SharpDisplay.Client iClient; - TextField iTextFieldTop; - TextField iTextFieldBottom; - TextField iTextFieldTopRight; - TextField iTextFieldBottomRight; + private global::SharpDisplay.DisplayClient iClient; + DataField iTextFieldTop; + DataField iTextFieldBottom; + DataField iTextFieldTopRight; + DataField iTextFieldBottomRight; - TextField[] iTextFields; + DataField[] iTextFields; private int iNextSensorToDisplay = 0; private int iTickCounter = 0; @@ -53,32 +53,18 @@ //Connect our client //Instance context is then managed by our client class - iClient = new global::SharpDisplay.Client(this); + iClient = new global::SharpDisplay.DisplayClient(); // - iTextFieldTop = new TextField(0); - iTextFieldBottom = new TextField(1); - iTextFieldTopRight = new TextField(2, "", ContentAlignment.MiddleRight); - iTextFieldBottomRight = new TextField(3, "", ContentAlignment.MiddleRight); - - iTextFields = new TextField[] { iTextFieldTop, iTextFieldBottom }; - + iTextFieldTop = new DataField(0); + iTextFieldBottom = new DataField(1); + iTextFieldTopRight = new DataField(2, "", ContentAlignment.MiddleRight); + iTextFieldBottomRight = new DataField(3, "", ContentAlignment.MiddleRight); + // + iClient.Open(); + iClient.SetName("Open Hardware Monitor"); + CreateFields(); } - //From ICallback - public void OnConnected() - { - //Debug.Assert(Thread.CurrentThread.IsThreadPoolThread); - //Trace.WriteLine("Callback thread = " + Thread.CurrentThread.ManagedThreadId); - //MessageBox.Show("OnConnected()", "Client"); - } - - //From ICallback - public void OnCloseOrder() - { - iClient.Close(); - } - - private void HardwareRemoved(IHardware hardware) { hardware.SensorAdded -= new SensorEventHandler(SensorAdded); @@ -123,6 +109,27 @@ } + private void CreateFields() + { + if (iPacked) + { + //We just switched to packed mode + //Make sure our layout is proper + TableLayout layout = new TableLayout(2, 2); + iClient.SetLayout(layout); + iTextFields = new DataField[] { iTextFieldTop, iTextFieldBottom, iTextFieldTopRight, iTextFieldBottomRight }; + iClient.CreateFields(iTextFields); + } + else + { + //Non packed mode + TableLayout layout = new TableLayout(1, 2); + iClient.SetLayout(layout); + iTextFields = new DataField[] { iTextFieldTop, iTextFieldBottom }; + iClient.CreateFields(iTextFields); + } + } + public void Redraw(bool aPacked, bool aDisplayTime) { const int KNumberOfTickBeforeSwitch = 4; @@ -141,19 +148,8 @@ //Remember mode iPacked = aPacked; - if (iPacked) - { - //We just switched to packed mode - //Make sure our layout is proper - TableLayout layout = new TableLayout(2, 2); - iClient.SetLayout(layout); - } - else - { - //Non packed mode - TableLayout layout = new TableLayout(1, 2); - iClient.SetLayout(layout); - } + CreateFields(); + } } @@ -169,7 +165,7 @@ //First slot is taken by time display count++; iTextFieldTop.Text = time; - iClient.SetText(iTextFieldTop); + iClient.SetField(iTextFieldTop); } if (aPacked) @@ -180,22 +176,22 @@ if (count == 1) { iTextFieldTop.Text = packedText; - iClient.SetText(iTextFieldTop); + iClient.SetField(iTextFieldTop); } else if (count == 2) { iTextFieldBottom.Text = packedText; - iClient.SetText(iTextFieldBottom); + iClient.SetField(iTextFieldBottom); } else if (count == 3) { iTextFieldTopRight.Text = packedText; - iClient.SetText(iTextFieldTopRight); + iClient.SetField(iTextFieldTopRight); } else if (count == 4) { iTextFieldBottomRight.Text = packedText; - iClient.SetText(iTextFieldBottomRight); + iClient.SetField(iTextFieldBottomRight); } } } @@ -345,7 +341,7 @@ //iServer.SendMessage("set-vfd-text:" + aUpperLine + "\n" + aLowerLine); iTextFieldTop.Text = aUpperLine; iTextFieldBottom.Text = aLowerLine; - iClient.SetTexts(iTextFields); + iClient.SetFields(iTextFields); } diff -r 0cb7b9f6a6f8 -r af40a92d480d GUI/SharpDisplayClient.cs --- a/GUI/SharpDisplayClient.cs Mon Sep 22 21:59:11 2014 +0200 +++ b/GUI/SharpDisplayClient.cs Thu Jan 01 23:35:49 2015 +0100 @@ -1,207 +1,64 @@ -/* - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - - Copyright (C) 2009-2012 Michael Möller - -*/ - + using System; using System.Collections.Generic; -using System.Drawing; +using System.Linq; using System.Text; -using System.Diagnostics; +using System.Threading.Tasks; using System.Windows.Forms; -using System.Windows; -using OpenHardwareMonitor.Hardware; -using OpenHardwareMonitor.Utilities; -using System.Runtime.InteropServices; -using UacHelpers; -// +using SharpDisplay; using System.ServiceModel; -using System.Runtime.Serialization; -using SharpDisplay; +using System.ServiceModel.Channels; + namespace SharpDisplay { - - - - /// - /// For client to specify a specific layout. - /// - [DataContract] - public class TableLayout - { - public TableLayout() - { - Columns = new List(); - Rows = new List(); - Cells = new List(); - } - - public TableLayout(int aColumnCount, int aRowCount) - { - Columns = new List(); - Rows = new List(); - - for (int i = 0; i < aColumnCount; i++) - { - Columns.Add(new ColumnStyle(SizeType.Percent, 100 / aColumnCount)); - } - - for (int i = 0; i < aRowCount; i++) - { - Rows.Add(new RowStyle(SizeType.Percent, 100 / aRowCount)); - } - } - - [DataMember] - public List Cells { get; set; } - - [DataMember] - public List Columns { get; set; } - - [DataMember] - public List Rows { get; set; } - - } - /// /// /// - [DataContract] - public class DataField + [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] + public class Callback : ICallback, IDisposable { - [DataMember] - public int Column { get; set; } + private DisplayClient DisplayClient { get; set; } - [DataMember] - public int Row { get; set; } + public Callback(DisplayClient aClient) + { + DisplayClient = aClient; + } - [DataMember] - public int ColumnSpan { get; set; } + public void OnConnected() + { + //Debug.Assert(Thread.CurrentThread.IsThreadPoolThread); + //Trace.WriteLine("Callback thread = " + Thread.CurrentThread.ManagedThreadId); - [DataMember] - public int RowSpan { get; set; } + MessageBox.Show("OnConnected()", "Client"); + } + + public void OnCloseOrder() + { + DisplayClient.Close(); + //Debug.Assert(Thread.CurrentThread.IsThreadPoolThread); + //Trace.WriteLine("Callback thread = " + Thread.CurrentThread.ManagedThreadId); + + //MessageBox.Show("OnServerClosing()", "Client"); + //MainForm.CloseConnectionThreadSafe(); + //MainForm.CloseThreadSafe(); + } + + //From IDisposable + public void Dispose() + { + + } } /// - /// TextField can be send to our server to be displayed on the screen. - /// - [DataContract] - public class TextField : DataField - { - public TextField() - { - Index = 0; - Text = ""; - Alignment = ContentAlignment.MiddleLeft; - } - - public TextField(int aIndex, string aText = "", ContentAlignment aAlignment = ContentAlignment.MiddleLeft) - { - Index = aIndex; - Text = aText; - Alignment = aAlignment; - } - - [DataMember] - public int Index { get; set; } - - [DataMember] - public string Text { get; set; } - - [DataMember] - public ContentAlignment Alignment { get; set; } - } - - /// - /// Define our SharpDisplay service. - /// Clients and servers must implement it to communicate with one another. - /// Through this service clients can send requests to a server. - /// Through this service a server session can receive requests from a client. - /// - [ServiceContract(CallbackContract = typeof(ICallback), SessionMode = SessionMode.Required)] - public interface IService - { - /// - /// Set the name of this client. - /// Name is a convenient way to recognize your client. - /// Naming you client is not mandatory. - /// In the absence of a name the session ID is often used instead. - /// - /// - [OperationContract(IsOneWay = true)] - void SetName(string aClientName); - - - /// - /// - /// - [OperationContract(IsOneWay = true)] - void SetLayout(TableLayout aLayout); - - /// - /// Put the given text in the given field on your display. - /// Fields are often just lines of text. - /// - /// - [OperationContract(IsOneWay = true)] - void SetText(TextField aTextField); - - /// - /// Allows a client to set multiple text fields at once. - /// - /// - [OperationContract(IsOneWay = true)] - void SetTexts(System.Collections.Generic.IList aTextFields); - - /// - /// Provides the number of clients currently connected - /// - /// - [OperationContract()] - int ClientCount(); - - } - - /// - /// SharDisplay callback provides a means for a server to notify its clients. - /// - public interface ICallback - { - [OperationContract(IsOneWay = true)] - void OnConnected(); - - /// - /// Tell our client to close its connection. - /// Notably sent when the server is shutting down. - /// - [OperationContract(IsOneWay = true)] - void OnCloseOrder(); - } - - - -} - - - -namespace SharpDisplay -{ - - /// /// /// [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] public class Client : DuplexClientBase { - public string Name { get; set; } public string SessionId { get { return InnerChannel.SessionId; } } public Client(ICallback aCallback) @@ -210,7 +67,6 @@ public void SetName(string aClientName) { - Name = aClientName; Channel.SetName(aClientName); } @@ -219,14 +75,14 @@ Channel.SetLayout(aLayout); } - public void SetText(TextField aTextField) + public void SetField(DataField aField) { - Channel.SetText(aTextField); + Channel.SetField(aField); } - public void SetTexts(System.Collections.Generic.IList aTextFields) + public void SetFields(System.Collections.Generic.IList aFields) { - Channel.SetTexts(aTextFields); + Channel.SetFields(aFields); } public int ClientCount() @@ -236,10 +92,200 @@ public bool IsReady() { - return State == CommunicationState.Opened; + return State == CommunicationState.Opened || State == CommunicationState.Created; } + } + + + /// + /// Handle connection with our Sharp Display Server. + /// If the connection is faulted it will attempt to restart it. + /// + public class DisplayClient + { + private Client iClient; + private Callback iCallback; + private bool resetingConnection = false; + + //private MainForm MainForm { get; set; } + public string SessionId { get { return iClient.SessionId; } } + public string Name { get; private set; } + private TableLayout Layout { get; set; } + private System.Collections.Generic.IList Fields { get; set; } + + + public DisplayClient(/*MainForm aMainForm*/) + { + //MainForm = aMainForm; + Name = ""; + Fields = new DataField[]{}; + } + + /// + /// Initialize our server connection. + /// + public void Open() + { + iCallback = new Callback(this); + iClient = new Client(iCallback); + } + + /// + /// Terminate our server connection. + /// + public void Close() + { + iClient.Close(); + iClient = null; + iCallback.Dispose(); + iCallback = null; + } + + /// + /// Tells whether a server connection is available. + /// + /// True if a server connection is available. False otherwise. + public bool IsReady() + { + return (iClient != null && iCallback != null && iClient.IsReady()); + } + + /// + /// Check if our server connection is available and attempt reset it if it isn't. + /// This is notably dealing with timed out connections. + /// + public void CheckConnection() + { + if (!IsReady() && !resetingConnection) + { + //Try to reconnect + Open(); + + //Avoid stack overflow in case of persisting failure + resetingConnection = true; + + try + { + //On reconnect there is a bunch of properties we need to reset + if (Name != "") + { + iClient.SetName(Name); + } + + SetLayout(Layout); + iClient.SetFields(Fields); + } + finally + { + //Make sure our this state does not get out of sync + resetingConnection = true; + } + } + } + + public void SetName(string aClientName) + { + Name = aClientName; + CheckConnection(); + iClient.SetName(aClientName); + } + + + public void SetLayout(TableLayout aLayout) + { + Layout = aLayout; + CheckConnection(); + iClient.SetLayout(aLayout); + } + + /// + /// Set the specified field. + /// + /// + /// True if the specified field was set client side. False means you need to redefine all your fields using CreateFields. + public bool SetField(DataField aField) + { + int i = 0; + bool fieldFound = false; + foreach (DataField field in Fields) + { + if (field.Index == aField.Index) + { + //Update our field then + Fields[i] = aField; + fieldFound = true; + break; + } + i++; + } + + if (!fieldFound) + { + //Field not found, make to use SetFields with all your fields at least once after setting your layout. + return false; + } + + CheckConnection(); + iClient.SetField(aField); + return true; + } + + /// + /// Use this function when updating existing fields. + /// + /// + public bool SetFields(System.Collections.Generic.IList aFields) + { + int fieldFoundCount = 0; + foreach (DataField fieldUpdate in aFields) + { + int i = 0; + foreach (DataField existingField in Fields) + { + if (existingField.Index == fieldUpdate.Index) + { + //Update our field then + Fields[i] = fieldUpdate; + fieldFoundCount++; + //Move on to the next field + break; + } + i++; + } + } + + // + if (fieldFoundCount!=aFields.Count) + { + //Field not found, make sure to use SetFields with all your fields at least once after setting your layout. + return false; + } + + CheckConnection(); + iClient.SetFields(aFields); + return true; + } + + /// + /// Use this function when creating your fields. + /// + /// + public void CreateFields(System.Collections.Generic.IList aFields) + { + Fields = aFields; + CheckConnection(); + iClient.SetFields(aFields); + } + + public int ClientCount() + { + CheckConnection(); + return iClient.ClientCount(); + } + + } -} \ No newline at end of file +} diff -r 0cb7b9f6a6f8 -r af40a92d480d GUI/SharpDisplayInterface.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/GUI/SharpDisplayInterface.cs Thu Jan 01 23:35:49 2015 +0100 @@ -0,0 +1,204 @@ +// +// Define a public API for both SharpDisplay client and server to use. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ServiceModel; +using System.Collections; +using System.Drawing; +using System.Runtime.Serialization; +using System.Windows.Forms; + + +namespace SharpDisplay +{ + /// + /// For client to specify a specific layout. + /// + [DataContract] + public class TableLayout + { + public TableLayout() + { + Columns = new List(); + Rows = new List(); + Cells = new List(); + } + + public TableLayout(int aColumnCount, int aRowCount) + { + Columns = new List(); + Rows = new List(); + + for (int i = 0; i < aColumnCount; i++) + { + Columns.Add(new ColumnStyle(SizeType.Percent, 100 / aColumnCount)); + } + + for (int i = 0; i < aRowCount; i++) + { + Rows.Add(new RowStyle(SizeType.Percent, 100 / aRowCount)); + } + } + + [DataMember] + public List Cells { get; set; } + + [DataMember] + public List Columns { get; set; } + + [DataMember] + public List Rows { get; set; } + } + + /// + /// + /// + [DataContract] + public class DataField + { + public DataField() + { + Index = 0; + ColumnSpan = 1; + RowSpan = 1; + //Text + Text = ""; + Alignment = ContentAlignment.MiddleLeft; + //Bitmap + Bitmap = null; + } + + //Text constructor + public DataField(int aIndex, string aText = "", ContentAlignment aAlignment = ContentAlignment.MiddleLeft) + { + ColumnSpan = 1; + RowSpan = 1; + Index = aIndex; + Text = aText; + Alignment = aAlignment; + // + Bitmap = null; + } + + //Bitmap constructor + public DataField(int aIndex, Bitmap aBitmap) + { + ColumnSpan = 1; + RowSpan = 1; + Index = aIndex; + Bitmap = aBitmap; + //Text + Text = ""; + Alignment = ContentAlignment.MiddleLeft; + } + + + //Generic layout properties + [DataMember] + public int Index { get; set; } + + [DataMember] + public int Column { get; set; } + + [DataMember] + public int Row { get; set; } + + [DataMember] + public int ColumnSpan { get; set; } + + [DataMember] + public int RowSpan { get; set; } + + //Text properties + [DataMember] + public string Text { get; set; } + + [DataMember] + public ContentAlignment Alignment { get; set; } + + //Bitmap properties + [DataMember] + public Bitmap Bitmap { get; set; } + + // + public bool IsBitmap { get { return Bitmap != null; } } + // + public bool IsText { get { return Bitmap == null; } } + // + public bool IsSameLayout(DataField aField) + { + return (aField.ColumnSpan == ColumnSpan && aField.RowSpan == RowSpan); + } + } + + /// + /// Define our SharpDisplay service. + /// Clients and servers must implement it to communicate with one another. + /// Through this service clients can send requests to a server. + /// Through this service a server session can receive requests from a client. + /// + [ServiceContract(CallbackContract = typeof(ICallback), SessionMode = SessionMode.Required)] + public interface IService + { + /// + /// Set the name of this client. + /// Name is a convenient way to recognize your client. + /// Naming you client is not mandatory. + /// In the absence of a name the session ID is often used instead. + /// + /// + [OperationContract(IsOneWay = true)] + void SetName(string aClientName); + + /// + /// + /// + [OperationContract(IsOneWay = true)] + void SetLayout(TableLayout aLayout); + + /// + /// Set the given field on your display. + /// Fields are often just lines of text or bitmaps. + /// + /// + [OperationContract(IsOneWay = true)] + void SetField(DataField aField); + + /// + /// Allows a client to set multiple fields at once. + /// + /// + [OperationContract(IsOneWay = true)] + void SetFields(System.Collections.Generic.IList aFields); + + /// + /// Provides the number of clients currently connected + /// + /// + [OperationContract()] + int ClientCount(); + + } + + /// + /// SharDisplay callback provides a means for a server to notify its clients. + /// + public interface ICallback + { + [OperationContract(IsOneWay = true)] + void OnConnected(); + + /// + /// Tell our client to close its connection. + /// Notably sent when the server is shutting down. + /// + [OperationContract(IsOneWay = true)] + void OnCloseOrder(); + } + +} diff -r 0cb7b9f6a6f8 -r af40a92d480d OpenHardwareMonitor.csproj --- a/OpenHardwareMonitor.csproj Mon Sep 22 21:59:11 2014 +0200 +++ b/OpenHardwareMonitor.csproj Thu Jan 01 23:35:49 2015 +0100 @@ -117,6 +117,7 @@ +