From c6dd03e061060dd8f02588c35c572f7c9225af45 Mon Sep 17 00:00:00 2001 From: xXJsonDeruloXx Date: Fri, 25 Sep 2026 00:26:58 -0400 Subject: [PATCH 1/2] Add live suspend policy to session drawer --- .../com/droiddeck/launcher/SessionActivity.kt | 9 ++++++++- .../launcher/session/SessionService.kt | 13 +++++++++++++ .../droiddeck/launcher/ui/SessionOverlay.kt | 19 ++++++++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt index 239e3d5f5..3df0052db 100644 --- a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt @@ -135,6 +135,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private var frameGenEngine by mutableStateOf(FrameGen.ENGINE_OFF) private var frameGenMultiplier by mutableStateOf(2) private var fexPreset by mutableStateOf("") + private var suspendPolicy by mutableStateOf(SessionPrefs.SUSPEND_MANUAL) private var oscMode by mutableStateOf(SessionPrefs.OSC_AUTO) private var shapeMode by mutableStateOf(SessionPrefs.SHAPE_AUTO) private var secondScreenMode by mutableStateOf(SecondScreenMode.NONE) @@ -260,7 +261,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { fillScreen = if (SessionState.mode == SessionService.MODE_STEAM) fillScreen else null, frameGenEngine = frameGenEngine, frameGenMultiplier = frameGenMultiplier, lsfgReady = LsfgNative.isInstalled(this@SessionActivity), - oscMode = oscMode, touchMode = touchMode, + oscMode = oscMode, suspendPolicy = suspendPolicy, touchMode = touchMode, touchAuto = if (usingTouchpad()) "touchpad" else "direct", shapeMode = shapeMode, fexPreset = fexPreset, secondScreenMode = secondScreenMode, @@ -283,6 +284,11 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { backActionsInverted = inverted }, onOsc = { v -> SessionPrefs.setOscMode(this@SessionActivity, v); readPrefs(); updateOnScreenControls() }, + onSuspendPolicy = { policy -> + SessionPrefs.setSuspendPolicy(this@SessionActivity, SessionState.mode, policy) + suspendPolicy = policy + SessionService.suspendPolicyChanged(this@SessionActivity) + }, onTouch = { v -> SessionPrefs.setTouchMode(this@SessionActivity, v); readPrefs() }, onShape = { v -> SessionPrefs.setShapeMode(this@SessionActivity, v); readPrefs() }, onFexPreset = { v -> SessionPrefs.setFexPreset(this@SessionActivity, v); readPrefs() }, @@ -391,6 +397,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { frameGenEngine = FrameGen.engine(this) frameGenMultiplier = FrameGen.multiplier(this) fexPreset = SessionPrefs.fexPreset(this) + suspendPolicy = SessionPrefs.suspendPolicy(this, SessionState.mode) oscMode = SessionPrefs.oscMode(this) shapeMode = SessionPrefs.shapeMode(this) backActionsInverted = SessionPrefs.backActionsInverted(this) diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt index 4e69e65ad..9357991ea 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -144,6 +144,13 @@ class SessionService : Service() { updateSuspendPolicy() return START_NOT_STICKY } + ACTION_SUSPEND_POLICY_CHANGED -> { + if (!SessionState.running) return START_NOT_STICKY + suspendPolicy = SessionPrefs.suspendPolicy(this, SessionState.mode) + suspendAttemptFailed = false + updateSuspendPolicy() + return START_NOT_STICKY + } ACTION_RESUME -> { activityVisible = true screenOn = (getSystemService(Context.POWER_SERVICE) as? PowerManager)?.isInteractive ?: screenOn @@ -1016,6 +1023,7 @@ class SessionService : Service() { const val ACTION_RESUME = "com.droiddeck.launcher.RESUME_SESSION" const val ACTION_HOME_GUIDE = "com.droiddeck.launcher.HOME_GUIDE" const val ACTION_AGENT_START = "com.droiddeck.launcher.AGENT_START" + private const val ACTION_SUSPEND_POLICY_CHANGED = "com.droiddeck.launcher.SUSPEND_POLICY_CHANGED" private const val ACTION_ACTIVITY_VISIBLE = "com.droiddeck.launcher.ACTIVITY_VISIBLE" private const val ACTION_ACTIVITY_HIDDEN = "com.droiddeck.launcher.ACTIVITY_HIDDEN" private const val ACTION_TRACK_AUXILIARY = "com.droiddeck.launcher.TRACK_AUXILIARY" @@ -1070,6 +1078,11 @@ class SessionService : Service() { context.startService(Intent(context, SessionService::class.java).setAction(ACTION_RESUME)) } + fun suspendPolicyChanged(context: Context) { + if (!SessionState.running) return + context.startService(Intent(context, SessionService::class.java).setAction(ACTION_SUSPEND_POLICY_CHANGED)) + } + /** Let the session service clean up the PTY's proot tree if the control screen closes. */ fun stopTerminalProcess(context: Context, pid: Int) { if (pid > 1) context.startService( diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt index db5d83889..fc949bda6 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt @@ -87,7 +87,7 @@ import com.droiddeck.launcher.input.SecondScreenMode import kotlinx.coroutines.flow.collect private val drawerPageTitles = listOf("Now", "Controls", "Session") -private val drawerPageEntries = listOf("hud", "touch", "shape") +private val drawerPageEntries = listOf("hud", "touch", "suspend") private class DrawerFocus { private val requesters = HashMap() @@ -194,6 +194,7 @@ class DrawerActions( val frameGenMultiplier: Int, val lsfgReady: Boolean, val oscMode: String, + val suspendPolicy: String, val backActionsInverted: Boolean, val touchMode: String, val touchAuto: String, @@ -213,6 +214,7 @@ class DrawerActions( val onSteamMenu: (() -> Unit)?, val onQam: (() -> Unit)?, val onOsc: (String) -> Unit, + val onSuspendPolicy: (String) -> Unit, val onBackActionsInverted: (Boolean) -> Unit, val onTouch: (String) -> Unit, val onShape: (String) -> Unit, @@ -475,6 +477,21 @@ fun SessionDrawer(open: Boolean, page: Int, controllerActive: Boolean, onPageCha } } } + SettingsGroup("Session behavior") { + ChoiceRow( + host, "suspend", "Background behavior", + "Applies now and to future sessions in this mode.", + listOf( + SessionPrefs.SUSPEND_AUTO to "Auto", + SessionPrefs.SUSPEND_MANUAL to "Manual", + SessionPrefs.SUSPEND_NEVER to "Never", + ), + a.suspendPolicy, + note = "Auto pauses in the background and resumes when visible. Manual pauses there and waits for Resume. Never keeps the session running.", + chipModifier = focus.track(page, "suspend"), + onPick = a.onSuspendPolicy, + ) + } SettingsGroup("Next session") { ChoiceRow(host, "shape", "Screen ratio", null, SessionPrefs.shapeChoices, a.shapeMode, From d2ebb7709eac8e75bfe60af86bc1a5992194e253 Mon Sep 17 00:00:00 2001 From: xXJsonDeruloXx Date: Fri, 25 Sep 2026 00:48:03 -0400 Subject: [PATCH 2/2] Set PRoot guest hostname to DroidDeck --- .../com/droiddeck/launcher/runtime/LinuxRuntime.java | 11 +++++++++++ .../com/droiddeck/launcher/session/SessionService.kt | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java index b0d9adb89..816d7c3ad 100644 --- a/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java +++ b/app/src/main/java/com/droiddeck/launcher/runtime/LinuxRuntime.java @@ -5,6 +5,7 @@ import android.os.Process; import android.system.ErrnoException; import android.system.Os; import android.system.StructStat; +import android.system.StructUtsname; import java.io.File; @@ -32,6 +33,7 @@ public final class LinuxRuntime { /** Shortcut extra naming which of the modes above a Linux entry launches. */ public static final String EXTRA_LINUX_MODE = "linux_mode"; private static final String KGSL_DEVICE = "/dev/kgsl-3d0"; + private static final String GUEST_HOSTNAME = "DroidDeck"; /** Where every Linux session's debug log lands: public, so a user can just hand the folder over. */ public static final String DEBUG_LOG_DIR = "DroidDeck"; @@ -116,6 +118,8 @@ public final class LinuxRuntime { List cmd = new ArrayList<>(); cmd.add(prootBinary(context).getPath()); cmd.add("--kill-on-exit"); + // Preserve the host kernel identity while giving the guest the app's branded host name. + cmd.add("--kernel-release=" + guestUtsname()); // Android's app seccomp policy traps the whole set*id family. Xwayland's Popen() calls // setgid()/setuid() before it execs xkbcomp and _exit(127)s when they fail, so without // this the keymap never compiles and Xwayland dies. -i makes proot answer those calls @@ -245,6 +249,13 @@ public final class LinuxRuntime { cmd.add(spec); } + /** PRoot's complex -k format: sysname, nodename, release, version, machine, domain, HWCAP. */ + private static String guestUtsname() { + StructUtsname host = Os.uname(); + return "\\" + host.sysname + "\\" + GUEST_HOSTNAME + "\\" + host.release + + "\\" + host.version + "\\" + host.machine + "\\localdomain\\-1\\"; + } + /** X access control and Steam look the session user up by uid: the app uid is root inside. */ public static void writeAccounts(Context context) throws IOException { File root = rootDir(context); diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt index 9357991ea..11b87b05b 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -601,7 +601,9 @@ class SessionService : Service() { components.add(networkLink) components.forEach { it.start() } - val line = command.joinToString(" ") { it.replace(" ", "\\ ") } + val line = command.joinToString(" ") { + it.replace("\\", "\\\\").replace(" ", "\\ ") + } watchLaunchRequests(sessionRoot) // One session replacing another (the desktop's Steam launchers): the old proot is killed // by the teardown a second after the new one has started, and its exit used to arrive