Skip to content

Conversation

@utkarshdalal
Copy link
Owner

@utkarshdalal utkarshdalal commented Jan 23, 2026


Summary by cubic

Cleanly exits the container when the game closes by watching for the game window to unmap and exiting once only essential Wine processes remain. Prevents lingering processes and ensures a single, clean exit, while forcing fullscreen for the correct executable.

  • New Features

    • Adds an exit watchdog in XServerScreen that matches the game window to the executable and polls WinHandler processes until only allowlisted services remain (with timeouts).
    • Cancels the watchdog on dispose to avoid leaks and false positives.
    • Forces fullscreen using container.executablePath to target the launched game.
  • Refactors

    • Centralizes Wine service defaults in WineUtils and exposes getEssentialServiceNames().
    • Updates changeServicesStatus to use the shared service defaults.

Written for commit 1061152. Summary will update on new commits.

Summary by CodeRabbit

  • Improvements

    • More reliable graceful exit when unmapped game windows close by monitoring background processes, waiting for a stable snapshot, and confirming only essential processes remain before shutdown.
    • Fullscreen detection now prefers the container executable when available.
    • Automatic cleanup and cancellation of background watchers to improve stability and avoid orphaned monitors.
  • New Features

    • Publicly exposed list of essential background services for diagnostics.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

Adds an asynchronous exit watcher to XServerScreen that monitors live process snapshots for unmapped game windows and triggers exit when only essential processes remain. Also extracts Wine service defaults into WineUtils and exposes them via a new public API.

Changes

Cohort / File(s) Summary
Process Exit Monitoring
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
Adds coroutine-based exit watcher for unmapped game windows: process normalization/executable-matching helpers, private essential-process allowlist builder, CoroutineScope watcher using CompletableDeferred/Job/withTimeoutOrNull/delay, OnGetProcessInfoListener wiring (preserve/restore), cleanup on dispose/teardown, and uses container.executablePath in fullscreen-class fallback.
Service Defaults API
app/src/main/java/com/winlator/core/WineUtils.java
Introduces SERVICE_DEFAULTS constant and new public method getEssentialServiceNames() returning an unmodifiable list; replaces inline defaults usage with the constant.

Sequence Diagram

sequenceDiagram
    actor Game
    participant Screen as XServerScreen
    participant Watcher as ExitWatcher
    participant ProcMon as ProcessMonitor
    participant Exit as ExitHandler

    Game->>Screen: onUnmapWindow(window)
    Screen->>Watcher: startExitWatchForUnmappedGameWindow(window)
    Watcher->>ProcMon: subscribe OnGetProcessInfoListener
    loop polling snapshots
        ProcMon->>Watcher: onGetProcessInfo(snapshot)
        Watcher->>Watcher: normalize executables & filter essential list
        alt only essential processes remain (stable / within timeout)
            Watcher->>Exit: invoke exit() on UI thread
            Exit->>Screen: shutdown sequence
            Watcher->>ProcMon: unsubscribe listener
        else non-essential processes present
            Watcher->>Watcher: delay and continue polling
        end
    end
    Screen->>Watcher: onDispose -> cancel Job and restore previous listener
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I watched the windows disappear,
I listened for processes far and near,
When only essentials softly stayed,
I hopped and signaled—exit made! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'exit container cleanly if game is exited' directly describes the main objective of this PR: implementing an exit watchdog to ensure the container exits cleanly when the game closes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Around line 178-183: The process-name normalization uses locale-dependent case
folding; update normalizeProcessName to use a stable locale by replacing
Locale.getDefault() with Locale.ROOT when calling lowercase so identifiers are
normalized consistently (e.g., change the call in normalizeProcessName that
computes lower to use Locale.ROOT).
🧹 Nitpick comments (1)
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt (1)

323-390: Guard listener restoration to prevent clobbering future concurrent listeners.

Currently, only the exit watcher installs listeners, so there's no immediate risk. However, defensively checking if the current listener is still the watcher's before restoring the previous one follows best practices for safe cleanup in potentially shared APIs. Since listProcesses() is queued via addAction() and processed on a single-threaded executor, it's already thread-safe from the background thread context.

🛡️ Defensive restore
-            } finally {
-                winHandler.setOnGetProcessInfoListener(previousListener)
+            } finally {
+                if (winHandler.getOnGetProcessInfoListener() === listener) {
+                    winHandler.setOnGetProcessInfoListener(previousListener)
+                }
                 synchronized(lock) {
                     pendingSnapshot = null
                 }
             }

Comment on lines +178 to +183
private fun normalizeProcessName(name: String): String {
val trimmed = name.trim().trim('"')
val base = trimmed.substringAfterLast('/').substringAfterLast('\\')
val lower = base.lowercase(Locale.getDefault())
return if (lower.endsWith(".exe")) lower.removeSuffix(".exe") else lower
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use Locale.ROOT for process-name normalization.
Locale-dependent case folding can cause mismatches (e.g., Turkish locale). Use a stable locale for identifier normalization.

♻️ Suggested tweak
-    val lower = base.lowercase(Locale.getDefault())
+    val lower = base.lowercase(Locale.ROOT)
🤖 Prompt for AI Agents
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` around
lines 178 - 183, The process-name normalization uses locale-dependent case
folding; update normalizeProcessName to use a stable locale by replacing
Locale.getDefault() with Locale.ROOT when calling lowercase so identifiers are
normalized consistently (e.g., change the call in normalizeProcessName that
computes lower to use Locale.ROOT).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Around line 361-382: The watcher currently ignores empty but valid snapshots
because of the snapshot.isNotEmpty() guard, causing stalls; remove the
isNotEmpty check so an empty snapshot is treated as "no non‑essential processes"
(since snapshot.any { ... } will be false for empty lists) and then call
exit(...) with the existing parameters (winHandler, PluviaApp.xEnvironment,
frameRating, currentAppInfo, container, onExit, navigateBack) when
!hasNonEssential; keep the withTimeoutOrNull(EXIT_PROCESS_RESPONSE_TIMEOUT_MS)
and deferred.await() logic unchanged.
♻️ Duplicate comments (1)
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt (1)

178-183: Locale-dependent normalization remains.
This is the same locale-sensitivity issue already noted previously; consider switching to Locale.ROOT for stable case folding.

@utkarshdalal utkarshdalal merged commit 7bf01d9 into master Jan 23, 2026
3 checks passed
@utkarshdalal utkarshdalal deleted the auto-close-2 branch January 23, 2026 13: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.

2 participants