mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
Merge pull request #50 from Droid-Deck/fix/release-blockers
Fix/release blockers
This commit is contained in:
commit
da0775913f
10 files changed
+94
-30
No files matched your search
@@ -79,8 +79,9 @@ jobs:
|
||||
}
|
||||
stable=$(package_line "$RUNNER_TEMP/stable/DroidDeck-$stable_tag.apk")
|
||||
candidate=$(package_line "${apks[0]}")
|
||||
[[ "$stable" =~ ^com\.droiddeck\.launcher\ [0-9]+$ && "$candidate" == "$stable" ]] || {
|
||||
echo "::error::PR package/versionCode '$candidate' differs from stable '$stable'"; exit 1;
|
||||
[[ "$stable" =~ ^com\.droiddeck\.launcher\ [0-9]+$ && "$candidate" =~ ^com\.droiddeck\.launcher\ [0-9]+$ &&
|
||||
${candidate##* } -ge ${stable##* } ]] || {
|
||||
echo "::error::PR package/versionCode '$candidate' cannot install over stable '$stable'"; exit 1;
|
||||
}
|
||||
|
||||
current=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
|
||||
|
||||
@@ -35,6 +35,10 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ $VERSION =~ ^[0-9A-Za-z._-]+$ ]] || { echo "::error::version '$VERSION': letters, digits, . _ - only"; exit 1; }
|
||||
name=$(sed -n 's/^ *versionName "\(.*\)"$/\1/p' app/build.gradle)
|
||||
[[ $VERSION == "$name" || $VERSION == "$name"-* ]] || {
|
||||
echo "::error::version '$VERSION' does not match versionName '$name' in app/build.gradle: bump versionName and versionCode first"; exit 1;
|
||||
}
|
||||
runs=$(gh run list -R "$GITHUB_REPOSITORY" --workflow build.yml --branch main --commit "$GITHUB_SHA" \
|
||||
--status success --json databaseId,event,headSha -L 20)
|
||||
id=$(jq -r --arg sha "$GITHUB_SHA" \
|
||||
|
||||
+2
-2
@@ -63,8 +63,8 @@ android {
|
||||
// file out of its own data directory, and this app's entire job is to exec a rootfs:
|
||||
// proot, gamescope, the Steam client and everything it launches all live under files/.
|
||||
targetSdkVersion 28
|
||||
versionCode 8
|
||||
versionName "0.1.7"
|
||||
versionCode 9
|
||||
versionName "0.2.0"
|
||||
buildConfigField "String", "BUILD_LABEL", "\"${escapedBuildLabel}\""
|
||||
|
||||
ndk {
|
||||
|
||||
@@ -97,13 +97,11 @@ object HomeApp {
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setComponent(ComponentName(app.packageName, app.className))
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if (displayId == null) {
|
||||
context.startActivity(intent)
|
||||
return
|
||||
}
|
||||
// Keep the target app's task separate from DroidDeck. Without this, Android can put the
|
||||
// app on top of the caller's task and move the entire task to the chosen display.
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
val options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId)
|
||||
context.startActivity(intent, options.toBundle())
|
||||
}
|
||||
|
||||
@@ -264,13 +264,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
handleHomeGuideIntent(intent)
|
||||
|
||||
updateOnScreenControls()
|
||||
WaylandCompositor.setFirstFrameListener {
|
||||
SessionEvents.firstFrame()
|
||||
runOnUiThread {
|
||||
loading.visible = false
|
||||
hud.start()
|
||||
}
|
||||
}
|
||||
WaylandCompositor.setFirstFrameListener(firstFrameListener)
|
||||
SessionEvents.markReadyIfPossible()
|
||||
SessionState.endListener = endListener
|
||||
// A single Back opens the session menu; two quick presses/swipes send the Steam QAM chord.
|
||||
@@ -1537,6 +1531,13 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
|
||||
/** Ours, so a finishing activity never unhooks the one that replaced it. */
|
||||
private val endListener: (Int) -> Unit = { status -> onSessionEnded(status) }
|
||||
private val firstFrameListener = Runnable {
|
||||
SessionEvents.firstFrame()
|
||||
runOnUiThread {
|
||||
loading.visible = false
|
||||
hud.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
// Deliberately does NOT end the session: this activity can be destroyed while the user is
|
||||
@@ -1549,7 +1550,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
if (::hud.isInitialized) hud.stop()
|
||||
padBridge?.stop()
|
||||
if (SessionState.endListener === endListener) SessionState.endListener = null
|
||||
WaylandCompositor.setFirstFrameListener(null)
|
||||
WaylandCompositor.clearFirstFrameListener(firstFrameListener)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,8 @@ public final class LinuxRuntimeInstaller {
|
||||
// runtime that isInstalled() would happily launch.
|
||||
File root = LinuxRuntime.rootDir(context);
|
||||
File staging = new File(root.getParentFile(), LinuxRuntime.DIR + ".new");
|
||||
File old = new File(root.getParentFile(), LinuxRuntime.DIR + ".old");
|
||||
recoverInterruptedSwap(root, staging, old);
|
||||
FileUtils.delete(staging);
|
||||
if (!staging.mkdirs()) return false;
|
||||
if (listener != null) listener.onProgress("Extracting", -1);
|
||||
@@ -219,7 +221,6 @@ public final class LinuxRuntimeInstaller {
|
||||
}
|
||||
FileUtils.writeString(new File(staging, VERSION_FILE), release.version);
|
||||
|
||||
File old = new File(root.getParentFile(), LinuxRuntime.DIR + ".old");
|
||||
FileUtils.delete(old);
|
||||
if (root.isDirectory() && !root.renameTo(old)) {
|
||||
FileUtils.delete(staging);
|
||||
@@ -244,6 +245,8 @@ public final class LinuxRuntimeInstaller {
|
||||
}
|
||||
|
||||
if (!staging.renameTo(root)) {
|
||||
File keptTo = new File(staging, USER_DATA);
|
||||
if (keptTo.isDirectory() && old.isDirectory()) keptTo.renameTo(new File(old, USER_DATA));
|
||||
if (old.isDirectory()) old.renameTo(root);
|
||||
return false;
|
||||
}
|
||||
@@ -257,6 +260,24 @@ public final class LinuxRuntimeInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
private static void recoverInterruptedSwap(File root, File staging, File old) {
|
||||
File target = root.isDirectory() ? root : old;
|
||||
File stagedHome = new File(staging, USER_DATA);
|
||||
File home = new File(target, USER_DATA);
|
||||
if (target.isDirectory() && stagedHome.isDirectory() && (!home.exists() || isEmptyDir(home))) {
|
||||
FileUtils.delete(home);
|
||||
if (stagedHome.renameTo(home)) Log.w(TAG, "recovered " + USER_DATA + " from an interrupted update");
|
||||
}
|
||||
if (!root.isDirectory() && old.isDirectory() && old.renameTo(root)) {
|
||||
Log.w(TAG, "restored the previous runtime after an interrupted update");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEmptyDir(File dir) {
|
||||
String[] names = dir.list();
|
||||
return dir.isDirectory() && names != null && names.length == 0;
|
||||
}
|
||||
|
||||
/** Removes the runtime; returns false without touching it while an install is running. */
|
||||
public static boolean uninstall(Context context) {
|
||||
synchronized (JOB_LOCK) {
|
||||
|
||||
@@ -81,7 +81,7 @@ class PerfHud(context: Context) {
|
||||
|
||||
fun stop() {
|
||||
running = false
|
||||
WaylandCompositor.setGameListener(null)
|
||||
WaylandCompositor.clearGameListener(listener)
|
||||
text = ""
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ import java.util.Locale
|
||||
*/
|
||||
class SessionService : Service() {
|
||||
private val components = java.util.concurrent.CopyOnWriteArrayList<SessionPart>()
|
||||
private val stopLock = Any()
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
private var wifiLock: WifiManager.WifiLock? = null
|
||||
private var sessionPid = -1
|
||||
@@ -84,7 +85,7 @@ class SessionService : Service() {
|
||||
}
|
||||
}
|
||||
/** Counts sessions this service has started; a process exit from an earlier one is ignored. */
|
||||
private var sessionGen = 0
|
||||
@Volatile private var sessionGen = 0
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -392,6 +393,11 @@ class SessionService : Service() {
|
||||
networkLink.attach(this)
|
||||
networkLink.publish()
|
||||
components.add(networkLink)
|
||||
if (gen != sessionGen || !SessionState.running) {
|
||||
Log.i(TAG, "session stopped while it was starting; not launching it")
|
||||
if (gen == sessionGen) components.clear()
|
||||
return
|
||||
}
|
||||
components.forEach { it.start() }
|
||||
|
||||
val line = command.joinToString(" ") {
|
||||
@@ -402,7 +408,7 @@ class SessionService : Service() {
|
||||
// by the teardown a second after the new one has started, and its exit used to arrive
|
||||
// here as "session ended: 137" and end the NEW session. An exit belongs to the session
|
||||
// that started it.
|
||||
sessionPid = HostProcess.start(line, hostEnv.asArray(), root, { status ->
|
||||
val pid = HostProcess.start(line, hostEnv.asArray(), root, { status ->
|
||||
if (gen != sessionGen) {
|
||||
Log.i(TAG, "an earlier session's process ended ($status); the current one carries on")
|
||||
return@start
|
||||
@@ -410,12 +416,24 @@ class SessionService : Service() {
|
||||
Log.i(TAG, "session ended: $status")
|
||||
stopSession(status ?: -1)
|
||||
}, null)
|
||||
Log.i(TAG, "session pid $sessionPid, log ${sessionLog.path}")
|
||||
if (gen == sessionGen && SessionState.running && sessionPid > 1) {
|
||||
SessionEvents.guestStarted(sessionPid)
|
||||
} else if (gen == sessionGen && SessionState.running) {
|
||||
SessionEvents.record("guest.start_failed", mapOf("pid" to sessionPid))
|
||||
SessionEvents.fail("GUEST_START_FAILED", "The guest process could not be started", sessionPid)
|
||||
Log.i(TAG, "session pid $pid, log ${sessionLog.path}")
|
||||
if (gen != sessionGen || !SessionState.running) {
|
||||
Log.i(TAG, "session stopped while its guest was starting; taking it down")
|
||||
if (gen == sessionGen) {
|
||||
launchWatcher?.stopWatching()
|
||||
launchWatcher = null
|
||||
components.reversed().forEach { runCatching { it.stop() } }
|
||||
components.clear()
|
||||
}
|
||||
if (pid > 1) Thread({ teardown(pid) }, "session-teardown").start()
|
||||
return
|
||||
}
|
||||
sessionPid = pid
|
||||
if (pid > 1) {
|
||||
SessionEvents.guestStarted(pid)
|
||||
} else {
|
||||
SessionEvents.record("guest.start_failed", mapOf("pid" to pid))
|
||||
SessionEvents.fail("GUEST_START_FAILED", "The guest process could not be started", pid)
|
||||
stopSession(-1)
|
||||
return
|
||||
}
|
||||
@@ -877,8 +895,12 @@ class SessionService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishSessionStop(status: Int) {
|
||||
private fun finishSessionStop(status: Int, stoppedGen: Int) {
|
||||
mainHandler.post {
|
||||
if (stoppedGen != sessionGen || SessionState.running) {
|
||||
Log.i(TAG, "session $stoppedGen finished stopping after a new one started")
|
||||
return@post
|
||||
}
|
||||
SessionState.suspended = false
|
||||
SessionState.guestPid = -1
|
||||
releaseLocks()
|
||||
@@ -897,8 +919,11 @@ class SessionService : Service() {
|
||||
}
|
||||
|
||||
private fun stopSession(status: Int) {
|
||||
if (!SessionState.running) return
|
||||
SessionState.running = false
|
||||
synchronized(stopLock) {
|
||||
if (!SessionState.running) return
|
||||
SessionState.running = false
|
||||
}
|
||||
val stoppedGen = sessionGen
|
||||
SessionState.stopRequested = false
|
||||
SessionEvents.record("guest.exited", mapOf("status" to status))
|
||||
SessionEvents.transition(SessionPhase.STOPPING, "session.stopping", mapOf("status" to status))
|
||||
@@ -942,10 +967,10 @@ class SessionService : Service() {
|
||||
auxiliary.forEach { (pid, started) -> teardown(pid, started) }
|
||||
if (prootPid > 1) teardown(prootPid)
|
||||
killGuestLeftovers()
|
||||
finishSessionStop(status)
|
||||
finishSessionStop(status, stoppedGen)
|
||||
}, "session-teardown").start()
|
||||
} else {
|
||||
finishSessionStop(status)
|
||||
finishSessionStop(status, stoppedGen)
|
||||
}
|
||||
}
|
||||
if (prootPid > 1) acquireLocks()
|
||||
@@ -1127,6 +1152,10 @@ class SessionService : Service() {
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
if (!SessionState.running) {
|
||||
SessionState.stopRequested = true
|
||||
return
|
||||
}
|
||||
context.startService(Intent(context, SessionService::class.java).setAction(ACTION_STOP))
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ public final class WaylandCompositor {
|
||||
* listener must marshal to the UI thread itself. */
|
||||
public static void setFirstFrameListener(Runnable r) { firstFrameListener = r; }
|
||||
|
||||
public static synchronized void clearFirstFrameListener(Runnable r) {
|
||||
if (firstFrameListener == r) firstFrameListener = null;
|
||||
}
|
||||
|
||||
/** Invoked from native (banner_on_first_frame) on the first present. */
|
||||
@SuppressWarnings("unused")
|
||||
static void onFirstFramePresented() {
|
||||
@@ -52,6 +56,10 @@ public final class WaylandCompositor {
|
||||
|
||||
public static void setGameListener(GameListener l) { gameListener = l; }
|
||||
|
||||
public static synchronized void clearGameListener(GameListener l) {
|
||||
if (gameListener == l) gameListener = null;
|
||||
}
|
||||
|
||||
/** Invoked from native (banner_on_game_surface). */
|
||||
@SuppressWarnings("unused")
|
||||
static void onGameSurface(String window, String gpuName) {
|
||||
|
||||
@@ -591,6 +591,8 @@ BUSCONF
|
||||
wait "$decky_supervisor" 2>/dev/null || true
|
||||
echo "== Decky: session supervisor stopped"
|
||||
fi
|
||||
_bl_jobs=$(jobs -p)
|
||||
[ -n "$_bl_jobs" ] && kill $_bl_jobs 2>/dev/null
|
||||
reap_orphans
|
||||
fi
|
||||
exit "$rc"
|
||||
|
||||
Reference in new issue
Block a user