Split TrayIconService into interop, renderer, and orchestration
The 660-line tray service mixed three concerns. Split into: - TrayIconInterop: all P/Invoke declarations, interop structs, and the subclass delegate (consumers use 'using static'), so the Win32 surface is isolated and reviewable. - SensorIconRenderer: the GDI drawing of the 16x16 per-sensor value icon. - TrayIconService: slimmed to tray orchestration (icon lifecycle, context menus, callback routing) at ~340 lines. Also dedupes the per-sensor settings-key helper onto SensorSelectionService.GetSensorSettingName. Pure relocation; behavior is unchanged. Verified by clean build, 199 tests, and an app smoke-run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using LibreHardwareMonitor.Hardware;
|
||||
using LibreHardwareMonitor.Windows.WinUI.ViewModels;
|
||||
using static LibreHardwareMonitor.Windows.WinUI.Services.TrayIconInterop;
|
||||
using WinUIColor = Windows.UI.Color;
|
||||
|
||||
namespace LibreHardwareMonitor.Windows.WinUI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Renders a sensor's current value into a 16x16 GDI icon (solid background tinted by the sensor's tray color, with the
|
||||
/// value drawn on top) for display in the system tray. Extracted from <see cref="TrayIconService" /> to keep the GDI
|
||||
/// drawing separate from the tray orchestration.
|
||||
/// </summary>
|
||||
internal sealed class SensorIconRenderer
|
||||
{
|
||||
private const int IconSize = 16;
|
||||
|
||||
private readonly AppSettings _settings;
|
||||
private readonly Func<TemperatureUnit> _temperatureUnitProvider;
|
||||
|
||||
public SensorIconRenderer(AppSettings settings, Func<TemperatureUnit> temperatureUnitProvider)
|
||||
{
|
||||
_settings = settings;
|
||||
_temperatureUnitProvider = temperatureUnitProvider;
|
||||
}
|
||||
|
||||
/// <summary>Creates an icon handle for the sensor. The caller owns the handle and must DestroyIcon() it.</summary>
|
||||
public IntPtr CreateIcon(ISensor sensor)
|
||||
{
|
||||
IntPtr hdc = GetDC(IntPtr.Zero);
|
||||
IntPtr memoryDc = CreateCompatibleDC(hdc);
|
||||
IntPtr bitmap = IntPtr.Zero;
|
||||
IntPtr mask = IntPtr.Zero;
|
||||
IntPtr oldBitmap = IntPtr.Zero;
|
||||
IntPtr font = IntPtr.Zero;
|
||||
IntPtr oldFont = IntPtr.Zero;
|
||||
IntPtr brush = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
BitmapInfo bitmapInfo = BitmapInfo.Create(IconSize, IconSize);
|
||||
bitmap = CreateDIBSection(hdc, ref bitmapInfo, 0, out _, IntPtr.Zero, 0);
|
||||
if (bitmap == IntPtr.Zero)
|
||||
return IntPtr.Zero; // never return the shared main icon: callers DestroyIcon() the returned handle
|
||||
|
||||
oldBitmap = SelectObject(memoryDc, bitmap);
|
||||
WinUIColor color = GetSensorTrayColor(sensor);
|
||||
brush = CreateSolidBrush(ToColorRef(color));
|
||||
Rect fill = new() { Left = 0, Top = 0, Right = IconSize, Bottom = IconSize };
|
||||
FillRect(memoryDc, ref fill, brush);
|
||||
|
||||
font = CreateFont(-11, 0, 0, 0, 700, 0, 0, 0, 0, 0, 0, 0, 0, "Segoe UI");
|
||||
oldFont = SelectObject(memoryDc, font);
|
||||
SetBkMode(memoryDc, 1);
|
||||
SetTextColor(memoryDc, ToColorRef(WinUIColor.FromArgb(255, 255, 255, 255)));
|
||||
|
||||
string text = GetSensorIconText(sensor);
|
||||
Rect textRect = new() { Left = 0, Top = 0, Right = IconSize, Bottom = IconSize };
|
||||
DrawText(memoryDc, text, text.Length, ref textRect, 0x00000001 | 0x00000004 | 0x00000020);
|
||||
|
||||
mask = CreateBitmap(IconSize, IconSize, 1, 1, IntPtr.Zero);
|
||||
IconInfo iconInfo = new()
|
||||
{
|
||||
IsIcon = true,
|
||||
ColorBitmap = bitmap,
|
||||
MaskBitmap = mask
|
||||
};
|
||||
|
||||
return CreateIconIndirect(ref iconInfo);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (oldFont != IntPtr.Zero)
|
||||
SelectObject(memoryDc, oldFont);
|
||||
if (oldBitmap != IntPtr.Zero)
|
||||
SelectObject(memoryDc, oldBitmap);
|
||||
if (font != IntPtr.Zero)
|
||||
DeleteObject(font);
|
||||
if (brush != IntPtr.Zero)
|
||||
DeleteObject(brush);
|
||||
if (bitmap != IntPtr.Zero)
|
||||
DeleteObject(bitmap);
|
||||
if (mask != IntPtr.Zero)
|
||||
DeleteObject(mask);
|
||||
if (memoryDc != IntPtr.Zero)
|
||||
DeleteDC(memoryDc);
|
||||
if (hdc != IntPtr.Zero)
|
||||
ReleaseDC(IntPtr.Zero, hdc);
|
||||
}
|
||||
}
|
||||
|
||||
private WinUIColor GetSensorTrayColor(ISensor sensor)
|
||||
{
|
||||
WinUIColor defaultColor = sensor.SensorType is SensorType.Load or SensorType.Control or SensorType.Level
|
||||
? WinUIColor.FromArgb(255, 0x70, 0x8c, 0xf1)
|
||||
: WinUIColor.FromArgb(255, 0x00, 0x78, 0xd4);
|
||||
|
||||
return _settings.GetValue(SensorSelectionService.GetSensorSettingName(sensor, "traycolor"), defaultColor);
|
||||
}
|
||||
|
||||
private string GetSensorIconText(ISensor sensor)
|
||||
{
|
||||
if (!sensor.Value.HasValue)
|
||||
return "-";
|
||||
|
||||
float value = sensor.Value.Value;
|
||||
if (sensor.SensorType == SensorType.Temperature && _temperatureUnitProvider() == TemperatureUnit.Fahrenheit)
|
||||
value = value * 1.8f + 32;
|
||||
|
||||
return sensor.SensorType switch
|
||||
{
|
||||
SensorType.TimeSpan => TimeSpan.FromSeconds(value).ToString("g", CultureInfo.CurrentCulture),
|
||||
SensorType.Timing => value.ToString("F1", CultureInfo.CurrentCulture),
|
||||
SensorType.Clock or SensorType.Fan or SensorType.Flow => (value * 1e-3f).ToString("F1", CultureInfo.CurrentCulture),
|
||||
SensorType.Voltage or SensorType.Current or SensorType.SmallData or SensorType.Factor or SensorType.Throughput or SensorType.Conductivity => value.ToString("F1", CultureInfo.CurrentCulture),
|
||||
_ => value.ToString("F0", CultureInfo.CurrentCulture)
|
||||
};
|
||||
}
|
||||
|
||||
private static uint ToColorRef(WinUIColor color)
|
||||
{
|
||||
return (uint)(color.R | (color.G << 8) | (color.B << 16));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user