Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3381707
Open Lot 8, and give the updater a lot of its own
geeooff Sep 17, 2026
f16e698
Measure the per-user MSI: install, upgrade and uninstall without a pr…
geeooff Sep 17, 2026
70ba710
Ignore makecab's reports
geeooff Sep 17, 2026
ac8b90a
Give each executable a version block
geeooff Sep 17, 2026
29ce1fe
Build the Windows Installer package, and validate it
geeooff Sep 17, 2026
93a2a27
Add purge, the one way out that removes every trace
geeooff Sep 17, 2026
de5975f
Publish a release from a tag, and point the documentation at it
geeooff Sep 17, 2026
f9315a8
Keep purge's shell hidden, and say when the folder cannot go yet
geeooff Sep 17, 2026
f4c7f65
Record the first real purge and what it left behind
geeooff Sep 17, 2026
adc056d
Finish the removal from PowerShell, waiting for this process
geeooff Sep 17, 2026
32d090f
Finish an install by writing the configuration and starting the watcher
geeooff Sep 17, 2026
5dbf014
Record that the installer's actions open no window
geeooff Sep 17, 2026
45799ea
Share one command line between the two executables and log what setup…
geeooff Sep 17, 2026
d95fc17
Stop the watcher from the command line and before the installer touch…
geeooff Sep 17, 2026
834fe4e
Record the first uninstall that stopped the watcher itself
geeooff Sep 17, 2026
7852e48
Say who saw that no dialog appeared
geeooff Sep 17, 2026
78c7fe6
Skip the stop action in a product an upgrade is removing
geeooff Sep 17, 2026
eda34f4
Remove the logon task with the package, and name the package in its s…
geeooff Sep 17, 2026
247fa1b
Record the uninstall that took its task with it
geeooff Sep 17, 2026
b24116d
Upload the installer with the zip from CI, uncompressed
geeooff Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,31 @@ jobs:

# The same checklist a developer runs: fmt, clippy -D warnings, tests,
# every shipped config.toml validated, every documentation link
# resolved, release build, PE subsystem check, commit stamp check, and
# the zip archive. A release nobody can make by hand is not one CI can
# make either, so there is one script and CI calls it.
# resolved, release build, PE subsystem check, commit stamp check, the
# zip and the installer with the SDK's ICEs run over it. A release
# nobody can make by hand is not one CI can make either, so there is
# one script and CI calls it.
- name: Test, build and package
shell: pwsh
run: .\scripts\build.ps1 release

- name: Read the version
id: version
shell: pwsh
run: |
$version = (Select-String -Path Cargo.toml -Pattern '^version = "(.+)"').Matches[0].Groups[1].Value
"version=$version" >> $env:GITHUB_OUTPUT

# Both artefacts, in one download. GitHub serves every artifact as a
# zip of its own whatever it holds -- there is no raw download from
# the Actions page -- so the level is 0 to keep it a plain container
# around two files that are already compressed. Raw files come from
# the release workflow, which attaches them to the release itself.
- uses: actions/upload-artifact@v7
with:
name: GameModeExecutor-zip
path: dist/*.zip
name: GameModeExecutor-${{ steps.version.outputs.version }}
path: |
dist/*.msi
dist/*.zip
compression-level: 0
if-no-files-found: error
59 changes: 59 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Release

# A tag alone produces the release: push vX.Y.Z and this runs the same
# checklist a developer runs, builds the installer and the zip from that
# exact commit, and publishes them with their checksums. Nothing is built
# or uploaded by hand. docs/design/08-distribution.md has the rules.

on:
push:
tags: ['v*']

permissions:
contents: write

jobs:
release:
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
with:
# The notes list the commits since the previous tag.
fetch-depth: 0

- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

# The version is bumped in the release commit, so the tag and Cargo.toml
# must agree; a release named after one and built from the other would
# carry the wrong version in every file.
- name: The tag names the version in Cargo.toml
shell: pwsh
run: |
$version = (Select-String -Path Cargo.toml -Pattern '^version\s*=\s*"([^"]+)"').Matches[0].Groups[1].Value
if ($env:GITHUB_REF_NAME -ne "v$version") {
throw "tag $env:GITHUB_REF_NAME does not match Cargo.toml version $version"
}
"GME_VERSION=$version" | Add-Content $env:GITHUB_ENV

- name: Test, build and package
shell: pwsh
run: .\scripts\build.ps1 release

- name: Checksums and notes
shell: pwsh
run: .\scripts\release-notes.ps1 -Tag $env:GITHUB_REF_NAME

- name: Publish
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create $env:GITHUB_REF_NAME `
"dist\GameModeExecutor-$env:GME_VERSION.msi" `
"dist\GameModeExecutor-$env:GME_VERSION.zip" `
dist\SHA256SUMS.txt `
--title "GameModeExecutor $env:GME_VERSION" `
--notes-file dist\notes.md `
--verify-tag
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@

# Personal, per-machine instructions for Claude Code
/CLAUDE.local.md

# makecab writes its report next to whoever calls it
setup.inf
setup.rpt
15 changes: 11 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ deleted.
`ALL CAPS` categories, no `camelCase` in prose. Conversation with the
maintainer is in French.
- **Log lines follow the contract in `docs/reference.md`.** `info` is
reserved for detection and the watcher's own start and stop; everything
else is `debug` unless it is a degradation (`warn`) or needs the user
(`error`). The message is the sentence, the fields are the technical annex,
reserved for detection, the watcher's own start and stop, and what the
setup commands did to the machine; everything else is `debug` unless it is
a degradation (`warn`) or needs the user (`error`). The message is the sentence, the fields are the technical annex,
and every call names a `target:` — a test fails the build otherwise.
- Module-level doc comments carry the rules a module is shaped by (the tray's
re-entrancy rule, the marker's location, the engine's callback). Read them
Expand All @@ -88,7 +88,7 @@ deleted.
```powershell
.\scripts\build.ps1 test # fmt, clippy -D warnings, tests, every shipped config.toml validated, every doc link resolved
.\scripts\build.ps1 build # + release build, PE subsystem check
.\scripts\build.ps1 release # + refuses a dirty tree, checks the stamped commit, zips into dist\
.\scripts\build.ps1 release # + refuses a dirty tree, checks the stamped commit, zips into dist\, builds and validates the MSI
```

Run `test` before every commit and read its result — a `FAILED` scrolling
Expand All @@ -97,6 +97,13 @@ tree because the binaries carry the commit they were built from, and
`--version` links to that commit's `docs/getting-started.md`: build a release
from the commit that carries the final documentation, never before it.

**Publishing a release** is a tag, and the tag needs explicit approval like
any push: bump `version` in `Cargo.toml` in the release commit, merge it,
tag that commit `vX.Y.Z`, push the tag. The release workflow runs the same
script on a runner and publishes the installer, the zip and their checksums.
Versions follow `docs/design/08-distribution.md`: the number moves only in
a release commit, and 1.0.0 waits for the criteria written there.

Three tests read this machine's registry — the Known Game List, the Game Bar
registration, the real sensor — which a GitHub-hosted Windows Server runner
does not have. They are `#[ignore]`d with that reason and the script runs
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[package]
name = "game-mode-executor"
version = "0.1.0"
authors = ["Geoffrey Vancoetsem"]
edition = "2024"
# Let chains, stable since 1.88.
rust-version = "1.88"
Expand Down Expand Up @@ -37,6 +38,9 @@ windows = { version = "0.62", features = [
# CreateEventW and CreateMutexW take SECURITY_ATTRIBUTES, even as None.
"Win32_Security",
"Win32_Storage_Packaging_Appx",
# MsiEnumRelatedProducts: whether the installer owns the executables, which
# decides how `purge` removes them.
"Win32_System_ApplicationInstallationAndServicing",
# WNDCLASSEXW names HBRUSH, HICON and HCURSOR, so the window class needs Gdi
# even though this program never draws anything.
"Win32_Graphics_Gdi",
Expand Down
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,38 @@ small icon in the notification area.

## Status

Version 0.1.0, in daily use on its author's machine; no release has been
published yet. Developed and measured on Windows 11 25H2. It relies on the Game
Bar component Windows ships by default, so a machine where Game Bar has been
In daily use on its author's machine, and released from
[the releases page](https://github.com/Geeooff/GameModeExecutor/releases).
Developed and measured on Windows 11 25H2. It relies on the Game Bar
component Windows ships by default, so a machine where Game Bar has been
removed will not detect anything.

## Install

Two self-contained executables, no runtime dependencies. `gamemode-executor.exe`
is the one you type commands into; `gamemode-executorw.exe` is the same watcher
is the one you type commands into; `gamemode-executorw.exe` is the same program
with no console, started at logon by a task it registers for you.

Until a release exists, build from source with the stable Rust toolchain:
From the [latest release](https://github.com/Geeooff/GameModeExecutor/releases/latest),
take the **`.msi`** and run it: per user, no administrator prompt, into
`%LOCALAPPDATA%\Programs\GameModeExecutor`. It writes a starter
configuration, registers the logon task and starts the watcher — the icon
appearing beside the clock is the confirmation. Right-click it, *Edit
configuration*, and say what to run.

```powershell
.\scripts\build.ps1 release
```

That runs the checks, builds, and leaves a zip archive in `dist\`. Unzip it
in `%LOCALAPPDATA%\Programs\GameModeExecutor`, then:
The **`.zip`** holds the same executables for anyone who would rather unpack
them by hand; then:

```powershell
gamemode-executor init # write a starter config.toml
gamemode-executor validate # check it
gamemode-executor install-task # start the watcher at every logon
gamemode-executor init # write the starter config.toml
gamemode-executor install-task # start the watcher now and at every logon
```

To remove it, *Programs and Features* takes the executables and the logon
task away and leaves your configuration; `gamemode-executor purge` removes
every trace. Building
from source is in the [reference](docs/reference.md#building-and-releasing).

## License

MIT. See [LICENSE](LICENSE).
Loading