diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 870c6b5f2..7f443c710 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,3 +62,59 @@ jobs: label: ${{ inputs.version }} artifact: droiddeck-release secrets: inherit + + # The release is made here, from the apks this run signed, so it traces back to its Build APK run. + publish: + name: publish the release + needs: [find, sign] + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: actions/download-artifact@v4 + with: + pattern: droiddeck-release-* + merge-multiple: true + path: out + + - name: Publish + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ inputs.version }} + BUILD_RUN: ${{ needs.find.outputs.run_id }} + run: | + set -euo pipefail + notes="docs/releases/$VERSION.md" + [ -f "$notes" ] || { echo "::error::$notes is missing: add the release notes first"; exit 1; } + if gh release view "$VERSION" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo "::error::release $VERSION already exists"; exit 1 + fi + # The notes' first line is the title; media paths become raw URLs at this release's tag. + title=$(sed -n '1s/^# *//p' "$notes") + [ -n "$title" ] || title="DroidDeck $VERSION" + raw="https://raw.githubusercontent.com/$GITHUB_REPOSITORY/$VERSION/docs/releases/media/" + { + sed '1{/^# /d;}' "$notes" | sed -E "s#(\]\(|src=\")media/#\1$raw#g" + echo + echo "---" + echo + echo "Built by [Build APK run $BUILD_RUN]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$BUILD_RUN) from \`$GITHUB_SHA\`, signed and published by [Release APKs run $GITHUB_RUN_ID]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID)." + } > "$RUNNER_TEMP/body.md" + ( cd out && cat SHA256SUMS-*.txt | sort -k2 > SHA256SUMS.txt && rm SHA256SUMS-*.txt && cat SHA256SUMS.txt ) + gh release create "$VERSION" -R "$GITHUB_REPOSITORY" out/*.apk out/SHA256SUMS.txt \ + --target "$GITHUB_SHA" --title "$title" --notes-file "$RUNNER_TEMP/body.md" --latest + echo "Published [$title]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$VERSION)" >> "$GITHUB_STEP_SUMMARY" + + # A release made with this run's token fires no release event, so the in-app catalog is refreshed here. + catalog: + needs: publish + uses: ./.github/workflows/update-catalog.yml + permissions: + contents: read + pull-requests: read + secrets: inherit diff --git a/app/build.gradle b/app/build.gradle index c63c8e7bf..025b1740a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -79,8 +79,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 9 - versionName "0.2.0" + versionCode 10 + versionName "0.3.0" buildConfigField "String", "BUILD_LABEL", "\"${escapedBuildLabel}\"" buildConfigField "String", "SOURCE_COMMIT", "\"${sourceCommit}\"" buildConfigField "int", "PR_NUMBER", "${prNumber}" diff --git a/app/src/main/java/com/droiddeck/launcher/App.kt b/app/src/main/java/com/droiddeck/launcher/App.kt index 77551f925..2476bcc4a 100644 --- a/app/src/main/java/com/droiddeck/launcher/App.kt +++ b/app/src/main/java/com/droiddeck/launcher/App.kt @@ -14,7 +14,7 @@ class App : Application() { override fun onCreate() { super.onCreate() CrashHandler.install(this) - // Before anything else touches the session prefs: a new install's first read decides it. + // Before anything else touches the session prefs, so the first session already runs in Deck mode. com.droiddeck.launcher.session.SessionPrefs.settleDeckModeDefault(this) com.droiddeck.launcher.session.SessionPrefs.settleGpuDriverMode(this) com.droiddeck.launcher.update.AppUpdates.init(this) diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt index 6c69bdb7e..9a6c12e09 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt @@ -357,21 +357,20 @@ object SessionPrefs { prefs(context).getString("steamController", CONTROLLER_DECK) ?: CONTROLLER_DECK fun setSteamController(context: Context, id: String) { prefs(context).edit().putString("steamController", id).apply() } - /** Runs the SteamOS gamepad client with its Quick Access performance controls. On for new installs (settleDeckModeDefault). */ + /** Runs the SteamOS gamepad client with its Quick Access performance controls. On by default (settleDeckModeDefault). */ fun steamDeckMode(context: Context): Boolean = prefs(context).getBoolean("steamDeckMode", true) fun setSteamDeckMode(context: Context, on: Boolean) { prefs(context).edit().putBoolean("steamDeckMode", on).apply() } /** - * Deck mode became the default for new installs; an install from before keeps what it ran with - * (off), so an update never changes its interface or restarts the client on its own. Run once at - * process start, before anything reads or writes these prefs: a new install has neither prefs - * nor a runtime yet. The answer is written down, so it is decided once. + * Deck mode is the default: every install, new or from before, is moved to it once. An install + * from before 0.3.0 had off written down for it whether or not anyone chose it, so the move + * can't tell a choice from that default and moves everyone; turning it off afterwards sticks. + * Run once at process start, before anything reads or writes these prefs. */ fun settleDeckModeDefault(context: Context) { val p = prefs(context) - if (p.contains("steamDeckMode")) return - val existing = p.all.isNotEmpty() || java.io.File(context.filesDir, "linuxfs").exists() - p.edit().putBoolean("steamDeckMode", !existing).apply() + if (p.getBoolean("deckModeMoved", false)) return + p.edit().putBoolean("steamDeckMode", true).putBoolean("deckModeMoved", true).apply() } /** diff --git a/app/src/main/java/com/droiddeck/launcher/ui/UpdatesPage.kt b/app/src/main/java/com/droiddeck/launcher/ui/UpdatesPage.kt index 5adf3c624..de4363430 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/UpdatesPage.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/UpdatesPage.kt @@ -156,6 +156,7 @@ private fun StatusPanel(s: FrontEndState, u: UpdatesState, ua: UpdatesActions, m !me.updatable -> Look(colors.onSurfaceVariant, stringResource(R.string.upd_signed_status), stringResource(R.string.upd_signed_headline), stringResource(R.string.upd_signed_detail)) catalog == null -> Look(colors.onSurfaceVariant, if (u.checking) stringResource(R.string.common_checking) else stringResource(R.string.upd_not_checked), stringResource(R.string.upd_title), null) + offered && installBlock != null -> Look(colors.onSurfaceVariant, stringResource(R.string.upd_blocked_status), newBuild(u.follow, release!!), null) offer == Offer.UPDATE -> Look(AttentionAmber, stringResource(R.string.upd_available), newBuild(u.follow, release!!), null) offer == Offer.SWITCH -> Look(pal.signal, stringResource(R.string.upd_switch), newBuild(u.follow, release!!), null) offer == Offer.AHEAD -> Look(pal.signal, stringResource(R.string.upd_ahead_status), stringResource(R.string.upd_ahead_headline), @@ -177,7 +178,7 @@ private fun StatusPanel(s: FrontEndState, u: UpdatesState, ua: UpdatesActions, m ReleaseNotes(changeTitle(release!!.title.ifBlank { release.tag }), release.summary, release.tag) } if (look.detail != null) Text(look.detail, fontSize = 14.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(top = 10.dp)) - if (installBlock != null && (offered || offer == Offer.AHEAD)) { + if (installBlock != null && offered) { Text(installBlock, fontSize = 13.sp, color = AttentionAmber, modifier = Modifier.padding(top = 10.dp)) } if (u.error != null) Text(u.error, fontSize = 13.sp, color = pal.error, modifier = Modifier.padding(top = 12.dp)) @@ -190,6 +191,7 @@ private fun StatusPanel(s: FrontEndState, u: UpdatesState, ua: UpdatesActions, m if (u.percent >= 0) LinearProgressIndicator(progress = { u.percent / 100f }, modifier = Modifier.fillMaxWidth().height(6.dp).clip(CircleShape)) else LinearProgressIndicator(modifier = Modifier.fillMaxWidth().height(6.dp).clip(CircleShape)) } + offered && installBlock != null -> Unit offer == Offer.UPDATE -> PrimaryButton(stringResource(R.string.upd_update_button, button), enabled = installable, main = true) { ua.onInstall(release!!) } offer == Offer.SWITCH -> PrimaryButton(stringResource(R.string.upd_install_button, button), enabled = installable, main = true) { ua.onInstall(release!!) } offer == Offer.AHEAD && installBlock == null -> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d5a877c00..5be0da17a 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -576,6 +576,7 @@ No se puede actualizar sobre la instalada Esta copia no está firmada con la clave de publicación de DroidDeck, así que las actualizaciones no se pueden instalar sobre ella. Desinstálala para cambiar. Actualización disponible + No se puede instalar Lista para cambiar Por delante de Stable Vas por delante de Stable diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 507dd6c47..618b44821 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -608,6 +608,7 @@ Can\'t update in place This copy isn\'t signed with DroidDeck\'s release key, so updates can\'t install over it. Uninstall it to switch. Update available + Can\'t install Ready to switch Ahead of Stable You\'re ahead of Stable diff --git a/docs/releases/0.3.0.md b/docs/releases/0.3.0.md new file mode 100644 index 000000000..774edeca4 --- /dev/null +++ b/docs/releases/0.3.0.md @@ -0,0 +1,125 @@ +# DroidDeck 0.3.0 (beta) + +

Join the DroidDeck Discord

+ +## Highlights + +### Steam, as on a Steam Deck +Deck mode is now the default. Steam's performance overlay goes from level 1 to 4 in Quick Access and shows GPU and CPU load, clocks, temperatures, VRAM and battery, even on phones that lock those files away. Games keep running behind the Quick Access Menu and the Steam menu, and Quick Access shows battery life and time to full. + +![Performance overlay at level 4 over Alan Wake's American Nightmare](media/0.3.0/mangohud-level4.jpg) + +### A Steam Deck controller +In Steam sessions the client reads your pad as a Steam Deck controller, with a real Quick Access button and your handheld's gyro. Games get Steam Input's virtual pads, so your Steam Input layouts reach them. On a dual-screen handheld, the second screen can be the Deck's back grips and trackpads. + +

+Steam's Steam Deck Controller test on the top screen reading the trackpads and clicks played on the second screen's Deck trackpads and grips +

+ +### Faster to Steam, faster in games +Steam's own startup is down from about 31 seconds to about 6, and that makes **Offline mode** work. proot has been optimized for calls games make most, and there's a new per-mode frame limiter. + +![Cloud stepping out of a doorway into the open, played with the on-screen controls](media/0.3.0/in-game.gif) + +### GPU drivers that set themselves +A new **GPU drivers** tab detects your GPU and keeps a matched pair of runtime and display drivers installed for it, including DroidDeck's own DD-Turnip builds. Switch to **Manual** to pick a pair yourself. + +![Components GPU drivers tab on an Adreno 740, set to Auto with DD-Turnip v0.1.1 in use](media/0.3.0/gpu-drivers.jpg) + +### Linux apps from Flathub, bring your own Appimage, and more +Turn on the **Flathub Store** in Setup to install ARM64 apps and games from Flathub. They open full screen from the front end with your controller. The Desktop page also takes your own apps: a script, an AppImage, a GitHub repo's newest ARM64 AppImage, or a Flatpak ID. + +

+Space Cadet Pinball's page in the Flathub Store +Adding Zen Browser from its GitHub repo, with icons suggested from the repo +

+ +![Desktop page with added AppImages and Flatpak apps next to the emulators](media/0.3.0/desktop-apps.jpg) + +### Updates in the app +The **Updates** page installs new builds without leaving DroidDeck: **Stable**, **Preview** (each build of main) or a **Test build** of an open pull request. Every download is checked against its checksum and DroidDeck's release key before Android sees it. + +![Updates page with the Stable, Preview and Test builds channels](media/0.3.0/updates.jpg) + +### A front end that moves as one +One focus ring slides between controls, settings flood out of the cog the way Play floods into a session, and Stop asks before it ends anything. DroidDeck is now available in Spanish, with more languages coming soon (contributions welcome). + +

+Moving the focus ring with a D-pad, opening Steam settings from the cog and backing out +Stopping a session: the confirmation grows out of the Stop button, then the session floods back into Play Steam +

+ +--- + +## Everything in 0.3.0 + +### Steam + +- Steam's startup down from about 31 s to about 6 s, so Offline mode works (@xXJSONDeruloXx, #69) +- Deck mode on for everyone, moved once on update, always on the Steam Deck client branch so Steam stops offering a downgrade (@xXJSONDeruloXx, #100, #101, #141) +- Steam's performance overlay (MangoHud), levels 1–4, with Adreno GPU, CPU, VRAM and temperature stats, and a switch to turn it off (@xXJSONDeruloXx, #55, #101) +- The overlay keeps running and filled in under an enforcing SELinux policy, as on retail phones (@xXJSONDeruloXx, #127) +- Games keep running behind the Quick Access Menu and the Steam menu (@xXJSONDeruloXx, #131) +- A game that minimized itself behind the Steam menu comes back on Resume (@The412Banner, #89) +- Quick Access shows battery life and time to full (@xXJSONDeruloXx, #117) +- Steam shuts down cleanly when you stop a session, so it keeps its caches (@xXJSONDeruloXx, #68) +- Games you own in a second library or games folder become real Steam games instead of shortcuts (@maxjivi05, #62) +- Imported games keep their installed build, so Steam doesn't re-verify them (@maxjivi05, #108) +- Launch options on added games are kept between sessions (@xXJSONDeruloXx, #111) +- Choose the host name Steam sees (@magarto, #61) +- Stretch 16:9 games onto screens narrower than 16:9, such as foldables (opt-in) (@magarto, #63) + +### Controls + +- Steam Deck controller for the Steam client: a real Quick Access button, the handheld's gyro, and Steam Input's virtual pads for games (@xXJSONDeruloXx, #86) +- Deck back grips and trackpads on the second screen (@xXJSONDeruloXx, #86) +- Xbox 360 controller option, with the right axis layout for games that have Steam Input off (@xXJSONDeruloXx, #86, #102) +- The Steam client finds the Deck controller under an enforcing SELinux policy (@xXJSONDeruloXx, #125) +- Touch scrolls Steam on phones with no controller at start (@xXJSONDeruloXx, #123) +- Quick Access from Back comes sooner, and no longer opens the focused game under load (@xXJSONDeruloXx, #70, #73) +- Shift and Caps Lock type capitals (@xXJSONDeruloXx, #71) +- Adaptive sticks hide when idle and appear where you touch (@maxjivi05, #53) + +### Display and performance + +- GPU drivers tab: Auto keeps a matched runtime and display pair for your GPU, Manual lets you pick one (@xXJSONDeruloXx, #103) +- DD-Turnip, DroidDeck's own driver bundles (@maxjivi05, #106) +- Adreno 840v2 (retail SM8850) works with the runtime's own driver (@xXJSONDeruloXx, #93) +- Setup says when a GPU is untested (Adreno 6xx, 710, 720, 722) (@xXJSONDeruloXx, #103) +- LSFG takes Lossless Scaling from your Steam library by itself, adds 60, 90 and 120 fps targets, and paces generation to the game's frames (@maxjivi05, #96, #129) +- Per-mode frame limiter, live panel refresh, opt-in GPU clock pin and Mesa's database shader cache (@maxjivi05, #87) +- proot stops less often: kompat, fake_id0 and ioctl traps trimmed, and common path lookups answered in-process (@xXJSONDeruloXx, @maxjivi05, #81, #84, #113) +- Turnip's GPU timestamp polls answered without blocking (@maxjivi05, #87) +- Cheaper defaults: Steam's xalia helper skipped, gamescope's realtime queue off, microphone off (@xXJSONDeruloXx, #85) +- Per-game environment variables, now in an editor that works with a pad (@maxjivi05, @xXJSONDeruloXx, #60, #140) + +### Apps and desktop + +- Flathub Store: ARM64 Flatpak apps and games, opened full screen from the front end and listed on the Desktop page (@xXJSONDeruloXx, #77) +- Add your own apps to the Desktop page: a script, an AppImage, a GitHub repo or a Flatpak ID, with icons and update checks (@maxjivi05, #130) +- The desktop hands its cursor to the app, so full-screen games skip a compositing pass (@xXJSONDeruloXx, #57) + +### Launcher + +- Updates page: Stable, Preview and Test builds, installed in the app and verified first (@xXJSONDeruloXx, #98, #112, #115) +- Game saves: import and export GameHub and Winlator save zips for added games (@The412Banner, #91) +- One focus ring slides between controls, settings flood out of the cog, and focus returns to what opened a page (@xXJSONDeruloXx, #105, #140) +- Stop asks first, and quitting floods back into the launch button (@xXJSONDeruloXx, #72, #105) +- Spanish, with every screen's text moved to translatable strings (@xXJSONDeruloXx, #140) +- Pair wireless debugging from a notification to lift Android's child-process limit (@xXJSONDeruloXx, #67) + +### Sessions and logs + +- Session folders are named by day, number and mode, the newest 30 are kept, and Setup has **Clear logs** (@xXJSONDeruloXx, #122) +- Shared logs carry controller and touch-mode details for input reports (@xXJSONDeruloXx, #125) + +### Fixed + +- Steam sessions failing to start with the app on adopted SD storage (@xXJSONDeruloXx, #119) +- Second-library games failing to launch or install their dependencies (@xXJSONDeruloXx, #134) +- Steam's own installs on a second library showing up as non-Steam shortcuts (@xXJSONDeruloXx, #138) +- A white veil left after closing the session menu (@xXJSONDeruloXx, #135) +- Update channel cards flashing as a controller moved over them (@xXJSONDeruloXx, #99) +- Flatpak installs failing (@maxjivi05, #82) +- gamescope respawning a crashing overlay every second (@xXJSONDeruloXx, #81) + diff --git a/docs/releases/media/0.3.0/add-app.jpg b/docs/releases/media/0.3.0/add-app.jpg new file mode 100644 index 000000000..2882af9f9 Binary files /dev/null and b/docs/releases/media/0.3.0/add-app.jpg differ diff --git a/docs/releases/media/0.3.0/deck-controller.gif b/docs/releases/media/0.3.0/deck-controller.gif new file mode 100644 index 000000000..627e91c27 Binary files /dev/null and b/docs/releases/media/0.3.0/deck-controller.gif differ diff --git a/docs/releases/media/0.3.0/desktop-apps.jpg b/docs/releases/media/0.3.0/desktop-apps.jpg new file mode 100644 index 000000000..5e85a326d Binary files /dev/null and b/docs/releases/media/0.3.0/desktop-apps.jpg differ diff --git a/docs/releases/media/0.3.0/focus-ring.gif b/docs/releases/media/0.3.0/focus-ring.gif new file mode 100644 index 000000000..9c53971d8 Binary files /dev/null and b/docs/releases/media/0.3.0/focus-ring.gif differ diff --git a/docs/releases/media/0.3.0/gpu-drivers.jpg b/docs/releases/media/0.3.0/gpu-drivers.jpg new file mode 100644 index 000000000..91d7c6945 Binary files /dev/null and b/docs/releases/media/0.3.0/gpu-drivers.jpg differ diff --git a/docs/releases/media/0.3.0/in-game.gif b/docs/releases/media/0.3.0/in-game.gif new file mode 100644 index 000000000..04ac378c8 Binary files /dev/null and b/docs/releases/media/0.3.0/in-game.gif differ diff --git a/docs/releases/media/0.3.0/mangohud-level4.jpg b/docs/releases/media/0.3.0/mangohud-level4.jpg new file mode 100644 index 000000000..29096d5ca Binary files /dev/null and b/docs/releases/media/0.3.0/mangohud-level4.jpg differ diff --git a/docs/releases/media/0.3.0/stop-session.gif b/docs/releases/media/0.3.0/stop-session.gif new file mode 100644 index 000000000..99b4716e4 Binary files /dev/null and b/docs/releases/media/0.3.0/stop-session.gif differ diff --git a/docs/releases/media/0.3.0/store-app.jpg b/docs/releases/media/0.3.0/store-app.jpg new file mode 100644 index 000000000..9fd3bf3f3 Binary files /dev/null and b/docs/releases/media/0.3.0/store-app.jpg differ diff --git a/docs/releases/media/0.3.0/store.jpg b/docs/releases/media/0.3.0/store.jpg new file mode 100644 index 000000000..529ca3f05 Binary files /dev/null and b/docs/releases/media/0.3.0/store.jpg differ diff --git a/docs/releases/media/0.3.0/updates.jpg b/docs/releases/media/0.3.0/updates.jpg new file mode 100644 index 000000000..4af951cd9 Binary files /dev/null and b/docs/releases/media/0.3.0/updates.jpg differ