Conversation
Playnite can swap its first big window (a loading screen on slow starts) for the main one. The exit check read the watched window disappearing as "Playnite closed" and restored the desk after a few seconds. In Playnite mode the loop now follows the new window (re-arming the exit watch) and ends the session only when Playnite.FullscreenApp exits, or stays without a big window for 20 s. The decision lives in PlayniteExitPolicy, with unit tests. Fixes #81 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Build-only run on this branch (workflow_dispatch, nothing published): tests 137/137 passed, portable + installer built. Artifacts for testing on Windows: https://github.com/lippdev/consolemode/actions/runs/36176223057 |
There was a problem hiding this comment.
Important
This PR can keep console mode active after the watched Playnite window disappears, because the replacement lookup can accept an invalid window or a different Playnite process. Please address the two ranked findings below before merging.
Reviewed changes
This review covers the Playnite window-swap exit detection, process-running check, runtime-state reset, and the new pure policy tests.
- Window-swap handling — clears the cached HWND, searches for a replacement, re-arms the destruction watch, and applies a 20-second windowless grace period.
- Process detection — adds a
Playnite.FullscreenAppprocess check and resetsPlayniteWindowMissingSincewhen a session starts. - Test coverage — adds four cross-platform tests for the pure exit policy. Those tests pass, but the Windows HWND integration is not exercised.
⚠️ The window-swap integration is not covered by tests
The four added tests cover only PlayniteExitPolicy.IsExit; none drives ConsoleEngine.IsPlayniteExitSignaled through the old HWND disappearing, a replacement appearing later, or a replacement being invalid. The core slow-start behavior can therefore regress while the new tests still pass.
Technical details
# Window-swap integration coverage
## Affected sites
- `src/ConsoleMode/Services/ConsoleEngine.cs:434-445` — the new stateful replacement and process/window decision path is untested.
- `tests/ConsoleMode.Tests/PlayniteExitPolicyTests.cs:10-24` — the tests exercise only the pure policy inputs, not HWND selection or watch re-arming.
## Required outcome
- Add deterministic coverage for the window replacement transition, delayed replacement, invalid or hidden candidate, and process/window mismatch cases, or introduce test seams that make those cases verifiable without running the app.GPT Luna | 𝕏
| private bool IsPlayniteExitSignaled(bool watchedGone) | ||
| { | ||
| if (watchedGone) State.CachedBigPictureHandle = 0; | ||
| var showing = Launch.IsPlayniteActive(State); |
There was a problem hiding this comment.
[P1] A newly discovered candidate is accepted as active based only on GetWindowArea; unlike the cached-handle branch in IsPlayniteActive, this path does not require IsWindowStillVisible. During splash teardown or with a hidden/stale large main window, showing can remain true while no fullscreen window is present, clearing PlayniteWindowMissingSince at line 443 and preventing the 20-second fallback, so the desktop is never restored.
Technical details
# Invalid replacement HWND prevents restoration
## Affected sites
- `src/ConsoleMode/Services/ConsoleEngine.cs:435,443-445` — treats the result of `IsPlayniteActive` as proof that a visible Playnite fullscreen window exists.
- `src/ConsoleMode/Services/LaunchService.cs:127-131` — accepts a fresh `MainWindowHandle` when its rectangle exceeds 200,000 pixels, without the visibility check used for the cached handle.
## Required outcome
- Only clear `PlayniteWindowMissingSince` and re-arm the watch for a currently valid, visible, sufficiently large Playnite fullscreen window.|
|
||
| if (showing) State.PlayniteWindowMissingSince = null; | ||
| else State.PlayniteWindowMissingSince ??= DateTime.Now; | ||
| var exit = PlayniteExitPolicy.IsExit(Launch.IsPlayniteRunning(), showing, State.PlayniteWindowMissingSince, DateTime.Now); |
There was a problem hiding this comment.
[P2] IsPlayniteRunning() is global to the executable name and is not tied to the HWND selected by IsPlayniteActive. If an old, restarting, or concurrent Playnite.FullscreenApp process retains a large window after the launched session closes, the replacement lookup can adopt that other window and this process check keeps the policy from exiting, so the desktop is not restored.
Technical details
# Playnite session is not identified
## Affected sites
- `src/ConsoleMode/Services/LaunchService.cs:104-110` — enumerates any `Playnite.FullscreenApp` process rather than identifying the launched process.
- `src/ConsoleMode/Services/ConsoleEngine.cs:435,443-445` — accepts any matching process window as the active session and uses any same-named process to suppress exit.
## Required outcome
- Keep the tracked Playnite process/window identity associated with the console-mode launch, and use that identity consistently when evaluating replacement windows and process exit.
Fixes #81, reported on Reddit: when Playnite fullscreen takes a while to load, Console Mode restores the desk after ~5 s and Playnite has to be launched again.
Cause (most likely):
IsExitSignaledtreated the watched Playnite window disappearing (exit watch orIsWindowStillVisible) as "Playnite closed". On slow starts Playnite can show a big loading window first and then replace it with the main window.Fix: in Playnite mode, after the window has appeared:
Playnite.FullscreenAppwindow and re-arm the exit watch on itPlaynite.FullscreenAppis no longer running, or has stayed without a big window for 20 s (PlayniteExitPolicy.WindowGrace)Big Picture and Xbox paths are unchanged.
PlayniteExitPolicy(pure) +PlayniteExitPolicyTestsLaunchService.IsPlayniteRunning()Not verified on Windows yet. I couldn't build or run it locally (macOS). To test: Playnite mode with a slow start (e.g. a big library / theme), confirm it stays on the TV, then close Playnite fullscreen and confirm the desk comes back.
🤖 Generated with Claude Code