Skip to content

Commit ea350bf

Browse files
committed
Add core mask management and remove Game Boost features
Introduces a new core mask system for managing CPU affinity, including models, services, and Windows platform handlers. Adds a dedicated Core Masks tab and related views/viewmodels. Removes all Game Boost related code, settings, and UI. Updates process association models to support core masks and process priority. Improves graceful shutdown to clear masks and restore power plans. Refines affinity display and default settings for better usability.
1 parent fc71df4 commit ea350bf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3762
-1097
lines changed

.claude/settings.local.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(dotnet build:*)",
5+
"Bash(Select-String -Pattern \"error|Build succeeded|Build FAILED\" -Context 0,2)",
6+
"Bash(ls -la \"c:\\\\Users\\\\Lorenzo\\\\Documents\\\\Projects\\\\CPUSetSetter-main\\\\CPUSetSetter\"\" | grep -i \".xaml \")",
7+
"Bash(cat:*)",
8+
"Bash(grep:*)",
9+
"Bash(dotnet run:*)",
10+
"Bash(powershell -Command \"Start-Process ''C:\\\\Users\\\\Lorenzo\\\\Documents\\\\Projects\\\\ThreadPilot\\\\bin\\\\Release\\\\net8.0-windows\\\\win-x64\\\\ThreadPilot.exe'' -Verb RunAs\")"
11+
]
12+
}
13+
}

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# Escludi tutti gli .exe
22
*.exe
33

4+
# Escludi la sorgente CPUSetSetter upstream
5+
/CPUSetSetter-main/
6+
7+
# Escludi Markdown tranne il README principale
8+
*.md
9+
!README.md
10+
411
# Escludi directory di build comuni
512
/bin/
613
/obj/

App.xaml.cs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
using System.Windows;
2-
using System.Windows.Threading;
1+
using System;
2+
using System.Linq;
3+
using System.Security.Principal;
4+
using System.Threading;
35
using System.Threading.Tasks;
6+
using System.Windows;
7+
using System.Windows.Threading;
48
using Microsoft.Extensions.DependencyInjection;
59
using Microsoft.Extensions.Logging;
610
using ThreadPilot.Services;
711
using ThreadPilot.ViewModels;
8-
using System;
9-
using System.Linq;
10-
using System.Security.Principal;
1112

1213
namespace ThreadPilot
1314
{
1415
public partial class App : System.Windows.Application
1516
{
17+
private Mutex? _singleInstanceMutex;
1618
public IServiceProvider ServiceProvider { get; private set; }
1719

1820
public App()
@@ -32,6 +34,21 @@ public App()
3234

3335
protected override void OnStartup(StartupEventArgs e)
3436
{
37+
// Enforce single-instance: bail out if another instance is already running
38+
bool createdNew;
39+
_singleInstanceMutex = new Mutex(initiallyOwned: true, name: "Global\\ThreadPilot_SingleInstance", createdNew: out createdNew);
40+
if (!createdNew)
41+
{
42+
System.Windows.MessageBox.Show(
43+
"ThreadPilot è già in esecuzione.",
44+
"Istanza già aperta",
45+
MessageBoxButton.OK,
46+
MessageBoxImage.Information);
47+
48+
Shutdown();
49+
return;
50+
}
51+
3552
// Set up global exception handlers first
3653
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
3754
DispatcherUnhandledException += OnDispatcherUnhandledException;
@@ -108,9 +125,14 @@ protected override void OnStartup(StartupEventArgs e)
108125

109126
// Show the window with explicit visibility settings
110127
mainWindow.Visibility = Visibility.Visible;
128+
mainWindow.WindowState = (startMinimized || isAutostart)
129+
? WindowState.Minimized
130+
: WindowState.Normal;
111131
mainWindow.Show();
112-
mainWindow.WindowState = WindowState.Normal;
113-
mainWindow.Activate();
132+
if (mainWindow.WindowState != WindowState.Minimized)
133+
{
134+
mainWindow.Activate();
135+
}
114136

115137
logger.LogInformation("Main window displayed successfully");
116138
}
@@ -129,6 +151,25 @@ protected override void OnStartup(StartupEventArgs e)
129151
}
130152
}
131153

154+
protected override void OnExit(ExitEventArgs e)
155+
{
156+
if (_singleInstanceMutex != null)
157+
{
158+
try
159+
{
160+
_singleInstanceMutex.ReleaseMutex();
161+
}
162+
catch
163+
{
164+
// Ignore; we just want to clean up quietly
165+
}
166+
_singleInstanceMutex.Dispose();
167+
_singleInstanceMutex = null;
168+
}
169+
170+
base.OnExit(e);
171+
}
172+
132173
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
133174
private static extern bool AllocConsole();
134175

Converters/ItemIndexConverter.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Windows;
4+
using System.Windows.Controls;
5+
using System.Windows.Data;
6+
7+
namespace ThreadPilot.Converters
8+
{
9+
/// <summary>
10+
/// Converter to get the index of an item in an ItemsControl
11+
/// </summary>
12+
public class ItemIndexConverter : IValueConverter
13+
{
14+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
15+
{
16+
if (value is DependencyObject item)
17+
{
18+
var itemsControl = ItemsControl.ItemsControlFromItemContainer(item);
19+
if (itemsControl != null)
20+
{
21+
int index = itemsControl.ItemContainerGenerator.IndexFromContainer(item);
22+
return index;
23+
}
24+
}
25+
26+
return -1;
27+
}
28+
29+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
30+
{
31+
throw new NotImplementedException();
32+
}
33+
}
34+
}

Helpers/Converters.cs

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,57 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
2828
{
2929
if (value is long mask)
3030
{
31-
var cores = new System.Text.StringBuilder();
32-
for (int i = 0; i < 16; i++)
31+
var selectedIndices = new System.Collections.Generic.List<int>();
32+
// Dynamic core count based on system, limited to 64 (long is 64-bit)
33+
int maxCores = Math.Min(64, Environment.ProcessorCount);
34+
35+
for (int i = 0; i < maxCores; i++)
3336
{
3437
if ((mask & (1L << i)) != 0)
3538
{
36-
if (cores.Length > 0) cores.Append(", ");
37-
cores.Append(i);
39+
selectedIndices.Add(i);
40+
}
41+
}
42+
43+
if (selectedIndices.Count == 0)
44+
return "None";
45+
46+
// Build the display string
47+
var indicesStr = string.Join(", ", selectedIndices);
48+
int selectedCount = selectedIndices.Count;
49+
50+
// Detect if this is likely physical cores only (every other logical processor)
51+
// This heuristic checks if selected indices are evenly spaced by 2 (e.g., 0,2,4,6,8...)
52+
bool isProbablyPhysicalCoresOnly = false;
53+
if (selectedCount > 1 && selectedCount <= maxCores / 2)
54+
{
55+
isProbablyPhysicalCoresOnly = true;
56+
for (int i = 1; i < selectedIndices.Count; i++)
57+
{
58+
if (selectedIndices[i] - selectedIndices[i - 1] != 2)
59+
{
60+
isProbablyPhysicalCoresOnly = false;
61+
break;
62+
}
3863
}
3964
}
40-
return $"CPU {cores}";
65+
66+
// Choose terminology based on what's selected
67+
string label;
68+
if (selectedCount == maxCores)
69+
{
70+
label = $"All threads (0-{maxCores - 1})";
71+
}
72+
else if (isProbablyPhysicalCoresOnly && selectedIndices[0] == 0)
73+
{
74+
label = $"Physical cores ({indicesStr}) - {selectedCount} cores";
75+
}
76+
else
77+
{
78+
label = $"Threads ({indicesStr}) - {selectedCount} threads";
79+
}
80+
81+
return label;
4182
}
4283
return "Unknown";
4384
}

Installer/Installer.iss

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
; ThreadPilot installer script (Inno Setup)
2+
; Requires Inno Setup to build the installer. Adjust MyAppVersion/MyAppBuildDir as needed.
3+
4+
#define MyAppName "ThreadPilot"
5+
#define MyAppPublisher "ThreadPilot"
6+
#define MyAppURL "https://github.com/"
7+
#define MyAppExeName "ThreadPilot.exe"
8+
#define MyAppVersion "1.0.0"
9+
; Point this to the folder containing the published binaries (e.g. dotnet publish -c Release -r win-x64)
10+
#define MyAppBuildDir "..\\artifacts\\build"
11+
12+
[Setup]
13+
AppId={{A2A4C8B5-4A9A-4B1B-93F4-5F8B1C7E8C2A}
14+
AppName={#MyAppName}
15+
AppVersion={#MyAppVersion}
16+
AppVerName={#MyAppName} {#MyAppVersion}
17+
AppPublisher={#MyAppPublisher}
18+
AppPublisherURL={#MyAppURL}
19+
AppSupportURL={#MyAppURL}
20+
AppUpdatesURL={#MyAppURL}
21+
DefaultDirName={autopf}\{#MyAppName}
22+
UninstallDisplayIcon={app}\{#MyAppExeName}
23+
ArchitecturesAllowed=x64compatible
24+
ArchitecturesInstallIn64BitMode=x64compatible
25+
DefaultGroupName={#MyAppName}
26+
AllowNoIcons=yes
27+
PrivilegesRequiredOverridesAllowed=dialog
28+
OutputBaseFilename=ThreadPilot_Setup
29+
SetupIconFile=..\ico.ico
30+
SolidCompression=yes
31+
WizardStyle=modern
32+
33+
[Languages]
34+
Name: "english"; MessagesFile: "compiler:Default.isl"
35+
36+
[Tasks]
37+
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
38+
39+
[Files]
40+
Source: "{#MyAppBuildDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; BeforeInstall: KillRunningInstance()
41+
42+
[Icons]
43+
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
44+
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
45+
46+
[Run]
47+
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
48+
49+
[UninstallRun]
50+
Filename: taskkill.exe; Parameters: "/IM '{#MyAppExeName}' /F"; Flags: runhidden waituntilterminated; RunOnceId: UninstallKill
51+
52+
[Code]
53+
54+
procedure KillRunningInstance();
55+
var
56+
ResultCode: Integer;
57+
begin
58+
Exec('taskkill.exe', '/IM "{#MyAppExeName}" /F', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
59+
end;

MainWindow.xaml

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@
7070
<TabItem Header="🔧 Process Management">
7171
<views:ProcessView/>
7272
</TabItem>
73+
<TabItem Header="🎯 Core Masks">
74+
<views:MasksView/>
75+
</TabItem>
7376
<TabItem Header="⚡ Power Plans">
7477
<views:PowerPlanView/>
7578
</TabItem>
@@ -79,11 +82,8 @@
7982
<TabItem Header="🔧 Tweaks">
8083
<views:SystemTweaksView x:Name="SystemTweaksView"/>
8184
</TabItem>
82-
<TabItem Header="📋 Activity Logs">
83-
<views:LogViewerView/>
84-
</TabItem>
8585
<TabItem Header="⚙️ Settings">
86-
<views:SettingsView/>
86+
<views:SettingsView x:Name="SettingsView"/>
8787
</TabItem>
8888
</TabControl>
8989
</Grid>
@@ -94,17 +94,6 @@
9494
<TextBlock Text="{Binding StatusMessage}"/>
9595
</StatusBarItem>
9696
<Separator/>
97-
<StatusBarItem>
98-
<StackPanel Orientation="Horizontal">
99-
<TextBlock Text="{Binding GameBoostStatusText}"
100-
FontWeight="{Binding IsGameBoostActive, Converter={StaticResource BoolToFontWeightConverter}}"
101-
Foreground="{Binding IsGameBoostActive, Converter={StaticResource BoolToColorConverter}}"/>
102-
<TextBlock Text="🛡️"
103-
Margin="5,0,0,0"
104-
Visibility="{Binding IsGameBoostActive, Converter={StaticResource BoolToVisibilityConverter}}"/>
105-
</StackPanel>
106-
</StatusBarItem>
107-
<Separator/>
10897
<StatusBarItem>
10998
<StackPanel Orientation="Horizontal">
11099
<TextBlock Text="{Binding ElevationStatusText}"

0 commit comments

Comments
 (0)