From 79c11b8a3cf4ac84c3ced8bc0e5d07bd9952ccaf Mon Sep 17 00:00:00 2001 From: Geoffrey Vancoetsem <10533139+geeooff@users.noreply.github.com> Date: Fri, 18 Sep 2026 02:47:07 +0200 Subject: [PATCH] Bring the documentation up to the first release, and measure the update path A pass over every page and the comments the last two days touched. Lot 8 is closed in the index and its record, with the second machine's install as the one field check still outstanding; the record's claim that the summary bit alone makes a package per-user is corrected in place with the real package's measurement. The recipes told an installer user to run install-task after copying a configuration, which keeps a running watcher on the old file: they say stop then install-task now, as getting-started does. AGENTS.md says how a change is verified on a machine the installer owns. Comments in task.rs, the twin and msi.ps1 no longer describe the console binary as the only one that runs the setup commands. Lot 13's page records what v0.1.0 actually answers -- the redirect that carries the tag, the checksum file, the asset chain, the API's digest -- and what the package now does for the updater: it stops and restarts the watcher itself, so the earlier quit-then-relaunch plan is superseded, and the updater keeps only the failure path. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 13 +- docs/design/08-distribution.md | 58 +++--- docs/design/13-updating.md | 184 ++++++++++++++---- docs/design/README.md | 4 +- docs/getting-started.md | 8 +- docs/how-it-works.md | 2 +- .../recipes/fancontrol-fan-profiles/README.md | 4 +- docs/recipes/windows-power-plan/README.md | 5 +- scripts/msi.ps1 | 14 +- src/bin/gamemode-executorw.rs | 11 +- src/task.rs | 14 +- 11 files changed, 223 insertions(+), 94 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 101bd0c..2e40209 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -118,11 +118,14 @@ user's commands. It is the whole chain, end to end, with no game: cargo test -- --ignored a_real_activation_drives_a_session ``` -Verifying a change means running it as the user does: deploy the two -executables to the install folder, restart the logon task -(`schtasks /Run /TN "GameModeExecutor\Watcher"`), and read the log at `debug` -through a real game session. Restarting the watcher while a game is running -fires the stop commands; do not. +Verifying a change means running it as the user does: `gamemode-executor +stop`, copy the two release executables over the installed ones, +`gamemode-executor install-task`, and read the log at `debug` through a real +game session. A change to the package itself is verified by uninstalling +from *Programs and Features* and installing the new `dist\` package — the +product code is fixed per version, so the same version cannot install over +itself. Restarting the watcher while a game is running fires the stop +commands; do not. ## Pitfalls that have already cost time diff --git a/docs/design/08-distribution.md b/docs/design/08-distribution.md index eef72e3..a63ab92 100644 --- a/docs/design/08-distribution.md +++ b/docs/design/08-distribution.md @@ -1,11 +1,12 @@ # Lot 8 — Distribution -**Status: in progress since 2026-09-17.** Already there: `scripts/build.ps1` -runs the whole checklist and produces the zip archive in `dist/`, -`.vscode/tasks.json` drives it, and the repository is public with CI green -on a stock runner. The script was written first on purpose — a release that -cannot be made by hand is not one CI can make either. What is left, in the -order it is taken: +**Status: done 2026-09-18.** `v0.1.0` was published that night by the +workflow alone, from the tag's commit, with the installer, the zip and their +checksums — which is the "done when" below. The lot was taken 2026-09-17/18 +in the order listed; `scripts/build.ps1` was written first on purpose, since +a release that cannot be made by hand is not one CI can make either. One +field check is still outstanding and is recorded when it happens: the first +install on a second machine. - [x] Create the public GitHub repository and push — done 2026-09-17, with approval - [x] The three measurements below, on a minimal package, before any table is written — done 2026-09-17 @@ -16,9 +17,9 @@ order it is taken: the checklist, build the **MSI** and the **zip archive**, and publish a GitHub Release carrying both with their SHA-256 — nothing built or uploaded by hand. Written 2026-09-17 (`release.yml`, `scripts/release-notes.ps1`); - its first run is the first tag + first run 2026-09-18 on the `v0.1.0` tag, green, three assets published - [x] The documentation: *Getting started* and the README point at the release rather than at `cargo build`, the reference gains `purge`, *How it works* gains removal — 2026-09-17 -- [ ] Verified in the field: the MSI on two machines, one real upgrade, one purge round trip — the maintainer's machine done 2026-09-17, below +- [ ] Verified in the field: the MSI on two machines, one real upgrade, one purge round trip — the maintainer's machine done 2026-09-17/18, below: install, upgrade, uninstall, purge; the second machine's first install still to come **Done when** a tag alone produces a release a stranger can install from, and the two artefacts on it were built by the workflow from that tag's commit. @@ -366,23 +367,34 @@ unelevated shell, `/passive`, with a verbose log each time: | Upgrade to 0.2.0 | exit 0 in 6 s, no prompt; file replaced, the old product gone, one product left at 0.2.0 | `Nested installation UAC elevation tracks that of parent (is not elevated)` — `RemoveExistingProducts` at 1510 removed 0.1.0 first | | Uninstall | exit 0 in 6 s, no prompt; folder gone, registration gone; the neighbouring folders, the watcher's install and its scheduled tasks untouched | `Removal completed successfully` | -Two things the documentation had not made plain. **The summary stream's -"elevated privileges not required" bit (WordCount bit 3) is the whole -mechanism**: with it set, Windows Installer treats the package as per-user -outright, redirects `ProgramFilesFolder` to `%LOCALAPPDATA%\Programs`, and -logs `MSIINSTALLPERUSER property is not valid for UAC compliant package. -Ignoring` — so `ALLUSERS=2` and `MSIINSTALLPERUSER=1`, the dual-purpose -recipe, are not needed for a program with no per-machine story, and the -package is simpler without them. And **no SDK tool is needed to build the -database**: the COM automation creates tables, inserts rows and embeds the -cabinet, which means the release script can produce the MSI on a stock -runner the same way it produces the zip. `MsiDb`, `MsiFiler` and `Orca` -remain what they are, tools to inspect one. What the probe did not do and -the real package must: carry versioned files with `VERSIONINFO`, and pass -ICE validation (`MsiVal2`, from the SDK). +Two things the documentation had not made plain. The first this page got +wrong on the probe and corrected on the real package the same day, so both +readings stay here. **The probe's reading:** the summary stream's "elevated +privileges not required" bit (WordCount bit 3) looked like the whole +mechanism — with it set, Windows Installer ran without a prompt, put the +file under `%LOCALAPPDATA%\Programs`, and logged `MSIINSTALLPERUSER property +is not valid for UAC compliant package. Ignoring`, which read as "the +dual-purpose properties are not needed". **The real package's reading, +measured 2026-09-17 on an administrator account running unelevated:** it +takes all three. The bit lets the install run without a prompt; `ALLUSERS=2` +with `MSIINSTALLPERUSER=1` is what resolves the install to per-user and +redirects `ProgramFilesFolder`. With the bit alone the folder stayed at +`C:\Program Files (x86)`; with `ALLUSERS=2` alone the install turned +per-machine for an administrator and failed unelevated, 1603. The log's +"Ignoring" line is misleading: the property still decides how `ALLUSERS=2` +resolves. The probe had passed because its one file was small enough to be +looked for in the wrong folder without anyone noticing. And **no SDK tool is +needed to build the database**: the COM automation creates tables, inserts +rows and embeds the cabinet, which means the release script can produce the +MSI on a stock runner the same way it produces the zip — it did, on +2026-09-18. `MsiDb`, `MsiFiler` and `Orca` remain what they are, tools to +inspect one. What the probe did not do and the real package does: carry +versioned files with `VERSIONINFO`, and pass ICE validation (`MsiVal2`, from +the SDK, 102 evaluators, none tolerated). The choice is closed: **MSI, authored from PowerShell through Windows -Installer's automation, per-user by the summary bit.** +Installer's automation, per-user by the summary bit and the two +properties together.** Both candidates can offer per-user *or* per-machine from one installer, but **this program has no per-machine story**: the logon task, the configuration diff --git a/docs/design/13-updating.md b/docs/design/13-updating.md index e8242a9..28e0d94 100644 --- a/docs/design/13-updating.md +++ b/docs/design/13-updating.md @@ -1,10 +1,12 @@ # Lot 13 — Updating -**Status: proposed.** Decided 2026-09-17 to be a lot of its own rather than -a tail of [Lot 8](08-distribution.md): updating touches the "no network" -non-goal, the tray menu and the running process, and each of those deserves -its own measurement. Nothing here is built. It needs Lot 8 first — there is -nothing to update to until a release exists. +**Status: proposed, measured against a real release on 2026-09-18.** Decided +2026-09-17 to be a lot of its own rather than a tail of +[Lot 8](08-distribution.md): updating touches the "no network" non-goal, the +tray menu and the running process, and each of those deserves its own +measurement. Nothing here is built. Lot 8 is done and `v0.1.0` exists, so +there is now something to update from, and what a release actually answers +is recorded below rather than assumed. **Goal.** A user who wants the newer version gets it from the notification icon, without a browser, without an administrator prompt, and without the @@ -15,50 +17,148 @@ says what it found, installs it on request while no game is running, and the watcher comes back on the new version — verified in the field across a real release pair. -## What Lot 8 already settled +## What Lot 8 settled, and what it then did for this lot **The installer is the updater.** An updater that swaps files under an installer is the wrong shape, for reasons the distribution page keeps in its table of Windows Installer's four moments. So the updater downloads the -new package, verifies it, and runs it silently: `msiexec /i new.msi -/passive`, per-user, no UAC. The **zip** is the opposite case — nothing else -owns the files, so there the updater replaces them itself. -`MsiEnumRelatedProducts` on the package's UpgradeCode tells the two -installations apart. +new package, verifies it, and runs it silently: `msiexec /i new.msi`, +per-user, no UAC. `MsiEnumRelatedProducts` on the package's UpgradeCode — +already in `purge` — tells an installed copy from an unpacked one. -## What is decided, ahead of building it +**The package now stops and restarts the watcher itself.** This page first +proposed that the watcher quit before launching the installer and hand its +own relaunch to a detached shell, because Windows Installer's Restart +Manager would otherwise put up a files-in-use dialog. Lot 8 met that dialog +on its first uninstall and answered it in the package: an immediate action +runs `stop` before `InstallValidate`, and `install-task` at the end starts +the watcher through its task. Measured on a real upgrade on 2026-09-18: +700 ms from *Stopped* to *starting*, no dialog, one product listed. So the +updater has less to do than planned — start the installer detached and let +the package close the process that started it; the new version comes back +by the package's own doing. What the updater still owns is the failure +path: if the install fails after the watcher was stopped, nothing restarts +it until the next logon, so something must wait for `msiexec` and run the +task again when it exits non-zero. The same idiom as `purge`'s after-exit +shell: hidden Windows PowerShell, `Wait-Process`, then `schtasks /Run`. + +## What the release answers, measured 2026-09-18 against `v0.1.0` + +| Request | Answer | +| --- | --- | +| `HEAD github.com/{owner}/{repo}/releases/latest` | `302`, `Location: …/releases/tag/v0.1.0` — the tag, and nothing to parse but a URL | +| `HEAD …/releases/latest/download/SHA256SUMS.txt` | `302` to `…/releases/download/v0.1.0/SHA256SUMS.txt`, then the file: one line per asset, ` ` | +| `…/releases/download/v0.1.0/GameModeExecutor-0.1.0.msi` | `302` to a signed `release-assets.githubusercontent.com` URL valid for about an hour, then `200`, `Content-Length: 1462272`, `application/octet-stream` | +| `GET api.github.com/repos/{owner}/{repo}/releases/latest` | JSON: `tag_name`, `name`, `draft`, `prerelease`, `published_at`, `body`, and per asset `name`, `size`, `browser_download_url` and `digest: sha256:…`; `X-RateLimit-Limit: 60` per IP unauthenticated, `Cache-Control: max-age=60`, an `ETag` | + +Two witnesses to the same hash: the workflow's `SHA256SUMS.txt`, computed +on the runner from the files it built, and GitHub's own `digest` on each +asset, computed on upload. For the `.msi` they agreed, +`85d6178b…d0d5d1`. Neither defends against a compromised account, only +against a corrupted or truncated download, and the record says so. + +**What the program already knows without connecting:** its own version, +`build_info::VERSION`; whether Windows Installer owns it and under which +product code, `purge::installed_product()`; the installed product's +version, `MsiGetProductInfoW` with `VersionString` — which is the number +*Programs and Features* shows and the one an upgrade must beat; whether a +game is running; whether the logon task exists. + +## The shape, decided ahead of building it - **Never a silent poll.** An automatic release check breaks the "no - network" non-goal. The compatible shape is a *Check for updates…* entry - that connects only when clicked, or an explicit opt-in in the - configuration; nothing else ever opens a connection. -- **Over WinHTTP**, a Microsoft library using the system certificate store. - No HTTP crate. -- **The download verified against a SHA-256 published with the release.** - That guards against corruption, not against a compromised account, and - the record says so. -- **GitHub provides for the check with no key.** Either the REST API — - `GET /repos/{owner}/{repo}/releases/latest`, 60 requests an hour per IP - unauthenticated, `User-Agent` mandatory — or no API at all: - `github.com/{owner}/{repo}/releases/latest` answers 302 with the tag in - `Location`, and `…/releases/latest/download/{asset}` serves the latest - asset through a redirect to `objects.githubusercontent.com`. The second - suffices: a `HEAD`, a `Location`, a tag compared with - `build_info::VERSION`, nothing to parse. A token would matter only for a - private repository, and embedding one in a public executable would be a - fault. -- **The running executable.** The watcher holds its own `.exe`; Windows - Installer's Restart Manager would show a files-in-use dialog even under - `/passive`. So: refuse to update while a game is on — the same rule as the - purge — then launch the installer *and quit*, with the relaunch handed to - a detached shell (`msiexec … & schtasks /Run Watcher`). No custom action. + network" non-goal. *Check for updates…* connects when clicked and at no + other time. An opt-in check at start is not offered in this lot; if it + ever is, it is a configuration key that defaults to off, at most once a + day, and the record says what it sends. +- **The check is one request and no API.** `HEAD …/releases/latest` with + redirects disabled, the tag read from `Location`, `x.y.z` parsed from it + and compared with the running version as three numbers. A tag that does + not parse as exactly `vX.Y.Z` is "a release this version does not + understand", shown as such, never guessed at. No rate limit to think + about, no JSON, no `User-Agent` contract. The API stays in reserve for + the release notes, should the menu ever show them. +- **Every later request names the tag, not `latest`.** The checksum file + and the package are fetched from `…/releases/download//…`, so a + release published between the check and the download cannot mix one + version's hash with another's file. +- **Verified against `SHA256SUMS.txt`**, the line for the package's exact + file name, with the hash computed through BCrypt — a Microsoft library, + no crate. A mismatch deletes the file and says so; nothing is ever run + unverified. +- **Over WinHTTP**, a Microsoft library using the system certificate store + and the system proxy (`WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY`). No HTTP + crate, no relaxed certificate flag. It follows `https` → `https` + redirects by default, which the asset chain needs, and can be told not + to for the one request whose redirect *is* the answer. +- **Downloaded to `%LOCALAPPDATA%\GameModeExecutor\updates\`**, local and + disposable like the log; the watcher empties that folder when it starts, + so a package is kept only until the version it carries is running. + Windows Installer caches its own copy of every package it installs, so + deleting the download costs a later repair nothing. +- **Installed with `msiexec /i /qn /l*v \install.log`**, + started detached — not a child that shares the watcher's fate — and the + package's own actions stop this process and start the new one. `/qn` + rather than `/passive`, provisionally: `/passive` shows Windows + Installer's progress window, and the program's rule is no windows; the + icon going and coming back is the visible part, as it is for the + installer run by hand, and the version in the tooltip afterwards is the + confirmation. To be measured on screen before it is settled. +- **Refused while a game is running**, for the purge's reason: the stop + commands would fire mid-game and the new watcher would fire the start + commands seconds later. The menu says so; the user quits the game and + clicks again. +- **The zip copy is told, not updated.** Nothing owns its files but the + user, and replacing two executables under a running logon task from a + hidden shell is exactly the file-swapping shape this lot exists to avoid. + A hand-installed copy gets the same check, and the menu entry then + opens the release page. The person who chose no installer keeps their + files in their hands. +- **The menu is the whole interface**, as everywhere else: the entry reads + *Check for updates…*, then *Up to date (0.1.0)* greyed, or *Update to + 0.2.0…*, or *Could not check: offline* greyed; the tooltip mirrors it. + No balloon, no dialog. The log carries the same lines under `setup`, + with the URL, the size and the hash, so an update is as readable + afterwards as an install. +- **A downgrade is never offered.** The package refuses one anyway + (`NEWERVERSIONDETECTED`), and the comparison makes it unreachable. + +## What it does not defend against, said plainly + +- **A compromised release or account.** The hash proves the file is the one + the workflow published, not that the workflow was honest. Authenticode + was declined in Lot 8 for want of a certificate anyone else's Windows + trusts; that decision, not this lot, is where the line moves if it ever + does. +- **SmartScreen does not see it.** A file fetched through WinHTTP carries + no Mark of the Web — browsers and Explorer write it, libraries do not — + so `msiexec` runs the package without the warning a person downloading + the same file would meet. Convenient, and worth knowing: the program is + the one vouching for the file, through the hash and nothing else. +- **A stale mirror or a captive portal.** A `302` to somewhere that is not + GitHub, or a `200` that is an HTML page, must fail the parse and be + shown as "could not check", never as "up to date". ## To measure, when the lot is taken -1. The redirect chain of `releases/latest` and `releases/latest/download` - through WinHTTP, and what a rate-limited or offline answer looks like - from the menu. -2. A `/passive` upgrade launched by the watcher itself, with the watcher - gone by the time Windows Installer looks for files in use. -3. The zip path: replacing two executables under a running logon task, and - what happens when the task fires in the middle of it. +1. WinHTTP against the four requests above: reading `Location` with + redirects disabled, following the asset chain to the signed URL with + them enabled, a proxy, an offline machine and a DNS failure, each as + seen from the menu and the log. +2. The watcher launching its own upgrade: `msiexec /qn` detached, + `StopWatcher` closing the process that started it, `RegisterTask` + bringing the new version back — and the failure path, with a package + built to fail after `InstallValidate`, restarting the old one. +3. `/qn` against `/passive`, on screen, success and failure. +4. The updates folder emptied at start while Windows Installer's cache + still serves a repair. +5. The zip path: the check, the notice, the page opening, and nothing else + happening. + +## Size + +A module of a few hundred lines — the requests, the hash, the version +comparison, the launch — with the comparison and the `SHA256SUMS.txt` parse +under unit tests, one menu entry and one tooltip state in the tray, and a +`setup` line for each step. Verifying it needs a real release pair: it is +built against `v0.1.0` and proved by installing whatever `v0.1.1` becomes. diff --git a/docs/design/README.md b/docs/design/README.md index 406a3f5..83d6a51 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -19,7 +19,7 @@ session rather than when the code compiles. Each has its own page. | 5 | [A Windows program with no window](05-windowless-watcher.md) | done | | 6 | [Notification area icon](06-notification-icon.md) | done | | 7 | [Icon, tooltip and menu as one state](07-tray-state.md) | done | -| 8 | [Distribution](08-distribution.md) | in progress | +| 8 | [Distribution](08-distribution.md) | done | | 9 | [Robustness](09-robustness.md) | partly done | | 10 | [Configuration window](10-configuration-window.md) | proposed | | 11 | [Documentation for the people who use it](11-user-documentation.md) | done | @@ -55,7 +55,7 @@ Recorded so they stop coming back. - **No allow-list of game executables, and no heuristics that guess at what a game is.** Detection is Windows' verdict, read from Windows. - **No telemetry, no network access.** A future update check must be manual or - opt-in, or this stops being true — see [Lot 8](08-distribution.md). + opt-in, or this stops being true — see [Lot 13](13-updating.md). - **No elevation.** The watcher runs as the user, on purpose. Programs that need administrator rights are reached through a scheduled task, never by elevating the watcher — see [Lot 1](01-console-watcher.md). diff --git a/docs/getting-started.md b/docs/getting-started.md index 2d835c4..bfe66f8 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -170,8 +170,8 @@ The log keeps the history of every session. It lives in It starts earlier than the first session: the lines marked `setup` say when the configuration was written and the logon task registered, whether you -typed the command or the installer did it. `init`, `install-task` and -`uninstall-task` print those same lines as they run. +typed the command or the installer did it. `init`, `install-task`, +`uninstall-task` and `stop` print those same lines as they run. ## Using the game's name in your command @@ -260,7 +260,9 @@ gamemode-executor uninstall-task ``` The first stops the one running now; the second removes the logon task, so -nothing starts at the next logon. +nothing starts at the next logon. To take the program off the machine, see +[Removing it](how-it-works.md#removing-it): *Programs and Features* for the +installer's copy, or `purge` for every trace whichever way it came. ## Everything else diff --git a/docs/how-it-works.md b/docs/how-it-works.md index 062b3f0..c78fadc 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -224,7 +224,7 @@ added. | What | Where | | --- | --- | -| The program | wherever you put it. `%LOCALAPPDATA%\Programs\GameModeExecutor` is the Windows convention for a per-user install, and it stays writable, which `C:\Program Files` would not | +| The program | `%LOCALAPPDATA%\Programs\GameModeExecutor` from the installer — the Windows convention for a per-user install, and it stays writable, which `C:\Program Files` would not. From the zip, wherever you put it | | Configuration | `config.toml` next to the executable if there is one, otherwise `%APPDATA%\GameModeExecutor\config.toml` | | Log | `%LOCALAPPDATA%\GameModeExecutor\logs\gamemode-executor.log`, one file, local timestamps | | Scheduled tasks | a `GameModeExecutor` folder in Task Scheduler, holding `Watcher` and anything a recipe added | diff --git a/docs/recipes/fancontrol-fan-profiles/README.md b/docs/recipes/fancontrol-fan-profiles/README.md index fc8f3b5..6cdc29d 100644 --- a/docs/recipes/fancontrol-fan-profiles/README.md +++ b/docs/recipes/fancontrol-fan-profiles/README.md @@ -307,12 +307,14 @@ FanControl records the active configuration in a file called `CACHE`, in its ConvertFrom-Json).CurrentConfigFileName ``` -When both work: +When both work, restart the watcher so it reads the file: ```bash +gamemode-executor stop gamemode-executor install-task ``` +From the zip, the second line also registers the logon task, the first time. Done. Play a game and the fans follow. ## Worth knowing diff --git a/docs/recipes/windows-power-plan/README.md b/docs/recipes/windows-power-plan/README.md index c0a5124..1a6b322 100644 --- a/docs/recipes/windows-power-plan/README.md +++ b/docs/recipes/windows-power-plan/README.md @@ -81,12 +81,15 @@ powercfg /getactivescheme gamemode-executor trigger stop ``` -When both work: +When both work, restart the watcher so it reads the file: ```bash +gamemode-executor stop gamemode-executor install-task ``` +From the zip, the second line also registers the logon task, the first time. + ## Worth knowing **On a laptop, Windows may override you.** Some machines switch plans by diff --git a/scripts/msi.ps1 b/scripts/msi.ps1 index 1a61dff..adf2047 100644 --- a/scripts/msi.ps1 +++ b/scripts/msi.ps1 @@ -3,11 +3,15 @@ # Per-user, no elevation, no UI: the two executables and the license go to # %LOCALAPPDATA%\Programs\GameModeExecutor. The user's configuration, log, # marker and scheduled tasks are not components, so no repair, upgrade or -# uninstall reaches them. Two custom actions, both the program's own -# commands and both idempotent, finish an install or upgrade: `init`, which -# writes a starter configuration only where there is none, and -# `install-task`, which registers the logon task only where there is none -# and then starts the watcher -- the icon appearing is the confirmation. +# uninstall reaches them. Four custom actions, all the program's own +# commands and all idempotent, run through the windowless executable: +# `stop` before an uninstall or upgrade touches the files, so the Restart +# Manager never has to ask; `init`, which writes a starter configuration +# only where there is none, and `install-task`, which registers the logon +# task only where there is none and then starts the watcher -- the icon +# appearing is the confirmation -- to finish an install or upgrade; and +# `uninstall-task` on an uninstall, since the task is the package's to take +# down. # # Written with nothing but Windows Installer's own COM automation and # makecab, so a stock runner can build it -- docs/design/08-distribution.md diff --git a/src/bin/gamemode-executorw.rs b/src/bin/gamemode-executorw.rs index c49bb99..e0dda5e 100644 --- a/src/bin/gamemode-executorw.rs +++ b/src/bin/gamemode-executorw.rs @@ -12,11 +12,12 @@ //! nothing this binary prints goes anywhere. That is why people type the //! console one and why the documentation only ever names it. This one is for //! the two callers that have no console to give: the logon task, which runs -//! the watcher, and the installer, which runs `init` and `install-task` -- -//! Windows Installer starts an executable action without hiding its console, -//! and the console binary flashed a window twice at the end of every install, -//! seen on 2026-09-18. The commands log what they did, so nothing is lost by -//! not printing it; the exit code is what the caller records. +//! the watcher, and the installer, which runs `stop`, `init`, `install-task` +//! and `uninstall-task` -- Windows Installer starts an executable action +//! without hiding its console, and the console binary flashed a window twice +//! at the end of every install, seen on 2026-09-18. The commands log what +//! they did, so nothing is lost by not printing it; the exit code is what the +//! caller records. use clap::Parser; diff --git a/src/task.rs b/src/task.rs index 2638b16..88d403f 100644 --- a/src/task.rs +++ b/src/task.rs @@ -20,8 +20,9 @@ pub const TASK_FOLDER: &str = "GameModeExecutor"; /// the folder's name, so it reads as `GameModeExecutor \ Watcher` in the tree. pub const TASK_NAME: &str = "GameModeExecutor\\Watcher"; -/// The windowless twin this task is meant to run. Sits beside the console -/// binary, which is the one the user types and therefore the one running now. +/// The windowless twin this task is meant to run. Sits beside whichever +/// binary is running this command: the console one a person typed, or the +/// twin itself when the installer runs it. const WATCHER_EXE: &str = "gamemode-executorw.exe"; /// What `install` did about the task. @@ -344,10 +345,11 @@ mod tests { assert!(xml.contains(&format!(r"\{TASK_NAME}")), "{xml}"); } - /// The task must run the windowless binary with no subcommand. `run` and - /// `--hidden` belong to the console binary, and passing either here would - /// make the task fail at every logon with an argument error nobody sees, - /// because there is no console to see it in. + /// The task runs the windowless binary with the configuration and nothing + /// else. `run` is the default command and `--hidden` is a leftover kept + /// for tasks registered before the twin existed; a definition that + /// names neither keeps working whatever later versions do with them, + /// and a task that fails at logon fails where nobody sees it. #[test] fn the_task_passes_only_the_configuration() { let xml = definition(