diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
index ae0a4a3..d49a073 100644
--- a/.github/workflows/master.yml
+++ b/.github/workflows/master.yml
@@ -1,17 +1,16 @@
-name: master
+name: release
on:
- push:
- branches: [master]
+ workflow_dispatch:
jobs:
build:
- runs-on: windows-2022
+ runs-on: windows-2025
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
- - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # 5.2.0
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ - uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
with:
- dotnet-version: '10.0.x'
+ dotnet-version: "10.x"
- uses: nuget/setup-nuget@fd55a6f3b34392fa83fde1454582407d8c714123 # 4.0.0
- uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # 3.0.0
with:
@@ -27,14 +26,14 @@ jobs:
- name: Update version
if: steps.changes.outputs.buildprops == 'false'
run: |
- (Get-Content Directory.Build.props) | % {
+ (Get-Content Directory.Build.props) | % {
$m = [regex]::match($_, '(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?');
if(!$m.Success -or $m.Groups[4].Success -or $m.Groups[5].Success) { $_; }
else { $_ -replace $m.Value, ("{0}.{1}.{2}-pre${{ github.run_number }}" -f $m.Groups[1].Value,$m.Groups[2].Value,([convert]::ToInt32($m.Groups[3].Value)+1)); }
} | Set-Content Directory.Build.props
- name: Restore application packages
- run: dotnet restore LibreHardwareMonitor.Windows.WinUI\LibreHardwareMonitor.Windows.WinUI.csproj
+ run: dotnet restore LibreHardwareMonitor.Windows.WinUI\LibreHardwareMonitor.Windows.WinUI.csproj
- name: Build application
run: dotnet build LibreHardwareMonitor.Windows.WinUI\LibreHardwareMonitor.Windows.WinUI.csproj -c Release --no-restore -p:Platform=x64
@@ -47,7 +46,7 @@ jobs:
bin/Release/net10.0-windows10.0.19041.0
- name: Restore library packages
- run: dotnet restore LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj
+ run: dotnet restore LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj
- name: Build x64 libraries
run: dotnet build LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj -c Release --no-restore -p:Platform=x64
@@ -59,26 +58,6 @@ jobs:
path: |
bin/Release/x64
- - name: Build x86 libraries
- run: dotnet build LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj -c Release --no-restore -p:Platform=x86
-
- - name: Publish x86 libraries
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 7.0.1
- with:
- name: LibreHardwareMonitorLib (x86)
- path: |
- bin/Release/x86
-
- - name: Build ARM64 libraries
- run: dotnet build LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj -c Release --no-restore -p:Platform=ARM64
-
- - name: Publish ARM64 libraries
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 7.0.1
- with:
- name: LibreHardwareMonitorLib (ARM64)
- path: |
- bin/Release/ARM64
-
- name: Build reference libraries
run: dotnet build LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj -c Release --no-restore -p:BuildOnlyRefs=true
diff --git a/.github/workflows/pull requests.yml b/.github/workflows/pull requests.yml
index 12415bf..3088799 100644
--- a/.github/workflows/pull requests.yml
+++ b/.github/workflows/pull requests.yml
@@ -2,16 +2,16 @@ name: pull requests
on:
pull_request:
- branches: [master]
+ branches: [main]
jobs:
build:
- runs-on: windows-2022
+ runs-on: windows-2025
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
- - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # 5.2.0
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ - uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
with:
- dotnet-version: '10.0.x'
+ dotnet-version: "10.x"
- uses: nuget/setup-nuget@fd55a6f3b34392fa83fde1454582407d8c714123 # 4.0.0
- uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # 3.0.0
with:
@@ -27,14 +27,14 @@ jobs:
- name: Update version
if: steps.changes.outputs.buildprops == 'false'
run: |
- (Get-Content Directory.Build.props) | % {
+ (Get-Content Directory.Build.props) | % {
$m = [regex]::match($_, '(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?');
if(!$m.Success -or $m.Groups[4].Success -or $m.Groups[5].Success) { $_; }
else { $_ -replace $m.Value, ("{0}.{1}.{2}-ci${{ github.run_number }}" -f $m.Groups[1].Value,$m.Groups[2].Value,([convert]::ToInt32($m.Groups[3].Value)+1)); }
} | Set-Content Directory.Build.props
- name: Restore application packages
- run: dotnet restore LibreHardwareMonitor.Windows.WinUI\LibreHardwareMonitor.Windows.WinUI.csproj
+ run: dotnet restore LibreHardwareMonitor.Windows.WinUI\LibreHardwareMonitor.Windows.WinUI.csproj
- name: Build application
run: dotnet build LibreHardwareMonitor.Windows.WinUI\LibreHardwareMonitor.Windows.WinUI.csproj -c Release --no-restore -p:Platform=x64
@@ -47,7 +47,7 @@ jobs:
bin/Release/net10.0-windows10.0.19041.0
- name: Restore library packages
- run: dotnet restore LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj
+ run: dotnet restore LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj
- name: Build x64 libraries
run: dotnet build LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj -c Release --no-restore -p:Platform=x64
@@ -59,26 +59,6 @@ jobs:
path: |
bin/Release/x64
- - name: Build x86 libraries
- run: dotnet build LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj -c Release --no-restore -p:Platform=x86
-
- - name: Publish x86 libraries
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 7.0.1
- with:
- name: LibreHardwareMonitorLib (x86)
- path: |
- bin/Release/x86
-
- - name: Build ARM64 libraries
- run: dotnet build LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj -c Release --no-restore -p:Platform=ARM64
-
- - name: Publish ARM64 libraries
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 7.0.1
- with:
- name: LibreHardwareMonitorLib (ARM64)
- path: |
- bin/Release/ARM64
-
- name: Build reference libraries
run: dotnet build LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj -c Release --no-restore -p:BuildOnlyRefs=true
diff --git a/docs/fixes-and-architecture-changes.md b/docs/fixes-and-architecture-changes.md
new file mode 100644
index 0000000..4d928c8
--- /dev/null
+++ b/docs/fixes-and-architecture-changes.md
@@ -0,0 +1,85 @@
+# PR Description: WinUI 3 Migration, Performance Optimization, and Architectural Refactoring
+
+## Summary
+This Pull Request modernizes the presentation layer, optimizes startup performance, refactors the codebase to a clean MVVM + Dependency Injection (DI) architecture, and hardens thread safety and correctness across both `LibreHardwareMonitorLib` and the UI applications.
+
+Specifically, this branch introduces a new modern Windows front-end built with **WinUI 3** and modern **.NET**, deprecating direct reliance on legacy WinForms/ .NET Framework 4.7.2 for the primary modern target, while maintaining thread-safe compatibility for the existing WinForms app. It also resolves critical startup blocking issues and concurrency bottlenecks through background/deferred hardware discovery.
+
+---
+
+## Key Changes
+
+### 1. Modern WinUI 3 Presentation Layer
+- **Fluent Design & Mica Backdrops**: Leverages modern Windows 11 styling including Mica theme materials matching light and dark modes.
+- **Improved UI Elements**:
+ - Host a modern `TreeView` control for sensor visualization.
+ - Interactive plot views (`PlotView`) containing a configurable floating top-right plot legend overlay.
+ - Secondary windows including a floating desktop sensor gadget (`SensorGadgetWindow`) and pop-out graphs.
+- **High-DPI Support**: Fixes layout issues on high-DPI scaling displays and tray restore behavior.
+- **MVVM Pattern**: Designed around cleanly separated ViewModels (`MainWindowViewModel`, `SensorTreeItemViewModel`, etc.) using Dependency Injection.
+
+### 2. Startup Performance & Progressive Loading
+- **Staged & Async Initialization**: Refactored `Computer.Open()` to support staged/progressive loading. Rather than blocking the main thread on slow motherboard LPC/EC/IPMI, memory SPD, or GPU probes, the UI shell loads immediately and populates hardware groups progressively as background discovery tasks complete.
+- **Background TSC Estimation**: Moved CPU TSC (Time Stamp Counter) frequency estimation off the startup blocking path in `GenericCpu.cs`. It now runs asynchronously with full cancellation support.
+- **Startup Instrumentation**: Introduced tracing and measurement tools under the `IStartupTracer` abstraction (`FileStartupTracer`, `NoOpStartupTracer`) to track execution time per discovery phase and identify bottlenecks.
+
+### 3. Architecture & Composition Root Modernization
+- **Dependency Injection**: Replaced manual instantiation chains with `Microsoft.Extensions.DependencyInjection`.
+- **Decoupled Services**: Extracted logic out of monolithic UI containers into clean, testable service abstractions:
+ - `IHardwareMonitorService` / `HardwareMonitorService`
+ - `ILogger` / `Logger`
+ - `IRemoteWebServer` / `RemoteWebServer`
+ - `SecondaryWindowCoordinator` (for plot/gadget window lifecycles)
+ - `TrayIconService` (split into interop, renderer, and orchestration layers)
+ - `TreeRebuildCoalescer` (debounces and coalesces tree rebuild requests to optimize performance)
+ - `PlotTrackingService`, `SensorSelectionService`, `WindowPlacementService`, `WindowChromeManager`, and `SensorColumnMeasurer`.
+
+### 4. Thread Safety, Correctness, and Security Hardening
+A comprehensive code review identified and fixed several critical concurrency issues:
+- **`Computer.Close()` Race Condition Fix**: `CancelDeferredGroupRun()` now fully drains in-flight deferred tasks (`Task.WaitAll` via `WaitForDeferredGroupTasks`) before disposing token sources, stopping background threads from executing native API / OpCode calls during teardown.
+- **Atomic State Transitions**: Wrapped `Open`/`OpenAsync`/`Close` in `Computer.cs` with a new `_openLock` to make checking/setting the `_open` state atomic and prevent concurrent initialization leaks.
+- **UI Logger Synchronization**: Added a `_sync` lock inside `Logger` to protect concurrent reads/writes on `_sensors` and `_identifiers` arrays.
+- **WinForms UI Thread Marshalling**: Legacy UI forms (`MainForm`, `SystemTray`, `SensorGadget`) were updated to marshal background `HardwareAdded`/`HardwareRemoved` events onto the UI `SynchronizationContext` to prevent unmarshaled cross-thread UI mutation.
+- **Memory DIMM Discovery Serialization**: Standardized discovery ordering via `IHardwareDiscoveryTask.StartHardwareDiscovery()`, preventing duplicated or lost DIMM `HardwareAdded` announcements.
+- **Web Server Optimization & Security**:
+ - Fixed a vulnerability in `PasswordHasher.VerifyPbkdf2` where empty salt/hash segments could bypass authentication.
+ - Reused a static `JsonSerializerOptions` in `RemoteWebServer` to restore metadata caching on the high-frequency `data.json` endpoint.
+
+---
+
+## Detailed Audit & Code Review Fixes
+
+| Finding | Severity | Component | Resolution |
+| :--- | :---: | :--- | :--- |
+| **F1** | 🔴 Critical | `Computer.cs` | Drains in-flight deferred tasks during `Close()` to prevent races against native teardown. |
+| **F2** | 🔴 Critical | `Computer.cs` | Extracted `OpenCore` and synchronized all open/close paths under `_openLock`. |
+| **F3** | 🔴 Critical | `Logger.cs` | Added `_sync` mutex to synchronize sensor collections across threads safely without blocking file I/O. |
+| **D1** | 🟠Major | `GenericCpu.cs` | Deferred TSC task now receives a `CancellationToken` and is fully awaited on `Close()`. |
+| **D2** | 🟠Major | WinForms UI | Captures UI `SynchronizationContext` and dispatches deferred events to the UI thread. |
+| **D4** | 🟠Major | `Computer.cs` | Deferred-DIMM discovery is serialized via `StartHardwareDiscovery()` to avoid event duplication. |
+| **D5** | 🔴 Critical | `PasswordHasher.cs` | Enforces non-empty salt/hash checks to prevent empty password bypass. |
+| **D6** | 🟠Major | `Computer.cs` | `CompleteDeferredGroupRunWhenRegistered` re-arms to wait for nested deferred tasks registered late. |
+| **D9** | 🟡 Minor | `TrayIconService.cs` | Reuses existing GDI tray icons when tooltips/values are unchanged to avoid rendering churn. |
+| **D10**| 🟡 Minor | `PlotTrackingService` | Eliminated redundant full-history sorts on every tick. |
+| **D11**| 🟡 Minor | `MainWindowViewModel` | Cached sensor counts instead of traversing the entire tree on every update tick. |
+
+---
+
+## Verification & Testing
+All tests and builds run successfully on `Release` for `x64` platform target.
+
+### Automated Tests
+Run the test suites with:
+```powershell
+# Run Library Unit Tests
+dotnet test LibreHardwareMonitorLib.Tests/LibreHardwareMonitorLib.Tests.csproj -c Release -p:Platform=x64
+
+# Run WinUI UI Tests
+dotnet test LibreHardwareMonitor.Windows.WinUI.Tests/LibreHardwareMonitor.Windows.WinUI.Tests.csproj -c Release -p:Platform=x64
+```
+
+### Build Check
+Ensure WinForms app compiles without errors:
+```powershell
+dotnet build LibreHardwareMonitor.Windows.Forms/LibreHardwareMonitor.Windows.Forms.csproj -c Release -p:Platform=x64
+```