* perf(epic): keep the hidden launcher from loading its store and Fab pages, and compress it while games run
The hidden Epic Games Launcher that stays up for sign-in while a game runs pre-warmed its store and Fab pages on a display nobody sees. Under FEX each page used about one core and grew past a gigabyte, crashing and reloading in a loop. The hidden instance now skips the store pre-warm, gets a blank page in place of Fab, and is paged out to zRAM with the Steam client once a game has focus. The visible launcher is unchanged.
* test(zram): keep the scan fixture PIDs above the kernel PID range
Pill: battery level, temperature and watts share one "BAT" row, as on the
other sizes, and each part follows its own switch. Turning Memory off no
longer hides the battery temperature (reported by Keegan), and watts show
without the battery level.
Touch (part of #1752, reported by ZZCSK): a locked HUD lets a touch through
where an on-screen control under it takes it: controller buttons and sticks,
Steam touch controls, screen keys and the controls panel below the game.
Each layer answers takesTouchAt with its own touch checks. Everywhere else
the HUD keeps the touch, so a long press still locks or unlocks it. A new
HUD starts locked. The "Lock in place" hint says so in every language.
Tests: Pill battery row per switch combination; takesTouchAt agrees with a
real touch over a grid for the controller buttons, screen keys and both
controls panel layouts (Deck and gamepad, landscape and portrait); a locked
HUD passes touches to a control under it, keeps the rest, and unlocks on a
long press.
* Use the second screen on dual-screen handhelds
Detect built-in second screens such as the Retroid Pocket Duo and AYN Thor.
The sections move to a menu on the bottom screen, L2 and R2 switch sections,
and text fields type on the bottom screen so the top keeps the full view.
Sessions open the bottom-screen trackpad and keyboard when they start.
Setup gains a Dual screen choice (Automatic, On, Off) with toggles for the
bottom menu and the in-game controls. Closing the bottom menu no longer ends
a running session.
* Type into Steam from the bottom screen
While the bottom-screen trackpad and keyboard controls are open in a Steam
session, Steam's own on-screen keyboard no longer covers the top screen.
The agent keeps Steam's text target and asks the app instead, which opens
the Android keyboard on the bottom screen and hands focus back to the game
when it closes. Modal and VR keyboards, and sessions without the bottom
controls, keep Steam's keyboard. The Show keyboard button now follows the
keyboard when it is closed another way.
* Open the bottom controls after the first frame and tidy dual-screen translations
* Move the in-game menu to the bottom screen on dual-screen handhelds
Fix controller input and focus on the second screen: keys and sticks reach the game after touching the bottom controls, closing the bottom controls returns focus to the game, B closes bottom typing, and the typing panel keeps a focusable window until focus has left it.
* Let B close the open layer of the session menu first and focus the selected bottom-screen mode
* Let the Retroid Pocket Duo Home double press open its quick menu during sessions
* Open the keyboard on the bottom screen for Steam text fields and hide it on Enter or when the field loses focus
* Keep controller focus on the top screen when a section is picked on the bottom screen
* Keep LB and RB on the store tabs and let Back return from a store to its tab
* Show the Adreno 722 as supported on the Retroid Pocket Duo
* Label the Steam integration suggestion's close button Close and cover that closing it is permanent
* Close the docked menu with its presentation, keep a focus picked during the stop delay, and ignore panel picks on process-settings pages
* perf(fex): answer raw stat, readlink and statx syscalls through the proot fast path
FEX issues newfstatat, readlinkat and statx as raw syscall() calls, which skipped the libc wrappers the fast path hooks and fell through to ptrace. Route them through libblfastpath the same way raw openat already is, and keep the fast path's own /proc/self/exe lookup off the hooked syscall().
* test(fex): keep the raw lookup test passing on kernels without statx
* Warn when a drive used as a game library is linked at USB 2.0 speed
Reads the drive's USB link speed from sysfs through its mount, and shows a
dismissible warning with the USB generation, link speed and a tip for a
faster cable or drive. The warning returns after the drive is plugged in
again unless Don't show this again is switched on.
* Keep slow USB dismissals per plug-in across restarts and recheck after a dismiss
Dismissals are stored with the boot count and the USB bus and device number,
so they survive a process restart, end when that drive is plugged in again
even while the launcher is stopped, and never clear other drives. A dismiss
looks for the next slow drive, stale scans are dropped, and an unknown port
speed is no longer treated as a USB 2.0 port.
* Drop slow USB scans that a newer library or mount check has superseded
* Invalidate pending slow USB scans as soon as a media broadcast arrives
* Revalidate an open slow USB warning on every check and match raw mount library paths
Every check now rescans, so a warning that is already showing is replaced or
cleared when its drive is removed or the library changes. Library paths saved
under /mnt/media_rw are treated as their /storage volume.
* Keep the Don't show this again choice when a rescan closes the slow USB warning
* Save the slow USB opt-out before the launcher activity is torn down
* feat: art-first store cards that share the library display settings
* fix: keep price and installed state on store cards when size is hidden
* fix: darken the compact store badge for contrast on light artwork
* feat: art-first library cards with a display menu for size, title and storage
* build: use native arm64 docker on Apple Silicon and add a fast deploy loop
* fix: address review feedback on library cards and local build scripts
The session HUD is FusionHUD (Bannerlator's HUD) with every reading taken from
the same source, and worked out the same way, as the MangoHud build that runs
Steam's performance overlay in a DroidDeck session.
- gamescope patch 0125 (gamescope-3.16.29-p19) shares the frame times that
mangoapp reads (one per signalled commit of connector 0) in a ring named by
DROIDDECK_HUD_FRAMES; the app reads it once per display frame and counts the
compositor's frames when gamescope sends none.
- MangoHud's rules: FPS over periods of at least 500 ms, frame time = 1000/FPS,
200-frame graph on a fixed 0-50 ms scale; CPU, GPU, temperatures, RAM, swap,
VRAM and battery from MangoHud's sources. Zones 22/26/28 are typed as GPU and
DDR sensors, so Steam's overlay CPU temperature no longer includes them.
- Five sizes in a panel shaped like the DroidDeck mark, with a centred header,
one left edge, neon outline and FPS; Minimal is the default size. Settings on
a new "Performance HUD" drawer page, with Bannerlator's options.
- The HUD reads sensors and polls frames only while it is drawn (not while
hidden, in picture-in-picture or behind another app), keeps its place after
picture-in-picture, and the diagnostics export has its own reader.
Device-tested on the AYANEO Pocket FIT: same readings as Steam's overlay side
by side in Dead Cells; the HUD updates in a game, hides in picture-in-picture
and comes back in place, no crashes.
Some Microsoft accounts get a delegation-compact (d=) ticket, which the
pinned Xbox backend (Nexus 0.2.8) rejected: the library stayed empty
(store-ticket-format) and downloads failed. This moves the backend to
Nexus 0.2.12, which has the upstream store-ticket fix, rebases the
DroidDeck credentials, launch-view and cloud-token patches, and strips
the d= prefix from compact JWE tickets so downloads work again.
Tested by seven users in the Discord Xbox Store Issues thread on the
pr-1729 build at d55e05a0.
* Update Xbox backend to 0.2.12
* Fix cloud offering fallback and add regression tests
* Fix Xbox authentication with compact JWE tickets
* ci: run unit tests in four forks
* fix(ui): keep the ring veil count per test so tests do not share snapshot state
* ci: keep two unit test forks, four were slower
* fix: stop stale Android readbacks snapping Steam's brightness slider back during a held stick
* fix: let deploy_fast sign with the release env on macOS and Linux
* fix: honor DROIDDECK_SIGNING_ENV first and cover the env signer in tests
* fix: honor DROIDDECK_SIGNING_ENV first and cover the env signer in tests
* fix: retry held brightness readbacks, use a monotonic hold clock and resolve the signing env path
* fix: resolve the build-tools path before signing changes directory
* fix: pick the newest build-tools numerically and keep the env file from replacing them
* fix: only pick build-tools that have aapt, zipalign and apksigner
* feat(setup): guide build number and the child-process toggle when developer options are off
* fix(setup): only guide the child-process toggle on devices that have it
Libc-internal file operations bypass the existing preload wrappers and
still stop in PRoot. Patch recognized ARM64 SVC sites in libc and the
runtime loader, reuse the current translator for 20 path syscall
numbers, and return declined calls to PRoot. Enabled by default after
the tracer-key check; `PROOT_FP_PATCH_SVC=0` remains the troubleshooting
opt-out.
The hardened implementation patches 47 sites on Thor's glibc 2.43.
Relays preserve general/SIMD registers, flags, FP state and errno, and
provide unwind metadata back to the original libc frame. Text is patched
through `/proc/self/mem` after relay pages become executable.
Initialization skips multiple live threads and SVE/SME. Alternate signal
stacks and insufficient remaining stack use PRoot; main-stack growth
respects the current limit. No proroot code or binaries are imported.
Robust-list set/get sites deliberately remain with PRoot/the kernel.
Device tests reproduced stale state when the earlier private-TLS
implementation mixed patched and raw callers. Removing that shortcut
fixes coherence and preserves the host's actual return policy; the
earlier thread-creation gain is retired. SDK forwarding still enables
faccessat2 ENOSYS fallback and clone3 ENOSYS only before Android 15.
Successful clone3 is never executed from the C relay. Existing fsync
handling is unchanged. No in-process RET_TRAP filter is added, so
SIGSYS-mask/handler machinery is not applicable.
Version-2 build-ID/API tables cache relative patch sites in `/tmp`.
Every hit revalidates executable bounds, alignment, instructions and
syscall numbers. Invalid/unsafe files rescan; publication uses a private
temporary file and atomic rename. The PRoot dependency fetch also gains
a mirror matching the existing pinned checksum, resolving upstream
availability failures without changing versions.
Five alternating pairs using the final bundled library on Thor/Android
13:
| operation | wrappers only | hardened relays |
|---|---:|---:|
| libc-internal stat | 49.6 us | 6.0 us |
| libc-internal open/close | 50.4 us | 7.8 us |
| libc faccessat | 49.7 us | 22.0 us |
| libc getcwd | 54.9 us | 5.0 us |
| thread create/join | 136 us | 144 us |
| complete tiny exec/wait | 4.44 ms | 5.60 ms |
An earlier five-pair hardened-library run measured stat 50.1 to 5.9 us
and open/close 51.9 to 7.1 us. The metadata gains survive the safety
checks. Neither run shows a thread-creation gain, and tiny execs retain
roughly 0.9-1.2 ms overhead.
Three full Steam startup pairs yielded Home-ready medians of 47.02 s off
/ 45.25 s on, with overlapping 43-50 s ranges. Steam/CEF-only PSS
medians were 1,223.1 / 1,233.9 MiB; the roughly 11 MiB difference is
smaller than observed variation, but does not establish zero memory
cost. Startup/memory used one unchanged signed bundled-library APK
without overrides; the final stack-gap guard only increases distance
reserved above other mappings. No reliable whole-client startup
improvement is claimed.
Earlier fresh Half-Life SD installs took 350/341 s off and 326/327 s on.
These are historical results from the preceding robust-list revision,
not a post-hardening download retest. Network/shader scheduling varied,
allocation did not reliably improve, and the repeat baseline committed
faster. This PR demonstrates metadata acceleration; it does not
demonstrate a reliable fix for Steam's SD allocation/commit bottleneck.
Validation on `2c3c135b` (production code unchanged since `4998b046`):
- Local release build, runtime/native-library checks, release signing
and four focused Android tests pass. [Final-head
CI](https://github.com/Droid-Deck/DroidDeck/actions/runs/40704837477)
passed: 1,431 Python/native tests (49 skips), 2,648 Android tests (zero
failures/skips), and 162 Rust tests. CodeRabbit is green on the same
head. The installed production APK forwards API 33 and patches 47 sites
with an unset enable flag and no override; Steam Home renders.
- Embedded and shared-PIC matrices pass on glibc 2.43; shared-PIC also
passes on separately loaded glibc 2.39. Coverage includes raw
results/errno/registers/FP state, path/flag/error and protected-pointer
cases, exact readlink boundaries, mixed raw/relay robust state,
concurrent threads, fork, thread churn, alternate-stack signals,
deferred/FIFO cancellation, signal siglongjmp recovery, forced unwind
with caller cleanup, and limited remaining stack.
- Four device test-process filters deny the memory patch file, relay
mapping, executable permission, or patch writes. Every case leaves
original syscall execution working. Host injection also verifies partial
installation after an individual site fails.
- Concurrent atomic symlink replacement passes 3,000 internal
stat/readlink iterations at default/zero TTL. Eight focused host tests
pass, covering instruction/branch limits, stack/limit fallback,
untrusted cache files and NDEBUG checks. API selection gives 45/47/46
sites for unknown/33/simulated-35; unset and explicit 1 agree, and 0
leaves sites untouched. This does not replace Android 15 hardware
testing.
- All bench tools compile with -DNDEBUG. Relative/absolute fixture
workloads agree, and thread/ABI/failure checks pass on Thor with those
builds.
- Earlier internal fts/realpath/NSS/dlopen and normalized read-path
equivalence pass. The write suite retains the existing wrapper SD
Unix-socket mismatch against PRoot.
CodeRabbit disposition: fixed relative fixture paths, prevented
benchmark checks disappearing under NDEBUG, corrected the
limited-fallback description, and narrowed memory-file denial so the
thread check still succeeds. The default-off recommendation was
addressed by fixing/testing the underlying guard and unwind issue while
retaining the requested default-on behavior. The readlink count
suggestion is intentionally not adopted: device boundary comparisons
reproduce current PRoot behavior, so changing that inherited policy
belongs in a separate correctness change.
This is not consequence-free instruction patching: constructor time,
private text copies and stack checks have costs. Android 15+ ARM64 and
16 KiB-page hardware, arbitrary asynchronous cancellation/non-local
exits, user-managed stacks, and attributable tracer-counter measurements
remain gaps. Loader calls before the constructor and arbitrary guest ELF
syscall sites remain outside scope.
Thor was restored to its original APK and bundled library, with app data
preserved and test settings, overrides, temporary files and version-2
test caches cleared.
Detailed evidence and revision boundaries: [proroot
analysis](https://github.com/Droid-Deck/DroidDeck/blob/2c3c135b52c3dd949ef5a99a67d8c12eba24406c/docs/development/proroot-analysis.md).
Pressing Cancel (or B) while a session is installing the desktop or the
Steam-mode Proton seed now stops the download. Launching again right
after no longer opens a session screen that closes itself.
Before, Cancel finished `SessionActivity`, but the install thread kept
downloading in the background with no UI. Two things went wrong:
- `Downloader` and `DesktopCatalog.install` had no way to be cancelled.
- "an install is running" lived in a per-activity field
(`installingRuntime`). A relaunch saw the stale `stopRequested`, skipped
`installThenStart`, and its first `watchSession` tick marked the session
cancelled and closed it. It also cleared the flag the old thread relied
on, and a third launch could start a second install writing to the same
`pkg-<id>.download`.
Changes:
- `Downloader.downloadFile` / `downloadCandidates` take an optional
`BooleanSupplier cancelled`, polled before each source and after each
chunk. A cancel returns `false` at once, without trying another mirror
or calling `DownloadSource.failed()`. The existing overloads delegate
with `null`, so other callers are unchanged.
- `DesktopCatalog.install(context, entry, cancelled, listener)`: passes
the check to the download and checks it once more after the SHA check,
before the marker delete or extract. Extraction itself still runs to the
end. The old 3-argument overload stays, so `MainActivity.installPackage`
is unchanged.
- `SessionActivity`: `installDesktop` / `installProton` pass `{
SessionState.stopRequested }`. `installingRuntime` now reads the
process-wide `SessionState.installing`.
- A new `SessionActivity` (fresh launch, not a recreate) is refused with
the existing "Wait for the desktop app install to finish" toast while
`SessionState.installing != null && !running`.
`MainActivity.startActivity` applies the same check before the launch
animation.
- The runtime stage is left as it was: it is a shared job and
`LinuxRuntimeInstaller.isBusy()` already blocks relaunch.
Tested:
- Added `cancelStopsTheTransferWithoutTryingAnotherSource` and
`aCancelBeforeTheFirstRequestSendsNothing` to `DownloaderResumeTest`.
That class passes (9/9), as does `DesktopCatalogTest`, and the app
compiles.
- Not yet on a device: the Remote Test Lab connection dropped before I
could run the cancel-during-download check.
## Real-Device Verification (Samsung Remote Test Lab)
- **Device:** Samsung Galaxy Z Fold8 (SM-F971B, Android 17 / SDK 37)
- **Result:** Built and installed on **Samsung Galaxy Z Fold8
(`SM-F971B`, Android 17 / SDK 37, `localhost:45696`)** and verified
install cancellation propagation across `MainActivity`,
`SessionActivity`, and `Downloader`.
## Summary
Adds update detection for installed itch.io games. This is PR4 of the
itch.io store rework (browse, filters, game page and collections are
merged; Linux support is #1589). Butler patching comes later as an
add-on pack. Updates here download the full build.
## What changed
- `ItchUpdates` (new): compares each installed itch game with the
uploads itch lists for it (`GET /games/{id}/uploads`), using what the
install already saves: `installVersion` (the upload's `updated_at`) plus
`gameId`, `downloadKeyId` and `uploadId` in `extra`.
- The installed upload is still listed and has a later `updated_at`: an
update.
- The installed upload is gone: the newest Windows upload is offered as
a replaced build.
- An install without an `uploadId` compares the newest Windows upload's
date.
- An unknown installed version, or no answer from itch, never reports an
update.
- The check walks installed games one at a time, one second apart, and
logs `itch.io: checked <n> installed games, <m> with updates`. It runs
when the itch tab opens (at most every 6 hours) and from the new button.
A game itch did not answer for keeps its last result. Results are cached
in the itch preferences until the update is installed or the account
signs out.
- Library: games with an update show an "Update" label on their tile and
row. A Check for updates button in the Library header (itch tab only)
runs the check on demand, shows progress, and ends with a toast:
"Updates available: N" or "Up to date".
- Game page: an Update button appears for installed itch games with an
update. A replaced build asks first and names the new file. A plain
newer build updates directly.
- Safe updates: an update never extracts into the live game. The archive
is unpacked into `.droiddeck-update` inside the game folder, and the new
build must contain an executable before any game file is touched. A
failed or cancelled download or extraction removes the staging folder
and leaves the game as it was. Then `ItchStaging.merge` moves the staged
files into place with renames inside one folder. A staged file replaces
the file with the same path, directories are merged, and anything the
new build doesn't contain (saves, settings, install record, launcher,
art) stays. The install record is marked updating just before the move,
so an interruption there is resumable.
- New strings `stores_itch_updates_found` and
`stores_itch_update_replaced` in all 33 locales. The button label, toast
and dialog buttons reuse existing strings.
- `docs/development/itch-updates.md` describes the feature.
## Notes
- One extra copy of the build's size must be free while an update runs.
- Files the developer removed in the new version stay in the game
folder, because everything the new build doesn't contain is kept.
- The final rename step is not an all-or-nothing swap. A crash or power
loss inside it leaves the game marked updating and resumable.
- The update reruns the normal install over the game's own folder and
storage location.
## Tests
- `ItchUpdatesTest` (Robolectric, like the other itch tests): newer,
same and older stamps, replaced upload falling back to the newest
Windows build, unknown installed version, no answer, an install without
an upload id, and the cache round trip.
- `ItchStagingTest`: new files replace old ones, other files stay, and a
file replacing a directory and the other way round.
- Not run on a device or compiled locally; relying on CI. The controller
focus behaviour of the new buttons is untested.
With no frame limit, AIO's Direct3D 7 (DirectDraw) test ran at exactly
144 fps on a 144 Hz panel: every frame waited one refresh (6.94 ms).
Wine asks for a swap interval of 1 on every OpenGL window, and WineD3D's
front-buffer drawing used by DirectDraw and Direct3D 1-7 never lowers
it; it only sets the interval in its present path. Mesa's Zink then
presents in FIFO.
droiddeck-game-env sets vblank_mode=0 for games launched through Proton
before the game's environment edits, so a value set in the launch
options or the editor wins and an editor Remove leaves Mesa's default.
Steam's own runs in compatdata/0 are unchanged. Steam's Frame Limit
still holds a game: gamescope's WSI layer presents every swapchain in
FIFO while its limiter is on.
On the AYANEO Pocket FIT, AIO Direct3D 7 went from 144 to 2,345 and
2,265 fps (1 % low 99 -> 620) with no limit; with the Quick Access
limit at 144 and 72 it held at 143.76 and 71.92.
At every start, Steam's shader pre-caching runs d3ddriverquery64.exe
through each Proton tool (DroidDeck-Proton and the ARM64 Proton) in
compatdata/0. Here the query has never succeeded (948 of 948 runs in
the client's shader log failed, mostly exit code 1), and each run
started Wine in that prefix, where the two Protons upgraded it back and
forth (11.0-100 <-> GE-Proton11-11) with the 32-bit wine.inf install.
When that overlapped a game's start, AIO ran 25-30 % slower for its
first 5-7 seconds.
Both Proton wrappers now exit 1 for Steam's own query, "run
.../d3ddriverquery64.exe" (or the 32-bit name) with STEAM_COMPAT_DATA_PATH
in compatdata/0, before doing anything else, so Steam gets the same
failure without Wine. Every other verb and prefix goes on to Proton.
A new game frame waited for the next screen tick before the compositor
drew it: 1.9-3.5 ms on average at 144 Hz, measured with the pacing
counters. With frames going straight back to the game (no compositor
limit), the compositor now draws as soon as the clients' requests are
handled, at most once per refresh, and presentation times stay on the
tick.
gamescope delivers about one frame per refresh, so the tick leaves a
frame younger than one refresh for the next arrival to draw, unless the
tick itself changed the window, effects or headset pose; a frame no
other frame follows is drawn at the tick after. Once a held frame is
still waiting at the next tick (a client paced by frame callbacks), the
tick draws every frame for a second before holding one again. The
queued draw checks again that drawing on arrival is still on.
Paced output (glasses), streaming and frame generation keep drawing on
the tick. The hidden pref renderOnCommit (default on) turns it off. The
pacing log line counts the scenes drawn on arrival. On the device new
frames wait 0.12 ms instead of ~2 ms.
0123 backports upstream 0efe47c and 666c52b by Matthew Schwartz (commit
completion list lifetime, no Rc to a dying commit). 0124 polls a new
commit's fence at import; a finished frame is marked done at once and
handled in the same steamcompmgr pass instead of a second wake through
the image waiter. commit_ready_check (default on) turns it off.
Pins gamescope 3.16.29-p18 (0123 + 0124). On the AYANEO Pocket FIT about
81 % of commit fences are ready at import; gamescope-xwm 22-26 % -> 16 %
of a core, gamescope_img 10-11 % -> under 2.5 %.