Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions app/src/main/java/app/gamenative/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ class MainActivity : ComponentActivity() {
)
super.onCreate(savedInstanceState)

// stale keepAlive from a prior crash/swipe — no container is actually running
if (SteamService.keepAlive && PluviaApp.xEnvironment == null) {
Timber.w("onCreate: clearing stale keepAlive — no container running")
PluviaApp.shutdownEnvironment()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Apply immersive mode based on user preference
applyImmersiveMode()

Expand Down Expand Up @@ -265,9 +271,20 @@ class MainActivity : ComponentActivity() {
}

override fun onDestroy() {
super.onDestroy()
// emit before super so Compose DisposableEffects (which unregister
// listeners during super.onDestroy's lifecycle transition) still fire
if (!isChangingConfigurations) {
PluviaApp.events.emit(AndroidEvent.ActivityDestroyed)

// if exit() didn't run (listener already unregistered, race, etc.)
// force-clear so the app isn't stuck on next launch
if (SteamService.keepAlive) {
Timber.w("onDestroy: keepAlive still set after ActivityDestroyed — forcing cleanup")
PluviaApp.shutdownEnvironment()
}
Comment thread
jeremybernstein marked this conversation as resolved.
}

PluviaApp.events.emit(AndroidEvent.ActivityDestroyed)
super.onDestroy()
Comment thread
coderabbitai[bot] marked this conversation as resolved.

PluviaApp.events.off<AndroidEvent.SetSystemUIVisibility, Unit>(onSetSystemUi)
PluviaApp.events.off<AndroidEvent.StartOrientator, Unit>(onStartOrientator)
Expand Down
30 changes: 29 additions & 1 deletion app/src/main/java/app/gamenative/PluviaApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import app.gamenative.db.dao.GOGGameDao
import app.gamenative.events.AndroidEvent
import app.gamenative.events.EventDispatcher
import app.gamenative.service.DownloadService
import app.gamenative.service.SteamService
import app.gamenative.utils.ContainerMigrator
import app.gamenative.utils.IntentLaunchManager
import app.gamenative.utils.PlayIntegrity
Expand All @@ -28,13 +29,13 @@ import com.winlator.widget.InputControlsView
import com.winlator.widget.TouchpadView
import com.winlator.widget.XServerView
import com.winlator.xenvironment.XEnvironment
import timber.log.Timber
import dagger.hilt.android.HiltAndroidApp

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import timber.log.Timber

typealias NavChangedListener = NavController.OnDestinationChangedListener

Expand Down Expand Up @@ -200,6 +201,33 @@ class PluviaApp : SplitCompatApplication() {
hasInitializedSuspendPolicyState = true
}

/**
* full environment teardown — shared by XServerScreen.exit() and
* MainActivity.onDestroy fallback so both paths clean up identically
*/
fun shutdownEnvironment() {
val env = xEnvironment
Timber.i("shutdownEnvironment: env=%s", env != null)

// per-step catch so one failing teardown doesn't prevent the rest from running
runCatching { achievementWatcher?.stop() }
.onFailure { Timber.e(it, "shutdownEnvironment: achievementWatcher.stop") }
runCatching { SteamService.clearCachedAchievements() }
.onFailure { Timber.e(it, "shutdownEnvironment: clearCachedAchievements") }
runCatching { touchpadView?.releasePointerCapture() }
.onFailure { Timber.e(it, "shutdownEnvironment: releasePointerCapture") }
runCatching { env?.stopEnvironmentComponents() }
.onFailure { Timber.e(it, "shutdownEnvironment: stopEnvironmentComponents") }

xEnvironment = null
inputControlsView = null
inputControlsManager = null
touchpadView = null
achievementWatcher = null
SteamService.keepAlive = false
clearActiveSuspendState()
}

fun clearActiveSuspendState() {
activeSuspendPolicy = Container.SUSPEND_POLICY_MANUAL
isOverlayPaused = false
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/app/gamenative/service/SteamService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3224,6 +3224,16 @@ class SteamService : Service(), IChallengeUrlChanged {
scope.launch { stop() }
}

override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
if (!hasActiveOperations()) {
Timber.i("Task removed and no active work — stopping service")
stopSelf()
} else {
Timber.i("Task removed but active work exists — keeping service alive")
}
}

override fun onBind(intent: Intent?): IBinder? = null

private fun connectToSteam() {
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/app/gamenative/service/epic/EpicService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -642,5 +642,15 @@ class EpicService : Service() {
instance = null
}

override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
if (!hasActiveOperations()) {
Timber.tag("Epic").i("Task removed and no active work — stopping service")
stopSelf()
} else {
Timber.tag("Epic").i("Task removed but active work exists — keeping service alive")
}
}

override fun onBind(intent: Intent?): IBinder? = null
}
12 changes: 11 additions & 1 deletion app/src/main/java/app/gamenative/service/gog/GOGService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ class GOGService : Service() {
// ==========================================================================

fun hasActiveOperations(): Boolean {
return syncInProgress || backgroundSyncJob?.isActive == true
return syncInProgress || backgroundSyncJob?.isActive == true || hasActiveDownload()
}

private fun setSyncInProgress(inProgress: Boolean) {
Expand Down Expand Up @@ -695,5 +695,15 @@ class GOGService : Service() {
instance = null
}

override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
if (!hasActiveOperations()) {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Timber.tag("GOG").i("Task removed and no active work — stopping service")
stopSelf()
} else {
Timber.tag("GOG").i("Task removed but active work exists — keeping service alive")
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

override fun onBind(intent: Intent?): IBinder? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,6 @@ fun XServerScreen(
withContext(Dispatchers.Main) {
exit(
winHandler,
PluviaApp.xEnvironment,
frameRating,
currentAppInfo,
container,
Expand Down Expand Up @@ -949,7 +948,7 @@ fun XServerScreen(
imeInputReceiver?.hideKeyboard()
// Resume processes before exiting so they can receive SIGTERM cleanly.
forceResumeIfSuspended()
exit(xServerView!!.getxServer().winHandler, PluviaApp.xEnvironment, frameRating, currentAppInfo, container, appId, onExit, navigateBack)
exit(xServerView!!.getxServer().winHandler, frameRating, currentAppInfo, container, appId, onExit, navigateBack)
true
}

Expand Down Expand Up @@ -1051,7 +1050,7 @@ fun XServerScreen(
DisposableEffect(lifecycleOwner, container) {
val onActivityDestroyed: (AndroidEvent.ActivityDestroyed) -> Unit = {
Timber.i("onActivityDestroyed")
exit(xServerView!!.getxServer().winHandler, PluviaApp.xEnvironment, frameRating, currentAppInfo, container, appId, onExit, navigateBack)
exit(xServerView!!.getxServer().winHandler, frameRating, currentAppInfo, container, appId, onExit, navigateBack)
}
val onKeyEvent: (AndroidEvent.KeyEvent) -> Boolean = {
val isKeyboard = Keyboard.isKeyboardDevice(it.event.device)
Expand Down Expand Up @@ -1151,11 +1150,11 @@ fun XServerScreen(
}
val onGuestProgramTerminated: (AndroidEvent.GuestProgramTerminated) -> Unit = {
Timber.i("onGuestProgramTerminated")
exit(xServerView!!.getxServer().winHandler, PluviaApp.xEnvironment, frameRating, currentAppInfo, container, appId, onExit, navigateBack)
exit(xServerView!!.getxServer().winHandler, frameRating, currentAppInfo, container, appId, onExit, navigateBack)
}
val onForceCloseApp: (SteamEvent.ForceCloseApp) -> Unit = {
Timber.i("onForceCloseApp")
exit(xServerView!!.getxServer().winHandler, PluviaApp.xEnvironment, frameRating, currentAppInfo, container, appId, onExit, navigateBack)
exit(xServerView!!.getxServer().winHandler, frameRating, currentAppInfo, container, appId, onExit, navigateBack)
}
val debugCallback = Callback<String> { outputLine ->
Timber.i(outputLine ?: "")
Expand Down Expand Up @@ -3359,7 +3358,6 @@ private fun getSteamlessTarget(

private fun exit(
winHandler: WinHandler?,
environment: XEnvironment?,
frameRating: FrameRating?,
appInfo: SteamApp?,
container: Container,
Expand Down Expand Up @@ -3392,15 +3390,13 @@ private fun exit(
container.saveData()
}

PluviaApp.achievementWatcher?.stop()
PluviaApp.achievementWatcher = null
SteamService.clearCachedAchievements()

PluviaApp.touchpadView?.releasePointerCapture()
winHandler?.stop()
environment?.stopEnvironmentComponents()
SteamService.keepAlive = false
PluviaApp.clearActiveSuspendState()
// only needed in exit() — OS reclaims on process death, so onDestroy fallback skips this
try {
winHandler?.stop()
} catch (e: Exception) {
Timber.e(e, "winHandler.stop() failed during exit")
}
PluviaApp.shutdownEnvironment()
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// empty Wine/XDG trash in background after container stops
CoroutineScope(Dispatchers.IO).launch {
Expand All @@ -3420,16 +3416,6 @@ private fun exit(
Timber.e(e, "Error emptying Wine/XDG trash")
}
}
// AppUtils.restartApplication(this)
// PluviaApp.xServerState = null
// PluviaApp.xServer = null
// PluviaApp.xServerView = null
PluviaApp.xEnvironment = null
PluviaApp.inputControlsView = null
PluviaApp.inputControlsManager = null
PluviaApp.touchpadView = null
// PluviaApp.touchMouse = null
// PluviaApp.keyboard = null
frameRating?.writeSessionSummary()

if (MainActivity.wasLaunchedViaExternalIntent) {
Expand Down
Loading