Commit Graph
928 Commits
Author SHA1 Message Date
MaxsTechReview 92ef850999 fix: Steam starts on ROMs that refuse the Android ID read (#1421)
On a Redmi K70 (Android 14), tapping Steam drops straight back to the
main screen. The session thread dies while writing the guest network
link:

```
java.lang.IllegalArgumentException: Illegal char: - at offset 8
at android.provider.Settings$Secure.getStringForUser(Settings.java:6486)
at com.droiddeck.launcher.runtime.LinuxNetworkLinkComponent.describe(LinuxNetworkLinkComponent.kt:281)
```

The guest MAC is seeded from `ANDROID_ID`. That ROM throws from the
read, which had no guard. A failed read now uses the same fallback seed
as a missing ID. Devices that can read it keep the MAC they have now.

- `SeededMacTest`: stable MAC from an ID, and a read that throws the
ROM's exception falls back instead of crashing.
- REDMAGIC NP06J: the guest MAC is the same before and after the change
(`16:4e:5b:53:e9:ef`).

## Credits
Reported by lc666 on Discord.
2026-10-23 16:08:53 -04:00
MaxsTechReview 64b94f7cdb fix: the app follows automatic landscape rotation again unless an orientation was picked (#1420)
Fixes #1348, fixes #1372, fixes #1388.

Since 0.6.0 the app ignores the sensor and stays in one landscape side,
even with auto-rotate on. #1287 changed the default orientation from
sensor landscape to fixed landscape, so everyone who never picked an
orientation in settings got locked.

The default is sensor landscape again. A picked orientation is still
saved and used as before.

| | Before | After |
| --- | --- | --- |
| No orientation picked | `SCREEN_ORIENTATION_LANDSCAPE`, no flip |
`SCREEN_ORIENTATION_SENSOR_LANDSCAPE`, follows the sensor |
| Orientation picked in settings | that orientation | that orientation |

Tested on REDMAGIC NP06J (Android 16) with no saved orientation:
`dumpsys activity` reports the requested orientation, and the app flips
with the device.
2026-10-23 16:08:02 -04:00
maxjivi05 b6ca673d6e fix: Steam starts on ROMs that refuse the Android ID read 2026-10-23 15:50:16 -04:00
maxjivi05 6378c3f3d2 fix: the app follows automatic landscape rotation again unless an orientation was picked 2026-10-23 15:48:03 -04:00
Kurt Himebauch 2d59f03e7b fix: preserve GPU stats while pinning clocks (#1416)
Enabling GPU clock pinning froze Thor's kernel utilization counters,
leaving the HUD stuck at an arbitrary value (10–11% in the reproduced
sessions). KGSL_PROP_PWRCTRL disables both the governor and its
utilization sampling.

Use a dedicated KGSL context with a maximum-power constraint, renewed by
a small NOP submission every 25 ms. Keep devfreq running, bound the
queue to one submission, and release the context, mapped command buffer
and GPU allocation on stop or failure. Preserve recovery of legacy
global pins and report native setup failures instead of claiming
success.

Validation:
- AYN Thor: reproduced frozen counters with the old pin. The replacement
native implementation, run as a standalone ARM64 harness alongside the
installed Steam/Bulletstorm session, kept 680 MHz while utilization
changed from 17% in Steam to about 4% loading and 44% at the animated
title. A final-source smoke run also returned changing 60–66% samples at
680 MHz.
- Native lifecycle regression passed on Linux and as an ARM64 executable
on Thor: setup failure cleanup, renewal, bounded queue, runtime failure,
restart, and type-3/type-7 NOP selection for older/newer Adreno cores.
- Android native build and Kotlin compilation passed locally (NDK
27.0.12077973).
- CI build, Android tests, native regressions, and signing passed.
Installed the release-signed PR APK on Thor with app data preserved.
With GPU pin enabled, the normal Steam/Bulletstorm session held 680 MHz
while the HUD changed from 3% loading to 53% in the animated game scene
at 60 FPS; raw gpubusy samples also continued changing. This checks
utilization reporting, not long-term gameplay stability.

The earlier session exit was recorded by Android as LOW_MEMORY; this PR
addresses the utilization freeze.
2026-10-23 15:41:12 -04:00
MaxsTechReview 8f0bd51fd8 Merge pull request #1401 from Droid-Deck/perf/ntsync-fast-paths
## Summary

droiddeck-ntsync (the userspace stand-in for the Linux ntsync driver,
Wine sync = ntsync) was slower than droiddeck-esync on every cheap
operation. On the tablet a `SetEvent` cost 0.94 µs against esync's 0.21
µs, and a wait on a signaled event 0.45 µs against 0.08 µs. Four things
made it slow:

- **Signal masking:** every operation blocked and restored the signal
mask, so each call made 2 `rt_sigprocmask` syscalls, and a blocking wait
made 4. The masking itself is needed: if Wine's suspend signal stopped a
thread while it held an object lock, a GC thread that suspended it and
then touched the same object would deadlock.
- **Queueing for objects that were already free:** a wait linked itself
onto every object and unlinked again even when the first object could
simply be taken.
- **Wakes nobody needed:** a futex wake syscall went out for every
handoff, even when the waiter was still spinning and would have seen the
change anyway.
- **One shared counter:** every call took and dropped a reference on a
single process-wide device counter, so threads working on unrelated
objects bounced one cache line between cores.

### Changes

- **Signals are deferred instead of masked:** handlers installed by
Wine's `ntdll.so` run behind a small trampoline. If such a signal
arrives inside a critical section (a per-thread depth count), it is
saved with its `siginfo` and sent again with `rt_tgsigqueueinfo`
(falling back to `tgkill`) as soon as the section ends.
  - `sigaction` queries still return Wine's own handler.
- Synchronous signals (SEGV, BUS, ILL, FPE, TRAP, SYS) are never
wrapped, as before.
  - Handlers installed by anything other than `ntdll.so` are left alone.
- **Wait-any fast path:** an object that is signaled and has nobody
queued on it is taken directly, in index order. A wait whose timeout has
already passed returns `ETIMEDOUT` without building a queue.
- **Short spin before sleeping:** a waiter spins for up to 4 µs, bounded
by time rather than by iteration count, before it sleeps on the futex.
- **Wake only sleeping waiters:** the waiter publishes a `sleeping`
flag, and the waker only calls `FUTEX_WAKE` when it is set. Both sides
use seq_cst ordering, so a wake can't be lost. The queue layout changed,
so `NS_VERSION` is now 2.
- **Per-thread device reference:** a thread takes one reference on the
device the first time it uses it and keeps it until it exits or moves to
another device, instead of two atomics on the shared counter per call.
- **More than four signals in one section:** a fifth distinct signal is
held pending with the thread's signal mask, then released once the
section ends. Its handler never runs while an object lock is held.
- **Replay:** deferred signals are replayed with every signal blocked,
so each one is sent exactly once. A held signal is released at the same
signal-nesting level that held it. Handlers installed with
`SA_RESETHAND` are not wrapped, so a one-shot handler keeps its kernel
behaviour.
- **Waits from a handler during another wait's sleep:** they get their
own queue and, for a second device, a reference for that call only. The
outer wait keeps its queue and can't pick up the inner wait's result.

### ntsync becomes the default

- **New installs:** start on ntsync.
- **Existing installs:** move to ntsync once, on the first start of this
version, whatever sync they were on before (a one-time `ntsyncDefaultV1`
marker). Anything picked after that is kept, including esync, fsync and
wineserver.
- **Wording:** the ntsync switch in Performance no longer says
"experimental" or "for testing", in all 33 languages.
- **Unchanged:** per-game sync choices on a game's settings page still
win over the app-wide choice. Proton builds without ntsync still fall
back to droiddeck-esync, then droiddeck-fsync.
- **Tests:** `SyncBackendTest` covers a fresh install, each earlier
choice moving to ntsync once, and a later choice surviving the next
start. All 2,160 unit tests pass.

## Performance (REDMAGIC tablet)

Windows microbenchmark (`syncbench.exe`, x86-64 under FEX), µs per
operation:

| | esync | ntsync before | ntsync after |
|---|---|---|---|
| SetEvent | 0.57 / 0.70 | 0.51 / 0.65 | 0.38 / 0.35 |
| wait on signaled event | 0.13 / 0.13 | 0.44 / 0.43 | 0.27 / 0.20 |
| wait, timeout 0 | 0.27 / 0.25 | 0.52 / 0.51 | 0.19 / 0.18 |
| semaphore release + wait | 0.72 / 0.68 | 0.66 / 0.63 | 0.12 / 0.16 |
| mutex wait + release | 0.77 / 0.73 | 0.66 / 0.64 | 0.12 / 0.12 |
| wait on 8 objects | 0.10 / 0.09 | 0.59 / 0.57 | 0.15 / 0.15 |
| alertable wait | 0.06 / 0.05 | 0.43 / 0.42 | 0.07 / 0.07 |
| event ping-pong, 2 threads | 25.6 / 24.5 | 24.4 / 29.3 | 1.97 / 1.66 |
| 4 threads on one semaphore | 9.4 / 4.6 | 20.3 / 19.1 | 7.3 / 7.6 |

The backend each run actually used was checked from its file
descriptors:
- **esync:** wineserver held eventfds.
- **ntsync:** wineserver held the `droiddeck-ntsync` device and its
timerfd objects.

Native arm64 benchmark against the device directly, µs (before / after):
- set: 0.31–0.36 → 0.06–0.08
- signaled wait: 0.37–0.45 → 0.04–0.08
- semaphore pair: 0.56–0.58 → 0.06–0.07
- mutex pair: 0.56–0.61 → 0.04
- ping-pong: 15–30 → 1.7–2.5

Removing the shared device counter on its own takes 4 threads working on
their own objects from 0.21 to 0.04 µs per operation (x86-64 host).

Games, default Steam launch, The Forest and PEAK at the same scene each
run, 20 s pacing. Columns are esync, ntsync before and ntsync after;
each cell gives two runs.

| | esync | ntsync before | ntsync after |
|---|---|---|---|
| The Forest Low, fps | 64.5 / 61.9 | 70.1 / 74.8 | 72.1 / 72.1 |
| The Forest Low, CPU | 489 / 511% | 434 / 419% | 436 / 426% |
| The Forest High, fps | 58.3 / 57.8 | 72.0 / 69.8 | 72.5 / 69.9 |
| The Forest High, CPU | 506 / 504% | 417 / 429% | 425 / 435% |
| PEAK, fps | 50.4 / 48.6 | — | 47.0 |
| RDR2, launch to signed in / game process | — | — | 29.2 / 42.0 s and
28.3 / 41.1 s |

Two more games, esync against ntsync after, three runs each, default
settings:

| | esync | ntsync after |
|---|---|---|
| Among Us, menu fps (59 cap) | 59.0 / 58.9 / 59.3 | 59.0 / 59.0 / 58.7
|
| Among Us, launch to main menu | 6.65 / 6.84 / 6.85 s | 6.39 / 6.52 /
6.64 s |
| Among Us, CPU | 123 / 132 / 133% | 133 / 133 / 124% |
| Mortal Kombat X attract fight, fps | 56.7 / 54.1 / 56.3 | 61.1 / 56.9
/ 56.2 |
| Mortal Kombat X, 1% low | 20.0 / 30.3 / 30.3 | 30.3 / 30.3 / 30.3 |
| Mortal Kombat X, launch to window | 7.75 / 7.79 / 7.75 s | 7.81 / 7.71
/ 7.63 s |

Mortal Kombat X's CPU moves between 140% and 275% in both modes because
the attract mode changes fighters and stage, so it isn't compared.

The Forest's 1% low was 41.7 fps in every run. PEAK is GPU-bound and
stays in run-to-run noise. Its second ntsync run stopped at the title
menu because the rig's menu input didn't register; the game was still
running at 87 fps, and the same rig miss happened on esync before. RDR2
signs in and reaches the game on the new code.

## Tests

- **`tools/tests/test_ntsync_preload.py` (new):** builds the preload and
checks the emulation against the semantics documented for the kernel
driver:
  - semaphore counts and `EOVERFLOW`;
- mutex owners, recursion, `EPERM`, kill/abandon with `EOWNERDEAD` on
read and on the next wait;
  - auto and manual events, set/reset/pulse previous state;
- wait-any index order, duplicates, the alert object, `EINVAL` limits,
timeouts;
  - wait-all atomicity, duplicates and alert;
- wakes across threads and across processes (inherited fds and fds sent
with `SCM_RIGHTS`);
- 20,000-round ping-pong and 4-thread semaphore stress with infinite
timeouts;
- two signal cases. In one, a handler that re-enters the same object is
fired 2,000 times at a busy thread. In the other, a handler parks the
thread while another thread uses the same object, as Wine's suspend does
under a GC.
- eight real-time signals fired in bursts at a thread busy on one
object, each handler touching that object, so more signals arrive inside
a section than can be deferred;
- a handler that waits on the same device and on a second device while
its thread sleeps in another wait;
- a one-shot (`SA_RESETHAND`) handler installed and fired 300 times at a
busy thread, every one of which must run.
- **The signal tests catch a broken implementation:** with signal
protection removed, the program hangs and the test fails. Before the
last fix, the burst test hung and the outer wait returned the nested
wait's result.
- **The suite passes:** 8 of 8 runs natively on arm64 under proot on the
tablet, and 40 of 40 on the host.
- **On the final build:** The Forest Low ran at 77.7 fps, PEAK at 47.5
fps, and Among Us and two Mortal Kombat X runs played through.
- **The new paths stay out of the way in games:** a build that logged
them saw about 1,400 Wine signals land during ntsync sleeps over two
Mortal Kombat X runs and one The Forest run. None of those handlers
waited on ntsync and no signal was held back, so games run the same code
as before the last fix. The Forest Low ran at 71.6 fps and PEAK at 47.2
fps on the final build.
- **Existing preload tests still pass:** fsync, openat2, usr2 (also with
`BL_SYNC=1`), tracer, dircache, noexec, binfmt, faultreport and esync
packs.

## Credits

- [Linux](https://github.com/torvalds/linux)
`Documentation/userspace-api/ntsync.rst` (GPL-2.0): the object, wait and
error semantics the new test checks. Also added to the README credits.
2026-10-23 15:10:28 -04:00
XxJSONDeruloxX 9c66a1ac0a fix: select NOP packets for older Adreno cores 2026-10-23 15:04:50 -04:00
XxJSONDeruloxX 4d86839910 fix: preserve GPU stats while pinning clocks 2026-10-23 15:00:11 -04:00
maxjivi05 293e84a64c feat: droiddeck-ntsync is the default Wine sync
- New installs start on ntsync.
- Existing installs move to ntsync once on the first start of this version, whatever sync they used before. A choice made after that is kept.
- The ntsync switch is no longer labelled experimental or for testing, in every language.
2026-10-23 13:57:38 -04:00
Kurt Himebauch 16bedf3635 fix: keep the UBWC hint consistent after custom guest environment 2026-10-23 13:54:01 -04:00
Kurt Himebauch e06b942e67 fix: warn about UBWC restart only after a hint is active 2026-10-23 13:54:01 -04:00
Kurt Himebauch 2e1156038e test: cover the inclusive A740 blob 700 boundary 2026-10-23 13:54:01 -04:00
Kurt HimebauchandVabian124 dccd5a7c69 fix: match the A740 UBWC hint on driver 512.676.76
Co-authored-by: Vabian124 <82999916+Vabian124@users.noreply.github.com>
2026-10-23 13:54:01 -04:00
Kurt Himebauch fc768a3c45 feat: add an A740 UBWC compatibility override 2026-10-23 13:54:01 -04:00
0x1Nawaf 7c51e6e904 fix: draw the gamescope cursor only when that option is on
The extra plane costs frames on a phone, so compositing stays off unless Performance asks for it.
2026-10-23 13:30:42 -04:00
Kurt Himebauch b76f13f7ac fix: keep the shader pre-caching choice until a session confirms it, and read it from its own VDF section 2026-10-23 13:05:04 -04:00
Kurt Himebauch f88227ed74 feat: toggle Steam shader pre-caching from DroidDeck's Steam settings 2026-10-23 13:05:04 -04:00
Kurt Himebauch f683ca1dea fix: show the paused card already settled, skip it on auto resume, and cancel manual pauses on a quick return 2026-10-23 12:28:01 -04:00
Kurt Himebauch 6b6d7d2ac7 feat: lift paused frame into a grey card with a glass resume button 2026-10-23 12:28:01 -04:00
ovurrsl 7aeb7779bb fix: fall back to Valve's stable Steam client when the beta one keeps failing
The native arm64 client follows steamdeck_publicbeta, so a Valve beta
regression reaches every user at once. If the repaired beta client is still
started twice without being shown, or the beta manifest carries no arm64
client, droiddeck-steam-install now installs steamdeck_stable, keeps it, and
logs why (listing the manifest's blocks in the second case). The session asks
the installer for the channel, so -clientbeta and package/beta stay in sync.
BL_STEAM_CHANNEL or ~/.config/droiddeck/steam-channel (beta|stable) pins it.

Valve's 2026-10-15 beta moved the x86 client into a Steam Runtime container.
In case the arm64 client follows, its session now exports
STEAM_STEAMRT_RECURSION_GUARD=1 and, only if steamrtarm64 carries pv-runtime
or a steam-runtime-check-requirements probe, starts it through the same
steamrt shim the x86-64 client uses (now droiddeck-steamrt-shim) and logs it.
2026-10-23 12:03:41 -04:00
FX192 b88eebe09b Inner Gamer: let Compose apply the animator scale once 2026-10-23 11:20:34 -04:00
FX192 5f341dda16 Inner Gamer: a new speed takes over at once, continuing from the current colour 2026-10-23 11:20:34 -04:00
FX192 5a1819a713 Themes.current also reloads the saved Inner Gamer speed 2026-10-23 11:20:34 -04:00
FX192 6b010baa9e Inner Gamer: read the animated hue while composing so the colors keep cycling 2026-10-23 11:20:34 -04:00
FX192 c7961bdacf Fix the build: rename moveRgbHue to avoid clashing with the rgbHue setter; Hindi verb agreement 2026-10-23 11:20:34 -04:00
FX192 f7acb7fcd7 Inner Gamer RGB theme with a speed slider; keep the colour picker's hex box in view above the keyboard
The hex box sat at the bottom of the picker, so with the keyboard up the dialog
was cut off and the field could not be seen. While the box has focus the dialog
now scrolls to its end, and again whenever the visible height changes, so the
hex box and OK / Cancel stay above the keyboard.

Inner Gamer is a new theme just above Gradient. Its accent walks round the
colour wheel on a Compose infinite transition, so Android's animation clock
drives it and it holds still when animations are off. The hue moves in 3 degree
steps so the app restyles only when the colour visibly changes. Setup shows a
Speed slider (1 to 10) while it is selected. The palette comes from
ThemeColors.palette, so button text stays readable at every hue.
2026-10-23 11:20:34 -04:00
Kurt Himebauch bf4aa58c68 Add File Manager Return and Exit navigation (#1301) 2026-10-23 11:04:35 -04:00
Kurt Himebauch 747c6eca87 feat: show dot-prefixed folders in game picker (#1363) 2026-10-23 10:59:38 -04:00
Kurt Himebauch f3b5cadb7b fix: release the microphone before freezing a session so the SoC can sleep 2026-10-23 07:57:34 -04:00
SuziJay 0127fa24aa feat: show dot-prefixed folders in game picker 2026-10-23 09:36:54 +08:00
MaxsTechReview 22eac0831d fix: keep the default launcher entry named MainActivity so RedMagic Game Space keeps DroidDeck (#1345) 2026-10-22 20:54:12 +00:00
MaxsTechReview de505d79a1 chore: bump version to 0.6.0 (15) (#1344) 2026-10-22 16:07:07 -04:00
The412Banner 32c85dfba1 fix(xbox): put the helper's sign-in failure code in the shared log (#1342)
A user's log (#1300) showed "the Xbox helper stopped with exit code 1", but
not why. Before it asks for the Microsoft page the helper can only fail while
opening the credential store or registering the device, and it prints a code
for that (NEXUS_CREDENTIAL_ERROR_V1 keyring-unavailable, device-
authentication, ...). Sign-in runs in its own process, where
StoresState.logLine writes no file, so that line only reached logcat. The
sign-in window now writes the helper's known diagnostics to the stores log
itself, and the credential bridge writes why it refused the helper there too.
Lines are stamped by a shared StoreLogFiles.stamped.
2026-10-22 15:56:09 -04:00
MaxsTechReview d9c03369a2 Merge branch 'main' into Test 2026-10-22 14:52:13 -04:00
Kurt Himebauch f61525162d feat: games show their logo, and store games get one from the store (#999) 2026-10-22 14:48:23 -04:00
The412BannerandxXJsonDeruloXx 58f0f27f39 fix(xbox): show why Microsoft sign-in fails, and the gamertag instead of the email (#1322)
Sign-in sat on a black "Preparing Microsoft sign-in…" screen while the Xbox
components downloaded and the helper started, then only said "did not
complete" (#1300). The window now names each step (with download progress),
and a failure says which step failed and why. Our own messages are shown as
written; anything else is named by its exception type only. The line also goes
to the stores log, which the shared log zip carries.

The Stores page showed the account's sign-in email. It now shows the Xbox
gamertag, read with `xodus-cli profile` after the library sync on its own
thread (75 s limit: the command allows itself 45 s, plus PRoot start-up), once
per signed-in account. Until it is known the email is shown masked, and only
the store label while credentials are temporarily unreadable. The account key
used for caches and launches is unchanged.

Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
2026-10-22 14:30:38 -04:00
xXJSONDeruloXx 13249402ad Merge main and share GPU busy reads 2026-10-22 13:31:17 -04:00
Kurt Himebauch a50f7333ea fix: latch kgsl GPU busy windows so MangoHud's GPU load stops reading 0% (#1327) 2026-10-22 13:28:24 -04:00
Kurt Himebauch 3c3f550e96 Merge current main into PR #1326 2026-10-22 12:13:54 -04:00
Kurt Himebauch 3501d13d29 Merge current main into PR #1327 2026-10-22 12:13:54 -04:00
Kurt Himebauch e8a8de1b22 Merge current main into PR #1317 2026-10-22 12:13:54 -04:00
Kurt Himebauch 848f13d6a4 Merge current main into PR #1301 2026-10-22 12:13:53 -04:00
Kurt Himebauch de4b778bd1 Merge current main into PR #999 2026-10-22 12:13:48 -04:00
angelrenefm 6c22248d77 fix: feeds that share a reader also share a failed read 2026-10-22 17:44:34 +02:00
Kurt Himebauch 701e70bd3e fix: the emulated Steam Controller reports the firmware build Steam ships (#1323)
* fix: the emulated Steam Controller reports the firmware build Steam ships

* fix Triton firmware lookup for Steam root binary
2026-10-22 11:43:05 -04:00
Kurt Himebauch c3f3c04fc5 fix: latch kgsl GPU busy windows so MangoHud's GPU load stops reading 0% 2026-10-22 11:39:50 -04:00
angelrenefm 2dc2a44115 fix: the performance overlay shows the GPU load on Adreno phones whose kgsl busy percentage is unreadable
GpuStatsComponent read kgsl's gpubusy once per feed, so the msm reader path
(perf_now), read second, got the empty read and showed 0 %. Feeds that share a
reader now share one read, and one empty read between gpubusy windows keeps the
last load.
2026-10-22 17:35:05 +02:00
Kurt Himebauchandzooluis e49557a02d fix: register Android CJK fonts with fontconfig (#1319)
* fix: write CJK fontconfig as valid Kotlin

* fix: choose CJK fonts for requested language

* fix: prefer exact Chinese fontconfig locales

---------

Co-authored-by: zooluis <150430329+zooluis@users.noreply.github.com>
2026-10-22 10:47:12 -04:00
3c622a9323 feat: the unfolded controls can stay while a controller is connected (#996)
On foldables the unfolded-screen controls panel hides whenever a
controller is connected (#829). Phones held in a telescopic controller
still want its trackpads and back grips, so a new setting under
"Controls on the unfolded screen" keeps the panel with a controller.
It is off by default and appears in the launcher settings and the
in-session menu while unfolded controls are on.

The visibility decision moves into SessionDisplay.unfoldedControlsWanted
with unit tests.

Closes #861

Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com>
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
2026-10-22 10:43:41 -04:00
Kurt Himebauch 8ed98b570b Keep visible external-display sessions running (#1320)
Track visibility on the activity display

Separate activity visibility from the compositor display so a powered external screen keeps the session visible when the handset is locked. Notify the service as the activity moves between displays and avoid reporting a removed host display before Android moves the activity.
2026-10-22 10:42:57 -04:00