// 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) LibreHardwareMonitor and Contributors. // Partial Copyright (C) Michael Möller and Contributors. // All Rights Reserved. using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms; using System.IO; using System.Linq; using LibreHardwareMonitor.Hardware; using LibreHardwareMonitor.Utilities; namespace LibreHardwareMonitor.UI; public class SensorGadget : Gadget { private const int TopBorder = 6; private const int BottomBorder = 7; private const int LeftBorder = 6; private const int RightBorder = 7; private readonly UnitManager _unitManager; private Image _back = Utilities.EmbeddedResources.GetImage("gadget.png"); private Image _image; private Image _fore; private Image _barBack = Utilities.EmbeddedResources.GetImage("barback.png"); private Image _barFore = Utilities.EmbeddedResources.GetImage("barblue.png"); private Image _background = new Bitmap(1, 1); private readonly float _scale; private float _fontSize; private int _iconSize; private int _hardwareLineHeight; private int _sensorLineHeight; private int _rightMargin; private int _leftMargin; private int _topMargin; private int _bottomMargin; private int _progressWidth; private readonly IDictionary> _sensors = new SortedDictionary>(new HardwareComparer()); private readonly PersistentSettings _settings; private readonly UserOption _hardwareNames; private Font _largeFont; private Font _smallFont; private Brush _textBrush; private StringFormat _stringFormat; private StringFormat _trimStringFormat; private StringFormat _alignRightStringFormat; public SensorGadget(IComputer computer, PersistentSettings settings, UnitManager unitManager) { _unitManager = unitManager; _settings = settings; computer.HardwareAdded += HardwareAdded; computer.HardwareRemoved += HardwareRemoved; _stringFormat = new StringFormat { FormatFlags = StringFormatFlags.NoWrap }; _trimStringFormat = new StringFormat { Trimming = StringTrimming.EllipsisCharacter, FormatFlags = StringFormatFlags.NoWrap }; _alignRightStringFormat = new StringFormat { Alignment = StringAlignment.Far, FormatFlags = StringFormatFlags.NoWrap }; if (File.Exists("gadget_background.png")) { try { Image newBack = new Bitmap("gadget_background.png"); _back.Dispose(); _back = newBack; } catch { } } if (File.Exists("gadget_image.png")) { try { _image = new Bitmap("gadget_image.png"); } catch { } } if (File.Exists("gadget_foreground.png")) { try { _fore = new Bitmap("gadget_foreground.png"); } catch { } } if (File.Exists("gadget_bar_background.png")) { try { Image newBarBack = new Bitmap("gadget_bar_background.png"); _barBack.Dispose(); _barBack = newBarBack; } catch { } } if (File.Exists("gadget_bar_foreground.png")) { try { Image newBarColor = new Bitmap("gadget_bar_foreground.png"); _barFore.Dispose(); _barFore = newBarColor; } catch { } } Location = new Point(settings.GetValue("sensorGadget.Location.X", 100), settings.GetValue("sensorGadget.Location.Y", 100)); LocationChanged += delegate { settings.SetValue("sensorGadget.Location.X", Location.X); settings.SetValue("sensorGadget.Location.Y", Location.Y); }; // get the custom to default dpi ratio using (Bitmap b = new Bitmap(1, 1)) { _scale = b.HorizontalResolution / 96.0f; } SetFontSize(settings.GetValue("sensorGadget.FontSize", 7.5f)); Resize(settings.GetValue("sensorGadget.Width", Size.Width)); ContextMenuStrip contextMenuStrip = new ContextMenuStrip(); ToolStripMenuItem hardwareNamesItem = new ToolStripMenuItem("Hardware Names"); contextMenuStrip.Items.Add(hardwareNamesItem); ToolStripMenuItem fontSizeMenu = new ToolStripMenuItem("Font Size"); for (int i = 0; i < 4; i++) { float size; string name; switch (i) { case 0: size = 6.5f; name = "Small"; break; case 1: size = 7.5f; name = "Medium"; break; case 2: size = 9f; name = "Large"; break; case 3: size = 11f; name = "Very Large"; break; default: throw new NotImplementedException(); } ToolStripItem item = new ToolStripMenuItem(name) { Checked = _fontSize == size }; item.Click += delegate { SetFontSize(size); settings.SetValue("sensorGadget.FontSize", size); foreach (ToolStripMenuItem mi in fontSizeMenu.DropDownItems) mi.Checked = mi == item; }; fontSizeMenu.DropDownItems.Add(item); } contextMenuStrip.Items.Add(fontSizeMenu); Color fontColor = settings.GetValue("sensorGadget.FontColor", Color.White); int fontColorArgb = fontColor.ToArgb(); SetFontColor(fontColor); IEnumerable providedColors = Enum.GetValues(typeof(KnownColor)) .Cast() .Select(x => Color.FromKnownColor(x)) .Where(x => !x.IsSystemColor && x.Name.Length < 7); ToolStripMenuItem fontColorMenu = new ToolStripMenuItem("Font Color"); foreach (Color color in providedColors) { ToolStripItem item = new ToolStripMenuItem(color.Name) { Checked = fontColorArgb == color.ToArgb() }; item.Click += delegate { SetFontColor(color); settings.SetValue("sensorGadget.FontColor", color); foreach (ToolStripMenuItem mi in fontColorMenu.DropDownItems) mi.Checked = mi == item; }; fontColorMenu.DropDownItems.Add(item); } contextMenuStrip.Items.Add(fontColorMenu); contextMenuStrip.Items.Add(new ToolStripSeparator()); ToolStripMenuItem lockItem = new ToolStripMenuItem("Lock Position and Size"); contextMenuStrip.Items.Add(lockItem); contextMenuStrip.Items.Add(new ToolStripSeparator()); ToolStripMenuItem alwaysOnTopItem = new ToolStripMenuItem("Always on Top"); contextMenuStrip.Items.Add(alwaysOnTopItem); ToolStripMenuItem opacityMenu = new ToolStripMenuItem("Opacity"); contextMenuStrip.Items.Add(opacityMenu); Opacity = (byte)settings.GetValue("sensorGadget.Opacity", 255); for (int i = 0; i < 5; i++) { ToolStripMenuItem item = new ToolStripMenuItem((20 * (i + 1)).ToString() + " %"); byte o = (byte)(51 * (i + 1)); item.Checked = Opacity == o; item.Click += delegate { Opacity = o; settings.SetValue("sensorGadget.Opacity", Opacity); foreach (ToolStripMenuItem mi in opacityMenu.DropDownItems) mi.Checked = mi == item; }; opacityMenu.DropDownItems.Add(item); } ContextMenuStrip = contextMenuStrip; _hardwareNames = new UserOption("sensorGadget.Hardwarenames", true, hardwareNamesItem, settings); _hardwareNames.Changed += delegate { Resize(); }; UserOption alwaysOnTop = new UserOption("sensorGadget.AlwaysOnTop", false, alwaysOnTopItem, settings); alwaysOnTop.Changed += delegate { AlwaysOnTop = alwaysOnTop.Value; }; UserOption lockPositionAndSize = new UserOption("sensorGadget.LockPositionAndSize", false, lockItem, settings); lockPositionAndSize.Changed += delegate { LockPositionAndSize = lockPositionAndSize.Value; }; HitTest += delegate (object sender, HitTestEventArgs e) { if (lockPositionAndSize.Value) return; if (e.Location.X < LeftBorder) { e.HitResult = HitResult.Left; return; } if (e.Location.X > Size.Width - 1 - RightBorder) { e.HitResult = HitResult.Right; } }; SizeChanged += delegate { settings.SetValue("sensorGadget.Width", Size.Width); Redraw(); }; VisibleChanged += delegate { Rectangle bounds = new Rectangle(Location, Size); Screen screen = Screen.FromRectangle(bounds); Rectangle intersection = Rectangle.Intersect(screen.WorkingArea, bounds); if (intersection.Width < Math.Min(16, bounds.Width) || intersection.Height < Math.Min(16, bounds.Height)) { Location = new Point(screen.WorkingArea.Width / 2 - bounds.Width / 2, screen.WorkingArea.Height / 2 - bounds.Height / 2); } }; MouseDoubleClick += delegate { SendHideShowCommand(); }; } public override void Dispose() { _largeFont.Dispose(); _largeFont = null; _smallFont.Dispose(); _smallFont = null; _textBrush.Dispose(); _textBrush = null; _stringFormat.Dispose(); _stringFormat = null; _trimStringFormat.Dispose(); _trimStringFormat = null; _alignRightStringFormat.Dispose(); _alignRightStringFormat = null; _back.Dispose(); _back = null; _barFore.Dispose(); _barFore = null; _barBack.Dispose(); _barBack = null; _background.Dispose(); _background = null; if (_image != null) { _image.Dispose(); _image = null; } if (_fore != null) { _fore.Dispose(); _fore = null; } base.Dispose(); } private void HardwareRemoved(IHardware hardware) { hardware.SensorAdded -= SensorAdded; hardware.SensorRemoved -= SensorRemoved; foreach (ISensor sensor in hardware.Sensors) SensorRemoved(sensor); foreach (IHardware subHardware in hardware.SubHardware) HardwareRemoved(subHardware); } private void HardwareAdded(IHardware hardware) { foreach (ISensor sensor in hardware.Sensors) SensorAdded(sensor); hardware.SensorAdded += SensorAdded; hardware.SensorRemoved += SensorRemoved; foreach (IHardware subHardware in hardware.SubHardware) HardwareAdded(subHardware); } private void SensorAdded(ISensor sensor) { if (_settings.GetValue(new Identifier(sensor.Identifier, "gadget").ToString(), false)) Add(sensor); } private void SensorRemoved(ISensor sensor) { if (Contains(sensor)) Remove(sensor, false); } public bool Contains(ISensor sensor) { return _sensors.Values.Any(list => list.Contains(sensor)); } public void Add(ISensor sensor) { if (Contains(sensor)) return; // get the right hardware IHardware hardware = sensor.Hardware; while (hardware.Parent != null) hardware = hardware.Parent; // get the sensor list associated with the hardware if (!_sensors.TryGetValue(hardware, out IList list)) { list = new List(); _sensors.Add(hardware, list); } // insert the sensor at the right position int i = 0; while (i < list.Count && (list[i].SensorType < sensor.SensorType || (list[i].SensorType == sensor.SensorType && list[i].Index < sensor.Index))) i++; list.Insert(i, sensor); _settings.SetValue(new Identifier(sensor.Identifier, "gadget").ToString(), true); Resize(); } public void Remove(ISensor sensor) { Remove(sensor, true); } private void Remove(ISensor sensor, bool deleteConfig) { if (deleteConfig) _settings.Remove(new Identifier(sensor.Identifier, "gadget").ToString()); foreach (KeyValuePair> keyValue in _sensors) { if (keyValue.Value.Contains(sensor)) { keyValue.Value.Remove(sensor); if (keyValue.Value.Count == 0) { _sensors.Remove(keyValue.Key); break; } } } Resize(); } public event EventHandler HideShowCommand; public void SendHideShowCommand() { HideShowCommand?.Invoke(this, null); } private Font CreateFont(float size, FontStyle style) { try { return new Font(SystemFonts.MessageBoxFont.FontFamily, size, style); } catch (ArgumentException) { // if the style is not supported, fall back to the original one return new Font(SystemFonts.MessageBoxFont.FontFamily, size, SystemFonts.MessageBoxFont.Style); } } private void SetFontSize(float size) { _fontSize = size; _largeFont = CreateFont(_fontSize, FontStyle.Bold); _smallFont = CreateFont(_fontSize, FontStyle.Regular); double scaledFontSize = _fontSize * _scale; _iconSize = (int)Math.Round(1.5 * scaledFontSize); _hardwareLineHeight = (int)Math.Round(1.66 * scaledFontSize); _sensorLineHeight = (int)Math.Round(1.33 * scaledFontSize); _leftMargin = LeftBorder + (int)Math.Round(0.3 * scaledFontSize); _rightMargin = RightBorder + (int)Math.Round(0.3 * scaledFontSize); _topMargin = TopBorder; _bottomMargin = BottomBorder + (int)Math.Round(0.3 * scaledFontSize); _progressWidth = (int)Math.Round(5.3 * scaledFontSize); Resize((int)Math.Round(17.3 * scaledFontSize)); } private void SetFontColor(Color color) { _textBrush?.Dispose(); _textBrush = new SolidBrush(color); } private void Resize() { Resize(Size.Width); } private void Resize(int width) { int y = _topMargin; foreach (KeyValuePair> pair in _sensors) { if (_hardwareNames.Value) { if (y > _topMargin) y += _hardwareLineHeight - _sensorLineHeight; y += _hardwareLineHeight; } y += pair.Value.Count * _sensorLineHeight; } if (_sensors.Count == 0) y += 4 * _sensorLineHeight + _hardwareLineHeight; y += _bottomMargin; Size = new Size(width, y); } private void DrawImageWidthBorder(Graphics g, int width, int height, Image back, int t, int b, int l, int r) { GraphicsUnit u = GraphicsUnit.Pixel; g.DrawImage(back, new Rectangle(0, 0, l, t), new Rectangle(0, 0, l, t), u); g.DrawImage(back, new Rectangle(l, 0, width - l - r, t), new Rectangle(l, 0, back.Width - l - r, t), u); g.DrawImage(back, new Rectangle(width - r, 0, r, t), new Rectangle(back.Width - r, 0, r, t), u); g.DrawImage(back, new Rectangle(0, t, l, height - t - b), new Rectangle(0, t, l, back.Height - t - b), u); g.DrawImage(back, new Rectangle(l, t, width - l - r, height - t - b), new Rectangle(l, t, back.Width - l - r, back.Height - t - b), u); g.DrawImage(back, new Rectangle(width - r, t, r, height - t - b), new Rectangle(back.Width - r, t, r, back.Height - t - b), u); g.DrawImage(back, new Rectangle(0, height - b, l, b), new Rectangle(0, back.Height - b, l, b), u); g.DrawImage(back, new Rectangle(l, height - b, width - l - r, b), new Rectangle(l, back.Height - b, back.Width - l - r, b), u); g.DrawImage(back, new Rectangle(width - r, height - b, r, b), new Rectangle(back.Width - r, back.Height - b, r, b), u); } private void DrawBackground(Graphics g) { int w = Size.Width; int h = Size.Height; if (w != _background.Width || h != _background.Height) { _background.Dispose(); _background = new Bitmap(w, h, PixelFormat.Format32bppPArgb); using (Graphics graphics = Graphics.FromImage(_background)) { DrawImageWidthBorder(graphics, w, h, _back, TopBorder, BottomBorder,LeftBorder, RightBorder); if (_fore != null) DrawImageWidthBorder(graphics, w, h, _fore, TopBorder, BottomBorder, LeftBorder, RightBorder); if (_image != null) { int width = w - LeftBorder - RightBorder; int height = h - TopBorder - BottomBorder; float xRatio = width / (float)_image.Width; float yRatio = height / (float)_image.Height; float destWidth, destHeight; float xOffset, yOffset; if (xRatio < yRatio) { destWidth = width; destHeight = _image.Height * xRatio; xOffset = 0; yOffset = 0.5f * (height - destHeight); } else { destWidth = _image.Width * yRatio; destHeight = height; xOffset = 0.5f * (width - destWidth); yOffset = 0; } graphics.DrawImage(_image, new RectangleF(LeftBorder + xOffset, TopBorder + yOffset, destWidth, destHeight)); } } } g.DrawImageUnscaled(_background, 0, 0); } private void DrawProgress(Graphics g, float x, float y, float width, float height, float progress) { g.DrawImage(_barBack, new RectangleF(x + width * progress, y, width * (1 - progress), height), new RectangleF(_barBack.Width * progress, 0, (1 - progress) * _barBack.Width, _barBack.Height), GraphicsUnit.Pixel); g.DrawImage(_barFore, new RectangleF(x, y, width * progress, height), new RectangleF(0, 0, progress * _barFore.Width, _barFore.Height), GraphicsUnit.Pixel); } protected override void OnPaint(PaintEventArgs e) { try { Graphics g = e.Graphics; int w = Size.Width; g.Clear(Color.Transparent); DrawBackground(g); int x; int y = _topMargin; if (_sensors.Count == 0) { x = LeftBorder + 1; g.DrawString("Right-click on a sensor in the main window and select " + "\"Show in Gadget\" to show the sensor here.", _smallFont, _textBrush, new Rectangle(x, y - 1, w - RightBorder - x, 0)); } foreach (KeyValuePair> pair in _sensors) { if (_hardwareNames.Value) { if (y > _topMargin) y += _hardwareLineHeight - _sensorLineHeight; x = LeftBorder + 1; g.DrawImage(HardwareTypeImage.Instance.GetImage(pair.Key.HardwareType), new Rectangle(x, y + 1, _iconSize, _iconSize)); x += _iconSize + 1; g.DrawString(pair.Key.Name, _largeFont, _textBrush, new Rectangle(x, y - 1, w - RightBorder - x, 0), _stringFormat); y += _hardwareLineHeight; } foreach (ISensor sensor in pair.Value) { int remainingWidth; if ((sensor.SensorType != SensorType.Load && sensor.SensorType != SensorType.Control && sensor.SensorType != SensorType.Level && sensor.SensorType != SensorType.Humidity) || !sensor.Value.HasValue) { string formatted; if (sensor.Value.HasValue) { string format = ""; switch (sensor.SensorType) { case SensorType.Voltage: format = "{0:F3} V"; break; case SensorType.Current: format = "{0:F3} A"; break; case SensorType.Clock: format = "{0:F0} MHz"; break; case SensorType.Frequency: format = "{0:F0} Hz"; break; case SensorType.Temperature: format = "{0:F1} °C"; break; case SensorType.Fan: format = "{0:F0} RPM"; break; case SensorType.Flow: format = "{0:F0} L/h"; break; case SensorType.Power: format = "{0:F1} W"; break; case SensorType.Data: format = "{0:F1} GB"; break; case SensorType.SmallData: format = "{0:F0} MB"; break; case SensorType.Factor: format = "{0:F3}"; break; case SensorType.TimeSpan: format = "{0:g}"; break; case SensorType.Energy: format = "{0:F0} mWh"; break; case SensorType.Noise: format = "{0:F0} dBA"; break; case SensorType.Conductivity: format = "{0:F1} µS/cm"; break; } if (sensor.SensorType == SensorType.Temperature && _unitManager.TemperatureUnit == TemperatureUnit.Fahrenheit) { formatted = $"{UnitManager.CelsiusToFahrenheit(sensor.Value):F1} °F"; } else if (sensor.SensorType == SensorType.Throughput) { string result; switch (sensor.Name) { case "Connection Speed": { switch (sensor.Value) { case 100000000: result = "100Mbps"; break; case 1000000000: result = "1Gbps"; break; default: { if (sensor.Value < 1024) result = $"{sensor.Value:F0} bps"; else if (sensor.Value < 1048576) result = $"{sensor.Value / 1024:F1} Kbps"; else if (sensor.Value < 1073741824) result = $"{sensor.Value / 1048576:F1} Mbps"; else result = $"{sensor.Value / 1073741824:F1} Gbps"; } break; } } break; default: { if (sensor.Value < 1048576) result = $"{sensor.Value / 1024:F1} KB/s"; else result = $"{sensor.Value / 1048576:F1} MB/s"; } break; } formatted = result; } else if (sensor.SensorType == SensorType.TimeSpan) { formatted = string.Format(format, TimeSpan.FromSeconds(sensor.Value.Value)); } else { formatted = string.Format(format, sensor.Value); } } else { formatted = "-"; } g.DrawString(formatted, _smallFont, _textBrush, new RectangleF(-1, y - 1, w - _rightMargin + 3, 0), _alignRightStringFormat); remainingWidth = w - (int)Math.Floor(g.MeasureString(formatted, _smallFont, w, StringFormat.GenericTypographic).Width) - _rightMargin; } else { DrawProgress(g, w - _progressWidth - _rightMargin, y + 0.35f * _sensorLineHeight, _progressWidth, 0.6f * _sensorLineHeight, 0.01f * sensor.Value.Value); remainingWidth = w - _progressWidth - _rightMargin; } remainingWidth -= _leftMargin + 2; if (remainingWidth > 0) { g.DrawString(sensor.Name, _smallFont, _textBrush, new RectangleF(_leftMargin - 1, y - 1, remainingWidth, 0), _trimStringFormat); } y += _sensorLineHeight; } } } catch (ArgumentException) { // #1425. } } private class HardwareComparer : IComparer { public int Compare(IHardware x, IHardware y) { switch (x) { case null when y == null: return 0; case null: return -1; } if (y == null) return 1; if (x.HardwareType != y.HardwareType) return x.HardwareType.CompareTo(y.HardwareType); return x.Identifier.CompareTo(y.Identifier); } } }