Merge branch 'main' into feat/hamburger-rail

This commit is contained in:
Kurt Himebauch authored and GitHub committed 2026-09-25 01:25:28 -04:00
commit 60a3ff8372
4 files changed
+53 -3

No files matched your search

@@ -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)
@@ -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<String> 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);
@@ -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
@@ -594,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
@@ -1016,6 +1025,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 +1080,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(
@@ -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<String, FocusRequester>()
@@ -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,