- .NET 8 WPF app with full MVVM architecture - One-click boost: CPU, RAM, GPU, network, services, timer resolution - Real-time system monitor (LibreHardwareMonitor) - Auto game detection (30+ games) - RAM optimizer with kernel-level cleanup - Network optimizer (TCP registry tweaks) - Service manager, process manager, shader cache cleaner - Desktop widget overlay - Debug log viewer - Custom dark gaming theme with trans pride colors - 84 unit tests (xUnit) - Inno Setup installer script - Landing page website
265 lines
11 KiB
C#
265 lines
11 KiB
C#
using System.Collections.ObjectModel;
|
||
using System.Diagnostics;
|
||
using System.Windows;
|
||
using System.Windows.Input;
|
||
using System.Windows.Threading;
|
||
using SpdUp.Core;
|
||
using SpdUp.Models;
|
||
using SpdUp.Services;
|
||
|
||
namespace SpdUp.ViewModels;
|
||
|
||
/// <summary>
|
||
/// Root ViewModel – drives the main window, navigation, boost, and monitoring.
|
||
/// </summary>
|
||
public sealed class MainViewModel : ObservableObject, IDisposable
|
||
{
|
||
// ── Services ───────────────────────────────────────────────────────
|
||
private readonly AppSettings _settings;
|
||
private readonly OptimizationService _optimizationService;
|
||
private readonly GameDetectionService _gameDetectionService;
|
||
private readonly MonitoringService _monitoringService;
|
||
private readonly NetworkService _networkService = new();
|
||
private readonly Dispatcher _dispatcher;
|
||
|
||
// ── Sub-ViewModels ─────────────────────────────────────────────────
|
||
public DashboardViewModel Dashboard { get; }
|
||
public BoostViewModel Boost { get; }
|
||
public MonitorViewModel Monitor { get; }
|
||
public GameLibraryViewModel GameLibrary { get; }
|
||
public ProcessManagerViewModel ProcessManager { get; }
|
||
public ServiceManagerViewModel ServiceManager { get; }
|
||
public SettingsViewModel Settings { get; }
|
||
public DebugLogViewModel DebugLog { get; }
|
||
|
||
// ── Navigation ─────────────────────────────────────────────────────
|
||
private object _currentPage;
|
||
public object CurrentPage
|
||
{
|
||
get => _currentPage;
|
||
set => SetProperty(ref _currentPage, value);
|
||
}
|
||
|
||
private string _currentPageTitle = "Dashboard";
|
||
public string CurrentPageTitle
|
||
{
|
||
get => _currentPageTitle;
|
||
set => SetProperty(ref _currentPageTitle, value);
|
||
}
|
||
|
||
// ── Status ─────────────────────────────────────────────────────────
|
||
private string _statusText = "Ready";
|
||
public string StatusText
|
||
{
|
||
get => _statusText;
|
||
set => SetProperty(ref _statusText, value);
|
||
}
|
||
|
||
private bool _isBoosted;
|
||
public bool IsBoosted
|
||
{
|
||
get => _isBoosted;
|
||
set => SetProperty(ref _isBoosted, value);
|
||
}
|
||
|
||
// ── Live Metrics (bound by views & widget) ─────────────────────────
|
||
private float _cpuUsage;
|
||
public float CpuUsage { get => _cpuUsage; set => SetProperty(ref _cpuUsage, value); }
|
||
|
||
private float _ramUsagePercent;
|
||
public float RamUsagePercent { get => _ramUsagePercent; set => SetProperty(ref _ramUsagePercent, value); }
|
||
|
||
private float _ramUsedMB;
|
||
public float RamUsedMB { get => _ramUsedMB; set => SetProperty(ref _ramUsedMB, value); }
|
||
|
||
private float _ramTotalMB;
|
||
public float RamTotalMB { get => _ramTotalMB; set => SetProperty(ref _ramTotalMB, value); }
|
||
|
||
private float _gpuUsage;
|
||
public float GpuUsage { get => _gpuUsage; set => SetProperty(ref _gpuUsage, value); }
|
||
|
||
private float _cpuTemp;
|
||
public float CpuTemp { get => _cpuTemp; set => SetProperty(ref _cpuTemp, value); }
|
||
|
||
private float _gpuTemp;
|
||
public float GpuTemp { get => _gpuTemp; set => SetProperty(ref _gpuTemp, value); }
|
||
|
||
private float _pingMs;
|
||
public float PingMs { get => _pingMs; set => SetProperty(ref _pingMs, value); }
|
||
|
||
// ── Commands ───────────────────────────────────────────────────────
|
||
public ICommand NavigateCommand { get; }
|
||
public ICommand BoostCommand { get; }
|
||
public ICommand ToggleWidgetCommand { get; }
|
||
public ICommand CleanRamCommand { get; }
|
||
public ICommand CleanShaderCacheCommand { get; }
|
||
|
||
// ── Widget ─────────────────────────────────────────────────────────
|
||
private bool _isWidgetVisible;
|
||
public bool IsWidgetVisible
|
||
{
|
||
get => _isWidgetVisible;
|
||
set
|
||
{
|
||
if (SetProperty(ref _isWidgetVisible, value))
|
||
_settings.ShowWidget = value;
|
||
}
|
||
}
|
||
|
||
public MainViewModel()
|
||
{
|
||
_dispatcher = Dispatcher.CurrentDispatcher;
|
||
_settings = AppSettings.Load();
|
||
|
||
// Initialise services
|
||
_optimizationService = new OptimizationService(_settings);
|
||
_gameDetectionService = new GameDetectionService(_settings);
|
||
_monitoringService = new MonitoringService(_settings.PingTarget);
|
||
|
||
// Sub-ViewModels
|
||
Dashboard = new DashboardViewModel(this);
|
||
Boost = new BoostViewModel(this, _optimizationService);
|
||
Monitor = new MonitorViewModel(this);
|
||
GameLibrary = new GameLibraryViewModel(_settings, _gameDetectionService);
|
||
ProcessManager = new ProcessManagerViewModel();
|
||
ServiceManager = new ServiceManagerViewModel(_settings);
|
||
Settings = new SettingsViewModel(_settings, this);
|
||
DebugLog = new DebugLogViewModel();
|
||
|
||
_currentPage = Dashboard;
|
||
|
||
// Commands
|
||
NavigateCommand = new RelayCommand<string>(Navigate);
|
||
BoostCommand = new RelayCommand(async () => await ToggleBoostAsync());
|
||
ToggleWidgetCommand = new RelayCommand(() => IsWidgetVisible = !IsWidgetVisible);
|
||
CleanRamCommand = new RelayCommand(async () => await CleanRamAsync());
|
||
CleanShaderCacheCommand = new RelayCommand(async () => await CleanShaderCacheAsync());
|
||
|
||
// Wire up monitoring
|
||
_monitoringService.MetricsUpdated += OnMetricsUpdated;
|
||
_monitoringService.Start(_settings.MonitoringIntervalMs);
|
||
|
||
// Wire up game detection
|
||
_gameDetectionService.GameStarted += OnGameStarted;
|
||
_gameDetectionService.GameStopped += OnGameStopped;
|
||
if (_settings.AutoDetectGames)
|
||
_gameDetectionService.Start();
|
||
|
||
// Widget state
|
||
_isWidgetVisible = _settings.ShowWidget;
|
||
|
||
Logger.PurgeOldLogs();
|
||
Logger.Info("Application started.");
|
||
}
|
||
|
||
// ── Navigation ─────────────────────────────────────────────────────
|
||
private void Navigate(string? pageName)
|
||
{
|
||
(CurrentPage, CurrentPageTitle) = pageName switch
|
||
{
|
||
"Dashboard" => ((object)Dashboard, "Dashboard"),
|
||
"Boost" => (Boost, "Game Boost"),
|
||
"Monitor" => (Monitor, "System Monitor"),
|
||
"Games" => (GameLibrary, "Game Library"),
|
||
"Processes" => (ProcessManager, "Process Manager"),
|
||
"Services" => (ServiceManager, "Service Manager"),
|
||
"Settings" => (Settings, "Settings"),
|
||
"Debug" => (DebugLog, "Debug Log"),
|
||
_ => (Dashboard, "Dashboard")
|
||
};
|
||
}
|
||
|
||
// ── Boost Toggle ───────────────────────────────────────────────────
|
||
private async Task ToggleBoostAsync()
|
||
{
|
||
if (_optimizationService.IsBoosted)
|
||
{
|
||
StatusText = "Deactivating boost...";
|
||
await _optimizationService.DeactivateBoostAsync();
|
||
IsBoosted = false;
|
||
StatusText = "Boost deactivated – settings restored.";
|
||
}
|
||
else
|
||
{
|
||
StatusText = "Activating boost...";
|
||
var gameProcess = _gameDetectionService.CurrentGameProcess;
|
||
var result = await _optimizationService.ActivateBoostAsync(gameProcess);
|
||
IsBoosted = result.Success;
|
||
StatusText = result.Success ? "🚀 Boost Active" : result.Message;
|
||
}
|
||
}
|
||
|
||
// ── Quick Actions ──────────────────────────────────────────────────
|
||
private async Task CleanRamAsync()
|
||
{
|
||
StatusText = "Cleaning RAM...";
|
||
var result = await Task.Run(() => new RamOptimizationService().FullCleanup());
|
||
StatusText = $"RAM cleaned: {result.TrimmedCount} processes trimmed.";
|
||
}
|
||
|
||
private async Task CleanShaderCacheAsync()
|
||
{
|
||
StatusText = "Cleaning shader caches...";
|
||
var result = await Task.Run(() => ShaderCacheCleanerService.CleanAll());
|
||
StatusText = $"Shader cache: {result.FilesDeleted} files, {result.MBFreed:F1} MB freed.";
|
||
}
|
||
|
||
// ── Monitoring Callback ────────────────────────────────────────────
|
||
private void OnMetricsUpdated(SystemMetrics m)
|
||
{
|
||
_dispatcher.BeginInvoke(() =>
|
||
{
|
||
CpuUsage = m.CpuUsage;
|
||
RamUsagePercent = m.RamUsagePercent;
|
||
RamUsedMB = m.RamUsageMB;
|
||
RamTotalMB = m.RamTotalMB;
|
||
GpuUsage = m.GpuUsage;
|
||
CpuTemp = m.CpuTemperature;
|
||
GpuTemp = m.GpuTemperature;
|
||
PingMs = m.NetworkPingMs;
|
||
});
|
||
}
|
||
|
||
// ── Game Detection Callbacks ───────────────────────────────────────
|
||
#pragma warning disable CS4014 // Fire-and-forget is intentional for event handlers
|
||
private async void OnGameStarted(string processName)
|
||
{
|
||
_dispatcher.BeginInvoke(() => StatusText = $"Game detected: {processName}");
|
||
|
||
if (_settings.AutoBoostOnGameLaunch && !_optimizationService.IsBoosted)
|
||
{
|
||
var result = await _optimizationService.ActivateBoostAsync(processName);
|
||
_dispatcher.BeginInvoke(() =>
|
||
{
|
||
IsBoosted = result.Success;
|
||
StatusText = result.Success ? $"🚀 Auto-Boost for {processName}" : result.Message;
|
||
});
|
||
}
|
||
}
|
||
|
||
private async void OnGameStopped(string processName)
|
||
{
|
||
if (_optimizationService.IsBoosted)
|
||
{
|
||
await _optimizationService.DeactivateBoostAsync();
|
||
_dispatcher.BeginInvoke(() =>
|
||
{
|
||
IsBoosted = false;
|
||
StatusText = $"Game exited: {processName} – boost deactivated.";
|
||
});
|
||
}
|
||
}
|
||
#pragma warning restore CS4014
|
||
|
||
public void SaveSettings() => _settings.Save();
|
||
|
||
public void Dispose()
|
||
{
|
||
_monitoringService.MetricsUpdated -= OnMetricsUpdated;
|
||
_monitoringService.Dispose();
|
||
_gameDetectionService.Dispose();
|
||
_settings.Save();
|
||
Logger.Info("Application closed.");
|
||
}
|
||
}
|