Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 45 additions & 85 deletions MainWindow.Behaviors.partial.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ private void SetDataContexts()
// Set DataContext for the association view
this.AssociationView.DataContext = this.associationViewModel;

// Set DataContext for the performance view
this.PerformanceViewControl.DataContext = this.performanceViewModel;

// Set DataContext for the log viewer view
this.LogViewerViewControl.DataContext = this.logViewerViewModel;

Expand Down Expand Up @@ -340,15 +337,7 @@ private async Task LoadViewModelsAsync()
await powerPlanTask; // Ensure we get any exceptions
this.LogDebug("PowerPlanViewModel loaded successfully");

this.LogDebug("About to initialize PerformanceViewModel...");
var performanceTask = this.performanceViewModel.InitializeAsync();
var performanceResult = await Task.WhenAny(performanceTask, Task.Delay(5000));
if (performanceResult != performanceTask)
{
throw new TimeoutException("PerformanceViewModel.InitializeAsync() timed out after 5 seconds");
}
await performanceTask; // Ensure we get any exceptions
this.LogDebug("PerformanceViewModel initialized successfully");
this.LogDebug("Skipping optional diagnostics initialization until the diagnostics page is opened.");

this.LogDebug("About to load SystemTweaksViewModel...");
var systemTweaksTask = this.systemTweaksViewModel.LoadCommand.ExecuteAsync(null);
Expand Down Expand Up @@ -1009,57 +998,7 @@ private async Task UpdateSystemTrayContextMenuAsync()
{
try
{
// Update power plans in system tray
var powerPlanService = this.serviceProvider.GetRequiredService<IPowerPlanService>();
var powerPlans = await powerPlanService.GetPowerPlansAsync();
var activePowerPlan = await powerPlanService.GetActivePowerPlan();
this.systemTrayService.UpdatePowerPlans(powerPlans, activePowerPlan);

// Update profiles in system tray
var profilesDirectory = StoragePaths.ProfilesDirectory;
var profileNames = new List<string>();

if (Directory.Exists(profilesDirectory))
{
profileNames = Directory.GetFiles(profilesDirectory, "*.json")
.Select(Path.GetFileNameWithoutExtension)
.Where(name => !string.IsNullOrWhiteSpace(name))
.ToList()!;
}

this.systemTrayService.UpdateProfiles(profileNames);

// Update system status (with timeout to prevent hanging)
try
{
var performanceService = this.serviceProvider.GetRequiredService<IPerformanceMonitoringService>();
var metricsTask = performanceService.GetSystemMetricsAsync(lightweight: true);
var metricsResult = await Task.WhenAny(metricsTask, Task.Delay(2000)); // 2 second timeout

if (metricsResult == metricsTask)
{
var currentMetrics = await metricsTask;
this.systemTrayService.UpdateSystemStatus(
activePowerPlan?.Name ?? "Unknown",
currentMetrics?.TotalCpuUsage ?? 0.0,
currentMetrics?.MemoryUsagePercentage ?? 0.0);
}
else
{
// Timeout - use default values
this.systemTrayService.UpdateSystemStatus(
activePowerPlan?.Name ?? "Unknown",
0.0, 0.0);
}
}
catch (Exception metricsEx)
{
System.Diagnostics.Debug.WriteLine($"Failed to get performance metrics for tray: {metricsEx.Message}");
// Use default values
this.systemTrayService.UpdateSystemStatus(
activePowerPlan?.Name ?? "Unknown",
0.0, 0.0);
}
await this.systemTrayStatusUpdater.UpdateContextMenuAsync(this.systemTrayService);
}
catch (Exception ex)
{
Expand All @@ -1073,6 +1012,12 @@ private void StartSystemTrayUpdateTimer()
{
this.systemTrayUpdateTimer?.Stop();
this.systemTrayUpdateTimer?.Dispose();
this.systemTrayUpdateTimer = null;

if (!this.systemTrayStatusUpdater.ShouldRunPerformanceStatusUpdates)
{
return;
Comment on lines +1017 to +1019
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Continue tray status refreshes when diagnostics are disabled

This early return disables the tray refresh timer in the default configuration (ShowAdvancedDiagnostics == false), and there is no alternate periodic path to call UpdateSystemTrayStatusAsync afterward. That leaves the tray status line stale after startup (for example, after power plan changes), even though the non-diagnostics status still needs current plan information; only CPU/RAM sampling needed to be gated.

Useful? React with 👍 / 👎.

}

this.systemTrayUpdateFailureStreak = 0;
this.systemTrayUpdateTimer = new System.Timers.Timer(SystemTrayUpdateBaseIntervalMs);
Expand Down Expand Up @@ -1150,21 +1095,9 @@ private async Task<bool> UpdateSystemTrayStatusAsync()
{
try
{
var powerPlanService = this.serviceProvider.GetRequiredService<IPowerPlanService>();
var performanceService = this.serviceProvider.GetRequiredService<IPerformanceMonitoringService>();

var activePowerPlan = await powerPlanService.GetActivePowerPlan();
var currentMetrics = await performanceService.GetSystemMetricsAsync(lightweight: true);

await this.Dispatcher.InvokeAsync(() =>
{
this.systemTrayService.UpdateSystemStatus(
activePowerPlan?.Name ?? "Unknown",
currentMetrics?.TotalCpuUsage ?? 0.0,
currentMetrics?.MemoryUsagePercentage ?? 0.0);
});

return true;
return await this.systemTrayStatusUpdater.UpdateStatusAsync(
this.systemTrayService,
action => this.Dispatcher.InvokeAsync(action).Task);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -1343,6 +1276,12 @@ private void ResumeForegroundRefreshes()
this.isSystemTrayUpdatesSuspended = false;
this.systemTrayUpdateFailureStreak = 0;
this.systemTrayUpdateTimer?.Stop();

if (!this.systemTrayStatusUpdater.ShouldRunPerformanceStatusUpdates)
{
return;
}

if (this.systemTrayUpdateTimer != null)
{
this.systemTrayUpdateTimer.Interval = SystemTrayUpdateBaseIntervalMs;
Expand All @@ -1365,8 +1304,13 @@ private void ResumeForegroundRefreshes()

private AppActivityState GetForegroundActivityState()
{
return this.ProcessManagementTab.Visibility == Visibility.Visible
? AppActivityState.ForegroundProcessView
if (this.ProcessManagementTab.Visibility == Visibility.Visible)
{
return AppActivityState.ForegroundProcessView;
}

return this.PerformanceViewControl.Visibility == Visibility.Visible
? AppActivityState.ForegroundDiagnosticsView
: AppActivityState.ForegroundOtherTab;
}

Expand Down Expand Up @@ -1408,9 +1352,9 @@ private void ApplyAppRefreshPolicy(AppActivityState state)

if (decision.PerformanceUiMonitoringEnabled)
{
_ = this.performanceViewModel.ResumeBackgroundMonitoringAsync();
_ = this.GetPerformanceViewModel().ActivateDiagnosticsAsync();
}
else
else if (this.performanceViewModel != null)
{
_ = this.performanceViewModel.SuspendBackgroundMonitoringAsync();
}
Expand Down Expand Up @@ -1541,6 +1485,11 @@ private void SelectMainTab(string tag)
return;
}

if (string.Equals(tag, "Performance", StringComparison.Ordinal))
{
this.GetPerformanceViewModel();
}

this.ApplySectionVisibility(tag);

if (string.Equals(tag, "Performance", StringComparison.Ordinal))
Expand Down Expand Up @@ -1791,9 +1740,14 @@ private void ApplySectionVisibility(string tag)
return;
}

this.ApplyAppRefreshPolicy(string.Equals(tag, "Process", StringComparison.Ordinal)
? AppActivityState.ForegroundProcessView
: AppActivityState.ForegroundOtherTab);
var activityState = tag switch
{
"Process" => AppActivityState.ForegroundProcessView,
"Performance" => AppActivityState.ForegroundDiagnosticsView,
_ => AppActivityState.ForegroundOtherTab,
};

this.ApplyAppRefreshPolicy(activityState);
}

private void NavMenuItem_Click(object sender, RoutedEventArgs e)
Expand Down Expand Up @@ -1840,6 +1794,11 @@ private async Task NavMenuItem_ClickAsync(object sender, RoutedEventArgs e)
return;
}

if (string.Equals(tag, "Performance", StringComparison.Ordinal))
{
this.GetPerformanceViewModel();
}

this.ApplySectionVisibility(tag);

if (string.Equals(tag, "Performance", StringComparison.Ordinal))
Expand Down Expand Up @@ -1895,6 +1854,7 @@ protected override void OnClosed(EventArgs e)

this.initializationTimeoutTimer?.Stop();
this.initializationTimeoutTimer?.Dispose();
this.performanceViewModel?.Dispose();

this.selfResourceManagementService.RestoreForegroundMode();
this.navigationBehavior.Dispose();
Expand Down
14 changes: 7 additions & 7 deletions MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
<ui:SymbolIcon Symbol="TasksApp24"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="NavPerf" Content="Performance" Tag="Performance" TargetPageTag="Performance" Click="NavMenuItem_Click">
<ui:NavigationViewItem x:Name="NavPerf" Content="Diagnostics" Tag="Performance" TargetPageTag="Performance" Click="NavMenuItem_Click">
<ui:NavigationViewItem.Icon>
<ui:SymbolIcon Symbol="DataHistogram24"/>
</ui:NavigationViewItem.Icon>
Expand Down Expand Up @@ -325,14 +325,14 @@
</Border.Effect>
<StackPanel>
<TextBlock x:Name="PerformanceIntroTitle"
Text="Performance Dashboard"
Text="Optional Diagnostics"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Margin="0,0,0,10"/>

<TextBlock x:Name="PerformanceIntroDescription"
Text="This page gives you live CPU, memory and hotspot visibility so you can build precise optimization rules."
Text="Diagnostics are optional and intended for troubleshooting. For in-game overlays and detailed performance graphs, use dedicated tools."
TextWrapping="Wrap"
Margin="0,0,0,10"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
Expand All @@ -344,21 +344,21 @@
Margin="0,0,0,6"/>

<TextBlock x:Name="PerformanceIntroTip1"
Text="1. Start live metrics to collect a current system snapshot."
Text="1. Open diagnostics only when you need a focused troubleshooting snapshot."
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,0,4"/>
<TextBlock x:Name="PerformanceIntroTip2"
Text="2. Review top processes and pick recurring workloads."
Text="2. Review hotspots only as a hint before creating automation rules."
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,0,4"/>
<TextBlock x:Name="PerformanceIntroTip3"
Text="3. Create rules from hotspots to automate power plan and affinity behavior."
Text="3. Stop live metrics when you are done troubleshooting."
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,0,16"/>

<Button x:Name="PerformanceIntroContinueButton"
Content="Continue to Performance"
Content="Continue to Diagnostics"
Click="PerformanceIntroContinue_Click"
HorizontalAlignment="Right"
Background="{DynamicResource AccentFillColorDefaultBrush}"
Expand Down
30 changes: 27 additions & 3 deletions MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow

private readonly ProcessViewModel processViewModel;
private readonly PowerPlanViewModel powerPlanViewModel;
private readonly PerformanceViewModel performanceViewModel;
private readonly IDiagnosticsViewModelProvider diagnosticsViewModelProvider;
private readonly ProcessPowerPlanAssociationViewModel associationViewModel;
private readonly LogViewerViewModel logViewerViewModel;
private readonly ISystemTrayService systemTrayService;
private readonly ISystemTrayStatusUpdater systemTrayStatusUpdater;
private readonly IApplicationSettingsService settingsService;
private readonly INotificationService notificationService;
private readonly IProcessMonitorService processMonitorService;
Expand All @@ -59,6 +60,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
private readonly IServiceProvider serviceProvider;
private readonly IThemeService themeService;
private System.Timers.Timer? systemTrayUpdateTimer;
private PerformanceViewModel? performanceViewModel;
private bool isSystemTrayUpdatesSuspended;
private int isSystemTrayUpdateInProgress;
private int systemTrayUpdateFailureStreak;
Expand All @@ -84,10 +86,11 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
public MainWindow(
ProcessViewModel processViewModel,
PowerPlanViewModel powerPlanViewModel,
PerformanceViewModel performanceViewModel,
IDiagnosticsViewModelProvider diagnosticsViewModelProvider,
ProcessPowerPlanAssociationViewModel associationViewModel,
LogViewerViewModel logViewerViewModel,
ISystemTrayService systemTrayService,
ISystemTrayStatusUpdater systemTrayStatusUpdater,
IApplicationSettingsService settingsService,
INotificationService notificationService,
IProcessMonitorService processMonitorService,
Expand All @@ -109,6 +112,7 @@ public MainWindow(

this.InitializeComponent();
System.Diagnostics.Debug.WriteLine("InitializeComponent completed");
this.ConfigureDiagnosticsNavigation();

// Initialize loading overlay
this.InitializeLoadingOverlay();
Expand All @@ -117,10 +121,11 @@ public MainWindow(

this.processViewModel = processViewModel;
this.powerPlanViewModel = powerPlanViewModel;
this.performanceViewModel = performanceViewModel;
this.diagnosticsViewModelProvider = diagnosticsViewModelProvider;
this.associationViewModel = associationViewModel;
this.logViewerViewModel = logViewerViewModel;
this.systemTrayService = systemTrayService;
this.systemTrayStatusUpdater = systemTrayStatusUpdater;
this.settingsService = settingsService;
this.notificationService = notificationService;
this.processMonitorService = processMonitorService;
Expand Down Expand Up @@ -159,5 +164,24 @@ public MainWindow(
throw;
}
}

private void ConfigureDiagnosticsNavigation()
{
this.NavPerf.Visibility = AppNavigationOptions.ShowAdvancedDiagnostics
? Visibility.Visible
: Visibility.Collapsed;
}

private PerformanceViewModel GetPerformanceViewModel()
{
if (this.performanceViewModel != null)
{
return this.performanceViewModel;
}

this.performanceViewModel = this.diagnosticsViewModelProvider.GetOrCreate();
this.PerformanceViewControl.DataContext = this.performanceViewModel;
return this.performanceViewModel;
}
}
}
26 changes: 26 additions & 0 deletions Services/AppNavigationOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* ThreadPilot - Advanced Windows Process and Power Plan Manager
* Copyright (C) 2025 Prime Build
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace ThreadPilot.Services
{
/// <summary>
/// Compile-time navigation switches for optional surfaces.
/// </summary>
public static class AppNavigationOptions
{
public static bool ShowAdvancedDiagnostics => false;
}
}
Loading
Loading