7 Commits

Author SHA1 Message Date
019781b2f4 Update floating status window lib
All checks were successful
Deploy to Gitea Releases / deploy-to-gitea-releases (push) Successful in 2m34s
- Pad labels
2026-03-02 18:21:44 -05:00
5bb26c1c3f Improve startup
All checks were successful
Deploy to Gitea Releases / deploy-to-gitea-releases (push) Successful in 48s
2026-02-27 16:06:23 -05:00
f8aa7c9118 Show sensor value when adding/editing sensors
All checks were successful
Deploy to Gitea Releases / deploy-to-gitea-releases (push) Successful in 49s
2026-02-27 14:34:43 -05:00
afa7bd987a Update LibreHardwareMonitor
All checks were successful
Deploy to Gitea Releases / deploy-to-gitea-releases (push) Successful in 3m10s
- Some nullability updates
2026-02-27 13:46:06 -05:00
4cd7e74923 Minor change to test upgrade
All checks were successful
Deploy to Gitea Releases / deploy-to-gitea-releases (push) Successful in 52s
2026-01-27 21:25:59 -05:00
d65c556656 Try stopping service task before upgrade/uninstall
All checks were successful
Deploy to Gitea Releases / deploy-to-gitea-releases (push) Successful in 54s
2026-01-27 21:20:35 -05:00
fe3941d8bd Fix update source
All checks were successful
Deploy to Gitea Releases / deploy-to-gitea-releases (push) Successful in 54s
2026-01-27 20:40:39 -05:00
16 changed files with 171 additions and 63 deletions

View File

@@ -11,9 +11,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.5" />
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.6" />
<PackageReference Include="PipeMethodCalls" Version="4.0.3" />
<PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="Serilog" Version="4.3.1" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="TaskScheduler" Version="2.12.2" />
</ItemGroup>

View File

@@ -8,7 +8,7 @@ namespace HardwareMonitorStatusWindow.StatusWindow;
public partial class App
{
private List<IDisposable> _windowSourceList;
private List<IDisposable>? _windowSourceList;
protected override void OnStartup(StartupEventArgs e)
{
@@ -30,7 +30,7 @@ public partial class App
protected override void OnExit(ExitEventArgs e)
{
_windowSourceList.ForEach(ws => ws.Dispose());
_windowSourceList?.ForEach(ws => ws.Dispose());
base.OnExit(e);
}

View File

@@ -6,17 +6,18 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Serilog;
namespace HardwareMonitorStatusWindow.StatusWindow;
internal static class Data
{
private static PipeClient<IHardwareMonitorService> _pipeClient;
private static IEnumerable<Hardware> _hardware;
private static PipeClient<IHardwareMonitorService>? _pipeClient;
private static IEnumerable<Hardware>? _hardware;
internal static ObservableCollection<SensorEntry> SensorEntries { get; set; }
internal static ObservableCollection<SensorEntry> SensorEntries { get; set; } = [];
internal static async Task LoadComputer()
private static async Task LoadComputer()
{
try
{
@@ -25,25 +26,28 @@ internal static class Data
}
catch (Exception exception)
{
Log.Error(exception, "");
}
}
internal static void RefreshComputer()
internal static async Task RefreshComputer()
{
if (_pipeClient is not { State: PipeState.Connected })
await LoadComputer();
_hardware = _pipeClient.InvokeAsync(service => service.GetHardware()).Result;
}
internal static void CloseComputer()
{
_pipeClient.Dispose();
_pipeClient?.Dispose();
}
internal static IList<Hardware> ComputerHardware => _hardware.ToList();
internal static IList<Hardware> ComputerHardware => _hardware?.ToList() ?? [];
internal static void Load()
{
SensorEntries = JsonSerializer.Deserialize<ObservableCollection<SensorEntry>>(Settings.Default.Sensors);
SensorEntries = JsonSerializer.Deserialize<ObservableCollection<SensorEntry>>(Settings.Default.Sensors) ?? [];
}
internal static void Save()

View File

@@ -7,15 +7,18 @@ namespace HardwareMonitorStatusWindow.StatusWindow;
internal class DataErrorDictionary : Dictionary<string, List<string>>
{
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
public IEnumerable? GetErrors(string? propertyName)
{
if (propertyName == null)
return null;
return TryGetValue(propertyName, out var value) ? value : null;
}

View File

@@ -1,4 +1,6 @@
using Serilog;
using HardwareMonitorStatusWindow.Service;
using Microsoft.Win32.TaskScheduler;
using Serilog;
using System;
using System.Diagnostics;
using Velopack;
@@ -17,7 +19,30 @@ internal class Program
Log.Information("Start");
VelopackApp.Build().SetLogger(new SerilogVelopackLogger()).Run();
var stopServiceHook = new VelopackHook(_ =>
{
try
{
using var taskService = new TaskService();
Log.Information("Checking for task name: {name}", HardwareMonitorService.ScheduledTaskName);
var existingTask = taskService.FindTask(HardwareMonitorService.ScheduledTaskName);
Log.Information("Task: {existingTask}", existingTask);
Log.Information("Stopping task");
existingTask?.Stop();
}
catch (Exception exception)
{
// Ignored
Log.Error(exception, "");
}
});
VelopackApp.Build().OnBeforeUpdateFastCallback(stopServiceHook).OnBeforeUninstallFastCallback(stopServiceHook).SetLogger(new SerilogVelopackLogger()).Run();
var app = new App();
app.InitializeComponent();

View File

@@ -169,6 +169,15 @@ namespace HardwareMonitorStatusWindow.StatusWindow {
}
}
/// <summary>
/// Looks up a localized string similar to Current Value.
/// </summary>
public static string CurrentValueWatermark {
get {
return ResourceManager.GetString("CurrentValueWatermark", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete.
/// </summary>

View File

@@ -248,4 +248,7 @@ Would you like to download and install it now?</value>
<data name="ServiceNotStarted" xml:space="preserve">
<value>Waiting for service to start...</value>
</data>
<data name="CurrentValueWatermark" xml:space="preserve">
<value>Current Value</value>
</data>
</root>

View File

@@ -1,21 +1,20 @@
using System;
using HardwareMonitorStatusWindow.Service;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
using HardwareMonitorStatusWindow.Service;
namespace HardwareMonitorStatusWindow.StatusWindow;
public class SensorEntry : INotifyDataErrorInfo, INotifyPropertyChanged
{
private readonly DataErrorDictionary _dataErrorDictionary;
private readonly DataErrorDictionary _dataErrorDictionary = new();
public SensorEntry()
{
_dataErrorDictionary = new DataErrorDictionary();
_dataErrorDictionary.ErrorsChanged += DataErrorDictionaryErrorsChanged;
}
@@ -29,9 +28,9 @@ public class SensorEntry : INotifyDataErrorInfo, INotifyPropertyChanged
SetField(ref field, value);
}
}
} = string.Empty;
public string HardwareId
public string? HardwareId
{
get;
set
@@ -41,7 +40,7 @@ public class SensorEntry : INotifyDataErrorInfo, INotifyPropertyChanged
}
}
public string SensorId
public string? SensorId
{
get;
set
@@ -60,19 +59,22 @@ public class SensorEntry : INotifyDataErrorInfo, INotifyPropertyChanged
[JsonIgnore]
public bool HasErrors => _dataErrorDictionary.Any();
public IEnumerable GetErrors(string propertyName)
public IEnumerable GetErrors(string? propertyName)
{
return _dataErrorDictionary.GetErrors(propertyName);
if (string.IsNullOrWhiteSpace(propertyName))
throw new InvalidOperationException();
return _dataErrorDictionary.GetErrors(propertyName) ?? Enumerable.Empty<string>();
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
private void DataErrorDictionaryErrorsChanged(object sender, DataErrorsChangedEventArgs e)
private void DataErrorDictionaryErrorsChanged(object? sender, DataErrorsChangedEventArgs e)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(e.PropertyName));
}
private bool ValidateLabel(string newValue)
private bool ValidateLabel(string? newValue)
{
_dataErrorDictionary.ClearErrors(nameof(Label));
@@ -84,19 +86,18 @@ public class SensorEntry : INotifyDataErrorInfo, INotifyPropertyChanged
return false;
}
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
private void SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
if (EqualityComparer<T>.Default.Equals(field, value)) return;
field = value;
OnPropertyChanged(propertyName);
return true;
}
public string SensorValueFormat

View File

@@ -58,12 +58,12 @@
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Hardware.Type}"
<TextBlock Text="{Binding Path=Hardware.Type, FallbackValue=null}"
Height="Auto"
FontSize="10"
VerticalAlignment="Center"
Margin="0,2,0,2" />
<TextBlock Text="{Binding Path=Hardware.Name}"
<TextBlock Text="{Binding Path=Hardware.Name, FallbackValue=null}"
Height="Auto"
VerticalAlignment="Center" />
</StackPanel>
@@ -75,12 +75,12 @@
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Sensor.Type}"
<TextBlock Text="{Binding Path=Sensor.Type, FallbackValue=null}"
Height="Auto"
FontSize="10"
VerticalAlignment="Center"
Margin="0,2,0,2" />
<TextBlock Text="{Binding Path=Sensor.Name}"
<TextBlock Text="{Binding Path=Sensor.Name, FallbackValue=null}"
Height="Auto"
VerticalAlignment="Center" />
</StackPanel>

View File

@@ -7,7 +7,7 @@ namespace HardwareMonitorStatusWindow.StatusWindow.SettingsWindow;
public partial class HardwareSettingsPanel
{
private CollectionViewSource _collectionViewSource;
private CollectionViewSource? _collectionViewSource;
public HardwareSettingsPanel()
{
@@ -67,13 +67,13 @@ public partial class HardwareSettingsPanel
EditSelectedSensor();
}
private void AddSensor()
private async void AddSensor()
{
var sensorEntry = new SensorEntry();
var sensorWindow = new SensorWindow();
var result = sensorWindow.Display(sensorEntry, Window.GetWindow(this));
var result = await sensorWindow.Display(sensorEntry, Window.GetWindow(this));
if (!result.HasValue || !result.Value)
return;
@@ -83,7 +83,7 @@ public partial class HardwareSettingsPanel
SetSensorButtonStates();
}
private void EditSelectedSensor()
private async void EditSelectedSensor()
{
if (SensorDataGrid.SelectedItem == null)
return;
@@ -92,7 +92,7 @@ public partial class HardwareSettingsPanel
var sensorWindow = new SensorWindow();
sensorWindow.Display(sensorEntry, Window.GetWindow(this));
await sensorWindow.Display(sensorEntry, Window.GetWindow(this));
}
private void DeleteSelectedSensors()
@@ -105,7 +105,7 @@ public partial class HardwareSettingsPanel
SensorDataGrid.SelectedItems.CopyTo(selectedItems, 0);
foreach (var sensorEntry in selectedItems)
Data.SensorEntries.Remove(sensorEntry);
Data.SensorEntries?.Remove(sensorEntry);
SetSensorButtonStates();
}

View File

@@ -68,8 +68,14 @@
DisplayMemberPath="Name"
VirtualizingPanel.IsVirtualizing="False"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static hardwareMonitorStatusWindow:Resources.SensorWatermark}">
mah:TextBoxHelper.Watermark="{x:Static hardwareMonitorStatusWindow:Resources.SensorWatermark}"
SelectionChanged="SensorComboBox_SelectionChanged">
</ComboBox>
<TextBox Name="CurrentValueTextBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static hardwareMonitorStatusWindow:Resources.CurrentValueWatermark}"
mah:TextBoxHelper.SelectAllOnFocus="True"
IsReadOnly="True" />
</StackPanel>
<StackPanel Grid.Column="0"
Grid.Row="1"

View File

@@ -1,22 +1,28 @@
using System.Linq;
using System.Windows;
using System;
using ChrisKaczor.Wpf.Validation;
using HardwareMonitorStatusWindow.Service;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Threading;
namespace HardwareMonitorStatusWindow.StatusWindow.SettingsWindow;
public partial class SensorWindow
{
private readonly DispatcherTimer _timer = new();
public SensorWindow()
{
InitializeComponent();
}
public bool? Display(SensorEntry sensorEntry, Window owner)
public async Task<bool?> Display(SensorEntry sensorEntry, Window? owner)
{
DataContext = sensorEntry;
Data.RefreshComputer();
await Data.RefreshComputer();
HardwareTypeComboBox.ItemsSource = Data.ComputerHardware.Where(h => h.Sensors.Any()).DistinctBy(h => h.Type).Select(s => new HardwareTypeItem(s.Type)).OrderBy(s => s.Name);
@@ -32,9 +38,18 @@ public partial class SensorWindow
Owner = owner;
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += TimerOnTick;
_timer.Start();
return ShowDialog();
}
private void TimerOnTick(object? sender, EventArgs e)
{
UpdateCurrentSensorValue();
}
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
if (!this.IsValid())
@@ -95,4 +110,26 @@ public partial class SensorWindow
SensorComboBox.ItemsSource = hardware.Sensors.Where(s => s.Type == sensorType.Value);
}
private void SensorComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
UpdateCurrentSensorValue();
}
private void UpdateCurrentSensorValue()
{
var sensor = (Sensor)SensorComboBox.SelectedItem;
if (sensor?.Value == null)
{
CurrentValueTextBox.Text = string.Empty;
return;
}
var hardware = (Hardware)HardwareComboBox.SelectedItem;
var displaySensorEntry = new SensorEntry { HardwareId = hardware.Identifier, SensorId = sensor.Identifier };
CurrentValueTextBox.Text = string.Format(displaySensorEntry.SensorValueFormat, displaySensorEntry.Sensor!.Value);
}
}

View File

@@ -1,6 +1,5 @@
using System.Windows;
using System.Windows.Input;
using HardwareMonitorStatusWindow.StatusWindow;
namespace HardwareMonitorStatusWindow.StatusWindow.SettingsWindow;

View File

@@ -28,10 +28,10 @@
<PackageReference Include="ChrisKaczor.Wpf.Controls.Link" Version="1.0.4" />
<PackageReference Include="ChrisKaczor.Wpf.Validation" Version="1.0.4" />
<PackageReference Include="ChrisKaczor.Wpf.Windows.CategoryWindow" Version="1.0.2" />
<PackageReference Include="ChrisKaczor.Wpf.Windows.FloatingStatusWindow" Version="2.0.0.7" />
<PackageReference Include="ChrisKaczor.Wpf.Windows.FloatingStatusWindow" Version="2.0.0.9" />
<PackageReference Include="gong-wpf-dragdrop" Version="4.0.0" />
<PackageReference Include="PipeMethodCalls" Version="4.0.3" />
<PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="Serilog" Version="4.3.1" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="Velopack" Version="0.0.1298" />
</ItemGroup>

View File

@@ -9,9 +9,9 @@ namespace HardwareMonitorStatusWindow.StatusWindow;
internal static class UpdateCheck
{
private static UpdateManager _updateManager;
private static UpdateManager? _updateManager;
public static UpdateManager UpdateManager => _updateManager ??= new UpdateManager(new GithubSource("https://gitea.kaczorzoo.net/ckaczor/HardwareMonitorStatusWindow", null, false));
public static UpdateManager UpdateManager => _updateManager ??= new UpdateManager(new GiteaSource("https://gitea.kaczorzoo.net/ckaczor/HardwareMonitorStatusWindow", null, false));
public static string LocalVersion => (UpdateManager.CurrentVersion ?? new SemanticVersion(0, 0, 0)).ToString();

View File

@@ -2,12 +2,14 @@
using ChrisKaczor.Wpf.Windows.FloatingStatusWindow;
using HardwareMonitorStatusWindow.Service;
using HardwareMonitorStatusWindow.StatusWindow.SettingsWindow;
using LibreHardwareMonitor.PawnIo;
using Microsoft.Win32.TaskScheduler;
using Serilog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@@ -30,6 +32,17 @@ internal class WindowSource : IWindowSource, IDisposable
{
using var taskService = new TaskService();
if (!PawnIo.IsInstalled)
{
Log.Information("PawnIO not installed");
}
else
{
var pawnIoVersion = PawnIo.Version;
Log.Information("PawnIO installed: {version}", pawnIoVersion);
}
Log.Information("Checking for task name: {name}", HardwareMonitorService.ScheduledTaskName);
var existingTask = taskService.FindTask(HardwareMonitorService.ScheduledTaskName);
@@ -127,7 +140,7 @@ internal class WindowSource : IWindowSource, IDisposable
return true;
}
private async Task Start(bool hasUpdate)
private void Start(bool hasUpdate)
{
Log.Information("Start: hasUpdate={hasUpdate}", hasUpdate);
@@ -136,7 +149,7 @@ internal class WindowSource : IWindowSource, IDisposable
Log.Information("Load");
await Load();
Load();
Log.Information("Starting timer");
@@ -145,14 +158,12 @@ internal class WindowSource : IWindowSource, IDisposable
_timer.Enabled = true;
}
private static async Task Load()
private static void Load()
{
await Data.LoadComputer();
Data.Load();
}
private void Save()
private static void Save()
{
Data.Save();
}
@@ -215,26 +226,36 @@ internal class WindowSource : IWindowSource, IDisposable
if (existingTask == null)
{
_dispatcher.Invoke(() => _floatingStatusWindow.SetText(Resources.ServiceNotInstalled));
_timer.Start();
return;
}
if (existingTask.State != TaskState.Running)
{
_dispatcher.Invoke(() => _floatingStatusWindow.SetText(Resources.ServiceNotStarted));
existingTask.Run();
_timer.Start();
return;
}
}
var text = new StringBuilder();
Data.RefreshComputer();
Data.RefreshComputer().Wait();
var labelLength = Data.SensorEntries.Max(x => x.Label.Length);
foreach (var sensorEntry in Data.SensorEntries)
{
if (sensorEntry.Sensor == null)
continue;
if (text.Length > 0)
text.AppendLine();
text.Append($"{sensorEntry.Label}: {string.Format(sensorEntry.SensorValueFormat, sensorEntry.Sensor.Value)}");
text.Append($"{sensorEntry.Label.PadLeft(labelLength)}: {string.Format(sensorEntry.SensorValueFormat, sensorEntry.Sensor.Value)}");
}
_dispatcher.Invoke(() => _floatingStatusWindow.SetText(text.ToString()));