From 57acdfcf0045353e803ba1d3c96d4eae856535c6 Mon Sep 17 00:00:00 2001
From: RegentScar <188844243+RegentScar@users.noreply.github.com>
Date: Fri, 3 Apr 2026 19:16:23 +0200
Subject: [PATCH 1/7] Refactor to show real-time CPU usage per process
Replaces average CPU usage with real-time measurement in the process list.
Pause process CPU usage updates, core usage updates and sorting updates when the window is hidden (in System-Tray).
---
CPUSetSetter/Platforms/IProcessHandler.cs | 4 +-
.../Windows/ProcessHandlerWindows.cs | 59 ++++++++-----------
.../CoreUsage/CoreUsageControl.xaml.cs | 21 +++++--
.../Processes/ProcessListEntryViewModel.cs | 11 ++--
.../UI/Tabs/Processes/ProcessesTab.xaml | 8 +--
.../Tabs/Processes/ProcessesTabViewModel.cs | 32 ++++++++--
6 files changed, 83 insertions(+), 52 deletions(-)
diff --git a/CPUSetSetter/Platforms/IProcessHandler.cs b/CPUSetSetter/Platforms/IProcessHandler.cs
index 3005e84..d85e831 100644
--- a/CPUSetSetter/Platforms/IProcessHandler.cs
+++ b/CPUSetSetter/Platforms/IProcessHandler.cs
@@ -6,10 +6,10 @@ namespace CPUSetSetter.Platforms
public interface IProcessHandler : IDisposable
{
///
- /// Get the average CPU usage of the process of the recent past (~30 seconds)
+ /// Get the CPU usage of the process
///
/// Between 0 and 1 on success. -1 on fail
- double GetAverageCpuUsage();
+ double GetCpuUsage();
bool ApplyMask(LogicalProcessorMask mask);
}
}
diff --git a/CPUSetSetter/Platforms/Windows/ProcessHandlerWindows.cs b/CPUSetSetter/Platforms/Windows/ProcessHandlerWindows.cs
index a4713fd..eb41cc5 100644
--- a/CPUSetSetter/Platforms/Windows/ProcessHandlerWindows.cs
+++ b/CPUSetSetter/Platforms/Windows/ProcessHandlerWindows.cs
@@ -11,7 +11,6 @@ namespace CPUSetSetter.Platforms.Windows
public class ProcessHandlerWindows : IProcessHandler
{
private readonly static Dictionary _setIdPerLogicalProcessor;
- private readonly Queue _cpuTimeMovingAverageBuffer = new();
private readonly string _executableName;
private readonly uint _pid;
@@ -32,46 +31,46 @@ public ProcessHandlerWindows(string executableName, uint pid, SafeProcessHandle
_queryLimitedInfoHandle = queryHandle;
}
- public double GetAverageCpuUsage()
+ private bool _isFirstCall = true;
+ private long _lastTimeStamp = 0;
+ private TimeSpan _lastTotalCpuTime = TimeSpan.Zero;
+
+ public double GetCpuUsage()
{
if (_queryLimitedInfoHandle.IsInvalid)
{
return -1;
}
- DateTime now = DateTime.Now;
- // Remove datapoints older than 30 seconds from the moving average buffer
- while (_cpuTimeMovingAverageBuffer.Count > 0)
- {
- TimeSpan datapointAge = now - _cpuTimeMovingAverageBuffer.Peek().Timestamp;
- if (datapointAge.TotalSeconds > 30)
- {
- _cpuTimeMovingAverageBuffer.Dequeue();
- }
- else
- {
- break;
- }
- }
-
- // Get the current total CPU time of the process
+ // Get the current CPU time using GetProcessTimes
bool success = NativeMethods.GetProcessTimes(_queryLimitedInfoHandle, out FILETIME _, out FILETIME _, out FILETIME kernelTime, out FILETIME userTime);
if (!success)
{
return -1;
}
+
+ long now = System.Diagnostics.Stopwatch.GetTimestamp();
TimeSpan totalCpuTime = TimeSpan.FromTicks((long)(kernelTime.ULong + userTime.ULong));
- _cpuTimeMovingAverageBuffer.Enqueue(new() { Timestamp = now, TotalCpuTime = totalCpuTime });
- // Take the CPU time from now and (up to) a minute ago, and get the average usage %
- CpuTimeTimestamp startDatapoint = _cpuTimeMovingAverageBuffer.Peek();
- TimeSpan deltaTime = now - startDatapoint.Timestamp;
- TimeSpan deltaCpuTime = totalCpuTime - startDatapoint.TotalCpuTime;
+ if (_isFirstCall)
+ {
+ _isFirstCall = false;
+ _lastTimeStamp = now;
+ _lastTotalCpuTime = totalCpuTime;
+ return 0; // There is no delta on the first call
+ }
+
+ TimeSpan deltaTime = System.Diagnostics.Stopwatch.GetElapsedTime(_lastTimeStamp, now);
+ TimeSpan deltaCpuTime = totalCpuTime - _lastTotalCpuTime;
+
+ _lastTimeStamp = now;
+ _lastTotalCpuTime = totalCpuTime;
- if (deltaCpuTime.Ticks == 0)
+ if (deltaCpuTime.Ticks <= 0 || deltaTime.Ticks <= 0)
return 0;
- else
- return (double)deltaCpuTime.Ticks / deltaTime.Ticks / CpuInfo.LogicalProcessorCount;
+ else if (CpuInfo.LogicalProcessorCount <= 0)
+ return 0;
+ return Math.Clamp((double)deltaCpuTime.Ticks / deltaTime.Ticks / CpuInfo.LogicalProcessorCount, 0, 1);
}
public bool ApplyMask(LogicalProcessorMask mask)
@@ -276,7 +275,7 @@ private static Dictionary GetCpuSetIdPerLogicalProcessor()
while (current < bufferEnd)
{
SYSTEM_CPU_SET_INFORMATION item = Marshal.PtrToStructure(current);
-
+
if (item.Type != CPU_SET_INFORMATION_TYPE.CpuSetInformation)
{
throw new InvalidCastException("Invalid data type encountered; aborting");
@@ -299,13 +298,7 @@ public void Dispose()
_queryLimitedInfoHandle.Dispose();
_setLimitedInfoHandle?.Dispose();
_setInfoHandle?.Dispose();
- _cpuTimeMovingAverageBuffer.Clear();
}
- private class CpuTimeTimestamp
- {
- public DateTime Timestamp { get; init; }
- public TimeSpan TotalCpuTime { get; init; }
- }
}
}
diff --git a/CPUSetSetter/UI/Tabs/Processes/CoreUsage/CoreUsageControl.xaml.cs b/CPUSetSetter/UI/Tabs/Processes/CoreUsage/CoreUsageControl.xaml.cs
index a30714b..c225cd1 100644
--- a/CPUSetSetter/UI/Tabs/Processes/CoreUsage/CoreUsageControl.xaml.cs
+++ b/CPUSetSetter/UI/Tabs/Processes/CoreUsage/CoreUsageControl.xaml.cs
@@ -14,6 +14,9 @@ namespace CPUSetSetter.UI.Tabs.Processes.CoreUsage
///
public partial class CoreUsageControl : UserControl
{
+ private const int UPDATE_DELAY_VISIBLE_MS = 1000;
+ private const int UPDATE_DELAY_HIDDEN_MS = 2000;
+
private static bool _isRunning = false;
private static List coreUsages = CpuInfo.LogicalProcessorNames.Select(cpuName => new CoreUsage(cpuName)).ToList();
@@ -140,6 +143,19 @@ private static async Task PerCoreUsageUpdateLoopInner(Dispatcher dispatcher)
bool[] parkedValues = new bool[coreUsages.Count];
while (true)
{
+ bool windowIsVisible = false;
+ await dispatcher.InvokeAsync(() =>
+ {
+ windowIsVisible = App.Current.MainWindow.Visibility == Visibility.Visible;
+ });
+
+ if (!windowIsVisible)
+ {
+ // Skip the update if the window is not visible
+ await Task.Delay(UPDATE_DELAY_HIDDEN_MS);
+ continue;
+ }
+
for (int i = 0; i < coreUsages.Count; ++i)
{
// Get the Utility% of each logical processor, and clamp it between 0.0-1.0
@@ -152,11 +168,9 @@ private static async Task PerCoreUsageUpdateLoopInner(Dispatcher dispatcher)
parkedValues[i] = false;
}
- bool windowIsVisible = false;
// Apply the new values on the dispatcher to make sure changes are done in the same UI frame
await dispatcher.InvokeAsync(() =>
{
- windowIsVisible = App.Current.MainWindow.Visibility == Visibility.Visible;
for (int i = 0; i < coreUsages.Count; ++i)
{
coreUsages[i].Utility = utilityValues[i];
@@ -164,8 +178,7 @@ await dispatcher.InvokeAsync(() =>
}
});
- int delayTime = windowIsVisible ? 1500 : 6000; // Poll the CPU usage less often when not visible
- await Task.Delay(delayTime);
+ await Task.Delay(UPDATE_DELAY_VISIBLE_MS);
}
}
}
diff --git a/CPUSetSetter/UI/Tabs/Processes/ProcessListEntryViewModel.cs b/CPUSetSetter/UI/Tabs/Processes/ProcessListEntryViewModel.cs
index 87ac9a8..99cda79 100644
--- a/CPUSetSetter/UI/Tabs/Processes/ProcessListEntryViewModel.cs
+++ b/CPUSetSetter/UI/Tabs/Processes/ProcessListEntryViewModel.cs
@@ -19,8 +19,9 @@ public partial class ProcessListEntryViewModel : ObservableObject, IDisposable
public string ImagePath { get; }
[ObservableProperty]
- [NotifyPropertyChangedFor(nameof(AverageCpuPercentageStr))]
- private double _averageCpuUsage;
+ [NotifyPropertyChangedFor(nameof(CpuPercentageStr))]
+ private double _cpuUsage;
+
[ObservableProperty]
private LogicalProcessorMask _mask;
@@ -28,7 +29,7 @@ public partial class ProcessListEntryViewModel : ObservableObject, IDisposable
[ObservableProperty]
private bool _previousApplyFailed = false;
- public string AverageCpuPercentageStr => AverageCpuUsage == -1 ? "" : $"{AverageCpuUsage * 100:F1}%";
+ public string CpuPercentageStr => CpuUsage == -1 ? "" : $"{CpuUsage * 100:F1}%";
public ProcessListEntryViewModel(ProcessInfo pInfo)
{
@@ -44,12 +45,12 @@ public ProcessListEntryViewModel(ProcessInfo pInfo)
SetMask(mask, false);
_mask = mask; // _mask is already set by SetMask, this just suppresses a warning
- AverageCpuUsage = _processHandler.GetAverageCpuUsage();
+ CpuUsage = _processHandler.GetCpuUsage();
}
public void UpdateCpuUsage()
{
- AverageCpuUsage = _processHandler.GetAverageCpuUsage();
+ CpuUsage = _processHandler.GetCpuUsage();
}
public bool SetMask(LogicalProcessorMask newMask, bool updateRule)
diff --git a/CPUSetSetter/UI/Tabs/Processes/ProcessesTab.xaml b/CPUSetSetter/UI/Tabs/Processes/ProcessesTab.xaml
index 1c7b22d..e722fac 100644
--- a/CPUSetSetter/UI/Tabs/Processes/ProcessesTab.xaml
+++ b/CPUSetSetter/UI/Tabs/Processes/ProcessesTab.xaml
@@ -73,10 +73,10 @@
Width="3*">
-
-
+ ((ProcessListEntryViewModel)item).Name.Contains(ProcessNameFilter, StringComparison.OrdinalIgnoreCase);
Task.Run(ProcessCpuUsageUpdateLoop);
@@ -132,19 +135,40 @@ private void OnExitedProcess(uint exitedPid)
private async Task ProcessCpuUsageUpdateLoop()
{
+ bool wasVisible = true;
+
while (true)
{
bool windowIsVisible = false;
await _dispatcher.InvokeAsync(() =>
{
windowIsVisible = App.Current.MainWindow.Visibility == Visibility.Visible;
+ });
+
+ if (!windowIsVisible)
+ {
+ if (wasVisible)
+ {
+ await _dispatcher.InvokeAsync(PauseListUpdates);
+ wasVisible = false;
+ }
+ await Task.Delay(UPDATE_DELAY_HIDDEN_MS);
+ continue;
+ }
+ else if (!wasVisible)
+ {
+ await _dispatcher.InvokeAsync(ResumeListUpdates);
+ wasVisible = true;
+ }
+
+ await _dispatcher.InvokeAsync(() =>
+ {
foreach (ProcessListEntryViewModel pEntry in RunningProcesses)
{
pEntry.UpdateCpuUsage();
}
});
- int delayTime = windowIsVisible ? 1000 : 5000; // Poll the CPU usage less often when not visible
- await Task.Delay(delayTime);
+ await Task.Delay(UPDATE_DELAY_VISIBLE_MS);
}
}
}
From 007f5017422aa4b358adcf4f3b32186caa36dcbb Mon Sep 17 00:00:00 2001
From: RegentScar <188844243+RegentScar@users.noreply.github.com>
Date: Fri, 3 Apr 2026 21:11:28 +0200
Subject: [PATCH 2/7] Update title bar color to match app theme
---
CPUSetSetter/Themes/AppTheme.cs | 73 +++++++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/CPUSetSetter/Themes/AppTheme.cs b/CPUSetSetter/Themes/AppTheme.cs
index f90b913..58f77a0 100644
--- a/CPUSetSetter/Themes/AppTheme.cs
+++ b/CPUSetSetter/Themes/AppTheme.cs
@@ -1,5 +1,7 @@
using Microsoft.Win32;
+using System.Runtime.InteropServices;
using System.Windows;
+using System.Windows.Interop;
namespace CPUSetSetter.Themes
@@ -9,6 +11,7 @@ public static class AppTheme
public static void ApplyTheme(Theme theme)
{
string themePath;
+ bool isDark = false;
switch (theme)
{
case Theme.Light:
@@ -16,6 +19,7 @@ public static void ApplyTheme(Theme theme)
break;
case Theme.Dark:
themePath = "Themes/DarkThemeColors.xaml";
+ isDark = true;
break;
case Theme.System:
ApplySystemTheme();
@@ -41,6 +45,8 @@ public static void ApplyTheme(Theme theme)
{
throw new InvalidOperationException($"Unexpected MergedDictionaries count: {mergedDicts.Count}");
}
+
+ UpdateTitleBarStatus(isDark);
}
private static void ApplySystemTheme()
@@ -54,6 +60,73 @@ private static void ApplySystemTheme()
}
ApplyTheme(Theme.Light);
}
+
+ [DllImport("dwmapi.dll")]
+ private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
+
+ private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;
+ private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
+
+ private static bool _isDarkTheme;
+ private static bool _isWindowLoadedRegistered;
+
+ private static void UpdateTitleBarStatus(bool isDarkTheme)
+ {
+ if (Application.Current == null) return;
+ Application.Current.Dispatcher.VerifyAccess();
+
+ _isDarkTheme = isDarkTheme;
+
+ if (!_isWindowLoadedRegistered)
+ {
+ EventManager.RegisterClassHandler(typeof(Window), FrameworkElement.LoadedEvent, new RoutedEventHandler(Window_Loaded));
+ _isWindowLoadedRegistered = true;
+ }
+
+ foreach (Window window in Application.Current.Windows)
+ {
+ var hwnd = new WindowInteropHelper(window).Handle;
+ if (hwnd == IntPtr.Zero)
+ {
+ window.SourceInitialized += OnSourceInitialized;
+ }
+ else
+ {
+ ApplyDarkMode(hwnd, _isDarkTheme);
+ }
+ }
+ }
+
+ private static void OnSourceInitialized(object? sender, EventArgs e)
+ {
+ if (sender is Window window)
+ {
+ window.SourceInitialized -= OnSourceInitialized;
+ var hwnd = new WindowInteropHelper(window).Handle;
+ ApplyDarkMode(hwnd, _isDarkTheme);
+ }
+ }
+
+ private static void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ if (sender is Window window)
+ {
+ var hwnd = new WindowInteropHelper(window).Handle;
+ if (hwnd != IntPtr.Zero)
+ {
+ ApplyDarkMode(hwnd, _isDarkTheme);
+ }
+ }
+ }
+
+ private static void ApplyDarkMode(IntPtr hwnd, bool isDarkTheme)
+ {
+ int useImmersiveDarkMode = isDarkTheme ? 1 : 0;
+ if (DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, ref useImmersiveDarkMode, sizeof(int)) != 0)
+ {
+ DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, ref useImmersiveDarkMode, sizeof(int));
+ }
+ }
}
public enum Theme
From edfa5426cccb1c7cd946226d31f898ded375e945 Mon Sep 17 00:00:00 2001
From: RegentScar <188844243+RegentScar@users.noreply.github.com>
Date: Sat, 4 Apr 2026 23:40:43 +0200
Subject: [PATCH 3/7] Add in-app Windows Game Mode control
- Add a CheckBox to the processes window for toggling Game Mode directly from the app
- Allow disabling Game Mode through the GameModeWarning popup
- Clear and reapply all active masks when Windows Game Mode is enabled/disabled
- Add periodic polling to detect external Game Mode changes
Allows quick switching for games that cause issues with CPU Set Setter without needing to close the app
---
CPUSetSetter/App.xaml.cs | 1 +
CPUSetSetter/Platforms/Windows/GameMode.cs | 69 +++++++++++++++
.../Platforms/Windows/GameModeWarning.cs | 57 +++++++------
.../Windows/ProcessHandlerWindows.cs | 7 +-
CPUSetSetter/Themes/AppTheme.cs | 5 ++
CPUSetSetter/UI/MainWindow.xaml | 84 +++++++++++--------
CPUSetSetter/UI/MainWindow.xaml.cs | 24 +++++-
.../Processes/ProcessListEntryViewModel.cs | 42 +++++++++-
.../UI/Tabs/Processes/ProcessesTab.xaml | 5 +-
.../Tabs/Processes/ProcessesTabViewModel.cs | 18 ++++
10 files changed, 238 insertions(+), 74 deletions(-)
create mode 100644 CPUSetSetter/Platforms/Windows/GameMode.cs
diff --git a/CPUSetSetter/App.xaml.cs b/CPUSetSetter/App.xaml.cs
index 6c35d7d..f87e916 100644
--- a/CPUSetSetter/App.xaml.cs
+++ b/CPUSetSetter/App.xaml.cs
@@ -1,5 +1,6 @@
using CPUSetSetter.Config.Models;
using CPUSetSetter.Platforms;
+using CPUSetSetter.Platforms.Windows;
using CPUSetSetter.TrayIcon;
using CPUSetSetter.UI;
using CPUSetSetter.Util;
diff --git a/CPUSetSetter/Platforms/Windows/GameMode.cs b/CPUSetSetter/Platforms/Windows/GameMode.cs
new file mode 100644
index 0000000..ebe70d1
--- /dev/null
+++ b/CPUSetSetter/Platforms/Windows/GameMode.cs
@@ -0,0 +1,69 @@
+using CPUSetSetter.UI.Tabs.Processes;
+using Microsoft.Win32;
+using System;
+
+namespace CPUSetSetter.Platforms.Windows
+{
+ public static class GameMode
+ {
+ private const string GameBarKey = @"Software\Microsoft\GameBar";
+ private const string ValueName = "AutoGameModeEnabled";
+
+ public static event EventHandler? IsEnabledChanged;
+ private static readonly object _stateLock = new();
+ private static bool? _lastState;
+
+ public static void CheckState()
+ {
+ bool raiseEvent = false;
+
+ lock (_stateLock)
+ {
+ bool currentState = IsEnabled;
+ if (!_lastState.HasValue)
+ {
+ _lastState = currentState;
+ return;
+ }
+ if (_lastState != currentState)
+ {
+ _lastState = currentState;
+ raiseEvent = true;
+ }
+ }
+
+ if (raiseEvent)
+ {
+ IsEnabledChanged?.Invoke(null, EventArgs.Empty);
+ }
+ }
+
+ public static bool IsEnabled
+ {
+ get
+ {
+ using var key = Registry.CurrentUser.OpenSubKey(GameBarKey);
+ return key?.GetValue(ValueName, 0) is int intValue && intValue != 0;
+ }
+ set
+ {
+ try
+ {
+ using var key = Registry.CurrentUser.CreateSubKey(GameBarKey);
+ if (key == null)
+ {
+ WindowLogger.Write("Unable to toggle Windows Game Mode: Registry key could not be created or opened.");
+ return;
+ }
+
+ key.SetValue(ValueName, value ? 1 : 0, RegistryValueKind.DWord);
+ CheckState();
+ }
+ catch (Exception ex)
+ {
+ WindowLogger.Write($"Unable to toggle Windows Game Mode: {ex.Message}");
+ }
+ }
+ }
+ }
+}
diff --git a/CPUSetSetter/Platforms/Windows/GameModeWarning.cs b/CPUSetSetter/Platforms/Windows/GameModeWarning.cs
index 3bbcdc6..a5da9f0 100644
--- a/CPUSetSetter/Platforms/Windows/GameModeWarning.cs
+++ b/CPUSetSetter/Platforms/Windows/GameModeWarning.cs
@@ -1,11 +1,8 @@
using CPUSetSetter.Config.Models;
using CPUSetSetter.UI.Tabs.Processes;
-using Microsoft.Win32;
-using System.Diagnostics;
using System.Windows;
-
-namespace CPUSetSetter.Platforms
+namespace CPUSetSetter.Platforms.Windows
{
///
/// Windows Game Mode is abysmal to gaming performance when combined with CPU Set Setter.
@@ -13,23 +10,35 @@ namespace CPUSetSetter.Platforms
///
public static class WindowsGameModeWarning
{
+ private static bool _eventHooked = false;
+ private const string LogWarningMessage = "WARNING: Windows Game Mode is enabled. As long as Game Mode is enabled, no masks are applied.";
+
public static void ShowIfEnabled()
{
- if (!GameModeEnabled())
- return;
+ if (!_eventHooked)
+ {
+ _eventHooked = true;
+ GameMode.IsEnabledChanged += (s, e) =>
+ {
+ if (GameMode.IsEnabled)
+ {
+ WindowLogger.Write(LogWarningMessage);
+ }
+ };
+ }
- WindowLogger.Write("WARNING: Windows Game Mode is enabled. This might have a severe negative impact on gaming performance/stability " +
- "when also using CPU Set Setter on games.");
+ if (!GameMode.IsEnabled)
+ return;
if (AppConfig.Instance.ShowGameModePopup)
{
- App.Current.Dispatcher.InvokeAsync(() =>
+ Application.Current?.Dispatcher?.InvokeAsync(() =>
{
MessageBoxResult result = MessageBox.Show(
"Windows Game Mode is currently enabled.\n" +
"On AMD CPUs, Game Mode is known to conflict with CPU Set Setter, leading to lower FPS and game crashes.\n" +
"I am not sure if Intel CPUs are affected too. So if you have one, please share your findings with me on GitHub!\n\n" +
- "Would you like to open the Game Mode Settings page?\n\n" +
+ "Would you like CPU Set Setter to disable Game Mode for you?\n\n" +
"(This popup can be disabled in the Settings tab)",
"CPU Set Setter: Windows Game Mode warning",
MessageBoxButton.YesNo,
@@ -37,28 +46,18 @@ public static void ShowIfEnabled()
if (result == MessageBoxResult.Yes)
{
- OpenGameModeSettings();
+ GameMode.IsEnabled = false;
+ }
+ else
+ {
+ WindowLogger.Write(LogWarningMessage);
}
});
}
- }
-
- private static bool GameModeEnabled()
- {
- using RegistryKey? gameBarKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\GameBar");
- if (gameBarKey is null)
- return false;
-
- object? gameModeValue = gameBarKey.GetValue("AutoGameModeEnabled");
- if (gameModeValue is int value)
- return value != 0;
-
- return false;
- }
-
- private static void OpenGameModeSettings()
- {
- Process.Start(new ProcessStartInfo { FileName = "ms-settings:gaming-gamemode", UseShellExecute = true });
+ else
+ {
+ WindowLogger.Write(LogWarningMessage);
+ }
}
}
}
diff --git a/CPUSetSetter/Platforms/Windows/ProcessHandlerWindows.cs b/CPUSetSetter/Platforms/Windows/ProcessHandlerWindows.cs
index eb41cc5..e0df4fa 100644
--- a/CPUSetSetter/Platforms/Windows/ProcessHandlerWindows.cs
+++ b/CPUSetSetter/Platforms/Windows/ProcessHandlerWindows.cs
@@ -2,6 +2,7 @@
using CPUSetSetter.UI.Tabs.Processes;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
+using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
@@ -49,7 +50,7 @@ public double GetCpuUsage()
return -1;
}
- long now = System.Diagnostics.Stopwatch.GetTimestamp();
+ long now = Stopwatch.GetTimestamp();
TimeSpan totalCpuTime = TimeSpan.FromTicks((long)(kernelTime.ULong + userTime.ULong));
if (_isFirstCall)
@@ -60,7 +61,7 @@ public double GetCpuUsage()
return 0; // There is no delta on the first call
}
- TimeSpan deltaTime = System.Diagnostics.Stopwatch.GetElapsedTime(_lastTimeStamp, now);
+ TimeSpan deltaTime = Stopwatch.GetElapsedTime(_lastTimeStamp, now);
TimeSpan deltaCpuTime = totalCpuTime - _lastTotalCpuTime;
_lastTimeStamp = now;
@@ -85,6 +86,8 @@ public bool ApplyMask(LogicalProcessorMask mask)
result = ApplyCpuSet(mask);
else if (_previousMaskType == MaskApplyType.Affinity)
result = ApplyAffinity(mask);
+ else if (_previousMaskType == MaskApplyType.NoMask)
+ result = true;
else
throw new NotImplementedException();
break;
diff --git a/CPUSetSetter/Themes/AppTheme.cs b/CPUSetSetter/Themes/AppTheme.cs
index 58f77a0..12d25ec 100644
--- a/CPUSetSetter/Themes/AppTheme.cs
+++ b/CPUSetSetter/Themes/AppTheme.cs
@@ -61,6 +61,11 @@ private static void ApplySystemTheme()
ApplyTheme(Theme.Light);
}
+ ///
+ /// DwmSetWindowAttribute is used to set the title bar color on Windows 10 2004 and later.
+ /// On earlier versions, it will fail and falls back to the older attribute that only works on Windows 10 1903 and later.
+ ///
+
[DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
diff --git a/CPUSetSetter/UI/MainWindow.xaml b/CPUSetSetter/UI/MainWindow.xaml
index 05cf8cc..054cf8e 100644
--- a/CPUSetSetter/UI/MainWindow.xaml
+++ b/CPUSetSetter/UI/MainWindow.xaml
@@ -14,41 +14,51 @@
Width="1020"
MinWidth="900">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CPUSetSetter/UI/MainWindow.xaml.cs b/CPUSetSetter/UI/MainWindow.xaml.cs
index c5030c5..34fe7ba 100644
--- a/CPUSetSetter/UI/MainWindow.xaml.cs
+++ b/CPUSetSetter/UI/MainWindow.xaml.cs
@@ -1,15 +1,18 @@
-using CPUSetSetter.UI.Tabs.Processes;
+using CPUSetSetter.Platforms.Windows;
+using CPUSetSetter.UI.Tabs.Processes;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
-
namespace CPUSetSetter.UI
{
public partial class MainWindow : Window
{
private bool _listIsPaused = false;
+ // Store the handler in a field for proper unsubscription
+ private readonly EventHandler _gameModeChangedHandler;
+
public MainWindow()
{
InitializeComponent();
@@ -19,6 +22,23 @@ public MainWindow()
PreviewKeyUp += (_, e) => KeyReleased(e);
Deactivated += (_, _) => ResumeListUpdates();
+
+ GameModeCheckBox.IsChecked = GameMode.IsEnabled;
+ GameModeCheckBox.Checked += (s, e) => GameMode.IsEnabled = true;
+ GameModeCheckBox.Unchecked += (s, e) => GameMode.IsEnabled = false;
+
+ _gameModeChangedHandler = (s, e) =>
+ {
+ Dispatcher.InvokeAsync(() =>
+ {
+ if (GameModeCheckBox.IsChecked != GameMode.IsEnabled)
+ {
+ GameModeCheckBox.IsChecked = GameMode.IsEnabled;
+ }
+ });
+ };
+ GameMode.IsEnabledChanged += _gameModeChangedHandler;
+ Application.Current.Exit += (s, ev) => GameMode.IsEnabledChanged -= _gameModeChangedHandler;
}
protected override void OnClosing(CancelEventArgs e)
diff --git a/CPUSetSetter/UI/Tabs/Processes/ProcessListEntryViewModel.cs b/CPUSetSetter/UI/Tabs/Processes/ProcessListEntryViewModel.cs
index 99cda79..10d292a 100644
--- a/CPUSetSetter/UI/Tabs/Processes/ProcessListEntryViewModel.cs
+++ b/CPUSetSetter/UI/Tabs/Processes/ProcessListEntryViewModel.cs
@@ -2,6 +2,7 @@
using CPUSetSetter.Config.Models;
using CPUSetSetter.Util;
using CPUSetSetter.Platforms;
+using CPUSetSetter.Platforms.Windows;
namespace CPUSetSetter.UI.Tabs.Processes
@@ -24,8 +25,23 @@ public partial class ProcessListEntryViewModel : ObservableObject, IDisposable
[ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(DisplayedMask))]
private LogicalProcessorMask _mask;
+ public LogicalProcessorMask DisplayedMask
+ {
+ get => GameMode.IsEnabled ? LogicalProcessorMask.NoMask : (Mask ?? LogicalProcessorMask.NoMask);
+ set
+ {
+ if (!GameMode.IsEnabled && value != null)
+ {
+ Mask = value;
+ }
+ }
+ }
+
+ public bool IsMaskAllowed => !GameMode.IsEnabled;
+
[ObservableProperty]
private bool _previousApplyFailed = false;
@@ -73,11 +89,22 @@ public bool SetMask(LogicalProcessorMask newMask, bool updateRule)
}
ruleSuccess = programRule.SetMask(newMask, true);
}
- bool success = _processHandler.ApplyMask(newMask);
+ bool success = ApplyMaskConsideringGameMode(newMask);
PreviousApplyFailed = !success;
return success && ruleSuccess;
}
+ public void RefreshGameModeApply()
+ {
+ ApplyMaskConsideringGameMode(Mask);
+ }
+
+ public void UpdateVisualMask()
+ {
+ OnPropertyChanged(nameof(DisplayedMask));
+ OnPropertyChanged(nameof(IsMaskAllowed));
+ }
+
///
/// The UI picked a different mask
///
@@ -92,7 +119,18 @@ partial void OnMaskChanged(LogicalProcessorMask? oldValue, LogicalProcessorMask
private void OnMaskEdited(object? sender, EventArgs e)
{
// One of the logical processors in the mask or the type has changed, apply it
- PreviousApplyFailed = !_processHandler.ApplyMask(Mask);
+ bool success = ApplyMaskConsideringGameMode(Mask);
+ PreviousApplyFailed = !success;
+ }
+
+ private bool ApplyMaskConsideringGameMode(LogicalProcessorMask mask)
+ {
+ if (GameMode.IsEnabled)
+ {
+ return _processHandler.ApplyMask(LogicalProcessorMask.NoMask);
+ }
+
+ return _processHandler.ApplyMask(mask);
}
///
diff --git a/CPUSetSetter/UI/Tabs/Processes/ProcessesTab.xaml b/CPUSetSetter/UI/Tabs/Processes/ProcessesTab.xaml
index e722fac..7fde97a 100644
--- a/CPUSetSetter/UI/Tabs/Processes/ProcessesTab.xaml
+++ b/CPUSetSetter/UI/Tabs/Processes/ProcessesTab.xaml
@@ -85,12 +85,13 @@
+ SelectedValue="{Binding DisplayedMask, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
+ IsEnabled="{Binding IsMaskAllowed}">
diff --git a/CPUSetSetter/UI/Tabs/Processes/ProcessesTabViewModel.cs b/CPUSetSetter/UI/Tabs/Processes/ProcessesTabViewModel.cs
index f9d239f..f7754e9 100644
--- a/CPUSetSetter/UI/Tabs/Processes/ProcessesTabViewModel.cs
+++ b/CPUSetSetter/UI/Tabs/Processes/ProcessesTabViewModel.cs
@@ -1,6 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CPUSetSetter.Config.Models;
using CPUSetSetter.Platforms;
+using CPUSetSetter.Platforms.Windows;
using CPUSetSetter.Util;
using System.ComponentModel;
using System.Windows;
@@ -40,6 +41,15 @@ public ProcessesTabViewModel(Dispatcher dispatcher)
runningProcessesView.LiveSortingProperties.Add(nameof(ProcessListEntryViewModel.CpuUsage));
runningProcessesView.Filter = item => ((ProcessListEntryViewModel)item).Name.Contains(ProcessNameFilter, StringComparison.OrdinalIgnoreCase);
+ GameMode.IsEnabledChanged += (s, e) =>
+ {
+ foreach (var process in RunningProcesses)
+ {
+ process.RefreshGameModeApply();
+ process.UpdateVisualMask();
+ }
+ };
+
Task.Run(ProcessCpuUsageUpdateLoop);
}
@@ -48,6 +58,11 @@ public ProcessesTabViewModel(Dispatcher dispatcher)
///
public void OnMaskHotkeyPressed(LogicalProcessorMask mask)
{
+ if (GameMode.IsEnabled)
+ {
+ return;
+ }
+
ProcessListEntryViewModel? foregroundProcess = GetCurrentForegroundProcess();
if (foregroundProcess is not null)
{
@@ -139,6 +154,9 @@ private async Task ProcessCpuUsageUpdateLoop()
while (true)
{
+ // Periodically sync Windows Game Mode registry changes
+ GameMode.CheckState();
+
bool windowIsVisible = false;
await _dispatcher.InvokeAsync(() =>
{
From a7541bd2cd4b39a47e0bbf9d9d1164cae7c33335 Mon Sep 17 00:00:00 2001
From: RegentScar <188844243+RegentScar@users.noreply.github.com>
Date: Sun, 5 Apr 2026 02:31:28 +0200
Subject: [PATCH 4/7] Add Game Mode toggle to system tray menu
---
CPUSetSetter.TrayIcon/AppTrayIcon.cs | 25 +++++++++++++++++++++++++
CPUSetSetter/App.xaml.cs | 8 ++++++++
2 files changed, 33 insertions(+)
diff --git a/CPUSetSetter.TrayIcon/AppTrayIcon.cs b/CPUSetSetter.TrayIcon/AppTrayIcon.cs
index 41ccb2d..ccc5d77 100644
--- a/CPUSetSetter.TrayIcon/AppTrayIcon.cs
+++ b/CPUSetSetter.TrayIcon/AppTrayIcon.cs
@@ -6,14 +6,39 @@ namespace CPUSetSetter.TrayIcon
public class AppTrayIcon : IDisposable
{
private readonly NotifyIcon? _trayIcon;
+ private readonly ToolStripMenuItem _gameModeItem;
+ private bool _isGameModeEnabled;
public event EventHandler? OpenClicked;
public event EventHandler? CloseClicked;
+ public event EventHandler? GameModeToggled;
+
+ public bool IsGameModeEnabled
+ {
+ get => _isGameModeEnabled;
+ set
+ {
+ _isGameModeEnabled = value;
+ _gameModeItem.Text = value ? "Disable Game Mode" : "Enable Game Mode";
+ }
+ }
public AppTrayIcon(Stream iconStream)
{
ContextMenuStrip trayMenu = new();
trayMenu.Items.Add("Open", null, (_, _) => OpenClicked?.Invoke(this, new()));
+ trayMenu.Items.Add(new ToolStripSeparator());
+
+ _gameModeItem = new ToolStripMenuItem();
+ IsGameModeEnabled = false; // This triggers the setter and sets the initial text
+ _gameModeItem.Click += (_, _) =>
+ {
+ IsGameModeEnabled = !_isGameModeEnabled;
+ GameModeToggled?.Invoke(this, _isGameModeEnabled);
+ };
+ trayMenu.Items.Add(_gameModeItem);
+
+ trayMenu.Items.Add(new ToolStripSeparator());
trayMenu.Items.Add("Close", null, (_, _) => CloseClicked?.Invoke(this, new()));
_trayIcon = new()
{
diff --git a/CPUSetSetter/App.xaml.cs b/CPUSetSetter/App.xaml.cs
index f87e916..481afbf 100644
--- a/CPUSetSetter/App.xaml.cs
+++ b/CPUSetSetter/App.xaml.cs
@@ -78,6 +78,14 @@ protected override void OnStartup(StartupEventArgs e)
trayIcon.OpenClicked += (_, _) => ShowMainWindow();
trayIcon.CloseClicked += (_, _) => ExitAppGracefully();
+ // Sync the Game Mode state between the tray icon and the GameMode class
+ trayIcon.IsGameModeEnabled = GameMode.IsEnabled;
+ trayIcon.GameModeToggled += (_, isEnabled) => GameMode.IsEnabled = isEnabled;
+ GameMode.IsEnabledChanged += (_, _) =>
+ {
+ Dispatcher.InvokeAsync(() => trayIcon.IsGameModeEnabled = GameMode.IsEnabled);
+ };
+
if (AppConfig.Instance.IsFirstRun)
{
// Promote the tray icon directly onto the Taskbar instead of in the "up-arrow" menu
From 2ac03d7f9cdc130f8e7a14bc1d830773b3681691 Mon Sep 17 00:00:00 2001
From: RegentScar <188844243+RegentScar@users.noreply.github.com>
Date: Sun, 5 Apr 2026 18:32:11 +0200
Subject: [PATCH 5/7] Improve WindowLogger to collapse repeated log messages
Reduces log clutter when processes have the same name, like Discord or browsers
---
CPUSetSetter/UI/Tabs/Processes/WindowLogger.cs | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/CPUSetSetter/UI/Tabs/Processes/WindowLogger.cs b/CPUSetSetter/UI/Tabs/Processes/WindowLogger.cs
index c818dd3..6c4fd90 100644
--- a/CPUSetSetter/UI/Tabs/Processes/WindowLogger.cs
+++ b/CPUSetSetter/UI/Tabs/Processes/WindowLogger.cs
@@ -8,7 +8,7 @@ public partial class WindowLogger : ObservableObject
[ObservableProperty]
private string _text = "";
- private readonly Queue _logLines = new();
+ private readonly List<(string Message, int Count)> _logLines = new();
private readonly Lock _lock = new();
private bool _isUpdating = false;
@@ -23,7 +23,15 @@ private void WriteImp(string message)
{
using (_lock.EnterScope())
{
- _logLines.Enqueue(message + "\n");
+ if (_logLines.Count > 0 && _logLines[^1].Message == message)
+ {
+ var last = _logLines[^1];
+ _logLines[^1] = (last.Message, last.Count + 1);
+ }
+ else
+ {
+ _logLines.Add((message, 1));
+ }
// Begin updating the logger text in the UI
// A small delay is used before updating, so multiple logs can be rendered in one go
@@ -43,9 +51,9 @@ private async Task UpdateText()
{
while (_logLines.Count > 50)
{
- _logLines.Dequeue();
+ _logLines.RemoveAt(0);
}
- Text = string.Join("", _logLines);
+ Text = string.Join("", _logLines.Select(l => l.Count > 1 ? $"{l.Message} ({l.Count})\n" : $"{l.Message}\n"));
_isUpdating = false;
}
}
From d8d20a9d2c8662677f449d643286c4f85dc6909d Mon Sep 17 00:00:00 2001
From: RegentScar <188844243+RegentScar@users.noreply.github.com>
Date: Tue, 7 Apr 2026 01:08:46 +0200
Subject: [PATCH 6/7] Clarified logic for setting immersive dark mode for title
bar
---
CPUSetSetter/Themes/AppTheme.cs | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/CPUSetSetter/Themes/AppTheme.cs b/CPUSetSetter/Themes/AppTheme.cs
index 12d25ec..dff306f 100644
--- a/CPUSetSetter/Themes/AppTheme.cs
+++ b/CPUSetSetter/Themes/AppTheme.cs
@@ -61,15 +61,12 @@ private static void ApplySystemTheme()
ApplyTheme(Theme.Light);
}
- ///
- /// DwmSetWindowAttribute is used to set the title bar color on Windows 10 2004 and later.
- /// On earlier versions, it will fail and falls back to the older attribute that only works on Windows 10 1903 and later.
- ///
-
[DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
+ // On Windows 10 20H1 (build 19041) and later, attribute 20 is the correct value.
private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;
+ // Fallback for Windows 10 1903/1909 (builds 18362–18363), where attribute 19 was used instead.
private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
private static bool _isDarkTheme;
@@ -127,8 +124,12 @@ private static void Window_Loaded(object sender, RoutedEventArgs e)
private static void ApplyDarkMode(IntPtr hwnd, bool isDarkTheme)
{
int useImmersiveDarkMode = isDarkTheme ? 1 : 0;
+
+ // Try the modern attribute first (Windows 10 20H1+).
if (DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, ref useImmersiveDarkMode, sizeof(int)) != 0)
{
+ // If it failed, fall back to the pre-20H1 attribute (Windows 10 1903/1909).
+ // On versions older than 1903, this will also fail silently with no effect.
DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, ref useImmersiveDarkMode, sizeof(int));
}
}
From 0a08b3c033f69b2faf0730a92c9d6f71185dd54a Mon Sep 17 00:00:00 2001
From: RegentScar <188844243+RegentScar@users.noreply.github.com>
Date: Wed, 8 Apr 2026 15:45:09 +0200
Subject: [PATCH 7/7] Add option to disable mask clearing when Game Mode is
enabled
---
CPUSetSetter/Config/AppConfigFile.cs | 4 ++++
CPUSetSetter/Config/Models/AppConfig.cs | 5 +++++
.../Platforms/Windows/GameModeWarning.cs | 10 +++++----
CPUSetSetter/UI/MainWindow.xaml | 1 +
.../Processes/ProcessListEntryViewModel.cs | 10 +++++----
.../Tabs/Processes/ProcessesTabViewModel.cs | 22 +++++++++++++++++--
.../UI/Tabs/Settings/SettingsTab.xaml | 9 ++++++++
7 files changed, 51 insertions(+), 10 deletions(-)
diff --git a/CPUSetSetter/Config/AppConfigFile.cs b/CPUSetSetter/Config/AppConfigFile.cs
index 0cb1906..44bf84f 100644
--- a/CPUSetSetter/Config/AppConfigFile.cs
+++ b/CPUSetSetter/Config/AppConfigFile.cs
@@ -178,6 +178,7 @@ private static AppConfig JsonToConfig(ConfigJson configJson, bool generateDefaul
configJson.ShowGameModePopup,
configJson.ShowUpdatePopup,
configJson.ClearMasksOnClose,
+ configJson.DisableGameModeMaskClearing,
Enum.Parse(configJson.UiTheme),
generateDefaultMasks,
isFirstRun,
@@ -196,6 +197,7 @@ private class ConfigJson
public bool ShowGameModePopup { get; init; }
public bool ShowUpdatePopup { get; init; }
public bool ClearMasksOnClose { get; init; }
+ public bool DisableGameModeMaskClearing { get; init; }
public string UiTheme { get; init; }
public int ConfigVersion { get; init; } // Can be used in the future to migrate config files
@@ -215,6 +217,7 @@ private ConfigJson()
ShowGameModePopup = true;
ShowUpdatePopup = true;
ClearMasksOnClose = false;
+ DisableGameModeMaskClearing = false;
UiTheme = Theme.System.ToString();
ConfigVersion = 0;
}
@@ -255,6 +258,7 @@ public ConfigJson(AppConfig config)
ShowGameModePopup = config.ShowGameModePopup;
ShowUpdatePopup = config.ShowUpdatePopup;
ClearMasksOnClose = config.ClearMasksOnClose;
+ DisableGameModeMaskClearing = config.DisableGameModeMaskClearing;
UiTheme = config.UiTheme.ToString();
ConfigVersion = AppConfigFile.ConfigVersion;
}
diff --git a/CPUSetSetter/Config/Models/AppConfig.cs b/CPUSetSetter/Config/Models/AppConfig.cs
index 70f9e81..e93c99c 100644
--- a/CPUSetSetter/Config/Models/AppConfig.cs
+++ b/CPUSetSetter/Config/Models/AppConfig.cs
@@ -40,6 +40,9 @@ public partial class AppConfig : ObservableConfigObject
[ObservableProperty]
private bool _clearMasksOnClose;
+ [ObservableProperty]
+ private bool _disableGameModeMaskClearing;
+
[ObservableProperty]
private Theme _uiTheme;
@@ -57,6 +60,7 @@ public AppConfig(List logicalProcessorMasks,
bool showGameModePopup,
bool showUpdatePopup,
bool clearMasksOnClose,
+ bool disableGameModeMaskClearing,
Theme uiTheme,
bool generateDefaultMasks,
bool isFirstRun,
@@ -77,6 +81,7 @@ public AppConfig(List logicalProcessorMasks,
_showGameModePopup = showGameModePopup;
_showUpdatePopup = showUpdatePopup;
_clearMasksOnClose = clearMasksOnClose;
+ _disableGameModeMaskClearing = disableGameModeMaskClearing;
_uiTheme = uiTheme;
IsFirstRun = isFirstRun;
diff --git a/CPUSetSetter/Platforms/Windows/GameModeWarning.cs b/CPUSetSetter/Platforms/Windows/GameModeWarning.cs
index a5da9f0..802a073 100644
--- a/CPUSetSetter/Platforms/Windows/GameModeWarning.cs
+++ b/CPUSetSetter/Platforms/Windows/GameModeWarning.cs
@@ -11,7 +11,7 @@ namespace CPUSetSetter.Platforms.Windows
public static class WindowsGameModeWarning
{
private static bool _eventHooked = false;
- private const string LogWarningMessage = "WARNING: Windows Game Mode is enabled. As long as Game Mode is enabled, no masks are applied.";
+ private const string LogWarningMessage = "WARNING: While Windows Game Mode is enabled, no masks are applied. (This behavior can be disabled in the Settings tab)";
public static void ShowIfEnabled()
{
@@ -20,7 +20,7 @@ public static void ShowIfEnabled()
_eventHooked = true;
GameMode.IsEnabledChanged += (s, e) =>
{
- if (GameMode.IsEnabled)
+ if (GameMode.IsEnabled && !AppConfig.Instance.DisableGameModeMaskClearing)
{
WindowLogger.Write(LogWarningMessage);
}
@@ -50,13 +50,15 @@ public static void ShowIfEnabled()
}
else
{
- WindowLogger.Write(LogWarningMessage);
+ if (!AppConfig.Instance.DisableGameModeMaskClearing)
+ WindowLogger.Write(LogWarningMessage);
}
});
}
else
{
- WindowLogger.Write(LogWarningMessage);
+ if (!AppConfig.Instance.DisableGameModeMaskClearing)
+ WindowLogger.Write(LogWarningMessage);
}
}
}
diff --git a/CPUSetSetter/UI/MainWindow.xaml b/CPUSetSetter/UI/MainWindow.xaml
index 054cf8e..97f1c88 100644
--- a/CPUSetSetter/UI/MainWindow.xaml
+++ b/CPUSetSetter/UI/MainWindow.xaml
@@ -15,6 +15,7 @@
MinWidth="900">
+
GameMode.IsEnabled && !AppConfig.Instance.DisableGameModeMaskClearing;
+
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CpuPercentageStr))]
private double _cpuUsage;
@@ -30,17 +32,17 @@ public partial class ProcessListEntryViewModel : ObservableObject, IDisposable
public LogicalProcessorMask DisplayedMask
{
- get => GameMode.IsEnabled ? LogicalProcessorMask.NoMask : (Mask ?? LogicalProcessorMask.NoMask);
+ get => GameModeForcesNoMask ? LogicalProcessorMask.NoMask : (Mask ?? LogicalProcessorMask.NoMask);
set
{
- if (!GameMode.IsEnabled && value != null)
+ if (!GameModeForcesNoMask && value != null)
{
Mask = value;
}
}
}
- public bool IsMaskAllowed => !GameMode.IsEnabled;
+ public bool IsMaskAllowed => !GameModeForcesNoMask;
[ObservableProperty]
private bool _previousApplyFailed = false;
@@ -125,7 +127,7 @@ private void OnMaskEdited(object? sender, EventArgs e)
private bool ApplyMaskConsideringGameMode(LogicalProcessorMask mask)
{
- if (GameMode.IsEnabled)
+ if (GameModeForcesNoMask)
{
return _processHandler.ApplyMask(LogicalProcessorMask.NoMask);
}
diff --git a/CPUSetSetter/UI/Tabs/Processes/ProcessesTabViewModel.cs b/CPUSetSetter/UI/Tabs/Processes/ProcessesTabViewModel.cs
index f7754e9..0f4e0e9 100644
--- a/CPUSetSetter/UI/Tabs/Processes/ProcessesTabViewModel.cs
+++ b/CPUSetSetter/UI/Tabs/Processes/ProcessesTabViewModel.cs
@@ -41,11 +41,29 @@ public ProcessesTabViewModel(Dispatcher dispatcher)
runningProcessesView.LiveSortingProperties.Add(nameof(ProcessListEntryViewModel.CpuUsage));
runningProcessesView.Filter = item => ((ProcessListEntryViewModel)item).Name.Contains(ProcessNameFilter, StringComparison.OrdinalIgnoreCase);
+ AppConfig.Instance.PropertyChanged += (s, e) =>
+ {
+ if (e.PropertyName == nameof(AppConfig.DisableGameModeMaskClearing) && GameMode.IsEnabled)
+ {
+ foreach (var process in RunningProcesses)
+ {
+ process.RefreshGameModeApply();
+ // UpdateVisualMask is called to ensure UI bindings (like IsMaskAllowed and DisplayedMask) immediately update when the user changes this setting.
+ process.UpdateVisualMask();
+ }
+ }
+ };
+
GameMode.IsEnabledChanged += (s, e) =>
{
+ bool shouldRefresh = !AppConfig.Instance.DisableGameModeMaskClearing;
foreach (var process in RunningProcesses)
{
- process.RefreshGameModeApply();
+ if (shouldRefresh)
+ process.RefreshGameModeApply();
+
+ // UpdateVisualMask must always be called when GameMode toggles to ensure the UI correctly updates the combobox logic,
+ // regardless of whether we actively re-apply the masks to the underlying process.
process.UpdateVisualMask();
}
};
@@ -58,7 +76,7 @@ public ProcessesTabViewModel(Dispatcher dispatcher)
///
public void OnMaskHotkeyPressed(LogicalProcessorMask mask)
{
- if (GameMode.IsEnabled)
+ if (GameMode.IsEnabled && !AppConfig.Instance.DisableGameModeMaskClearing)
{
return;
}
diff --git a/CPUSetSetter/UI/Tabs/Settings/SettingsTab.xaml b/CPUSetSetter/UI/Tabs/Settings/SettingsTab.xaml
index 2701f43..c05be6a 100644
--- a/CPUSetSetter/UI/Tabs/Settings/SettingsTab.xaml
+++ b/CPUSetSetter/UI/Tabs/Settings/SettingsTab.xaml
@@ -61,6 +61,15 @@
+
+
+
+
+
+
+
+