Skip to content

feat: add manual pause for running game#542

Open
xXJSONDeruloXx wants to merge 6 commits intoutkarshdalal:masterfrom
xXJSONDeruloXx:feat/manual-pause
Open

feat: add manual pause for running game#542
xXJSONDeruloXx wants to merge 6 commits intoutkarshdalal:masterfrom
xXJSONDeruloXx:feat/manual-pause

Conversation

@xXJSONDeruloXx
Copy link
Contributor

@xXJSONDeruloXx xXJSONDeruloXx commented Feb 17, 2026

in back button menu, add a pause/play button that suspends wine procs similar to how sleeping android does, but this can be user controlled.

Inspired by this decky plugin I love for Steam Deck:
https://github.com/popsUlfr/SDH-PauseGames

A few use cases I find this nice for:

  • games like dark souls, I can pause it even though the game does not naturally pause
  • some handhelds if I have lock screen disabled and they get bumped and waken up, now this will turn on screen but not ramp up CPU and melt the device in my pocket, an iPod lock of sorts
  • on something like the Thor, I might multitask on secondary screen, but I do not want to let the game idle and spend resources, nor swipe it away and risk it getting purged from memory
  • Still testing, but in some cases where I observed lengthy suspends caused the app to crash on next wake, not seeing that here, need more time with it to say for sure
image image image

Summary by cubic

Add a manual Pause/Resume control in the back button menu to suspend and resume the game on demand. Manual pause persists across background/foreground, pauses audio, and shows a localized pause overlay.

  • New Features
    • Pause/Resume in the navigation dialog; icon/label toggle by state with localized strings for Pause, Resume, and Paused (dialog reflects current state).
    • Suspends/resumes Wine and audio (PulseAudio, ALSA) via XEnvironment; global PluviaApp.isManuallyPaused blocks lifecycle auto-resume/auto-pause.
    • Full-screen paused overlay with icon and localized text.
    • On exit, resumes once for clean shutdown, then clears manual pause; emits analytics events.

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

Summary by CodeRabbit

  • New Features

    • In-game manual Pause/Resume added to the navigation menu with play/pause icons and a translucent "PAUSED" overlay.
    • Pause state is exposed to dialogs, preserved through UI and exit flows, and now controls audio subsystems so audio is paused/resumed with the game.
    • New localized "Pause / Resume / Paused" strings added across many languages.
  • Bug Fixes

    • Lifecycle auto-pause/resume now respects manual pause; exiting clears manual pause and resumes paused processes.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a user-controlled pause/resume flow: new manual pause flag, lifecycle guards in MainActivity, NavigationDialog action and constructor change, in-screen pause state and overlay, audio component pause/resume methods and XEnvironment lifecycle calls, new pause/play drawables, and localized pause/resume strings.

Changes

Cohort / File(s) Summary
App lifecycle
app/src/main/java/app/gamenative/PluviaApp.kt, app/src/main/java/app/gamenative/MainActivity.kt
Introduce isManuallyPaused flag; modify onPause/onResume to check SteamService.keepAlive and PluviaApp.isManuallyPaused, adjust logging.
In-game UI & flow
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
Add isGamePaused state initialized from PluviaApp.isManuallyPaused; handle ACTION_PAUSE_RESUME to toggle pause, call environment onPause/onResume, set/clear manual flag, emit analytics, show pause overlay, and ensure resume before exit.
Dialog API & actions
app/src/main/java/com/winlator/contentdialog/NavigationDialog.java
Add ACTION_PAUSE_RESUME constant and extend constructor to NavigationDialog(Context, NavigationListener, boolean isPaused); conditionally add pause/resume menu item based on isPaused.
Audio lifecycle components
app/src/main/java/com/winlator/xenvironment/XEnvironment.java, app/src/main/java/com/winlator/xenvironment/components/PulseAudioComponent.java, app/src/main/java/com/winlator/xenvironment/components/ALSAServerComponent.java
XEnvironment now calls pause()/resume() on PulseAudioComponent and ALSAServerComponent; both components gain public pause() and resume() methods (PulseAudio suspends/resumes process; ALSA toggles client pause/start).
Connector helpers
app/src/main/java/com/winlator/xconnector/XConnectorEpoll.java
Added getConnectedClientsCount() and getConnectedClientAt(int) accessor methods for iterating connected clients.
Drawables
app/src/main/res/drawable/icon_pause.xml, app/src/main/res/drawable/icon_play.xml
Add 24×24 vector drawables for pause and play icons.
Strings & localizations
app/src/main/res/values/strings.xml, app/src/main/res/values-*/strings.xml
Add pause_game, resume_game, and game_paused strings across base and many locale files (da, de, es, fr, it, ko, pl, pt-rBR, ro, uk, zh-rCN, zh-rTW).

Sequence Diagram

sequenceDiagram
    actor User
    participant XServerScreen
    participant NavigationDialog
    participant MainActivity
    participant XEnvironment
    participant PluviaApp

    User->>XServerScreen: open menu
    XServerScreen->>NavigationDialog: show(menu, isPaused)
    NavigationDialog->>User: display pause/resume item
    User->>NavigationDialog: select ACTION_PAUSE_RESUME
    NavigationDialog->>XServerScreen: notify ACTION_PAUSE_RESUME
    XServerScreen->>PluviaApp: set/clear isManuallyPaused
    XServerScreen->>MainActivity: request lifecycle pause/resume
    MainActivity->>XEnvironment: call onPause/onResume
    XEnvironment->>PulseAudioComponent: pause()/resume()
    XEnvironment->>ALSAServerComponent: pause()/resume()
    XServerScreen->>XServerScreen: toggle isGamePaused / render overlay
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Poem

🐰
I nibble keys and hush the play,
A pause to rest mid-hop and sway.
White icons gleam, the screen grows calm,
I’ll bound again when code says “resume.”

🚥 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 'feat: add manual pause for running game' clearly and concisely summarizes the main change: adding a manual pause feature for paused games.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 7 files

Copy link
Contributor

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Around line 1144-1168: The pause overlay currently hardcodes visible strings;
replace the literal "PAUSED" and the Icon contentDescription "Game Paused" with
string resources (add e.g. <string name="game_paused">Paused</string>) and use
androidx.compose.ui.res.stringResource(R.string.game_paused) in XServerScreen.kt
where the Text and Icon contentDescription are set (look for isGamePaused block
and the composables Icon and Text) so UI text is localized and accessible.
- Line 329: The local mutable state isGamePaused is reinitialized to false on
recomposition causing UI desync with the global PluviaApp.isManuallyPaused;
replace its initializer with a saveable one seeded from
PluviaApp.isManuallyPaused (use rememberSaveable {
mutableStateOf(PluviaApp.isManuallyPaused) }) and ensure any toggles of
isGamePaused also update PluviaApp.isManuallyPaused so the global and UI stay in
sync (change references to isGamePaused in XServerScreen to maintain this
two‑way update).

var showElementEditor by remember { mutableStateOf(false) }
var elementToEdit by remember { mutableStateOf<com.winlator.inputcontrols.ControlElement?>(null) }
var showPhysicalControllerDialog by remember { mutableStateOf(false) }
var isGamePaused by remember { mutableStateOf(false) }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Initialize pause state from the global flag to avoid UI desync.

Line 329 resets isGamePaused on recreation, while PluviaApp.isManuallyPaused can remain true, causing the menu/overlay to show the wrong state after config changes. Initialize from the global flag (and make it saveable).

🔧 Proposed fix
-    var isGamePaused by remember { mutableStateOf(false) }
+    var isGamePaused by rememberSaveable { mutableStateOf(PluviaApp.isManuallyPaused) }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` at line
329, The local mutable state isGamePaused is reinitialized to false on
recomposition causing UI desync with the global PluviaApp.isManuallyPaused;
replace its initializer with a saveable one seeded from
PluviaApp.isManuallyPaused (use rememberSaveable {
mutableStateOf(PluviaApp.isManuallyPaused) }) and ensure any toggles of
isGamePaused also update PluviaApp.isManuallyPaused so the global and UI stay in
sync (change references to isGamePaused in XServerScreen to maintain this
two‑way update).

Comment on lines 1144 to 1168
// Pause indicator overlay
if (isGamePaused) {
androidx.compose.foundation.layout.Box(
modifier = Modifier
.fillMaxSize()
.background(androidx.compose.ui.graphics.Color.Black.copy(alpha = 0.5f)),
contentAlignment = Alignment.Center
) {
androidx.compose.foundation.layout.Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
androidx.compose.material3.Icon(
painter = androidx.compose.ui.res.painterResource(R.drawable.icon_pause),
contentDescription = "Game Paused",
modifier = Modifier.size(128.dp),
tint = androidx.compose.ui.graphics.Color.White
)
androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(16.dp))
androidx.compose.material3.Text(
text = "PAUSED",
style = androidx.compose.material3.MaterialTheme.typography.displayMedium,
color = androidx.compose.ui.graphics.Color.White,
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize pause overlay strings.

Line 1157-1164 hardcodes user-facing text; use string resources for i18n/accessibility.

🌐 Proposed fix
-                        contentDescription = "Game Paused",
+                        contentDescription = stringResource(R.string.game_paused),
@@
-                        text = "PAUSED",
+                        text = stringResource(R.string.game_paused),

Add a string resource, e.g.:

<string name="game_paused">Paused</string>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` around
lines 1144 - 1168, The pause overlay currently hardcodes visible strings;
replace the literal "PAUSED" and the Icon contentDescription "Game Paused" with
string resources (add e.g. <string name="game_paused">Paused</string>) and use
androidx.compose.ui.res.stringResource(R.string.game_paused) in XServerScreen.kt
where the Text and Icon contentDescription are set (look for isGamePaused block
and the composables Icon and Text) so UI text is localized and accessible.

Copy link
Contributor

@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.

🧹 Nitpick comments (1)
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt (1)

1144-1171: Unnecessary fully-qualified names clutter the overlay composable.

Most types used here (Box, Column, Spacer, Icon, Text, MaterialTheme, FontWeight, painterResource) are already imported. Only androidx.compose.ui.graphics.Color genuinely conflicts with the existing android.graphics.Color import.

Consider adding an import alias for the Compose Color and using short names throughout:

Proposed fix

Add at the top of the file with the other imports:

import androidx.compose.ui.graphics.Color as ComposeColor

Then simplify the overlay block:

         // Pause indicator overlay
         if (isGamePaused) {
-            androidx.compose.foundation.layout.Box(
+            Box(
                 modifier = Modifier
                     .fillMaxSize()
-                    .background(androidx.compose.ui.graphics.Color.Black.copy(alpha = 0.5f)),
+                    .background(ComposeColor.Black.copy(alpha = 0.5f)),
                 contentAlignment = Alignment.Center
             ) {
-                androidx.compose.foundation.layout.Column(
+                Column(
                     horizontalAlignment = Alignment.CenterHorizontally,
                     verticalArrangement = Arrangement.Center
                 ) {
-                    androidx.compose.material3.Icon(
-                        painter = androidx.compose.ui.res.painterResource(R.drawable.icon_pause),
+                    Icon(
+                        painter = painterResource(R.drawable.icon_pause),
                         contentDescription = stringResource(R.string.game_paused),
                         modifier = Modifier.size(128.dp),
-                        tint = androidx.compose.ui.graphics.Color.White
+                        tint = ComposeColor.White
                     )
-                    androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(16.dp))
-                    androidx.compose.material3.Text(
+                    Spacer(modifier = Modifier.height(16.dp))
+                    Text(
                         text = stringResource(R.string.game_paused).uppercase(),
-                        style = androidx.compose.material3.MaterialTheme.typography.displayMedium,
-                        color = androidx.compose.ui.graphics.Color.White,
-                        fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
+                        style = MaterialTheme.typography.displayMedium,
+                        color = ComposeColor.White,
+                        fontWeight = FontWeight.Bold
                     )
                 }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` around
lines 1144 - 1171, The overlay composable uses unnecessary fully-qualified
names; add an import alias for Compose Color (e.g., import
androidx.compose.ui.graphics.Color as ComposeColor) and then replace occurrences
of androidx.compose.ui.graphics.Color with ComposeColor and simplify other
fully-qualified types to their short names (Box, Column, Spacer, Icon, Text,
MaterialTheme, FontWeight, painterResource) in the isGamePaused block so the
code uses the existing imports and the new ComposeColor alias for color
references.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Line 329: The local mutable state isGamePaused is initialized to false causing
UI desync; change its initializer to use the global PluviaApp.isManuallyPaused
(e.g. var isGamePaused by remember { mutableStateOf(PluviaApp.isManuallyPaused)
}) so recompositions reflect the current global pause state, and if
PluviaApp.isManuallyPaused can change at runtime consider also syncing updates
(e.g. observe/collect or use LaunchedEffect/key on PluviaApp.isManuallyPaused)
to keep isGamePaused in sync with the global value.

---

Nitpick comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Around line 1144-1171: The overlay composable uses unnecessary fully-qualified
names; add an import alias for Compose Color (e.g., import
androidx.compose.ui.graphics.Color as ComposeColor) and then replace occurrences
of androidx.compose.ui.graphics.Color with ComposeColor and simplify other
fully-qualified types to their short names (Box, Column, Spacer, Icon, Text,
MaterialTheme, FontWeight, painterResource) in the isGamePaused block so the
code uses the existing imports and the new ComposeColor alias for color
references.

Copy link
Contributor

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/res/values-pt-rBR/strings.xml`:
- Line 132: The Brazilian Portuguese locale is missing the pause_game and
resume_game string resources, so add translations for the keys pause_game and
resume_game to the strings.xml locale (matching the existing game_paused entry);
specifically add entries for pause_game with "Pausar Jogo" and resume_game with
"Retomar Jogo" so the UI buttons use localized text instead of English.

In `@app/src/main/res/values-ro/strings.xml`:
- Line 197: The Romanian locale is missing translations for the string resources
pause_game and resume_game, causing mixed-language UI; add two new entries
<string name="pause_game">...</string> and <string
name="resume_game">...</string> to the file alongside the existing <string
name="game_paused">Pausat</string>, providing appropriate Romanian translations
(e.g., "Pauză" for pause_game and "Reia" or "Continuă" for resume_game) so the
pause button labels match the localized overlay text.

Copy link
Contributor

@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

🧹 Nitpick comments (2)
app/src/main/res/values-pt-rBR/strings.xml (1)

108-109: Minor capitalization inconsistency with nearby strings.

Line 107 uses "Sair do Jogo" (uppercase J), while these new strings use lowercase "jogo". Consider aligning to "Pausar Jogo" / "Retomar Jogo" for consistency within this section.

Suggested fix
-    <string name="pause_game">Pausar jogo</string>
-    <string name="resume_game">Retomar jogo</string>
+    <string name="pause_game">Pausar Jogo</string>
+    <string name="resume_game">Retomar Jogo</string>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/res/values-pt-rBR/strings.xml` around lines 108 - 109, Update
the two Portuguese strings to match the capitalization convention used nearby:
change the values for string resources pause_game and resume_game to use an
uppercase "J" in "Jogo" (i.e., "Pausar Jogo" and "Retomar Jogo") so they are
consistent with "Sair do Jogo".
app/src/main/res/values-da/strings.xml (1)

134-134: Nit: consider "Pauseret" instead of "Pause" for the overlay state.

The English overlay displays "PAUSED" (a state), while "Pause" is a noun. "Pauseret" would more accurately convey the paused state in Danish, though the current value is understandable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/res/values-da/strings.xml` at line 134, Update the string
resource named "game_paused" so the Danish overlay shows the paused state
correctly by replacing the current value "Pause" with "Pauseret"; modify the
<string name="game_paused"> value to "Pauseret" to reflect the state (string
resource identifier: game_paused).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/res/values-ro/strings.xml`:
- Line 199: The string resource named "game_paused" uses the non-standard
Romanian term "Pausat"; update this resource to a more natural phrasing such as
"Pauză" or "În pauză" (to match the existing "pause_game" wording). Locate the
<string name="game_paused"> entry and replace its value with one of the
suggested translations to ensure consistency and standard Romanian usage across
the full-screen overlay.

---

Duplicate comments:
In `@app/src/main/res/values-ro/strings.xml`:
- Around line 173-174: No change required: the translations for pause_game
("Pauză") and resume_game ("Reia") are consistent with existing pause_download
and resume_download entries; keep <string name="pause_game"> and <string
name="resume_game"> as-is and do not modify these resource entries.

---

Nitpick comments:
In `@app/src/main/res/values-da/strings.xml`:
- Line 134: Update the string resource named "game_paused" so the Danish overlay
shows the paused state correctly by replacing the current value "Pause" with
"Pauseret"; modify the <string name="game_paused"> value to "Pauseret" to
reflect the state (string resource identifier: game_paused).

In `@app/src/main/res/values-pt-rBR/strings.xml`:
- Around line 108-109: Update the two Portuguese strings to match the
capitalization convention used nearby: change the values for string resources
pause_game and resume_game to use an uppercase "J" in "Jogo" (i.e., "Pausar
Jogo" and "Retomar Jogo") so they are consistent with "Sair do Jogo".

Copy link
Contributor

@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.

🧹 Nitpick comments (1)
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt (1)

1144-1171: Remove unnecessary fully-qualified names in the pause overlay.

Most types used here (Box, Column, Spacer, Icon, Text, MaterialTheme, painterResource, FontWeight) are already imported at the top of the file. Only androidx.compose.ui.graphics.Color genuinely needs the FQN (since android.graphics.Color is imported on line 5). Consider adding a Compose Color import alias and using short names for the rest.

♻️ Suggested cleanup

Add an import alias at the top:

import androidx.compose.ui.graphics.Color as ComposeColor

Then simplify the overlay:

         // Pause indicator overlay
         if (isGamePaused) {
-            androidx.compose.foundation.layout.Box(
+            Box(
                 modifier = Modifier
                     .fillMaxSize()
-                    .background(androidx.compose.ui.graphics.Color.Black.copy(alpha = 0.5f)),
+                    .background(ComposeColor.Black.copy(alpha = 0.5f)),
                 contentAlignment = Alignment.Center
             ) {
-                androidx.compose.foundation.layout.Column(
+                Column(
                     horizontalAlignment = Alignment.CenterHorizontally,
                     verticalArrangement = Arrangement.Center
                 ) {
-                    androidx.compose.material3.Icon(
-                        painter = androidx.compose.ui.res.painterResource(R.drawable.icon_pause),
+                    Icon(
+                        painter = painterResource(R.drawable.icon_pause),
                         contentDescription = stringResource(R.string.game_paused),
                         modifier = Modifier.size(128.dp),
-                        tint = androidx.compose.ui.graphics.Color.White
+                        tint = ComposeColor.White
                     )
-                    androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(16.dp))
-                    androidx.compose.material3.Text(
+                    Spacer(modifier = Modifier.height(16.dp))
+                    Text(
                         text = stringResource(R.string.game_paused).uppercase(),
-                        style = androidx.compose.material3.MaterialTheme.typography.displayMedium,
-                        color = androidx.compose.ui.graphics.Color.White,
-                        fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
+                        style = MaterialTheme.typography.displayMedium,
+                        color = ComposeColor.White,
+                        fontWeight = FontWeight.Bold
                     )
                 }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` around
lines 1144 - 1171, The pause overlay block using isGamePaused should be cleaned
up to remove unnecessary fully-qualified names: add an import alias like "import
androidx.compose.ui.graphics.Color as ComposeColor" at the top, replace
androidx.compose.ui.graphics.Color with ComposeColor, and shorten usages of Box,
Column, Spacer, Icon, Text, MaterialTheme, painterResource and FontWeight to
their simple names (they are already imported) so the pause overlay code uses
concise identifiers while preserving the existing behavior and tint/color
values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Line 329: No change required: keeping var isGamePaused by remember {
mutableStateOf(PluviaApp.isManuallyPaused) } is acceptable because
PluviaApp.isManuallyPaused is a static field and config changes are already
handled; if you do want state to survive process death, replace remember with
rememberSaveable for isGamePaused, otherwise leave the current usage as-is.

---

Nitpick comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Around line 1144-1171: The pause overlay block using isGamePaused should be
cleaned up to remove unnecessary fully-qualified names: add an import alias like
"import androidx.compose.ui.graphics.Color as ComposeColor" at the top, replace
androidx.compose.ui.graphics.Color with ComposeColor, and shorten usages of Box,
Column, Spacer, Icon, Text, MaterialTheme, painterResource and FontWeight to
their simple names (they are already imported) so the pause overlay code uses
concise identifiers while preserving the existing behavior and tint/color
values.

Copy link
Contributor

@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

🧹 Nitpick comments (3)
app/src/main/java/com/winlator/xconnector/XConnectorEpoll.java (1)

214-223: Thread-safety: connectedClients is accessed without synchronization.

connectedClients is mutated on the epoll thread (via JNI callbacks like handleNewConnection/killConnection), while these new public accessors will likely be called from the UI thread (for the pause/resume feature). SparseArray is not thread-safe, and there's a TOCTOU gap in getConnectedClientAt between the bounds check on line 219 and valueAt on line 220 — a concurrent removal could shift indices.

This mirrors the existing unsynchronized access pattern in the class, so it's not a regression, but since these methods widen the surface to external callers, consider guarding access with a synchronized block or iterating over a snapshot.

♻️ Suggested synchronized accessors
-    public int getConnectedClientsCount() {
-        return this.connectedClients.size();
-    }
-
-    public Client getConnectedClientAt(int index) {
-        if (index >= 0 && index < this.connectedClients.size()) {
-            return this.connectedClients.valueAt(index);
-        }
-        return null;
-    }
+    public synchronized int getConnectedClientsCount() {
+        return this.connectedClients.size();
+    }
+
+    public synchronized Client getConnectedClientAt(int index) {
+        if (index >= 0 && index < this.connectedClients.size()) {
+            return this.connectedClients.valueAt(index);
+        }
+        return null;
+    }

Note: this only helps if the mutation sites (handleNewConnection, killConnection) are also synchronized on the same monitor, which they currently are not. A broader thread-safety pass on connectedClients access would be the complete fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/winlator/xconnector/XConnectorEpoll.java` around lines
214 - 223, getConnectedClientsCount() and getConnectedClientAt(int) access the
shared SparseArray connectedClients without synchronization, causing TOCTOU and
race issues with mutations from JNI callbacks like handleNewConnection and
killConnection; fix by guarding reads with the same monitor used for mutations
(wrap body of getConnectedClientsCount and getConnectedClientAt in
synchronized(this) or a dedicated lock) or by returning/iterating over a shallow
snapshot (clone or copy of connectedClients) to avoid index shifts—ensure you
also apply the same synchronization to mutation sites
(handleNewConnection/killConnection) so all access uses the same lock.
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt (2)

1144-1171: Use short imports instead of fully-qualified names for already-imported composables.

Box, Column, Spacer, Icon, Text, MaterialTheme, Color, painterResource, and FontWeight are all already imported (lines 14, 24, 26, 28, 50-51, etc.), but lines 1146-1167 use fully-qualified references like androidx.compose.foundation.layout.Box, androidx.compose.ui.graphics.Color, etc. This hurts readability.

♻️ Suggested cleanup
-        if (isGamePaused) {
-            androidx.compose.foundation.layout.Box(
-                modifier = Modifier
-                    .fillMaxSize()
-                    .background(androidx.compose.ui.graphics.Color.Black.copy(alpha = 0.5f)),
-                contentAlignment = Alignment.Center
-            ) {
-                androidx.compose.foundation.layout.Column(
-                    horizontalAlignment = Alignment.CenterHorizontally,
-                    verticalArrangement = Arrangement.Center
-                ) {
-                    androidx.compose.material3.Icon(
-                        painter = androidx.compose.ui.res.painterResource(R.drawable.icon_pause),
-                        contentDescription = stringResource(R.string.game_paused),
-                        modifier = Modifier.size(128.dp),
-                        tint = androidx.compose.ui.graphics.Color.White
-                    )
-                    androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(16.dp))
-                    androidx.compose.material3.Text(
-                        text = stringResource(R.string.game_paused).uppercase(),
-                        style = androidx.compose.material3.MaterialTheme.typography.displayMedium,
-                        color = androidx.compose.ui.graphics.Color.White,
-                        fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
-                    )
-                }
-            }
-        }
+        if (isGamePaused) {
+            Box(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(androidx.compose.ui.graphics.Color.Black.copy(alpha = 0.5f)),
+                contentAlignment = Alignment.Center
+            ) {
+                Column(
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                    verticalArrangement = Arrangement.Center
+                ) {
+                    Icon(
+                        painter = painterResource(R.drawable.icon_pause),
+                        contentDescription = stringResource(R.string.game_paused),
+                        modifier = Modifier.size(128.dp),
+                        tint = androidx.compose.ui.graphics.Color.White
+                    )
+                    Spacer(modifier = Modifier.height(16.dp))
+                    Text(
+                        text = stringResource(R.string.game_paused).uppercase(),
+                        style = MaterialTheme.typography.displayMedium,
+                        color = androidx.compose.ui.graphics.Color.White,
+                        fontWeight = FontWeight.Bold
+                    )
+                }
+            }
+        }

Note: For Color.Black / Color.White, you'd need to add import androidx.compose.ui.graphics.Color (or alias it) to avoid shadowing with android.graphics.Color already imported on line 5. Alternatively, keep the FQN only for Color.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` around
lines 1144 - 1171, Replace the fully-qualified Compose references in the pause
overlay with the short imported names (use Box, Column, Spacer, Icon, Text,
MaterialTheme, painterResource, and FontWeight) so the composable code is more
readable; also add an import for androidx.compose.ui.graphics.Color (or alias
it) to avoid shadowing with android.graphics.Color, then update
Color.Black/Color.White usages accordingly while keeping painterResource(...)
and other existing identifiers unchanged.

2295-2300: Exit cleanup: unconditional onResume() before stopEnvironmentComponents() is safe but could be guarded.

Line 2296 calls environment?.onResume() regardless of whether the game was actually paused. While this is likely harmless (resuming already-running processes should be a no-op), adding a guard makes the intent explicit and avoids sending unnecessary SIGCONT signals to processes.

♻️ Optional: guard the resume call
     winHandler?.stop()
     // Resume any paused processes before stopping to ensure clean shutdown
-    environment?.onResume()
+    if (PluviaApp.isManuallyPaused) {
+        environment?.onResume()
+    }
     environment?.stopEnvironmentComponents()
     SteamService.keepAlive = false
     // Clear manual pause state on exit
     PluviaApp.isManuallyPaused = false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` around
lines 2295 - 2300, The unconditional call to environment?.onResume() should be
guarded so we only resume if the environment is actually paused; update the exit
cleanup to check the environment's paused state (e.g., environment?.isPaused or
a similar property/method on the Environment class) before calling
environment?.onResume(), then proceed to
environment?.stopEnvironmentComponents() and the existing SteamService/PluviaApp
cleanup; this makes intent explicit and avoids sending SIGCONT when not needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/com/winlator/xenvironment/components/ALSAServerComponent.java`:
- Around line 47-71: The iteration over XConnectorEpoll in
ALSAServerComponent.pause() and resume() can miss clients if the connected
client list changes during the loop; fix both methods by first snapshotting the
current clients into a local List<ALSAClient> (call getConnectedClientsCount()
once, loop i=0..count-1 calling getConnectedClientAt(i) and collecting non-null
client.getTag() instances that are ALSAClient), then iterate that local list to
call ALSAClient.pause() or ALSAClient.start(); also add a short clarifying
comment in resume() that ALSAClient.start() is intentionally the counterpart to
ALSAServerComponent.resume() (or consider renaming resume() if you prefer naming
symmetry) so future maintainers understand the mapping.

---

Duplicate comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Line 329: The initialization of the composable state variable isGamePaused
using remember { mutableStateOf(PluviaApp.isManuallyPaused) } is correct and
keeps the UI in sync with the global flag across activity recreation; no code
changes required—leave the isGamePaused declaration as-is in XServerScreen
(remember/mutableStateOf with PluviaApp.isManuallyPaused).
- Around line 1158-1164: The same stringResource(R.string.game_paused) is being
called twice for contentDescription and Text; cache it once in a local val
(e.g., val paused = stringResource(R.string.game_paused)) in XServerScreen so
both contentDescription and the Text use that value, and when uppercasing call
paused.uppercase(Locale.current) to respect locale; update the references for
the Image contentDescription and the androidx.compose.material3.Text to use the
cached val.

---

Nitpick comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Around line 1144-1171: Replace the fully-qualified Compose references in the
pause overlay with the short imported names (use Box, Column, Spacer, Icon,
Text, MaterialTheme, painterResource, and FontWeight) so the composable code is
more readable; also add an import for androidx.compose.ui.graphics.Color (or
alias it) to avoid shadowing with android.graphics.Color, then update
Color.Black/Color.White usages accordingly while keeping painterResource(...)
and other existing identifiers unchanged.
- Around line 2295-2300: The unconditional call to environment?.onResume()
should be guarded so we only resume if the environment is actually paused;
update the exit cleanup to check the environment's paused state (e.g.,
environment?.isPaused or a similar property/method on the Environment class)
before calling environment?.onResume(), then proceed to
environment?.stopEnvironmentComponents() and the existing SteamService/PluviaApp
cleanup; this makes intent explicit and avoids sending SIGCONT when not needed.

In `@app/src/main/java/com/winlator/xconnector/XConnectorEpoll.java`:
- Around line 214-223: getConnectedClientsCount() and getConnectedClientAt(int)
access the shared SparseArray connectedClients without synchronization, causing
TOCTOU and race issues with mutations from JNI callbacks like
handleNewConnection and killConnection; fix by guarding reads with the same
monitor used for mutations (wrap body of getConnectedClientsCount and
getConnectedClientAt in synchronized(this) or a dedicated lock) or by
returning/iterating over a shallow snapshot (clone or copy of connectedClients)
to avoid index shifts—ensure you also apply the same synchronization to mutation
sites (handleNewConnection/killConnection) so all access uses the same lock.

Comment on lines +47 to +71
public void pause() {
XConnectorEpoll xConnectorEpoll = this.connector;
if (xConnectorEpoll != null) {
// Pause all connected ALSA clients
for (int i = 0; i < xConnectorEpoll.getConnectedClientsCount(); i++) {
com.winlator.xconnector.Client client = xConnectorEpoll.getConnectedClientAt(i);
if (client != null && client.getTag() instanceof ALSAClient) {
((ALSAClient) client.getTag()).pause();
}
}
}
}

public void resume() {
XConnectorEpoll xConnectorEpoll = this.connector;
if (xConnectorEpoll != null) {
// Resume all connected ALSA clients
for (int i = 0; i < xConnectorEpoll.getConnectedClientsCount(); i++) {
com.winlator.xconnector.Client client = xConnectorEpoll.getConnectedClientAt(i);
if (client != null && client.getTag() instanceof ALSAClient) {
((ALSAClient) client.getTag()).start();
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find ALSAClient class and check for pause/resume/start methods
fd -t f -e java -i alsa | head -20

Repository: utkarshdalal/GameNative

Length of output: 345


🏁 Script executed:

# Search for ALSAClient definition and its methods
rg -n "class ALSAClient" --type java

Repository: utkarshdalal/GameNative

Length of output: 302


🏁 Script executed:

# Check ALSAClient's pause, resume, start methods
rg -n "(void pause|void resume|void start)\(" --type java --glob "*ALSAClient*"

Repository: utkarshdalal/GameNative

Length of output: 242


🏁 Script executed:

# Find XConnectorEpoll and check getConnectedClientAt implementation
rg -n "getConnectedClientAt|getConnectedClientsCount" --type java -A 5 -B 2

Repository: utkarshdalal/GameNative

Length of output: 3582


Concurrent-modification risk during client iteration and asymmetric method naming.

Both pause() and resume() iterate over connected clients by index without synchronization. If a client connects or disconnects concurrently (changing the count between the loop condition check and getConnectedClientAt(i)), entries could be skipped or the loop could terminate prematurely, even though getConnectedClientAt() safely returns null rather than throwing an exception. The null check on lines 53 and 66 prevents crashes but does not prevent incomplete iteration.

Additionally, resume() calls ALSAClient.start() while pause() calls ALSAClient.pause() — note that ALSAClient has no resume() method, only start(). While start() is the correct counterpart, this naming asymmetry between the component's resume() method and the client's start() method could confuse future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/winlator/xenvironment/components/ALSAServerComponent.java`
around lines 47 - 71, The iteration over XConnectorEpoll in
ALSAServerComponent.pause() and resume() can miss clients if the connected
client list changes during the loop; fix both methods by first snapshotting the
current clients into a local List<ALSAClient> (call getConnectedClientsCount()
once, loop i=0..count-1 calling getConnectedClientAt(i) and collecting non-null
client.getTag() instances that are ALSAClient), then iterate that local list to
call ALSAClient.pause() or ALSAClient.start(); also add a short clarifying
comment in resume() that ALSAClient.start() is intentionally the counterpart to
ALSAServerComponent.resume() (or consider renaming resume() if you prefer naming
symmetry) so future maintainers understand the mapping.

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