Skip to content

Commit 7f5f063

Browse files
Roveyclaude
andcommitted
feat: gao --apply switches a running tray's watchdog back on; --optimize exits 2 when not saved
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent a6668b9 commit 7f5f063

6 files changed

Lines changed: 32 additions & 22 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ That builds two programs in `.\build\Release\`: `GpuAutoOptimizer.exe`, the wind
4444

4545
Start `GpuAutoOptimizer.exe`. The window shows the GPU, live telemetry, the saved tune and what is applied right now; pick a preset and press **Optimize** (it offers to restart as administrator first, because tuning writes clocks and power limits). The run screen shows temperature, power and every probe as it happens; **Abort** restores stock. The results screen compares before and after and can turn on **apply at every logon**.
4646

47-
With apply-at-logon on, Windows starts the app in the tray at every logon. It applies the saved tune and then keeps it applied: every 30 seconds it reads back what the driver reports. If the tune was reset (a driver reset or TDR), it applies it again; if that happens four times within an hour it stops and tells you the tune is probably not stable; if another program such as MSI Afterburner changed the settings, it leaves them alone and tells you; after a driver update it does not apply the old tune and tells you to optimize again. While it keeps a tune applied (or a run is in progress), closing the window keeps it in the tray and **Exit** in the tray menu quits; otherwise closing the window quits. **Revert to stock** in the tray menu, or `gao --reset` from a shell, puts the card at stock and the watchdog leaves it there until you apply the tune again from the window or the tray menu (`gao --apply` applies it but does not switch the watchdog back on). Only one optimize runs at a time, from the window or the command line.
47+
With apply-at-logon on, Windows starts the app in the tray at every logon. It applies the saved tune and then keeps it applied: every 30 seconds it reads back what the driver reports. If the tune was reset (a driver reset or TDR), it applies it again; if that happens four times within an hour it stops and tells you the tune is probably not stable; if another program such as MSI Afterburner changed the settings, it leaves them alone and tells you; after a driver update it does not apply the old tune and tells you to optimize again. While it keeps a tune applied (or a run is in progress), closing the window keeps it in the tray and **Exit** in the tray menu quits; otherwise closing the window quits. **Revert to stock** in the tray menu, or `gao --reset` from a shell, puts the card at stock and the watchdog leaves it there until you apply the tune again (window, tray menu or `gao --apply`). Only one optimize runs at a time, from the window or the command line.
4848

4949
### The command line
5050

@@ -59,7 +59,7 @@ With apply-at-logon on, Windows starts the app in the tray at every logon. It ap
5959
.\build\Release\gao.exe --status
6060
```
6161

62-
`--optimize` runs baseline → power → core → memory → 60 s soak (~10 minutes), leaves the result applied until reboot, and keeps a crash journal so a setting that froze the machine is never tried again. Ctrl+C restores stock. The core and memory edges it finds are each re-checked with a 30 s probe before the safety margin is applied, and the memory search stops at the **bandwidth peak** rather than at the highest offset that does not produce errors: GDDR6X and GDDR6 retry failed transfers, so an excessive memory clock costs speed long before it produces a wrong result. `--bandwidth` prints the current memory bandwidth.
62+
`--optimize` runs baseline → power → core → memory → 60 s soak (~10 minutes), leaves the result applied until reboot (exit code 0; 2 if it was applied but could not be saved), and keeps a crash journal so a setting that froze the machine is never tried again. Ctrl+C restores stock. The core and memory edges it finds are each re-checked with a 30 s probe before the safety margin is applied, and the memory search stops at the **bandwidth peak** rather than at the highest offset that does not produce errors: GDDR6X and GDDR6 retry failed transfers, so an excessive memory clock costs speed long before it produces a wrong result. `--bandwidth` prints the current memory bandwidth.
6363

6464
All state (`gao.json`, the crash journal, `boot.log`) lives in `%ProgramData%\GpuAutoOptimizer`, which users can read but only administrators can write. `--boot on` copies both executables to `%ProgramFiles%\GpuAutoOptimizer\` and registers a logon task (`\GpuAutoOptimizer\BootApply`) that starts that copy of the app in the tray, which re-applies the saved profile and keeps it applied; `--status` says whether the installed copies match your build; if the machine crashes within 2 minutes of three logons in a row, or the NVIDIA driver version changes, boot-apply stops and `--status` / `boot.log` say why. A profile only applies to the card it was tuned on (NVML UUID).
6565

‎docs/hardware-checks.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ root; adjust the path for a Debug build.
5959
| 33 | Watchdog re-applies after a TDR | tray running, `dxcap -forcetdr`, wait 45 s, `gao --status` | yes | `applied:` equals the saved tune again; `boot.log` has `watchdog: ... re-applied`; a balloon said so | |
6060
| 34 | Watchdog backs off from another tool | tray running, set an offset in MSI Afterburner and Apply | no | a balloon says another program changed the settings; the watchdog does not fight it | |
6161
| 35 | One instance | start the app twice | no | the second start brings the first window forward and exits | Pass (2026-09-27, after the P5 fix pass): the second start exits with code 0, the first keeps running. |
62-
| 36 | CLI and tray side by side | tray running with the tune kept applied; `gao --reset`, wait 45 s, `gao --status`; then `gao --optimize` from a shell while the window runs an optimize | yes | after `--reset` the card stays at stock (the tray notes it and does not re-apply); the second optimize refuses with `another optimize is already running` | |
62+
| 36 | CLI and tray side by side | tray running with the tune kept applied; `gao --reset`, wait 45 s, `gao --status`; then `gao --optimize` from a shell while the window runs an optimize | yes | after `--reset` the card stays at stock (the tray notes it and does not re-apply); after `gao --apply` the tray keeps the tune applied again; the second optimize refuses with `another optimize is already running` | |
6363
| 37 | Closing the window | close the window with no tune applied, then again with a tune kept applied | no / yes | without a tune the app exits; with one it hides to the tray | Pass without a tune (2026-09-27): the app exits on close. |
6464

6565
## Notes

‎src/app/common.cpp‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ bool tuning_in_progress() {
4343
return r == WAIT_TIMEOUT;
4444
}
4545

46-
unsigned stock_by_choice_message() {
47-
static const UINT msg = RegisterWindowMessageW(L"GpuAutoOptimizer.StockByChoice");
46+
unsigned tray_notice_message() {
47+
static const UINT msg = RegisterWindowMessageW(L"GpuAutoOptimizer.TrayNotice");
4848
return msg;
4949
}
5050

51-
void tell_tray_stock_by_choice() {
52-
if (const HWND tray = FindWindowW(kTrayWindowClass, nullptr)) PostMessageW(tray, stock_by_choice_message(), 0, 0);
51+
void tell_tray(TrayNotice notice) {
52+
if (const HWND tray = FindWindowW(kTrayWindowClass, nullptr))
53+
PostMessageW(tray, tray_notice_message(), static_cast<WPARAM>(notice), 0);
5354
}
5455

5556
bool is_elevated() {

‎src/app/common.hpp‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ class TuningLock {
4343
// True while another thread or process holds the TuningLock.
4444
bool tuning_in_progress();
4545

46-
// Tells a running tray app that the card was put back to stock on purpose
47-
// (gao --reset), so its watchdog must not re-apply the tune.
48-
void tell_tray_stock_by_choice();
49-
// The registered window message tell_tray_stock_by_choice() posts.
50-
unsigned stock_by_choice_message();
46+
// What the command line tells a running tray app, so its watchdog follows:
47+
// stock on purpose (gao --reset) stops it; the tune applied (gao --apply)
48+
// starts it again.
49+
enum class TrayNotice : unsigned { StockByChoice = 0, TuneApplied = 1 };
50+
void tell_tray(TrayNotice notice);
51+
// The registered window message tell_tray() posts; the notice is its wParam.
52+
unsigned tray_notice_message();
5153

5254
struct OptimizeHooks {
5355
std::function<bool()> aborted; // polled between probes

‎src/app/gui/main_gui.cpp‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ struct App {
5555
HWND hwnd = nullptr;
5656
HANDLE instance_mutex = nullptr;
5757
UINT taskbar_created = 0;
58-
UINT stock_by_choice = 0;
58+
UINT tray_notice = 0;
5959
bool visible = false;
6060
bool exit_requested = false;
6161
int input_frames = 0; // frames still to draw after input, so hover/click feedback shows
@@ -430,9 +430,15 @@ LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
430430
tray_icon(NIM_ADD);
431431
return 0;
432432
}
433-
if (msg == g.stock_by_choice && g.stock_by_choice) { // gao --reset in a shell
434-
g.watch = false;
435-
g.ui.watchdog_note = gao::app::now_text() + " Set to stock from the command line; not re-applied until you apply it again.";
433+
if (msg == g.tray_notice && g.tray_notice) { // gao --reset / gao --apply in a shell
434+
if (wp == static_cast<WPARAM>(gao::app::TrayNotice::TuneApplied)) {
435+
g.watch = true;
436+
g.watchdog = gao::Watchdog();
437+
g.ui.watchdog_note = gao::app::now_text() + " Applied from the command line; kept applied from now on.";
438+
} else {
439+
g.watch = false;
440+
g.ui.watchdog_note = gao::app::now_text() + " Set to stock from the command line; not re-applied until you apply it again.";
441+
}
436442
refresh_status(false);
437443
return 0;
438444
}
@@ -561,11 +567,11 @@ int WINAPI wWinMain(HINSTANCE inst, HINSTANCE, PWSTR cmdline, int) {
561567
DwmSetWindowAttribute(g.hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark, sizeof(dark));
562568

563569
// Explorer runs unelevated; let its tray notifications and a relaunch's
564-
// "come forward" reach an elevated window. Not the stock-by-choice notice:
565-
// gao --reset is always elevated, and an unelevated process must not be
566-
// able to switch the watchdog off.
570+
// "come forward" reach an elevated window. Not the command-line notices:
571+
// gao --reset and --apply are always elevated, and an unelevated process
572+
// must not be able to switch the watchdog on or off.
567573
g.taskbar_created = RegisterWindowMessageW(L"TaskbarCreated");
568-
g.stock_by_choice = gao::app::stock_by_choice_message();
574+
g.tray_notice = gao::app::tray_notice_message();
569575
for (UINT m : {WM_APP_TRAY, g.taskbar_created, WM_APP_SHOW})
570576
ChangeWindowMessageFilterEx(g.hwnd, m, MSGFLT_ALLOW, nullptr);
571577

‎src/app/main.cpp‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ static int set_offset(const char* label, int mhz, bool core) {
9797
static int reset() {
9898
// Stock by choice: tell a running tray app first, or a watchdog tick
9999
// between the writes and the notice would take it for a TDR and re-apply.
100-
gao::app::tell_tray_stock_by_choice();
100+
gao::app::tell_tray(gao::app::TrayNotice::StockByChoice);
101101
gao::Nvapi nvapi;
102102
if (!nvapi.Init()) { std::printf("NVAPI init failed: %s\n", nvapi.Error().c_str()); return 1; }
103103
bool ok = nvapi.ResetOffsets(kGpu);
@@ -239,7 +239,7 @@ static int optimize(gao::Preset preset) {
239239
if (out.saved) std::printf("Saved: `gao --apply` re-applies it, `gao --boot on` applies it at every logon.\n");
240240
else std::printf("not saved: %s\n", out.save_note.c_str());
241241
std::printf("Applied until reboot. `gao --reset` returns to stock.\n");
242-
return 0;
242+
return out.saved ? 0 : 2; // 2: tuned and applied, but --apply and boot-apply cannot use it
243243
}
244244

245245
static int apply() {
@@ -260,6 +260,7 @@ static int apply() {
260260
}
261261
const gao::GpuControl gpu = gao::make_gpu_control(nvml, nvapi, kGpu);
262262
if (!gao::apply_profile(gpu, *cfg.profile, &why)) { std::printf("not applied: %s\n", why.c_str()); return 1; }
263+
gao::app::tell_tray(gao::app::TrayNotice::TuneApplied); // a running tray keeps it applied from now on
263264
std::printf("applied %s\nOK\n", gao::app::profile_text(*cfg.profile).c_str());
264265
return 0;
265266
}

0 commit comments

Comments
 (0)