-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
212 lines (181 loc) · 7.37 KB
/
Copy pathProgram.cs
File metadata and controls
212 lines (181 loc) · 7.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace OsuCursorOverlay;
internal static class Program
{
[STAThread]
static void Main(string[] args)
{
// Watchdog mode: launched by the main instance to monitor for forced termination.
// Usage: OsuCursorOverlay.exe --watchdog <parentPid> <overlayHwnd>
if (args.Length >= 2 && args[0] == "--watchdog")
{
if (int.TryParse(args[1], out int parentPid))
{
IntPtr hwnd = IntPtr.Zero;
if (args.Length >= 3 && long.TryParse(args[2], out long hwndValue))
hwnd = new IntPtr(hwndValue);
RunWatchdog(parentPid, hwnd);
}
return;
}
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Find skins directory
var skinsDir = SkinDiscovery.FindSkinsDirectory();
if (skinsDir == null)
{
MessageBox.Show(
"ERROR: No se encontró la carpeta de skins de osu!\n\nAsegúrate de que osu! esté instalado en tu sistema.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
return;
}
// Load config
var configPath = Path.Combine(AppContext.BaseDirectory, "config.ini");
var settings = ConfigManager.Load(configPath);
// List available skins
var skins = SkinDiscovery.ListSkins(skinsDir);
if (skins.Count == 0)
{
MessageBox.Show(
"ERROR: No hay skins disponibles con cursor.png\n\nVerifica: " + skinsDir,
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
return;
}
string selectedSkinName = "";
string selectedSkinPath = "";
bool forceSelector = args.Contains("--select");
// Try to use last saved skin — skip selector if valid and not forced
if (!forceSelector && !string.IsNullOrEmpty(settings.LastSkinName))
{
var lastSkinPath = Path.Combine(skinsDir, settings.LastSkinName);
if (Directory.Exists(lastSkinPath) && File.Exists(Path.Combine(lastSkinPath, "cursor.png")))
{
selectedSkinName = settings.LastSkinName;
selectedSkinPath = lastSkinPath;
}
}
// Show selector if no valid last skin or forced
if (string.IsNullOrEmpty(selectedSkinName))
{
using var selectorForm = new SkinSelectorForm(skinsDir, skins, settings, configPath);
if (selectorForm.ShowDialog() != DialogResult.OK)
return;
selectedSkinName = selectorForm.SelectedSkinName ?? "";
selectedSkinPath = selectorForm.SelectedSkinPath ?? "";
settings = selectorForm.FinalSettings;
if (string.IsNullOrEmpty(selectedSkinName) || string.IsNullOrEmpty(selectedSkinPath))
return;
}
// NOTE: Watchdog is spawned from OverlayForm.OnLoad where we have the HWND.
// Load skin files and assets
var skinFiles = SkinDiscovery.GetSkinFiles(selectedSkinPath);
var assets = SkinAssets.Load(skinFiles, settings.Scale);
// Run overlay
using var overlayForm = new OverlayForm(settings, assets, selectedSkinName);
Application.Run(overlayForm);
}
/// <summary>
/// Launches a hidden copy of this exe as a cursor-restore watchdog.
/// Passes PID + HWND so the watchdog can detect hangs AND forced kills.
/// </summary>
public static void SpawnWatchdog(IntPtr overlayHwnd)
{
try
{
var exePath = Process.GetCurrentProcess().MainModule?.FileName;
if (exePath == null) return;
var pid = Environment.ProcessId.ToString();
var hwnd = overlayHwnd.ToInt64().ToString();
var psi = new ProcessStartInfo(exePath, $"--watchdog {pid} {hwnd}")
{
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};
Process.Start(psi);
}
catch
{
// Non-fatal: graceful-exit path still restores cursors normally.
}
}
/// <summary>
/// Watchdog mode: polls every 3 s.
/// • If parent has terminated → restore cursor.
/// • If parent is hung (IsHungAppWindow returns true) → force-kill it, restore cursor.
/// </summary>
private static void RunWatchdog(int parentPid, IntPtr overlayHwnd)
{
const uint SYNCHRONIZE = 0x00100000;
const uint PROCESS_TERMINATE = 0x0001;
const uint WAIT_OBJECT_0 = 0x00000000;
const int POLL_MS = 3000; // check every 3 s
const int HUNG_CHECKS = 2; // kill after 2 consecutive hung checks (~6 s)
IntPtr hProcess = IntPtr.Zero;
try
{
hProcess = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, false, (uint)parentPid);
if (hProcess == IntPtr.Zero)
{
// Parent already dead
RestoreCursors();
return;
}
int hungCount = 0;
while (true)
{
uint result = WaitForSingleObject(hProcess, (uint)POLL_MS);
if (result == WAIT_OBJECT_0)
{
// Parent exited normally or was killed
break;
}
// result == WAIT_TIMEOUT → still alive; check if hung
if (overlayHwnd != IntPtr.Zero && IsHungAppWindow(overlayHwnd))
{
hungCount++;
if (hungCount >= HUNG_CHECKS)
{
// App is stuck: force-kill it then restore cursor
TerminateProcess(hProcess, 1);
break;
}
}
else
{
hungCount = 0; // app is responsive, reset counter
}
}
}
catch { /* ignore */ }
finally
{
if (hProcess != IntPtr.Zero)
CloseHandle(hProcess);
}
RestoreCursors();
}
private static void RestoreCursors()
{
// SPI_SETCURSORS = 0x0057 — reloads system cursors from the registry
SystemParametersInfoW(0x0057, 0, IntPtr.Zero, 0);
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
[DllImport("user32.dll")]
private static extern bool IsHungAppWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SystemParametersInfoW(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);
}