Skip to content

Per-game activity, part 2: background installs, installers, actions by id - #55

Merged
sevenns merged 11 commits into
release/v0.9.0from
feature/per-game-activity
Oct 6, 2026
Merged

sevenns merged 11 commits into
release/v0.9.0from
feature/per-game-activity

Conversation

@sevenns

@sevenns sevenns commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Per-game activity, part 2: background installs, installers, actions by id

Follows #53 (Steam downloads per game). Every install and removal now runs as a background job of its own game, beside any other game's job and beside the one running game. AppState is the game session only.

Background jobs (src/main/game-jobs.ts)

  • Copy installs, uninstalls and prefix cleanups leave AppState. Each job owns its abort controller and process and shows up as the game's activity. Jobs never raise the window: a result is a notification (game-installed / game-uninstalled, new game-install-failed / game-uninstall-failed with the reason). The error popup appears only while the launcher has focus.
  • Atomic copy: files go to <install dir>.partial and are moved into place only when complete and holding the executable. A stopped copy leaves nothing that looks installed.
  • Card installers (nsis / inno / custom) are jobs too: a .playhook-installing marker for the run, the installer, then the grace poll for the executable. A stopped or failed run sweeps the install dir.
  • Queues (startVerdict, pure):
    • at most PLAYHOOK_MAX_PARALLEL_COPY_INSTALLS (2) copies and PLAYHOOK_MAX_PARALLEL_INSTALLERS (1) installer runs at once;
    • interactive and elevated installers wait for the session to end, and so does a removal whose own uninstaller would run elevated;
    • under gamescope nothing that opens a window runs while a game does: no installer, no winetricks prefix setup, no third-party uninstaller;
    • waiting jobs read "Waiting to install..." / "Will install after you quit the game" and start when the state settles.
  • Cancel: More (⋯) → Cancel installation (action:cancel-job with the id) for a queued or running install.
  • Card removal stops the card's installs, even during a busy session (reported as failed with the reason). Removals target the PC and finish. A reload that drops a game stops its job (game-gone).
  • Gates: Delete / Move to card / Save in Customize by id. The updater and keep-awake count running jobs.

Linux: ProvisionLock

winetricks shares one download cache and takes no lock, so ensurePrefixDeps runs one at a time across games. A launch with nothing to provision does not wait. umu-launcher 1.4.1 already serializes its runtime, Proton download and prefix setup with flock (spike result, read from the sources; to be confirmed on the Deck).

Quit with work in progress (src/main/quit-gate.ts)

  • One server-side gate for Quit (launcher and tray), Shutdown and Reboot: with jobs running, an unconfirmed request asks first.
  • Inside the launcher it is the Shutdown card's confirm popup, with the job count in plural forms; Shutdown / Reboot get a "with jobs" text.
  • Tray Quit asks in the launcher popup through quit:confirm / quit:confirm-reply. The question queues behind an open question, error or work popup (deferred-questions.ts). If the window does not confirm it is asking within PLAYHOOK_QUIT_CONFIRM_ACK_MS, a native dialog asks instead.
  • A confirmed quit waits up to PLAYHOOK_QUIT_GRACE_MS for the jobs to clean up. Orphaned .partial dirs are swept on start.

Actions by id

  • action:launch / action:uninstall carry the game id. GameActions (game-actions.ts) resolves the target with the pure resolveActionTarget (action-target.ts): while a game runs, another game can be installed (Steam or a job) or removed, never launched.
  • The confirm popup fixes its target id when it opens, and closes if the screen moves to another game.
  • Renderer: screenActions in state-view.ts decides Play / Install / Uninstall / Cancel / Force close and the Play view from the game on screen. Force close shows only on the running game's screen. Another game's Play is shown but inactive (data-play-locked).

Cleanup and docs

  • installing / uninstalling are gone from AppState.
  • README: a "Background installs and removals" section with the env table. CLAUDE.md: the session vs activity models.
  • Ratchet entries lowered: game-controller.ts 1468 → 1397, createControls 672 → 611.

Also included

Tests

New:

  • game-jobs (queues, cancel, atomic copy, installers, gamescope and session rules);
  • quit-gate, confirm-quit, provision-lock, action-target (full matrix);
  • state-view (screenActions combinations);
  • renderer controls (cancel item, quit popup and its queue, actions while another game runs).

game-controller.test.ts:

  • the install / uninstall / prefix-cleanup scenarios rewritten against the activity journal;
  • new scenarios for jobs beside a running session, card removal, reloads and actions by id.

typecheck, lint, vitest (1479) and npm run build are green locally.

Not covered here

Manual runs on the Deck (Game Mode and Desktop) and on Windows, from the plan's checklists:

  • a silent install beside a running game; interactive and elevated installs waiting for the game to end;
  • a first install on a clean prefix while another game launches; two installers back to back from the queue;
  • Start+Back over a game on Windows: install a Steam and a copy game, remove a third;
  • Steam's window switcher in Game Mode: the gamepad works in the launcher, and switching back to the game keeps the session;
  • Steam's install dialog over a running game in Game Mode.

@sevenns
sevenns merged commit db17157 into release/v0.9.0 Oct 6, 2026
6 checks passed
@sevenns
sevenns deleted the feature/per-game-activity branch October 6, 2026 18:33
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