Skip to content

release: 2.3.2 - #17

Merged
ruscher merged 60 commits into
mainfrom
release/2.3.2
Oct 10, 2026
Merged

ruscher merged 60 commits into
mainfrom
release/2.3.2

Conversation

@ruscher

@ruscher ruscher commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Big Game Mode 2.3.2, a stability release from a full review of the code, the helper and the package.

  • pkgbuild/PKGBUILD: pkgver=2.3.2, pkgrel=1.
  • bigame-engine/Cargo.toml (and Cargo.lock): workspace version 2.3.2; rust-version 1.88, the real minimum.
  • AppStream: a 2.3.2 release entry (2026-10-10), translated in all 29 catalogues.
  • README: version badge and What's new in 2.3.2; 2.3.1's notes stay below it.

In this release

  • Turbo on processors falcond cannot run on (Sandy Bridge, Ivy Bridge and older with an x86-64-v3 falcond) no longer switches itself off a few seconds after it starts. A mode the login set no longer fails every preset change.
  • Nothing left behind: turning Turbo off or changing the preset reaches Steam and Heroic once they are closed; deleting a profile takes its launch options out of Steam, Heroic and Lutris.
  • Your settings stay yours: only what Big Game Mode added is removed, account by account (a native and a Flatpak Steam are two installs); the user's own launch options, Lutris' MangoHud line and MangoHud/vkBasalt files are kept.
  • AI Graphics makes one change at a time to a game; Restore from Profiles works as on the AI Graphics page.
  • Idle cost: one systemd reading for the tray and Home, no property cache traffic, slower polling while a game runs, nothing woken while the window is hidden.
  • Game detection: Electron/Chromium helpers, the Steam runtime's locale tools, Windows installers and crash reporters are not taken for the game.
  • A stricter helper: /run and the machine-wide /sys trees read-only, memory and task limits, no systemd reload when nothing changes, profiles named after system processes refused. Its Polkit messages are translated through gettext.

Packaging

  • The Polkit policy is merged with the catalogues by msgfmt --xml (rules from polkit's polkit.its), with pt-BR tags put back in the POSIX form polkit looks up.
  • depends: cairo, gdk-pixbuf2, graphene and pango, which bigame-ui links directly (namcap), and gtk4>=1:4.14, the GTK the code is built for (v4_14). Every other dependency was checked against the libraries the binaries link and the programs they run; namcap's "may not be needed" notes on curl, libarchive, iputils, iproute2, hwdata, dbus, polkit and systemd are programs and data used at run time.

CI on Rust 1.99

  • Clippy 1.99's chunks_exact_to_as_chunks: the PE import tables and UTF-16 strings are read through as_chunks (stable since 1.88, the declared minimum).
  • tests/daemon-authorization.sh checks the Peer interface with Peer.Ping: GetMachineId needs /etc/machine-id, which the CI container and the legacy-CPU VMs do not have.

Checks

  • makepkg from this branch: build, cargo test --release --workspace (906 + 29 + 47, 1 ignored), namcap without dependency warnings; installed and run on a BigLinux machine (tray and helper restarted on the new binaries).
  • cargo fmt --check and Clippy (-D warnings) clean.
  • msgfmt --check --check-format on every catalogue; extract-strings.py --check; appstreamcli validate --pedantic passes.
  • tests/daemon-authorization.sh passes locally; both CI jobs pass (Arch, Rust 1.99).
  • pkgver, the Cargo version and the newest AppStream release are all 2.3.2.

…Home

With a falcond build recorded as crashing on this processor, Turbo is the
Booster's: turbo::state() reads the Booster's journal. Home's 10-second
watcher still read falcond's unit alone, saw it inactive, and set Turbo to
Off a few seconds after it came on. Its tidy-up then did nothing (falcond
does not govern), so the power profile stayed at performance while every
surface said Off, and the next start showed Turbo on again before flipping.

The watcher and the support report now ask turbo::backend_governs, made
public for that, before taking falcond's unit as Turbo's state.
A centred grid of wrapping labels is measured for its width at the height
it was given; on a short screen that is less than it needs, and GTK warned
at every first start ("Trying to measure GtkGrid … for height of 464, but
it needs at least 1299"). An AdwClamp centres it without that measurement.
The kernel's "Registered N planes with drm panic", the Vulkan loader
skipping lsfg-vk 2's layer in a process without a profile, and libadwaita
ignoring the GTK dark-theme key KDE writes were shown as errors and
warnings, with nothing to act on.
docs/issue-4-falcond-sigill.md and docs/ci-workflows-audit.md recorded one
investigation and one audit each. What stays true is now where it belongs:
CPU-COMPATIBILITY.md says what Big Game Mode does with a falcond built for a
newer level and how to build falcond for the baseline (the patch it linked
is two lines, inlined), and the new CI.md describes the workflows, how the
BigLinux builders are triggered, namcap's expected warnings and how to
reproduce and investigate a run. The workflows and the README point there.
Categories=Game;System;Settings; named three main categories, so menus
could list Big Game Mode three times (desktop-file-validate's hint). It is
a game application. .gitignore also covers Thumbs.db and *.save.
AdwAboutDialog shows developer_name under the application's name, and
AppStream's <developer> is Rafael Ruscher; the dialog showed the tagline
there instead.
rust-version said 1.85, but zbus 5.14 and zvariant 5.10 need 1.87 and the
code's let chains need 1.88: `cargo +1.85 check` fails, `cargo +1.88 check
--locked --workspace --all-targets` passes. Declaring the real minimum lets
Clippy's version-aware lints apply; their rewrites are mechanical (nested
`if let`/`if` collapsed into let chains, one manual `is_multiple_of`).
…estate code

Each removed allow was checked by turning it into expect: none of them
suppresses a lint any more. config::read documented a failure on invalid
TOML it does not have (anything else reads as falcond's own format).
Messages joined a label and a value with a literal ": ", although the
catalogues already translate "%s: %s" (French puts a space before the
colon, Chinese uses a full-width one); i18n::labelled now does it everywhere
outside AI Graphics. The Gamescope options count goes through ngettext.
The UI re-exports bigame-core's N_ instead of defining its own.
details-gamer-light, home-default-light, home-game and welcome are not
referenced by the README, the AppStream file or any page.
The governor and the energy preference are written on every online CPU,
but were read on cpu0 alone. A write that failed on cpu0 and took on the
others read back as the baseline, so turning Booster off called the knob
already correct, wrote nothing and cleared the journal: the other CPUs
stayed at performance until the next boot.

Both knobs are now read on every CPU that has the attribute, as the
helper writes them. CPUs that disagree are no single value: never a
baseline to capture (one write cannot put them back), never "already
restored", so restoring writes the baseline; verifying reports the
values found. The journal's format is unchanged.
Setting Big Game Mode's entries aside wrote lsfg-vk's file first and the
state after: a failed state write lost the entries for good. Saving a
game's frame generation did the same the other way: an entry written to
lsfg-vk's file but missing from the state is one the general switch
never sets aside again.

The state is written first in both. If lsfg-vk's file then fails, the
entries are still in force there, which putting them back already
passes over, and an entry set aside twice by a retry replaces the first.
A preset's vkBasalt switch reaches Steam's launch options (the Gamescope
segment that keeps vkBasalt in the game) and Heroic's game settings.
Turbo on and off refreshed Steam only, and while Steam is open, which is
when Turbo is usually toggled, nothing is written to it: the refresh
returned SteamRunning, which was taken as done. The Enhanced preset's
vkBasalt then stayed in the launch options after Turbo off, for good;
Heroic's settings were never refreshed at all.

Both launchers are refreshed now. A game an open launcher (or an error)
kept from following leaves a mark in the state folder, with a warning
in the log; Big Game Mode's start, Turbo's resync, the next preset
change and the next Turbo on or off try again, and the mark goes once
every game follows.
A preset's record that does not parse reads as Standard. Turbo off
asked for the preset in force, found Standard and returned before
deactivate(), whose branch for an unreadable record (clear the preset's
variables anyway) could not be reached from there; Turbo's "something
left" check said nothing was left either.

has_record() tells whether a record is there, readable or not, and both
use it. A record that could not be read has no preset to name, so its
removal goes to the log rather than the report.
The Enhanced preset writes its own CAS-only vkBasalt file in
~/.config/bigame-mode, which nothing ever removed: Turbo off left it
behind. It goes once the session reads back without the preset, the
only thing that names it; a failure to remove it is logged, since the
preset is gone either way.
Tuning's video.toml and the session's environment.d file were written
in place. A crash or a full disk mid-write left a file cut short: a
video.toml that does not parse loads as every default, and the login
reads half an environment.d file.

Both go through the preset's atomic writer now (a temporary beside the
file, synced, then renamed). It follows a dotfile manager's symlink, as
lsfg-vk's writer does, so the link stays a link, and each write gets a
temporary of its own.
Home asks whether the session holds the Turbo preset every ten seconds,
and each time opened a new session bus connection (a new handshake with
the bus) and read the user manager's Environment through a proxy that
caches properties: one GetAll of every Manager property, plus a match
rule for their changes, to read one of them.

The connection is opened once and shared, as systemd::Reader::shared
does for the system bus; a failed connect is not kept, so a later call
tries again. The manager's proxy reads the property it is asked for and
caches nothing.
Two changes to one game at once - a double click on Apply, the page's
Restore beside the Profiles menu's, Restore while the recovery at start
still runs - had nothing keeping them apart. Each backed up what the
other had just placed as the game's original, and the first removal
then put OptiScaler back "as the original": the game's own files were
gone.

Install, reinstall, update (and go back), restore, repair and switching
the game's setting on again now run under a per-game flock in
state/.locks/<key>. flock is per opening of the file, so it also keeps
two threads of the UI apart, and it goes with a process that dies. The
lock files are never deleted (a waiter would lock the unlinked file
while the next change locks a new one) and live apart from the game's
state folder, which a removal still deletes once empty. The recovery at
start takes the same lock and reads the manifest again under it, so it
never rolls back an apply of this process that is still running.

The names two changes could share are unique now too:

- the staging folder was staging-<pid>, one per process;
- manifest.json.tmp and kept.json.tmp were one name for every writer;
- the backup folder was backup/<unix seconds>: an apply in the same
  second as an earlier removal that kept originals (files another
  program changed since) shared its folder, and its own removal deleted
  them. The first unused second is taken, which the manifest records as
  before, so older manifests still find their backups;
- edited configs a removal keeps went to edited-<seconds>, written over
  by a second removal in the same second.

kept.json is one list for every game, so it is written under a lock of
its own.
When a folder on the way to a placed file became a symlink (a mod
manager linking the game's bin folder elsewhere), Restore left the file
alone, as it must - the link is not Big Game Mode's to follow - and
reported it as kept, but then deleted the manifest. What is behind the
link may still be OptiScaler, and the game was left with it and nothing
that knew: no Restore offered, no launch protections, a later Apply
backing it up as the game's original.

Such an entry is told apart from a link at the file's own path (whose
file is someone else's, and ends the record as before) and stays in the
manifest, with its original's backup and the folders the install
created around it, and the staged copies Repair and Apply read. The
game still reads as changed, and the next Restore finishes once the
folder is a folder again. An interrupted apply keeps its state, for the
recovery at the next start to try again.
Apply, Repair, Restore, Update, Go back and "Switch it back on" greyed
the page's buttons only after awaiting the running-game check, so a
second click before that answer started a second run beside the first.
Update and Go back, in the versions group, were never greyed at all,
and an analysis started during a change (a choice changed while a
release downloads) made every button usable again mid-change.

Each of these now claims the page in the click handler itself, before
anything is awaited, and a click while a change runs does nothing. The
versions group is greyed with the other buttons, and while a change
runs only its own end makes the page usable again.

The Profiles menu's Restore had no guard either. Its menu and actions
are made anew each time the menu opens, so the games whose Restore runs
are kept apart from the action, and Restore is grey for them in every
menu until it is done.

The files are kept safe by the per-game lock in core whatever the UI
does; this keeps the second run from starting at all.
The two Restores did not do the same. The AI Graphics page took Big
Game Mode's WINE_FULLSCREEN_FSR=0 back out of the game's Steam launch
options after putting the files back; the Profiles menu left it there,
so Wine FSR stayed off for a game that no longer had OptiScaler. And the
menu said "The game's files are as they were before" even when files
another program changed since were left alone, or the user's edited
OptiScaler settings were kept as a copy somewhere.

graphics::restore now takes the Wine FSR switch out itself when it went
in with Apply (Steam closed; otherwise it stays, harmless, as before),
and both Restores call it. Both report the outcome with one function,
the page's existing wording and msgids: what was left alone, where the
edited settings went, and a game setting not put back yet.
"%s starts %s" was filled with two replacen calls, which only know the
plain %s: a translation that names the game first with %2$s ... %1$s
showed its placeholders as written. It is filled with Text::fill, which
reads numbered placeholders too.

Every "label: value" on the page was joined with a hard-coded ": ",
while the catalogues already translate that layout as "%s: %s" - French
writes "%s : %s", Chinese a full-width colon. They go through that
msgid now, as the status chips do. No new msgid.
graphics::versions had its own now(), the same as crate::unix_now(), and
the AI Graphics page its own sentence(), the same as the public
widgets::notice::sentence. One of each, so a fix to one reaches every
caller.
MemoryMax=64M and TasksMax=64. The helper is about 5 MiB resident (ps, and
systemd's MemoryPeak of 4.6 MiB on 2.3.1), so a leak or a runaway loop in it
now fails inside its own cgroup instead of pressing on the machine.

Tokio's runtime started one worker per CPU (17 tasks on a 16-thread Ryzen),
so a task ceiling would have kept the helper from starting on a 64-thread
processor. It now runs four workers: writing calls serialise on one lock
anyway, and every wait (Polkit, a password prompt, systemd settling) is
asynchronous.

The comment on MAX_PAYLOAD said it stopped an unauthenticated peer; such a
peer is refused before any argument is read. It bounds what an authorized
caller can have written for falcond to parse.
ProtectSystem=strict is documented to leave only /dev, /proc and /sys
writable, but the installed helper's mountinfo on systemd 261 shows /run
mounted rw, with /run/media/<user>/..., /run/systemd/system and
/run/udev/rules.d under it: a bug in the helper could have planted a unit
or a udev rule there.

The helper writes nothing under /run. It has no RuntimeDirectory, and the
system bus socket it connects to needs no write access to the mount.
Checked with a transient unit carrying the same settings: /run and
/run/dbus resolve to the read-only mount.
All of /sys/devices was writable, including /sys/devices/system (CPU
hotplug, SMT control, microcode reload, intel_pstate/amd_pstate limits,
clock sources) and /sys/devices/virtual (thermal trip points, powercap
limits). Both are now read-only, with /sys/devices/system/cpu/cpufreq
mounted writable over the first.

Every sysfs write in the helper still lands on a writable mount:
governor and EPP go through cpuN/cpufreq, a symlink to cpufreq/policyN;
the DPM level through /sys/class/drm/cardN/device, which resolves to the
card's PCI device; the V-Cache mode through the amd_x3d_vcache driver's
ACPI platform device under /sys/devices/platform.

Checked with a transient unit carrying the same settings: cpu0's
scaling_governor resolves to the rw cpufreq mount, cpu/online and
virtual/powercap to read-only ones, and the amdgpu DPM attribute to the
rw /sys mount. The real helper still has to be exercised on the installed
system (SetCpuGovernor, SetCpuEpp, SetGpuDpmLevel).
SetGameBackend needs no password in an active session, and every call
enabled or disabled the unit and then had PID 1 reload every unit file,
whatever the state: a process looping SetGameBackend(false) kept systemd
in daemon-reload.

A request for the state falcond is already in (enabled and running with
no restart by systemd since it was started, or disabled and stopped) now
returns that state at once. Ownership is still recorded first, since
taking control back after a hand-back is exactly such a request, and a
build recorded as crashing here is still refused. Otherwise the full path
runs as before, failure reset and illegal-instruction record included,
and Reload is called only when EnableUnitFiles or DisableUnitFiles report
a changed link; ReleaseGameBackend likewise.

The decision and the reload test are pure functions, covered by unit
tests.
The profile name check refused 21 names, so systemd-logind,
NetworkManager, sshd, zsh, fish, gamescope or systemd-journal were
accepted, and falcond would have applied a game's performance mode and
scheduler to them.

The deny-list now covers the service manager and its daemons, the bus and
Polkit, login and privilege programs, display managers, compositors, the
sound server, network, hardware and power daemons, shells, and Big Game
Mode with what it drives. Exact names only, compared without case as
falcond compares them, so no game title is caught by a prefix; a name is
also refused when it is the 15-byte comm form of an entry, which falcond
matches too (systemd-journal for systemd-journald).

On top of it, SaveProfile refuses a name falcond would match against a
process running as root at that moment: its comm, or the name falcond
takes from its command line (same rule as its
selectProcessNameFromCmdline). Under ProtectProc=invisible the helper,
uid 0 without CAP_SYS_PTRACE, still sees root processes whose ids are all
0 and that are dumpable, and every kernel thread; an unreadable /proc
refuses nothing, the deny-list remains the guarantee. DeleteProfile is not
restricted, so an existing profile can always be removed.
Every CheckAuthorization passed AllowUserInteraction, so any process
calling a privileged method could make a password dialog appear on the
user's screen, whether or not it could use the answer.

The helper now passes it only when the call's header carries D-Bus's
ALLOW_INTERACTIVE_AUTHORIZATION flag. Big Game Mode's client sets it on
every privileged method (zbus's allow_interactive_auth), and busctl sets
it by default, so the bench scripts behave as before. A caller without it
still passes on a cached or allow_active=yes authorization.

Checked on a private bus with busctl monitor: the client's
ReleaseGameBackend and busctl's SetCpuEpp carry Flags=4 and reach Polkit
with flags 1; busctl --allow-interactive-authorization=no carries Flags=0
and reaches Polkit with flags 0.
The Polkit policy carried English texts plus hand-written pt_BR twins,
was not extracted, and was installed as it is: 27 of the 29 languages got
English password prompts, and its pt_BR could drift from pt_BR.po.

The twins are gone. locale/extract-strings.py extracts each action's
<description> and <message> (what gettext's polkit.its translates) from a
.policy listed in POTFILES.in, and the policy is listed. The PKGBUILD
merges every catalogue into it with msgfmt --xml, as it does for the
desktop entry and the AppStream file, and installs the merged copy; the
ITS rules come with polkit, already a dependency, and a missing one fails
the build.

msgfmt tags a translation pt-BR, but polkit looks one up as pt_BR, then
pt: asked through EnumerateActions for pt_BR, polkit 127 served another
policy's pt text over its pt-BR one, and served pt-BR only to the locale
string pt-BR. The build rewrites ll-CC tags to ll_CC, and the package
check fails on one left over, besides checking that the installed policy
has the source's actions and English texts.

"Turbo Mode" becomes "Turbo", the feature's name everywhere else.

The template is not regenerated here: extract-strings.py --check reports
it stale until it is, with 12 new strings.
Interactive Polkit prompts only on request, the profile-name deny-list
and the running-root-process check, SetGameBackend's no-op and
reload-on-change, the memory and task ceilings, read-only /run and
machine-wide sysfs trees, translated Polkit texts, and what the
authorization test now proves about the shipped bus policy.
users() sorted the accounts by id and dropped every repeat of an id, so
one account signed in to both the native Steam and the Flatpak kept only
one of its two localconfig.vdf files: the other install's games never got
their launch options (Gamescope, MangoHud, FSR 4), and an option written
earlier there was never taken out. Each client reads only its own file,
so accounts are now told apart by that file.

The FSR 4 record named an account by its id, which now stands for two
files: it names the file instead, so a variable the user typed in one
install is not taken out because Big Game Mode added it in the other. A
record from before still holds the id and keeps meaning that id's
account in every install, as it did.
For an app block with nothing in it ("123" { }), the block's first line
is its own closing brace, and the new key took that line's depth: one
tab short of the keys inside, at the block's own level. The key is now
written one level in, where Steam's own keys are.
localconfig.vdf was written to `.localconfig.bigame.<pid>`, a name every
thread of the process shares, with no lock around the read, the change
and the read-back: two saves at once (a profile and AI Graphics) could
write each other's temporary, or the second could put back the first
one's old launch options. Nothing was flushed before the rename, so a
crash could leave an empty file in place of the user's Steam
configuration, and the new file took the umask's mode, not the old one's.

Heroic's and Lutris's settings (`<file>.bigame-new`) and MangoHud's
record had the same shared temporary and no flush. All of them now go
through one helper: a temporary of its own per write, flushed, renamed,
the folder flushed, the original permissions kept, and a symlink (stow,
chezmoi) resolved so the file it points to is replaced, not the link.
Steam's edits hold a process-wide lock from the read to the read-back.
The style's state came from read_to_string(...).ok(): a MangoHud.conf
or vkBasalt.conf that is not UTF-8 (Latin-1 comments from an old editor)
or a symlink to nothing (an unmounted dotfiles folder) read as "no file",
so choosing a style replaced it with no backup and "My own file" had
nothing to put back. The file is now read as bytes, decoded leniently
for the marker, and only "nothing at that path" counts as MangoHud's or
vkBasalt's defaults; anything else there is kept first, a dangling
symlink as the symlink itself.

Writing and putting back renamed over the path, which turned a stow or
chezmoi symlink into a regular file and left the user's dotfile behind.
Both now resolve the symlink and replace the file it points to, as
lsfg-vk's file already is.
The word MangoHud added to a Steam game's launch options was recorded
once per game, from whichever account added it first: with MANGOHUD=1
typed by the user in one account and added by Big Game Mode in another,
the record said "added", and Off took the user's own word out of the
first account too. Accounts were also written one by one as they were
worked out, so a failure in the second left the first changed with no
record of it, and the word stayed there for good.

The record now holds the word by account (its localconfig.vdf) and app,
and every account's new options are worked out before any is written;
one that cannot be written puts back the ones before it, as the FSR 4
option already does. A record from an older version (one word for the
game) still reads, as that word in every account.
With `ENABLE_VKBASALT=1 %command%` typed by the user and vkBasalt on for
the game, the options already started with the variable and were left
as they were, but the game's settings recorded `ENABLE_VKBASALT=1` as
Big Game Mode's: switching vkBasalt off later took the user's own word
out. The same held for WINE_FULLSCREEN_FSR=0/1 and
WINE_FULLSCREEN_FSR_MODE, and across accounts: one record for the game
stood for every account, whatever each one held.

A variable the options already set in front is now neither added again
nor recorded, and what was put in is recorded by account (its
localconfig.vdf) and app, in Big Game Mode's state directory. Every
account's options are worked out before any is written, and one that
cannot be written puts back the ones before it, so a half-done write no
longer leaves variables without a record. A game with no record by
account yet still takes its old record for every account, as before.
Off removed any `mangohud:` line under a Lutris game's `system:`,
whoever wrote it: a `mangohud: true` the user set in Lutris went when
Big Game Mode's MangoHud was switched off for the game, and a
`mangohud: false` became Lutris's default.

The first time Big Game Mode switches the option on, the line the game
had (or none) is recorded by the game's file, before the file changes;
Off puts that line back, and only over Big Game Mode's own
`mangohud: true`: a value changed since in Lutris is the user's and
stays. A game an older version switched on, with no record, still loses
the `true` that version wrote, and only that.
…e launchers

Deleting a profile removed falcond's file and lsfg-vk's entry only. What
Big Game Mode had written for the game elsewhere stayed: the Gamescope
segment and variables in its Steam launch options, MANGOHUD=1 or the
mangohud wrapper, FSR4_UPGRADE=1, its keys in Heroic's GamesConfig and
`mangohud: true` in its Lutris file. The game kept starting with them,
and its own launch settings, still saved, were written back into Steam
and Heroic at the next change in Tuning.

After the profile is gone, the game's own launch settings are cleared
and each path that writes into a launcher runs once more asking for
nothing, so only what Big Game Mode recorded writing comes out and the
user's own options stay; FSR 4 only where its record says it was added.
delete_reporting() says what each launcher did, Steam or Heroic being
open included, so the caller can tell the user what is still there;
delete() keeps its signature and logs it.
profiles::delete now returns what it took out of Steam, Heroic and Lutris.
With Steam or Heroic open they keep the launch options they read at start
and would write them back, so Profiles says to close them instead of only
"Profile deleted", with the existing launch-option texts.
Chromium, Electron and CEF helpers rewrite their command line as one
space-joined string, so argv[0] was the whole line and its basename
garbage; and a game's `--type=gpu-process` child, which submits the GPU
work, won the Steam tree over the program itself (Heroic added to Steam,
Electron games). argv[0] of such a line now ends where its switches
begin, and processes with ` --type=` in their command line are left out
of the Steam tree's pool.

A Proton tree fell back to its Linux processes while no Windows binary
was there yet, so whatever the container and the launch scripts ran for
a moment could be announced as the game. In a Proton tree only .exe
processes can be the game now. localedef, getopt, env, sleep, timeout,
zenity, yad and a versioned python3.N join the machinery, as do the
Windows installers and .NET compilers a first launch runs (msiexec,
mscorsvw, ngen) and BugSplat's crash reporter.
While a game runs, the tray's watcher enriched it on every look: every
descriptor link and DRM fdinfo of the process read for its render card,
possibly an NVML question, and Steam's manifest parsed again, every few
seconds for as long as the game ran. The render card is now kept like
the graphics path, once known, for 30 s; the manifest's title, install
folder and prefix for the life of the process.

The native-game list behind detection also forced a scan of the whole
library every five minutes, walking every install folder all day while
nothing changed. It now asks the library only for a change
(games::detect_all_changed): a scan when a watched file changed, never
for age alone, and its part of the map is worked out again only then.
The tray and Home each read falcond's unit every ten seconds through
fresh Unit and Service proxies built with zbus's default lazy property
cache: the first read of each added a PropertiesChanged match and
fetched every property of the interface (hundreds for a service), for
proxies that read five between them and are dropped. Uncached, each
property is one Get.

Measured with 501 readings of falcond.service in a release build: the
reader's CPU time fell from about 1.0 s to 0.23 s, systemd's from 88 to
32 ticks, the system bus's from 32 to 19.
games::menu_games and games::installed_game_for_process had no caller
left; menu_game and running::identify_with only served tests, so they
live there now. The detect example reads the GPU through
gpu_telemetry::sample, as the application does, which leaves
hardware::Gpu::hwmon_u64 and busy_percent unused. Two allows in
running.rs no longer suppressed anything; systemd.rs's type_complexity
still does and stays.
The status watch sits on the file's directory, so a rename into place is
seen, and woke for every event in it. While /var/lib/falcond/status does
not exist that directory is /tmp, where any program's temporary file
woke the watcher for one more read of an unchanged status. Records are
now matched against the file's name; an overflowed queue or a removed
watch still wakes it, as the file may have changed.
The tray's game watch walked /proc every five seconds for as long as a
game ran, all day if it stayed open, while the game is what should have
the machine. With a game known it now looks every fifteen seconds; its
exit is still caught at once when falcond drops its profile, and a game
falcond has no profile for is seen gone up to fifteen seconds later.
With no game the pace is unchanged.
The tray's status loop and Home's Turbo watcher each asked systemd for
falcond's unit every ten seconds, on timers of their own. Home now
takes the status loop's reading (app::follow_backend_unit), with the
same checks as before, including whether that falcond governs Turbo at
all; systemd is asked once per tick.

Home also read the session's preset every ten seconds with the window
hidden in the tray; it does so while the page is on screen, when it is
shown, and when Turbo went on or off.

The status loop asked pacman whether the missing runtime packages are in
the repositories on the main thread, through show_status; the install
command and the one to copy are now worked out on the loop's worker.
Home's live readings woke the main loop every two seconds, and Logs'
follow every five, all day with the window hidden in the tray, only to
find the page off screen and return. Both loops now wait for their page
to be shown (details::mapped), as Details' already do.
cargo doc -D warnings (CI) refused the link to LIBRARY_MAX_AGE.
What changed, as it reaches people: Turbo staying on where falcond cannot
run, nothing left in Steam, Heroic or Lutris, the user's own files kept,
AI Graphics one change at a time, idle cost, detection, and the stricter
helper. The README's 2.3.1 notes become a short history entry, its security
summary follows the helper's new limits, and it names the lead developer
and the Rust version the workspace really needs.
The template now holds the Polkit policy's 12 messages, the 2.3.2 release
notes and the Gamescope options count's plural; all are translated into the
29 languages (pt_BR's Polkit texts are the ones the policy carried, with
"Modo Turbo" now "Turbo"). Obsolete entries are dropped.
systemd 261 keeps a value its environment.d generator gave at login;
UnsetEnvironment cannot remove it from the running manager. With Wine FSR on
in Tuning at login, then off, WINE_FULLSCREEN_FSR_MODE stays, and the
read-back failed whenever a preset turned Wine FSR on (More FPS): every
start of the application logged 'could not bring the Turbo preset back', and
a preset switch would fail the same way. Such a detail only tunes a feature
its switch turns on or off, so it is logged as kept until the next login; a
variable that changes what a game gets (a frame cap) still fails it.
It showed 2.3.0 and the tagline where the developer's name now is.
@ruscher
ruscher merged commit 18eb808 into main Oct 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant