Files
2026-06-08 10:37:00 -05:00

263 lines
9.0 KiB
C#

// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using LibreHardwareMonitor.Hardware;
using LibreHardwareMonitor.Hardware.Storage;
using LibreHardwareMonitor.Windows.WinUI.ViewModels;
namespace LibreHardwareMonitor.Windows.WinUI.Services;
public sealed class HardwareMonitorService : IHardwareMonitorService
{
private readonly object _updateLock = new();
private readonly UpdateVisitor _updateVisitor = new();
private readonly TreeRebuildCoalescer _treeRebuildCoalescer;
private bool _isOpen;
private bool _disposed;
public HardwareMonitorService(AppSettings settings)
{
Settings = settings;
_treeRebuildCoalescer = new TreeRebuildCoalescer(() => RebuildTree(), () => _isOpen);
ApplyWinUiHardwareDefaults();
Computer = new Computer(settings);
Computer.HardwareAdded += HardwareChanged;
Computer.HardwareRemoved += HardwareChanged;
ApplyHardwareFlagsFromSettings();
}
public event EventHandler? TreeRebuilt;
public Computer Computer { get; }
/// <summary>
/// Lock guarding access to live sensor collections (values and active-sensor sets). Held by <see cref="UpdateAsync" />
/// while sensors are updated; other readers (tree rebuild, the remote web server) take it to read consistently.
/// </summary>
public object SensorReadLock => _updateLock;
public AppSettings Settings { get; }
public SensorTreeItemViewModel Root { get; private set; } = SensorTreeItemViewModel.CreateRoot(Environment.MachineName);
public bool IsMotherboardEnabled
{
get => Computer.IsMotherboardEnabled;
set => SetHardwareEnabled("mainboardMenuItem", value, v => Computer.IsMotherboardEnabled = v);
}
public bool IsCpuEnabled
{
get => Computer.IsCpuEnabled;
set => SetHardwareEnabled("cpuMenuItem", value, v => Computer.IsCpuEnabled = v);
}
public bool IsMemoryEnabled
{
get => Computer.IsMemoryEnabled;
set => SetHardwareEnabled("ramMenuItem", value, v => Computer.IsMemoryEnabled = v);
}
public bool IsGpuEnabled
{
get => Computer.IsGpuEnabled;
set => SetHardwareEnabled("gpuMenuItem", value, v => Computer.IsGpuEnabled = v);
}
public bool IsPowerMonitorEnabled
{
get => Computer.IsPowerMonitorEnabled;
set => SetHardwareEnabled("powerMonitorMenuItem", value, v => Computer.IsPowerMonitorEnabled = v);
}
public bool IsControllerEnabled
{
get => Computer.IsControllerEnabled;
set => SetHardwareEnabled("fanControllerMenuItem", value, v => Computer.IsControllerEnabled = v);
}
public bool IsStorageEnabled
{
get => Computer.IsStorageEnabled;
set => SetHardwareEnabled("hddMenuItem", value, v => Computer.IsStorageEnabled = v);
}
public bool IsNetworkEnabled
{
get => Computer.IsNetworkEnabled;
set => SetHardwareEnabled("nicMenuItem", value, v => Computer.IsNetworkEnabled = v);
}
public bool IsPsuEnabled
{
get => Computer.IsPsuEnabled;
set => SetHardwareEnabled("psuMenuItem", value, v => Computer.IsPsuEnabled = v);
}
public bool IsBatteryEnabled
{
get => Computer.IsBatteryEnabled;
set => SetHardwareEnabled("batteryMenuItem", value, v => Computer.IsBatteryEnabled = v);
}
public bool ForceDriveWakeup
{
get => Settings.GetValue("forceDriveWakeupItem", false);
set
{
Settings.SetValue("forceDriveWakeupItem", value);
foreach (StorageDevice storageDevice in Computer.Hardware.OfType<StorageDevice>())
storageDevice.ForceWakeup = value;
}
}
public void Open(bool raiseTreeRebuilt = true)
{
if (_isOpen)
return;
Computer.Open();
_isOpen = true;
RebuildTree(raiseTreeRebuilt);
}
public async Task OpenAsync(bool raiseTreeRebuilt = true, CancellationToken cancellationToken = default)
{
if (_isOpen)
return;
await Computer.OpenAsync(cancellationToken).ConfigureAwait(false);
_isOpen = true;
RebuildTree(raiseTreeRebuilt);
await Computer.HardwareDiscoveryTask.WaitAsync(cancellationToken).ConfigureAwait(false);
RebuildTree(raiseTreeRebuilt);
}
public void Reset()
{
if (_isOpen)
Computer.Close();
Root = SensorTreeItemViewModel.CreateRoot(Environment.MachineName);
Computer.Open();
RebuildTree();
}
public Task UpdateAsync()
{
return Task.Run(() =>
{
lock (_updateLock)
Computer.Accept(_updateVisitor);
});
}
public void ResetMinMax()
{
SensorVisitor visitor = new(sensor => sensor.ResetMin());
visitor.VisitComputer(Computer);
visitor = new SensorVisitor(sensor => sensor.ResetMax());
visitor.VisitComputer(Computer);
}
public void ClearSensorValues()
{
SensorVisitor visitor = new(sensor => sensor.ClearValues());
visitor.VisitComputer(Computer);
}
public void RebuildTree(bool raiseTreeRebuilt = true)
{
SensorTreeItemViewModel root = SensorTreeItemViewModel.CreateRoot(Environment.MachineName);
// Building the tree reads each hardware's live sensor set (a HashSet). Hold the update lock so it cannot run
// concurrently with UpdateAsync()'s Computer.Accept, which mutates that set via ActivateSensor on another thread.
lock (_updateLock)
{
foreach (IHardware hardware in Computer.Hardware.OrderBy(hardware => hardware.HardwareType).ThenBy(hardware => hardware.Name, StringComparer.OrdinalIgnoreCase))
root.Children.Add(SensorTreeItemViewModel.FromHardware(hardware, Settings));
}
Root = root;
if (raiseTreeRebuilt)
TreeRebuilt?.Invoke(this, EventArgs.Empty);
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
// Clear before closing so any tree-rebuild task still pending its delay bails instead of rebuilding from a
// half-closed Computer.
_isOpen = false;
Computer.HardwareAdded -= HardwareChanged;
Computer.HardwareRemoved -= HardwareChanged;
Computer.Close();
}
private void ApplyHardwareFlagsFromSettings()
{
Computer.IsMotherboardEnabled = Settings.GetValue("mainboardMenuItem", true);
Computer.IsCpuEnabled = Settings.GetValue("cpuMenuItem", true);
Computer.IsMemoryEnabled = Settings.GetValue("ramMenuItem", true);
Computer.IsGpuEnabled = Settings.GetValue("gpuMenuItem", true);
Computer.IsPowerMonitorEnabled = Settings.GetValue("powerMonitorMenuItem", true);
Computer.IsControllerEnabled = Settings.GetValue("fanControllerMenuItem", true);
Computer.IsStorageEnabled = Settings.GetValue("hddMenuItem", true);
Computer.IsNetworkEnabled = Settings.GetValue("nicMenuItem", true);
Computer.IsPsuEnabled = Settings.GetValue("psuMenuItem", true);
Computer.IsBatteryEnabled = Settings.GetValue("batteryMenuItem", true);
ForceDriveWakeup = Settings.GetValue("forceDriveWakeupItem", false);
}
private static readonly string[] WinUiDeferredDetectionDefaults =
{
HardwareSettingsKeys.MemoryDeferDimmDetection,
HardwareSettingsKeys.CpuDeferInitialUpdate,
HardwareSettingsKeys.CpuDeferTscEstimation,
HardwareSettingsKeys.NvidiaDeferDetection,
HardwareSettingsKeys.StorageDeferDetection,
HardwareSettingsKeys.NetworkDeferDetection,
HardwareSettingsKeys.IntelGpuDeferDetection,
HardwareSettingsKeys.ControllerDeferDetection,
HardwareSettingsKeys.PsuDeferDetection,
};
private void ApplyWinUiHardwareDefaults()
{
foreach (string setting in WinUiDeferredDetectionDefaults)
{
if (!Settings.Contains(setting))
Settings.SetValue(setting, true);
}
}
private void SetHardwareEnabled(string settingName, bool value, Action<bool> setter)
{
setter(value);
Settings.SetValue(settingName, value);
if (_isOpen)
RebuildTree();
}
private void HardwareChanged(IHardware hardware)
{
// Storage discovery is deferred, so drives usually appear after ForceDriveWakeup was applied at startup (against
// an empty hardware list). Apply the current setting to each newly discovered drive so it actually takes effect.
if (hardware is StorageDevice storageDevice)
storageDevice.ForceWakeup = Settings.GetValue("forceDriveWakeupItem", false);
if (_isOpen)
_treeRebuildCoalescer.Request();
}
}