Compare commits

..
Author SHA1 Message Date
MaxsTechReviewandKurt Himebauch 006db14561 perf: SD card library files resolve through the fast path instead of proot (#1331)
perf: files in an SD card library resolve through the fast path instead of proot

A library bound at several guest names (droiddeck-sd, bannerlator-sd and
case variants) made every symlink and /proc/self/fd readlink under it fall
back to proot. The fast path now orders its binds the way proot does and
gives each host path the name proot would, and lstat("link/.") follows
the link as the kernel does.

Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-22 19:11:29 +00: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 <[email protected]>
2026-10-22 14:30:38 -04:00
Kurt Himebauch 633f66fd49 Fix main APK 7zip dependency download (#1334) 2026-10-22 14:13:05 -04:00
Kurt Himebauch 5461886a82 Pin CI 7zip download to verified package mirror 2026-10-22 13:54:22 -04:00
Kurt Himebauch 498a33170f fix: the performance overlay shows the GPU load on Adreno phones whose kgsl busy percentage is unreadable (#1326) 2026-10-22 13:31: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 a4eee5c2a4 feat: page the session out to zram before suspending it, and zram is always on where supported (#1317) 2026-10-22 13:28:20 -04:00
Kurt Himebauch 65aeeabfb5 Merge current main into PR #1326 2026-10-22 13:11:10 -04:00
Kurt Himebauch 90efeacb41 Merge current main into PR #1327 2026-10-22 13:11:10 -04:00
Kurt Himebauch 8c0fcd8475 Merge current main into PR #1317 2026-10-22 13:11:09 -04:00
Kurt Himebauch 9851339d47 Merge current main into PR #999 2026-10-22 13:11:05 -04:00
Kurt Himebauch e4a7e52f81 Fix nested KWin rendering on Android (#1330)
Select the explicitly configured Android render node for nested KWin 6.8, apply OpenGL settings before Plasma starts, and pin the original signed Mesa 26.2.4 package that passed Thor rendering. Remove build-directory runtime search paths and fail closed on ELF inspection errors.

Native ARM64 build passed. Thor validated a visible nested Wayland terminal, active OpenGL ES/Zink on Turnip, 1280x720 output, and keyboard input. Mesa pin regression tests, launcher and ELF failure controls, shell syntax, and human authorship checks passed. Published desktop pin remains r2; full latest-head CI is pending and maintainer authorized this merge after device validation.
2026-10-22 13:10:16 -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 de4b778bd1 Merge current main into PR #999 2026-10-22 12:13:48 -04:00
Kurt Himebauch e1a7d527fd Update the nested KWin builder to 6.8.0 (#1328)
Match the native builder and desktop package override to Arch Linux ARM KWin 6.8.0, pin the official source hash, and port the nested-scaling patch. Check package/source version consistency in both build jobs.

Official source checksum, patch application, live package closure, and shell syntax verified. The published desktop remains pinned to r2; candidate 6.8 device/rendering validation remains pending before any package publication.
2026-10-22 12:13:03 -04:00
Kurt Himebauch 792a69ebfe Combine authorship validation with the required APK result (#1329)
Combine trusted source and squash-metadata validation with the exact-head APK compilation result. Preserve automatic trailer cleanup in the installed hooks.

Validated with 10 authorship/hook tests and 9 combined-gate tests.
2026-10-22 12:11:05 -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 Himebauch 3a09e5698b fix: stop Steam's Steam Controller welcome tour from opening on every boot (#1321)
* fix: stop Steam's Steam Controller welcome tour from opening on every boot

* fix steam tour failure not to block helper attach
2026-10-22 11:27:59 -04:00
Kurt Himebauch 4421e851dd Enforce human authorship with automatic trailer scrubbing (#1325)
Install commit and push hooks that remove bot co-author trailers, preserve human credits, and reject bot primary authors. Add a trusted default-branch CI checker and publish its required status on the exact proposed commit. Validate main pushes as well. Ten regression tests pass, including real Git hook tests and the --no-verify case.
2026-10-22 11:12:41 -04:00
7bb1b5b74b feat: tools/device-smoke.sh checks a build on a real device over plain adb (#911)
* feat: tools/device-smoke.sh checks a build on a real device over plain adb

Installs and launches the app, changes the density and the window size, cycles a foldable's device state, then reads the newest debug session (first frame, last frame rate, memory page size, process exits). It restores the display and the device state it changed. No python needed; complements tools/droiddeck-scenario, which drives games.

* fix: device-smoke.sh restores only what it changed and checks more carefully

Restore the device state by cancelling the override the script made, or
by putting back the override that was already pinned (a test lab pins
one), instead of pinning the committed state. Restore density and size
only after the script changed them. Keep adb's exit status, count only
crashes of the tested app and fail when the crash buffer cannot be read,
compare the top activity after the density change, and exit 2 when an
option value is missing.

* fix: check wm density/size exit statuses and keep restore flags on failure

* fix: verify smoke-test display changes and restoration

* fix: reject false smoke-test launches and quote remote paths

* Run final display cleanup before reporting smoke results

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:48:38 -04: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 <[email protected]>
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 <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
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
xXJsonDeruloXx e6265c1262 Merge current main while preserving subscription access and game logos 2026-10-22 10:41:17 -04:00
3331edf265 perf: session log step times carry milliseconds (#993)
The == STEP lines in session.log now read HH:MM:SS.mmm, so the setup
steps before Steam starts can be timed; with whole seconds most of them
showed the same time.

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:40:48 -04:00
d60f683b62 fix(gamescope): release cursor SHM buffers during long sessions (#1189)
* fix(gamescope): avoid retaining pointer cursor SHM commits

* test(gamescope): verify cursor release and pending window control

* test(gamescope): keep live probe inert during test discovery

* test: match Gamescope process name in cursor probe

* test: clarify cursor-role probe serial behavior

---------

Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:34:24 -04:00
xXJsonDeruloXx e163b4fdba Preserve newer sleep requests when a freeze is cancelled 2026-10-22 10:32:29 -04:00
4513940717 a11y: give the button-like clickables a Button role for screen readers (#891)
* a11y: give the button-like clickables a Button role for screen readers

33 clickable / combinedClickable modifiers on buttons, tiles, list rows and
rail items had no semantic role, so TalkBack could not announce them as
buttons. Pass role = Role.Button on those. Scrims and pointer-consuming
backdrops are left alone. No visual or focus change.

* a11y: expose selection state for the drawer tabs, driver rows and suggested icons

The three selectable items (drawer page tabs, driver radio rows, suggested app icons) were given a plain Button role. Use selectable with Role.Tab or Role.RadioButton and the selected value so a screen reader announces which one is chosen.

* a11y: the rail's top tabs expose selection to screen readers

The tab cells were plain buttons, so a screen reader could not tell which
section was open. Tabs use selectable with Role.Tab; the link cells keep
the Button role.

* fix: expose selected store header tabs

* Group selectable accessibility controls

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:30:55 -04:00
3d9f200a7e feat: add SDK-free 3DoF tracking for VITURE Pro 2 (#1115)
* feat: itch.io store, library and installs for DRM-free Windows builds (#1064)

* feat: add experimental SDK-free VITURE Pro 2 tracking

* docs: record full-app Pro 2 hardware validation

* docs: record successful Pro 2 user game test

* fix: translate Pro 2 tracking notice for all supported locales

* fix: respect model capabilities for glasses controls

* fix: handle fold configuration during glasses probe

---------

Co-authored-by: kingokksa <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:28:14 -04:00
FX1922andxXJsonDeruloXx 7482f797c7 feat: custom Gradient theme with primary and secondary colors (#1309)
* feat: a custom Gradient theme with a primary and a secondary colour, picked with a wheel, sliders and a hex box

* Gradient theme: OK waits for a valid hex, deep blues are made paler, button text suits the worse gradient colour

* Gradient theme: nudge only the gradient colour that button text cannot read on

* fix: meet readable contrast across gradient themes

* fix: preserve contrast through gradient interpolation

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:26:53 -04:00
Danbro007 d2abaef2a3 fix: recognize SM8975 and mark Adreno 850 support experimental (#1249)
* fix: recognize SM8975 and mark Adreno 850 support experimental

* fix: retain A850 experimental status with spoofed Vulkan names
2026-10-22 10:19:23 -04:00
Kurt HimebauchandDelete228 0577a22459 fix: keep Steam shortcuts out of desktop controls (#1318)
* Enable existing second-screen controls in desktop sessions

* Keep controller drawer navigation consistent in desktop sessions

* Cover desktop and Steam second-screen controls with regression tests

* fix: hide Steam shortcuts in desktop controls

---------

Co-authored-by: Delete228 <[email protected]>
2026-10-22 10:16:25 -04:00
de8b5568c1 Fix: Keep Android library names visible in diagnostic logs (#1063)
* Preserve Android shared-library names in logs

* Restrict library redaction exceptions to loader diagnostics

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-22 10:12:57 -04:00
87e4b42ed8 fix: third-party GitHub mirrors only serve downloads with a trusted digest (#1057)
fix: a third-party GitHub mirror can only serve downloads that carry a trusted digest

Downloads without an expected SHA-256 were still sent through gh-proxy,
ghfast and other mirrors, and the first answer was accepted, so a mirror
could swap an AppImage or a Windows component. Downloader.downloadFile
now tries only the original URL when there is no digest. UserApps passes
GitHub's own asset digest, and WinComponents passes the snapshot's
archive digest, so those large files keep their mirror fallback.

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-22 10:12:33 -04:00
Jordan Y d9328c39df fix: Game cores also apply to games started through "Linux x86 (FEX)" (#1294)
* Run FEX-tool games on the chosen game cores

Performance > Game cores reached only the Proton launchers: games started
through "Linux x86 (FEX)" ran on every core, including the slowest cluster.

* Test that SteamVR FEX launches keep the game cores
2026-10-22 10:08:25 -04:00
9ed658c45c fix: seed HKCU redist markers in the user registry (#1286)
* fix: seed current-user redist markers in user.reg

* test: skip permission case when running as root

---------

Co-authored-by: Jammo <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:07:56 -04:00
xXJsonDeruloXx 1b14939731 fix: suspend page-out waits for each handler's ack and can be cancelled 2026-10-22 10:01:49 -04:00
kingokksa eb0eecda7a fix: let a connected controller keep the first player slot (#1304) 2026-10-22 09:37:01 -04:00
Resul Övürandovurrsl 4361182b0e fix(ui): stop touch-blocking dialog and drawer containers from merging descendant semantics (#1145)
Co-authored-by: ovurrsl <[email protected]>
2026-10-22 09:35:11 -04:00
Resul Övürandovurrsl 0444b3eff9 l10n: Turkish for the Storage page, GPU driver mode and the other untranslated screens (#989)
l10n(tr): translate the Storage page, GPU driver mode and other missing strings

Adds 213 strings and 9 plurals that were missing from values-tr, so the
new Storage page, GPU driver mode UI, SteamGridDB art, downloads source,
VR and latency toggles no longer fall back to English on Turkish devices.

Co-authored-by: ovurrsl <[email protected]>
2026-10-22 09:34:17 -04:00
xXJsonDeruloXx 240b7fcce6 feat: page the session out to zram before suspending it, and zram is always on where supported 2026-10-22 09:02:37 -04:00
Kurt Himebauch 22f7d5b909 feat: make desktop mode easy to uninstall safely (#1311) 2026-10-22 08:17:14 -04:00
Kurt Himebauch d012a1f5b6 fix: pause Steam Controller BLE reconnects while the session is suspended (#1312) 2026-10-22 12:05:17 +00:00
The412Banner aaa2e40def feat: DroidDeck's linux-nightly components, with a Stable/Nightly switch (#1284)
The component catalog also reads the linux-nightly release of
Droid-Deck/DroidDeck-Components (hourly builds of DXVK, VKD3D-Proton and
FEX for glibc Protons). That release holds all three components, so each
file goes to its list by name: DD-dxvk-, DD-vkd3d-proton-, DD-FEXCore-.

A nightly that matches a stable build (same flavor and version, e.g.
DD-dxvk-3.1.1+153-nightly and dxvk-3.1.1-linux) has no row of its own;
if two nightlies match, the newer one is used. On the components page
the stable row gets a Stable | Nightly switch before Get, styled like
the Get button, one focus stop that flips with A or a tap and reports
its state as a switch. In the in-session drawer's download menus the
row gets Stable and Nightly tabs beside it (the existing TabStrip, now
with optional bumpers/compact), each its own focus stop. Get or A on the
row downloads whichever channel is selected. A nightly without a stable
counterpart keeps its own row, listed below the stable ones.

Downloads are allowed from that repository's releases too. A failed
nightly download says the file may have been replaced and asks for a
refresh. Strings in all locales.
2026-10-22 04:23:46 -04:00
Tomasz Kloc 593014fe8c Let GOG and Epic installs pick a game language (#1199) 2026-10-22 00:38:01 -04:00
Kurt Himebauch 138d313b7f Default Deck controls to both trackpads (#1295) 2026-10-22 00:35:46 -04:00
Resul Övür e0b3f928a6 fix: auto-driver trial rolls back when the display driver crashes at startup (#1031) 2026-10-21 23:56:43 -04:00
Resul Övür e865fa6b62 fix: Steam Controller (Triton) output/feature reports are no longer overwritten unread (#1032) 2026-10-21 23:55:00 -04:00
Resul Övür 37535a3d38 fix: a full disk no longer crashes the app during cover art fetches (#1030) 2026-10-21 23:52:35 -04:00
kingokksa 60043873f2 Switch the GOG store to GOG's official listing endpoint: fewer requests, the tab now opens in seconds (#786) 2026-10-21 23:51:16 -04:00
The412Banner cd18e87864 Return to the launcher when a front-end game closes (#395) 2026-10-21 23:20:18 -04:00
Resul Övür ec5a08ddf1 fix: in-app update works on Android 9 and 10 (#1066) 2026-10-21 23:18:02 -04:00
Kurt Himebauch 0de6413914 Revert dedicated second-screen activity (#1288) 2026-10-22 02:53:35 +00:00
Moroni Granja 4cae235154 fix: Xbox search results open with their metadata and Game Pass installs (#1279) 2026-10-21 22:43:51 -04:00
MaxsTechReview 3305724f06 perf: FEX's file lookups and opens go through the fast path instead of proot (#1283)
* perf: FEX's in-root path lookups go through the fast path instead of proot

Android has no openat2, so libblsession walks RESOLVE_IN_ROOT lookups for
FEX one component at a time. Every fstatat, readlinkat and openat in that
walk was a raw syscall that stopped in proot. When libblfastpath is loaded,
the walk now calls its fstatat, readlinkat and openat, which answer without
proot and fall back to the raw syscall themselves. Without it the walk uses
the raw syscalls as before.

One lookup drops from 250-940 us to 26-86 us. A Graveyard Keeper launch
makes 37.5k proot stops instead of 54.0k and reaches its first scene
0.31 s sooner.

* test: count fstatat64 where the preload asks for it

* perf: FEX's own file opens go through the fast path instead of proot

FEX opens host files for the game with syscall(SYS_openat), so every one
stopped in proot even when the fast path could answer it. libblsession's
syscall() now sends SYS_openat to the fast path's openat as well, the same
way the in-root walk does, and makes the raw syscall when the fast path is
not loaded.

A Graveyard Keeper launch makes 28.0k proot stops instead of 37.5k; FEX's
openat stops go from 6,739 to none.

* fix: the fast path's start-up reads don't come back through syscall()

libblsession's syscall() now sends SYS_openat to the fast path. A process
named FEX starts the fast path by reading /proc/self/cmdline with
syscall(SYS_openat), which came straight back into the fast path and
waited on its own pthread_once. Its start-up reads now call libc's
syscall directly.
2026-10-21 22:33:39 -04:00
Kurt Himebauch 1251e9d3ee Default session orientation to landscape (#1287) 2026-10-21 21:27:25 -04:00
kingokksa 2b0c55f72e feat: let the built-in controller be turned off (#1207) 2026-10-21 19:49:31 -04:00
MaxsTechReview ed29f77fc2 fix: Chromium browsers start on the desktop (#1280)
A process's status read for another process is served read-only, so
Chromium's ReadOnlyFile check no longer kills the browser. Chromium-based
AppImages without chrome-sandbox get --no-sandbox, and Flathub Chromium and
Electron apps skip zypak's spawn portal, which cannot work here.
2026-10-21 18:17:07 -04:00
kingokksa 400325fa8f fix: some games never loaded their own Steam touch layout, and the sticks could not be placed (#1058) 2026-10-21 17:39:05 -04:00
Resul Övür 2f4fe2c5be fix: manual cloud-save upload/download no longer reports a skipped sync as success (#1068) 2026-10-21 17:32:28 -04:00
Kurt Himebauch e5b54f6248 feat: SteamVR controller types, phone controls on glasses, flat games as VR panels (#1268) 2026-10-21 21:22:21 +00:00
MaxsTechReview 2111197bea perf: performance overlay updates ride the next game frame instead of an extra composite (#1274)
* perf: performance overlay updates ride the next game frame instead of an extra composite

* gamescope: a held overlay repaint is only cleared once an output was painted

* gamescope: release.env points at p12
2026-10-21 17:20:12 -04:00
MaxsTechReview 7fa8008944 perf: network tables Android refuses are served from the synthesized copy without retrying the open (#1275) 2026-10-21 17:18:11 -04:00
MaxsTechReview e04727dbaf perf: SysV message queues stay mapped instead of reopening per call (#1272) 2026-10-21 17:14:03 -04:00
MaxsTechReview bd1350ac25 perf: games skip Steam's install-script evaluator once it has finished (#1273) 2026-10-21 16:59:52 -04:00
greg 99136ea23b Add support for Java 17 via mise (#1231) 2026-10-21 18:30:34 +00:00
kingokksa cad260b450 fix: give the touch gesture dialog room to scroll on phones (#1214) 2026-10-21 14:14:03 -04:00
Kurt Himebauch d44c20f2bb feat: Rokid Max and Air glasses with 3DoF, SBS modes, 120 Hz and brightness (#1266) 2026-10-21 14:11:01 -04:00
Kurt Himebauch e9891d5a29 feat: Remote Play host: PipeWire capture, hardware encoding, touch and game audio (#1139)
* feat: run SteamOS PipeWire and WirePlumber for gamescope screen capture

* feat: hardware Remote Play encoding through MediaCodec behind Steam's V4L2 stage

* feat: deliver Remote Play touches to gamescope as touches

* feat: route game audio through PulseAudio while a Remote Play client is connected

* fix: keep the Remote Play marker when a forked child closes the encoder

* fix: allow standalone preload tests without V4L2 encoder hook
2026-10-21 17:57:24 +00:00
Kurt Himebauch d70f5f0636 feat: local builds reuse the add-on packs a CI build installed (#1265) 2026-10-21 17:23:16 +00:00
FX1922 7bb66de192 fix: keep controller focus after installing a boot movie; Xbox and itch artwork covered (#1229) 2026-10-21 13:13:31 -04:00
kingokksa ff4a7943f5 fix: store dots stay dim when signed out and stores open on the first arranged tab (#1236) 2026-10-21 13:12:49 -04:00
0x1Nawaf 83b2164e4a feat: the performance HUD adds a colored frame-time graph, battery temperature, and opacity controls (#1256) 2026-10-21 13:12:23 -04:00
Kurt Himebauch cf17f583d4 feat: Decky Loader update channel selector (#1264) 2026-10-21 17:08:46 +00:00
Kurt Himebauch 5d8fd3f5f0 feat: warn before installing a Decky plugin ZIP (#1263) 2026-10-21 12:47:10 -04:00
Kurt Himebauch bd76b39b84 fix: keep Steam downloads running across suspend with Manual policy (#1262) 2026-10-21 15:45:39 +00:00
MaxsTechReview daeef1995f fix: moving a mouse no longer recomposes the whole session overlay (#1253) 2026-10-21 06:36:30 -04:00
MaxsTechReview 3eb0400df8 fix: clearing a game's compatibility tool in Steam keeps the Linux build (#1250) 2026-10-21 06:07:16 -04:00
MaxsTechReview 093a986b88 fix: Google sign-in from a store shows the real device instead of a Galaxy S23 Ultra (#1252) 2026-10-21 05:42:41 -04:00
MaxsTechReview 11674b711b fix: browsers added as non-Steam games show their window and load pages in Gaming Mode (#1243) 2026-10-21 02:02:18 -04:00
MaxsTechReview 20577ec045 fix: a failing image or script on a store sign-in page no longer replaces the page with an SSL error (#1238) 2026-10-21 01:04:03 -04:00
Resul Övür abeaf7ccb6 fix: a failed Steam client download ends the session with a message instead of silently (#997) 2026-10-21 00:56:05 -04:00
MaxsTechReview 152b961940 fix: GOG sign-in starts without the previous account's web session (#1235) 2026-10-21 00:44:40 -04:00
MaxsTechReview 705da08a0e fix: game files keep the full game name instead of cutting it at 40 characters (#1230) 2026-10-20 23:24:29 -04:00
Resul Övür 25938cc249 fix: file manager copy/move keeps symlinks (#1027) 2026-10-20 23:24:25 -04:00
Resul Övür 3149567328 fix: cancelling a paste stops the copy and keeps a move's source (#1029) 2026-10-20 20:05:05 -04:00
Juan Martí 8ccd771059 fix: open game Components on the game's profile (#1146) 2026-10-20 19:31:17 -04:00
Resul Övür aa5e9c3e91 fix: paste bar ignores taps while a paste is running (#1025) 2026-10-20 19:22:33 -04:00
Resul Övür e032f25114 fix: ignore uinput-* system virtual input devices in controller detection (#954) 2026-10-20 18:39:35 -04:00
Resul Övür 87763976d8 fix: empty App ID no longer crashes the game environment editor (#1014) 2026-10-20 18:31:27 -04:00
Resul Övür bdd2485e96 fix: a slow folder listing no longer replaces the folder being shown (#1028) 2026-10-20 18:31:21 -04:00
Resul Övür 6eefcf4267 fix: file manager selection actions respect the search filter (#1021) 2026-10-20 18:31:14 -04:00
MaxsTechReview 1c06ddeaf2 fix: Mono P/Invoke readv calls reach the controller hook too (#1227) 2026-10-20 18:18:10 -04:00
Resul Övür 57e1b4a71b fix: moving a stick no longer releases a D-pad or trigger held as a key (#1060) 2026-10-20 18:17:37 -04:00
maxjivi05 750780b722 fix: Mono P/Invoke readv calls reach the controller hook too 2026-10-20 18:02:29 -04:00
maxjivi05 1df7760e99 Merge branch 'main' into fix/readv-follow-up 2026-10-20 18:02:29 -04:00
Resul Övür 4575bfc32d fix: file provider rejects document ids with . or .. segments (#1016) 2026-10-20 17:58:36 -04:00
Charan Rathore 2699666766 fix: treat Unreal bootstrap launcher PE name as junk (#1020) 2026-10-20 17:52:47 -04:00
Delete228 92dbd1cd5a fix: restore Factorio ARM64 controller input through readv (#1195) 2026-10-20 17:52:40 -04:00
Resul Övür 67ab3fa634 fix: shared session logs are always scrubbed, whatever their size (#1065) 2026-10-20 17:16:36 -04:00
MaxsTechReview b3122aece0 fix: Rewired games see DroidDeck's pads (udev listing, dlsym-resolved libc calls) (#1222) 2026-10-20 16:53:37 -04:00
Resul Övür 5d87822a8a fix: added-games scan keeps games like Reach, Breach, Crashday and Observer (#1023) 2026-10-20 16:53:29 -04:00
Resul Övür 385af59e9f fix: Steam Touch labels no longer corrupt the controller config (#1017) 2026-10-20 16:53:22 -04:00
Kurt HimebauchandDetinator10 4f8a7c5596 fix: let the device sleep while a session is suspended (#1220)
* fix: stop the DirectAudio output while the sink is suspended

* fix: verify every session thread before reporting suspension

* fix: freeze PRoot first and stop each guest thread on suspend

---------

Co-authored-by: Detinator10 <[email protected]>
2026-10-20 16:43:09 -04:00
MaxsTechReview 8ea48dc039 fix: leave the PC keyboard's pad handling to #956 and fix the red keyboard test (#1221) 2026-10-20 16:30:51 -04:00
Resul Övür 746a850a44 fix: preserve Electric contrast for filled and text buttons (#1149) 2026-10-20 15:45:08 -04:00
kingokksa 2457f27cdf feat: uninstall or remove a game from the Games page (#1190) 2026-10-20 15:37:21 -04:00
Resul Övür 6090da0eb5 fix: hide on-screen controls while the PC keyboard is open (#956) 2026-10-20 15:06:38 -04:00
Kurt Himebauch 2a8618ab5c feat: launch Easy Anti-Cheat games offline when they support it (#1039) 2026-10-20 18:58:56 +00:00
MaxsTechReview 8ca389149b fix: native Linux games under FEX reach FEXServer, and a refused compat app no longer loops the sync (#1213)
FEX launched through a script (Valve's _v2-entry-point) was judged 32-bit, so the
fast path stayed off in the FEX client while FEXServer bound its socket through it.
The fast path now follows #! lines before judging the program.

The live compat sync drops apps the client refuses GetCompatTools for instead of
reattaching forever.
2026-10-20 14:58:30 -04:00
MaxsTechReview 862afdf0a9 fix: the on-screen pad steps aside while Steam's or DroidDeck's keyboard is open (#1217) 2026-10-20 14:29:50 -04:00
MaxsTechReview b8b3ecf915 fix: a store game's first launch creates its prefix before the cloud save download (#1215) 2026-10-20 14:11:46 -04:00
MaxsTechReview 78932ef836 fix: a session on an external display stays awake while the phone screen is off (#1211) 2026-10-20 14:11:42 -04:00
MaxsTechReview 2862b77495 Work around Android's denial of executable stacks (#1208) 2026-10-20 12:44:49 -04:00
Kurt Himebauch 4131d9a978 fix: prefetch Comet and Xbox packs for existing users (#1204) 2026-10-20 16:29:08 +00:00
Kurt Himebauch e3246b7dd5 feat: ship proot, DirectAudio, Xbox, Comet and hand tracking as CI-built add-on packs (#1198)
* perf: download hand tracking models and native libs on first use

* feat: build and publish add-on packs in CI, add Xbox and Comet packs

* feat: ship proot and DirectAudio drivers as CI-built packs

* test: hand tracking readiness requires the tracking pack

* ci: attach PR test-build packs to the PR release instead of Components
2026-10-20 11:18:44 -04:00
Kurt Himebauch 0663593920 Fix Game Pass library sync when subscription plan rows are omitted (#1193) 2026-10-20 11:14:06 -04:00
kingokksa 7775a87129 feat: arrange and hide/show store tabs from stores settings (#1121) 2026-10-20 11:12:27 -04:00
Delete228 a59ed6075e test: cover controller destination faults and partial reads
Exercise keyframe and ring reads with unmapped, PROT_NONE, read-only and page-boundary destinations. Verify complete-event progress within one vector and across vectors, including a NULL destination. The Linux fakeinput suite passes 11 tests with 3 i386 skips; both native ARM64 probes pass on the Thor.
2026-10-20 17:55:25 +03:00
Delete228 58382e5182 fix: safely copy controller events to caller buffers
Stage ring and keyframe events before copying them with process_vm_writev. Return EFAULT for inaccessible destinations and advance the reader only for complete events delivered, preserving partial readv progress. Retested Factorio on the Thor with the exact ARM64 library.
2026-10-20 17:53:40 +03:00
MaxsTechReview e308224391 fix: RetroArch fills the screen from the emulator rail (#1194) 2026-10-20 10:38:52 -04:00
Delete228 0b3e7428a6 test: reject inaccessible readv descriptor arrays
Cover unmapped, PROT_NONE, and partially accessible iovec arrays. The probe crashes with SIGSEGV before the descriptor-copy fix and returns EFAULT afterward. The Linux fakeinput suite and native ARM64 probe pass; Factorio was manually retested on the Thor with the updated library.
2026-10-20 17:34:14 +03:00
Delete228 e2d6b5dabf fix: safely copy readv descriptors before validation
Copy the caller's iovec array through process_vm_readv before inspecting it, so unmapped, protected, or partially accessible arrays return EFAULT instead of crashing the process. Verified valid and invalid copies in the Android guest.
2026-10-20 17:29:42 +03:00
Delete228 d8978639c4 build: require the controller readv export
Check that packaged libfakeinput libraries export readv alongside the existing read hook.
2026-10-20 16:43:35 +03:00
Delete228 4a1aba3c02 test: cover vectored controller reads
Cover readv on original and virtual controller rings, an overlay-style read-to-readv fallback, event-aligned vectors, empty vectors, nonblocking errors, and ordinary descriptor passthrough.
2026-10-20 16:42:24 +03:00
MaxsTechReview 4f799ea59c fix: open Flatpak apps in renamed app variants (#1192)
Flatpak starts bwrap with an empty environment, so the bwrap shim lost DROIDDECK_BWRAP_SOCKET and dialled the default package's socket. The session now writes the socket name to /etc/droiddeck/bwrap-socket and the shim reads it when the variable is missing.

Fixes #1092
2026-10-20 09:38:14 -04:00
Delete228 d5e87a8c7c fix: restore Factorio ARM64 controller input through readv
Steam's ARM64 overlay can fall back from read() to readv(). Route registered controller reads through the existing private ring reader so the game receives input events instead of backing-file bytes. Preserve ordinary descriptor passthrough and evdev per-vector reads.
2026-10-20 16:38:14 +03:00
FX1922 e53232b301 feat: Better boot movies system; Added steamdeckrepo support (#1036) 2026-10-20 09:36:25 -04:00
MaxsTechReview b868863133 perf: tile rendering for games, faster session start (#1188)
Games launched from Steam get Turnip's own per-render-pass choice between
tile memory and system memory again; TU_DEBUG=sysmem now stays with the
Steam client, whose interface it was set for. A per-game TU_DEBUG, the
debug override file and the Adreno 710/720 driver still win. On the
REDMAGIC tablet (Adreno 840) PEAK runs 46.8 -> 52.3 fps over 7 alternating
pairs and Valheim's 3D menu needs 12.6 % less GPU time per frame; light
games are unchanged.

droiddeck-seed-redists remembers the identity (inode, size, mtime) of each
prefix registry it has verified and skips re-reading it while it is
unchanged. Steam started after it on every session, ~1.8 s later.

SessionFiles keeps a ledger of the files it staged for the installed APK
and copies only the ones that changed, ~180 ms less per session start.

Session start to Steam ready: 5.57 -> 3.57 s.
2026-10-20 09:19:46 -04:00
MaxsTechReview ca352bf225 fix: RPCS3 opens from the emulator rail again (#1191)
RPCS3's AppImage could not start: its own runtime unpacks the image with
a dwarfsextract it runs from a memfd, which Android refuses (EACCES), and
it has no other way to run it. Emulator AppImages for arm64 now start
through the shipped uruntime, which falls back to a file in /tmp.

RPCS3 then stopped with "Null object" in utils::shm (errno 13): it reads
/proc/sys/vm/overcommit_memory to pick memfd for guest memory, and apps
cannot read it. The guest now gets the kernel's default policy (0) there
when the real file is unreadable, like file-max.
2026-10-20 08:52:31 -04:00
MaxsTechReview a5c587bd71 fix: add a picked exe the session cannot see by binding its folder (#1173)
Adding a game from a single exe outside internal storage and the game
library, such as a microSD card, only showed "This location is not
available in the session" and added nothing. #1153 made "Add all games in
this folder" bind such a folder under /root/Games, but a picked exe still
had no guest path.

addExe now binds the folder the same way when the session cannot see the
exe (the game it belongs to, or else the exe's own folder), then adds the
game. The bind is mounted from the next session start.
A folder the session already sees is not bound again, and a folder the app
cannot read is still refused.
2026-10-20 00:45:56 -04:00
MaxsTechReview bc3715aad6 fix: route ALSA to PulseAudio for x86 Linux games under FEX (no game audio) (#1166)
fix: route ALSA to PulseAudio for x86 Linux games under FEX
2026-10-20 00:44:56 -04:00
MaxsTechReview 79f43d8def fix: GOG games miss the registry entries their install script sets (#1159)
A GOG download only places the files, so games that look up their own registry keys (Fallout: New Vegas reads HKLM\Software\Bethesda Softworks\FalloutNV "Installed Path") said they were not installed. Every GOG launch now reads the setRegistry actions of the game's goggame-<id>.script and imports them into the prefix's 32-bit view through Proton's reg.exe, once per change of the script, so games already downloaded are fixed on their next launch.
2026-10-20 00:38:08 -04:00
MaxsTechReview 6ef042487a fix: find and save Steam touch layouts of non-Steam shortcuts (#1171)
The touch layout editor opened "App -<n> Layout" for a non-Steam shortcut
(an added game, the Epic Games Launcher) with LB, LT, RT, RB, both sticks
and LS/RS greyed out, so they could not be placed. Steam keys a shortcut's
controller config by the shortcut's lowercase name and logs its app id
unsigned, but the editor looked both up by the signed app id, found
neither, and fell back to a blank layout with nothing bound.

SteamTouchConfig now reads the shortcut's name from shortcuts.vdf, loads
and saves its layout under that name the way Steam does (autosave folder
and configset entry), and matches Steam's log line by the unsigned id. The
editor is titled with the game's name, and the app id in the panel is
shown unsigned. A name that is not a plain folder name (a
path separator, "." or "..", a quote, a backslash or a control character)
keeps the numeric app id. Existing configset entries are matched
without regard to case, as Steam reads them.
2026-10-20 00:34:01 -04:00
MaxsTechReview 6ba159a2f2 fix: let Wine write hooks into code FEX has translated (Ubisoft "Error at hooking API") (#1163)
fix: let Wine write hooks into code FEX has translated

Wine 11's wineserver writes another process's memory with process_vm_writev,
which the kernel refuses on a page that is not writable. FEX's WoW64 layer
keeps the pages it has translated read-only to catch self-modifying code and
re-protects them right after a program unprotects them, so WriteProcessMemory
and NtWriteVirtualMemory into ntdll failed with a partial copy and Ubisoft
games stopped at "Error at hooking API NtProtectVirtualMemory". Wine only
falls back to /proc/<pid>/mem on ENOSYS or EPERM.

libblsession now completes a single-buffer write that wineserver was refused
with EFAULT through /proc/<pid>/mem, the way Wine wrote before Wine 11. Other
programs and every other error keep the kernel's answer. The iovecs are read
with process_vm_readv, so a bad pointer still returns EFAULT, and offsets are
64-bit in the i386 build.
2026-10-20 00:31:03 -04:00
MaxsTechReview b924dc02de fix: add games picked inside a folder game as their own games (#1155)
Picking an exe anywhere under an added folder treated it as that game, so a
folder added with "Add all games in this folder" (or a removed one) swallowed
every game below it: picking another game's exe switched the folder game's
target, or brought the removed folder game back under its old name and art.

Only treat a pick as the same game when it sits in the folder root, in the
same top-level subfolder as the game's current exe, or is another build of
that exe, and prefer the deepest matching folder.
2026-10-20 00:29:21 -04:00
MaxsTechReview eaa46e1fd6 fix: show the app's cursor in sessions started from the front end (#1154)
gamescope makes its cursor surfaces by attaching an shm buffer, committing,
destroying the buffer and only then calling wl_pointer.set_cursor. The
compositor kept that buffer only as a reference, so set_cursor found nothing
to publish: apps run from the front end (store apps, Flatpaks, emulators)
showed DroidDeck's fallback arrow, or no cursor at all after a desktop
session had hidden its own.

Copy a role-less surface's pixels when its held buffer is destroyed and
publish that copy when the surface becomes the cursor, for as long as the
surface lives.
2026-10-20 00:28:14 -04:00
Resul Övür 0953a38765 fix: confirm before bulk-removing unused components in Storage (#1140) 2026-10-19 23:51:11 -04:00
Resul Övür 68b4c87d9a fix: keep held Steam Touch controls pressed across action-set and layer changes (#1143) 2026-10-19 23:50:29 -04:00
Kurt Himebauch 6b8ce9e675 fix: an interrupted package install can no longer break the Linux runtime (#1078) 2026-10-19 23:40:30 -04:00
Grimarius fe7b75318a feat: add configurable touchscreen gestures (#940) 2026-10-20 03:22:41 +00:00
MaxsTechReview fd74819c50 fix: Steam no longer sleeps again right after a paused session resumes (#1162) 2026-10-19 23:06:48 -04:00
Resul Övürandovurrsl 421ffc7506 fix: keep ModeHeader search from stealing focus when recomposed (#1141)
Co-authored-by: ovurrsl <[email protected]>
2026-10-19 21:28:22 -04:00
Resul Övürandovurrsl 2d6393df24 fix: keep touchpad two-finger scroll from jumping the cursor or starting a drag (#1142)
Co-authored-by: ovurrsl <[email protected]>
2026-10-19 21:28:06 -04:00
Resul Övür c2a3740237 fix: uppercase section headings in the app language (#1148)
* fix: uppercase section headings in the app language

* fix: localize store section headings
2026-10-19 21:27:50 -04:00
MaxsTechReview da916e7ed7 fix: give x86 programs under FEX the runtime's OpenSSL 3 (libcrypto.so.3 not found) (#1167)
fix: give x86 programs under FEX the runtime's OpenSSL 3
2026-10-19 21:27:25 -04:00
MaxsTechReview cfc6dfb05f fix: let x86 apps under FEX use the device's fonts (Epic launcher CJK text shows boxes) (#1168)
fix: let x86 apps under FEX use the device's fonts for CJK text
2026-10-19 21:27:18 -04:00
MaxsTechReview af4f0a7edb fix: user apps open at the Desktop's resolution (#1172)
A user app (script, AppImage or Flatpak) started from its shortcut runs in
a Run session, which took the Steam resolution (720p by default). It
ignored the resolution chosen under Open Desktop and was letterboxed at
1280x720 on screens that are not 16:9.

User apps now use the Desktop's resolution once one has been chosen. Until
then, and for every other Run program, nothing changes.
2026-10-19 21:27:06 -04:00
Kurt Himebauch 80a62cd0f0 Restore Xbox launches from Steam (#1170) 2026-10-20 00:46:35 +00:00
Kurt Himebauch 7eae6ca3e7 Restrict Xbox credential IPC and address launch review findings (#1164)
* Restrict Xbox credentials to host backends and lease game launches

* Verify mapped PRoot backend images and retry lease heartbeat timeouts

* Bound Xbox backend shutdown and isolate service socket lifetimes

* Keep unique Xbox service sockets within Unix address limits

* Own licensed launch directories and recover abandoned views safely

* Serialize Xbox account revocation with sign-in commits and launch authorization
2026-10-19 20:10:54 -04:00
Kurt Himebauch a63f3b1fbc Add experimental Xbox and PC Game Pass store support (#1156) 2026-10-19 18:44:10 -04:00
The412Banner 8e02904a04 fix: adding a games folder on an SD card found nothing (#1153)
Adding a folder on an SD card outside the Steam library added nothing and said
"Nothing new in this folder": every game's exe had no guest path, so each one
was skipped. Since #542 nothing created the game-folder binds that let older
builds use such folders; only migrated ones remained.

addFolder now binds the picked folder under /root/Games (named as
GamesFolderMigration names them, with a suffix when the name is taken) when the
session cannot see it, then adds its games. The bind is mounted from the next
session start.
2026-10-19 19:43:03 +00:00
Kurt Himebauch aa6fae1924 Fix intermittent CI package updates and escaped test worker (#1151)
Fix escaped cloud-save test worker and CI package updates
2026-10-19 14:52:30 -04:00
Kurt Himebauch adcb04f624 fix: skip DirectAudio for Steam install scripts (#1046) 2026-10-19 14:19:10 -04:00
Kurt Himebauch 2ea86b4abb fix: run x86 Proton under FEX in the Steam runtime it requires (#1034) 2026-10-19 14:18:52 -04:00
Kurt Himebauch 7c20d4b5c3 Fix direct-touch right-click and Steam Touch mode restoration (#677) 2026-10-19 14:18:26 -04:00
Kurt Himebauch a789d60f6a fix: register SteamVR only during a DroidDeck VR session (#1137) 2026-10-19 12:31:23 -04:00
Kurt Himebauch 2415ff0518 fix: DD-FG 3x/4x flicker and rebrand Win-FG to DD-FG (#1132) 2026-10-19 11:21:32 -04:00
Kurt Himebauch ab6d100cea fix: keep the TP UBWC hint on for Samsung One UI 8 Gen 2 (#1128) 2026-10-19 09:58:24 -04:00
kingokksa 55bf51cf37 Ship the desktop's translations as per-language packs (#942) 2026-10-19 09:54:19 -04:00
Kurt Himebauch 7733e29d10 feat: capsule view with DVD-case store art across all stores (#1117) 2026-10-19 08:45:58 -04:00
Kurt Himebauch c5f7f43726 feat: play button on installed store game pages (#1125) 2026-10-19 12:23:02 +00:00
ovurrsl 8faf88de85 feat: the game page shows the game's logo
The hero on the Games page draws the game's logo instead of the plain title
when there is one: Steam's cached logo.png or a chosen logo for Steam games,
the resolved logo for added and store games. The title text stays as the
fallback and as the logo's description for screen readers.
2026-10-19 15:12:47 +03:00
ovurrsl be384ca2a2 feat: games that already have a cover still get a logo and icon
The automatic art fill no longer skips a game just because its folder has a
cover. Store games and added games with their own cover now get a missing logo
(the store's own logo first, then Steam, then SteamGridDB) and a missing icon
from SteamGridDB. Existing, folder and chosen art is never replaced; a slot
that stays empty is asked about again after a week, as before.
2026-10-19 15:12:47 +03:00
ovurrsl 8ceff5641a feat: store games get their logo from the store
GOG (api.gog.com v2 `_links.logo`), Epic (catalog keyImage `DieselGameBoxLogo`)
and Amazon (`productDetail.details.logoUrl`) logos are kept in the install
record as an optional `logo` field and written next to the game as logo.png
when the install finishes. The PNG is kept as it came, transparency included,
and anything that does not decode as an image is dropped. Older install records
without the field still read as before.
2026-10-19 15:12:46 +03:00
Kurt Himebauch 5970de928e feat: library and store tabs per store with installed games listed first (#1116) 2026-10-19 02:06:38 -04:00
kingokksaandxXJSONDeruloXx 7370145f89 feat: itch.io store, library and installs for DRM-free Windows builds (#1064)
Co-authored-by: xXJSONDeruloXx <[email protected]>
2026-10-19 05:25:00 +00:00
Kurt Himebauch 2cf009d025 feat: add light launcher icon option (#1104) 2026-10-19 02:54:50 +00:00
MaxsTechReview 1e06e908e8 Fill missing plural forms and translate English left in the app strings (#1102)
Nine storage plurals lacked the forms Arabic, French, Spanish, Italian
and Portuguese use (zero/two/few/many), and 71 strings across 16
languages were still the English text.
2026-10-18 21:28:36 -04:00
MaxsTechReview 5c07c122fe Steam x86-64 client: menus come up, sign-in stays put, games listed once (#1096)
* Steam x86-64 client: menus come up, sign-in stays put, games listed once

Switching the Steam client from arm64 to x86-64 left the client on a blank
screen behind "Downloading the Steam client update 100%", and each switch
signed one of the clients out.

- The x86-64 client is Valve's steamrt64 platform, but it was installed and
  synced from the ubuntu12 manifest, which lags it (1792108788 against
  1792192922). The client was therefore never the arm64 client's build, the
  arm64 web helper was never used, and the client then updated itself in
  place after the helper had been chosen. Install, sync and the build check
  now read the steamrt64 manifest.
- The x86 web helper's GPU process loads libpci, which exits when
  /sys/bus/pci/devices lists a device whose files the app may not read (a
  RedMagic's PCIe root ports). Every GPU process exited with status 1, even
  in software, until Chromium gave up and the menus stayed blank. An empty
  folder is bound over /sys/bus/pci/devices on such devices.
- Steam refuses one client's login token from the other ("Invalid
  Password" for a token the arm64 client signed in with minutes before), so
  copying the newer token at every start signed the other client out. Each
  client now keeps its own token; a client with no accounts only gets the
  other's account list.
- Games could be listed twice: an added game whose folder a Steam import
  already covers is now shown as that Steam game only, a client switch
  drops the old client's imported entries, an import stays one when its
  ownership cannot be confirmed instead of gaining a shortcut beside it,
  and only the client's own library folders count as an install.

* Steam x86-64 client: share the arm64 sign-in again

The x86-64 client refused the arm64 client's login token because it did
not know the account's SteamID: it takes it from Accounts in
config/config.vdf, which this script creates without one. It logged on as
[U:1:0] with a valid token, Steam answered "Invalid Password", and the
client dropped the token and AutoLoginUser, so every switch asked for the
password.

The newer sign-in is copied between the clients again, each start adds
the accounts either client knows to the other's config.vdf, and an
AutoLoginUser or RememberPassword one client lacks is filled in. Device:
the x86 client signs in with the arm64 token on a fresh install and on an
install left signed out, and the arm64 client stays signed in.
2026-10-18 20:38:48 -04:00
MaxsTechReview 48a70c8229 Boot Linux games under the FEX compatibility tool again (#1100)
Valve's FEX opens rootfs paths with openat2(RESOLVE_IN_ROOT), which the
preload emulates under proot. droiddeck-fex's rootfs links to the Steam
runtimes with absolute paths outside the rootfs, so every lookup missed
and FEX fell back to the arm64 host files: x86 bash, and with it Stardew
Valley's launcher, failed on libdl.so.2. An absolute link whose target
is missing in the root but present on the host now finishes there.
2026-10-18 20:38:08 -04:00
MaxsTechReview 7800d3d810 fix: Steam client responds sooner and its sounds play on time (#1094)
- Pace the session at the rate the panel actually runs. When the system
  keeps a lower rate than the mode DroidDeck asked for (a vendor game
  assistant holding 120 Hz while 185 Hz was requested), gamescope,
  Xwayland and the compositor's presentation feedback were told 185 Hz,
  and Steam's interface drew every 10.8 ms out of step with the panel.
- Build the DirectAudio PulseAudio sink from source (LGPL-2.1-or-later,
  The412Banner/directaudio v1.1.0) with its max request kept to its ring
  target. Advertising 100 ms made PulseAudio grow every client's buffer
  to about 112 ms, so Steam's interface sounds played that late.
2026-10-18 19:26:26 -04:00
MaxsTechReview 123fbc723c fix(ci): give unit tests a 2 GB heap (#1091)
Robolectric keeps one sandbox per SDK level the tests ask for, and the
suite now asks for seven (26, 28, 29, 31, 33, 34 and the default). They
no longer fit in Gradle's default 512 MB test heap, so testReleaseUnitTest
runs out of memory part way through (SteamHomeHeadsetTest,
GlassesMenuCaptureTest) on main and on every open PR.
2026-10-18 19:24:09 -04:00
Kurt Himebauch df146d011f fix: keep hidden and private Steam games off the Steam home art wall (#1097) 2026-10-18 19:11:30 -04:00
Kurt Himebauch e4503129ea fix: install Decky Loader from upstream repository (#1090) 2026-10-18 19:31:14 +00:00
Resul Övür 25082786aa fix: added games are handed to Steam right away and retried only if they never take focus (#994) 2026-10-18 14:59:39 -04:00
Kurt Himebauch 1fbe103ffe feat: auto driver picks come from a Drivers-CI manifest with per-GPU fallbacks (#977) 2026-10-18 14:23:41 -04:00
Kurt Himebauch 632f13d1b2 fix: keep second-screen controls in their own activity (#1041) 2026-10-18 14:23:04 -04:00
Kurt Himebauch 5d07c575e2 fix: skip DirectAudio setup for Proton queries (#1044) 2026-10-18 14:21:46 -04:00
MaxsTechReview 9ac663aae0 chore: bump version to 0.5.0 (14) (#1081) 2026-10-18 11:25:10 -04:00
Kurt Himebauchandmaxjivi05 5f807a0a26 docs: draft DroidDeck 0.5.0 release notes (#986)
* docs: draft DroidDeck 0.5.0 release notes

* docs: expand DroidDeck 0.5.0 release notes

* docs: complete 0.5.0 contributor credits

* docs: update the 0.5.0 release notes for everything merged so far, with screenshots and GIFs

Adds the x86-64 Steam client, Linux games under FEX, app-wide glasses tracking, store-game artwork and Adjust logo, and the merged Epic launcher and XR glasses entries. Media is in docs/releases/media/0.5.0.

---------

Co-authored-by: maxjivi05 <[email protected]>
2026-10-18 11:22:00 -04:00
MaxsTechReview 64a4cc887c fix: only start SteamVR for VR-only games from Steam's library (#1076)
Games that merely support VR (No Man's Sky) were held at Play while
DroidDeck downloaded and started SteamVR, so they always opened in VR.
They now launch normally; VR-only games still wait for SteamVR.
2026-10-18 09:26:01 -04:00
MaxsTechReview 41abcd203a feat: fetch Proton, FEX and the runtimes automatically, and make Linux games run under FEX (#1045)
* fix: x86 Linux games that also ship for Windows run under Proton again

Since #749 the live compatibility helper treated every installed game with
both Windows and x86 Linux builds as a user's native choice, because the
client reports an empty tool for unmapped games. Those games were left on
the Steam Linux Runtime, which cannot start them here (bwrap fails, or FEX
looks for a RootFS that is not shipped), so Valheim, Moonlighter, Half-Life 2,
TF2 and others exited within seconds.

- An unmapped game the helper never managed now gets the default Proton.
  Clearing a mapping the helper set is still kept as a native choice.
- Native choices are stored under "native_choices". The old "native" list
  also held games nobody picked, so it is dropped.

* feat: Linux builds run under FEX by default, with the runtimes fetched automatically

- Steam fetches Proton, FEX, Steam Linux Runtime 3.0 and 4.0 on the first start and keeps them updated instead of leaving them pending.
- Games that ship a Linux build go to FEX once it can run them; Windows-only games and installed Windows builds keep the Proton default.
- FEX runs games on a combined x86 runtime with the newer glibc and libstdc++ the GL and Vulkan thunks need.
- Vulkan display extensions the driver cannot handle are hidden in front of the thunk, without an audit module.
- Mono's incremental garbage collector is turned off under FEX, which stops Unity games crashing.
- x86 Steamworks finds the x86 steamclient.
- 32-bit games read the virtual controller.

* fix: controllers work in Linux Unity games such as Valheim

Joystick ioctls on an event node now fail with EINVAL like the kernel's evdev, so the SDL2 that Unity embeds reads the pad's real ID instead of ignoring it as 0000:0000.

* test: skip the switch script test without node, and the ring tests without gcc

* fix: games with a Linux build keep the Proton default

The Windows build often runs better, so titles that ship both builds stay on the default Proton again. Users can still pick FEX for one, and that choice is kept. Linux-only titles still go to FEX.

* fix: unmapped dual-platform games get Proton even when config.vdf cannot be read
2026-10-18 08:59:54 -04:00
+13 e7bc4b03a5 Add app-wide 3DoF for VITURE, RayNeo and XREAL glasses and 6DoF for Luma Ultra (#1035)
* Add VITURE glasses support: hardware control, 3D, head tracking

Glasses get their own controller over the MCU (permission, 120 Hz default, brightness, volume, sunblock, 2D/3D) and the game moves onto them when plugged in. The IMU drives Cardboard head tracking and the SteamVR driver. 3D falls back to one eye's view on phones that can't send the 3840x1080 signal. Options open from a glasses button in game and on the Steam page.

* Drop the glasses settings like Steam's: Play-style button, Settings lip, tabs

* Hang the glasses Settings lip over the top of the button

* Space glasses MCU commands so the IMU rate sticks, fit fixed resolutions to the glasses

* Keep Steam alive with SteamVR registered, run SteamVR at the glasses' rate, keep the glasses lip on top

* Fit VR games on the glasses: full view, seated origin at the head, pad hands reach with the d-pad

* Let the pad hands reach a metre out so buttons with deep colliders press

* Show every VR frame in order at 120 fps: wait for SteamVR's GPU work, present only finished copies, skip the grace on idle shared counters

* Glasses: SteamVR starts and stops with them, closer view with a field of view setting, Recenter on the pad

* Glasses: SteamVR field of view defaults to 90°

* VR pad hands: sticks move and aim the hands, left stick click switches the sticks to the game, d-pad pulls a held hand

* Glasses: Play SteamVR beside the glasses button instead of starting SteamVR with them, pad hands drawn as Index controllers and off while other VR controllers are connected, glasses sheet tabs sized like Steam's

* Track glasses mode, refresh-aware glasses window, align Play SteamVR with Play Steam

* Fix 3D on the glasses: full-res SteamVR presenter, revert VR 3D at session end, coalesce vsync ticks, no session restart after end

* Address review: glasses output after installs and session end, VR revert retry, display fallback, presenter swapchain retry, agent hex checks, drawer bumpers, glasses translations, docs

* Harden VITURE stereo negotiation and output refresh tracking

* Localize stereo recovery feedback and record Astra reconnect tests

* Improve test failure diagnostics and refresh Astra firmware findings

* Handle VITURE docks separately and reject truncated USB frames

* Glasses: remember a 3D fallback per model, docks as display only, VR timing fixes

- A 3D attempt that ends at the 640x480 fallback is remembered for that glasses
  model, so Play SteamVR no longer blacks out the glasses after every restart;
  Retry clears it and "3D for VR games" stays reachable
- A VITURE Mobile Dock on its own no longer counts as glasses; through a dock
  Play SteamVR is hidden and the sheet shows the output and why there is no control
- HDR is probed on the glasses when the session starts there
- Glasses frames with a mismatched checksum are marked in the status frame log
- SteamVR layer and driver vblank counters stay monotonic across refresh changes;
  one-eye render scale applies from activation and on mono/3D switches
- Compositor binds announce the refresh clients were last told; the presenter
  retries a failed first swapchain
- Dead strings and helpers removed; docs updated

* Glasses: drop USB frames with a bad checksum

* Glasses: count frames dropped for a bad checksum

* Feat/viture xr (#907)

* Add RayNeo Air and GT support to the XR glasses pipeline

* Add RayNeo Air and GT support to the XR glasses pipeline

* Checkpoint glasses tracking, calibration, and SteamVR launch integration

* Checkpoint glasses tracking, calibration, and SteamVR launch integration

* Add XREAL 1S support and correct GT and Pro 2 tracking

* Add XREAL 1S support and correct GT and Pro 2 tracking

* Fix mirrored glasses tracking and recover SteamVR after device loss

* Fix mirrored glasses tracking and recover SteamVR after device loss

* Document confirmed XREAL 1S brightness control

* Document confirmed XREAL 1S brightness control

* Fix SteamVR glasses defaults and rendering controls

* Fix SteamVR glasses defaults and rendering controls

* chore: keep only the 3 newest session log folders (#852)

* chore: add agent comment rules (#748)

Co-authored-by: Kurt Himebauch <[email protected]>

* Fix SteamVR polling, VITURE checksums, and fence tracking

Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter.

Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage.

* Fix SteamVR polling, VITURE checksums, and fence tracking

Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter.

Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage.

* fix: wireless ADB page names the child-process switch the way Android shows it (#848)

* l10n: refresh stale and untranslated strings in es, fr, ko, ru (#850)

* l10n: refresh stale and untranslated strings in es, fr, ko, ru

- mode_added_art_hint: now mentions Steam then SteamGridDB (all four locales).
- wincomp_needs_installer / wincomp_installers_note: es and ko still described
  the old "coming soon" behaviour; align with the current English text.
- summary_remove_game: was still English in all four locales.

Machine-assisted translation; native-speaker review is welcome.

* l10n(es): say registro, not grabación, for the missing installer record

---------

Co-authored-by: ovurrsl <[email protected]>

* fix: a failed cloud save recovery no longer ends the app (#846)

The cloud-recovery thread started at every app start had nothing to
catch an error, so one went to CrashHandler, which halts the process
with status 2. In unit tests the thread can outlive the test that
started it, fail on the torn-down preferences, and take the whole test
run down. The error is now logged and the thread ends.

Co-authored-by: Kirk <[email protected]>

* fix: an interrupted Linux runtime download resumes instead of starting again (#847)

* fix: an interrupted Linux runtime download resumes instead of starting again

installOnce() asked the downloader to resume but always deleted the archive in its finally block, so the next attempt began at zero. The archive is now named after the release version and checksum, a failed download keeps what it fetched, and archives of other releases (and the old fixed name) are removed. A checksum or extraction failure still deletes the file.

* test: Downloader resumes a cut-off download from the bytes it kept

Starts a socket server that promises the whole file, hangs up part way and then answers a Range request, and checks that the second downloadFile call resumes with the right offset and ends with identical bytes.

* fix: a resumed download discards a 206 that does not start where the kept bytes end

Downloader appended every 206 response to the partial file without
looking at Content-Range. A server or proxy that answers a Range request
with a different offset would leave a corrupt file. Check that the range
starts at the kept length; otherwise delete the partial file and start
again without a Range header.

Adds a test with a server that answers the Range request from offset 0.

---------

Co-authored-by: ovurrsl <[email protected]>

* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings (#849)

* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings

Adds the strings values-de, values-it and values-pt were missing (14 each) and the six that values-fr, values-es and values-ru were missing. Machine-assisted drafts that follow each file's existing register and terms; native review is welcome.

* l10n: correct missing active swap translations

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>

* fix: droiddeckctl reaches variant-package builds (recover #842) (#854)

fix: droiddeckctl reaches builds shipped under a variant package

The provider authority and the start Activity's component were fixed to
com.droiddeck.launcher, so droiddeckctl could not talk to the antutu,
ludashi or pubg builds from tools/release/variants.txt. DROIDDECK_PACKAGE
now picks the package; the Activity's class name stays the same in every
variant.

Co-authored-by: angelrenefm <[email protected]>

* feat: droiddeckctl reports on-screen pacing (recover #843) (#855)

feat: droiddeckctl pacing reports the frames that reached the screen

Reads SurfaceFlinger's timestats for the game's layer (or the session's
SurfaceView) over a capture: frames on screen, fps on screen, the
present-to-present histogram and how many intervals were off the usual
cadence. Records split by a refresh-rate change are summed. A game's own fps
counter counts frames handed over, so it can't show dropped or doubled frames.

Co-authored-by: angelrenefm <[email protected]>

* fix: Steam can restart more than twice in a session (#754)

* fix: Steam can restart more than twice in a session

Every exit 42 counted toward the cap that stops an update which will
not install, and the count was never cleared, so the third restart of
a session ended it however far apart they were. A client that ran for
a minute before asking is not that loop: the count starts again.

* fix: measure the client's run time as it exits

The time spent collecting logs and checking FEX after the client exits
counted toward its run, so a slow cleanup could make a quick exit 42
look like a long one and clear the count.

---------

Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>

* fix: the front end keeps its page when the Fold is folded or unfolded (#832)

* fix: the front end keeps its page when the Fold is folded or unfolded

MainActivity, FileManagerActivity, FilePickerActivity and StoreLoginActivity did not declare the screen-size configuration changes, so folding or unfolding a foldable recreated them and dropped their in-memory state. They now handle orientation, screenSize, screenLayout, smallestScreenSize and density themselves, like SessionActivity already does. uiMode is left out on purpose.

* fix: store login keeps rebuilding its toolbar when the display density changes

StoreLoginActivity builds its toolbar in code with dp-to-pixel values taken at creation time and has no onConfigurationChanged. It no longer declares density in configChanges, so a density change recreates it as before this PR. Size and layout changes are still handled in place.

---------

Co-authored-by: ovurrsl <[email protected]>

* Release pointer buttons without replaying stale motion (#803)

* Fix cursor warping on native touch down

* Preserve input cleanup without replaying stale pointer motion

---------

Co-authored-by: siyuan.qsy <[email protected]>

* l10n: translate the strings added since #284 into zh-CN, zh-HK, zh-TW and ja (#539)

* l10n: translate the 184 strings added since #284 (zh-CN/HK/TW, ja)

The stores, cloud saves, Windows components, Steam touch, desktop
removal and background-downloads strings landed after #284's
translation snapshot, so every one of them fell back to English.
Translate all 184 into Simplified Chinese, Traditional Chinese
(Taiwan and Hong Kong) and Japanese, keeping each locale file's own
vocabulary (工作階段/會話, 解除安裝/卸載, 元件/組件, 設定).

Also fix four Hong Kong strings that kept the simplified 着色器.

* l10n(zh): fix stale and ambiguous strings found in review

Severe: summary_remove_game was untranslated English in all three
files; mode_added_art_hint, wincomp_installers_note and
wincomp_needs_installer still described the pre-#480/#492 English.

Also: distinguish the Setup wizard from Android system settings in
zh-HK/zh-TW (初始設定 / 系統設定 / Android 設定), unify library as
收藏庫, component as 組件 in zh-HK, session as 會話 in zh-HK, account
as 帳戶 in zh-HK, Trending as 趋势/趨勢, Forget as 清除, reword the
re-applied-at-launch, action-failed and already-in-Games strings, and
play as 執行/開啟遊戲 in zh-TW/zh-HK.

* l10n(ja): fix stale and ambiguous strings found in review

---------

Co-authored-by: Dantalian <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>

* Rebuild and verify Windows VR runtime components

* Rebuild and verify Windows VR runtime components

* Add top, bottom, and centered picture positions for Steam sessions (#871)

* fix: dependency scan ignores folders above the game folder (#859)

* DroidDeck-Proton: get releases from the Droid-Deck org repo

DroidDeck-Proton is now built and released on Droid-Deck/DroidDeck-Proton
(11-9.1 is Latest there), so the Components page, the update check and the
guest installer read releases from the org repo instead of The412Banner's.

* feat: low-latency controller and touch toggles in the session drawer (#808)

* fix: SteamGridDB lookups no longer stick as "no match" after a failure; add a search to the art chooser (#876)

* feat: Steam home wall fills from the signed-in library instead of store picks (#878)

* test: cover GPU family and support classification (#880)

* Fix local builds on Linux failing after the first Comet build (#866)

* feat: sort the store library and installed tabs, and switch between card and list views (#798)

* feat: three-finger tap toggles Steam's on-screen keyboard in game mode (#782)

* fix: keep Quest-patched Turnip as the auto default (#853)

* Fix deploy_local.sh and droiddeckctl not finding a phone connected over wireless debugging (#864)

* fix: keep proot sessions alive when the kernel loses fork events (#457)

* fix(quest): correct desktop and Steam display corruption across sessions (#733)

fix(quest): stabilize desktop and Steam layer presentation

Keep Quest SDR scenes and the black backdrop off the Android Vulkan swapchain regardless of session order or HDR preferences. Only report the first frame after client content is submitted.

Add backdrop lifecycle and first-frame regression coverage. Record the user's successful Quest 3 Test 2 result.

Co-authored-by: xXJsonDeruloXx <[email protected]>

* fix: keep SteamVR syscall hooks and report Steam crashes (#905)

* fix: preserve SteamVR syscall hooks

* fix: report Steam crashes through gamescope

* fix: honor glasses pacing and resolution in SteamVR launches

---------

Co-authored-by: connor g <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: Yassine Gherbi <[email protected]>
Co-authored-by: Resul Övür <[email protected]>
Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: angelrenefm <[email protected]>
Co-authored-by: asdf17128 <[email protected]>
Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: FX1922 <[email protected]>
Co-authored-by: Muaz Kadan <[email protected]>
Co-authored-by: kingokksa <[email protected]>
Co-authored-by: Jamie Schenker <[email protected]>
Co-authored-by: Jagger15 <[email protected]>

* Glasses: keep a pending 2D restore and the SteamVR recovery offer, show Play SteamVR only with a picture

* Translate the glasses calibration, XREAL and SteamVR recovery strings

* SteamVR recovery: give a finished teardown time to collect logs before restarting

* Credit the projects the XR glasses support builds on

* Joy-Cons: SteamVR hands from their gyro while SteamVR runs, one combined pad otherwise

* Add experimental Luma tracking and stabilize SteamVR launches

Add native Luma Ultra spatial tracking, optical controllers, and stereo
calibration with DroidDeck styling, imperial/metric measurements, and
pending recenter recovery. Include the pinned hand model and calibration board.

Keep live guest and SDK files outside Android's evictable cache. Filter the
Adreno 840 sysmem override in the shared SteamVR helper, serialize timeline
mirroring, retain invalid optical controllers without event floods, and
prevent an unprivileged scheduling fallback from lowering compositor RTTIME.

Validation: latest APK build and runtime freshness checks pass; 247 affected
glasses/calibration tests pass. Native VR, helper, and packaging regressions
pass. Two earlier phone library launches sustained 60 fps for 308 and 188
seconds without recovery or restart. The newer calibration APK is installed
and checksum-verified; calibrated-hand and sustained gameplay acceptance
remain pending. One existing Android test requires Windows symlink privileges.

* Headsets: settings badge on Play SteamVR instead of a top-right button, both shown only once connected

* Hand tracking: camera-tracked Joy-Cons and bare hands in SteamVR, drawn as white gloves

- Joy-Con movement from the accelerometer (report reordering, per-Joy-Con calibration, zero-velocity updates)
- MediaPipe pose and hand landmarks from the device camera, placement check, per-hand Kalman filter with the Joy-Con
- No controller connected: bare camera hands with Quest-style gestures, 1€ filtered, rendered as a white glove model built from a distance field
- SteamVR safe mode cleared before each start so one crash no longer blocks the driver

* Improve Luma Ultra tracking and isolate device-specific controls

Refine stereo hand and Joy-Con tracking, gesture release, calibration,
recovery UI, diagnostics, display modes, and compositor handling.
Keep Ultra controls and input routing behind a live SDK-confirmed Carina
connection; clear retained spatial mode when other glasses connect.
Add optional UVC transport with pinned dependencies and packaged notices.

Validation: 456 Android tests, ARM64 VR build and native regressions,
47 tooling tests, SDK/UVC-enabled and disabled builds, and 150 packaged
runtime checksums. Two shader compilation tests skipped because the
compiler is unavailable. Hardware interaction gates remain experimental.

* Delay Luma Ultra 6DoF until SteamVR settles

Suspend spatial SDK startup while SteamVR launches, then restore it two seconds
after RUNNING is confirmed. Preserve saved tracking preferences and automatically
recenter same-device restoration, including cancellation and early stop.

Discard superseded launch requests and status replies after session teardown.

Validation: 128 focused JVM tests passed on the isolated commit snapshot; 129
passed with the current local hand-tracking work. Debug build and runtime checks
passed, and the reviewed APK was installed and hash-verified on the phone.

* Clarify glasses and phone camera tracking options

Separate Luma Ultra camera controls from phone-camera tracking so the two
hand-tracking switches have clear sources. Label bare-hand controllers and
Joy-Con position tracking explicitly, explain physical Joy-Con inputs and
camera priority, and update the camera prompt and calibration guidance.

Use fresh phone section and toggle labels so existing translations do not
hide the distinction. Preserve tracking behavior and saved preferences.

Validation: 39 existing UI tests, debug build, and packaged runtime verification
passed. Reviewed changes and installed the APK on the connected phone.

* Fix tracking handoff and shared runtime regressions

Release spatial ownership and controller overrides when output returns to the phone. Preserve USB-loss recovery, keep Vulkan submissions asynchronous, clean persistent guest scratch on runtime removal, and clarify the 6DoF head-tracking control.

* Apply smart-glasses virtual screens across DroidDeck menus

* Finalize Luma hand baseline and document smart-glasses support

Keep raw grayscale-to-RGB Full hand inference, wrist-only Joy-Con reconstruction, bounded visual holds, and the dead-zone rollback. Retain opt-in steady aim, recenter controls, reproducible Lite models, and the JPEG-padding fix.

Remove unsuccessful benchmark-only and USB transport experiments. Document the cross-brand 3DoF matrix, Luma Ultra 6DoF, SDK requirements, and app-wide screen behavior.

* Fix executable permission for SteamVR launcher

* Address hand tracking and VR review findings

* Bound stereo recovery test image comparison memory

* Localize smart-glasses setup and tracking diagnostics

Move calibration controls, status messages, validation errors, distance labels,
and printed-board instructions into Android string resources. Add and update
machine-assisted translations in all 32 supported non-English locale catalogs,
with corrections for recentering, hand tracking, product names, and measurements.

Keep print instructions within the page using text layout that supports wrapping
and right-to-left text. Correct the Lite model toggle description to reflect its
Lite palm detector and Lite landmark model.

Add locale coverage, format-argument, line-break, and measurement checks, plus
French and Arabic calibration UI tests. Preserve tracking calculations and the
shared runtime storage behavior.

Validation: Android resource and Kotlin compilation; 3 localization checks;
68 focused tests; 1,597 JVM tests at a 512 MB heap; French and Arabic UI renders.

* Correct smart-glasses translations from PR review

Fix the 42 localization findings covering hand and head terminology, disabled states, calibration dimensions, camera visibility, capture rejection, and menu references. Correct the repeated capture-error ambiguity in related locales and clarify its English source. Keep Romanian setup instructions consistent with the existing informal voice.

Extend localization measurement validation to distance hints and retry guidance. Verify the recovery screen's palette-dependent text color with one row-sized pixel buffer and guaranteed bitmap cleanup.

Validation: Android resource and Kotlin compilation; 3 localization checks across all supported locales; 68 focused JVM tests at a 512 MB heap.

* Work around SteamVR device loss with Adreno 840 Turnip v0.2.0

* Withdraw ineffective SteamVR flushall workaround

---------

Co-authored-by: maxjivi05 <[email protected]>
Co-authored-by: MaxsTechReview <[email protected]>
Co-authored-by: connor g <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: Yassine Gherbi <[email protected]>
Co-authored-by: Resul Övür <[email protected]>
Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: angelrenefm <[email protected]>
Co-authored-by: asdf17128 <[email protected]>
Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: FX1922 <[email protected]>
Co-authored-by: Muaz Kadan <[email protected]>
Co-authored-by: kingokksa <[email protected]>
Co-authored-by: Jamie Schenker <[email protected]>
Co-authored-by: Jagger15 <[email protected]>
2026-10-18 08:37:09 -04:00
The412Banner 292295ba30 feat: x86-64 client follows DroidDeck-Components' fex.json; fuller x86-64 session logs (#1053)
FEX builds can reach devices without an app release: while the x86-64
client is selected the app reads DroidDeck-Components' fex.json (directly,
on its own thread, not in Offline Mode) and a session passes that build to
droiddeck-steam-x64, which installs it only once its sha256 matches and its
programs load, keeping the replaced files aside until the installed copy
loads too; it keeps a working installed build otherwise and falls back to
the build it pins. FEX is built and published by Components' build-fex.yml
on its single fex release; this repository's build-fex.yml only checks the
build. x86-64 session logs name the FEX build and its source, the rootfs and
the client's builds, report the rootfs download in tenths, and say in one
line when the web helper's GPU process keeps exiting, with x86 glxinfo and
vulkaninfo through FEX. Settings shows the FEX build installed.
2026-10-18 03:40:17 -04:00
The412Banner 798386d02a fix: x86-64 Steam client fails without the KDE desktop (FEX r5 bundles libxxhash) (#1050)
FEX r4 links libxxhash, which the Linux runtime does not ship; devices
without the KDE desktop package (where the library came from) exited 127
at the x86-64 client's first FEX call. FEX r5 bundles it beside libFEXCore
with RUNPATH $ORIGIN/../lib, and the FEX build now fails when a program in
the package needs a library that is neither bundled nor listed in
tools/fex/runtime-libs.txt. droiddeck-steam-x64 moves to fex-2610-r5
(DroidDeck-Components #5). Progress log: the org review of #152 and this fix.
2026-10-18 02:00:29 -04:00
The412Banner db8d1c9910 feat: optional x86-64 Steam client (Valve's Linux client under FEX) beside the arm64 one (#152)
An opt-in second Steam client (Steam settings, Steam client: x86-64): Valve's
x86-64 Linux client, unchanged, runs under FEX in its own home with its own
game library and the same sign-in. Linux builds run directly, so titles such
as Team Fortress 2 and Half-Life use their Linux versions; Windows games use
DroidDeck-Proton as on arm64. The arm64 client stays the default.

- droiddeck-steam-x64: FEX and the x86-64 rootfs from DroidDeck-Components
  (sha256-pinned), client install and sync from Valve's CDN, sign-in shared
  with the arm64 client, its web helper on the GPU (ANGLE GL over FEX's GL
  thunk, zink, Turnip), Proton wrappers for the arm64 client's tools,
  DroidDeck direct for Linux builds, SD-card library (x86-64/ beside the
  arm64 one), repair.
- FEX 2610 built by build-fex.yml with thunks and three patches: an openat2
  fallback without openat2 (proot), newer-clang thunkgen, MAP_32BIT
  exhaustion returning ENOMEM. libblsession resolves RESOLVE_IN_ROOT paths
  inside the root.
- Session: x86-64 gating, shortcuts and compatibility tools per client,
  logs from the client that ran.
- App: client setting with the FEX version, an x86-64 chip on the Games
  header, the active client's library, art, storage, downloads, repair,
  Offline Mode, Decky, desktop mode, optional VAC diagnostics; per-client
  state for art, the content log, repair and leftovers.
- Strings in every locale.
- Review fixes: the library cache is saved only by the newest scan; an
  abandoned session's Steam logs come from the client it recorded; distinct
  VAC console names; FEXInterpreter in CPU pinning; clearer VAC hint in
  pl, ru, uk, cs and bg.
2026-10-17 23:57:52 -04:00
MaxsTechReview ab799e6938 fix: rename the VR recovery task affinity in variant builds (#1038) 2026-10-17 17:24:31 -04:00
MaxsTechReview b77135cee8 fix: rename the VR recovery task affinity in variant builds
The PUBG, AnTuTu and Ludashi signing jobs stopped on
com.droiddeck.launcher.vr_recovery, the task affinity of the VR
recovery activity added with the VITURE XR controls, because the
package rename did not cover it.
2026-10-17 17:14:06 -04:00
MaxsTechReview 2fe4d5303c feat: Epic Games Launcher for Linux (#928)
* feat: Epic Games Launcher for Linux

Install Epic's official Linux launcher (EGLv2) from Epic's CDN with Valve's
Steam Runtime 4 and run it under FEX, signed in with the linked Epic account.
Epic games launch through a hidden launcher instance so they get the
launcher's own arguments, with the exchange-code launch as the fallback, and
DroidDeck installs show up in the launcher as installed.

In the launcher, touch scrolls like a phone app and the gamepad drives the UI:
D-pad and left stick move the selection, A opens, B goes back, the right stick
and triggers scroll proportionally, X opens the options menu, Y the keyboard
and LB/RB switch tabs.

* feat: Epic Games Launcher for Linux

Install Epic's official Linux launcher (EGLv2) from Epic's CDN with Valve's
Steam Runtime 4 and run it under FEX, signed in with the linked Epic account.
Epic games launch through a hidden launcher instance so they get the
launcher's own arguments, with the exchange-code launch as the fallback, and
DroidDeck installs show up in the launcher as installed.

In the launcher, touch scrolls like a phone app and the gamepad drives the UI:
D-pad and left stick move the selection, A opens, B goes back, the right stick
and triggers scroll proportionally, X opens the options menu, Y the keyboard
and LB/RB switch tabs.

* Route Epic games through Steam, import launcher installs, offer the second library

- Every Epic launch goes Steam -> Epic Games Launcher -> game; Play inside the open launcher hands the game to Steam
- Games installed from inside the launcher get a Games-tab entry and launch through Steam; the launcher installs to the last Stores target
- Stores install picker offers the Steam second library, with side-by-side choices
- uruntime xattr-only failures no longer fail the launcher unpack (app-extract)

* Hand launcher Play and Steam links to Steam, wait for FEX on first run

Play on a game the launcher just installed is matched to its install
record and handed to Steam. Steam games listed in the launcher open
steam://rungameid links, which now start the game in a Steam session.
Opening the launcher before Steam has downloaded FEX asks Steam for it
and starts Steam instead of failing with no-fex.

* Address review: FEX check, play-in-Steam action, touch route thread, scroll scale

fexReady needs a FEX binary in Steam's FEX tool, not just its folder.
The play-in-Steam action stays until the link is handled, so Run Steam
at startup can't start a session first. The pad touch route reads the
mouse mode without side effects off the main thread and posts the pad
update there. Vertical scroll uses the output height. A status-2 unpack
counts only when AppRun is there. The xdg-open command is tested
end to end for Steam links.

* Open the launcher through Steam, recover dropped shortcuts, sign out of both

The Epic Games Launcher now runs as its own Steam shortcut, so opening
it starts Steam, which opens the launcher, and Play inside it goes to
the running Steam.

Steam dropped a running shortcut from its library when the game exited
after a sleep/wake replay or a server re-logon, and launching it then
waited forever at ShowInterstitials. login1 only replays sleep/wake
when Steam is asleep. The session restarts Steam when a requested
shortcut is missing, and when a game exits with its shortcut gone and
nothing else runs.

Resuming after a long pause no longer reports failure while traced
processes are still waking, so "Steam is paused" goes away on the first
tap.

Signing out of the launcher signs DroidDeck's Epic store out, and
signing out in DroidDeck restarts the launcher signed out and clears
the Epic web cookies.

* Address review: one session-switch poll, Epic setup can't block a session

A new switch confirmation replaces the pending poll instead of adding a
second one that could start the session again. Epic library sync and
install-folder setup at session start are each guarded, so a bad
preference no longer stops the guest from launching.

* Uninstall the Epic Games Launcher from the Games tab

An Uninstall button next to Launch asks first, closes a running
session, and removes the launcher and its runtime. The launcher's
sign-in and install records stay, so its games keep their Games-tab
entries and a reinstall comes back signed in. Its Steam shortcut goes
away on the next Steam start.

* Address review: uninstall only after the session has ended

The launcher's files are removed only once the session is not running
and its phase has ended; a shutdown still in progress after the wait
reports failure instead. The confirmation reads the session state from
the front-end state.

* Find FEX that Steam installed on the second library

Steam's tools on a second library live in DroidDeck's private copy,
which the session binds over the library. The launcher's FEX check
only looked on the shared storage, so with FEX there it always asked
Steam for FEX and only Steam opened. It now checks the private copy
too.

* Start games without Steam, per-game Settings menu with Wine sync

"Start without Steam" now really leaves the Steam client out: the game
runs in its own Proton/FEX through droiddeck-proton-run in a run
session, and the Epic Games Launcher opens on its own with its games
playing in it. Run sessions for these games are not pinned to the big
cores and get DirectAudio and the added-games list like Steam.

The Games tab card is now "Settings", titled "<game> Settings": Start
without Steam, FEX preset, Wine sync (ntsync, fsync, esync, wineserver
or the app default) and Default, which puts everything changed for the
game back to the app's settings. The app-wide Wine sync choice is shown
in the Steam settings Session tab.

* Address review: uninstall guard, routed games, shortcut matching

Opening or reinstalling the Epic Games Launcher waits while it is being
uninstalled. Added games that are Steam installs keep their Start
without Steam setting under their Steam app id and start as that Steam
game. Live shortcut lookups wait for Steam's library to load and match
a same-name shortcut by its executable, so no duplicate is added. The
session treats the open launcher as itself even when it could not tell
its Steam app id.

* Address review: drop a queued launcher switch when it is uninstalled

A confirmed switch to the Epic Games Launcher that is still waiting for
the old session to end is cancelled when the launcher is uninstalled,
and never starts while the uninstall runs. The launcher id test sets
its environment for its own runs only.
2026-10-17 14:40:39 -04:00
+13 16f9916825 Add VITURE XR controls, head tracking, and SteamVR (#732)
* Add VITURE glasses support: hardware control, 3D, head tracking

Glasses get their own controller over the MCU (permission, 120 Hz default, brightness, volume, sunblock, 2D/3D) and the game moves onto them when plugged in. The IMU drives Cardboard head tracking and the SteamVR driver. 3D falls back to one eye's view on phones that can't send the 3840x1080 signal. Options open from a glasses button in game and on the Steam page.

* Drop the glasses settings like Steam's: Play-style button, Settings lip, tabs

* Hang the glasses Settings lip over the top of the button

* Space glasses MCU commands so the IMU rate sticks, fit fixed resolutions to the glasses

* Keep Steam alive with SteamVR registered, run SteamVR at the glasses' rate, keep the glasses lip on top

* Fit VR games on the glasses: full view, seated origin at the head, pad hands reach with the d-pad

* Let the pad hands reach a metre out so buttons with deep colliders press

* Show every VR frame in order at 120 fps: wait for SteamVR's GPU work, present only finished copies, skip the grace on idle shared counters

* Glasses: SteamVR starts and stops with them, closer view with a field of view setting, Recenter on the pad

* Glasses: SteamVR field of view defaults to 90°

* VR pad hands: sticks move and aim the hands, left stick click switches the sticks to the game, d-pad pulls a held hand

* Glasses: Play SteamVR beside the glasses button instead of starting SteamVR with them, pad hands drawn as Index controllers and off while other VR controllers are connected, glasses sheet tabs sized like Steam's

* Track glasses mode, refresh-aware glasses window, align Play SteamVR with Play Steam

* Fix 3D on the glasses: full-res SteamVR presenter, revert VR 3D at session end, coalesce vsync ticks, no session restart after end

* Address review: glasses output after installs and session end, VR revert retry, display fallback, presenter swapchain retry, agent hex checks, drawer bumpers, glasses translations, docs

* Harden VITURE stereo negotiation and output refresh tracking

* Localize stereo recovery feedback and record Astra reconnect tests

* Improve test failure diagnostics and refresh Astra firmware findings

* Handle VITURE docks separately and reject truncated USB frames

* Glasses: remember a 3D fallback per model, docks as display only, VR timing fixes

- A 3D attempt that ends at the 640x480 fallback is remembered for that glasses
  model, so Play SteamVR no longer blacks out the glasses after every restart;
  Retry clears it and "3D for VR games" stays reachable
- A VITURE Mobile Dock on its own no longer counts as glasses; through a dock
  Play SteamVR is hidden and the sheet shows the output and why there is no control
- HDR is probed on the glasses when the session starts there
- Glasses frames with a mismatched checksum are marked in the status frame log
- SteamVR layer and driver vblank counters stay monotonic across refresh changes;
  one-eye render scale applies from activation and on mono/3D switches
- Compositor binds announce the refresh clients were last told; the presenter
  retries a failed first swapchain
- Dead strings and helpers removed; docs updated

* Glasses: drop USB frames with a bad checksum

* Glasses: count frames dropped for a bad checksum

* Feat/viture xr (#907)

* Add RayNeo Air and GT support to the XR glasses pipeline

* Add RayNeo Air and GT support to the XR glasses pipeline

* Checkpoint glasses tracking, calibration, and SteamVR launch integration

* Checkpoint glasses tracking, calibration, and SteamVR launch integration

* Add XREAL 1S support and correct GT and Pro 2 tracking

* Add XREAL 1S support and correct GT and Pro 2 tracking

* Fix mirrored glasses tracking and recover SteamVR after device loss

* Fix mirrored glasses tracking and recover SteamVR after device loss

* Document confirmed XREAL 1S brightness control

* Document confirmed XREAL 1S brightness control

* Fix SteamVR glasses defaults and rendering controls

* Fix SteamVR glasses defaults and rendering controls

* chore: keep only the 3 newest session log folders (#852)

* chore: add agent comment rules (#748)

Co-authored-by: Kurt Himebauch <[email protected]>

* Fix SteamVR polling, VITURE checksums, and fence tracking

Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter.

Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage.

* Fix SteamVR polling, VITURE checksums, and fence tracking

Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter.

Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage.

* fix: wireless ADB page names the child-process switch the way Android shows it (#848)

* l10n: refresh stale and untranslated strings in es, fr, ko, ru (#850)

* l10n: refresh stale and untranslated strings in es, fr, ko, ru

- mode_added_art_hint: now mentions Steam then SteamGridDB (all four locales).
- wincomp_needs_installer / wincomp_installers_note: es and ko still described
  the old "coming soon" behaviour; align with the current English text.
- summary_remove_game: was still English in all four locales.

Machine-assisted translation; native-speaker review is welcome.

* l10n(es): say registro, not grabación, for the missing installer record

---------

Co-authored-by: ovurrsl <[email protected]>

* fix: a failed cloud save recovery no longer ends the app (#846)

The cloud-recovery thread started at every app start had nothing to
catch an error, so one went to CrashHandler, which halts the process
with status 2. In unit tests the thread can outlive the test that
started it, fail on the torn-down preferences, and take the whole test
run down. The error is now logged and the thread ends.

Co-authored-by: Kirk <[email protected]>

* fix: an interrupted Linux runtime download resumes instead of starting again (#847)

* fix: an interrupted Linux runtime download resumes instead of starting again

installOnce() asked the downloader to resume but always deleted the archive in its finally block, so the next attempt began at zero. The archive is now named after the release version and checksum, a failed download keeps what it fetched, and archives of other releases (and the old fixed name) are removed. A checksum or extraction failure still deletes the file.

* test: Downloader resumes a cut-off download from the bytes it kept

Starts a socket server that promises the whole file, hangs up part way and then answers a Range request, and checks that the second downloadFile call resumes with the right offset and ends with identical bytes.

* fix: a resumed download discards a 206 that does not start where the kept bytes end

Downloader appended every 206 response to the partial file without
looking at Content-Range. A server or proxy that answers a Range request
with a different offset would leave a corrupt file. Check that the range
starts at the kept length; otherwise delete the partial file and start
again without a Range header.

Adds a test with a server that answers the Range request from offset 0.

---------

Co-authored-by: ovurrsl <[email protected]>

* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings (#849)

* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings

Adds the strings values-de, values-it and values-pt were missing (14 each) and the six that values-fr, values-es and values-ru were missing. Machine-assisted drafts that follow each file's existing register and terms; native review is welcome.

* l10n: correct missing active swap translations

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>

* fix: droiddeckctl reaches variant-package builds (recover #842) (#854)

fix: droiddeckctl reaches builds shipped under a variant package

The provider authority and the start Activity's component were fixed to
com.droiddeck.launcher, so droiddeckctl could not talk to the antutu,
ludashi or pubg builds from tools/release/variants.txt. DROIDDECK_PACKAGE
now picks the package; the Activity's class name stays the same in every
variant.

Co-authored-by: angelrenefm <[email protected]>

* feat: droiddeckctl reports on-screen pacing (recover #843) (#855)

feat: droiddeckctl pacing reports the frames that reached the screen

Reads SurfaceFlinger's timestats for the game's layer (or the session's
SurfaceView) over a capture: frames on screen, fps on screen, the
present-to-present histogram and how many intervals were off the usual
cadence. Records split by a refresh-rate change are summed. A game's own fps
counter counts frames handed over, so it can't show dropped or doubled frames.

Co-authored-by: angelrenefm <[email protected]>

* fix: Steam can restart more than twice in a session (#754)

* fix: Steam can restart more than twice in a session

Every exit 42 counted toward the cap that stops an update which will
not install, and the count was never cleared, so the third restart of
a session ended it however far apart they were. A client that ran for
a minute before asking is not that loop: the count starts again.

* fix: measure the client's run time as it exits

The time spent collecting logs and checking FEX after the client exits
counted toward its run, so a slow cleanup could make a quick exit 42
look like a long one and clear the count.

---------

Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>

* fix: the front end keeps its page when the Fold is folded or unfolded (#832)

* fix: the front end keeps its page when the Fold is folded or unfolded

MainActivity, FileManagerActivity, FilePickerActivity and StoreLoginActivity did not declare the screen-size configuration changes, so folding or unfolding a foldable recreated them and dropped their in-memory state. They now handle orientation, screenSize, screenLayout, smallestScreenSize and density themselves, like SessionActivity already does. uiMode is left out on purpose.

* fix: store login keeps rebuilding its toolbar when the display density changes

StoreLoginActivity builds its toolbar in code with dp-to-pixel values taken at creation time and has no onConfigurationChanged. It no longer declares density in configChanges, so a density change recreates it as before this PR. Size and layout changes are still handled in place.

---------

Co-authored-by: ovurrsl <[email protected]>

* Release pointer buttons without replaying stale motion (#803)

* Fix cursor warping on native touch down

* Preserve input cleanup without replaying stale pointer motion

---------

Co-authored-by: siyuan.qsy <[email protected]>

* l10n: translate the strings added since #284 into zh-CN, zh-HK, zh-TW and ja (#539)

* l10n: translate the 184 strings added since #284 (zh-CN/HK/TW, ja)

The stores, cloud saves, Windows components, Steam touch, desktop
removal and background-downloads strings landed after #284's
translation snapshot, so every one of them fell back to English.
Translate all 184 into Simplified Chinese, Traditional Chinese
(Taiwan and Hong Kong) and Japanese, keeping each locale file's own
vocabulary (工作階段/會話, 解除安裝/卸載, 元件/組件, 設定).

Also fix four Hong Kong strings that kept the simplified 着色器.

* l10n(zh): fix stale and ambiguous strings found in review

Severe: summary_remove_game was untranslated English in all three
files; mode_added_art_hint, wincomp_installers_note and
wincomp_needs_installer still described the pre-#480/#492 English.

Also: distinguish the Setup wizard from Android system settings in
zh-HK/zh-TW (初始設定 / 系統設定 / Android 設定), unify library as
收藏庫, component as 組件 in zh-HK, session as 會話 in zh-HK, account
as 帳戶 in zh-HK, Trending as 趋势/趨勢, Forget as 清除, reword the
re-applied-at-launch, action-failed and already-in-Games strings, and
play as 執行/開啟遊戲 in zh-TW/zh-HK.

* l10n(ja): fix stale and ambiguous strings found in review

---------

Co-authored-by: Dantalian <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>

* Rebuild and verify Windows VR runtime components

* Rebuild and verify Windows VR runtime components

* Add top, bottom, and centered picture positions for Steam sessions (#871)

* fix: dependency scan ignores folders above the game folder (#859)

* DroidDeck-Proton: get releases from the Droid-Deck org repo

DroidDeck-Proton is now built and released on Droid-Deck/DroidDeck-Proton
(11-9.1 is Latest there), so the Components page, the update check and the
guest installer read releases from the org repo instead of The412Banner's.

* feat: low-latency controller and touch toggles in the session drawer (#808)

* fix: SteamGridDB lookups no longer stick as "no match" after a failure; add a search to the art chooser (#876)

* feat: Steam home wall fills from the signed-in library instead of store picks (#878)

* test: cover GPU family and support classification (#880)

* Fix local builds on Linux failing after the first Comet build (#866)

* feat: sort the store library and installed tabs, and switch between card and list views (#798)

* feat: three-finger tap toggles Steam's on-screen keyboard in game mode (#782)

* fix: keep Quest-patched Turnip as the auto default (#853)

* Fix deploy_local.sh and droiddeckctl not finding a phone connected over wireless debugging (#864)

* fix: keep proot sessions alive when the kernel loses fork events (#457)

* fix(quest): correct desktop and Steam display corruption across sessions (#733)

fix(quest): stabilize desktop and Steam layer presentation

Keep Quest SDR scenes and the black backdrop off the Android Vulkan swapchain regardless of session order or HDR preferences. Only report the first frame after client content is submitted.

Add backdrop lifecycle and first-frame regression coverage. Record the user's successful Quest 3 Test 2 result.

Co-authored-by: xXJsonDeruloXx <[email protected]>

* fix: keep SteamVR syscall hooks and report Steam crashes (#905)

* fix: preserve SteamVR syscall hooks

* fix: report Steam crashes through gamescope

* fix: honor glasses pacing and resolution in SteamVR launches

---------

Co-authored-by: connor g <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: Yassine Gherbi <[email protected]>
Co-authored-by: Resul Övür <[email protected]>
Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: angelrenefm <[email protected]>
Co-authored-by: asdf17128 <[email protected]>
Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: FX1922 <[email protected]>
Co-authored-by: Muaz Kadan <[email protected]>
Co-authored-by: kingokksa <[email protected]>
Co-authored-by: Jamie Schenker <[email protected]>
Co-authored-by: Jagger15 <[email protected]>

* Glasses: keep a pending 2D restore and the SteamVR recovery offer, show Play SteamVR only with a picture

* Translate the glasses calibration, XREAL and SteamVR recovery strings

* SteamVR recovery: give a finished teardown time to collect logs before restarting

* Credit the projects the XR glasses support builds on

* Joy-Cons: SteamVR hands from their gyro while SteamVR runs, one combined pad otherwise

* Headsets: settings badge on Play SteamVR instead of a top-right button, both shown only once connected

* Hand tracking: camera-tracked Joy-Cons and bare hands in SteamVR, drawn as white gloves

- Joy-Con movement from the accelerometer (report reordering, per-Joy-Con calibration, zero-velocity updates)
- MediaPipe pose and hand landmarks from the device camera, placement check, per-hand Kalman filter with the Joy-Con
- No controller connected: bare camera hands with Quest-style gestures, 1€ filtered, rendered as a white glove model built from a distance field
- SteamVR safe mode cleared before each start so one crash no longer blocks the driver

---------

Co-authored-by: MaxsTechReview <[email protected]>
Co-authored-by: informalTechCode <[email protected]>
Co-authored-by: connor g <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: Yassine Gherbi <[email protected]>
Co-authored-by: Resul Övür <[email protected]>
Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: angelrenefm <[email protected]>
Co-authored-by: asdf17128 <[email protected]>
Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: FX1922 <[email protected]>
Co-authored-by: Muaz Kadan <[email protected]>
Co-authored-by: kingokksa <[email protected]>
Co-authored-by: Jamie Schenker <[email protected]>
Co-authored-by: Jagger15 <[email protected]>
2026-10-17 13:17:05 -04:00
kingokksa 8acf3037fd l10n: translate the missing strings in every locale, and complete es-419 (#1013)
* l10n: translate the missing strings into zh-CN, zh-HK and zh-TW

* l10n: translate the missing strings into ja and ko

* l10n: translate the missing strings into it, pt, pt-PT, ru and tr

* l10n: translate the missing strings into nl, pl, sv, da, nb and fi

* l10n: translate the missing strings into ar, bg, cs and el

* l10n: translate the missing strings into hi, hu, in, ms and ro

* l10n: translate the missing strings into th, uk and vi

* l10n: complete the es-419 translation

* l10n: translate the missing strings into de, es and fr
2026-10-17 11:44:59 -04:00
FX1922 77c3fd323e feat: Adjust a game’s logo position and size in the artwork editors (#1004)
* Add an Adjust logo screen to move and resize a game's logo in Steam

* Report a failed write of the local logo position instead of throwing

* Record a Steam-side logo position change only once it has been taken over here
2026-10-17 10:02:22 -04:00
FX1922 dc7ed61d38 Feat/store game artwork (#976) 2026-10-17 01:19:02 -04:00
Resul Övür fb0f73e300 feat: the session report lists the panel refresh modes, thermal state and hint-session support (#990) 2026-10-17 02:31:17 +00:00
Resul Övür 197098f92e Launch an added game after a bounded wait for its Steam overview (#973) 2026-10-16 17:21:53 -04:00
Kurt Himebauch 602a6fd734 feat: auto drivers update on app open and roll back if a new pair never starts a session (#972) 2026-10-16 16:17:44 -04:00
FX1922 4b2537a36b fix: Keep game art in step with Steam's grid folder; apply review fixes (#948) 2026-10-16 16:14:49 -04:00
Kurt Himebauch 6bc6edafea feat: boot movie settings for 30/60 fps, sound and custom imports (#969) 2026-10-16 15:07:46 -04:00
Kurt Himebauch 23479d15f5 Feat/gpu drivers redesign (#858) 2026-10-16 17:50:45 +00:00
kingokksa 7c90879b39 feat: downloads resume where they stopped, and fall back to another source (#623) 2026-10-16 13:45:35 -04:00
Kurt Himebauch 5e57d78b5d fix: reduce flickering on thor and a7xx: match the A740 TP UBWC flag hint to the system driver so Android UI stops flickering (#963) 2026-10-16 13:37:28 -04:00
TideGear 3f3ed3923b fix: hold XInput rumble until the game changes it, instead of stopping after a second (#938) 2026-10-16 17:30:21 +00:00
Kurt Himebauch 2c37f79701 fix: session achievements page no longer sticks on Reading (#961) 2026-10-16 15:51:57 +00:00
Kurt Himebauch 8b7a22e19e feat: merge SteamVR and Cardboard into one VR drawer tab (#959) 2026-10-16 14:59:34 +00:00
angelrenefm 91b02a9dda fix: the proot fast path finds unix sockets whose host path is longer than sun_path (#841)
* fix: the proot fast path finds unix sockets whose host path is longer than sun_path

When a socket's host path did not fit sun_path, bind() went to proot, which
bound it to a short temporary name the fast path cannot see. The fast path's
own connect, stat and unlink then answered ENOENT, so FEXServer's socket under
compatdata/<app>/fex-emu/Server could never be reached (#839, #579).

Name such a socket through its directory (/proc/self/fd/N/<name>) instead, so
it lives at its real host path for every process, and report the guest path
back from getsockname, getpeername and accept. equiv3.py's long-path section
binds, connects, stats, names and unlinks a socket there.

* fix: key long socket names per socket and reset their lock after fork

Review follow-up. A long name is now looked up by the socket's inode and the
name, separately for its own and its peer's address, so two sockets with the
same file name in different directories no longer mix up; a missing entry is
left as the kernel reports it. The lock is re-initialised in the child after
fork, and the copies use snprintf. equiv3.py binds a second socket with the
same name in a sibling directory and checks both names.
2026-10-16 10:05:12 -04:00
angelrenefm 4cf4cf57b7 fix: droiddeckctl refuses a DROIDDECK_PACKAGE that is not a package name (#896)
The value goes to the device shell inside `am start -n` and `content call --uri`.
Anything but a dotted Android package name is refused before adb runs.
2026-10-16 10:05:02 -04:00
angelrenefm cd04e056db fix: droiddeckctl pacing judges cadence per refresh-rate record and always turns timestats off (#894)
* fix: droiddeckctl pacing judges cadence per refresh-rate record and always turns timestats off

Follow-up to the CodeRabbit reviews of #843/#855:
- off-cadence is counted per layer record against that record's own cadence,
  allowing the neighbouring millisecond bucket (16.7 ms lands in 16 or 17);
- refreshRates comes from the chosen layer records only;
- records with no counted frames are skipped, so an empty game layer no longer
  hides the session's SurfaceView, and a capture with no frames exits NO_FRAMES;
- adb failures while enabling or dumping map to PACING_FAILED, and timestats
  is disabled in every case.

* fix: droiddeckctl pacing reports when timestats could not be turned off
2026-10-16 09:50:43 -04:00
Resul Övürandovurrsl a4f2c3b3f0 Keep waiting for a shortcut's Steam overview instead of dropping its cold launch (#914)
fix: keep waiting for a shortcut's Steam overview instead of dropping its launch

The cold-launch gate waited once for 45 s and, on a timeout, held the
launch URL for good. A library that loads slower than that (or a client
still at the sign-in screen) left the requested game never started, with
nothing on screen. Retry the wait up to 40 times, 45 s each, and launch
as soon as the overview exists. The no-overview case still does not
forward the CGameID.

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:50:35 -04:00
Resul Övürandovurrsl ca6a26e078 fix: the store sign-in page keeps its place when the display density changes (#886)
* fix: the store sign-in page keeps its place when the display density changes

StoreLoginActivity sized its toolbar and error panel in code with dp(), so it
was left out of the density change and a fold between two displays of
different density still recreated it and reloaded the sign-in page. Handle
density in the manifest and apply the dp sizes again in onConfigurationChanged.

Checked on the foldable emulator with the GOG sign-in open: wm density
420 -> 560 -> 320 does not relaunch the activity, the page stays, and the top
bar follows the new density.

* fix: re-apply the sign-in text sizes together with the other dp sizes

The title (16sp) and error text (15sp) were set in sp at creation, so after a density change they kept the old scale. Set them again in onConfigurationChanged.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:50:28 -04:00
Resul Övürandovurrsl 631213728c fix: the store sign-in WebView cannot read local files or content URIs (#890)
The sign-in pages are ordinary web pages. file access and content access
default to on below targetSdk 30, so switch both off explicitly.

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:50:21 -04:00
Resul Övürandovurrsl d573b60e4c fix: the launcher does not offer its own display as a second screen (#889)
* fix: the launcher does not offer its own display as a second screen

MainActivity asked for the second-screen candidates with the default
display as the primary one. When the launcher is itself shown on another
display (DeX, an external monitor), that display was listed as a second
screen. Pass the Activity's own display id, as SessionActivity already does.

* fix: read the Activity display only on API 30 and newer

minSdk is 26 and Activity.getDisplay needs API 30. Use windowManager.defaultDisplay below that.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:50:14 -04:00
Resul Övürandovurrsl 0d2e268dd5 fix: store background jobs log a failure instead of ending the app (#898)
* fix: store background jobs log a failure instead of ending the app

refresh, sign-out, uninstall and the EOS overlay cleanup ran on plain threads. An exception there reaches the crash handler, which ends the process. Run them through one helper that logs the failure; a failed uninstall also clears its row and calls onDone so the UI does not stay on uninstalling.

* ci: run the build again

The first run failed while installing packages (archive.debian.org returned 404 for libnsl-dev). Nothing in the change differs.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:50:06 -04:00
Resul Övürandovurrsl c74a6b3888 l10n: refresh outdated strings, one name for the child-process switch, pt-rPT gaps (#884)
l10n: refresh outdated strings and name the child-process switch the same way everywhere

- hu: ctrl_size_hint showed a literal "%%".
- es, fr, ru: game_env_reset, mode_controller_note and comp_no_proton still
  described older English text.
- fr: the x86_64 AppImage (through FEX) strings were missing the x86_64 case.
- fr: put the spaces back inside the guillemets of the switch name.
- All locales: gate_toggle_steps and phantom_fix_toggle now use the same
  switch name as wadb_restrict_on/off, which is the title Android 17 shows.
- pt-rPT: add the 14 strings it was missing (reset settings, zRAM, store
  free space), as a European Portuguese draft.

Machine-assisted; native review is welcome.

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:49:58 -04:00
Resul Övür 2388527fe5 feat: the session report lists the memory page size and the last process exits (#897) 2026-10-16 09:46:10 -04:00
Resul Övürandovurrsl 9db3b7a20b fix: read Settings.Global phantom process flag on Android 12 (SDK 31) (#955)
On Android 12 (SDK 31), read() only checked the android12-off preference written by in-app Wireless ADB, so disabling phantom process monitoring over USB ADB left the status as UNREADABLE. Also write settings_enable_monitor_phantom_procs in the SDK 31 shell commands and accept a disabled Settings.Global / fflag value in read().

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:37:03 -04:00
Zeyad Saber Refaei Kenawi d269f26604 Add support for android auto (#952)
Add CAR_LAUNCHER intent filter to support android auto
2026-10-16 09:35:39 -04:00
Kurt Himebauch 700a843b10 Fix Quest CI headers when preloads are cached (#951) 2026-10-16 12:35:55 +00:00
FX1922 bab09b798a feat: Change artwork for Steam games; remove “Show more” from the SteamGridDB search. (#939) 2026-10-16 02:08:09 -04:00
kmnkv1 8726e43658 Sync session logs every 2 s so a hard reset keeps their last minutes (#513) 2026-10-16 01:51:56 -04:00
kmnkv1 78429c93a8 perf: boost the game's busiest threads onto the prime cores on the Odin 3 (#682) 2026-10-16 01:51:00 -04:00
Kurt Himebauch 679e3d8934 feat: add immersive Quest SteamVR to the standard app (#910) 2026-10-16 01:48:00 -04:00
Kurt Himebauch 559120313a update readme (#937)
docs: credit mmap input work
2026-10-15 23:00:15 -04:00
Kurt Himebauch 54d5f30ee7 fix: pace adaptive frame generation on the game's frame rate, not the compositor loop's (#927) 2026-10-15 18:36:40 -04:00
Resul Övür 9d6251c37b feat: the second-screen Deck controls get the Gamepad tab (#895) 2026-10-15 16:54:43 -04:00
Resul Övür bf5a33a0c8 fix: update zstd-jni so the Linux runtime installs on a 16 KB page-size device (#900) 2026-10-15 16:54:04 -04:00
Kurt Himebauch ccafdf99c3 feat: storage page to see and free DroidDeck's storage (#913) 2026-10-15 16:45:37 -04:00
Kurt Himebauch 9ce5a24459 fix: keep SteamVR syscall hooks and report Steam crashes (#905)
* fix: preserve SteamVR syscall hooks

* fix: report Steam crashes through gamescope
2026-10-15 15:50:26 +00:00
Jagger15andxXJsonDeruloXx a692436f67 fix(quest): correct desktop and Steam display corruption across sessions (#733)
fix(quest): stabilize desktop and Steam layer presentation

Keep Quest SDR scenes and the black backdrop off the Android Vulkan swapchain regardless of session order or HDR preferences. Only report the first frame after client content is submitted.

Add backdrop lifecycle and first-frame regression coverage. Record the user's successful Quest 3 Test 2 result.

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-15 09:51:02 -04:00
Jamie Schenker c3cd955686 fix: keep proot sessions alive when the kernel loses fork events (#457) 2026-10-15 09:50:29 -04:00
Muaz Kadan cd18bdb3dc Fix deploy_local.sh and droiddeckctl not finding a phone connected over wireless debugging (#864) 2026-10-15 09:47:01 -04:00
Kurt Himebauch ea0a61ca2b fix: keep Quest-patched Turnip as the auto default (#853) 2026-10-15 09:46:26 -04:00
Kurt Himebauch 356f048f00 feat: three-finger tap toggles Steam's on-screen keyboard in game mode (#782) 2026-10-15 09:36:28 -04:00
kingokksa 82f757f000 feat: sort the store library and installed tabs, and switch between card and list views (#798) 2026-10-15 09:20:49 -04:00
Muaz Kadan aa9552d999 Fix local builds on Linux failing after the first Comet build (#866) 2026-10-15 09:20:03 -04:00
Resul Övür 091f5e45f8 test: cover GPU family and support classification (#880) 2026-10-15 09:19:40 -04:00
Kurt Himebauch e3c197d94e feat: Steam home wall fills from the signed-in library instead of store picks (#878) 2026-10-15 09:18:07 -04:00
FX1922 8874276458 fix: SteamGridDB lookups no longer stick as "no match" after a failure; add a search to the art chooser (#876) 2026-10-15 08:36:48 -04:00
Kurt Himebauch 7c73adb3aa feat: low-latency controller and touch toggles in the session drawer (#808) 2026-10-15 07:53:25 -04:00
The412Banner ef8e748a94 DroidDeck-Proton: get releases from the Droid-Deck org repo (#875) 2026-10-15 07:29:42 -04:00
The412Banner 454ae3e9c2 DroidDeck-Proton: get releases from the Droid-Deck org repo
DroidDeck-Proton is now built and released on Droid-Deck/DroidDeck-Proton
(11-9.1 is Latest there), so the Components page, the update check and the
guest installer read releases from the org repo instead of The412Banner's.
2026-10-15 07:20:09 -04:00
Kurt Himebauch 9e7b4c0b50 fix: dependency scan ignores folders above the game folder (#859) 2026-10-15 07:03:17 -04:00
asdf17128 d3b0b8e51c Add top, bottom, and centered picture positions for Steam sessions (#871) 2026-10-15 07:01:01 -04:00
9fd13cec4d l10n: translate the strings added since #284 into zh-CN, zh-HK, zh-TW and ja (#539)
* l10n: translate the 184 strings added since #284 (zh-CN/HK/TW, ja)

The stores, cloud saves, Windows components, Steam touch, desktop
removal and background-downloads strings landed after #284's
translation snapshot, so every one of them fell back to English.
Translate all 184 into Simplified Chinese, Traditional Chinese
(Taiwan and Hong Kong) and Japanese, keeping each locale file's own
vocabulary (工作階段/會話, 解除安裝/卸載, 元件/組件, 設定).

Also fix four Hong Kong strings that kept the simplified 着色器.

* l10n(zh): fix stale and ambiguous strings found in review

Severe: summary_remove_game was untranslated English in all three
files; mode_added_art_hint, wincomp_installers_note and
wincomp_needs_installer still described the pre-#480/#492 English.

Also: distinguish the Setup wizard from Android system settings in
zh-HK/zh-TW (初始設定 / 系統設定 / Android 設定), unify library as
收藏庫, component as 組件 in zh-HK, session as 會話 in zh-HK, account
as 帳戶 in zh-HK, Trending as 趋势/趨勢, Forget as 清除, reword the
re-applied-at-launch, action-failed and already-in-Games strings, and
play as 執行/開啟遊戲 in zh-TW/zh-HK.

* l10n(ja): fix stale and ambiguous strings found in review

---------

Co-authored-by: Dantalian <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-15 03:41:17 +00:00
asdf17128andsiyuan.qsy 9cc802dc9c Release pointer buttons without replaying stale motion (#803)
* Fix cursor warping on native touch down

* Preserve input cleanup without replaying stale pointer motion

---------

Co-authored-by: siyuan.qsy <[email protected]>
2026-10-14 23:37:35 -04:00
Resul Övürandovurrsl 700bf18ab2 fix: the front end keeps its page when the Fold is folded or unfolded (#832)
* fix: the front end keeps its page when the Fold is folded or unfolded

MainActivity, FileManagerActivity, FilePickerActivity and StoreLoginActivity did not declare the screen-size configuration changes, so folding or unfolding a foldable recreated them and dropped their in-memory state. They now handle orientation, screenSize, screenLayout, smallestScreenSize and density themselves, like SessionActivity already does. uiMode is left out on purpose.

* fix: store login keeps rebuilding its toolbar when the display density changes

StoreLoginActivity builds its toolbar in code with dp-to-pixel values taken at creation time and has no onConfigurationChanged. It no longer declares density in configChanges, so a density change recreates it as before this PR. Size and layout changes are still handled in place.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-14 23:30:19 -04:00
8f4e0ac011 fix: Steam can restart more than twice in a session (#754)
* fix: Steam can restart more than twice in a session

Every exit 42 counted toward the cap that stops an update which will
not install, and the count was never cleared, so the third restart of
a session ended it however far apart they were. A client that ran for
a minute before asking is not that loop: the count starts again.

* fix: measure the client's run time as it exits

The time spent collecting logs and checking FEX after the client exits
counted toward its run, so a slow cleanup could make a quick exit 42
look like a long one and clear the count.

---------

Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-15 02:18:26 +00:00
Kurt Himebauchandangelrenefm 704e95edb3 feat: droiddeckctl reports on-screen pacing (recover #843) (#855)
feat: droiddeckctl pacing reports the frames that reached the screen

Reads SurfaceFlinger's timestats for the game's layer (or the session's
SurfaceView) over a capture: frames on screen, fps on screen, the
present-to-present histogram and how many intervals were off the usual
cadence. Records split by a refresh-rate change are summed. A game's own fps
counter counts frames handed over, so it can't show dropped or doubled frames.

Co-authored-by: angelrenefm <[email protected]>
2026-10-15 02:17:06 +00:00
Kurt Himebauchandangelrenefm af13f04156 fix: droiddeckctl reaches variant-package builds (recover #842) (#854)
fix: droiddeckctl reaches builds shipped under a variant package

The provider authority and the start Activity's component were fixed to
com.droiddeck.launcher, so droiddeckctl could not talk to the antutu,
ludashi or pubg builds from tools/release/variants.txt. DROIDDECK_PACKAGE
now picks the package; the Activity's class name stays the same in every
variant.

Co-authored-by: angelrenefm <[email protected]>
2026-10-14 22:15:29 -04:00
02966a288e l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings (#849)
* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings

Adds the strings values-de, values-it and values-pt were missing (14 each) and the six that values-fr, values-es and values-ru were missing. Machine-assisted drafts that follow each file's existing register and terms; native review is welcome.

* l10n: correct missing active swap translations

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-14 22:09:50 -04:00
Resul Övürandovurrsl 03f1a14e81 fix: an interrupted Linux runtime download resumes instead of starting again (#847)
* fix: an interrupted Linux runtime download resumes instead of starting again

installOnce() asked the downloader to resume but always deleted the archive in its finally block, so the next attempt began at zero. The archive is now named after the release version and checksum, a failed download keeps what it fetched, and archives of other releases (and the old fixed name) are removed. A checksum or extraction failure still deletes the file.

* test: Downloader resumes a cut-off download from the bytes it kept

Starts a socket server that promises the whole file, hangs up part way and then answers a Range request, and checks that the second downloadFile call resumes with the right offset and ends with identical bytes.

* fix: a resumed download discards a 206 that does not start where the kept bytes end

Downloader appended every 206 response to the partial file without
looking at Content-Range. A server or proxy that answers a Range request
with a different offset would leave a corrupt file. Check that the range
starts at the kept length; otherwise delete the partial file and start
again without a Range header.

Adds a test with a server that answers the Range request from offset 0.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-14 22:01:29 -04:00
KirkandKirk 98be0838c4 fix: a failed cloud save recovery no longer ends the app (#846)
The cloud-recovery thread started at every app start had nothing to
catch an error, so one went to CrashHandler, which halts the process
with status 2. In unit tests the thread can outlive the test that
started it, fail on the torn-down preferences, and take the whole test
run down. The error is now logged and the thread ends.

Co-authored-by: Kirk <[email protected]>
2026-10-14 22:01:17 -04:00
Resul Övürandovurrsl 04f7b7e162 l10n: refresh stale and untranslated strings in es, fr, ko, ru (#850)
* l10n: refresh stale and untranslated strings in es, fr, ko, ru

- mode_added_art_hint: now mentions Steam then SteamGridDB (all four locales).
- wincomp_needs_installer / wincomp_installers_note: es and ko still described
  the old "coming soon" behaviour; align with the current English text.
- summary_remove_game: was still English in all four locales.

Machine-assisted translation; native-speaker review is welcome.

* l10n(es): say registro, not grabación, for the missing installer record

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-14 22:00:54 -04:00
Resul Övür 1357fd69ee fix: wireless ADB page names the child-process switch the way Android shows it (#848) 2026-10-14 21:45:18 -04:00
Yassine GherbiandKurt Himebauch 78aba50630 chore: add agent comment rules (#748)
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-14 21:39:47 -04:00
Kurt Himebauch 0ae0d45bdf chore: keep only the 3 newest session log folders (#852) 2026-10-14 21:32:05 -04:00
Kurt Himebauch 6526fffc02 fix: Steam touch controls load the layout Steam picked for DroidDeck's touch controller, including workshop layouts (#840) 2026-10-14 16:09:40 -04:00
RaidennrlandxXJsonDeruloXx f704fabc69 Fix wired PowerA and PDP right-stick and trigger mapping (#692)
Fix wired PowerA and PDP stick and trigger axes

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-14 13:56:17 -04:00
kmnkv1andxXJsonDeruloXx 60caa85353 Build and test on Windows: LF session scripts, '/' in relative paths (#552)
fix: build and test on Windows

- .gitattributes keeps the session's scripts LF under core.autocrlf=true;
  CRLF scripts made every session exit with status 2.
- runtime_inputs.py and the scrub record / log zip use '/' in relative
  paths, which Windows reported with '\' (runtime inputs never matched).
- The read-only-folder scrub test is skipped on Windows, which still
  creates files in a read-only directory.

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-14 13:56:12 -04:00
Gustash 9e1b3c1ac1 feat: hide unfolded controls when a controller is connected (#829)
* feat: hide unfolded controls when a controller is connected

* fix: also hide controls when a BLE controller is connected

* fix: use refreshControls on session resume as well
2026-10-14 13:52:13 -04:00
Tjebbe Kerstens 2e65efcfa7 fix: seed redist markers again when the registry lost them (#793)
* fix: redist markers a replaced registry lost are seeded again

droiddeck-seed-redists recorded what it seeded in .droiddeck-redists and
trusted that record from then on. A Proton update replaces its
default_pfx system.reg but leaves our record beside it, and every new
prefix copies both, so new games got a record of markers their registry
never had: Steam then ran their install scripts for real. Batman: Arkham
City's AMD Dual-Core Optimizer installer hangs there, leaving the game
on "Running install script" on every launch.

Each recorded marker is now looked up in the registry, in both views
where both were written, and seeded again when it is gone; a record of
markers nobody wants any more is rewritten without them.

* Check every VC runtime architecture before keeping the vcrt marker

vcrt writes Installed for x86, x64 and arm64 in both registry views, but
present() only looked at x86. If only an x64 or arm64 value was gone the
runtime would not be seeded again. Require all six values and test the
loss of the arm64 Wow6432Node one.
2026-10-14 13:52:07 -04:00
Tjebbe Kerstens 6528a7ed64 fix: Batman: Arkham City starts with full x87 precision under FEX (#792)
Arkham City (BatmanAC.exe) stopped with Unreal's "Fatal error!" about
1.5 s after start: an access violation in BatmanAC.exe right after
steam.dll loads. The game leans on x87 and needs full precision under
FEX (FEX wiki), while the default FEX preset sets
FEX_X87REDUCEDPRECISION=1. Engine fixes sit above the shared preset and
below the player's own game profile, so a fix keyed on BatmanAC.exe
beside the started exe (BatmanAC.exe or BmLauncher.exe) restores
x87 precision for this game only. Arkham Asylum, which shares
BmLauncher.exe but not BatmanAC.exe, keeps the preset.
2026-10-14 13:52:01 -04:00
KirkandKirk b201162475 fix: game shortcuts use the cover art when Steam's icon is too small (#825)
Steam keeps a 32x32 icon for each game, and the home screen stretched
it to launcher size. A square from the middle of the cover art is used
instead unless the icon is at least 128 px; the small icon is still
the fallback when there is no art.

Co-authored-by: Kirk <[email protected]>
2026-10-14 13:51:56 -04:00
Muaz Kadan ce444fbacb Fix battery time estimates showing thousands of hours on phones that report current in mA (#820)
fix: read battery current reported in mA so time estimates aren't 1000x too long
2026-10-14 13:51:51 -04:00
William Allen 422429d63f fix: read the replaced listing's watch descriptor before detach can free it (#685)
install() replaces an existing cache entry by calling detach(old), which
destroys the listing when no reader holds it, and then reads old->wd to
decide whether to release the watch. When the old listing has no readers,
that read happens after the free.

Two threads that enumerate the same uncached directory at the same time
reach this path: both build a listing, the first installs an entry with
no readers, and the second replaces it.

Save the watch descriptor before detaching, as drop() already does.
2026-10-14 13:51:44 -04:00
Resul Övürandovurrsl 29f5e8c689 fix: Turkish translation for zRAM, reset-settings and store storage messages (#826)
Adds the 14 translatable strings that values-tr was missing: the zRAM toggle and its hints, the Reset all settings row, dialog and toasts, and the store install free-space messages. Keys marked translatable=false stay untranslated on purpose.

Co-authored-by: ovurrsl <[email protected]>
2026-10-14 13:51:38 -04:00
Resul Övürandovurrsl 8810a56df3 docs: README names the child-process setting the way Android shows it (#831)
* docs: README names the child-process setting the way Android shows it

Developer options label this switch "Disable child process restrictions", and it has to be turned on. The README said to turn off "Restrict child processes".

* docs: README limits the child-process toggle to Android 14 and newer

The Developer options switch only exists from Android 14 (PhantomProcessLimit.hasDeveloperToggle); Android 12 and 13 keep the Fix it for me flow.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-14 13:51:34 -04:00
Imgau 58140a247d fix: declare all-files access for raw USB storage (#760) 2026-10-14 13:48:14 -04:00
Kurt Himebauch 72bd6be38d feat: store sign-in walls and the Steam home wall fill with curated featured art (#835) 2026-10-14 13:42:27 -04:00
Kushagra 30854422ff feat: map a controller's two back paddles to the Deck's L4 and R4 (#797) 2026-10-14 15:31:59 +00:00
Gustash 3ad5f936bf fix: the touchpad moves a game's own cursor when it locks the pointer (#772) 2026-10-14 11:30:54 -04:00
Kurt Himebauch 48adf4d1ff perf: local builds reuse unchanged native steps across runs and worktrees (#819) 2026-10-14 15:30:24 +00:00
Kurt Himebauch 0f084d1bab fix: store installs stay shortcuts instead of becoming Steam installs (#816) 2026-10-14 10:10:12 -04:00
Kurt Himebauch fed9474539 Show controller-driven cursors in nested Wayland sessions (#759) 2026-10-14 08:25:08 -04:00
The412Banner c8ac2d1e91 fix(stores): drop expired Epic and Amazon tokens when a refresh fails (#804)
A failed token refresh returned the stored access token even after it had expired, so the next request failed with a generic error instead of asking to sign in again. The stored token is now returned only while it is still valid.

The Epic docs now say that achievements are sent with Epic's launcher user agent, which the achievements service requires.
2026-10-14 07:43:02 -04:00
The412Banner 7ad3c60efe feat: rewrite the Epic and Amazon store modules (#802)
Both store modules are rewritten from a protocol-level spec: new Rust engines behind a small JNI surface (nativeCall/nativeInstall) and thin Kotlin facades, with the launcher's own user agent.

Epic: sign-in carries over, library, manifest download with byte-exact verification, assembly, uninstall, launch arguments, achievements.
Amazon: sign-in carries over, library, manifest download with per-file hash checks, path checks, executable detection from fuel.json, uninstall.
On-disk install layout is unchanged, so existing installs keep working.

Store game pages now take controller focus on their lead hero action (Install, Uninstall, Pause, Resume, Get, Buy), fall back to Back, and keep D-pad focus inside the page while it covers the library.
2026-10-14 07:20:06 -04:00
Kurt Himebauch a849556f3f docs: credit Phobos for Epic and GOG integration work (#801) 2026-10-14 06:41:42 -04:00
Kurt Himebauch 7ea4efef98 perf: the session's background watchers share two loops and one D-Bus service process (#575) 2026-10-14 01:02:24 -04:00
Kurt Himebauch a0d3e03ca0 perf: Steam's web helper runs its GPU, audio and zygote work inside one process (#566) 2026-10-14 01:01:49 -04:00
Kurt Himebauch 9f2e364157 Verify native SD-card library paths before binding them (#757) 2026-10-14 00:59:38 -04:00
Kurt Himebauch f9b58837d0 Fix microphone capture in DirectAudio games (#747) 2026-10-14 00:58:52 -04:00
Kurt Himebauch 7ab10aef04 Preserve native Linux and FEX compatibility choices (#749) 2026-10-14 00:57:45 -04:00
Kurt Himebauch 5d8722beca Wait for Steam shortcut overviews before cold launches (#758) 2026-10-14 00:55:04 -04:00
Kurt Himebauch c6f754c936 Run CodeRabbit reviews independently of APK builds (#777) 2026-10-14 00:29:37 -04:00
Kurt Himebauch 277cbc9c01 feat: redesign the store game page's facts, description and achievements (#775) 2026-10-13 23:33:43 -04:00
Kurt Himebauch 2ad57a0105 Configure CodeRabbit reviews (#774) 2026-10-13 22:47:19 -04:00
Juan MartíandThe412Banner 0d64ad8c23 Improve store install storage checks and recovery; adaptive parallel Epic installs (#710)
Space checks before Epic, GOG and Amazon installs (measured zero vs unknown), clear failure reasons with retry/clear, resume-aware checks, and safe per-file replacement so a failed or cancelled update never breaks an installed game.

Epic installs stream chunks straight into staged files in parallel (most bytes written once, a small budgeted cache for shared chunks, about 1x game size instead of 2x), pace updates by free space, and keep the lookahead, batch and bounded Java writers as hidden alternatives.

Co-authored-by: Juan Marti Mercado <[email protected]>
Co-authored-by: The412Banner <[email protected]>
2026-10-13 22:29:23 -04:00
34969ccf44 fix: desktop session: Shift no longer stays held after typing capitals on a physical keyboard (#766)
* fix: desktop session: Shift no longer stays held after typing capitals on a physical keyboard

Android repeats a held key as more key presses. Those were forwarded to the
session as more wl_keyboard presses, and KWin's xkb counts every press of a
modifier, so Shift held for a few capitals took several presses and one
release and stayed held: digits typed !@#, letters stayed capital. The same
applied to Ctrl and Alt.

- the compositor no longer sends a press for a key that is already down;
  releases always go through (clients repeat keys themselves from the
  repeat_info it already sends)
- the app stops forwarding Android's repeat events for keyboard keys

Reported by whatsupbros with a Keychron Q7 over USB - thank you.

* Remove stale comments from statx patch

---------

Co-authored-by: The412Banner <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-13 22:03:28 -04:00
Kurt Himebauch b616c9bef4 Fix Flatpak installs on older Android kernels (#752) 2026-10-13 21:31:26 -04:00
Kurt Himebauch aecd2ebe1d cleanup (#764) 2026-10-13 21:27:42 -04:00
The412Banner 5fd47421f9 fix: Windows components: .NET Core / Desktop runtimes install again (#740)
droiddeck-msi-install: a DuplicateFile entry onto the file itself (dotnet.exe in the .NET host MSI) is skipped like Windows Installer does, instead of stopping the whole install with SameFileError. Fixes .NET Desktop 3/6/7/8, .NET Core 3 and .NET 5. Thanks to whatsupbros for finding and tracing it.
2026-10-13 19:03:38 -04:00
Kurt Himebauch 93352da1d0 fix: pace gamescope's fixed-refresh frame limiter per window by time (#753) 2026-10-13 22:21:01 +00:00
Kirk 6e5be862cd fix: the Debugging tools hint points to Setup > Diagnostics (#725) 2026-10-13 15:01:40 -04:00
Kirk fc9d4d0646 fix: the Android keyboard opens on the first tap after the system dismissed it (#665) 2026-10-13 14:17:31 -04:00
Jagger15 6de6ec821c Add experimental Quest 2 and Adreno 6xx display-driver support (#676) 2026-10-13 14:16:20 -04:00
0x1Nawaf 249ab44533 Feat/reset all settings (#696) 2026-10-13 13:36:31 -04:00
MaxsTechReview 69e68a5008 Add translations for 23 more languages and fill missing strings (#669)
* Add translations for 23 more languages and fill missing strings

* Translate the strings added in main into every language

Cardboard VR and the session drawer's component downloads in all 31
languages, the game details facts and HowLongToBeat labels in the 23 new
ones, and the reworded Components page note there.
2026-10-13 13:34:00 -04:00
MaxsTechReviewandxXJsonDeruloXx 9a6d48c04b Add opt-in zRAM compression for the Steam client while a game runs (#442)
* Add opt-in zRAM compression for the Steam client while a game runs

A "zRAM compression" toggle under Steam > Settings > Session, below
"Keep downloads running". Off by default, kept once set, and disabled
on devices without swap or with a kernel older than 5.4.

When on, the session watches for Steam's reaper. 20 s into a game the
client and its web helpers page their private anonymous memory out to
the device's zram with MADV_PAGEOUT from inside their own process
(libblsession zram.c, SIGURG), again every 5 minutes, and read every
swapped page back once the game exits. No root is needed: only a
process itself may do this without CAP_SYS_NICE. With the toggle off
nothing is installed or started.

RedMagic NP06J, Palworld: Steam's resident memory fell from ~1.7 GB to
~1.1 GB with ~560 MB in zram, available memory during play rose by
~0.5-0.8 GB, and ~2 MB was left in zram after the read-back.

* Turn zRAM compression on by default below 12 GB of RAM

Until the toggle is changed, it follows the device: on when the kernel
supports it and the device has less than 12 GB of RAM, read from the
advertised memory size on Android 14+ and from total memory before that.
Turning it off is kept.

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-13 13:33:19 -04:00
Kurt Himebauch 7987b04529 fix: don't count a re-attached unchanged buffer as a new game frame (#731) 2026-10-13 13:24:50 -04:00
Phobos665 269ce3f422 feat(): Rich Game details for GOG & Epic - Publisher, Developer, Release Date, HLTB & Metacritic (#712) 2026-10-13 11:29:03 -04:00
Kurt Himebauch 0d099ef171 Use preview-builds for the Discord channel name (#729) 2026-10-13 11:27:39 -04:00
Kurt Himebauch ec838c2576 Align Discord build names with the app (#726) 2026-10-13 11:17:41 -04:00
Kurt Himebauch f4b0ab6867 feat: Cardboard VR mode with ARM64 SteamVR (#704)
* feat: add Cardboard mode with side-by-side lens-distorted eyes and 3DoF head tracking

* fix: derive the Cardboard field of view from the panel and recentre fully when far from upright

* feat: share the Cardboard head pose with the guest and add a SteamVR driver for it

* feat: run SteamVR's compositor on Turnip/KGSL with a display-extension layer and a driver-direct HMD

* feat: show SteamVR frames in Cardboard mode through a driver-side presenter and a fake direct-mode display

* feat: install and start SteamVR for Cardboard mode from the session drawer

* docs: describe Cardboard mode and the SteamVR path

* feat: drive SteamVR hands from the game pad and keep the Cardboard headset out of standby

* docs: record where the SteamVR dashboard stops

* fix: map Cardboard head tracking to the screen's axes and keep turns about the real vertical as yaw

* fix: give Cardboard its previous picture back when SteamVR stops

* fix: report sem_otime from emulated SysV semaphores so SteamVR's web helper can start

* fix: bridge SteamVR's shared semaphores across processes and keep Steam alive while SteamVR runs

* feat: present SteamVR straight to DroidDeck's compositor at panel resolution

* docs: update Cardboard VR notes for the dashboard, semaphore bridge and direct presenter

* fix: keep Wine and FEX signal handlers in the VR layer; add semaphore tracing

* fix: clear a stale SteamVR active marker on status

* fix: clear only an old SteamVR active marker

* fix: unblock SteamVR clients on Turnip and keep Steam's web helper alive across SteamVR runs

* fix: judge shared waits before a submit's own signals and on Vulkan 1.0 devices

* docs: record VR games, SteamVR Home and OpenXR results on the Thor

* docs: add Cardboard VR learnings from the Thor bring-up

* docs: add Cardboard VR screenshots from the Thor

* fix: keep the VR layer's display stand-in to SteamVR runs and clear a leftover marker at session start

* fix: report SteamVR start and stop failures from the helper's exit code
2026-10-13 14:36:56 +00:00
Kurt Himebauch 92d038b428 fix: anchor wp_presentation timestamps to the vsync tick (#717) 2026-10-13 13:29:32 +00:00
Kurt Himebauch ef1558527f fix: cap the session refresh rate at the device's peak refresh rate (#715) 2026-10-13 13:16:29 +00:00
Kurt Himebauch 623fb3fb62 Fix FEX dropdown scaling and Discover search focus (#681) 2026-10-13 07:01:05 -04:00
Kurt Himebauch 1c1a96cb19 Download components from the session drawer (#679) 2026-10-12 22:18:47 -04:00
Jagger15 6c35297dba Add Quest 3/3S compatibility to the standard APK (#642) 2026-10-12 17:13:07 -04:00
Kurt Himebauch 8f36eb7f54 Add opt-in GOG achievements through Comet (#651) 2026-10-12 16:40:06 -04:00
Kurt Himebauch 3ed756b067 Default Snapdragon 8 Gen 2 core allocation (#654) 2026-10-12 16:26:24 -04:00
Phobos665andxXJsonDeruloXx d9b152c7d2 feat(): Achievements UI for GOG & Epic game details page (#641)
* feat(): gog & epic achievements UI

* feat(): AI-generated translations - Please get review for these

* build_local: run on macOS hosts with Docker Desktop

Two steps of tools/build_local.sh failed on a Mac:

- The uruntime staging runs on the host, where macOS's BSD install has
  no -D, so `install -Dm644` stopped with "No such file or directory".
  The folders are now made first and the files copied with install -m644.
- Inside the cross-compile container, Docker Desktop's VirtioFS share
  now and then refuses the chmod that install -m makes (EPERM, on a
  different file each run, e.g. "setting permissions for
  .../droiddeck-esync: Operation not permitted"). The overlay and
  desktop scripts are copied with a plain write instead. Nothing is
  lost: the APK keeps no file modes, and the app sets them when it
  stages the runtime.

* feat(): adjust the store

* feat(): store-achievements adjustment for UI

* feat(): translation fixes and separate hidden achievements

* Show all achievements directly on store game details

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-12 19:49:55 +00:00
The412Banner 3b99f2d990 feat: Start without Steam, a per-game switch for games that close right away (#659) 2026-10-12 15:29:59 -04:00
The412Banner c9152103bf Merge remote-tracking branch 'org/main' into feat/start-without-steam 2026-10-12 15:16:35 -04:00
Kirk 1b4d08e448 fix: a touchpad tap holds its click long enough for games to see it (#644) 2026-10-12 19:12:20 +00:00
Gustash ba6f56d5a0 fix: an unfolded Follow screen session turns to portrait again (#626) 2026-10-12 15:07:35 -04:00
The412Banner 0cfb994924 feat: Start without Steam, a per-game switch for games that close right away
A game that closes as soon as it starts can be tripped by Proton's Steam
setup (steam.exe, the Steam registry entries, the Steam client bridge).
The Games page of the session drawer gets a per-game Start without Steam
switch, stored as DROIDDECK_NO_STEAM=1 in the game's environment and listed
in its profile summary.

droiddeck-game-env acts on it for a game launch only: a Proton with umu's
launch (GE-Proton, Proton-CachyOS, DroidDeck-Proton) gets UMU_ID, so it
runs umu.exe in place of steam.exe and still sets up the prefix; a Steam
game keeps its protonfixes under its own id, an added game gets 0. Any
other Proton runs the game straight in the prefix (runinprefix).
2026-10-12 13:55:56 -04:00
Muaz Kadan 1ceb1b87b7 fix: Shut Down in Steam's power menu ends the session, and Restart restarts the client (#629) 2026-10-12 13:44:16 -04:00
Muaz Kadan 0b133a2ed5 Fix the QAM button doing nothing when the pad is an Xbox or PlayStation controller (#631) 2026-10-12 13:35:41 -04:00
Phobos665 513ae84612 chore(): Fix the MacOS build process locally. (#637) 2026-10-12 17:11:01 +00:00
Kurt Himebauch 8c955abfa4 chore: bump version to 0.4.0 (13) (#627) 2026-10-12 10:56:11 -04:00
Kurt Himebauch e6738a699a docs: draft DroidDeck 0.4.0 release notes (#619)
* docs: draft 0.4.0 release notes

* docs: add Thor store and launcher media for 0.4.0

* docs: show Steam switching to KDE on Thor

* docs: show Castle Crashers touch layouts and controller choices

* docs: add Amazon game install view

* docs: add trimmed user recording of store downloads

* docs: smoother touch controller recording, GOG sign-in and per-game profile screenshots

* docs: clean desktop round-trip recording replaces the KDE gif and file explorer still

* docs: add multi-controller and Steam Controller media, rebuild desktop gif without glitch frames

* docs: add foldable touch gamepad screenshots from its PR

* docs: add the Discord badge to the 0.4.0 notes and tidy contributor lines

* docs: fold fixes to new 0.4.0 features into their entries, add Follow screen shots, point the changelog at the 0.4.0 tag

* docs: add the Add a game dialog to the non-Steam games entry

* docs: add a TL;DR for Mr. Sujano to the 0.4.0 notes
2026-10-12 10:49:14 -04:00
Neolyum d71178c955 Fix multiple controller discovery (#614) 2026-10-12 11:45:00 +00:00
Kurt Himebauch aa901c3902 feat: store uninstall confirms in a step-out, shows progress, and lands on Install (#609)
feat: store uninstall confirms in a step-out, shows its progress in place, and the game reads Install the moment it is gone
2026-10-12 11:03:03 +00:00
Kurt Himebauch 486032f5db fix: keep the Steam desktop client in KDE (#610) 2026-10-12 06:56:56 -04:00
Kurt Himebauch 4ec2146019 feat: store game pages flood onto their hero art, and downloads survive a restart (#608)
* feat: a store game's page floods out of its card onto its own hero art, and draws back from it

* fix: installs interrupted by the app closing show in Downloads with Resume and Clear

* feat: finished and interrupted downloads share the running card's art look, and the queue sits under what is running

* fix: the download queue outlives the app: queued, running and paused downloads come back after a restart
2026-10-12 03:58:07 +00:00
Kurt Himebauch 1603009687 fix: a step-out out of a wide control draws its box again (#607) 2026-10-12 03:31:48 +00:00
Kurt Himebauch 06269aab24 feat: components newest first, Steam settings open on Steam with offline mode, smoother chip selection (#606)
* feat: components list newest first, Steam's sheet opens on its Steam tab with offline mode, and Setup's overview drops the account group

* fix: chip selections stretch across on every move, drawn over the chips instead of behind them
2026-10-12 03:25:55 +00:00
Kurt Himebauch 9836dade79 feat: the focus ring is the only fill and keeps up with focus (#605)
* feat: with a pad the focus ring fills what it holds and is the only fill; chip rows mark their pick with a bar

* fix: the focus ring chases focus on a continuous spring per edge instead of restarting on every move
2026-10-12 03:16:07 +00:00
Kurt Himebauch 4bfc7154cd fix: store chips mark the shown store without faking focus, and step-outs open and fold cleanly (#604)
* fix: a chip selection hopping past a chip fades across instead of crawling over as a drop

* fix: the store chips' fill only shows where a pad's focus is, not on the shown store

* feat: the shown store's chip carries a bar on its foot, every chip pulses when picked, and the queue's settings live only in the cog

* fix: step-outs grow and fold in one move with no tail, keep the ring out while moving, and hand focus back to their control
2026-10-12 03:04:33 +00:00
Kurt Himebauch 674e679884 fix: settings sheet focus ring, Left to fold, chips pick on focus (#600) 2026-10-12 02:11:53 +00:00
Kurt Himebauch 5527f9b77e Fix black Steam output with FSR 1 (#597) 2026-10-11 21:03:07 -04:00
Kurt Himebauch b1690852e9 Fix Discord launcher layout and controller navigation (#596) 2026-10-11 20:08:59 -04:00
Gustash 8f5ccbf23f fix: Epic games with a JSON manifest download (#578) 2026-10-11 17:37:47 -04:00
Juan MartíandKurt Himebauch 55c51f843e feat: add persistent per-game compatibility profiles (#449)
* feat(profiles): add persistent per-game compatibility profiles

* fix(profiles): show Windows component overrides

Include per-game Windows component selections in the resolved profile summary so the session overlay marks those games as custom and shows the selected components.

* fix(session): detect Proton games across runtime paths

* fix(profiles): select the active Gamescope game

* fix(profiles): improve per-game reset feedback

* fix(profiles): align session components with game overrides

* fix(session): prefer selected Steam game during refresh

* fix(session): resume profile following when reopening games

* chore: keep local dev testing outside the repository

* test(session): remove local dev package path

* fix(session): preserve profiles when refresh fails

* fix(session): reject empty game profile reset scope

* perf(session): cache drawer inventory by refresh revision

* fix(profiles): align game presets and profile reset behavior

* fix(profiles): preserve originals and normalize shortcut ids

* fix(profiles): keep editor scope stable after rebase

* fix(tests): host Compose tests in both build variants

* fix(profiles): recover interrupted component selection saves

---------

Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-11 17:00:28 -04:00
The412Banner ef4807fbd0 DroidDeck-Proton as a Proton source, and update checks for every source (#588) 2026-10-11 16:07:44 -04:00
The412Banner 604763edc7 Merge remote-tracking branch 'org/main' into feat/droiddeck-proton-source 2026-10-11 15:56:18 -04:00
MaxsTechReview d207b35d94 Red Dead Redemption 2: launches again, no screensaver over games, hidden launcher held to one core, /tmp cleaned (#585)
* fix: Red Dead Redemption 2 launches again, its Vulkan runtime installer is left to run

droiddeck-seed-redists marked every process in every game's install script
as already run, Red Dead Redemption 2's VulkanRT-1.1.108.0 installer
included. Steam's compat config for the game says nativevulkanloader, so
Proton loads vulkan-1.dll native only, and Khronos' loader is in the prefix
only once that installer has run. Without it the Rockstar launcher starts
RDR2.exe and it exits with STATUS_DLL_NOT_FOUND (0xc0000135) before it
opens a window.

A Vulkan runtime installer is now left out of the seeding, and a marker an
earlier seeding wrote for one comes out of each prefix and Proton template
once (dates kept), so the client runs the installer on the next launch.
The installer finishes in a few seconds under FEX.

* fix: Steam's screensaver no longer covers a running game

The client's defaults show screensavers while a game runs, after 300 s on
AC power without a button press - and a held stick or trigger is no press.
Red Dead Redemption 2's intro runs longer than that: the slideshow covered
the game, and the button that woke it reached the game too (B asked to
quit to the desktop). Customization/Screensaver/WhileInGame is seeded "0"
in config.vdf once, like the UI scale and native resolution, so turning it
back on in Settings stays the user's choice.

On the RedMagic: 18 minutes from the last button press through the intro
and riding, focus never left the game and no screensaver was created.

* fix: Rockstar's hidden launcher no longer takes cores from its game

The Rockstar Games Launcher keeps drawing its Chromium interface
(SocialClubHelper.exe: renderers, and a GPU process compositing through
DXVK) for as long as the game runs, hidden behind it - and Wine passes
Chromium's priorities on, so those threads ran above the game's (the GPU
process at nice -10), across every core. With Red Dead Redemption 2 in
front that was 1.3 to 2.5 cores under FEX for a page nobody sees.

droiddeck-launcher-cores follows gamescope's focus from the agent's
focus.json: while a game window has it, every SocialClubHelper thread runs
on the last core of the slowest cluster at nice 19; when the launcher's own
window has focus again each thread gets its own mask and priority back
(also on exit). Stopping the helpers would not hold - proot resumes a
stopped tracee. Started for Steam sessions; BL_LAUNCHER_CORES=0 turns it off.

RedMagic, RDR2 all Low, riding at the thermal limit, same spot and session:
released 46.5 fps, 1% low 27.5, p99 31.8 ms, CPU 89%, 2200 MHz;
held 48.7 fps, 1% low 32.6, p99 27.6 ms, CPU 79%, 2638 MHz.

* fix: sessions clean up what earlier ones left in /tmp

The guest's /tmp is a directory in the rootfs that no boot ever empties,
and Android ends sessions by killing them. Every session left a
/tmp/mangohud.XXXXXXXX (mktemp, never removed), and the Steam client, its
web helper, Chromium apps, dbus-run-session, AppImage runs and KDE their
own temporary files and sockets: 1,422 entries on the RedMagic.

mangoapp's config is now one fixed file rewritten each session, and the
session's first pass sweeps each kind of leftover while nothing that could
own one is running, with a single find. On the device that cleared 1,391
entries over two sessions, leaving 38.
2026-10-11 15:53:01 -04:00
The412Banner 4a373807a4 feat: DroidDeck-Proton as a Proton source, and update checks for every source
Setup > Proton versions lists DroidDeck-Proton (The412Banner/DroidDeck-Proton) beside
GE-Proton and proton-cachyos: same aarch64 tarball pattern as GE, verified against its
sha512 file, installed by droiddeck-proton-extra (also as "droiddeck" in the session queue)
and adopted by steam-compatibility like any other extra, so it shows as
"DroidDeck-Proton <ver> (Compatible)". Its folder has a space; nothing in the install,
adopt, esync or components paths splits on it.

Update checks: each source's newest full release is looked up at app start and when the
page opens, at most every 6 h (offline keeps the last answer). A newer release shows
"Update available -> <tag>" with Update; it installs beside the old build, which stays
until removed ("Remove old ... versions"), since games are mapped to it by name.
Installed builds are ordered by their version numbers (11-9 before 11-10), not by name,
and the tag each was installed from is recorded. Prereleases and drafts are skipped.
2026-10-11 15:32:07 -04:00
Kurt Himebauch fc026263e4 feat: big ass redesign: Carry the stores' motion and layout into Steam settings, Components, Setup, Flathub and Desktop (#548) 2026-10-11 19:05:21 +00:00
Kurt Himebauch e5ba165027 feat: the built-in pad can be a DualShock 4 or DualSense, its touchpad fed by the trackpads (#559) 2026-10-11 04:21:31 +00:00
Kurt Himebauch 95f92b157b feat: the built-in pad's controller type switches from the session menu while Steam runs (#558) 2026-10-11 00:12:53 -04:00
Kurt Himebauch 8d34b37b7d feat: the built-in pad can be a Steam Controller to Steam (#545) 2026-10-11 03:24:22 +00:00
Kurt Himebauch d7f89fc32b fix: the Steam Controller's hidraw node reaches the input shim again (#557) 2026-10-10 23:20:09 -04:00
Kurt Himebauch b47302eb8c refactor: Steam always runs in Deck mode on the Deck beta branch (#546) 2026-10-10 22:15:42 -04:00
The412Banner a2e596a200 Windows components: picked automatically by each game's source (#554)
Each game's Windows components now come from the list its own source
trusts, and the ones that list names turn on by themselves. The user's own
on/off switches always win; each automatic row says why it is on.

- Steam games: the Steamworks Shared redistributables Steam installs with
  the game (appmanifest SharedDepots, else the client's appinfo.vdf).
  Steam keeps its install scripts marked as already run.
- GOG: the build manifest's dependencies. Epic: the manifest's
  prerequisite (Unreal's maps to VC++ and DirectX). Amazon: fuel.json
  PostInstall. Older GOG/Epic installs get their list fetched once with the
  saved sign-in; game files are never downloaded again.
- Custom games (the + button, Add all, migrated folders): one shared Steam
  match per game, also used for artwork. An appid from the game's files
  turns components on; a name-only match stays Recommended; no match falls
  back to the folder scan.
- Steam app info for apps the client never saw comes from an anonymous
  Steam PICS lookup (CM websocket, anonymous logon, product info), cached
  per appid; the user's Steam sign-in is never used.
- Missing components download in the background on one low-priority
  worker that pauses during sessions and waits on battery saver, heat or a
  metered network unless the user asked. A game Steam installs during a
  session is readied right away so it can be played without ending the
  session; the launch waits up to 10 s only for a component still on its
  way.
- Page: "On for this game" with reasons, a quiet "Checking what this game
  needs..." only for games never worked out, rows that ease in, pieces that
  need a download greyed out while offline. The intro names no other app.
- The folder scan skips Unity's *_Data/Managed (Mono, not .NET Framework).
2026-10-10 21:15:56 -04:00
MaxsTechReview d45933e559 FEX: preload the fault report only where x86 loaders look (#551)
Preload the FEX fault report only where x86 loaders look

droiddeck-fex put the x86 libfaultreport.so in LD_PRELOAD, which FEX's own
arm64 loader reads too - and FEXServer's, and every child FEX's - so each
x86 start printed "ERROR: ld.so: object '.../libfaultreport.so' from
LD_PRELOAD cannot be preloaded", and an i386 child added "wrong ELF class".
It is now listed in the x86 runtime's own /etc/ld.so.preload, which only
the loaders inside FEX read, as /usr/$LIB/droiddeck/libfaultreport.so.

faultreport.c builds for i386 too, so 32-bit programs get the report as
well instead of an error.
2026-10-10 19:12:51 -04:00
MaxsTechReview 2a06204eb4 Desktop: run programs, scripts and AppImages; Switch to Desktop without Deck mode (#549)
* Run programs, scripts and AppImages from the desktop

Windows programs open with the Proton Steam has chosen, and a Steam game's own
launch program is handed to Steam. x86-64 Linux programs and game scripts run
under FEX, and an exec the kernel refuses goes to droiddeck-open through
libblsession's binfmt.c. Programs on noexec shared storage pass KIO's checks; a
folder of Linux programs there, and sharun AppImages, are added by the app once
and run from the copy. Executable scripts run without KIO's prompt, mpv plays
on the software desktop, FEXServer's socket moves under XDG_RUNTIME_DIR, and
Proton runtimes no longer show up as games.

* Switch to Desktop from Big Picture without Deck mode

Big Picture's Switch to Desktop asks the client's SteamOS manager, which the
client only has when started as SteamOS (Deck mode). Without it the request
failed and the screen stayed on "Switching to Desktop..." for good. The live
compat helper now sees the request on its way to the client and hands it to
steamos-session-select, which opens the KDE desktop as Deck mode already did.

* Run x86 programs from shared storage where they lie

FEX asks for the guest's PROT_EXEC when it maps a program or library, which
the kernel refuses on noexec shared storage; FEX never runs those pages
itself, so libblsession maps them without it in FEX's processes. An x86
game, its libraries and its start script now run in place instead of being
copied in; only native code on noexec storage is still copied.

A Steam library's games are never copied, and a Linux game's launch program
opened from the desktop goes to Steam like a Windows one.
2026-10-10 19:12:20 -04:00
MaxsTechReview 15b2d19f68 Performance: idle front end, proot stops, launch-time passes and log collection (#529)
* Front end: draw the capsule wall without clipping it every frame

The tilted wall clipped each capsule to a rounded rect and recomposed its columns on every drift
step, and a rotated rounded clip is rasterised in software on the render threads: the idle main
page held about 1.7 cores. The art is now loaded at the capsule's size with its corners already
rounded, each column draws its capsules itself in one layer moved by the drift, and the drift
advances at most every 15 ms. The idle page takes about a third of a core.

* libfakeinput: let calls on descriptors it does not serve skip its lock

Every read, write, ioctl, close and poll of every process in a Proton session went through the
controller lock and the three map lookups, Wine's wineserver pipes and the game's files alike.
A bitmap of the descriptors the maps hold, set before one is handed out and cleared when it is
closed, lets everything else go straight to the kernel.

* Pad motion: ask the display for its rotation four times a second

The rotation was looked up for every sensor reading, every 2 ms, and cost more than the rest of
the reading; it is now refreshed every 250 ms, and the readings no longer allocate pairs.

* Session: keep the library passes out of a game's launch

The redist seeder stamped each prefix with the checksum of the whole set of names, which differs
between the session's pass and the game launch's (the game's own names are added), so every
launch re-appended the markers to every prefix in the library while the game loaded. Each
prefix's marker now lists the names it was given and only the missing ones are added.

The beat's seed and compatibility passes, and the Lossless Scaling ownership probe (which loads
steamclient.so), also wait while a wineserver runs: a launch rewrites the manifest and made them
due at exactly that moment. The game's own launch seeds its prefix regardless.

* Agent: keep the focus watcher's X connection open while nothing changes

The connect timeout stayed on the socket, so with no property change the read timed out, and the
watcher dropped the connection, reconnected and took a fresh snapshot every few seconds.

* Game environment: start Cyberpunk 2077 without REDlauncher

The prelauncher opens REDlauncher, a Qt WebEngine window that hands over to the game half a
minute later and then stays up beside it, well over a core busy for as long as the game runs; its
own --launcher-skip started the game twice. The game's executable is started directly instead.

* netif: answer Wine's network driver from a reading of the link

Mortal Kombat X has Wine's network driver enumerate every interface, address and route table
hundreds of times a second. Each enumeration read /etc/droiddeck-net several times, asked the
kernel for its interface dump again (a netlink socket bound under proot, refused each time) and
looked the real functions up with dlsym. The link file is now read at most once a second, a
refused dump is not asked for again within that second, and the real functions are looked up
once.

* Fast path: create memfds without a stop in proot

proot looks at a memfd's name only to refuse the few that are then executed or locked; every other
memfd_create is now issued from the trampoline. The session's /proc/net and status copies made
one per read, a stop each. Without memfd in the kernel the call still goes to proot's ashmem.

* Logs: run each redaction rule only on a line that could match it

A session's end copies some 70 MB of the Steam client's logs and puts every line through all
twenty-odd rules, most of a minute of a core on the phone after every session. Each rule now runs
only on a line holding what it cannot match without: its keyword, the five separators of a MAC,
the four dashes of a GUID, a long enough hex or token run, the learned account or address text.
Lines with non-ASCII text still meet every case-insensitive rule. Over 895,000 lines of real
client logs, with the device's accounts and addresses learned, the output is identical and the
pass takes a twentieth of the time.
2026-10-10 19:00:33 -04:00
SG3xHERO f21f20a578 Add Steam Controller support over Bluetooth LE (#404) 2026-10-10 20:53:07 +00:00
The412Banner cf59d39599 Read the package catalogs from Droid-Deck/DroidDeck-Components (#544)
The Windows components catalog, the desktop and emulator packages, the Linux runtime and the
Steam seed now live on the organisation's components repository, mirrored and built there by
CI; the app reads the catalogs from its main and the files from its releases. The personal
winlator-contents mirror keeps serving older app versions and Bannerlator.
2026-10-10 16:52:33 -04:00
The412Banner 3589ad9127 Games: old Games folders become entries, not Steam libraries (#542)
Games folders saved before the settings section went away were still
registered as Steam libraries on Automatic, with no way left to remove
them. Once, at app start and before any scan, each game such a folder
listed becomes an entry of its own, keeping everything keyed by its folder
(chosen exe, shortcut appid, name, Start in, launch options, art,
removal); store installs and steamapps are skipped. The folders stay bound
at their old guest paths, so no shortcut changes, and the saved roots are
cleared. A Games folder stays a library only when Steam installed a game
into it; the others leave the library list, and the runtime forgets them.
2026-10-10 16:19:17 -04:00
David Kubeša 49f6209c3a Add global manual landscape orientation setting (#441) 2026-10-10 16:07:12 -04:00
Kurt Himebauch ff9792f885 Merge rules (#540) 2026-10-10 19:13:01 +00:00
Ashish Ajin b6428901b1 Hide the touchpad cursor when switching to direct touch (#424) 2026-10-10 15:02:38 -04:00
The412Banner 3ea35abe83 Windows components: a recommended component is listed once, not again under All (#537)
The full list left out only the 'installed for this game' rows, never the recommendations, so
every recommended component appeared twice (DirectX 9 on Final Fantasy Resonance, both rows
switched on).
2026-10-10 14:54:28 -04:00
Muaz Kadan 6c5b9438bb Read the phone's motion sensors only while Steam asks for them (#534)
perf: only read the phone's motion sensors when Steam wants them

The gyro and accelerometer ran at 4 ms for the whole session, even in games without gyro, costing about 6% of a core. Steam already tells the Deck pad when it wants motion (setting 48), so libfakeinput now passes that on and PadMotion follows it, with a short delay to ride out Steam's brief toggles.
2026-10-10 14:44:05 -04:00
2c9267b0ef fix: preserve keyboard input on composite devices (#435)
* fix: preserve keyboard input on composite devices

* Fix composite input merge noteDevice call

---------

Co-authored-by: Jammo <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-10 14:40:53 -04:00
Muaz Kadan e845f91177 Fix CPU temperature showing 0°C in the performance overlay (#516)
Since the proot speed-up in #494, the overlay couldn't find the CPU sensor and showed 0°C. It shows the real temperature again.
2026-10-10 14:39:30 -04:00
ArthurandJames Yeung 285e271f3e Fix NativeAOT GC region range defaults (#385)
Use the bare hex value accepted by older NativeAOT runtimes and CoreCLR. Cover the default and shared and per-game overrides.

Co-authored-by: James Yeung <[email protected]>
2026-10-10 14:38:47 -04:00
0x142857andxXJsonDeruloXx 20f919d708 Controller: optional Select button that opens the Steam menu (#384)
* Controller: optional Select button that opens the Steam menu

* Controller: translate the Select-opens-Steam-menu option

* Controller: clearer wording for the Select-opens-Steam-menu option

* docs: evidence for the Select-opens-Steam-menu option

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-10 14:38:07 -04:00
abduznik a4ea72cb90 Terminal: release createSubprocess's JNI local references, and the cwd chars against cwd (#527)
Fix JNI local reference leak in createSubprocess

The argv and envp loops took one local reference per element from
GetObjectArrayElement and never released it, so the references piled up
until the native call returned. ART aborts the process at 512 local
references ("local reference table overflow"), and both loops share the
table, so a long argument list plus a large environment could hit it.

Delete each element's local reference once its chars are copied and
released, after ReleaseStringUTFChars since the chars depend on the string.

Also release the cwd chars against cwd rather than cmd; the call passed
the wrong jstring.

Adds a host-side test that runs termux.c against a mock JNIEnv that counts
local references and checks release pairing. It needs a jni.h (JNI_INCLUDE,
JAVA_HOME or the NDK) and skips without one.

Fixes #507
2026-10-10 14:29:51 -04:00
Nicolás Gómez Solano 2c404ead73 fix(audio): avoid InputStream.readAllBytes() for Android 12 compatibility (#531) 2026-10-10 14:29:46 -04:00
The412Banner 932de3fd72 Windows components: leave out WebView2, which only Microsoft's installer can lay out (#532)
Its files sit in a container no extractor opens and may not be hosted, so a recording cannot
source them; installing it means running the installer in the prefix, which no runtime here
does. With it gone the waiting section is empty.
2026-10-10 14:03:04 -04:00
MaxsTechReviewandxXJsonDeruloXx eb94916eb1 Launch the executable a Store package declares (#400)
Store and GDK folders name their game in MicrosoftGame.config or
appxmanifest.xml. Picking the top-level Unreal bootstrap instead failed
with "CreateProcess() returned 2" when the Store had renamed the
shipping exe.

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-10 13:46:03 -04:00
kmnkv1 e8821f4efc Rumble: don't restart the vibrator on every force-feedback packet (#514)
fix: stop restarting the vibrator on every force-feedback packet
2026-10-10 13:34:19 -04:00
kaosjammoandJammo 7570ce7e18 Fix launcher fullscreen around display cutouts (#432)
Co-authored-by: Jammo <[email protected]>
2026-10-10 13:33:59 -04:00
The412Banner e25fa5429b Windows components: leave out the OpenAL setup program, a twin of the DLL copy that installs here (#528) 2026-10-10 13:32:12 -04:00
SG3xHERO 6de130a428 Unfolded deck controls (#407) 2026-10-10 17:24:23 +00:00
Willie Yao 1959b1a875 Controller editor: snap controls into line while dragging (#490) 2026-10-10 15:11:50 +00:00
kmnkv1 c1433ac0b3 Odin 3: skip the low-latency Wi-Fi lock, whose WLAN firmware hang reboots the device (#512) 2026-10-10 11:06:29 -04:00
Kurt Himebauch 67a9ea2f42 feat(desktop): add desktop uninstall and legacy LXQt cleanup (#493) 2026-10-10 10:59:32 -04:00
Kurt Himebauch 79d92111e4 fix(amazon): sync library with nile's headers and registered serial (#500) 2026-10-10 10:58:18 -04:00
The412Banner 8bcd9701b1 Windows components: Access 2000 runtime installs here (an MSI inside a 7-Zip-openable wrapper) (#505)
art2kmin.exe is Setup.exe + Data1.msi in a wrapper 7-Zip opens, so the MSI engine lays it out
like any other package; it was on the setup-program list by assumption.
2026-10-10 01:31:23 -04:00
The412Banner e01a7c9e17 Build: verify the runtime inputs only after the session scripts are staged (#504)
With parallel task execution, verifyRuntimeInputs and stageSessionScripts could run at the
same time; main's build at 3d50e6b1 hashed droiddeck-netmanager mid-copy and failed with
'Missing or changed runtime input'. The check now runs after the staging tasks.
2026-10-10 00:38:23 -04:00
The412Banner d639668e6b Windows components: hide Vulkan RT and the IE8 update; honest captions for the waiting section (#502)
* Windows components: hide Vulkan RT, whose loader every Proton here ships

vulkan-1.dll is in lib/wine/x86_64-windows and i386-windows of Proton Experimental (ARM64),
GE-Proton and proton-cachyos alike, so the catalog's VulkanRT entry would only shadow it. It
joins the list the page leaves out.

* Windows components: the waiting section says what is really missing (a recording), not 'coming next'

* Windows components: leave out the IE8 security update, which patches a browser no prefix has

* Windows components: the waiting caption without an apostrophe the resource compiler rejects
2026-10-10 00:24:14 -04:00
The412Banner 9c052be865 CI: re-pin the Debian image and stop the publish workflow replacing it (#501)
The publish workflow's push trigger fired again on the branch's rebase and overwrote the
release asset with a freshly compressed copy, so main's first build failed the checksum and
fell back to the registry pull. The env file now pins the asset as published; the workflow is
dispatch-only, defaults to a new tag, and refuses to replace an existing asset unless asked.
2026-10-09 23:59:58 -04:00
The412Banner 2755e027da CI: host the Debian image the preloads build in on a release of this repo (#497)
* CI: host the Debian image the preloads build in on a release of this repo

The preloads step pulled debian:bullseye from a public mirror on every build whose preload
cache missed; the mirrors rate-limit the shared runner addresses and failed two builds in an
hour tonight. Debian 11 is frozen upstream, so the image is published once (workflow
ci-debian-image.yml, release ci-debian-bullseye-r1, pinned in tools/linuxfs/debian-image.env)
and the build downloads that file; only if that fails does it pull, with retries.

* CI: pin the published Debian image (ci-debian-bullseye-r1, 34 MB)

* CI: a new Debian image rebuilds the preloads (env file joins the cache key)
2026-10-09 23:21:11 -04:00
The412Banner 32b43694f4 Windows components: hide the CJK fonts and PowerShell, which every Proton here ships (#496)
Proton Experimental (ARM64), GE-Proton and proton-cachyos all carry Malgun, MS Gothic, YaHei
and SimSun in share/fonts and Wine's PowerShell wrapper, so the catalog's cjkfonts and
powershell entries would only shadow what is already there. They join gecko and mono in the
list the page leaves out.
2026-10-09 23:13:53 -04:00
The412Banner d15ffa3df2 Windows components: install from a recording of the installer's result (#495)
* Windows components: install from a recording of the installer's result

A catalog entry may name a 'snapshot': what its own setup program leaves in a prefix,
recorded once in Wine by winlator-contents' tools/snapshot/record.py (the files it places and
the registry values it writes, the self-registering DLLs' CLSID entries included). With one,
the setup program never runs here: the files come from the archive beside the recording, or
out of the vendor's installer by the paths the recording maps (small generated ones inline),
and the registry values go to registry.json for droiddeck-wincomponents to write at launch.
The steps a recording makes redundant are skipped; DLL overrides still apply. A component
with a recording counts as installable; its prerequisites must still be.

* droiddeck-msi-install --unpack: open archives nested inside an installer

QuickTime's cabinets and an NSIS pack's payloads sit inside the unpacked installer; they are
opened in place into '<name>.nested' folders, two levels deep, the same layout the component
recordings map their file sources to, so a recording's paths resolve on the device (QuickTime:
593 of 593).
2026-10-09 22:56:18 -04:00
Kurt Himebauchandmaxjivi05 0da45e53cb Steam's touch controller as on-screen controls (beta) (#431)
* feat: Steam's touch controller as on-screen controls (beta)

* fix: Steam touch controls appear once the session makes the device; tidier default layout

* fix: Steam touch layout decoding, saved layouts and new sessions

* docs: Steam's touch controls

* feat: Steam's touch icons, labels and colours on the touch controls; pick and save them like Steam

* feat: Steam touch icon picker as in-session panels; layout colour; docs

* Give each physical controller its own player slot

Every controller fed the one exported pad, so two pads played as one.
The first controller keeps slot 0 (the Deck in a Steam session); further
ones get slots 1-3 as separate Xbox 360 pads, created on first input and
removed on disconnect. In Deck mode only the Steam client sees those
pads and hands them to games through Steam Input. Rumble follows the
slot it names.

* feat: Steam Link's touch menu, per-game input modes, action set editing, add/copy/paste, oval controls, paste control

* feat: Steam touch controls drawn as Steam Link draws them; Steam Link's touch menu and layout tray

* feat: Steam touch controls in DroidDeck's style, D-mark guide button, shake to hide

* refactor: Steam touch controls become an On-screen controls choice instead of a second dropdown

---------

Co-authored-by: maxjivi05 <[email protected]>
2026-10-09 21:12:46 -04:00
MaxsTechReview a6d15bcd0b Expanded Fastpaths for proot and increased performance up to 57% (#494)
* perf: fast path resolves links, /proc and glue as proot does, and answers uname, isatty and passwd

The fast path handed back to proot whatever crossed a symlink, /proc or a directory proot
only shows as glue, which is most of what Steam and Wine ask. It now walks a path the way
proot's canonicalize() does, so those calls stay in-process:

- links are followed in guest terms and their text detranslated as detranslate_path() does,
  including readlink into a buffer the kernel cut short and /proc's exe, cwd and fd links;
- a binding proot dropped at start is dropped here too, glue above a binding is ENOENT, and a
  last component proot cannot stat is left to the kernel;
- pipe and socket dirfds are ENOTDIR, rmdir("dir/") removes dir, and "." and ".." go to proot;
- a blocking accept() with a connection already waiting no longer goes to proot.

Also answered: uname() and gethostname() (proot's utsname never changes), isatty() (glibc
2.43 asks with TCGETS2, which proot rewrites) and getpwnam/getpwuid when nsswitch.conf
sends passwd to files first and the entry is there (Xwayland asks for every client).

equiv.py, equiv2.py and equiv3.py match line for line with and without the fast path
(222, 507 and 924 lines). Per call on the RedMagic: realpath 240 us -> 1.0 us,
realpath /proc/self/fd/N 408 -> 9.9, getpwuid 41.5 -> 4.6, uname 28 -> 0.006.

* perf: LD_AUDIT module skips library lookups proot would fail

ld.so looks each library up along the game's search path, and Proton puts the game's
directory, Steam's runtime and video directories and its own library directories ahead of
/usr/lib: 72% of ld.so's opens in an Among Us launch failed, each a round trip to proot.

libblaudit.so answers la_objsearch() for a candidate proot would certainly fail with ENOENT
(its directory walked as canonicalize() walks it, the name or a directory on the way missing).
It has no libc and maps the fast path's trampoline itself. Each lookup's first candidate is
still opened, since a skipped one fails with ld.so's errno as it stands, and the default
directories are never skipped, so dlerror() reads the same. droiddeck-proton and the extra
wrapper add it to LD_AUDIT when the fast path is on.

* perf: droiddeck-proton checks a tool's components with one sha256sum

Each program the launcher starts pays a dozen traced library lookups under the game's search
path before it does anything. One sha256sum over every listed file replaces one per file;
the check itself is unchanged.

* perf: build a locale archive for C.UTF-8

Every program loads the locale LANG names before main(). glibc ships C.UTF-8 as a directory
it looks for once per category under two spellings, before reading gconv's module lists:
about 30 opens, each a stop in proot, in every process of the session. droiddeck-session
builds /usr/lib/locale/locale-archive (and gconv's cache) once per glibc package, so that is
one open.

localedef names a finished archive by hard link, which Android refuses; with
DROIDDECK_LINK_RENAME=1 libblsession turns a refused link into a rename that will not replace
an existing name.

* perf: library beat starts its helpers only when what they read changed

Every 15 s the session ran droiddeck-seed-redists (some forty programs under proot) and, when
a library directory had changed, steam-compatibility: a burst of thousands of proot stops each
quarter minute, game loads included. The seeder now runs when a library changed or a prefix
(or Proton's template for them) is unmarked, the compat pass when a manifest, depot, tool or
config.vdf changed, both checked from bash without starting anything, and each every five
minutes regardless. The seeder reads its stamp and the time without a subshell each.

* perf: fakeinput opens pad.log on its first line

Every process the library is loaded in opened the session's pad.log at start, a stop in proot
on shared storage, and most never write to it. The file is now opened when there is a line to
write.

* docs: the fast path as it ships, the audit module and measured launch times

* fix: a game the app launches after an idle spell comes to the front

The Steam client brings a game it launched forward only while it counts someone as present,
which it reads from gamescope's input counter. A game the app launched after four or more
minutes without a touch started, mapped its window, and stayed behind the library page until
the next input (device: Palworld, Stardew Valley, Fallout: New Vegas, Mortal Kombat X; the same
on main). The session now watches a requested launch, and when the game's window is focusable
but the interface keeps focus for two polls, counts one input on gamescope's counter, as a key
would. Nothing changes for a launch the client already brings forward.
2026-10-09 20:55:18 -04:00
The412Banner d250b43eb9 Games: revamped add games feature (#492)
Adding and managing your own (non-Steam) games moves out of the Steam
settings into the Games tab, and works like Steam's own non-Steam
Properties.

- A + beside "Games N" opens a compact in-app picker: the storage first,
  then that storage's root, every folder, .exe files only. Picking an exe
  adds the game with its name, art, Custom badge and Steam shortcut.
- "Add all games in this folder" adds every game folder inside at once.
  A summary opens immediately and fills in as each game is found, with a
  "?" where the exe was a close call and a red X to remove a game.
- An edit button on each Custom game opens its editor: Name, Target
  (ranked exes, Other exe…), Start in, Launch options, the four pieces of
  art with thumbnails, and Remove. Edits go through the same Steam
  shortcut, so the appid never changes and nothing duplicates.
- Art chooser per piece: images in the game folder, Steam's official art,
  SteamGridDB alternatives, your own image, or back to automatic.
- Finding the exe: up to five folders deep, every exe scored (name match,
  Unreal -Shipping builds, bin folders and size up; launchers, crash
  reporters, installers, redistributables and depth down).
- Real titles from the game's files (Steam manifest, GOG info, the exe's
  version info), keeping the folder's spacing when both spell the same
  title, underscores read as spaces.
- Art: the exact Steam appid when the game names one, strict name
  matches otherwise, Steam's hashed asset paths for newer games,
  SteamGridDB as a fallback (built-in key from the SGDB_API_KEY secret,
  optional own key in Setup, sealed with the Keystore), and a cover made
  from the exe's icon when nothing else has one.
- Removing a game is instant and keeps its files; adding it again brings
  it back. Steam's shortcut list is rewritten once, in the background.
- The Games section is gone from Steam settings; folders added before
  keep listing their games. Artwork switch and SteamGridDB key are in
  Setup.
2026-10-09 19:50:03 -04:00
Kurt Himebauch d99ed8e00b fix(desktop): address KDE feedback and keep local builds in sync with CI (#488) 2026-10-09 21:35:37 +00:00
Kurt Himebauch c761b83112 feat(stores): bring the launcher's motion to the stores pages (#482)
* refactor(ui): share End session's step-out as StepOut

* feat(ui): let PageFlood carry a tile's art

* feat(stores): bring the launcher's motion to the stores pages

* fix(stores): lay out the pane's rows in a column

* fix(stores): a game page no longer leaves the pad's focus on a dead card and crashes the next press

* perf(stores): GOG remembers its non-games and the library cache loads off the main thread

* fix(ui): a step-out's backdrop never takes the pad's focus

* feat(stores): modes step out of the store chip, each store in its own colour, search in the header

* feat(stores): sign-in pages in the Steam tab's style and an are-you-sure for sign out

* style(stores): drop the sign-in page's store eyebrow, which the title already names

* style(stores): the sign-in page is the store's name and Sign in

* fix(ui): the focus ring follows a control until it stops and meets one still rising into place

* feat(stores): the store a pad's focus is on shows without A, and a press never lands on where it sent focus

* perf(stores): keep chip motion responsive while libraries change
2026-10-09 14:55:03 -04:00
The412Banner 5af14829b0 Stores: seal the GOG, Epic and Amazon sign-in files with an Android Keystore key (#486)
The stores' sign-ins (files/stores/<store>/credentials.json: access and refresh
tokens, account id and name) were plain JSON. Other apps could not read them, but
every program in the Linux session runs as the app and could. They are now sealed
with AES-256-GCM under a non-exportable AndroidKeyStore key (StrongBox when the
device has one), so the session only ever sees ciphertext.

- Format: a versioned envelope {v, alg, iv, ct}, written atomically and readable
  only by the app. No new dependencies (javax.crypto and KeyStore).
- Migration: an existing plaintext file is sealed at the next start, checked by
  decrypting it, then the plaintext is removed; users stay signed in.
- No Keystore on the device: the file stays plain and the log says so once per
  start.
- A sealed file that can never be opened (tampered, key gone or invalidated,
  broken envelope) is removed and that store asks for sign-in again. A transient
  Keystore error keeps the file and retries.
2026-10-09 14:42:23 -04:00
The412Banner 71e2ae8683 Logs: every log in app-private storage, shared as one scrubbed zip (#484)
Session folders, tool and installer logs and the stores' log now all live under the
app's private files/logs instead of Download/DroidDeck, and every existing "Share
logs" action bundles them into one zip that goes through a final scrub.

- Where: LinuxRuntime.logDir() = files/logs; sessions keep their dated folders and
  30-session pruning, tools/ and stores/ beside them. The "Logs" setting keeps its
  meaning. Game save backups stay in Download/DroidDeck/Saves.
- Migration: at first start the old session folders, tools/ and loose logs move from
  Download/DroidDeck into files/logs (Saves untouched), and every moved folder is
  scrubbed under the current rules.
- Scrubbing: one rule set (SecretScrub) for the stores' log and the zip. The scrub
  marker carries the rules version, so a rules change re-scrubs every folder at the
  next start (in the background, a few at a time). Steam login account names and
  emails, account-name fields, OAuth codes, tokens in URLs and private LAN
  addresses are removed; Windows exception codes and server addresses are kept.
- Share: one zip of the current session folder plus the last 7 days of stores/ and
  tools/, written to cache/share and handed to the share sheet.
2026-10-09 13:28:03 -04:00
The412Banner 06e108a726 Windows components: lay out installer .exe wrappers without running them (#480)
* Windows components: lay out installer .exe wrappers without running them

The components page could only take .msi packages apart; most catalog entries ship an
installer .exe, so VC++ runtimes, .NET Framework 4.0-4.8 and the .NET Core/5+ runtimes sat
under 'Need a Windows installer'. droiddeck-msi-install now opens those wrappers and lays
out the packages inside the same way:

- WiX bundles (Visual C++ 2012-2022, .NET Core/5+ desktop runtimes): the .wixburn section
  names the containers, each a cabinet found by its header (signing moves them); payloads
  get the names the bundle manifest lists, so each package finds its cab1.cab beside it.
  ARM64 packages are skipped, x86 and x64 both laid out.
- Self-extracting cabinets (Visual C++ 2005-2010): cabextract, external cabinet beside the
  package (vc_red.cab).
- Self-extracting 7-Zip archives (.NET Framework 4.0-4.8): 7-Zip, now bundled with the
  msitools (Arch Linux ARM 7zip 26.04: 7z + its 7z.so plugin, licences beside the others).
  The complete product (netfx_Full_*) wins over the 4.0 parts and the per-Windows copies.
- An .exe that is its own setup program (NSIS, InnoSetup) reports that it holds no package.
- --unpack opens an archive for the app: DirectX's redistributable for XACT's cabinet picks.

The app executes the remaining catalog steps offline: set_windows (HKCU\Software\Wine
Version), set_register_key, get_from_cab (through --unpack) and 'uninstall Wine Mono' (the
mscoree override beside it is what matters), and reads url/file_name the catalog nests under
'environment' on a few .NET entries. Setup-program installers stay in the waiting section
with a note that says what they are.

Verified on this branch's engine with msitools, cabextract and 7z: VC++ 2010 x64 (19 files,
61 values), VC++ 2022 x64 bundle (26 files, 66 values), .NET 4.0 (1053 files, 6056 values,
Framework64\v4.0.30319 with clr.dll and mscorlib.dll, NDP\v4 keys) and .NET 4.8 (1633
files, 522 GAC assemblies, 6495 values). Not yet run on a device.

* Windows components: the pressed row greys out and shows its own two-step progress bar

Pressing a switch now greys that row at once and runs a thin bar across it: the download
0..100, then the install 0..100, with a caption naming the file or the stage (a dependency's
name in front while that installs first). Turning a component off greys the row the same way
until the pick is saved. The page-wide bar at the bottom is gone; Done waits on the busy row.

droiddeck-msi-install reports how far along it is ('progress 37% placing files'): reading,
each cabinet, every 5% of files placed, the registry, done - and a wrapper maps each package
into its share, so a bundle's two packages run 10..55 and 55..100. The app parses the percent
and passes a typed Progress (component, stage, phase, percent) up to the editor.

* Windows components: the ones turned on sort to the top of the list

In 'All components' the picks for this game come first, in their usual alphabetical order
among themselves; turning one off returns it to its place. The row moves once the install has
finished and the pick is saved, not while its progress bar is running.

* Windows components: an 'Installed for this game' section under the recommendations

What is turned on for this game gets its own section between Recommended and All components,
alphabetical; the recommendations stay where they are (a recommended pick stays there). A
component in the section leaves All components, and goes back to its place when turned off.
LB/RB jump through the new section too.
2026-10-09 12:36:05 -04:00
The412Banner e99d5f9505 Stores: GOG, Epic Games and Amazon Games libraries, installs and cloud saves (#476)
A Stores section (rail, under Games; Setup › Stores gate) with each store's sign-in,
library, storefront shelves and a download queue. Each store has two tabs, Store and
Library, and Library filters to All or Installed; libraries open from a saved copy
and refresh in the background, with install sizes on every card. Games install to internal storage,
or a card chosen at Install time, and are registered in the Steam client as non-Steam
games, so they launch through Steam and droiddeck-proton from either the front end or
the client.

- Downloads: GOG, Epic and Amazon engines in Rust (app/src/main/rust/stores, built by
  cargo-ndk, nothing compiled in the tree). One adaptive fetch core with a latency
  brake, stalled-host handoff and every CDN a store offers. Per-stage progress,
  pause/resume, two-step cancel that deletes, failed downloads kept for retry, a
  foreground service with Wi-Fi and wake locks and notification controls.
- Games tab: store source chips (Steam, GOG, Epic, Amazon, Custom) and store art.
- Epic: per-launch exchange code from the app and from Steam's Play button,
  "Epic sign-in (EOS)", "Launch offline", and a game's web links opened in Android's
  browser for EOS consent. The EOS overlay crashed games and is left out.
- Cloud saves (GOG, Epic): pulled before launch, pushed after the game or the session
  ends, no upload before a first download, conflicts never overwritten, backups both
  ways.
- Logs: every store line redacted; the engine log goes to daily files.
2026-10-09 11:08:17 -04:00
Kurt Himebauch fe71084f23 feat: replace the LXQt desktop with SteamOS-style KDE Plasma (#473)
* feat: replace the LXQt desktop with SteamOS-style KDE Plasma

* feat: leaving the KDE desktop logs out through Plasma and returns to gaming mode

* feat: DroidDeck logo wallpaper as the KDE desktop default

* feat: build the KDE wallpaper from the DroidDeck mark artwork

* ci: keep the built desktop package as a short-lived artifact for device testing

* fix: the desktop loading screen no longer promises Steam on the desktop

* docs: the output resize comments name the desktop's KWin

* fix: the desktop package verifies every package's Arch Linux ARM signature and never replaces a published tag

* fix: programs sent to the GPU from the desktop drop its software rendering overrides

* fix: an interrupted desktop install no longer counts as installed

* feat: pin the desktop package to its DroidDeck-Components release instead of the catalog
2026-10-09 10:39:50 -04:00
Magarto 79880d1502 Desktop: keep the session when the big cores cannot be pinned (#394) 2026-10-09 07:13:57 -04:00
SG3xHERO e93397011d Add a Follow screen resolution for foldables (#403) 2026-10-09 07:13:06 -04:00
Kurt Himebauch 416c1b9efd fix: proot fast path hands opendir of a directory symlink to proot (#454) 2026-10-09 07:11:09 -04:00
Kurt Himebauch 7c758c817f perf: fast path answers Steam's pre-2.33 stat calls and stays off without the patched proot (#466) 2026-10-09 03:14:29 +00:00
Kurt Himebauch d6ba3d27d8 fix: runtime removal never strands an undeletable leftover (#463) 2026-10-08 21:39:00 -04:00
The412BannerandThe412Banner 28da3dcbde Desktop: Firefox skips shared-memory seals again (Android 16 tab crash) (#460)
Desktop: Firefox skips shared-memory seals again (Android 16 tab crash, #280 line lost in #252)

Co-authored-by: The412Banner <[email protected]>
2026-10-08 19:45:44 -04:00
Kurt Himebauch 527750a366 feat: install Flatpak apps from a .flatpak file (#446) 2026-10-08 14:13:32 -04:00
The412BannerandThe412Banner 789b603022 DirectAudio from its own release: driver set per Proton interface (CachyOS fix), the Steam client's own DirectAudio sink, binaries out of tree (#439)
DirectAudio from its own release: driver set per Proton audio interface (CachyOS fix), the Steam client's own DirectAudio sink, binaries out of tree

Games
- Two driver sets ship from the pinned DirectAudio release (tools/directaudio/release.env =
  directaudio-linux-v1.1.0, sha256-checked by tools/directaudio/fetch.sh in CI and
  build_local.sh): linux-wine11 for Valve Proton 11 / Experimental / GE-Proton, and
  linux-wine11-systhread for Proton-CachyOS, whose Wine carries the system-thread mmdevapi
  interface. steam-compatibility picks by whether the Proton's own winepulse.so imports
  PsCreateSystemThread, never by the version string (both say wine-11.0; the wrong set is
  silence). No matching set: DirectAudio off, Proton's audio runs, logged.
- Proton-CachyOS's launcher pre-sets WINE_AUDIO_DRIVER=pulse,alsa and its mmdevapi reads that
  before the registry (Valve's and GE's do not read it): the wrapper exports
  WINE_AUDIO_DRIVER=directaudio,pulse as well as writing the registry. On CachyOS this also
  makes DirectAudio engage on a game's first launch.
- A wineserver from an earlier Steam verb can save its in-memory registry over the appended
  Audio value; the wrapper says so when one is live. Writing through it (wine reg add) and
  creating a missing prefix first (proton run wineboot) were tried on the device and withdrawn:
  a second Proton/Wine run inside Steam's launch sequence got launches aborted.
- Tests: both sets, no set, no prefix (34 pass).

Steam client audio
- DirectAudio = module-directaudio-native-sink, the game driver's AAudio engine inside the
  PulseAudio daemon, one step from Android (adaptive buffer, two-burst floor, primed ring with
  fades, reopen on route change, watchdog, limiter). Default. Shared output = the previous relay
  route, kept. New pref clientAudioRoute; the old boolean is not read. If the native sink cannot
  open its stream the daemon keeps running (.nofail) and ensureClientSink loads the plain AAudio
  sink instead, logged to audio.log. Audio bundle stamp bumped.

Out of tree
- app/src/main/assets/directaudio/ is generated and ignored; tools/directaudio-relay/ and the
  vendored module-directaudio-sink.c + da_relay_proto.h are gone; tools/aaudio-sink keeps only
  module-aaudio-sink (the fallback). The release carries the relay and sink field fixes by
  MaxsTechReview (#338), credited there and in its AUTHORS.

Device-proven on an AYANEO Pocket FIT: client sink 12 ms / 4 ms bursts, 0 underruns; FEZ on
cachyos-11.0-20261005-slr on DirectAudio (took pulse before); FEZ on Proton Experimental
unchanged; Curse of the Dead Gods on CachyOS from a cold prefix, DirectAudio on first launch.

Co-authored-by: The412Banner <[email protected]>
2026-10-08 12:35:00 -04:00
MaxsTechReview 39220c8fd5 Land taps and the pointer on what is drawn under them (#389)
The compositor takes touch and pointer positions over its whole output and
removes the letterbox bars itself, but the session sent them already
measured from the picture's edge, so the bars were taken off twice. Any
session whose output is a different shape from the panel (16:9 on a 16:10
tablet or a foldable, a fixed resolution on a wide phone) had taps and the
touchpad pointer pulled away from the centre, by up to a bar's height at
the edges: on a 2400x1504 panel showing 1280x720, a tap near the top
reached the first row and one near the bottom landed 32 rows low. Stretch
to panel hid it because that output has no bars.

Direct touch, the touchpad and mouse pointer, and the second-screen pointer
now send positions over the whole view, so the compositor's own mapping
puts them on the pixel under the finger.
2026-10-08 11:52:45 -04:00
Kurt Himebauch f39dc2b97a chore: bump version to 0.3.2 (12) (#437) 2026-10-08 10:49:33 -04:00
Kurt Himebauch 4106569a2d docs: point 0.3.2 full changelog at the 0.3.2 tag (#438) 2026-10-08 10:49:29 -04:00
Kurt Himebauch 60566eca31 Draft 0.3.2 release notes (#417)
* docs: draft 0.3.2 release notes

* docs: update 0.3.2 release notes

* docs: update external storage install note

* docs: add keep downloads running demo gif to 0.3.2 notes

* fix: restore colours in keep downloads running gif

* docs: add native steam sleep demo gif to 0.3.2 notes

* fix: drop glitched frames from native steam sleep gif

* docs: add windows components screenshot to 0.3.2 notes

* docs: use newer windows components screenshot in 0.3.2 notes
2026-10-08 10:42:16 -04:00
MaxsTechReview 0b7529da63 fix: keep Steam's directory listings on FUSE libraries (#410)
* fix: keep Steam's directory listings on FUSE libraries

Before it creates, moves or deletes a content file the Steam client lists
the whole destination directory about ten times to match names
case-insensitively. On Android's FUSE storage every listing goes through
the media provider, so reserving space grew quadratically with the number
of files: 33 minutes for Haunted Paws Demo (3,938 files) in a Games folder.

The client's listings under steamapps/ on FUSE are now kept while the
directory's stat is unchanged, for at most five seconds, and patched by
the client's own creates, renames and removals. Other changes fail the
stat check and the directory is read again. BL_NO_DIR_CACHE=1 turns it off.

Haunted Paws Demo into the internal shared Games folder: reserving space
33m08s -> 25-52s, committing 6m15s -> 19-30s, install 39m50s -> 1m12s-1m51s.
Internal storage is unchanged (5s reserve).

* fix: drop stale directory listings on coarse-timestamp volumes

NTFS reports whole-second directory times and a fixed size, so a change
made outside the client's own calls could leave a cached listing stale.
Listings now carry an inotify watch and are dropped on any event the
client's own patched changes don't account for. A forked child stops
caching, since it shares the parent's queue.

Covers rewinddir, telldir and seekdir on cached listings, and directories
opened by the fast-path preload's opendir, which goes through fdopendir.
2026-10-08 08:50:00 -04:00
Kurt Himebauch 3c5c9b7dab Include PR descriptions and queue catalog updates (#421)
* Include a short PR description in build announcements

* Queue catalog refreshes without cancelling publishing runs
2026-10-08 00:46:34 -04:00
Kurt Himebauch fd75f3c6bd Fix PR build report lookup (#420)
Use standalone jq to filter paginated PR comments
2026-10-08 00:32:11 -04:00
Kurt Himebauch eb9b3967f1 Separate PR build announcements (#418)
* Separate PR build announcements and retire closed builds

* Isolate watcher test clocks and clean up the agent server
2026-10-08 00:23:55 -04:00
Tak-attackandxXJsonDeruloXx d59e97df5c Improvements to the Japanese Translation (#314)
Update strings.xml

Improvements to the Japanese Translation
I have rewritten the translation to make it sound more natural in Japanese.

Also, since “Loader” in “Decky Loader” is a proper noun, I believe it does not need to be translated.

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-08 04:11:52 +00:00
kmnkv1andxXJsonDeruloXx 3ca2787108 Update Russian translation (#324)
Translate the 103 missing strings (screen effects, scaling, Wine sync
backends, FEX for AppImages, Steam repair, device info) and fix existing
ones: inverted meaning in mode_suspend_note, content_add_roms and
wadb_connect_address, double space in the update/install buttons,
outdated AppImage/Proton hints, "1 приложений"-style counts, gender
agreement with substituted names, lowercase fragments that are joined
into sentences, and inconsistent terms (Setup, Developer options, Stop
session, Button mapping).

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-08 04:06:56 +00:00
0x142857andxXJsonDeruloXx b56c44bd0b Share font defaults across runtime sessions (#252)
* Support CJK text with the device's own fonts

The Linux runtime's rootfs ships DejaVu alone, which has no CJK
glyphs, so Chinese, Japanese and Korean text in Steam Big Picture and
on the LXQt desktop draws as tofu boxes (issues #118, #206, #249).

Bind the device's own CJK fonts - Android's world-readable Noto CJK
.ttc, or DroidSansFallback / Source Han - into the guest under
/usr/share/fonts/droiddeck-cjk, so nothing is added to the ~790 MB
runtime image and the coverage follows the device. A new
droiddeck-fonts script, run by droiddeck-session before any program
that queries fontconfig, links the default rules, writes local.conf
with DejaVu first and the CJK families after it, and rebuilds the
fontconfig cache once per set of font files. The desktop's own
fontconfig block moves there, so every mode gets it.

* Fix CJK and Thai: bind the device font directory, not one file at a time

The first cut bound each device font with its own proot -b host:guest. A
per-file binding into a plain directory is not applied here: the guest sees a
zero-byte placeholder where the font should be (stat size=0, fc-scan empty),
so Chinese stayed tofu even though the app had "bound" the font. A directory
binding is what the rest of the runtime relies on, so bind /system/fonts as a
whole under /usr/share/fonts/droiddeck-device. That also brings Thai, Arabic,
Devanagari and the emoji fonts, which the runtime did not have either.

Verified on the AYN Thor: the session's fontconfig probe reports
sans=DejaVu Sans, zh=Noto Sans CJK SC, th=Noto Serif Thai, and Steam Big
Picture in Simplified Chinese renders without tofu (screenshot under
docs/development/evidence/).

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-08 04:06:23 +00:00
bb68a961ed Polish Spanish translation (#234)
* i18n: complete Spanish

Spanish gets the strings added since it landed and the ones that are only a format or a brand name, so it has the same keys as English, in the same order. Each English term keeps one Spanish word across the file, and a few existing strings are reworded to read right with the values they are filled with.

* i18n: use compilación for build in Spanish

* i18n: translate new strings to Spanish

---------

Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-08 04:05:09 +00:00
Kurt Himebauch 415b93f943 feat: agent bridge wayland and gamescope coverage (#406)
* feat: run a guest agent in every session for commands, focus and the Steam client's DevTools

* feat: give the agent bridge game, input, UI and experiment commands behind a Setup toggle

* feat: droiddeckctl commands for the new bridge, a scenario runner and contract tests

* feat: run cleanup steps after every scenario run so a failure cannot leak into the next

* feat: restart a session that died between scenario runs with start --reuse

* fix: give droiddeckctl stop its own, longer wait for the session's logs

* fix: harden debugging tools access and output
2026-10-08 03:50:58 +00:00
Kurt Himebauch 7b1f0f25b2 harden logging (#414) 2026-10-08 02:10:06 +00:00
The412Banner 5954ac58f8 fix: read droiddeck-env from ps (#413) 2026-10-07 21:38:33 -04:00
Kurt Himebauch 6eda278d9b fix: backport upstream gamescope fix for games left behind Steam's loading screen (#401) 2026-10-07 15:28:11 -04:00
Kurt Himebauch 9867a2d4ce fix: keep Updates channels and status side by side (#397) 2026-10-07 13:48:02 -04:00
MaxsTechReview 2a0512d19d Keep the shortcut settings changed in Steam (#393)
Steam saves a shortcut's Properties to shortcuts.vdf as soon as they
change and keeps its appid. The session rewrote every added game's entry
from the app's list before and after each client run, keeping only the
launch options, so a Target or Start In set in Steam, a new name or icon,
the hidden and overlay switches, the last played time and Steam's sort
name were all put back at the next exit.

droiddeck-steam-shortcuts now merges instead: an entry keeps every field
Steam has, and the app's exe is written only where the app changed it.
What the app last asked for and what Steam then had are recorded beside
shortcuts.vdf (.droiddeck-shortcuts.json). A Target changed in Steam to a
file inside the game's folder becomes the exe chosen in Steam > Settings >
Games, and a later pick there replaces it in Steam, so both show the same
exe. The appid now stays with the folder, so changing the exe no longer
turns the game into a new one with a new prefix, playtime and launch
options. A game whose folder is missing is set aside with its settings
and comes back with them. The exit-time merge runs before Steam's logs
are copied.
2026-10-07 09:32:15 -04:00
MaxsTechReview 6ee8866599 Make Games folders Steam libraries and skip the zero-filled reservation (#388)
With Second library on Automatic, every writable Games folder is now a
Steam library: it gets the same private prefix and tool binds as the card,
the app hands the session the list (BL_STEAM_LIBRARIES), and
droiddeck-steam-library registers, relabels and forgets entries from it.
Steam offers them in the install dialog as "<folder> (<volume>)", and
Proton mapping, esync, the redist seeder, desktop entries and the compat
beat read every registered library instead of only the card. Games folder
libraries stop at the fast path's bind budget.

Steam's preallocation on Android's FUSE storage either failed outright
(NTFS answers EPERM, so installs were cancelled as "not enough disk
space") or zero-filled the whole download first (exFAT, minutes for a
small game). After fallocate is refused, the Steam client's ftruncate
extension of its own staging files is reported as done; the writes that
follow fill the file. A 76 MB install to a USB SSD now preallocates
instantly instead of taking about 3.5 minutes.

A library that is not there is left out of the list and forgotten until
it returns, imports whose folder is missing are set aside rather than
offered as broken installs, and the front end refreshes when storage is
mounted or removed.
2026-10-07 09:31:33 -04:00
The412Banner bc0f6e7271 File manager: the Linux system's root under Storage (#392) 2026-10-07 08:12:02 -04:00
The412Banner a84e9cdc08 File manager: the Linux system's root under Storage
A "Linux system" entry in the file manager's Storage list (the rail and the
drive menu) opens the root of the Linux system the sessions run in - the
app's files/linuxfs, the desktop's / - so its files can be browsed, copied
in and out without the desktop. Shown once the runtime is installed; Back
stops at that root like a drive's, and the drive label reads Linux system
anywhere under it. The label is translated in all nine languages.
2026-10-07 08:05:43 -04:00
Kurt Himebauch 2a9cb977cf Let Steam draw at the session's full size above 1080p (#391) 2026-10-07 07:51:50 -04:00
Juan MartíandxXJsonDeruloXx f5e7d73654 Add Steam background download mode (#335)
* Add Steam background download mode

* Keep active Steam downloads running

* Make background downloads a toggle

* fix: keep background downloads alive between Steam's rate log lines

* Track all Steam content update types

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-07 10:36:49 +00:00
Kurt Himebauch 82ad9330d0 fix: revert gamescope patch 0113 now that games run on their own Xwayland (#372) 2026-10-07 06:25:06 -04:00
MaxsTechReview d1760cd49f Draw Steam's CJK and Thai text with the device's fonts (#374)
Steam ships no CJK font and the rootfs only has DejaVu, so a phone set to
Chinese, Japanese or Korean got Steam in that language with every character
drawn as a box. Bind Android's /system/fonts into the guest, index it again
whenever it changes, and prefer the matching Noto CJK face for each language.
2026-10-07 02:23:27 -04:00
The412Banner bd65f1660f File manager: up/back from any start folder, plus a ".." up row (#377) 2026-10-07 00:05:06 -04:00
The412Banner eda019a044 File manager: up/back work from any start folder, and a ".." row to go up
Opening the manager at a folder (a game's files, its Proton prefix, the
saves folder, or the picker's remembered folder) ran openDrive() on the
start dir, which pinned the up/back floor to that folder: the toolbar
arrow was greyed out and Back closed the screen. The start dir now opens
with loadDirectory(), keeping the volume-root floor computed for it, and
folders in the app's private storage (internal Steam library, prefixes)
floor at the app's files dir instead of themselves.

Adds a ".." (Up one level) row at the top of the list and grid whenever
the current folder is above its floor, sharing one goUp() with the
toolbar arrow. Covers both screens built on FileManagerScreen: the File
Manager and the in-app file/folder picker. Strings for all 9 locales.
2026-10-06 23:52:59 -04:00
MaxsTechReview fa99e2aab6 Language setting and translations: Spanish, Japanese, Korean, Chinese; Steam follows it (#187) (#284)
* l10n: move hard-coded UI text into string resources (#212)

About 650 user-facing strings were written in Kotlin and Java, so no
translation could reach them and the UI stayed half English in every
language. They now live in values/strings.xml.

- Dialogs, menus, status lines, toasts and errors shown to the user go
  through getString/stringResource.
- Code that matched on English text now uses IDs: installer and loading
  stages, Decky stages, storage labels and the Back button's order labels.
- Choice lists (upscalers, FPS limits, textures, themes, presets) carry
  @StringRes labels instead of English.
- Robolectric tests read the app's resources (includeAndroidResources).
- Six strings nothing used any more are dropped.

Existing translations are untouched; the new strings show in English
until they are translated.

* Layouts: fit longer text in other languages, and small phones

Found by screenshotting every page, tab and menu in each language on a
phone, a tablet, a 640dp phone and a foldable.

- Rail labels shrink to fit instead of being cut off (FitText).
- Tab rows tighten when they overflow and scroll the chosen tab into view.
- Menus fade over a down arrow when more entries are below, and open
  scrolled to the current choice.
- On a small phone the desktop card's button moves under its title,
  emulator tiles go two across, the GPU support verdict wraps and the
  Updates page stacks the channels under the status.

* l10n: translate every string into Spanish, Japanese, Korean and Chinese (#187)

Simplified Chinese, Traditional Chinese (Taiwan) and Traditional Chinese
(Hong Kong, reviewed for Hong Kong wording) are complete, as are Spanish,
Japanese and Korean: no missing keys and no placeholder mismatches.

* Language setting: follow the system, or a language picked in Setup; Steam uses it too (#187)

- Setup -> Launcher starts with a Language row: "System default (...)"
  and each language by its own name. With nothing picked the app follows
  the system language; a pick applies at once and stays until System
  default is picked again.
- Steam keeps its language in ~/.steam/registry.vdf under both
  Valve/Steam/language and Valve/Steamsteamglobal/language. Before each
  start of the client the guest helper droiddeck-steam-language writes
  both (and exportedsettings.json), and the client gets -language.
  SteamLanguage maps the app's language to Steam's names: english,
  spanish/latam, japanese, koreana, schinese, tchinese.

* l10n: translate the strings main added since, and Chinese review fixes

- Translate the 32 strings added on main since (Windows components,
  device details, Force SSBS, native Steam sleep) into all six languages.
- zh-CN: busy labels say what is in progress again (正在取消, 正在导出
  ...), as the other languages do.
- zh-CN: "set up" is 配置, as Setup is; 设置 stays for Settings.
- zh-TW: proton_compatible_note says 前綴 rather than "prefix".
2026-10-06 22:00:21 -04:00
MaxsTechReview 0b04678bdc l10n: move hard-coded UI text into string resources (#212) (#282)
About 650 user-facing strings were written in Kotlin and Java, so no
translation could reach them and the UI stayed half English in every
language. They now live in values/strings.xml.

- Dialogs, menus, status lines, toasts and errors shown to the user go
  through getString/stringResource.
- Code that matched on English text now uses IDs: installer and loading
  stages, Decky stages, storage labels and the Back button's order labels.
- Choice lists (upscalers, FPS limits, textures, themes, presets) carry
  @StringRes labels instead of English.
- Robolectric tests read the app's resources (includeAndroidResources).
- Six strings nothing used any more are dropped.

Existing translations are untouched; the new strings show in English
until they are translated.
2026-10-06 21:59:31 -04:00
MaxsTechReview 1951d8bfde Fix Audio Crackling on Pulse & DirectAudio (#338)
* Build the DirectAudio relay from source

The relay helper shipped as a prebuilt libdirectaudiorelay.so with no source in the
repo. Its source (The412Banner/directaudio @ 3548a4a, the commit the bundled driver
names in version.txt) now lives in tools/directaudio-relay, unmodified, and CI and
build_local.sh compile it into jniLibs the way proot is built. It shares
da_relay_proto.h with the PulseAudio sink, which is byte-identical to its own copy.

* DirectAudio relay: prime before playing, one consumer, safe buffer floors

- The output stream started before the client had its ring, so every connection
  opened with a run of short callbacks: six clicks per stream on a RedMagic, even idle.
  The callback now plays silence until the ring holds its target, and only a real
  underrun afterwards counts.
- An underrun ends the block with a short fade and waits for the ring to refill to
  target before resuming with a fade in, instead of zero-padding every short callback.
  Each callback spent starved grows the target, so one stall sizes it for the next.
  With a producer freezing 60 ms every 2 s: 14 underruns before, 1 after.
- During a reopen the outgoing and incoming streams could both drain the ring. Only
  the promoted stream reads it now; any other plays silence.
- The ring target starts at 40 ms instead of two bursts (8 ms) and keeps what it
  learned across reopens.
- The device buffer is at least two bursts; 12 ms was a single burst on devices that
  grant a 20 ms legacy stream.

* PulseAudio: the classic path plays through the native-rate AAudio sink

The classic path loaded the prebuilt 0.1.5 module, which opened AAudio at the daemon's
default 44.1 kHz: Android gave it a legacy stream (20 ms bursts) and resampled, after
PulseAudio had already resampled the 48 kHz clients down. Its data callback also waited
on the daemon's IO thread, and it started at two bursts and grew only after an audible
underrun. The app's own module-aaudio-sink, built by CI into the same bundle, now
carries that path:

- The stream opens at the device's rate (48 kHz) with the buffer at two bursts or
  30 ms, whichever is more, still growing a burst per underrun.
- A write that times out part way keeps the rest for the next write instead of
  dropping it.
- A stream reopened after a route change asks for the rate the first one got, so the
  sink keeps its format instead of unloading.

* Audio: DirectAudio sink at audio priority, deeper client buffers, no decay probing

- module-directaudio-sink's IO thread asks for nice -16 like module-aaudio-sink's, so
  the client's producer keeps up under game load.
- PULSE_LATENCY_MSEC=60 for the session: libpulse clients under FEX (the Steam
  client, winepulse when DirectAudio is off) keep enough queued to ride out a late
  wakeup.
- BANNER_AUDIO_DIRECT_DECAY=0: the driver's decay shrinks the device buffer every
  10 s of quiet until it underruns again, a click by design.

Both sit ahead of Downloads/droiddeck-env and per-game environment, which still win.

* Audio: drop the session-wide PULSE_LATENCY_MSEC

On an AYN Thor it took the Steam client's stream to 110 ms (60 ms tlength on top of the
sink's 40 ms) with no underruns to show for it, and the delay was audible. Clients pick
their own buffers again; the per-game option still sets it.

* PulseAudio sinks: wait in the poll loop, never in a blocking call

Clients' audio reaches a sink as messages its IO thread handles in pa_rtpoll_run. The AAudio
sink spent each iteration inside a blocking AAudioStream_write for up to a burst, and the
DirectAudio sink inside a futex wait for up to a burst, so a client's data sat unread and its
stream ran dry: a Chromium-shaped client (44.1 kHz float, 30 ms tlength, 10 ms minreq) had
304 underflows in 30 s on the AAudio sink, heard as crackling in the Steam client. Bursts of
20 ms (a legacy stream, an AYN Thor) made it worst.

- module-aaudio-sink writes without blocking, renders only what the device has room for, and
  comes back after half a burst. 0 underflows in 30 s, with and without 8 busy cores.
- module-directaudio-sink tops the ring up on the same half-burst timer instead of sleeping
  on the relay's futex.

* DirectAudio relay: vendor the source the shipped binary was built from

The import took 3548a4a, the commit the bundled driver names in version.txt, but the relay
binary that shipped came from e4727e9 on the same branch (feat/linux-relay-mic): its strings
match a build of that source exactly. 3548a4a has no --mic-fifo, so with the microphone on the
relay printed its usage and exited, and DirectAudio played nothing. This is e4727e9 as
published; the fixes made on top of 3548a4a follow in the next commit.

* DirectAudio relay: prime before playing, one consumer, safe buffer floors (on e4727e9)

The same changes as ca5d39b, carried onto the e4727e9 source: playback waits until the ring
holds its target, an underrun fades out and re-primes, starved callbacks grow the target, only
the promoted stream drains the ring, the ring target starts at 40 ms and survives reopens, and
the device buffer is at least two bursts.

* Steam client audio: DirectAudio by default

A fresh install plays the Steam client through the DirectAudio relay. Choosing Classic in the
Steam cog is stored and kept; only an install that never chose falls back to DirectAudio.
2026-10-06 17:10:00 -04:00
Juan Martí e3cf9f882a feat: recognize Adreno 650 as supported (#326) 2026-10-06 16:02:54 -04:00
Kurt Himebauch 8617ee1090 fix: give each new session the current screen's output size (#357) 2026-10-06 19:17:52 +00:00
Kurt Himebauch 670c9d1afd fix: share session logs in about a second instead of re-scrubbing them (#352) 2026-10-06 14:51:43 -04:00
The412BannerandThe412Banner 7484f43dba Windows components: install .msi packages (XNA, PhysX, GFWL, MSXML...) without running them (#347)
* Windows components: install .msi packages (XNA, PhysX, GFWL, MSXML...) without running them

droiddeck-msi-install reads a Windows Installer package's tables with msiinfo and unpacks its
cabinets with cabextract, then lays out what Wine's msiexec would have installed: files at their
64-bit Windows paths (Program Files (x86), syswow64...), .NET assemblies in the GAC, side-by-side
assemblies in winsxs, and the registry values in the view the component writes through. Conditions,
features and the folder-setting custom actions are evaluated as on Windows 10 64-bit; what needs
the package's own code (DXSETUP, self-registration) is listed in the component's notes.

droiddeck-wincomponents puts a component's drive_c tree into the prefix (keeping the prefix's own
spelling of each folder) and writes its registry values into system.reg/user.reg before launch,
following Wine's link keys (Wow6432Node\Classes, CurrentControlSet...). Turning it off puts every
value back and takes an added PATH entry out again.

The app installs these from the Windows components menu like the DLL ones: it downloads the .msi
into the runtime and runs the installer there (GuestCommand). msitools 0.106 and cabextract 1.11
come from Arch Linux ARM, pinned in tools/msitools/packages.txt and hosted on the msitools-0.106-r1
release. wine-gecko stays hidden (Proton ships it) and wine-mono keeps waiting (uninstall step).

* Windows components: open with the top switch focused, by touch or by pad

The dialog's window starts in touch mode, where a switch cannot take focus, and the first switch
was asked once, two frames after the list loaded, only when a pad opened it - racing the dialog's
own Done focus. Now it always puts the window in pad mode, holds Done while the list loads and
then asks the top switch every frame until it has focus.

* Windows components: give msitools its own libraries; show a failed install on its row

msiinfo and cabextract sit with their libraries in /usr/local/lib/droiddeck-msitools, which the
loader does not search: every .msi install stopped at "libmsi-1.0.so.0: cannot open shared
object file". droiddeck-msi-install now runs them with that folder on LD_LIBRARY_PATH.

A failed install also says so under the component itself ("Could not install: ..."), not
only at the foot of the dialog, past the list.

---------

Co-authored-by: The412Banner <[email protected]>
2026-10-06 14:14:28 -04:00
Kurt Himebauch 2278274588 feat: remove Force game windows fullscreen now that games run on their own Xwayland (#349) 2026-10-06 18:04:22 +00:00
Kurt Himebauch 5f3c0e83c7 feat: run Steam sessions on two Xwayland servers as SteamOS does (#344) 2026-10-06 17:56:13 +00:00
The412BannerandThe412Banner dba632ea14 Fast path: hold every bind, so added games start in their own folder (#343)
* Fast path: hold every bind, so added games start in their own folder

libblfastpath kept the first 64 binds and dropped the rest without a word. A session has 87 (two
Games folders are the last two), so /root/Games/<folder>/... was mapped into the rootfs - the
empty mount point - instead of the bound storage. Wine resolves a new process's working directory
by those paths, found no drive for it and started every added game in C:\windows ("could not
find DOS drive for current working directory"): DiRT 3 did not mind, FEZ looks for
Content\Essentials.pak there and quits.

The library now holds 256, and turns itself off for the process when a bind does not fit or the
list runs past that, leaving the call to proot, which never maps it wrong. The app keeps the fast
path off for a session with more binds than that.

Verified on an AYANEO Pocket FIT: FEZ (added game) reaches its title screen; the warning is gone.

* Fast path: say in the session log when it stays off

---------

Co-authored-by: The412Banner <[email protected]>
2026-10-06 13:48:01 -04:00
Kurt Himebauch 9fe5f29f64 fix: resolution presets keep the panel shape at 720p, 900p and 1080p (#337) 2026-10-06 13:19:46 -04:00
Kurt Himebauch 9931dd5d09 fix: hold flood edges at the page edge once reached (#330) 2026-10-06 12:07:24 -04:00
Kurt Himebauch e95009fac2 fix: catch KGSL GEM_CLOSE at ioctl instead of exporting drmIoctl (#305)
* fix: avoid reusing retired KGSL GEM handles

* fix: defer closing GEM_CLOSE'd KGSL dma-buf descriptors

* Revert "fix: defer closing GEM_CLOSE'd KGSL dma-buf descriptors"

This reverts commit 191bf4b6f2.

* test: interpose drmIoctl without releasing GEM_CLOSE handles

* fix: catch KGSL GEM_CLOSE at ioctl instead of exporting drmIoctl
2026-10-06 11:52:16 -04:00
The412BannerandThe412Banner 239ba48291 Windows components for Steam and added games; no more redist installer prompts (#287)
* Put a game's Windows components into its Proton prefix at launch

droiddeck-wincomponents copies the DLLs of the components picked for a game
into compatdata/<appid>/pfx before Proton starts and hands their overrides to
WINEDLLOVERRIDES, so the registry is never touched. It re-checks every launch
(an older Proton deletes the files it once placed), takes a component's files
out again when it is turned off, and forgets Proton's config_info then so
Proton puts its own builtins back.

* Windows components for added games: picker, packages and their catalog

Settings > Games lists a Windows components row under each added game. Its
dialog switches components on per game; the first switch-on for any game
downloads the package (a catalog of its own beside desktop.json, installed
with the desktop package installer to /opt/droiddeck/wincomponents/<id>).
The picks are kept in the app and published to the runtime at every session
start for droiddeck-wincomponents to apply.

tools/wincomponents/package.py builds the packages: Bannerlator's eight
wincomponents archives re-rooted, and OpenAL from OpenAL Soft's release
(soft_oal.dll placed as OpenAL32.dll, 32- and 64-bit).

* Windows components: list the installed ones when the catalog cannot be reached

Offline the dialog only looked up ids it already knew - the catalog's and
the game's picks - so a component downloaded earlier never showed.

* Windows components: Bannerlator's list with Recommended, on the game's launch settings

The picker moves from Settings to a Windows components card in an added
game's launch settings. It offers Bannerlator's whole catalog
(components.json on winlator-contents): what the game's folder points at
first (Bannerlator's DependencyDetector, mapped to the DLL-copy twin where
there is one, oalinst -> oalinst_dll), then every component that installs
here, then the ones that need a Windows installer, greyed until DroidDeck can
run one.

A component installs by running its catalog steps into the runtime once:
archives downloaded (one download per archive for a batch), DLLs found by
name as Bannerlator does and copied to /opt/droiddeck/wincomponents/<name>,
overrides and bundled components written to component.json. The launch
script now takes catalog names (xaudio2.7), expands bundles (directmusic),
places files one level down (drivers/gm.dls) and keeps an override's own
mode (dsound=n). Drops the hand-made packages and their script.

* Windows components: Steam games too, and a pad that can drive the list

The card shows on a Steam title's launch settings as well, keyed by its
appid - the compatdata folder droiddeck-game-env reads - and Recommended
scans the game's install folder.

With a pad: Done takes focus while the catalog loads and the first
component once it is there (the list was not drawn yet when the dialog
asked for focus, so nothing had it); switches stay enabled during a
download and a press then is ignored (a disabled switch dropped the
pad's focus); LB/RB jump between Recommended, All, the installers and
Done.

* Redist markers from games' own install scripts; VC++ runtime as installed

The client still started a game's own _CommonRedist installer - its
script sits in the game's folder, often with another version in the
value name, and only Steamworks Shared's were read. On ARM64 Proton the
x64 VC++ 2015-2022 bundle then stops with "Microsoft Visual C++
2015-2022 Redistributable (arm64) is required" (reported on an AYN Odin 2
Portal Max).

- Read installscript*.vdf from every installed game in each library as
  well; the list is cached until an appmanifest changes, since this runs
  every quarter minute.
- Record the VC++ 2015-2022 runtime as installed where its installers
  look (VisualStudio\14.0\VC\Runtimes\{x86,x64,arm64}, both registry
  views), so any bundle - the client's or a game's own - finds it and
  exits; Proton's msvcp140/vcruntime140 serve the game.
- Join the Wow6432Node path instead of sub(): mawk and gawk read the
  replacement's backslashes differently (gawk, the runtime's, was right).

* Mark a game's install-script processes as run just before the client evaluates them

An Odin 3 bundle (0.3.1, Proton Experimental ARM64) shows the client
running its install-script evaluator on the first launch of three new
titles (409710, 2161700, 3404260) while their Steamworks Shared
redistributables (VC++ 2010/2012/2019/2022, DirectX) were still
downloading - 2161700 at 22:03:22, the shared app finished at 22:03:29 -
so the periodic seeder had nothing to mark yet and the installers ran.

The client writes the exact steps to legacycompat/evaluatorscript_<id>.vdf
and starts iscriptevaluator.exe through our launcher. droiddeck-game-env
now hands that script to droiddeck-seed-redists first, which marks every
process in it in the template and the prefixes before the evaluator
reads them.

The seeder's reader no longer assumes a depth: a block with a HasRunKey
is a process named by the key it opened under, wherever it sits.

* Windows components: keep a file that was not Proton's, put it back when turned off

A component's DLL replaced whatever had that name in the prefix, and
turning it off deleted ours, so a file a real installer (or the user) had
put there was gone for good. Before replacing a file that is neither
Proton's own (a symlink into Proton, or a Wine builtin/placeholder DLL)
nor an earlier copy of ours, keep it under
compatdata/<id>/.droiddeck-wincomponents-backup and move it back when the
component is turned off. The launch note says how many went back.

* Windows components: recommend what Steam installs, say what the game already has

- Steam titles: the appmanifest's SharedDepots (Steamworks Shared, app
  228980) list the VC++ 2005-2022 and DirectX redists Steam installs with
  the game; they live outside the game folder, so the folder scan never
  saw them. They join Recommended as "Steam installs VC++ 2010 with this
  game" (SteamRedists).
- Bannerlator's PrefixInstalledDetector, on compatdata/<id>/pfx:
  OpenAL32, Mono, PhysX and Games for Windows Live that are already there
  from elsewhere show "Already in this game". Files droiddeck-wincomponents
  placed itself do not count.
- The game page passes the prefix and, for Steam titles, the appid.

* Windows components: friendly names

The list showed catalog keys (oalinst_dll, vcredist2010_dll, dotnet452,
d3dcompiler_47). Bannerlator's catalog has no display name and some of
its descriptions do not fit the entry, so WinComponentNames gives one:
families by pattern (Visual C++ <year>, .NET Framework x.y[.z], XAudio,
D3DCompiler, MSXML, Wine Mono, DirectMusic parts), the rest from a
table, the key itself for anything new. The key stays in the row's
detail for logs and bug reports; both lists sort by the name, and the
game page's card names the picks the same way.

---------

Co-authored-by: The412Banner <[email protected]>
2026-10-06 10:46:27 -04:00
The412BannerandThe412Banner 931e5e3362 Force SSBS for Proton games (libssbs.so), switch under FEX preset (#328)
* Force SSBS for a game's Wine: libssbs.so, opt in per game

Wine's arm64 signal code resumes threads from a Windows CONTEXT whose
Cpsr never carries PSTATE.SSBS, so every signal return leaves the
thread running with speculative store bypass disabled - slower on cores
such as Oryon. Bannerlator fixes it in its own Wine builds
(force_ssbs.diff, Sonicadvance1) and with an LD_PRELOAD adapter for
layers it cannot rebuild. DroidDeck's Protons (Valve, GE, CachyOS ARM64)
come prebuilt from Steam but run the same aarch64 Wine signal path, so
the adapter applies here: tools/linuxfs/ssbs/ssbs_adapter.c, the same
source built for glibc (+ sysv_signal), staged to
/usr/local/lib/libssbs.so.

droiddeck-game-env preloads it when the game's (or the shared)
environment sets DROIDDECK_FORCE_SSBS=1, and only into an ARM64 Proton.
Off unless asked for: the gain is unproven on Snapdragon 8 Gen 3.

For testing: SSBS_ADAPTER_OBSERVE=1 counts without changing anything
(the baseline) and SSBS_ADAPTER_DEBUG=1 logs the fixes; both also count
signals that ARRIVE while a thread runs without SSBS, which shows
whether anything besides signal returns clears it. Checked on an ARM64
Linux kernel: a handler that clears SSBS left 4 of 5 later signals
arriving with it clear; with the adapter, 0 of 5.

* Force SSBS: a switch under the FEX preset in Steam settings

Steam settings > Games gets "Force SSBS" right under the FEX preset, off
unless turned on. Like the preset it goes into the game environment's
defaults (DROIDDECK_FORCE_SSBS=1), published at once and read by
droiddeck-game-env on the next game launch, so a game's own environment
can still set it to 0.

Measured on the device with the adapter's counters (AYANEO Pocket FIT,
DiRT 3 on GE-Proton11-7-aarch64): observing only, 8192 of 8277 signals
found the thread running without SSBS; with the adapter, none did - it
keeps the bit set everywhere Wine clears it, as a rebuilt ntdll would.
The speed-up itself is reported on Oryon cores and was not measurable on
the FIT's 8 Gen 3, hence off by default.

---------

Co-authored-by: The412Banner <[email protected]>
2026-10-06 09:27:15 -04:00
UnbelievableFlavour bb43965529 Added support for Adreno 6xx devices (#320) 2026-10-06 09:08:04 -04:00
Kurt Himebauch e152dd0212 Add Native Steam background sleep and automatic wake (#291) 2026-10-06 00:15:39 -04:00
Kurt Himebauch 524de172d3 ci: require isolated version bumps (#303) 2026-10-06 03:26:14 +00:00
Kurt Himebauch 61b79a2ecb Return frontend-launched sessions to Android Home (#300) 2026-10-05 21:39:40 -04:00
xXJsonDeruloXx 3415301da4 Merge remote-tracking branch 'origin/main' into frontend-quit-return 2026-10-05 21:34:49 -04:00
xXJsonDeruloXx e984af6a7a Return frontend-launched sessions to Android Home 2026-10-05 21:34:36 -04:00
Magarto 440ec98f45 Keep the 16:9 stretch (#63) working in Steam Deck mode (#133) 2026-10-05 21:28:31 -04:00
The412BannerandThe412Banner 0bcb4a98fe Device details on the GPU card; Adreno model and SoC name when the kernel leaves them out (#295)
* Device details on the GPU card, and the Adreno model when the kernel leaves it out

The Components page called an AYANEO Pocket FIT (Adreno 750) "Adreno"
and warned it was "Below tested hardware (Adreno 725 and newer)": its
kernel names the GPU "Adreno33v2", GpuInfo wanted three digits, and the
SoC model is blank. When KGSL's name has no model, take it from the
phone's Vulkan driver (once this process has asked it) or from the
platform code name (pineapple -> 750, kalama -> 740, sun -> 830, ...).

The GPU card now opens with a tap or A onto the device's details,
gathered once off the main thread (DeviceInfo): SoC (maker, model,
platform, soc0 id), GPU (model and where it came from, the kernel's
name, clock, load, temperature, support), CPU clusters, memory, system,
the phone's own Vulkan driver - device, API, driver version and build,
conformance, limits, the extensions DXVK and VKD3D lean on, Android's
certified version and level - and the drivers DroidDeck uses. A button
copies it all for a bug report; each section takes pad focus so the
d-pad walks the list.

Vulkan is asked through a new small native library (deviceinfo,
vkinfo.c) that opens the system loader with dlopen and keeps nothing
open.

* Device details: the chip's model and name, CPU clusters, GPU clock range

Ideas from ClusterTune (GPL-2.0, its approach, not its code), which
tunes clocks on AYN, Retroid and other handhelds:
- The SoC model is read where vendors put it - ro.soc.model,
  ro.vendor.qti.soc_model, Android's SOC_MODEL, ro.fota.platform (unless
  it is a branch name like "MSM_13.0") - since handhelds often leave
  Android's blank (Pocket FIT: SG8350P).
- SocNames names it: model codes we are sure of (QCS8550 -> Snapdragon
  8 Gen 2, SM8750 -> Snapdragon 8 Elite, ...), else the platform as a
  family (pineapple -> "Snapdragon 8 Gen 3 family"). The GPU card's
  subtitle shows it: "Snapdragon 8 Gen 3 family (SG8350P)".
- CPU clusters come from the cpufreq policies (the cores each covers,
  min-max speed) rather than grouping cores by top speed.
- GPU clock range from KGSL's step list, or a devfreq GPU node on
  non-Adreno chips (Mali), not only the top clock.

* Device details: drop Qualcomm's own "Driver Build:" label from the driver build line

---------

Co-authored-by: The412Banner <[email protected]>
2026-10-05 20:32:51 -04:00
Kurt Himebauch 3d16dc7ea4 Fix legacy Steam sleep and missed wake handshakes (#289)
Fix legacy Steam sleep and recover missed wake handshakes
2026-10-05 16:49:55 -04:00
輕鈴andKurt Himebauch 86f9b63b79 Refine Simplified Chinese localization (#232)
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-05 15:58:59 -04:00
starwishHiandKurt Himebauch e9f66aec81 Add French and Russian translate, add fr,ru,jp,ko in locales_config.xml And overlap #234 and #259 (#286)
* Create values-zh-rCN

values language of Simplified Chinese

* Delete app/src/main/res/values-zh-rCN

* Add Korean,Japanese and Tranitional Chinese (zh-TW,zh-HK,zh-MO) translations

* Delete app/src/main/res/values/values-ko/strings.xml

* Create strings.xml

Add korean translate

* Add French translate

* Add Russian translate

* Fix French translate error

* Add the fr,ko,ja,ru in locales_config.xml
Fix French bulid error

* Overlap #234 and #259

---------

Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-05 15:52:39 -04:00
MaxsTechReview a73d2108e0 sign-apk: rename the agent provider's authority for the other packages (#285) 2026-10-05 14:41:38 -04:00
Kurt Himebauch 2e6e35fb99 Use plain-ID .steam files for frontend exports (#277) 2026-10-05 13:48:18 -04:00
The412BannerandThe412Banner 6f6bd35b96 Firefox: skip shared-memory seals (tabs crash on Android 16) (#280)
On Android 16 phones (Galaxy S25 SM-S931B, SM-S948B, RedMagic NP05J)
every Firefox and LibreWolf tab dies at once: Firefox seals the size of
the shared memory it hands a tab, adding the seal fails there, Firefox
only warns, and the tab refuses the unsealed memory ("IPDL protocol
error: Shared memory PlatformHandle is not safe to map") and exits on
signal 11. The window opens; no page ever loads.

MOZ_SHM_NO_SEALS=1 is Firefox's own switch to skip the seals
(ipc/glue/SharedMemoryPlatform_posix.cpp). Set it beside the sandbox
switches in all three places a browser starts from: the desktop session,
AppImages, and Flatpak sandboxes.

Co-authored-by: The412Banner <[email protected]>
2026-10-05 13:37:08 -04:00
Lobanokivan11 1b3a8c58cb merge bridge to main (second time) (#279) 2026-10-05 17:09:24 +00:00
The412BannerandThe412Banner 5ddcde7023 Force game windows fullscreen: off by default (#276)
A game whose resolution differs from the screen's (DiRT 3 at 1280x720)
fights --force-windows-fullscreen: every forced resize makes it rebuild
its swapchain, and the picture flickers between its own size and the
screen's - often from the first menu, before the player can reach the
game's resolution setting. It also drove the Xwayland dma-buf leak fixed
separately.

The switch stays in Settings > Games and the session drawer, now off
unless turned on; its hint says when to turn it on (a game coming back
small or in a corner after switching apps, e.g. FlatOut) and when to
leave it off (flicker). Users who already set it keep their choice. The
session script's fallback when the app passes nothing is off as well.

Co-authored-by: The412Banner <[email protected]>
2026-10-05 12:44:45 -04:00
The412BannerandThe412Banner 2ccc8204ad Fix Xwayland dma-buf leak: close Zink's emulated GEM handles on KGSL (#275)
Close Zink's emulated GEM handles on KGSL (Xwayland dma-buf leak)

The session preload hands out GEM handles on the KGSL render node by
duplicating the dma-buf descriptor, and released them only through
drmCloseBufferHandle. Zink gives handles back with
drmIoctl(DRM_IOCTL_GEM_CLOSE) instead, which KGSL refuses, so every
duplicated descriptor stayed open and kept its buffer alive.

Xwayland asks for a handle for each buffer it imports through gbm. With
"Force game windows fullscreen" fighting a game's own resolution
(DiRT 3 at 1280x720), the game recreates its swapchain many times a
second and Xwayland piled up ~2,000 old swapchain images (6+ GB) in
seconds until Android killed the session. A preload trace on the device
put 2,090 of 2,118 open dma-buf descriptors in Xwayland on
drmPrimeFDToHandle called from Zink.

Handle GEM_CLOSE on the KGSL node in drmIoctl, and check the three DRM
symbols are exported in CI.

Co-authored-by: The412Banner <[email protected]>
2026-10-05 12:44:16 -04:00
Kurt Himebauch 0d411b4cb6 Add app UI scale presets to Setup (#272) 2026-10-05 11:25:51 -04:00
Kurt Himebauch 2ca051fe71 Fix MangoHud double-counting battery power and remaining life (#270) 2026-10-05 15:07:40 +00:00
Kurt Himebauch 356208ca9e Compact Steam, Desktop, and Setup settings headers (#268) 2026-10-05 10:34:31 -04:00
Kurt Himebauch d3a83befaa Keep the game undimmed while the session menu is open (#267)
Remove background dimming from the session menu
2026-10-05 09:59:35 -04:00
Kurt Himebauch 8cbed6b6da Prepare 0.3.1 release (#263)
* docs: draft 0.3.1 release notes

* chore: bump version to 0.3.1 (11)
2026-10-05 07:46:36 -04:00
MaxsTechReview 06615ffffa Fix controller rings on linked data dirs, route rumble to the controller, capture the mouse for pointer locks (#242)
- #166: export the fake-input ring paths with the spelling the session tree is bound under
  (Context.getFilesDir(), /data/user/0/...) instead of the canonical path. Where /data/user/0
  resolves to /data/data (ColorOS 16) the guest has nothing bound at the canonical prefix, so
  libfakeinput could not open the rings and every pad, physical and on-screen, was dead. The ring
  path logic moves to FakeInputRingPaths so it can be unit-tested without the native library.
- #219: play a game's rumble on the physical controller that last drove the pad, when Android
  exposes its motors (strong and weak separately on Android 12+), and fall back to the device's
  vibrator for the on-screen pad or a controller without rumble. PadBridge records the active
  controller; the on-screen pad taking over moves rumble back to the phone.
- #153: when a program takes a pointer lock (a mouse-look game through gamescope), capture a
  physical mouse and send its raw deltas as relative motion, so the camera no longer stops where
  Android's pointer meets the screen edge. Capture is released for the drawer, the PC keyboard,
  picture in picture, focus loss and pause, and when the lock ends.
2026-10-05 07:33:33 -04:00
Kurt Himebauch cb9038b6a0 Clarify display settings and organize in-session controls (#247)
* Clarify display sizing, game fit and scaling controls

* Replace aspect and resolution controls with one dimension menu

* Organize Steam session settings into tabs

* Keep session resolution out of the live controls panel

* Group live session controls and add a conditional second-screen tab

* Refine session sliders, tab icons and contextual controls

* Simplify screen effects with presets and collapsed advanced controls

* Give in-session tab icons more spacing

* Center the loading cancel button glyph
2026-10-04 23:32:30 -04:00
MaxsTechReview 4c29c6a61e Fetch the Steam client again when it never shows up (#225)
A client stopped part way through updating itself can be left unable to
draw anything. Its files and our install stamp are still on disk, so the
installer never looked at it again and every session sat on "Waiting for
Steam" with nothing retried.

The session now adds a line to package/droiddeck-unconfirmed each time it
starts the client, and the app removes the file on the client's first
frame. After two starts in a row with nothing shown, the installer:
- drops Steam's update manifests, the stamp, .crash and steam.pid
- deletes package zips whose sha1 no longer matches their name
- downloads the client again over the old one

steamapps, config and userdata are left alone. An automatic repair runs
once per failure, until a start is seen again.

The Steam page's Client settings get a "Repair Steam client" item that
queues the same repair for the next start. The request is only cleared
once the download completes, so an interrupted repair is retried.
2026-10-04 21:33:16 -04:00
Kurt Himebauch a554db0fb4 docs: record Thor Steam SD allocation findings (#240) 2026-10-04 20:15:24 -04:00
Kurt Himebauch 4fe5fbab44 Add Decky plugin ZIP import (#79)
* Add Decky plugin ZIP import

* Match Decky plugin identity and loader settings on ZIP import

* Use the shared focus ring for the Decky toggle
2026-10-04 18:09:46 +00:00
MaxsTechReview eb6268f239 Start every title with a Valve pick on the Compatible Proton (#224)
The client runs in Steam Deck mode, where a title's "Default" is not the
"0" mapping but Valve's own pick for it: the Steam Deck compatibility
recommended_runtime (proton-stable, a pinned Proton, or native for the
Steam Linux Runtime) or an app_mappings entry in the SteamPlay manifests.
A title the session had not mapped yet - not installed, or installed
while the client was running on a build without the live helper - showed
"Default (Proton Stable)" and launched with a Proton that cannot run
here.

Every playable Windows title in the appinfo cache with such a pick is now
mapped to the default tool up front, before the client starts and in the
live helper, the same way installed titles are. A runnable tool the user
picks for one of them is kept, and they follow a change of the default.
2026-10-04 13:44:10 -04:00
MaxsTechReview 240d766400 Start fresh installs on droiddeck-esync (#223)
In the Valheim and PEAK runs esync used the least CPU per frame. fsync
stays available, and esync still hands a Proton build without a pack to
droiddeck-fsync until its pack is published. A sync someone already picked
is kept.
2026-10-04 12:55:32 -04:00
MaxsTechReview 27782068a3 ESync, FSync, NTSync + OpenNow + FEX for Desktop Apps (#197)
* Make the Compatible Proton ARM64 the default and label compatible builds

The CompatToolMapping "0" default always points at DroidDeck's Proton
ARM64 tool, and per-game picks of adopted GE/CachyOS builds are kept,
including when the shortcuts writer remaps.

The tool is named after the Proton build it runs, Proton Experimental
ARM64 (Compatible), or Proton 11.0 ARM64 (Compatible) once that newer build
is installed. Adopted builds carry the same Compatible mark, read from a
string resource the app stages into the runtime, and the Proton page
explains which builds have the prefix fixes.

The registrar is renamed steam-compatibility and the tool
droiddeck-proton-arm64. Existing installs migrate on the next session: the
old tool directory is removed, every mapping that named it moves to the new
tool, and the retired bannerlator-steam-compat script is removed from the
runtime. CI and local packaging include the renamed script.

* Drop the duplicate R import left by merging main

* Add userspace ntsync for Proton on Android

Android kernels have no /dev/ntsync and its app policy refuses futex_waitv,
so every Proton 11 build here fell back to wineserver for every wait.
libblsession now serves /dev/ntsync itself: wineserver opens a memfd region,
each sync object is a timerfd that carries its slot, and the kernel ntsync
ioctls are answered in shared memory with futexes, following the kernel
driver's wake order. Valve's Proton Experimental ARM64, GE-Proton and
proton-cachyos use it unchanged.

Locks record their owner so a killed process cannot wedge the others,
signals are held off inside each critical section, dead processes and
exiting threads are swept, and object lifetimes follow close, dup2, dup3,
close_range and received descriptors.

Fast sync is on by default under Performance > Session fixes, and
PROTON_NO_NTSYNC=1 turns it off per game.

* Add droiddeck-esync as the default sync for Proton on Android

Without a kernel ntsync driver, Proton 11 builds here fell back to
wineserver for every wait. droiddeck-esync brings eventfd and shared-memory
sync to the exact Proton build a game runs on: a pack holds that build's
ntdll.so and wineserver with the patch series in tools/droiddeck-esync
applied, and the guest runs Proton from a shadow tree that links the stock
tool and swaps in those two files. A wineserver from a pack still prefers
/dev/ntsync when a kernel driver or droiddeck-ntsync is there.

Packs are built in Valve's SDK image from each release's own wine source
(Proton Experimental ARM64 tags, GE-Proton and proton-cachyos releases); the
control build must reproduce the release binaries byte for byte, and a
signed index lists the packs. The APK bundles the packs pinned in
release.env, the app fetches newer ones, and Steam's compatibility tool
picks the pack for each Proton build, so new Proton releases get packs
without an app update.

The ntdll side reads, writes and polls its eventfds with raw system calls,
so the guest's LD_PRELOAD wrappers stay out of the sync path. The server
abandons a mutex once a thread killed by TerminateThread has really exited
and when its process dies, and a mutex's owner and recursion count change
in one atomic step, so a killed owner cannot touch the next owner's count.

droiddeck-esync is on by default and PROTON_NO_ESYNC=1 turns it off per
game; droiddeck-ntsync, the userspace ntsync, is now an opt-in toggle.

* Watch Valve's Proton releases and build droiddeck-esync packs for them

A scheduled workflow reads the Proton ARM64 builds Steam ships (anonymous
steamcmd app info) and the upstream source tags, compares them with the
previous watch state and starts the pack build for any build that has no
pack yet. The pack build applies the patch series with --fuzz=0 and runs a
smoke test of each new pack against the esstress suite before it is
published.

* Add droiddeck-fsync, keep Steam and the app on one Proton, fix DirectAudio

droiddeck-fsync: libblsession answers futex_waitv(2), which the app
sandbox refuses, in userspace, so Proton's own fsync runs on the stock
Proton files with no pack. Waiters register the words they wait on in a
table beside the fsync shared memory and sleep on a futex of their own;
FUTEX_WAKE on the fsync memory wakes the registered waiters. Slots carry
one owner word that the sweeper and the owner claim with a CAS, and the
mapping table is rebuilt under a lock held across the real munmap, so a
reused address or a recycled slot cannot lose a waiter. It stands in for
a Proton build without a pack (droiddeck-ntsync where fsync is off), takes
over for games with esync off, and the "droiddeck-fsync first" toggle
(BL_FSYNC_FIRST, also per game) puts it ahead of the packs. The games
Proton keeps off fsync and esync never get it. A launch into a prefix
whose wineserver is still running keeps that server's sync choice.

One Proton for Steam and the app: the Components page's Proton is the
default Steam uses, applied live through the client while Steam runs
(droiddeck-steam-compat) and at the next start otherwise; a choice made in
Steam's menu comes back to the app. Requests are ordered by a sequence that
never goes backwards, and the page no longer jumps back to the default
after a swap or restore.

DirectAudio: the wrapper wrote the driver key with its backslashes
stripped, so no game ever used DirectAudio. Wine 11 also loads a builtin
only from the prefix, so the driver is now linked into system32 and
syswow64 before "directaudio,pulse" is selected; mmdevapi falls back to
winepulse whenever DirectAudio cannot start.

* Pick esync, ntsync or fsync from the Steam settings' Games group

A three-way tab row above the FEX preset sets the sync Proton games use. It
writes the existing switches together (droiddeck-ntsync, droiddeck-fsync
first, droiddeck-esync), so the Performance toggles and the tabs always agree,
starts on esync and keeps the choice until it is changed again.

* Make droiddeck-fsync the default sync for Proton games

A fresh install, or one that never picked a sync, starts on fsync; a choice made
in the Steam settings' Games group still sticks. Games Proton keeps off fsync
still fall back to droiddeck-esync.

* Add wineserver to the Wine sync tabs and order them ntsync, fsync, esync, wineserver

wineserver turns every in-process sync off so games wait through wineserver;
picking any other tab turns sync back on. The tabs shrink to fit a narrow pane.

* Shorten the Wine sync description

* Answer openat2 and execveat for FEX in the session preload

FEX opens every file of its x86 rootfs with openat2(RESOLVE_IN_ROOT) and
only falls back to the host path on EXDEV. Under proot on Android openat2
answers ENOSYS, so each lookup of an x86 library silently went to the host
and missed. The exported syscall() now turns openat2 into openat once the
real call reports ENOSYS: RESOLVE_IN_ROOT paths are taken relative to the
root fd (FEX has already resolved the links inside the rootfs), and an
absolute path under RESOLVE_BENEATH fails with EXDEV as the kernel would.

execveat with an absolute path, or relative to AT_FDCWD, and without
AT_EMPTY_PATH or AT_SYMLINK_NOFOLLOW, is plain execve; it is sent as such
because proot refuses execveat with a directory fd, which FEX uses when it
re-executes itself for a child x86 program.

* Run x86 Linux games and programs through FEX

Steam: Linux-only titles that have no ARM64 build (from the client's
appinfo cache: type game, demo, application or beta) are mapped to a new
compatibility tool, droiddeck-fex ("Linux x86 (FEX)", linux to linux,
no required runtime). Valve's own "fex" tool needs pressure-vessel, which
cannot run under proot, so a mapping to it or to a Steam Linux Runtime is
replaced; any other pick the user made stays. Titles that also ship for
Windows stay on Proton. When such a title is installed and FEX or the
sniper runtime is missing, the client is asked to install them.

droiddeck-fex runs the program under the FEX that Steam installs (app
3127680), with Steam Linux Runtime 3.0 (sniper, app 1628350) as the x86
rootfs:
- the sniper platform is linked in place from its usr-mtree (relative
  links only, files/usr -> .), once per runtime version;
- the x86_64 and i386 builds of libblsession and libfakeinput go into the
  runtime's lib/<triplet>/droiddeck and its etc/ld.so.preload, mirroring
  the session's own list, so x86 programs get the pads and the session
  fixes (FEX passes their system calls straight to the kernel, so the
  ARM64 preloads cannot reach them);
- Config.json sets RootFS and the GL and Vulkan thunks, so x86 games draw
  with the device's Turnip and Mesa;
- the FEX preset and the game's own environment from the Steam settings
  apply, and ARM64 entries are dropped from LD_PRELOAD/LD_LIBRARY_PATH.
It also links ~/.steam/sdk64 and sdk32 to the client's x86 steamclient
when the client has one and nothing is there yet.

Desktop: x86_64 and i386 AppImages can be imported now; they unpack
through FEX (or unsquashfs) and record their architecture. Scripts,
AppImages and programs started in a run session use FEX automatically
when they are x86, and each added app has a "Use FEX" Auto/On/Off choice
in its edit dialog. The launchers' "still running" checks also look at
FEX processes, whose exe is FEX rather than the program. A GitHub release
without an ARM64 AppImage falls back to its x86_64 one. Decky's x86
loader starts through droiddeck-fex instead of the fex-mesa rootfs path,
which no runtime ships.

The x86 preloads are built against glibc 2.31 (Debian 11) so they load
in sniper: tools/linuxfs/build-x86-preloads.sh, run in CI and by
build_local.sh. fakeinput's stat hooks fall back to __xstat on a glibc
without a stat export, and mmap64 takes off64_t for the i386 build.

* Keep a FEX unixlib from outliving its DLL

A FEX package or original bundle without the aarch64-unix helpers left
the previous version's libarm64ecfex.so / libwow64fex.so in the Proton,
so the new DLLs loaded a unixlib built for another FEX. A swap now removes
the helpers the package does not carry; FEX then runs DLL-only, which it
already falls back to when Wine cannot load the unixlib.

* Fix the Wine sync choices an audit found

- wineserver tab: it passed Proton's environment through, so a build with
  esync built in (GE, cachyos) still ran esync and a kernel ntsync would
  still be used. It now sets PROTON_NO_ESYNC, PROTON_NO_FSYNC and
  PROTON_NO_NTSYNC (unless the game's own environment sets them).
- ntsync tab: a Proton whose wineserver has no /dev/ntsync client got
  wineserver-only sync although fsync would work. The hash cache records
  whether the wineserver has one; without it the launch goes to
  droiddeck-fsync, and no stand-in or pack turns droiddeck-ntsync on.
- A build with esync built in returned before the per-game fallbacks, so
  a game that turns esync off ran with no in-process sync. It now gets
  droiddeck-fsync, or droiddeck-ntsync where fsync is off too.
- When a pack's proton cannot start and the stock one is used, or the
  selection fails, the prefix's record now says stock, so a second
  launch into the same running prefix no longer picks the pack's ntdll.
- droiddeck-fsync: its mutex is held across fork and re-made in the
  child, a closed or replaced shm fd is forgotten (close, dup2, dup3,
  close_range, closefrom), and a waiter woken while it was leaving on a
  timeout or signal reports the wake instead of losing it.
- BL_SYNC_MIN_NOFILE applies per game to every verb, debug logs carry
  prefixes.json, the device report names the chosen sync instead of the
  three raw switches, and PATCHES.md and the proot notes describe the
  selector, the fsync default and droiddeck-fsync's futex_waitv.

* Name the runtime's scripts and files droiddeck, migrating old installs

The guest scripts are droiddeck-* now (droiddeck-session and the other 19),
as are the files the app and scripts generate (etc/droiddeck, the network
state files, the components dir, the Steam install stamp, the redist marker,
the extra-Proton wrapper and backups) and the compositor's own names
(libdroiddeckwayland, droiddeck_* sources, DROIDDECK_* switches, the
wl_output and seat names).

Installed runtimes carry on: LegacyNames moves etc/bannerlator and
share/bannerlator over and leaves relative links at the old paths before
every proot command, turns any old script into a link to its new name,
moves the generated files and points menu entries at the new scripts.
The session scripts and steam-compatibility move their markers on first
use, the components dir's desired.tsv is rewritten with it, and the
runtime counts as installed under either session script.

Kept: the banner_ahb_v1/banner_desktop_v1 wire names and BANNER_AUDIO_DIRECT
(read by prebuilt Turnip and Wine builds), Banners-Turnip and other
third-party names, BL_* switches, libblsession, and credits to Bannerlator.

* Unpack x86 AppImages without FEX, then ask Steam for FEX

Importing an x86 AppImage failed when FEX was not installed yet ("needs FEX
to unpack and no unsquashfs is installed"). AppImages now unpack with
uruntime (VHSgunzo, MIT, v0.8.1 aarch64, pinned by sha256 and shipped
unmodified with its licence), which reads SquashFS and DwarFS images of any
architecture natively. FEX's own --appimage-extract and unsquashfs stay as
fallbacks, and an ARM64 image whose own runtime cannot unpack it goes the
same way.

An x86 import with FEX missing records that FEX is wanted, so the next
Steam session requests FEX and the Steam Linux Runtime, and the program's
page says FEX is not set up yet and how to get it.

* Run Vulkan Qt Quick AppImages on Wayland

OpenNOW (Qt 6.8, Qt Quick on Vulkan) closed within two seconds in a run
mode. The session sends toolkits to gamescope's Xwayland, and Qt's xcb
backend only presents with Vulkan when Mesa's X11 WSI does, which needs
DRI3; Xwayland on KGSL has none, so Qt found no queue that can present and
aborted ("Failed to create RHI").

An AppImage that bundles Qt Quick, the Vulkan loader and Qt's Wayland
plugin now runs on gamescope's Wayland socket (the desktop's, outside
gamescope), where Turnip presents as it does for games, with Qt's xcb
backend as the fallback. SDL_VIDEODRIVER and XDG_SESSION_TYPE follow, so
helpers that pick their window system from them (OpenNOW's streamer) agree
with Qt. Everything else stays on X11; a qt file in the app's directory
(wayland or xcb) overrides the choice.

* Keep gamescope's WSI layer away from Qt apps on its Wayland socket

OpenNOW still closed at once: its FFmpeg (the Raspberry Pi fork, libavutil
61) creates its Vulkan queues internally synchronized whenever the driver
offers VK_KHR_internally_synchronized_queues, which DD-Turnip does. The
gamescope WSI layer registers every queue of a new device through
vkGetDeviceQueue, which only returns queues created without flags, so it
got VK_NULL_HANDLE and vkroots aborted on assert(obj) before Qt opened a
window.

Programs that draw on gamescope's Wayland socket do not need the layer,
which only carries X11 windows past Xwayland, so it is turned off for
them. OpenNOW bundles no Vulkan loader (it reaches Vulkan through Qt and
dlopen), so a binary that uses QVulkanInstance counts as a Vulkan app too,
and a Qt Quick app left on X11 says why in the session log.

* Keep the end of the session log, and say when FEX is missing

The session sends its output through head -c into session.log. head
writes through stdio, so it held up to 4 KB that proot's --kill-on-exit
threw away when the guest ended: every log stopped mid-line at a 4096-byte
boundary, losing exactly the lines that say why a program quit (an x86
AppImage's "x86 Linux programs need FEX" among them). dd writes each read
as it comes and keeps the same cap.

A run mode always ends with status 0 (gamescope does not pass its child's
on), so a program that could not start just closed. When the log says FEX
is missing, the ended screen now says so and how to get it.

The app reads FEX's readiness from status.json, which only a launch or an
import wrote, so it still said "not set up" after Steam installed FEX.
droiddeck-fex status rewrites it, and a Steam session ends with it while
FEX is wanted. FEX is also looked for at the tool's root and under files/,
and a tool folder without it lists what it holds.

* Let FEX and its runtime install runnable on an SD card library

A Steam library on shared storage loses the executable bits, so native
tools are staged in private directories bound over it; only Proton and
SteamLinuxRuntime folders were. FEX (3127680) installed there could never
run, droiddeck-fex saw no FEX and kept asking Steam for it. FEX and the
sniper runtime (1628350) now get private directories from their first
download, whatever their install folder is called.

* Keep fsync off in the pack smoke test

A runner whose kernel allows futex_waitv started stock Proton with fsync,
so the server-side baseline never said what the test waits for and every
pack was waved through as untestable; the esync run would have measured
fsync too.

* Judge a pack against stock's own esstress failures

Stock Proton fails esstress's dup wait all under server-side sync as well
(a Wine bug, seen on the phone too), and any stock failure made the test
call the runner unusable. A pack now fails only when it crashes, hangs or
fails a check stock passes.

* Ask Steam for FEX and its runtime one at a time

Both steam://install requests were started together, and the running client
took only the first: FEX installed, the sniper runtime FEX needs never did.
Each request now waits for the client to take it before the next.

* Bundle the first published droiddeck-esync packs in the apk

* Log how an x86 program under FEX ends

FEX keeps its own errors quiet unless asked, so a program it could not run
left nothing in the session log. Its log now goes to stderr, and the AppImage
launcher records the program's exit status or the signal that ended it.

* Show the log a failed AppImage program wrote

Programs like Eden stop themselves on a failed check and say why only in
their own log under the home folder. When the program ends badly, the end of
any log it wrote during the run is copied into the session log.

* Report where an x86 program under FEX hit an illegal instruction

A tiny x86_64 preload, built without libc, catches SIGILL and writes the
address, the instruction bytes and the mapping they belong to before the
program dies as it would have. droiddeck-fex preloads it into x86_64 programs.

* Load the fault reporter from the x86 runtime

Programs under FEX look libraries up in the x86 runtime, so the reporter is
copied there with the other preloads and named by its path inside it.

* Hand FEX its x86 runtime and thunks through the environment

Valve's FEX runs portable and reads its config beside itself, so the
Config.json droiddeck-fex writes was not seen: programs ran without the
runtime. The runtime, thunk libraries and thunk list now also go in FEX's
own environment variables, and each launch says what it was given.

* Use the sync pack built for the installed Proton release

Some Proton releases ship byte-identical ntdll.so and wineserver (Valve
11.0-2b/2c, CachyOS 0702/0703), so a pack built for a newer release
matched an older install exactly and the older install never fetched its
own pack. A pack from a newer release no longer counts as an exact match;
among matches the one built for the installed version wins, then rev.

The app ranks the pack a tool already uses the same way and only fetches
a pack that ranks higher, reads the release epoch from tools.tsv and
refetches when tools.tsv changes. A prefix whose server is still running
re-checks its recorded pack on waitforexitandrun and starts fresh when a
better one now applies.

* Keep published sync packs and revocations consistent

The publish step never replaces a pack that has an entry beside it; a
pack left without its entry by an interrupted run is replaced so the
build stops repeating. Discovery counts revocations the signed index does
not show yet, including superseded packs only the flavor releases list,
so the index is signed again. The release watch always builds every
flavor.

* Fetch the droiddeck-esync packs from DroidDeck-Components

The packs, the signed index and the apk bundle now live in the releases
of Droid-Deck/DroidDeck-Components, together with the pipeline that
builds them, so this repository keeps only the app and the guest code
that consumes them. The app's index and asset URLs point there,
release.env names the repository its bundle comes from, and the apk
builds drop bundled packs that the published index revokes.

* Stop the apk build when the published sync index cannot be read

* Ask Steam for one FEX dependency at a time

The client shows a confirmation dialog for each steam://install, and a
second request replaces the first dialog, so FEX was never installed
when the runtime was requested right after it. The next dependency is
now requested once the previous one has an app manifest.

* Use generic names in the AppImage test fixtures

* Give x86-64 apps under FEX the Vulkan thunk

Apps that bring their own glibc and Vulkan loader skipped FEX's thunk
overlay and loaded their bundled desktop Mesa drivers, which crash on the
phone's GPU during Vulkan setup. droiddeck-fex now adds an x86-64 audit
module when a program's bundled glibc and libstdc++ meet the thunk's
version needs. The module sends libvulkan.so.1 lookups to a nodelete stub
that needs FEX's thunk path. That keeps the thunk mapped when an app
unloads Vulkan and loads it again, since FEX keeps its first trampoline
links and the old addresses get reused.

The fault reporter now restores the default action before re-raising,
because FEX ignores SA_RESETHAND and the handler looped.
2026-10-04 12:43:57 -04:00
starwishHi 84bc7d9948 Add Korean,Japanese and Traditional Chinese(Taiwan,Macau and Hongkong)translate (#216) 2026-10-04 12:13:13 -04:00
Kurt Himebauch b2bde1c14d Add opt-in Steam storage diagnostics (#158) 2026-10-04 08:19:48 -04:00
Magarto 32ea22e033 Start .NET and Godot games under FEX without per-game setup (#168) 2026-10-04 08:19:11 -04:00
The412Banner 8502fc517b Effects page in the session menu: scaling modes, screen effects and texture filtering (#198) 2026-10-04 08:17:00 -04:00
Dantalian 986682167e l10n: add Simplified Chinese (#202) 2026-10-04 11:21:24 +00:00
Kurt Himebauch e9204c5e49 Add controller and desktop quality-of-life options (#194) 2026-10-04 00:56:09 -04:00
Kurt Himebauch 6c1681c664 Add Touch Off and compact session menu shortcuts (#196)
Add touch off mode and compact session menu shortcuts
2026-10-04 04:37:56 +00:00
Kurt Himebauch 6b34c57c15 Fix automatic Picture in Picture after session handoffs (#195)
Preserve session readiness for automatic picture in picture
2026-10-04 04:24:25 +00:00
Kurt Himebauch c176a0d6fa Restore 500 ms Back double-press window (#193) 2026-10-04 03:40:10 +00:00
Kurt Himebauch f38def4f4c Remove redundant in-session Desktop button (#188) 2026-10-03 23:00:02 -04:00
MaxsTechReview 8edf05b19a Add the droiddeck-esync pack pipeline (#189)
Builds esync packs for new Valve, GE and CachyOS Proton 11 releases on arm64
runners, smoke-tests them against stock with esstress, and on main publishes
each pack and a signed index as non-latest releases. A watcher checks Steam
and the GE/CachyOS releases every 20 minutes and starts a build for new ones.
The app side lands separately with feat/userspace-esync-ntsync.
2026-10-04 02:58:55 +00:00
Kurt Himebauch 3a8f4889e9 Empty library intro (#182) 2026-10-03 22:27:24 -04:00
Kurt Himebauch bc00bd9919 Fix runtime removal and first-run Steam setup (#185)
* Fix runtime removal races and show removal progress

* Bootstrap Steam from the selected client channel

* Show runtime progress below its Setup row

* Always stage current session helpers and verify APK assets

* Initialize Steam UI scale once using the handheld default
2026-10-03 22:03:26 -04:00
Kurt Himebauch abbbc4a88a Add game file exports and sync for Android frontends (#179) 2026-10-03 21:29:01 -04:00
Magarto e168f7e942 Give protonfixes a readable /proc/sys/fs/file-max (#169) 2026-10-03 15:26:20 -04:00
Kurt Himebauch 59c74e586d Add standard session picture in picture (#156)
* Add session picture in picture controls

* Simplify PiP to standard Android controls

* Remove PiP testing document
2026-10-03 15:06:16 -04:00
Kurt Himebauch 2224c96498 Merge pull request #172 from jmarti326/fix/steam-sleep-resume-black-screen 2026-10-03 14:28:24 -04:00
Kurt Himebauch 39e5ae6341 Merge branch 'main' into fix/steam-sleep-resume-black-screen 2026-10-03 14:18:44 -04:00
Kurt Himebauch ba18b70f59 Merge pull request #159 from Droid-Deck/preview-change-history
Show Preview update history
2026-10-03 13:36:00 -04:00
xXJsonDeruloXx b475a6940a Merge remote-tracking branch 'origin/main' into preview-history-status-card 2026-10-03 13:30:13 -04:00
xXJsonDeruloXx 3f3751804f Move Preview history below the update action 2026-10-03 13:27:09 -04:00
Juan Marti Mercado 9647a38d83 Fix Steam sleep resume handshake 2026-10-03 12:08:40 -04:00
Kurt Himebauch 7a94c75732 Merge pull request #157 from Droid-Deck/game-launcher-shortcuts 2026-10-03 12:04:11 -04:00
xXJsonDeruloXx 3c2064f30c Add Preview update history 2026-10-03 00:06:09 -04:00
xXJsonDeruloXx 37316f4677 Add game launcher shortcuts 2026-10-02 23:49:26 -04:00
Kurt Himebauch abdc46c7a7 Merge pull request #128 from magarto/upscaler-setting
Expose the compositor's upscaler (FSR 1 / GSR / NIS) as a session setting
2026-10-02 23:29:01 -04:00
Kurt Himebauch a8f421c396 Merge branch 'main' into upscaler-setting 2026-10-02 23:25:35 -04:00
Kurt Himebauch c215c045af Merge pull request #155 from Droid-Deck/fix/steam-idle-wake
Fix Steam idle sleep and Android session wake
2026-10-02 21:44:13 -04:00
xXJsonDeruloXx 5a38ff09bf Merge remote-tracking branch 'origin/main' into fix/steam-idle-wake 2026-10-02 21:43:52 -04:00
Kurt Himebauch d24800ae99 Merge pull request #146 from Droid-Deck/fix/deck-controller-discovery 2026-10-02 21:34:08 -04:00
xXJsonDeruloXx a25800149d Restore Steam after idle sleep through session pause and login1 wake 2026-10-02 21:32:12 -04:00
Kurt Himebauch 9fc378579c Merge branch 'main' into fix/deck-controller-discovery 2026-10-02 20:51:17 -04:00
xXJsonDeruloXx 9645032d2d Trim Deck discovery to mount-ID compatibility fix 2026-10-02 20:35:28 -04:00
xXJsonDeruloXx 7cc3e31889 Recover Deck discovery mount IDs on older kernels 2026-10-02 19:08:01 -04:00
Kurt Himebauch 620db5a8ef Merge pull request #151 from Droid-Deck/feat/android-clipboard 2026-10-02 17:47:25 -04:00
xXJsonDeruloXx 41a72ca6a1 Record visible Android copy and Steam paste test on Thor 2026-10-02 17:38:16 -04:00
xXJsonDeruloXx e23cada4ea Add Android clipboard text paste into Steam sessions 2026-10-02 17:29:41 -04:00
Kurt Himebauch 98ab37c638 Merge pull request #150 from Droid-Deck/feat/steam-native-network
Show Android Wi-Fi and cellular networks in Steam BPM
2026-10-02 16:16:46 -04:00
xXJsonDeruloXx f7996275be Merge remote-tracking branch 'origin/main' into feat/steam-native-network 2026-10-02 16:11:04 -04:00
xXJsonDeruloXx 8121a2e07e Add optional Wi-Fi discovery permission flow in Steam settings 2026-10-02 16:05:47 -04:00
xXJsonDeruloXx da6b7993ef Expose Android Wi-Fi names and nearby networks to Steam 2026-10-02 15:39:49 -04:00
xXJsonDeruloXx 2d72507dfc Report Android Wi-Fi and cellular connections to Steam 2026-10-02 15:05:03 -04:00
Kurt Himebauch ed1cdd4905 Merge pull request #147 from Droid-Deck/controller-overlay-toggle 2026-10-02 14:56:02 -04:00
xXJsonDeruloXx 47321773ea Release controls when touch is cancelled 2026-10-02 13:48:19 -04:00
xXJsonDeruloXx cec15326eb Add quick toggle for on-screen controls 2026-10-02 13:28:13 -04:00
xXJsonDeruloXx c6c74fb67d Preserve native HID discovery and trace Deck enumeration 2026-10-02 12:00:12 -04:00
xXJsonDeruloXx f1fb3026e9 Fix Deck controller discovery across sysfs layouts 2026-10-02 10:45:25 -04:00
Magarto 85ed4b952b Merge branch 'main' into upscaler-setting 2026-10-02 15:38:57 +02:00
Kurt Himebauch 40b1163330 Merge pull request #142 from Droid-Deck/xXJSONDeruloXx-patch-1
Update README.md
2026-10-02 01:55:28 -04:00
Kurt Himebauch 488f6aac22 Update README.md 2026-10-02 01:55:15 -04:00
Kurt Himebauch 8a547c9d5c Merge pull request #141 from Droid-Deck/release/0.3.0
Release 0.3.0: version bump, Deck mode for everyone, release notes and media, Updates page fix
2026-10-02 01:39:31 -04:00
xXJSONDeruloXx 41c8bebab0 ci: publish the GitHub release from the Release APKs run, with the notes from docs/releases 2026-10-02 01:34:41 -04:00
xXJSONDeruloXx ab3141e211 discord link and cleanup 2026-10-02 01:32:12 -04:00
xXJSONDeruloXx 71a7955c3c docs: drop the unused overlay GIF and the 'as above' it left behind 2026-10-02 01:16:04 -04:00
xXJSONDeruloXx 4282154b71 docs: in-game GIF for the faster-in-games highlight 2026-10-02 01:15:25 -04:00
xXJSONDeruloXx deb1c4cfd7 docs: GPU drivers shot with DD-Turnip in use 2026-10-02 01:03:16 -04:00
xXJSONDeruloXx 4a027cc68c docs: dual-screen Deck controller and Stop GIFs, Deck mode for everyone in the 0.3.0 notes 2026-10-02 00:59:48 -04:00
xXJSONDeruloXx 2ae8d8fd94 feat: move every install to Deck mode once, so updated installs get it too 2026-10-02 00:59:48 -04:00
xXJSONDeruloXx b7d15061b1 docs: show added apps on the Desktop page in the 0.3.0 notes 2026-10-02 00:49:37 -04:00
xXJSONDeruloXx 614d1cdf54 docs: 0.3.0 release notes with screenshots and GIFs 2026-10-02 00:47:35 -04:00
xXJSONDeruloXx 667a48a6e9 fix: say Can't install, not Update available, for a build Android would refuse 2026-10-02 00:47:35 -04:00
xXJSONDeruloXx da5d7a7825 release: 0.3.0 2026-10-02 00:47:35 -04:00
Kurt Himebauch fbe5c729f2 Merge pull request #140 from Droid-Deck/chore/ui-cleanups
UI cleanup for 0.3.0: motion, focus ring, copy and game environment editor
2026-10-02 00:22:26 -04:00
Kurt Himebauch 7403f457ed Merge branch 'main' into chore/ui-cleanups 2026-10-02 00:18:04 -04:00
xXJSONDeruloXx 1f8da82c19 frontend: draw the Steam wall on the first frame and keep it still
The wall waited about a second and a half for the game list on every start: Steam's own
manifests are quick, but the added games' folder walk and art lookups are not. The last list
is now kept in the app's files (LibraryCache) and shown before the first frame, and the fresh
one replaces it in a single update. The wall's order is fixed (last played, then game ID), so
the same games always land in the same capsules and the refresh moves nothing. The added-games
scan on every resume also leaves the main thread, which it stalled for most of a second.
2026-10-02 00:12:41 -04:00
xXJSONDeruloXx aca6eae55c ui: every way out of a session leaves as a session arrives
Backing out of the loading screen, closing the ended screen and the session ending on its own
used the slide-down card exit. Now, while the session is on screen, the blue spreads out of
the throbber's ball (the middle of the screen once a game is up) until it covers the screen,
and the front end opens on it and draws it back into the button the session came from, as it
already did after the drawer's Stop. A retry, a session closing off screen and animations off
still close at once; a timeout closes it should the flood never run.
2026-10-02 00:05:57 -04:00
xXJSONDeruloXx c89741cb7d i18n: add Spanish
All string resources get a Spanish translation except brand names and strings that are only a
format. The app declares English and Spanish in a locale config, so it is offered in Android's
per-app language setting and follows the system language otherwise. Short rail labels keep
to the rail's width (Configuración, Actualizar).
2026-10-01 23:56:02 -04:00
xXJSONDeruloXx 4b223a10ce ui: the Games page keeps its game while it animates out
It was drawn with the current selection, so leaving it for another rail item turned the
outgoing page into a copy of the new one for a few frames, which then faded out before the
real page rose in: a flash and a dip moving from Games to Steam.
2026-10-01 23:47:50 -04:00
xXJSONDeruloXx ca4ddd74de ui: game environment editor works with a pad, on/off variables use the dropdown
Opened with a pad, the editor and its variable dialog put focus on their first control; as
windows of their own they otherwise gave the pad nothing to move from. On/off variables pick
from their two values in the same dropdown as other choices instead of a switch. App dialogs
keep the page's focus ring out of sight behind them. The footer's Add variable button only
appears once the settings are loaded: drawn disabled first, it came up without its box.
2026-10-01 23:37:41 -04:00
xXJSONDeruloXx c11c6744f5 ui: rebuild the game environment editor in the app's own style
The settings row gets a one-line hint and the same secondary button as every other row. The
editor moves from a stock Material alert, with a paragraph of explanation and a column of text
buttons, into the app's dialog: a profile chip with its menu, the variables in a panel with
each one's value as a chip whose menu changes, removes or restores it, and a footer to add a
variable, reset the profile or finish. Editing a variable uses the same dialog, its name and
value as chips or the app's text fields, and the variable's note in the footer.
2026-10-01 23:24:11 -04:00
xXJSONDeruloXx 3b8faad7af ui: paging the session drawer lands on the page's first option
LB, RB or a tab now start focus at the top of the new page instead of wherever it was last
left on that page; closing a menu or reopening the drawer still returns to where it was. On
Settings the top option is Android apps when DroidDeck is the Home app.
2026-10-01 23:10:01 -04:00
xXJSONDeruloXx b8f412e241 ui: long focus jumps travel as a solid dot and take shape on arrival
The ring fills in as it pinches to a dot, crosses as one round dot on a timed ease with no
stretch, and opens into the control as it gets there instead of settling as a circle first.
2026-10-01 23:02:37 -04:00
xXJSONDeruloXx 7d9010b4e9 ui: focus ring nits - held moves, far jumps, floods, selection rings
- A held direction runs the ring along as one piece and keeps its speed between steps;
  restarting each edge from rest (and the trailing edge's delay) made it stutter.
- A jump of more than 360dp pinches the ring to a drop, carries it over and opens it onto
  the new control instead of stretching a box across the screen.
- The ring keeps out of launch, stop and return floods (RingVeil) and appears on the
  focused control once they are done.
- Leaving a page by moving along the rail no longer pulls focus into the new pane; only
  backing out of a page hands focus to the control it was opened from.
- Selection is a radio dot or a tick, never a ring: the Updates channels and test builds,
  the GPU modes and driver pairs, the Games list and suggested app icons lose their
  persistent rings, and the GPU cards use the sliding ring for focus like everything else.
2026-10-01 22:57:32 -04:00
xXJSONDeruloXx 7435f51d25 ui: the cog floods its settings in as Play floods a session
The cog's tile stretches over the pane on the same loose springs as Play, far edges first and
blobby in flight, while the page behind sinks; the settings rise in once it is covered. Back
draws the tile down into the cog on critically damped springs. The focus ring stays out of
it and appears on the focused control once the motion is done, replacing the ring riding the
edge of a stretching box.
2026-10-01 22:44:32 -04:00
xXJSONDeruloXx af0f64b3de ui: the focus ring carries the page bloom, focus returns to its opener
The bloom is now the opening control's own rounded box stretching out to the pane, not a
circle. With a controller the focus ring rides its edge out to the pane's frame and springs
in onto the page's first control; on Back it rides the box back into the control, which
takes focus again. By touch, with no ring up, the bloom traces the same edge itself.

Closing a page used to leave focus on the page while it animated out, so nothing took it
back afterwards.
2026-10-01 22:37:47 -04:00
xXJSONDeruloXx ed57ce963c ui: bloom session settings out of the cog, open it without blocking
The page grows from the cog as a filled disc with an accent rim and folds back into it on
Back, while the pane under it sinks and returns. Pages also keep drawing while they leave
instead of falling back to the missing-page note for a frame.

Opening the settings no longer scans the added-games folders, reads the driver files and the
storage volumes on the main thread first: the page opens at once and those land while it
animates in.
2026-10-01 22:28:32 -04:00
xXJSONDeruloXx 3b9572d88e ui: move remaining launcher and second-screen copy to string resources 2026-10-01 22:15:45 -04:00
xXJSONDeruloXx 66e3ad812d ui: move content, games, GPU drivers and loading copy to string resources 2026-10-01 22:14:13 -04:00
xXJSONDeruloXx 383c647aaf ui: move store, updates, controller and child-process pages to string resources 2026-10-01 22:12:42 -04:00
xXJSONDeruloXx 1557bab30b ui: move settings, components, drawer and setup copy to string resources, tighten wording 2026-10-01 22:10:22 -04:00
xXJSONDeruloXx 6b45975596 ui: remove controller hints bar, route all motion through Motion
Compose already scales its animation clock by the system animator duration
scale, so Motion.tw no longer multiplies durations a second time; Motion.ms
stays for coroutine delays. The drawer tabs, launch flood and the default-spec
expand/fade transitions now go through Motion, keeping their current speeds.
2026-10-01 22:04:36 -04:00
Kurt Himebauch 8b8b9bb7c5 Merge pull request #138 from Droid-Deck/fix/second-library-steam-installs
fix: keep Steam-installed second-library games out of non-Steam shortcuts
2026-10-01 21:51:05 -04:00
Kurt Himebauch 49c8e84099 Merge branch 'main' into fix/second-library-steam-installs 2026-10-01 21:36:40 -04:00
Kurt Himebauch 963b3e02aa Merge pull request #139 from Droid-Deck/fix/pr-release-publish
fix: find the stable app release and accept fork builds when signing PR test builds
2026-10-01 21:35:02 -04:00
xXJsonDeruloXx d8f91db8f4 ci: keep component releases off latest and pre-release 2026-10-01 21:34:06 -04:00
xXJsonDeruloXx 6b13d8d2a7 fix: check PR test builds against main's versionCode instead of a release 2026-10-01 21:29:01 -04:00
xXJsonDeruloXx a8edc1a2a8 fix: find the stable app release and accept fork builds when signing PR test builds 2026-10-01 21:19:55 -04:00
xXJsonDeruloXx aa1c5bc780 fix: keep Steam-installed second-library games out of non-Steam shortcuts 2026-10-01 20:47:10 -04:00
MaxsTechReview e31801df35 Feature adding custom Linux applications (#130)
* Add your own apps to the Desktop page

An Add tile on Desktop takes a .sh script, an AppImage, a GitHub repo
(newest ARM64 AppImage release) or a Flatpak ID, with an optional name
and icon. Script folders are linked into the session, or copied when
they hold Linux programs (shared storage is noexec); folders outside
internal storage are bound at their own path.

Each app's page opens it under gamescope, edits its name and icon (with
icons suggested from its GitHub repo), checks a GitHub app for a newer
release and updates it in place, or removes it. Replaces the beta
AppImages section.

* Put Add at the end of the installable emulators

Installed emulators and added apps keep the top section, shown once there is one. Add closes the installable grid, or the installed one when nothing is left to install.

* Import DwarFS AppImages and their SVG icons

uruntime images built with sharun keep one program under several names
as hard links, which Android denies apps, so dwarfsextract aborted. The
extraction runs with proot's link2symlink, and each linked name becomes
a file of its own afterwards; their AppDir is followed through the
squashfs-root link.

An image's icon is a PNG of at least 128 px from the name its entry
gives, else its SVG (drawn with AndroidSVG), else the largest PNG, else
.DirIcon.

* Give AppImages the session's pads

sharun images start the app through their own ld.so, patched not to read
/etc/ld.so.preload, and drop LD_PRELOAD unless allowed, so the app never
loaded libfakeinput and saw no controller. The launcher now hands the
preload list over in LD_PRELOAD with SHARUN_ALLOW_LD_PRELOAD set.

Pad defaults also put Player 1 on the controller for yuzu-family
emulators, once, while it is still on the keyboard.

* Redesign the Add and Edit app dialogs

Source and appearance sit in two panels side by side, with a live
preview of the Desktop tile. A disabled primary button keeps an outline
so it still reads as a button.
2026-10-01 18:23:47 -04:00
Kurt Himebauch d506394f87 Merge pull request #113 from maxjivi05/feat/performance-fixes-2 2026-10-01 17:08:17 -04:00
Kurt Himebauch 47345dfd09 Merge branch 'main' into feat/performance-fixes-2 2026-10-01 17:03:19 -04:00
Kurt Himebauch 404dfa19c8 Merge pull request #135 from Droid-Deck/fix/session-focus-overlay 2026-10-01 17:01:14 -04:00
maxjivi05 d09e7eaf32 Merge branch 'main' into feat/performance-fixes-2 2026-10-01 16:40:42 -04:00
xXJsonDeruloXx 31f1c8ac0a Prevent full-screen Android focus highlight in sessions 2026-10-01 16:39:44 -04:00
Kurt Himebauch 7c982a7d65 Merge pull request #134 from Droid-Deck/fix/secondary-library-dependencies 2026-10-01 16:35:02 -04:00
xXJsonDeruloXx 68ce64dea6 Keep secondary-library prefixes and Steam tools on private storage 2026-10-01 16:11:19 -04:00
Magarto 074e4f090b Merge branch 'main' into upscaler-setting 2026-10-01 22:02:07 +02:00
magartus 61f31f502d Add the upscaler to the session drawer, applied live
The drawer's Display page gets the same Upscaler and Upscaler sharpness
choices as the session settings. A pick is saved and handed straight to
the compositor, so it takes effect on the next frame without restarting
the session.
2026-10-01 21:21:52 +02:00
Kurt Himebauch 647ab46659 Merge pull request #131 from Droid-Deck/fix/qam-keeps-game-focused 2026-10-01 15:03:34 -04:00
Kurt Himebauch a5d9e0a943 Merge branch 'main' into fix/qam-keeps-game-focused 2026-10-01 14:56:59 -04:00
xXJsonDeruloXx 33fe6edd0d chore: stage gamescope-3.16.29-p5 2026-10-01 14:53:28 -04:00
xXJsonDeruloXx df7ba1fead fix: skip the pointer warps that jolted the game as the QAM opened and closed 2026-10-01 14:43:30 -04:00
Kurt Himebauch db2f548b3c Merge pull request #119 from Droid-Deck/fix/adopted-storage-runtime-dir 2026-10-01 14:42:26 -04:00
Kurt Himebauch 42aff89ffd Merge branch 'main' into feat/performance-fixes-2 2026-10-01 14:38:33 -04:00
Kurt Himebauch 28bba54aa6 Merge branch 'main' into fix/adopted-storage-runtime-dir 2026-10-01 14:36:18 -04:00
Kurt Himebauch 267e8ec08a Merge pull request #127 from Droid-Deck/fix/mangoapp-drm-listing 2026-10-01 14:35:58 -04:00
Kurt Himebauch 7022c47c85 Merge branch 'main' into fix/adopted-storage-runtime-dir 2026-10-01 14:31:45 -04:00
xXJsonDeruloXx e8c69a89a2 fix: keep the game running behind the Steam menu and QAM, as on a Steam Deck 2026-10-01 14:31:30 -04:00
Kurt Himebauch 5cfed09e3b Merge branch 'main' into fix/mangoapp-drm-listing 2026-10-01 14:31:23 -04:00
MaxsTechReview d8acb25ae8 Merge branch 'main' into feat/performance-fixes-2 2026-10-01 14:03:18 -04:00
xXJsonDeruloXx 2f99f3a50c perf: read GPU mapping sizes from maps and run costly overlay feeds only while it shows 2026-10-01 13:50:10 -04:00
xXJsonDeruloXx a3fafcb10e feat: feed the overlay's fan, GPU memory and Valve msm reader paths 2026-10-01 13:28:26 -04:00
Magarto c3d8395340 Merge branch 'main' into upscaler-setting 2026-10-01 19:10:55 +02:00
Kurt Himebauch 14516bd460 Merge pull request #129 from maxjivi05/fix/lsfg 2026-10-01 13:08:37 -04:00
Magarto f9b112e904 Merge branch 'main' into upscaler-setting 2026-10-01 19:06:00 +02:00
MaxsTechReview d7e5a90682 Merge branch 'main' into fix/lsfg 2026-10-01 13:02:30 -04:00
xXJsonDeruloXx e4a5bf45b9 fix: feed the overlay's GPU stats from every Adreno source other Android overlays read 2026-10-01 12:58:47 -04:00
xXJsonDeruloXx 6f9f265530 fix: give the session a live /proc/stat and read GPU load and clock where other overlays do 2026-10-01 12:53:17 -04:00
Kurt Himebauch 3487535b1a Merge pull request #63 from magarto/stretch-to-panel 2026-10-01 12:45:18 -04:00
Kurt Himebauch ccc3cf1e57 Merge branch 'main' into feat/performance-fixes-2 2026-10-01 12:44:00 -04:00
Kurt Himebauch d659a03467 Merge branch 'main' into stretch-to-panel 2026-10-01 12:41:57 -04:00
xXJsonDeruloXx f274a1ad68 fix: keep the performance overlay running and fed under an enforcing SELinux policy 2026-10-01 12:38:30 -04:00
magartus 19af3bcdd2 Expose the compositor's upscaler as a session setting
The Wayland compositor already carries the X11 renderer's spatial
upscalers (effects_chain.c: FSR 1 EASU+RCAS, Snapdragon GSR and its
quality variant, NIS, RCAS sharpen) behind
WaylandCompositor.nativeSetUpscaler / nativeSetUpscaleSharpness, but
nothing calls them since the Winlator-era drawer was removed, so the
scene is always enlarged to the panel with a plain linear blit.

Add "Upscaler" and "Upscaler sharpness" to the session page's Display
group (off by default, 75%), stored in SessionPrefs and applied next to
nativeSetScaleMode when the session's surface is set up. The choice is
also listed in device.txt.
2026-10-01 18:38:02 +02:00
MaxsTechReview f237fd8a84 Merge branch 'Droid-Deck:main' into fix/lsfg 2026-10-01 12:34:24 -04:00
Kurt Himebauch 6140c5af35 Merge pull request #125 from Droid-Deck/feat/input-evidence-logging 2026-10-01 12:14:15 -04:00
xXJsonDeruloXx 5fe5fa45c7 fix: let the Steam client find the Deck controller under an enforcing SELinux policy 2026-10-01 12:00:24 -04:00
maxjivi05 cd284ca59a Clock frame generation by the game's frames
With frame generation on, a scene drawn for another window (Steam's
overlay) went into the engine as a game frame. Adaptive counted it as
source rate and planned against that (36 base for a game capped at 30),
and a fixed multiplier gave it present slots while game frames were
dropped (26 -> 51 at 2x). Such a change now waits for the game's next
frame.

The HUD's base rate is the game window's own frames, as Steam's overlay
counts them, instead of the compositor's scene rate.
2026-10-01 11:57:58 -04:00
xXJsonDeruloXx 2896e0a945 fix: log an empty focused-app or touch-mode property as none 2026-10-01 10:37:58 -04:00
xXJsonDeruloXx f96e1b2b6e feat: always write the guest pad log to pad.log in the session folder 2026-10-01 10:35:24 -04:00
xXJsonDeruloXx ac40989d40 perf: skip the per-event device lookup when input comes from the same pad 2026-10-01 09:59:13 -04:00
xXJsonDeruloXx 7c0fc1cbfa feat: log controller, touch-mode and pad-ring evidence for input reports 2026-10-01 09:58:51 -04:00
Kurt Himebauch 0d6b544e8d Merge pull request #124 from Droid-Deck/ci/auto-publish-maintainer-prs 2026-10-01 09:19:44 -04:00
xXJsonDeruloXx cbeed44dc5 ci: publish signed test builds automatically for PRs from this repo's branches 2026-10-01 09:15:03 -04:00
Kurt Himebauch aa13ebdb0d Merge pull request #123 from Droid-Deck/fix/deck-pad-touch 2026-10-01 09:02:50 -04:00
xXJsonDeruloXx a53c4a3707 fix: keep touch scrolling the Steam interface when the client finds no controller at start 2026-10-01 08:18:24 -04:00
Kurt Himebauch 445bbcdcbe Merge pull request #122 from Droid-Deck/feat/readable-session-logs 2026-10-01 07:48:03 -04:00
xXJsonDeruloXx fb7c1d2f70 feat: name session log folders by day, number and mode, keep the newest 30, and add Clear logs 2026-10-01 07:43:21 -04:00
Kurt Himebauch dee60a3749 Merge pull request #102 from Droid-Deck/fix/x360-raw-pad-axes 2026-10-01 07:24:19 -04:00
Kurt Himebauch 5790ce7445 Merge branch 'main' into fix/x360-raw-pad-axes 2026-10-01 07:19:45 -04:00
xXJsonDeruloXx 6456127e87 chore: merge main into fix/adopted-storage-runtime-dir 2026-10-01 06:57:26 -04:00
Kurt Himebauch 6ae5940f0d Merge pull request #120 from Droid-Deck/ci/publish-test-waits-for-build
ci: wait for the PR build before publishing a test build instead of failing
2026-10-01 06:57:01 -04:00
xXJsonDeruloXx 9a424aabb7 ci: wait for the PR build before publishing a test build instead of failing 2026-10-01 06:54:05 -04:00
xXJsonDeruloXx 5f1b102e32 fix: give the guest a short XDG_RUNTIME_DIR so gamescope starts with the app on adopted storage 2026-10-01 06:49:03 -04:00
Kurt Himebauch 70b7408f1a Merge branch 'main' into fix/x360-raw-pad-axes 2026-10-01 06:21:24 -04:00
Kurt Himebauch 44c649f6e7 Merge pull request #93 from Droid-Deck/fix/a840v2-linux-turnip 2026-09-30 23:59:27 -04:00
Kurt Himebauch bf34b0c2bd Merge branch 'main' into feat/performance-fixes-2 2026-09-30 23:56:24 -04:00
Kurt Himebauch 8331b7fadf Merge branch 'main' into fix/a840v2-linux-turnip 2026-09-30 23:55:50 -04:00
Kurt Himebauch 3581fb1e9e Merge pull request #117 from Droid-Deck/fix/qam-battery-eta 2026-09-30 23:55:10 -04:00
Kurt Himebauch 684add97aa Merge branch 'main' into fix/qam-battery-eta 2026-09-30 23:50:02 -04:00
Kurt Himebauch 3d4397ce42 Merge branch 'main' into fix/x360-raw-pad-axes 2026-09-30 23:43:27 -04:00
Kurt Himebauch 424e714277 Merge pull request #116 from Droid-Deck/fix/pr-publish-post-release
fix: finish PR test publish after signing
2026-09-30 23:29:37 -04:00
xXJsonDeruloXx ff4f6edf08 fix: show QAM battery life and charge time estimates 2026-09-30 23:27:27 -04:00
xXJsonDeruloXx df730c472a fix: finish PR test publish after signing 2026-09-30 23:25:47 -04:00
Kurt Himebauch 77c35e968c Merge pull request #115 from Droid-Deck/feat/update-catalog
feat: publish and consume update catalog
2026-09-30 23:17:38 -04:00
xXJsonDeruloXx 3b849607f6 fix: allow catalog to inspect active tests 2026-09-30 23:06:09 -04:00
xXJsonDeruloXx 9fe3ce7a86 fix: reflect republished PR test freshness 2026-09-30 23:04:34 -04:00
xXJsonDeruloXx aece4b16bc fix: bypass stale update catalog caches 2026-09-30 22:57:43 -04:00
xXJsonDeruloXx 79f04eb69e docs: call main builds Preview 2026-09-30 22:54:32 -04:00
xXJsonDeruloXx a5f2895a6a feat: publish and consume update catalog 2026-09-30 22:46:44 -04:00
Kurt Himebauch b3a87d3d73 ci: streamline signed PR test builds (#114) 2026-09-30 22:18:08 -04:00
Kurt Himebauch a2105c2c0b Merge branch 'main' into fix/a840v2-linux-turnip 2026-09-30 21:47:17 -04:00
Kurt Himebauch c0597809ec Merge branch 'main' into fix/x360-raw-pad-axes 2026-09-30 21:46:58 -04:00
Kurt Himebauch 184ed90c51 fix: offer updates to release-signed local builds and align the Updates page (#112) 2026-09-30 21:46:45 -04:00
maxjivi05 fc90b80fd8 Add proot fast path for in-process path lookups
Preload library answers stat/open/access/readlink in-process via a
trampoline page proot 0014 lets through seccomp. On by default with a
Performance toggle; also sets GLIBC_TUNABLES rseq=0 and malloc top_pad.
2026-09-30 21:05:10 -04:00
xXJsonDeruloXx 69b5a17e20 Merge main into PR #93 2026-09-30 19:41:59 -04:00
MaxsTechReview 1d5d3acc46 fix: carry the installed Steam build into imported games so Steam sto… (#108)
* fix: carry the installed Steam build into imported games so Steam stops re-verifying them

A second-library game downloaded by WinNative was registered with a bare
manifest (no buildid or InstalledDepots), so Steam treated it as build 0 and
verified all ~92 GB of Final Fantasy XV through proot. Reuse WinNative's
.winnative-steam manifest, or a completed .DepotDownloader/depot.config, so
Steam only fetches what changed since the recorded build.

* fix: give queued Steam fresh installs of imported games the recorded build

Steam rewrites a bare import manifest into a fresh install at build 0
before downloading anything. While nothing is downloaded or staged, the
WinNative build is still safe to carry over, so Steam skips the full
re-download (FF7 Remake was queued for 98 GB this way).

* fix: mount the game storage at /mnt/droiddeck-sd

The second library was bound at /mnt/bannerlator-sd, a name left from the
Bannerlator port, which is what Steam shows in its paths and errors. Bind
it at /mnt/droiddeck-sd, keep the old path as an alias for links and
prefixes that still name it, move Steam's library entry in place, re-point
import links, read ownership snapshots taken under the old path, and key
added-game shortcut ids by the old path so their prefixes stay put.

* fix: restart Steam once when owned added games are confirmed

On a fresh install the importer runs before any ownership snapshot exists,
so every added game became a non-Steam shortcut and stayed one until the
client happened to restart. Once the watcher confirms owned titles that are
not imported, it asks the session for one restart, while no game runs and
no download is in progress, and the importer turns them into Steam entries.
2026-09-30 19:34:15 -04:00
Kurt Himebauch 771dca7048 Preserve added game launch options (#111) 2026-09-30 19:08:57 -04:00
Kurt HimebauchandMaxsTechReview 7e6df11344 feat: pick GPU drivers as matched pairs, set automatically for the device's GPU (#103)
* feat: pick GPU drivers as matched pairs, set automatically for the device's GPU

* DD-Turnip Driver Support (#106)

---------

Co-authored-by: MaxsTechReview <[email protected]>
2026-09-30 16:57:49 -04:00
MaxsTechReview 5251c6a5f6 Additional performance fixes (#87)
* perf: answer Turnip's zero-timeout KGSL timestamp polls without blocking

Turnip polls a timeline point with IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID
and a timeout of 0, which KGSL reads as wait forever; Mesa does it under
a lock on every signalling submit, so vkd3d-proton, DXVK, gamescope and
Zink ran in lock-step with the GPU. The session preload now answers
those polls from the context's retired timestamp and passes everything
else to the kernel. Bannerlator measured a D3D12 demo at 614 -> 4335 fps
with the same answer in its Vulkan adapter. BL_NO_KGSL_POLL_FIX=1 turns
it off.

* perf: frame limiter, live panel refresh, opt-in GPU clock pin, cheaper cursor sync

- A per-mode frame limit (Steam and desktop settings, off by default):
  gamescope's -r, the compositor's buffer-release pacer, the display
  layer's frame-rate vote and a panel mode the cap divides evenly, as
  WinNative's per-shortcut limit does. BL_FPS was always 0 and the
  pacer's limit was never set.
- The compositor follows the panel's refresh rate when it changes
  mid-session instead of reporting the rate read at start. (WinNative.)
- Performance > GPU: hold the Adreno clock at its top through KGSL's
  power-control property, no root (Bannerlator's PerfGpuTurbo). Off by
  default; cleared when the session stops and at the next app start if
  a killed process left it set.
- The cursor image is copied across JNI only when its serial changes,
  not on every pointer move.

* perf: stop re-seeding every prefix every 5 s, and run the library helpers only on change

- bannerlator-seed-redists stamped its change check from a block that
  carried the current time, so it never matched: every prefix had ~32
  keys appended to system.reg on every beat. The stamp now comes from
  the names; Proton's prefix template is seeded too (date kept), with
  WinNative's built-in list until the client has the install scripts.
- The library beat runs the seeder every 15 s and steam-compat only
  when a directory it reads has changed, or once a minute.
- The client-core pin reads /proc itself and runs taskset -a only for
  new processes or ones that grew threads.
- The games watcher backs off after a failed ownership probe and does
  not probe while a game runs.
- The Proton launcher runs the newest ARM64 depot by build number, and
  PROTON_USE_PIPEWIRE defaults to 0. (WinNative.)

* perf: Mesa's database shader cache, and proot's tracer above the guest

- MESA_DISK_CACHE_DATABASE=1 for the session: the file cache opens a
  file per lookup and opens and renames one per store, each a proot
  stop; the database is opened once and used with pread/pwrite.
- proot's single tracer thread is raised to nice -6 (under the
  compositor's -8) two seconds after it starts, so the first guest
  process it forks keeps nice 0. setpriority() directly, not
  Process.setThreadPriority, which would also move its cgroup.
2026-09-30 16:43:52 -04:00
MaxsTechReview b20f77f988 Added LSFG/Import/License Checking (#96)
* feat(lsfg): take Lossless Scaling from Steam by itself, import it once owned, rebuild on update

- Lossless.kt owns the DLL and its shader cache. The copy the Steam
  client installs (runtime library or the card's) is taken by itself
  and taken again when the client updates it (size/mtime stamp). The
  DLL is copied in and built into a cache named after its SHA-256, so
  a new DLL hands the compositor a new path; a build that fails keeps
  the working cache. Builds are serialized.
- The Steam session asks the signed-in client once per session whether
  the account owns app 993090 until the answer is yes, then records it
  and the app stops asking (BL_LOSSLESS_OWNED). Once owned, the frame
  generation menu offers "Import Lossless.dll…", on the main screen and
  in the session drawer; an import stays until Steam's copy changes.
- The compositor restarts the LSFG engine when its cache path changes,
  and a new engine selection clears an earlier start failure, so an
  updated DLL or a retry no longer needs the app restarted.
- The HUD says when LSFG is waiting on Lossless Scaling instead of
  "starting"; frame generation strings are resources.
- Removed LsfgNative's unused DLL variant and cache-match calls.

* Find Lossless.dll in added Games libraries too

* LSFG: 120, 90 and 60 fps targets alongside 2x-4x

The pacer's target-rate mode picks 0-3 generations per game frame. While it
is warm the input ring and shared chain follow every frame, so frames that
generate nothing don't leave the next one without history.

* Credit LSFG: Camille LaVey / Eden, ported to WinNative and DroidDeck by @maxjivi05

* LSFG: aim 0.1 fps under the target so output never passes the refresh rate
2026-09-30 16:40:46 -04:00
Kurt Himebauch bd5e9f4458 Merge branch 'main' into fix/a840v2-linux-turnip 2026-09-30 16:28:51 -04:00
Kurt Himebauch a690771d57 Merge branch 'main' into fix/x360-raw-pad-axes 2026-09-30 16:27:45 -04:00
Kurt Himebauch aac7e15f2b Feat/step out stop confirm (#105) 2026-09-30 16:26:39 -04:00
Kurt Himebauch a303e5c8a6 docs: add a join-the-Discord badge and community section to the README (#104) 2026-09-30 14:54:35 -04:00
xXJsonDeruloXx 98f5da262b fix: give games the Xbox 360 axis layout when Steam Input is off 2026-09-30 13:36:14 -04:00
Kurt Himebauch a81363d743 feat: turn Deck mode on for new installs and add a performance overlay toggle (#101)
* feat: turn Deck mode on for new installs and add a performance overlay toggle

* feat: hide the session fps HUD where Deck mode's performance overlay runs
2026-09-30 13:06:12 -04:00
Magarto 0041892a4b Merge branch 'main' into stretch-to-panel 2026-09-30 18:52:06 +02:00
Kurt Himebauch 11a8221623 fix: keep Deck mode on the Steam Deck client branch so Steam stops offering an update (#100) 2026-09-30 12:47:57 -04:00
Kurt Himebauch 6689f809e2 fix: stop update channel cards flashing as a controller moves between them (#99) 2026-09-30 11:45:44 -04:00
Kurt Himebauch 62e75e23ed feat: add an Updates page that installs Stable, Nightly or test builds in-app (#98)
* feat: add an Updates page that installs Stable, Nightly or test builds in-app

* style: put update channels on the left and the build on one line

* style: make the update card read like a system updater

* feat: expand update notes, show the build beside the title, fit with button hints

* style: check for updates with the refresh icon Components uses
2026-09-30 11:25:18 -04:00
Kurt Himebauch faf8ce97ba ci: sign PR test builds in every variant and take stable from the org repo (#97) 2026-09-30 10:53:26 -04:00
Kurt Himebauch 50890336c0 Merge pull request #94 from Droid-Deck/fix/remove-pr-approval-gates
ci: remove PR approval gates from signed releases
2026-09-30 09:19:58 -04:00
xXJsonDeruloXx 043547c7ee ci: remove PR approval gates from signed releases 2026-09-30 09:14:30 -04:00
xXJsonDeruloXx be86fd953a fix: report Adreno 840v2 as the A840 the runtime's Turnip knows 2026-09-30 07:53:12 -04:00
Magarto db8928f7ce Merge branch 'main' into stretch-to-panel 2026-09-30 09:30:37 +02:00
Kurt HimebauchandMaxsTechReview 4a234b2824 feat: Flatpak apps and a Flathub store (#77)
* feat: run Flatpak apps through a proot bwrap stand-in and add a Flathub store

* feat: give Flatpak apps the KGSL GPU, the controllers and a CPU LLVM can target

* feat: Flatpak games on the desktop, Store apps on the Desktop page, safer remove

* test: cover the sandbox planner and store helpers; docs: explain Flatpak on DroidDeck

* fix: keep controller focus through Store tabs and app pages; show progress per Flatpak step

* fix: let Chromium, Electron and Firefox Flatpaks start without their own sandboxes

* fix: fetch Flatpak's supporting libraries when the runtime lacks them

* fix: add Flathub without a download, log store commands, keep Electron apps on X11 under gamescope

* feat: import ARM64 AppImages from storage and run them from the Desktop page

* fix: report failed Flatpak installs as failures and deploy runtimes that ship a resolv.conf link

* fix: let Flatpak create its repo where Android denies hard links

* fix: point store commands at the current network's DNS

* fix: keep failed Flatpak extensions non-fatal, give Electron AppImages a bus, X11 and time to relaunch

* docs: AppImage import and what x86 apps would take

* feat: put the Flathub Store and AppImage import behind beta toggles in Setup

---------

Co-authored-by: MaxsTechReview <[email protected]>
2026-09-30 00:35:49 -04:00
The412Banner 970fc3a946 ci: say what each DroidDeck-CI build is at the top of its release notes (#92) 2026-09-30 00:11:55 -04:00
The412Banner d25c94471d Merge pull request #91 from Droid-Deck/feat/game-saves
Game saves: import and export save zips for added games
2026-09-29 23:49:50 -04:00
The412Banner 2805c94f9f Game saves: show Manage saves only on added games 2026-09-29 23:36:49 -04:00
The412Banner f22b8253fd Game saves: Manage saves on each game's page instead of a separate page
The floppy disk beside the Steam cog and its Game saves page are gone. A game's
page now has Manage saves beside Proton prefix, a menu with Import saves (a
GameHub or Winlator zip, picked in the app's file picker; the saves there now are
backed up first), Export as GameHub zip, Export as Winlator zip (a folder picked
the same way) and Open save folder. The game's saves are looked for when the menu
opens. The Proton list and config.vdf reader only that page needed are removed.
2026-09-29 23:21:40 -04:00
The412Banner 174321c0ec Game saves: import and export save zips per game
A floppy disk beside the Steam page's cog opens a Game saves page, laid out like
Components: the Proton at the top (from the client's compatibility tools, with
bannerlator-proton-arm64 shown as Proton Experimental), the zip type beside it
(GameHub or Winlator, LB / RB turn it), and the games the client runs with that
Proton (CompatToolMapping, its default for the rest), each with Import and Export.

Import picks a zip in the app's file picker, backs up the game's steamuser profile
to Download/DroidDeck/Saves/backups, then unzips drive_c with any user folder but
Public renamed to steamuser, so GameHub and Winlator zips both land. Export finds
the save folders by name under the usual roots (Bannerlator's SaveLocator), or
takes the whole profile, and writes <Game>_<date>.zip to a folder picked the same
way, under steamuser (GameHub) or xuser (Winlator). Imports wait while a session
runs. Ported from Bannerlator's GameSaveBackup and SaveLocator.
2026-09-29 23:01:21 -04:00
The412Banner 14de9a734c Merge pull request #89 from Droid-Deck/fix/resume-restore-minimized
Restore a game that minimized itself behind the Steam menu
2026-09-29 22:14:53 -04:00
The412Banner 42e6a45051 Merge branch 'main' into fix/resume-restore-minimized 2026-09-29 22:10:04 -04:00
Kurt Himebauch 8fbcf11fc8 feat: the pad as SteamOS has it - Steam Input's virtual pads for games, a Steam Deck controller for the client (#86)
* feat: stand in for /dev/uinput so Steam Input's virtual pads reach games

* feat: present the pad to the Steam client as a Steam Deck controller over hidraw

* docs: log the uinput stand-in and the Steam Deck controller

* feat: feed the handheld's gyro and accelerometer to the Steam Deck controller

* feat: let the Steam page choose between the Steam Deck controller and the Xbox 360 pad

* feat: Deck back grips and trackpads on the second screen

* fix: serialize ring teardown with its writers, fall back to the Xbox pad when the Deck cannot be set up, and read motion only for a Deck

* fix: keep the second screen's choice through sleep and bring it back on wake
2026-09-29 21:57:52 -04:00
The412Banner d1dcf2974c gamescope-3.16.29-p4: restore a game minimized behind the Steam menu (patch 0112) 2026-09-29 21:56:42 -04:00
The412Banner f50d775912 Restore a game that minimized itself behind the Steam menu
The Steam menu is an overlay that takes input without changing gamescope's focus
window. A fullscreen wine game minimizes itself when it loses input, and gamescope
only takes a window out of iconic when the focus window changes, so after Resume
the game stayed minimized: a black screen with its caption in the top-left corner
(Titanfall 2 on GE-Proton 11, proven on device).

gamescope patch 0112 remembers the iconify request and puts the window back to
NormalState before input returns to it, then hands focus over again so wine
restores and activates it. The session script also asks gamescope to restore the
game once input comes back from the client (GAMESCOPE_RESTORE_FOCUS_WINDOW);
BL_RESTORE_ON_RESUME=0 turns that off.
2026-09-29 21:52:34 -04:00
Kurt Himebauch 7d1e1707be perf: faster local build, skip xalia and gamescope realtime by default, mic off by default, show the saved Deck mode (#85)
* perf: cache pinned downloads, skip unchanged proot rebuilds, and wait out a Docker Desktop restart in the local build

* perf: skip xalia and gamescope realtime queues by default, add a realtime toggle, default the mic off

* fix: show the saved Steam Deck mode on the Steam page before Performance has been opened
2026-09-29 20:00:10 -04:00
Kurt Himebauch 026c1c32e5 Merge pull request #84 from maxjivi05/fix/performance-misc 2026-09-29 19:41:38 -04:00
maxjivi05 72e4147449 perf(gamescope): ask X for the pointer only while a cursor is drawn
The gamepad-cursor port queried the X pointer on every vblank, a
blocking round trip to Xwayland on the paint thread. With no cursor
image the position comes from wlserver as upstream; while the cursor is
hidden for inactivity X is asked every 50 ms. The realtime-queue comment
now says it is a no-op on KGSL Turnip.
2026-09-29 18:44:32 -04:00
maxjivi05 2fd209be83 perf: poll without starting processes, and stop rewriting unchanged Steam files
The session's watchers slept with sleep(1) and read files with cat, an
exec each under proot; they now wait in bash itself. steam-compat
writes its tool, the adopted wrappers and config.vdf only when they
change, the games watcher probes ownership when its matches change or
every five minutes instead of every 30 s, and Proton no longer inherits
the client's steamrtarm64 library path.
2026-09-29 18:44:32 -04:00
maxjivi05 1066ff5730 perf: default to Turnip sysmem, compact Zink descriptors and the Performance + TSO FEX preset
WinNative's defaults for every Linux session. Choices already saved by
a user are kept.
2026-09-29 18:44:32 -04:00
maxjivi05 62bb7a1e80 perf: pace buffer releases to the screen, read the requested refresh, drop sustained mode and ADPF
A replaced buffer went straight back to a game with no frame limit, so a
MAILBOX/IMMEDIATE swapchain drew frames that were never shown; it now
comes back once per refresh as in WinNative. gamescope's -r and the
compositor read the rate of the mode the activity asked for rather than
the one the panel was still in, and the zero-copy layer votes it.
Sustained performance mode, which caps clocks on most power HALs, and
the ADPF session, which reported frame intervals as work, are gone.
2026-09-29 18:44:32 -04:00
maxjivi05 cc37cc8a4d perf: stop proot's fake_id0 and ioctl traps from stopping every stat, sendmsg and GPU ioctl
fake_id0 (loaded by -i for Xwayland's set*id) now traces only the set*id
family and the xattr fixups when the ids given are proot's own, and the
seccomp filter traces ioctl only for the termios and FICLONE requests
proot rewrites. The faccessat2 and statx exit stops are dropped on
kernels that have the calls.
2026-09-29 18:44:32 -04:00
Magarto 92d04d6fa9 Merge branch 'main' into stretch-to-panel 2026-09-29 22:00:43 +02:00
Kurt Himebauch 33460f7342 Merge pull request #81 from Droid-Deck/fix/proot-kompat-utsname-only
perf: stop proot's kompat from trapping hot syscalls, and stop respawning a crashing mangoapp
2026-09-29 15:38:52 -04:00
xXJsonDeruloXx 23b2ffde14 fix: stop gamescope respawning a mangoapp that keeps dying at start 2026-09-29 15:08:28 -04:00
xXJsonDeruloXx 4f14d396bc perf: stop proot's kompat from trapping futex/epoll/fcntl and stripping the vDSO when only the hostname changes 2026-09-29 14:39:53 -04:00
Magarto 9d36f1a73b Merge branch 'main' into stretch-to-panel 2026-09-29 17:49:28 +02:00
Kurt Himebauch b1bb767c65 Merge pull request #78 from Droid-Deck/fix/ci-publish-retention
Fix DroidDeck-CI main build publishing
2026-09-29 10:48:38 -04:00
xXJsonDeruloXx 156e152ffc ci: keep main DroidDeck-CI releases published 2026-09-29 10:46:50 -04:00
Kurt Himebauch d30e34eec1 Merge pull request #55 from Droid-Deck/fix/steamdeck-performance-overlay
Deck mode: working Steam performance overlay (MangoHud)
2026-09-29 09:41:51 -04:00
xXJsonDeruloXx 69a140da28 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/MainActivity.kt
2026-09-29 09:41:11 -04:00
Kurt Himebauch 0ee8f57524 fix: use Android's 300 ms double-tap window for Back so the single-press action comes sooner (#73) 2026-09-29 02:10:51 -04:00
Kurt Himebauch 45f0969c5c fix: send wl_keyboard modifiers so Shift and Caps Lock produce capitals (#71) 2026-09-29 02:10:01 -04:00
Kurt Himebauch 7a0e087bf9 fix: give the QAM chord a Guide lead that stretches with slow frames so A is not taken as a press (#70)
* fix: give the QAM chord a Guide lead that stretches with slow frames so A is not taken as a press

* fix: start the QAM Guide lead at 80 ms and stretch it only when frames are slow

* fix: set the QAM Guide lead minimum to 80 ms

* fix: use Android's 300 ms double-tap window for Back so the single-press action comes sooner

* fix: keep the 500 ms Back double-press window
2026-09-29 02:01:21 -04:00
Kurt Himebauch 9318cd2a59 feat: flood out of Stop and back into the launch button on quit (#72)
* feat: flood out of Stop and back into the launch button on quit

* refactor: drop the stop flood's warble to match the launch
2026-09-29 01:53:13 -04:00
Kurt Himebauch e7d889e443 perf: cache the Steam client's repeated path lookups to cut startup from 31s to 6s (#69) 2026-09-28 23:18:48 -04:00
Kurt Himebauch 6840b9139f fix: let the Steam client shut down cleanly when a session stops (#68) 2026-09-28 22:12:19 -04:00
Kurt Himebauch 00e8c285ba feat: pair wireless debugging from a notification for the child-process gate (#67)
* feat: pair wireless debugging from a notification for the child-process gate

* fix: pair over loopback, handle Android 12 and 14 child-process routes, use D logo for notifications

* fix: read the adb tls port directly before falling back to mdns

* fix: end pairing on the same notification so progress never lingers
2026-09-28 21:37:10 -04:00
MaxsTechReviewandMaxsTechReview d647879299 Steam Owned Games Importing (#62)
* Add ownership-aware Steam game folder imports

* Fix Steam title lookup and installed game manifests

---------

Co-authored-by: MaxsTechReview <[email protected]>
2026-09-28 18:13:08 -04:00
Magarto 3061c5528c Merge branch 'main' into stretch-to-panel 2026-09-28 23:13:01 +02:00
Kurt Himebauch dd5bdcb3a5 Merge pull request #61 from magarto/guest-hostname-setting 2026-09-28 17:07:59 -04:00
magartus 93db74642c Add an opt-in 16:9 stretch for screens narrower than 16:9
On a foldable's inner panel (Galaxy Z Fold8: 2448x1848, ~4:3) gamescope
is started with the nested size equal to the output, so games are asked
for the panel's own shape. Many cannot do it and letterbox themselves,
and gamescope's scaler has nothing to stretch. "Stretch games to fill
the screen" only forces window size, so it does not help.

New Steam setting "Stretch 16:9 games to a narrower screen" (off by
default): bannerlator-session then gives games a 16:9 surface at the full
output height (-w) and adds -S stretch, so they render at a shape they
support and fill the panel with no bars and no crop. An output that is
already 16:9 or wider is left untouched. Passed as
BL_GAMESCOPE_STRETCH_16X9 and listed in device.txt.
2026-09-28 23:03:10 +02:00
magartus f823209ac5 Let the user choose the guest host name
0.2.0 hardcodes the proot utsname nodename to "DroidDeck". Steam sends
it as the machine name, so an install updated from an earlier build
(which reported Android's "localhost") shows up in Steam under a new
name, with the same machine-id and login. /etc/hostname cannot fix it
from inside: proot's -k override wins.

Add a "Host name" field (Performance > Session identity), stored in
SessionPrefs and passed to guestUtsname(). Only a valid RFC 1123 label
is saved; blank or invalid falls back to the old default, "DroidDeck",
so nothing changes for anyone who leaves it alone. The value is also
listed in device.txt.
2026-09-28 22:58:54 +02:00
Kurt Himebauch 47d8dc21d1 Merge branch 'main' into fix/steamdeck-performance-overlay 2026-09-28 16:36:33 -04:00
MaxsTechReviewandMaxsTechReview ae4f6727e0 Fix/variables (#60)
* Add editable per-game environment settings below FEX presets

* Add typed preset variable pickers and default D3D12 capability levels

---------

Co-authored-by: MaxsTechReview <[email protected]>
2026-09-28 15:17:57 -04:00
Kurt Himebauch 1ffc23623d Merge pull request #59 from Droid-Deck/ci/discord-build-announce
ci: publish each signed main build to DroidDeck-CI and post it to Discord
2026-09-28 14:54:29 -04:00
xXJSONDeruloXx 5e5ac79517 ci: publish to DroidDeck-CI with the org's CI app, not a personal token
DROIDDECK_CI_TOKEN never existed, so PR Release Publish skipped its
upload, and a personal token would expire and belong to one person. Both
workflows now mint a token of the org's DroidDeck CI Publisher app,
installed on DroidDeck-CI only with Contents: write (its ID in the
DROIDDECK_CI_APP_ID variable, its key in DROIDDECK_CI_APP_KEY). No
fallback: a main build without them fails its announce job.
2026-09-28 14:51:13 -04:00
xXJSONDeruloXx f4afc0ecc3 ci: publish each signed main build to DroidDeck-CI for the Discord post
Run artifacts need a GitHub sign-in to download. Each signed main build
is now also a prerelease main-<sha> of the public DroidDeck-CI (all
variants and their SHA-256s; the last 10 kept, PR releases untouched),
and the Discord post links its standard apk and the release. Without
DROIDDECK_CI_TOKEN the post links the run's artifacts as before.
2026-09-28 14:49:43 -04:00
MaxsTechReview 07c6a85ada Merge pull request #53 from maxjivi05/fix/adaptive-joysticks
Hide idle adaptive sticks and activate them at touch positions
2026-09-28 14:48:26 -04:00
MaxsTechReview 69ffc682e4 Merge branch 'main' into fix/adaptive-joysticks 2026-09-28 14:48:14 -04:00
xXJSONDeruloXx 3decdbd9e7 ci: post each signed main build to Discord
A job after the signing of a main build posts to the Discord's builds
channel: the merged PR (or the commit), and a link to the run's signed
apks. The channel's webhook URL is the DISCORD_BUILDS_WEBHOOK secret;
without it the job posts nothing and passes.
2026-09-28 14:46:41 -04:00
Kurt Himebauch 15fc9be3b0 Merge pull request #57 from Droid-Deck/fix/desktop-hardware-cursor
Desktop: hand labwc's cursor to the app, so full-screen games scan out
2026-09-28 14:40:02 -04:00
MaxsTechReview 0f75c0f3c2 Expand adaptive joystick activation area by fifty percent 2026-09-28 14:39:55 -04:00
Kurt Himebauch 8caf6d038c Merge branch 'main' into fix/desktop-hardware-cursor 2026-09-28 14:39:52 -04:00
Kurt Himebauch 7b7b4a9e3e Merge pull request #58 from Droid-Deck/tools/local-release-signing
build_local: sign with DroidDeck's key when a local .signing.env has it
2026-09-28 14:39:22 -04:00
xXJSONDeruloXx 080f72f7a0 build_local: sign with DroidDeck's key when a local .signing.env has it
A local build was signed with the testkey alone, which a release refuses
as an update ("signatures do not match"): testing on a device with a CI
build meant uninstalling it and losing its data first.

With an untracked .signing.env (RELEASE_KEYSTORE, RELEASE_STORE_PASSWORD,
RELEASE_KEY_ALIAS) in the checkout, or in the main checkout for a
worktree, build_local.sh finishes with tools/release/sign-apk.sh as CI's
main builds do, and the apk installs over a release. Without the file
nothing changes. DROIDDECK_SIGNING_ENV points at the file explicitly.
2026-09-28 14:33:15 -04:00
xXJSONDeruloXx f87289f75b Desktop: hand labwc's cursor to the app, so full-screen games scan out
The desktop forced a software cursor (WLR_NO_HARDWARE_CURSORS=1) because
labwc's cursor never showed when handed to the app's compositor. With a
software cursor wlroots never scans out: every frame of a full-screen game
was composited by labwc first ("Direct scan-out disabled by software
cursor", 2284 times in one Adreno 825 report).

Why the cursor never showed: wlroots' Wayland backend attaches and commits
its cursor surface before wl_pointer.set_cursor gives it the cursor role,
and sets it again with nothing new committed each time the pointer enters.
The compositor released a role-less surface's buffer on commit, so the
cursor surface had no image by the time it became the cursor.

- A role-less surface's last buffer (cursor-sized only) is held until the
  next commit; set_cursor publishes it, and a repeat set_cursor of the
  surface already shown unhides its last image.
- A dma-buf cursor is read back after the client's implicit render fence,
  as the zero-copy path waits for it.
- The desktop no longer forces a software cursor.

Device-tested on the AYN Thor (Adreno 740, 1920x1080): labwc's own cursor
is drawn, with its shapes (I-beam over a terminal); wlroots logs "Direct
scan-out enabled" for a full-screen glxgears. At the same 60 fps on screen
the GPU is 3.0 % busy against 4.6 % with the software cursor, both at
550 MHz.
2026-09-28 14:33:08 -04:00
MaxsTechReview f418b788bc Limit adaptive stick activation near saved positions 2026-09-28 14:21:34 -04:00
MaxsTechReview b1ac0a7820 Merge branch 'main' into fix/adaptive-joysticks 2026-09-28 12:53:44 -04:00
Kurt Himebauch 6d8c84b3b0 Merge pull request #56 from Droid-Deck/ci/sign-all-variants
ci: sign every package variant on main
2026-09-28 12:46:40 -04:00
xXJSONDeruloXx a9ae338bfd ci: sign every package variant on main
main's build signed only the standard package, so the pubg, antutu and
ludashi variants existed only after a manual Release APKs run. The build job
now reads the variant list from tools/release/variants.txt, as release.yml
does, and main's signing job signs all of them.
2026-09-28 12:42:56 -04:00
xXJSONDeruloXx 5128bbd453 mangoapp: stat sources for more Snapdragon generations
KGSL's files and thermal zone names differ between Snapdragon generations.
Each value now takes the first source that exists, in the order GameNative
and the Winlator forks read them: GPU load from gpu_busy_percentage or
devfreq/gpu_load, GPU clock from devfreq/cur_freq or gpuclk, GPU temperature
from KGSL's temp, devfreq/temp or a zone whose sensor name has "gpu". CPU
zones are any whose name has "cpu" and not "gpu" (cpuss-0, cpu0-silver-usr,
apc1-cpu0-usr, ...), matched without regard to case.
2026-09-28 12:28:45 -04:00
xXJSONDeruloXx 458de0acab mangoapp: GPU memory from KGSL
mangoapp's VRAM figure on a Qualcomm driver comes only from the gpu_mem_total
trace event. The tracefs stand-in's event list now declares that event as
Android kernels do, and reading the stand-in's events answers with one
record carrying the total KGSL reports in /sys/class/kgsl/kgsl/page_alloc,
which every Adreno under Android has and the app can read.
2026-09-28 12:28:45 -04:00
xXJSONDeruloXx 6d26eb8f70 mangoapp: show Adreno GPU and CPU stats
Valve's mangoapp reads an Adreno GPU's load, clock and temperatures from the
paths of its own hardware, and the CPU temperature from thermal zones named as
mainline kernels name them. Every Adreno under Android keeps the GPU values in
KGSL's sysfs, readable by the app, and Android kernels name the CPU sensors
cpuss-N / cpu-N-M; the session binds each where mangoapp looks. Thermal zones
are found by sensor name, since their numbering differs per device, and
anything that cannot be found is left alone.
2026-09-28 12:19:01 -04:00
xXJSONDeruloXx 016558ca0b mangoapp: run the performance overlay under Android
Three things kept Valve's mangoapp from drawing the overlay in a session:

- GLFW 3.4 takes XDG_SESSION_TYPE=wayland with WAYLAND_DISPLAY set as
  Wayland-only, and mangoapp needs gamescope's X server; the wrapper sets
  XDG_SESSION_TYPE=x11.
- On a Qualcomm display driver it opens a tracefs instance for the GPU memory
  total and aborts when that fails, which it always does for an Android app.
  The session preload now places the instance in an empty directory of its own
  when the real one cannot be made.
- gamescope sends it each frame's timings through a System V message queue,
  and Android kernels have no System V IPC. sysv.c now emulates the queue
  operations gamescope and mangoapp use, beside its semaphores and shared
  memory.
2026-09-28 12:19:01 -04:00
xXJSONDeruloXx 3cf51b3814 Deck mode: enable Steam's performance overlay controls
The Quick Access menu's performance overlay toggle and level slider stayed
greyed out: the client exposes SteamClient.System.Perf only with -steamos3,
which SteamOS and Armada pass alongside -steamdeck. Deck mode now launches the
client with both. The session already puts package/beta back to the forced
branch before every start, which keeps -steamos3's own branch switch from
turning into an update loop.

The Deck mode switch moves from the Performance page to the Steam page's
Client settings, next to the client branch it selects by default.
2026-09-28 12:19:01 -04:00
xXJSONDeruloXx e8d36ecfe5 Blend translucent Wayland surfaces
The compositor composed every surface with a blit, which replaces what is under it. gamescope's
Wayland backend hands its overlay and notification planes to the host as full-size translucent
subsurfaces, so a Steam toast, the Quick Access menu or Deck mode's performance overlay blacked
out what was beneath it.

Surfaces whose buffer has alpha and that are not declared opaque now go through an alpha pass
(premultiplied source-over); everything else, and any image the driver cannot sample, keeps the
blit. wl_surface.set_opaque_region is honoured, and only for programs that declare opaque regions
at all, so Wine windows with a meaningless alpha channel are drawn as before. On the SurfaceControl
path a translucent plane is never promoted to the game layer, and the overlay layer is marked
translucent when its plane is.

From WinNative (maxjivi05, feature/wayland-gamescope 69218fc; GPL-3.0), with the SurfaceControl
layer handling added.
2026-09-28 12:18:50 -04:00
Kurt Himebauch 9468a8fe43 Merge pull request #52 from maxjivi05/fix/splash-screen
Reduce Steam startup movie software decoding load
2026-09-28 11:35:25 -04:00
MaxsTechReview 7d0e6b134b Hide idle adaptive sticks and activate them at touch positions 2026-09-28 10:54:24 -04:00
MaxsTechReview 3c8fe938dc Reduce Steam startup movie software decoding load 2026-09-28 10:49:25 -04:00
Kurt Himebauch da0775913f Merge pull request #50 from Droid-Deck/fix/release-blockers
Fix/release blockers
2026-09-28 10:32:44 -04:00
xXJsonDeruloXx 20b63bba9b chore(release): set version 0.2.0 and remove code comments 2026-09-28 10:32:12 -04:00
Kurt Himebauch 545b616179 docs(release): show every 0.2.0 section open (#48) 2026-09-28 10:24:24 -04:00
Kurt Himebauch e406025d49 Merge branch 'main' into fix/release-blockers 2026-09-28 10:22:22 -04:00
The412Banner 35517f94cc Merge pull request #51 from Droid-Deck/fix/child-process-check
Child-process check: read the Developer options toggle too
2026-09-28 09:30:58 -04:00
The412Banner 63db6fe9b7 Child-process check: read the Developer options toggle too
Developer options' "Disable child process restrictions" writes only the
feature-flag property persist.sys.fflag.override.settings_enable_monitor_phantom_procs,
never the global setting. Android (FeatureFlagUtils.isEnabled) uses the global
setting when it is set and that property otherwise, but the check read only the
global setting, so a device switched off in Developer options stayed flagged as
unset however often Check again was pressed.

The check now follows Android's order, and the Setup card no longer says "On"
for a setting that is unset or unreadable.
2026-09-28 09:24:19 -04:00
Kurt Himebauch 738c40cbb9 fix(session): stop the session script's own loops when steam exits 2026-09-28 11:52:03 +00:00
Kurt Himebauch dcf1dc33b1 fix(home): open drawer apps in their own task 2026-09-28 11:52:03 +00:00
Kurt Himebauch eeeba13b6c fix(session): keep a retried session's first-frame and hud listeners 2026-09-28 11:52:03 +00:00
Kurt Himebauch 1ea8eb6cb3 fix(session): cancel a session start that is still setting up 2026-09-28 11:52:02 +00:00
Kurt Himebauch 6e25e0b007 fix(session): don't launch a cancelled session or end a newer one 2026-09-28 11:52:02 +00:00
Kurt Himebauch c029134ba2 chore(release): bump to 0.2.1 and check the version at release time 2026-09-28 11:52:02 +00:00
Kurt Himebauch 2920847939 fix(runtime): keep the user's home when a runtime update fails mid-swap 2026-09-28 11:52:02 +00:00
Kurt Himebauch 1d6827a4dc docs: home app and dual-screen demos, trimmed 0.2.0 visuals, README hero (#47) 2026-09-27 22:48:33 -04:00
Kurt Himebauch c7e692c5ec 0.2.0 release notes with screenshots, plus launch transition and UI fixes (#46)
* docs(release): draft 0.2.0 beta notes

* docs(release): credit prs and authors in 0.2.0 notes

* docs(release): add #39 and #45 to 0.2.0 notes

* chore: update readme

* fix(ui): cover the whole screen with the capsule wall

The wall was shifted right and then tilted around its own centre, which
left the bottom-left of the home and loading screens bare. Centre it and
size it to the tilted page's bounding box so every corner stays covered.

* feat(ui): flood from the play button into the throbber

Starting a session from a blue button no longer slides the session up
over the front end. The button stretches out on staggered springs until
the page is the ball's blue, the session opens on that same blue with no
window animation, and the blue gathers into the throbber's ball before
the throbber starts. Re-joining a running session, tiles and reduced
motion keep the old rise.

* docs(release): cover everything since 0.1 in the 0.2.0 notes

0.2.0 is the first release from the org, so list what DroidDeck does
as a whole, grouped by area: org pull requests as #N, pull requests
from before the move as The412Banner/DroidDeck#N, and the early work
that predates pull requests by author. CI and build work gets one
short section instead of a line each.

* docs(release): add screenshots and animations to the 0.2.0 notes

Lead with a game running and a few visual highlights, fold the full
list into collapsible sections by area, and shorten every line to the
feature and its credit. Pre-move pull requests read as legacy#N.

* fix(session): give the desktop its own loading hints

The loading screen's hints were written for Steam, so starting the
desktop or an emulator said the first run downloads Steam and that
leaving the app pauses Steam. Desktop and program sessions now rotate
through hints about the desktop instead.

* fix(build): skip incomplete NDKs when picking one locally

An interrupted sdkmanager install leaves a directory like
28.2.13676358.failed under ndk/, which sorted last and was picked
without a toolchain in it. Only consider directories that carry
source.properties.

* docs(release): show launching straight into Big Picture

* docs(release): credit the launch transition to #46
2026-09-27 22:14:23 -04:00
Kurt Himebauch d940175e4b fix(session): clear transient UI on pause (#45) 2026-09-27 20:37:58 -04:00
Kurt Himebauch b06c6cb478 Add game file and Proton prefix shortcuts (#39) 2026-09-27 20:37:43 -04:00
The412Banner a85acbf50b Merge pull request #43 from Droid-Deck/fix/session-stragglers
Session stop: kill guest processes proot lost, and cap the session log
2026-09-27 17:19:58 -04:00
The412Banner 9d13b238bd Session stop: kill guest processes proot lost, and cap the session log
Twice today a session's Xwayland aborted from one thread ("The futex
facility returned an unexpected error code"), proot lost the tracee, and
the process lived on reparented to init: proot's seccomp filter, with no
tracer left, answers ENOSYS, so Xwayland's main loop logged
"WaitForSomething(): poll: Function not implemented" until something killed
it - 9 GB after Dolphin, 13 GB after PPSSPP. --kill-on-exit and the tree
sweep only reach what proot still traces, and the straggler sweep ran only
at the next session's start.

Now a session stop also kills every process of ours whose executable lives
in the Linux runtime, and bannerlator-session keeps only the first
BL_LOG_CAP bytes (256 MB) of session.log, draining the rest so a writer
that filled it is not killed by SIGPIPE.
2026-09-27 16:52:51 -04:00
Kurt Himebauch 9aed4e7a5c Merge pull request #41 from Droid-Deck/fix/ui-ux-critique
Fix/UI ux critique
2026-09-27 16:41:18 -04:00
The412Banner c67323538b Merge pull request #42 from Droid-Deck/feat/cemu-wiiu
Cemu (Wii U) from the front end and the desktop, and two session fixes
2026-09-27 16:39:17 -04:00
xXJsonDeruloXx fa3442cefc fix(ui): remove duplicate focus imports 2026-09-27 16:34:34 -04:00
The412Banner fff1e837d3 Cemu (Wii U) from the front end and the desktop, and two session fixes
Cemu never showed a frame on DroidDeck: its Getting Started dialog blocked
every first launch (the folder picker cannot reach ROMs), the session's
controller helper aborted it, and the desktop's Vulkan renderer drew no
cursor. With the Turnip KGSL fix in the driver and the haptic-optional
Cemu in the catalog, this makes the app side complete:

- bannerlator-pad-defaults seeds Cemu's settings.xml once: the Wii U games
  folder, Vulkan, and Cubeb audio on the default device for both the TV
  and the GamePad speaker at full volume (Wipeout 3 plays through the
  GamePad; Cemu's own default is 20 %).
- The Cemu tile launches full screen (-f).
- libfakeinput.so is preloaded into every session process and exported its
  static C++ runtime (~7000 symbols); programs unwound through it and
  aborted (shadPS4 in pthread_exit, Cemu's controller thread). Link with
  --exclude-libs,ALL, and fail the build if the runtime leaks out again.
- On the Vulkan and GLES desktops the pointer worked but no cursor was
  drawn: wlroots hands the cursor to the app's compositor, which draws
  none. WLR_NO_HARDWARE_CURSORS=1 makes labwc composite it itself.

Device-tested on the FIT: Super Mario 3D World in-game at 35-37 fps with
sound, Deus Ex HR DC and Wipeout 3 launch with sound; RPCS3, Dolphin and a
Steam session (FlatOut) unchanged on the same driver.
2026-09-27 16:30:49 -04:00
Kurt Himebauch 68de31ef4b fix(copy): american spelling, clearer keyboard and package labels 2026-09-27 14:23:19 -04:00
Kurt Himebauch d2c9278783 fix(focus): land controller focus by frame instead of fixed delays 2026-09-27 14:22:56 -04:00
Kurt Himebauch d17e590398 fix(motion): honor the system animation scale everywhere and on resume 2026-09-27 14:22:17 -04:00
Kurt Himebauch 4f51416432 refactor(ui): share one frame generation menu and drop the unused dialog 2026-09-27 14:21:41 -04:00
Kurt Himebauch 5702890869 fix(rail): keep sections in fixed places with an empty games state 2026-09-27 14:21:01 -04:00
Kurt Himebauch da672ba00b fix(setup): trim child-process fix to one line and three actions 2026-09-27 14:20:35 -04:00
Kurt Himebauch 64066f7f64 fix(ui): raise buttons, chips and tabs to handheld touch sizes 2026-09-27 14:20:03 -04:00
Kurt Himebauch c4fb6adf7c fix(ui): show launch and tab hints where a pad does that 2026-09-27 14:19:32 -04:00
Kurt Himebauch 18985c581c fix(ui): enable launches like play and say why they are disabled 2026-09-27 14:19:02 -04:00
Kurt Himebauch c6332f88db fix(ui): flip booleans with a switch instead of on/off menus 2026-09-27 14:18:29 -04:00
Kurt Himebauch 8e117a865b fix(ui): use one name each for child-process limit, proton and components 2026-09-27 14:17:49 -04:00
Kurt Himebauch 54b826ff49 fix(session): keep ended screen with retry, share logs and back 2026-09-27 14:17:16 -04:00
Kurt Himebauch d86e6d7776 Merge pull request #40 from Droid-Deck/fix/review-hardening
Fix/review hardening
2026-09-27 13:57:20 -04:00
Kurt Himebauch 6b5bfddf2f Merge branch 'main' into fix/review-hardening 2026-09-27 12:27:22 -04:00
Kurt Himebauch 975f18e572 Merge pull request #37 from maxjivi05/fix/performance-enhancements 2026-09-27 12:26:53 -04:00
xXJsonDeruloXx 9071d574c6 refactor(session): move the overlay setup out of SessionActivity.onCreate 2026-09-27 11:57:43 -04:00
xXJsonDeruloXx bb4223bee3 refactor(session): split runSession into its setup stages 2026-09-27 11:57:08 -04:00
xXJsonDeruloXx c0cc0ae624 refactor(core): share one checksum helper across downloads 2026-09-27 11:56:15 -04:00
xXJsonDeruloXx 64273927f0 refactor(compositor): move zwp_linux_dmabuf_v1 into wl_dmabuf.c 2026-09-27 11:54:48 -04:00
xXJsonDeruloXx f91da6c3ab refactor(launcher): resolve displayNameOf on the activity in DriverMenus 2026-09-27 11:48:05 -04:00
xXJsonDeruloXx 9f04a51810 refactor(launcher): move components, decky and proton state out of MainActivity 2026-09-27 11:43:35 -04:00
xXJsonDeruloXx 39c54ee291 refactor(launcher): move driver menu state out of MainActivity 2026-09-27 11:41:21 -04:00
xXJsonDeruloXx 75db49bb88 refactor(ui): move the session loading screen out of SessionOverlay 2026-09-27 11:39:57 -04:00
xXJsonDeruloXx 63c58e94ce refactor(files): split FileManagerScreen helpers, items and dialog 2026-09-27 11:39:57 -04:00
xXJsonDeruloXx 7997696706 refactor(ui): split FrontEndScreen into per-page files 2026-09-27 11:33:09 -04:00
Kurt Himebauch 76bd76686b Merge branch 'main' into fix/performance-enhancements 2026-09-27 11:31:48 -04:00
xXJsonDeruloXx 4590b0a8c2 feat(runtime): install the runtime from a foreground service 2026-09-27 11:01:55 -04:00
xXJsonDeruloXx 8432fc5306 build(deps): bump commons-compress to 1.27.1 2026-09-27 11:00:47 -04:00
xXJsonDeruloXx a1a4174e1f build(native): enable warnings for the compositor 2026-09-27 10:55:25 -04:00
xXJsonDeruloXx 1ee909eb6b fix(compositor): bound cursor buffer copies by stride and 64-bit size 2026-09-27 10:55:25 -04:00
xXJsonDeruloXx 0976141fa7 fix(security): require a verified checksum for proton downloads 2026-09-27 10:54:31 -04:00
xXJsonDeruloXx 61b7b5486f fix(security): require sha256 for every catalog and release download 2026-09-27 10:54:05 -04:00
xXJsonDeruloXx 7242e3b21a test: add unit tests for archive paths and log redaction 2026-09-27 10:52:01 -04:00
xXJsonDeruloXx c94efd0b01 fix(runtime): refuse hard links that point outside the rootfs 2026-09-27 10:51:14 -04:00
xXJsonDeruloXx 9bc6423cdc fix(ui): keep the launcher alive when a pad connects 2026-09-27 10:49:54 -04:00
xXJsonDeruloXx 2cbd3c975a fix(runtime): single-flight the runtime install and reattach after rebuild 2026-09-27 10:49:54 -04:00
Kurt Himebauch ca466f5b59 Merge pull request #38 from Droid-Deck/feat/steam-startup-toggle
Add optional Steam startup
2026-09-27 09:55:22 -04:00
xXJsonDeruloXx 5110cb6b47 Add optional Steam startup 2026-09-27 09:27:30 -04:00
maxjivi05 866cfbd17a proot: port WinNative's seccomp and tracee performance fixes
Brings the proot patch set up to WinNative 53836ca9 on top of the #772 port:

- trace prctl, setrlimit and prlimit64 in the seccomp filter only for the
  arguments proot acts on, and uname only where the core rewrites it
- answer a seccomp stop's filter flags from the registers fetched for its
  translation, reuse those registers, and skip the sysexit stop of wait4 left
  to the kernel and accept without a sockaddr; the flags come from proot's list
  merged with the enabled extensions, and a syscall an extension replaced
  keeps its exit stop
- take a clone3 child's flags and exit signal from struct clone_args
- name a thread's /proc/self by its thread group and resolve /proc/thread-self
- translate fchmodat2, send openat2 callers to openat, and bound sysnum lookups
  by the table length
- sweep relatives only for tracees that have children or ptracees, and empty
  the per-stop memory collector instead of reallocating it
2026-09-27 08:10:58 -04:00
Kurt Himebauch 0c8882fb26 Merge pull request #36 from Droid-Deck/fix/steam-startup-movie-overrides
Fix Steam startup movie override paths
2026-09-27 00:31:31 -04:00
xXJsonDeruloXx b4dffd7e61 fix: use Steam startup movie override names 2026-09-27 00:16:37 -04:00
Kurt Himebauch 1dab735f8a Merge pull request #35 from Droid-Deck/ui/launcher-refresh
feat(ui): launcher ui refresh
2026-09-26 23:19:09 -04:00
xXJsonDeruloXx 110f5e595d feat(session): animate the logo on the loading screen 2026-09-26 22:44:09 -04:00
xXJsonDeruloXx 2b1ef0d3e6 ui: preserve rail focus on components 2026-09-26 22:16:38 -04:00
xXJsonDeruloXx 2ba872e7f7 ui: add fullscreen launcher and focus tabs 2026-09-26 22:10:17 -04:00
xXJsonDeruloXx 233955c5c1 ui: simplify suspended session overlay 2026-09-26 21:56:55 -04:00
xXJsonDeruloXx 03a624ef47 fix(ui): name the desktop cog's click 2026-09-26 21:37:40 -04:00
xXJsonDeruloXx 10569b5a8d feat(ui): turn the steam tab into a capsule wall 2026-09-26 21:31:26 -04:00
xXJsonDeruloXx a1b8cae36e feat(ui): add a games tab with list and detail 2026-09-26 21:30:19 -04:00
Kurt Himebauch 8a00e54543 Merge branch 'main' into ui/launcher-refresh 2026-09-26 20:46:47 -04:00
Kurt Himebauch 3c02645b1c Merge pull request #34 from Droid-Deck/fix/droiddeck-logo-white-on-black
Invert DroidDeck logo colors
2026-09-26 20:41:45 -04:00
xXJsonDeruloXx b43b803082 Invert DroidDeck logo colors 2026-09-26 20:39:52 -04:00
xXJsonDeruloXx d1ae843f4e fix(ui): raise small text on the remaining pages 2026-09-26 16:43:46 -04:00
The412Banner 747e54cb45 Merge pull request #33 from Droid-Deck/fix/steam-readonly-files
First Steam session: Proton ARM64 ready at sign-in; Steam updates past read-only files
2026-09-26 16:43:35 -04:00
xXJsonDeruloXx 1f426e3ceb feat(session): stack the loading screen on small screens 2026-09-26 16:42:58 -04:00
xXJsonDeruloXx 4438fcf358 feat(session): explain paused sessions and offer stop 2026-09-26 16:42:28 -04:00
xXJsonDeruloXx 2cfc346562 feat(session): compact the session menu on short screens 2026-09-26 16:41:17 -04:00
xXJsonDeruloXx 88b9cdab34 feat(session): redesign the session menu header and actions 2026-09-26 16:40:30 -04:00
xXJsonDeruloXx 2e79090b54 feat(ui): rework the components page layout 2026-09-26 16:38:53 -04:00
xXJsonDeruloXx 3358597dd8 feat(ui): split setup into tabs 2026-09-26 16:36:49 -04:00
xXJsonDeruloXx 72e863ea59 feat(ui): redesign the desktop and emulator pages 2026-09-26 16:35:07 -04:00
xXJsonDeruloXx ef4c03a13f feat(ui): fit the game page to narrow screens 2026-09-26 16:33:53 -04:00
xXJsonDeruloXx 8970bdc516 feat(ui): condense the steam page on small screens 2026-09-26 16:33:19 -04:00
xXJsonDeruloXx e50d7a60f2 feat(ui): scroll launcher pages as one and wrap actions 2026-09-26 16:32:04 -04:00
xXJsonDeruloXx 246f9b6d7b feat(ui): show an icon-only rail on narrow screens 2026-09-26 16:31:10 -04:00
xXJsonDeruloXx d01b536f1b feat(ui): reflow settings pages in narrow panes 2026-09-26 16:30:21 -04:00
xXJsonDeruloXx e31b674493 feat(theme): add readable text colour for signal fills 2026-09-26 16:29:05 -04:00
The412Banner 566018674f Steam updates: the client is not uid 0 in the session - drop that test
The session's proot runs with -i <app uid>, and the rootfs names that uid
root, so the client calls itself root while geteuid() returns the app's
uid. The read-only retry required geteuid() == 0 and never ran: the
Proton Experimental (ARM64) update still failed with Missing file
permissions on wineopenxr.dll (2026-09-26 16:07, 2,471 read-only files).

Also recognise the client by the path it was started by: the kernel's
/proc/self/exe names proot's loader for every process in the session.
2026-09-26 16:10:11 -04:00
The412Banner bc2d85d496 First Steam session: Play sends no mode, and no mode is Steam - seed the Proton then too 2026-09-26 15:44:20 -04:00
The412Banner dcbfc0bb06 First Steam session: place Valve's ARM64 Proton before the client first starts
The runtime setup now also fetches Proton Experimental (ARM64) and its
appmanifest from a catalog of their own (steam-seed.json) and unpacks them
where the client keeps them. At the first sign-in the compatibility tool is
already installed and the default, with no install dialog, no download and
no client restart; the client adopts the manifest and updates it like any
other app.

Done once: an install the client fetched itself, or one the user removed,
is left alone. A failed fetch stops nothing - the session asks the client
for it after sign-in, as before.
2026-09-26 15:36:23 -04:00
The412Banner 6f76bf2b02 Merge remote-tracking branch 'org/main' into fix/steam-readonly-files 2026-09-26 15:33:02 -04:00
The412Banner cb9726b41c Merge remote-tracking branch 'org/fix/first-login-proton-timing' into fix/steam-readonly-files 2026-09-26 15:33:01 -04:00
xXJsonDeruloXx 01c73c3484 fix(ui): guard missing runtime version in setup 2026-09-26 15:19:40 -04:00
xXJsonDeruloXx 6057843d12 feat(ui): add controller button hints 2026-09-26 15:19:00 -04:00
xXJsonDeruloXx 6969d4e2d8 feat(session): show staged progress on loading screen 2026-09-26 15:18:35 -04:00
xXJsonDeruloXx 9527414057 feat(ui): add system check to setup 2026-09-26 15:16:14 -04:00
xXJsonDeruloXx 6fe5ca8d3a feat(ui): add hero and launch settings to game page 2026-09-26 15:14:59 -04:00
xXJsonDeruloXx 85b7477467 feat(ui): add continue hero to steam page 2026-09-26 15:13:20 -04:00
xXJsonDeruloXx b8557d1772 style(ui): drop random hue washes behind pages 2026-09-26 15:12:14 -04:00
xXJsonDeruloXx 7d8f6f530a feat(ui): replace nav drawer with permanent side rail 2026-09-26 15:11:47 -04:00
xXJsonDeruloXx 571a3e2124 refactor(ui): move build label into setup about 2026-09-26 15:09:34 -04:00
xXJsonDeruloXx 60b1125399 feat(library): read hero art and last played from steam 2026-09-26 15:09:07 -04:00
xXJsonDeruloXx f1613bf5b0 feat(ui): show game titles on library tiles 2026-09-26 15:08:30 -04:00
xXJsonDeruloXx 6904beee29 fix(ui): raise small text to 12sp minimum 2026-09-26 15:07:42 -04:00
xXJsonDeruloXx 2ae693ceed feat(theme): add graphite theme as default 2026-09-26 15:07:10 -04:00
Kurt Himebauch 37a1d181cb Merge pull request #31 from Droid-Deck/docs/readme-banner
docs: add readme banner
2026-09-26 15:03:58 -04:00
The412Banner a9125900a2 Steam updates: rewrite read-only content files the way root could
The client runs as proot's root, which the kernel does not see, and
leaves many content files read-only; an update that rewrites one was
refused (EACCES) and cancelled with "Missing file permissions"
(Proton Experimental (ARM64), wineopenxr.dll). For the client's own
processes, a refused write-open of a file the app owns now gets the
owner's write bit - on the file, or on the directory a new file goes
into - and one more try. Games and Wine keep ordinary permissions.
2026-09-26 10:41:06 -04:00
xXJSONDeruloXx e93aab1c1d docs: add readme banner 2026-09-26 09:48:11 -04:00
The412Banner e231efe5e3 First sign-in: ask for the ARM64 Proton after the client's post-logon compat phase, and retry
With Steam publicbeta 1790380355 the client spent 27 s in its post-logon
compat phase and dropped the install request the session sent 15 s after
sign-in, so a clean install never offered Proton Experimental (ARM64).
Wait for the console log's "Waiting for compat in post-logon took" (90 s
at most), then ask, up to three times a minute apart until the install
starts.
2026-09-26 08:21:14 -04:00
The412Banner 7aa760b696 Merge pull request #29 from Droid-Deck/feat/components-manager
Components manager: swap FEX / DXVK / VKD3D-Proton per Proton, enforced at launch
2026-09-26 06:16:04 -04:00
The412Banner 91e02aa119 Components drawer: stacked rows so long names never squeeze the text, a focusable Running now card the pad can reach, and only the Proton a game actually runs on counts as running 2026-09-26 05:30:02 -04:00
The412Banner 2725c64df1 Proton wrappers: run the components check for non-Steam shortcuts too - decide by the game's own prefix rather than the app id, which is 0 for a shortcut 2026-09-26 05:15:36 -04:00
The412Banner bbb09ab909 Merge org main into feat/components-manager 2026-09-26 04:41:10 -04:00
The412Banner 408fbff7da Merge org main into feat/components-manager
Keeps both onResume additions: the phantom status refresh from main and the queued component swaps.
2026-09-26 04:36:02 -04:00
Kurt Himebauch f207076ce2 Merge pull request #28 from Droid-Deck/fix/session-desktop-button-label
Keep the session Desktop button on one line
2026-09-26 00:53:17 -04:00
xXJsonDeruloXx 29623ec389 Keep session desktop button label on one line 2026-09-26 00:52:53 -04:00
Kurt Himebauch ac61bde072 Merge pull request #27 from Droid-Deck/fix/boot-video-pitch-black
Set boot movie background to pitch black
2026-09-26 00:36:22 -04:00
xXJsonDeruloXx 7e012aade9 Set boot movie background to pitch black 2026-09-26 00:35:54 -04:00
The412Banner dd62cae62e Proton wrappers: run the components check only for a real game launch (waitforexitandrun, non-zero app id)
With the check reached from the client's own runs right after login, the Steam client segfaulted
within seconds, four sessions out of four; the same build without the check (test/bisect-no-launch-check)
and the last build before it both reach Big Picture. Now every run that is not a game launch -
the client's probes, app id 0 - is left exactly as before. Each check logs one line.
2026-09-26 00:28:49 -04:00
Kurt Himebauch b0982772d0 Merge pull request #26 from Droid-Deck/ui/build-info-top-bar
Show build identity in the launcher top bar
2026-09-26 00:24:39 -04:00
The412Banner f092391a39 Remove a stray __pycache__ from the overlay scripts and ignore it 2026-09-26 00:20:41 -04:00
xXJsonDeruloXx 23f833d471 Show build identity in launcher top bar 2026-09-26 00:03:56 -04:00
The412Banner c7aca2834f Proton wrappers: drop the per-tool prefix for Steam's tool-0 runs - the client segfaulted right after its probe started (first in 25 sessions); keep the launch-time component check 2026-09-26 00:03:45 -04:00
The412Banner 66ef6ca802 Components: choices enforced at every game launch; Steam's tool-0 probes get their own prefix per Proton
Steam runs its graphics probe through every registered Proton at once after login, all in
compatdata/0. Prefix system32 links point into the Proton that set it up last, and the other
Protons' Wines, running outside Proton's pfx.lock, wrote their builtin DLLs through them into that
Proton's tree (device: GE's FEX DLLs became Proton Experimental's at session start).

- bannerlator-steam-compat: both wrappers (Valve's depot launcher and the third-party
  bannerlator-proton-wrap) give a tool-0 run STEAM_COMPAT_DATA_PATH=.../compatdata/0-<tool>, and
  before every other launch check the Proton's files against the app's desired.tsv and put back
  anything that differs (same Proton build only), logging to reapplied.log.
- ComponentsManager writes desired.tsv plus unpacked copies of the packages in use into the
  runtime after every change; the page and the drawer show 're-applied at launch'.
2026-09-25 23:54:10 -04:00
Kurt Himebauch b5b71bb7bd Merge pull request #21 from Droid-Deck/fix/phantom-process-setup-gate
Gate Steam sessions on Android process limit
2026-09-25 23:51:11 -04:00
xXJsonDeruloXx 06ed076919 Merge remote-tracking branch 'origin/main' into fix/phantom-process-setup-gate 2026-09-25 23:50:57 -04:00
xXJsonDeruloXx e69ed55356 Reuse saved wireless ADB pairing and connection 2026-09-25 23:48:47 -04:00
xXJsonDeruloXx f5f5a8ec3b Clarify Wireless debugging connection address after pairing 2026-09-25 23:39:31 -04:00
Kurt Himebauch 14e59a1fde feat: set DroidDeck startup movie by default (#25) 2026-09-25 23:36:29 -04:00
xXJsonDeruloXx 90d7eede2e Improve wireless ADB text input 2026-09-25 23:17:28 -04:00
The412Banner ff73c44ca9 Session drawer: tab spacing fits the room between LB and RB, so all four tabs show 2026-09-25 22:59:50 -04:00
The412Banner 36a06f1ffc Components: the pad's LB / RB step through FEX, DXVK and VKD3D-Proton (wrapping), with LB / RB badges beside the tabs 2026-09-25 22:49:10 -04:00
The412Banner e1aa6a1d0e Components: fixed columns so details and Download buttons line up; focused button is a solid blue fill with a white outline 2026-09-25 22:37:07 -04:00
The412Banner 51885e95e6 Components: compact layout - one toolbar line (title, Proton drop-down, component tabs, in use, (i), refresh) and one list of short rows for installed and available packages 2026-09-25 22:27:25 -04:00
The412Banner 44e2ffaf80 Merge pull request #23 from Droid-Deck/docs/progress-log-0925
Progress log: 2026-09-25
2026-09-25 22:06:23 -04:00
The412Banner fe00cae540 Components: FEX, DXVK and VKD3D-Proton per Proton
A Components entry under Desktop opens a page built like the driver pages: a
Proton drop-down, FEX / DXVK / VKD3D-Proton tabs, what is in use, the installed
rows (each Proton build's original bundle and the stored packages; tap to
swap in, trash to delete), downloads from the Nightlies -Linux releases behind
the refresh button, and Import .wcp.

The session drawer gets a fourth tab, Components, for quick swaps between
what is installed. A swap into the Proton a running game uses waits until the
game closes and is applied then (drawer refresh, or the main screen on resume).

ComponentsManager keeps the same package format, originals layout and state as
the Decky Components Manager plugin and takes its data over once. A swap clears
only the component's own DLLs, so files a Proton keeps beside them stay.
2026-09-25 22:04:38 -04:00
The412Banner 12ed8fdf2d Merge pull request #24 from Droid-Deck/feat/drawer-tab-icons
Session drawer: QAM-style page tabs (Display, Controls, Settings)
2026-09-25 21:55:16 -04:00
The412Banner de8db505b6 Session drawer: QAM-style page tabs - Display, Controls, Settings
The three page dots between LB and RB become icons standing in a line in page order, as in
Steam's Quick Access Menu. The selected one steps forward (full size, bright, a soft glow in the
accent), the others step back to half size and fade, and the line leans a little toward the
selection; tapping or picking a tab with the pad, and LB / RB, turn the page as before. The
first page's heading becomes Display to match its icon.
2026-09-25 18:11:04 -04:00
The412Banner 2f1d91e8a6 Progress log: 2026-09-25 2026-09-25 17:29:12 -04:00
xXJsonDeruloXx 63f7554f43 Merge remote-tracking branch 'origin/main' into fix/phantom-process-setup-gate 2026-09-25 17:23:08 -04:00
xXJsonDeruloXx 4565bfe88b Compact Steam process limit recovery for split screen 2026-09-25 17:21:43 -04:00
The412Banner 12813f2e81 Merge pull request #22 from Droid-Deck/feat/steam-touch
Steam client: real touch in Big Picture, and a touchpad cursor that hovers and clicks
2026-09-25 17:06:19 -04:00
The412Banner 625f1a8980 Merge remote-tracking branch 'org/main' into feat/steam-touch 2026-09-25 16:58:18 -04:00
The412Banner 126410a05a gamescope: stage 3.16.29-p3 (0111, pointer warps in Passthrough) 2026-09-25 16:38:50 -04:00
xXJsonDeruloXx e31885441d Replace process gate dialogs with compact pages 2026-09-25 16:34:30 -04:00
The412Banner 18e6d8a942 gamescope: the nested pointer moves Steam's pointer in Passthrough too
In touchpad mode the cursor moved but Big Picture highlighted nothing and a tap-to-click
selected nothing: the nested pointer's motion reaches wlserver as touch 0, and in Passthrough
(the touch mode Big Picture sets) a motion for a touch that is not down moves nothing, so the X
pointer stayed where it was. 0111 passes bAlwaysWarpCursor, as the other touch modes already warp.
2026-09-25 16:34:22 -04:00
Kurt Himebauch 62ce42947a Merge branch 'main' into fix/phantom-process-setup-gate 2026-09-25 16:28:53 -04:00
xXJsonDeruloXx b6998206ef Replace process gate dialogs with pages 2026-09-25 16:28:15 -04:00
Kurt Himebauch cc38a1138c Add native Decky Loader support (#16)
* Add native Decky Loader management

* Enable Decky Steam frontend injection

* Disable unavailable stable Decky channel

* Decky: tie CEF debugging to session setting

* Decky: simplify Steam settings controls
2026-09-25 16:26:29 -04:00
xXJsonDeruloXx 09f8eb8ee3 Replace wireless ADB modal with setup page 2026-09-25 16:19:01 -04:00
The412Banner 0c06ede8d3 gamescope: stage 3.16.29-p2 (0110, touch from the host) 2026-09-25 16:06:31 -04:00
The412Banner 62687b8680 gamescope: take the host's touch in the nested Wayland backend
3.16.29's Wayland backend bound only wl_pointer and wl_keyboard; touch from DroidDeck's
compositor was never read, so Steam could not be touched. 0110 binds wl_touch in the input
thread and feeds each finger to wlserver_touchdown/touchmotion/touchup, the Steam Deck
touchscreen's path, so Big Picture's Passthrough touch mode gets real touches.
2026-09-25 15:55:26 -04:00
The412Banner d087f8174e Touch: a lost finger-up no longer blocks every later touch
On the pointer path (a client that takes no wl_touch - gamescope), touch_down counted the
fingers still down before clearing a sequence whose up had been lost, so the stale point left
any_active set and every later tap or swipe was dropped: in Direct touch mode Big Picture
ignored the screen while the pad worked. The count is taken after the clean-up, and the app
ends whatever the compositor still holds when a new gesture's first finger lands (Android has
ended every earlier gesture by then). The compositor logs once per gesture whether the window
got touch or the pointer.
2026-09-25 15:55:26 -04:00
xXJsonDeruloXx f379a1f30a Add reversible process limit controls 2026-09-25 14:52:04 -04:00
xXJsonDeruloXx f9848c23a2 Add wireless ADB process-limit repair 2026-09-25 14:35:37 -04:00
xXJsonDeruloXx 75387211d1 Auto-dismiss process limit gate after status clears 2026-09-25 13:55:04 -04:00
xXJsonDeruloXx 9c0ea07c39 Gate Steam sessions on Android process limit 2026-09-25 13:49:58 -04:00
Kurt Himebauch 93b9be67fc Build local APK with CI payload parity (#20) 2026-09-25 13:20:51 -04:00
The412Banner a9c1920f9f Merge pull request #19 from Droid-Deck/fix/log-privacy
Session logs: scrub public addresses, device id, Steam account names, and every file
2026-09-25 12:53:30 -04:00
The412Banner cf6f2f7969 Logs: scrub Steam account and persona names for every user
The client's UI log names the signed-in account on every login change ("Login:
OnLoginStateChange <account> ...", "OnLoginUsersChanged <account>"), ~200 lines on one device.
An email account was caught by the email rule; a plain account name was not, so a user signing in
with one had it in every shared log. Those two lines now lose the account, and every
AccountName and PersonaName in the client's loginusers.vdf is learned (with the device's own
addresses, learnFromRuntime) and replaced wherever it appears. The one-time pass over older
folders runs again for this (marker .scrubbed-2).
2026-09-25 11:51:37 -04:00
The412Banner 1fb6f088a2 Logs: no public addresses or device id, and every session file scrubbed
A privacy pass over the session folders found three leaks:
- the Steam client's IPv6 check logs "external address <the device's public IPv6>" about
  twenty times a session, and the redactor kept IP addresses on purpose, so steam/
  connection_log.txt carried it;
- network.txt listed the device's addresses (public IPv6 included) and the session's made-up
  MAC, which is seeded from ANDROID_ID and so stable per device;
- session.log, app.log, desktop.log, steam-desktop.log, crash.log were never scrubbed (clean on
  device, by luck).

The redactor now learns the device's own addresses from the session's link file (a global IPv6
as its /64, a public IPv4 exactly) and removes them wherever they appear, along with anything
logged as an "external address"; Valve's server addresses stay. network.txt gives every address
as its kind ("<public IPv6>") and masks the MAC. Every text file in a session folder goes
through the redactor when the session ends (killed sessions at the next app start), and again on
its way into the share-logs zip. Folders written before this are scrubbed once at app start.
2026-09-25 11:30:16 -04:00
The412Banner 495a42368e Merge pull request #18 from Droid-Deck/feat/melonds-preset
melonDS out of the box, cursor shapes and auto-hide, Steam desktop switching
2026-09-25 11:15:19 -04:00
The412Banner 856f694a0b Merge branch 'main' into feat/melonds-preset 2026-09-25 10:32:03 -04:00
The412Banner fa1ce6fb9c Session: start a relaunch request in place, so DESKTOP reaches the desktop
The drawer's DESKTOP ended Big Picture and landed on the front end. The session activity is
singleTop: startActivity(next) from it went to its own onNewIntent and was dropped when it
finished (on device the next session came up as "steam", not the desktop). It now restarts on
the request's intent (setIntent + recreate), as an agent start does; the desktop's Steam
launchers' handoff to a Steam session goes the same way.
2026-09-25 10:09:55 -04:00
The412Banner dad9d65701 Session drawer: DESKTOP in Steam sessions opens the desktop with Steam in it
Big Picture's own Switch to Desktop waits on SteamOS Manager and never finishes here. The
drawer's Steam row gets a DESKTOP button beside STEAM and QAM: the session ends and the desktop
starts in its place with Steam's desktop client open (the relaunch the Steam Desktop UI button
uses).
2026-09-25 09:56:33 -04:00
The412Banner 0bec2a5435 Revert the Steam session's D-Bus session bus
Tried on device: with a session bus the client still sat on "Switching to Desktop..." and
never reached its legacy path (no "No SteamOSManager support" line, steamos-session-select not
called), so the bus was not what it waited on. Sign-in was unaffected.

This reverts commit 40fc5463f3.
2026-09-25 09:55:59 -04:00
Kurt Himebauch 5dae95376a feat: add session log sharing (#17) 2026-09-25 09:44:33 -04:00
The412Banner c8620017a5 Steam session: a D-Bus session bus, so Switch to Desktop reaches steamos-session-select
Big Picture's Switch to Desktop asks SteamOS Manager over the session bus before falling back to
steamos-session-select (steamui.so). The Steam session had no session bus, the ask never
finished, and the client sat on "Switching to Desktop..."; the new stub was never called. A
bus with nothing on it answers at once, and the client takes its legacy path.
2026-09-25 09:41:21 -04:00
The412Banner 7c6df51bfa Steam desktop UI on the desktop; Big Picture's Switch to Desktop works
- The front end's "Steam Desktop UI" started a Steam session without -gamepadui, and the client
  put itself into Big Picture under gamescope anyway. It now opens the desktop with Steam's
  desktop client started in it (BL_DESKTOP_STEAM, the desktop's autostart, the same
  bannerlator-steam-launch the menu's Steam entry uses).
- Big Picture's power menu "Switch to Desktop" runs steamos-session-select, which did not exist,
  so it sat on "Switching to Desktop..." for ever. The new stub leaves the app a request
  (session=desktop in the steam-launch file); the app ends the Steam session and starts the
  desktop with Steam's desktop client open.
2026-09-25 08:59:06 -04:00
The412Banner 09e5172052 Desktop: centre melonDS's 800x600 window
AutoPlace on its own left the resized window in the top-left corner; with policy="center" it
opens in the middle of the desktop. Tried in a running desktop (labwc --reconfigure).
2026-09-25 08:36:04 -04:00
The412Banner 132b434d78 Desktop: melonDS opens as an 800x600 window, centred
melonDS remembers a 1x window for the front end's full-screen toggle, too small on a desktop.
A labwc window rule (only the desktop reads rc.xml) resizes it to 800x600 on open and places
it again by the desktop's centre policy.
2026-09-25 08:28:56 -04:00
The412Banner c331836bce Session: draw the program's own pointer - resize arrows at window edges, I-beams, its hide
The compositor threw every wl_pointer.set_cursor image away and the app drew one fixed arrow,
so the desktop never showed labwc's resize arrows at a window's edges and corners. Ported from
Bannerlator (7f19c669): the cursor surface gets a role, its image is snapshotted on commit with
its hotspot, and a NULL surface or buffer is an authoritative hide; the app's overlay draws the
image at the session's scale. New here: labwc on Vulkan/GLES hands its cursor over as a dma-buf
(wlroots' nested backend, usually UBWC), so it is read back through a small linear host-visible
copy (vkp_image_readback). The built-in arrow stays until a program sets a cursor.
2026-09-25 08:28:26 -04:00
The412Banner 347a5a4dcb Desktop: Qt falls back to X11, so melonDS opens from the menu
The desktop set QT_QPA_PLATFORM=wayland for every program. The melonDS AppImage bundles only
Qt's xcb plugin, so from the desktop it exited at once: "Could not find the Qt platform plugin
wayland ... Available platform plugins are: xcb" (exit 127). "wayland;xcb" keeps LXQt on
Wayland and lets such an AppImage use Xwayland. Proven in a running desktop: labwc mapped
"melonDS 1.1".
2026-09-25 08:18:43 -04:00
The412Banner 79a7f26852 Session: hide the mouse cursor while a controller or the on-screen controls are in use
The arrow is drawn by the app and, outside a Steam session, never went away once shown - on
the desktop and over emulators started from the front end alike. A button press, a stick,
trigger or d-pad past halfway, or any on-screen control now hides it (PadBridge tells the
activity); the next touchpad or mouse move brings it back, once the pad has been quiet for
1.2 s so a touch during play does not flash it up (Bannerlator's rule for its Wayland pointer).
2026-09-25 08:08:09 -04:00
The412Banner 5f7f6bd678 melonDS: leave full screen to a 1x window, so its menus are big enough to use
The guide button's toggle returns to melonDS's saved window size. A 512x288 window (1x wide,
16:9) is stretched by gamescope to fill the panel, menu bar and cursor with it; at 2x and up
the menus stay at their real size, tiny on a handheld. Written once, with the layout.
2026-09-25 07:52:26 -04:00
The412Banner 9adcb3d91f melonDS: big top screen with the bottom screen alone beside it
Hybrid repeats the big screen, small, above the bottom one. Horizontal with the top screen
emphasised fills the panel's height with the top screen and centres the bottom screen in the
rest. Installs set up with Hybrid (or still on melonDS's defaults) move once.
2026-09-25 07:47:59 -04:00
The412Banner e199d7a421 HUD: follow the game window, not a program's cursor or menus
A program's small surfaces (gamescope's 1x1 cursor, Qt's menus and tooltips) announced
themselves after the game's window and took the HUD, which then read 0 fps while melonDS ran
at 60. A new window now takes the HUD only when it is at least as big, and a window quiet for a
second gives way to the next one that draws.
2026-09-25 07:39:16 -04:00
The412Banner e753ab9c66 melonDS: Hybrid screens, full screen from the front end, guide toggles full screen, BIOS found in the ROMs folder
- bannerlator-pad-defaults: new installs start on the Hybrid layout with sharp pixels; an
  existing install is moved once, only where still on melonDS's defaults (marker keeps later
  choices). Guide = HK_FullscreenToggle. The user's DS/DSi BIOS, firmware and NAND are found in
  ROMs/nds/{bios,firmware} and filled in only where melonDS has no path.
- Front end starts melonDS with -f, at the panel's own resolution unless the user chose one.
2026-09-25 07:37:21 -04:00
Kurt Himebauch 2436f6388f Merge pull request #13 from Droid-Deck/feat/hamburger-rail
Use hamburger drawer for launcher navigation
2026-09-25 01:25:53 -04:00
Kurt Himebauch 60a3ff8372 Merge branch 'main' into feat/hamburger-rail 2026-09-25 01:25:28 -04:00
Kurt Himebauch de1bf8da2b Merge pull request #15 from Droid-Deck/feat/droiddeck-steam-hostname
Make Steam identify DroidDeck as the host
2026-09-25 00:50:32 -04:00
xXJsonDeruloXx d2ebb7709e Set PRoot guest hostname to DroidDeck 2026-09-25 00:48:03 -04:00
Kurt Himebauch 16b5e1c5b2 Merge pull request #14 from Droid-Deck/feat/live-session-suspend-policy
Add live suspend policy control
2026-09-25 00:31:16 -04:00
xXJsonDeruloXx c6dd03e061 Add live suspend policy to session drawer 2026-09-25 00:26:58 -04:00
xXJSONDeruloXx 49dbcfa2e2 fix: fit library art on short screens 2026-09-25 00:12:17 -04:00
xXJSONDeruloXx e9b07a508e fix: add headroom above focused game tiles 2026-09-25 00:01:40 -04:00
xXJSONDeruloXx f15426c136 feat: use hamburger drawer for launcher navigation 2026-09-24 23:57:01 -04:00
MaxsTechReview 00057932cd Merge pull request #9 from maxjivi05/feature/controller-editor
Feature/controller editor
2026-09-24 23:08:23 -04:00
MaxsTechReview 00e266d9b4 Merge branch 'main' into feature/controller-editor 2026-09-24 23:08:05 -04:00
maxjivi05 58b2b728ab Fit the on-screen pad to phones, add adaptive sticks
Scale controls down on short screens with a 9.5 mm face-button floor and
keep a bottom margin. On phones the shoulders, View and Menu sit along the
top edge; bumpers and triggers are drawn as side-by-side rectangles.
Adaptive sticks centre under the thumb and return home on release, with a
toggle in the Controller settings. The editor toolbar is a compact pill.
2026-09-24 23:05:45 -04:00
Kurt Himebauch c5001098fd Merge pull request #12 from Droid-Deck/fix/steam-library-capsules
Fix Steam library capsule artwork
2026-09-24 22:57:27 -04:00
Kurt Himebauch bf70ca8f25 Merge branch 'main' into fix/steam-library-capsules 2026-09-24 22:57:17 -04:00
xXJSONDeruloXx 44e4cfb785 Fix Steam library capsule artwork 2026-09-24 22:52:37 -04:00
MaxsTechReview afbead8049 Merge branch 'main' into feature/controller-editor 2026-09-24 22:45:27 -04:00
The412Banner 9767feb84b Merge pull request #10 from Droid-Deck/feat/desktop-steam-dolphin
Desktop: Steam launchers on the desktop, two-finger drag, Dolphin GameCube/Wii
2026-09-24 22:43:34 -04:00
MaxsTechReview 046eb9e319 Merge branch 'main' into feature/controller-editor 2026-09-24 22:38:46 -04:00
The412Banner 1103627ee1 Merge remote-tracking branch 'org/main' into feat/desktop-steam-dolphin
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/session/SessionService.kt
2026-09-24 22:35:37 -04:00
Kurt Himebauch 18dc3dd2de Merge pull request #7 from Droid-Deck/feat/debug-agent-control
Add debug-only DroidDeck agent control
2026-09-24 22:34:41 -04:00
The412Banner a28e2ffe72 Merge remote-tracking branch 'origin/feat/desktop-steam' into feat/desktop-steam-dolphin 2026-09-24 22:24:00 -04:00
MaxsTechReview 02806bb389 Merge branch 'Droid-Deck:main' into feature/controller-editor 2026-09-24 22:23:26 -04:00
xXJSONDeruloXx 1006688914 fix(agent-bridge): launch sessions through debug activity 2026-09-24 22:16:28 -04:00
maxjivi05 db79674d5e Add a Controller section to Setup with an on-screen pad editor
The on-screen pad is now placed from the panel's physical size: the
sticks and face buttons sit at the two-thumb rest point (about 17% of
the width inboard, 28% of the height up, clamped in millimetres), with
the d-pad and right stick below-inboard, shoulders above, and nothing
ever placed off screen or under a display cutout. Button sizes are
unchanged.

Setup > Controller holds the on-screen controls mode, a tint (Steam
blue #1A9FFF by default), opacity, button size, stick click (double-tap
for L3/R3), a full-screen layout editor, per-button mapping including
LT/RT, and resets for each.

Also from WinNative #772: the fakeinput ring poll backoff is capped at
2 ms instead of 16 ms, the session asks for unbuffered joystick input,
and the on-screen controls request unbuffered dispatch per gesture and
release everything when detached. The QAM button no longer draws at the
top-left corner in full-pad mode.
2026-09-24 22:13:48 -04:00
Kurt Himebauch 991d7e24d2 Merge branch 'main' into feat/debug-agent-control 2026-09-24 22:00:40 -04:00
Kurt Himebauch a54e7d45f1 Merge pull request #8 from Droid-Deck/chore/codebase-deslop
chore/cleanup
2026-09-24 22:00:23 -04:00
Kurt Himebauch bd9c60bd11 Merge branch 'main' into chore/codebase-deslop 2026-09-24 22:00:12 -04:00
Kurt Himebauch 0ad96a2d23 Merge pull request #4 from maxjivi05/fix/proot-performance
proot: build from source with WinNative's performance fixes
2026-09-24 21:59:27 -04:00
xXJSONDeruloXx f21e84be91 chore: remove dead code and stale comments 2026-09-24 21:53:31 -04:00
xXJsonDeruloXx 63b24ca4c0 Add debug-only agent control bridge 2026-09-24 21:34:50 -04:00
The412Banner 123a8f8fa3 Dolphin: guide = its Toggle Fullscreen hotkey (full default Hotkeys.ini), Wii Home on R1, input without focus from the rail 2026-09-24 21:31:25 -04:00
maxjivi05 531087be16 Merge Droid-Deck main into fix/proot-performance 2026-09-24 21:27:31 -04:00
Kurt Himebauch 643d82e2fd Merge pull request #6 from Droid-Deck/feat/in-session-sidebar-menu-review
Paged in-session drawer with controller navigation
2026-09-24 21:26:01 -04:00
Kurt Himebauch 7c9ec71a28 Merge branch 'main' into feat/in-session-sidebar-menu-review 2026-09-24 21:20:21 -04:00
xXJSONDeruloXx e95600ed0b feat: confirm session stop from drawer header 2026-09-24 21:19:20 -04:00
Kurt Himebauch c50e58caba Merge pull request #5 from Droid-Deck/chore/protect-main-signing
Gate release-signed PR APKs behind review
2026-09-24 21:15:27 -04:00
xXJSONDeruloXx 870a0b6279 feat: add paged in-session drawer navigation 2026-09-24 21:14:55 -04:00
xXJSONDeruloXx 4bc7c8dc99 Trim signing key script comments 2026-09-24 21:02:13 -04:00
xXJSONDeruloXx 67b675066f Trim workflow commentary and redundant build output 2026-09-24 21:01:35 -04:00
The412Banner d38586edb1 Dolphin from the rail: guide toggles full screen (its window and settings), drawn in the main window 2026-09-24 21:00:43 -04:00
xXJSONDeruloXx 98066f85ef Simplify approved PR signing workflow 2026-09-24 20:45:25 -04:00
The412Banner c0977d4344 Dolphin: its data where it looks for it (fonts, per-game fixes), game folders, Vulkan, no NKit box; full screen from the front end 2026-09-24 20:35:48 -04:00
xXJSONDeruloXx 6d4fc6775c Preserve the current PR APK until upload succeeds 2026-09-24 20:22:36 -04:00
xXJSONDeruloXx 097f56cd6c Match PR APK version to latest stable 2026-09-24 20:19:14 -04:00
xXJSONDeruloXx 2a0f859701 Use the pinned release signer for PR APKs 2026-09-24 20:16:28 -04:00
xXJSONDeruloXx 7aa56118ae Require repo write access for signing approvals 2026-09-24 20:13:40 -04:00
xXJSONDeruloXx 31cea4e0e3 Gate release-signed PR APKs on maintainer approval 2026-09-24 20:08:33 -04:00
xXJSONDeruloXx cbb66ea188 chore: harden release signing boundaries 2026-09-24 19:38:04 -04:00
The412Banner 0c06868c5d Touchpad: one finger held, the other slides = drag (move windows, resize, drag icons) 2026-09-24 19:10:29 -04:00
The412Banner 6e69f252f6 Desktop: the Steam and Big Picture launchers run the client on the desktop itself 2026-09-24 18:56:24 -04:00
MaxsTechReview a9cf5cacbc Merge pull request #3 from maxjivi05/ci/pr-release-publish
CI: publish each pull request's APK to DroidDeck-CI
2026-09-24 18:15:04 -04:00
The412Banner 42cde57c86 Merge pull request #45 from The412Banner/release/0.1.7
Release 0.1.7 (versionCode 8)
2026-09-24 16:42:59 -04:00
The412Banner b3794ffc20 Release 0.1.7 2026-09-24 16:37:31 -04:00
maxjivi05 ff213de6c3 CI: publish each pull request's APK to DroidDeck-CI
After a pull request's Build APK run succeeds, its APK is published as the
pr-<number> prerelease in Droid-Deck/DroidDeck-CI, named
DroidDeck-PR.<number>-<commit>.apk, with release notes taken from the pull
request. New commits to the same pull request replace its APK. Pull requests
from forks are resolved by head owner, branch and commit.

A pull request's build is signed only with the committed testkey, so every CI
build installs over the last; the publish checks the certificate against it
and refuses anything else.

Needs a DROIDDECK_CI_TOKEN secret: a fine-grained token with Contents
read/write on Droid-Deck/DroidDeck-CI. Without it the run skips with a warning.
2026-09-24 16:07:28 -04:00
The412Banner 2e53de0c13 Merge pull request #44 from The412Banner/feat/home-screen-toggle
Home screen: a switch in Setup, off by default (replaces the Non-Launcher apk)
2026-09-24 15:06:39 -04:00
The412Banner 952c28316f Home screen: a switch in Setup (off by default) instead of a second apk 2026-09-24 14:39:20 -04:00
The412Banner efde531208 Merge pull request #31 from The412Banner/docs/progress-log-0924
Progress log: 2026-09-24
2026-09-24 14:02:08 -04:00
The412Banner 7ec1fad2d1 Progress log: PS1, controller focus, own signing key, CI hardening (2026-09-24) 2026-09-24 13:57:10 -04:00
The412Banner cc2c49b3c5 Merge remote-tracking branch 'origin/main' into docs/progress-log-0924 2026-09-24 13:55:03 -04:00
The412Banner 8e11a44f85 Merge pull request #41 from The412Banner/feat/release-signing
Signing with DroidDeck's own key, four package names, file-picker access, CI hardening
2026-09-24 13:42:47 -04:00
The412Banner 68172e5d0b Merge remote-tracking branch 'origin/main' into feat/release-signing 2026-09-24 13:38:31 -04:00
The412Banner 6125d853f4 CI: build pull requests with pull_request, not pull_request_target (no shared caches with main) 2026-09-24 13:37:43 -04:00
The412Banner f1ba5503c9 CI: the key alias is plain text, not a secret 2026-09-24 13:24:47 -04:00
Kurt Himebauch f0c8e08af0 Improve session drawer stick navigation (#40) 2026-09-24 13:18:09 -04:00
The412Banner 709668a479 sign-apk: match apksigner's 'V3.0 Signer:' lines too 2026-09-24 13:17:57 -04:00
The412Banner c0a264564b sign-apk: read the signer digest in either apksigner format 2026-09-24 13:12:09 -04:00
The412Banner bdf246bc1f CI: pass secrets on to sign.yml so it can read the environment's key 2026-09-24 13:06:26 -04:00
The412Banner 38151a150a The release key's certificate digest (public), checked against every signed apk 2026-09-24 12:59:34 -04:00
The412Banner af66e29471 CI: give the signing job the permissions sign.yml asks for 2026-09-24 12:46:39 -04:00
The412Banner cd28b189a2 Signing with DroidDeck's own key: hand-over from the testkey, test builds and a four-package release 2026-09-24 12:45:13 -04:00
The412Banner 498a37db32 The app's files in Android's file picker (documents provider), for every package name 2026-09-24 12:35:32 -04:00
The412Banner e4613d39ab Merge pull request #38 from The412Banner/feat/duckstation-play
PS1 one-tap launch, one tile per disc, controller lands on the main button and first game
2026-09-24 11:33:31 -04:00
Kurt Himebauch 0d85bd3053 Update DroidDeck branding to split D mark (#37) 2026-09-24 11:33:14 -04:00
The412Banner 68ab64d08c Merge remote-tracking branch 'origin/main' into feat/duckstation-play 2026-09-24 11:27:12 -04:00
The412Banner cf36224ee9 Front end: Down from a page's buttons goes to the first tile 2026-09-24 11:19:12 -04:00
Kurt Himebauch 417c119501 Merge pull request #33 from The412Banner/feat/secondary-screen-app-launch
Add per-launch Android display selection
2026-09-24 11:14:27 -04:00
xXJsonDeruloXx 1753341a8f Merge remote-tracking branch 'origin/main' into feat/secondary-screen-app-launch
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/ui/FrontEndScreen.kt
#	app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt
2026-09-24 11:09:24 -04:00
Kurt Himebauch b41e5ff971 Merge pull request #35 from The412Banner/fix/ci-build-cleanliness
Snapshot CI build cleanliness before generated assets
2026-09-24 10:45:35 -04:00
The412Banner 41e6f461fd Front end: a page opened from a tile takes the pad on its main button 2026-09-24 10:42:46 -04:00
xXJsonDeruloXx 1d2df612e0 Snapshot CI tree state before generated assets 2026-09-24 10:39:38 -04:00
The412Banner fd4c019c9f DuckStation, ARMSX2: first-time setup answered for the user; ARMSX2 names the BIOS dump itself 2026-09-24 10:09:45 -04:00
Kurt Himebauch 78ecfd8be5 Merge pull request #24 from The412Banner/feat/launch-build-status
Show build identity and latest build link
2026-09-24 10:09:09 -04:00
The412Banner c39ea70cf5 DuckStation: don't try to inhibit a screensaver the session doesn't have 2026-09-24 09:46:59 -04:00
xXJsonDeruloXx 9039197349 Put build source below watermark identity 2026-09-24 09:43:09 -04:00
xXJsonDeruloXx 3345f1c32b Show build source and worktree state 2026-09-24 09:40:50 -04:00
The412Banner dbaaa5b541 Front end: one tile per disc - hide tracks a .cue/.gdi/.m3u names, skip BIOS folders 2026-09-24 09:39:38 -04:00
The412Banner 26bb514ccd Merge pull request #34 from The412Banner/feat/duckstation
DuckStation (PS1) setup + front-end controller focus
2026-09-24 09:34:53 -04:00
xXJsonDeruloXx 288e59e865 Add display choice to Android app launches 2026-09-24 09:30:34 -04:00
The412Banner 9584f94681 Front end: come back to the last focused tile or button, not the grid 2026-09-24 09:26:46 -04:00
The412Banner 3d051f018e Front end: leave touch mode on open so the rail item can take focus 2026-09-24 09:19:10 -04:00
The412Banner c5b58a938a Front end: outline the rail item on open, remember the pane control across Left/Right 2026-09-24 09:12:31 -04:00
The412Banner 181b320ee1 DuckStation (PS1) set up like ARMSX2
- From the rail: -batch -bigpicture -fullscreen (the same flags as PCSX2's,
  src/duckstation-qt/qthost.cpp) - straight into the game, gone when it is quit.
- bannerlator-pad-defaults: a new player starts on DuckStation's first-time setup
  with the BIOS folder in the ROMs folder (psx/bios, created, or an existing one),
  psx as the game list, Vulkan, the controller on Player 1 and the guide button
  on the pause menu (not the analog toggle). An existing install gets the guide
  and game-list changes, and once, its own empty BIOS folder moved to the ROMs
  folder and Automatic made Vulkan; later choices are kept. Users bring their own
  BIOS and games.
2026-09-24 09:03:34 -04:00
The412Banner 88803f1276 Front end: controller focus starts on the rail and moves between rail and page predictably
- The app opens with the selected rail item focused (its outline shows).
- Right from the rail enters the page on its main button (Play, Open desktop,
  Open <emulator>, Launch, Launch in <emulator>); Down from there reaches the
  tiles as before.
- Left from anything at the page's left edge - the main button, the first tile of
  any row - returns to the selected rail item, not whichever one is nearest.
  (Up, where the rail sits above the page.)
2026-09-24 08:59:45 -04:00
The412Banner 9ad38e0c3a Merge pull request #32 from The412Banner/feat/fullscreen-toggle
Stretch games to fill the screen (cog + live drawer), Quake-engine titles windowed at the session's size
2026-09-24 08:41:10 -04:00
The412Banner 691dbe80d9 Merge remote-tracking branch 'origin/main' into feat/fullscreen-toggle 2026-09-24 08:35:43 -04:00
The412Banner afdcf05915 Proton launch: Quake-engine titles stay windowed at the session's size
Quake III Arena, Team Arena, Return to Castle Wolfenstein and Jedi Academy set their own
resolution and switch the display to it. Under gamescope with the ARM64 Proton that switch is
refused ("calling CDS: failed, bad mode") and the game never gets OpenGL - device-seen on a
Retroid Pocket 5 on Proton Experimental build 25406135 - and a window left at the game's own size
draws small in a corner. Both wrappers (Valve's depot and an adopted GE or CachyOS) now add
+set r_fullscreen 0 +set r_mode -1 +set r_customwidth/height at BL_WIDTH x BL_HEIGHT for those
app ids, unless the user's launch options already set the video mode; the choice is logged.
2026-09-24 08:26:51 -04:00
xXJsonDeruloXx c11bca2dc2 Keep the build hash visible in rail watermark 2026-09-24 07:50:22 -04:00
xXJsonDeruloXx 2b25e9bedd Log session drawer controller routing 2026-09-24 07:46:20 -04:00
xXJsonDeruloXx 2853962c10 Move build status to launcher rail footer 2026-09-24 07:45:55 -04:00
xXJsonDeruloXx b7270186e0 Fix controller focus in session drawer 2026-09-24 07:36:48 -04:00
xXJsonDeruloXx e1ae162c79 Add direct touchscreen input for Steam sessions 2026-09-24 07:35:47 -04:00
xXJsonDeruloXx 7c1a603ddb Add second-screen controls close button 2026-09-24 07:35:26 -04:00
xXJsonDeruloXx 39141d5c23 Make session menu controller driven 2026-09-24 07:35:26 -04:00
xXJsonDeruloXx 8e3083507a Remove build badge caption 2026-09-24 07:33:16 -04:00
xXJsonDeruloXx c951c71f82 Show build identity on launcher 2026-09-24 07:33:16 -04:00
Kurt Himebauch 6a23556c03 Merge pull request #20 from The412Banner/controller-back-button-gestures 2026-09-24 06:47:23 -04:00
The412Banner 1f67850680 Progress log: GPU desktop, emulators like PS3, ARMSX2, game art (2026-09-24) 2026-09-24 06:30:39 -04:00
The412Banner ee941da5b4 Session drawer: stretch games to fill the screen, live
The same choice as the Steam cog's, in the drawer's Now group, changed while the client runs:
the app writes it to ~/.droiddeck-fill (and again at every session start, so a file an earlier
session left never disagrees), and a watcher in the Steam session hands each change to gamescope
as GAMESCOPE_FORCE_WINDOWS_FULLSCREEN on the root window, which gamescope reads whenever it
changes. The session log says so when it does.
2026-09-24 06:29:33 -04:00
The412Banner 6b109046cd Steam cog: stretch games to fill the screen, as a switch
0.1.6 forced every game window to the screen's size (--force-windows-fullscreen), which keeps
FlatOut filling the screen but leaves Quake 3 drawing small in the bottom-left: it sets its own
resolution and never looks at its window again, and OpenGL draws from that corner. The switch
sits in the Steam page's Games group, on by default as before, and reaches the session as
BL_GAMESCOPE_FORCE_FULLSCREEN, which the session script already reads. Listed in the device
report.
2026-09-24 06:26:12 -04:00
The412Banner abecd2d9d4 Merge pull request #29 from The412Banner/feat/armsx2-polish
ARMSX2 like RPCS3, controllers and on-screen controls everywhere, Back, game art
2026-09-24 05:44:41 -04:00
The412Banner 9b173da142 Front end: wide cover art shown whole, titles without dump tags
- A portrait tile or poster with wide art (a PS3 disc's ICON0, 320x176) shows it
  whole over a blurred, darkened copy of itself instead of cropping its sides
  ('Tomb Raider' read 'B RAI'); box art still fills the tile.
- A game's title drops the dump's tags - (USA), (En,Fr,Es,Pt), (v1.01), [!] -
  which stay in the file name and in the cover lookup.
2026-09-24 05:31:00 -04:00
The412Banner f161e061f6 Cover art: a PS3 disc image gives its own ICON0.PNG
The libretro PS3 collection holds 67 covers (Tomb Raider found none). A PS3 disc
image carries PS3_GAME/ICON0.PNG, unencrypted even on protected discs; it is read
out of the ISO 9660 image, the way an installed game's icon already is. The PS2
serial lookup uses the same reader.
2026-09-24 05:17:20 -04:00
The412Banner 550d8711f2 Front end: box art for emulator games, found automatically and kept
CoverArt looks a game up by its file name in RetroArch's libretro-thumbnails
collection for the system (exactly, then loosely: tags dropped, same region
preferred, against the collection's cached list of names), then by the disc's
own ID - PS2 serials from SYSTEM.CNF against xlenore/ps2-covers, GameCube/Wii IDs
against GameTDB. Covers are downloaded once into files/covers after the list is
shown, and the list is refreshed when some arrive; a game with none is retried
after three days; nothing is fetched offline. PS3 disc dumps use their own
PS3_GAME/ICON0.PNG.
2026-09-24 05:11:38 -04:00
The412Banner e4e93c5f4a Front end: Back steps out of a game or emulator page; PS2 and other games found deeper in their folder
- Back / B on an emulator page went back to Android (the app closed); it now
  returns to Desktop, a game page to its emulator, a Steam game to Steam - the
  same as each page's '‹' link.
- Games are looked for up to three folders inside the system folder
  (ROMs/ps2/games/<game>/<game>.iso showed ARMSX2 with 0 games), and the loose-file
  pass over the ROMs root no longer walks into other systems' folders.
2026-09-24 04:55:01 -04:00
The412Banner 1c7130545f Drawer: a game from the rail is headed by its emulator (ARMSX2, RPCS3), not "Desktop" 2026-09-24 04:43:24 -04:00
The412Banner 9f2c98d28d ARMSX2: a new player starts on the Vulkan renderer (GSRendererType::VK = 14), not Auto's pick 2026-09-24 04:32:58 -04:00
The412Banner 4ef3bc92de ARMSX2 like RPCS3: front-end launches, first-time setup, PCSX2 settings, 16K pages; on-screen controls everywhere
- From the rail ARMSX2 starts the game in its full-screen, controller-driven UI
  and exits with it (-batch -bigpicture -fullscreen).
- bannerlator-pad-defaults: a PCSX2 player's settings and memory cards are
  copied into ARMSX2 once; a new player starts on ARMSX2's first-time setup with
  the BIOS folder in the ROMs folder (ps2/bios, created, or ps2/firmware), the
  ps2 folder as the game list and the controller on Player 1; the guide button
  opens the pause menu (how a rail game is left) instead of toggling analog.
  Users bring their own BIOS and games.
- The catalog can name a file per memory page size ("pages"); the device's own
  size picks it (ARMSX2 has 4K and 16K builds).
- On-screen controls: Auto keeps them off the desktop (the screen is a touchpad
  there) and shows them for rail games and Steam when no controller is
  connected; the drawer offers the setting in every session.
2026-09-24 04:29:57 -04:00
MaxsTechReview e7f6f315b9 Merge branch 'The412Banner:main' into fix/proot-performance 2026-09-24 03:43:13 -04:00
xXJsonDeruloXx 582e467583 Revert "Improve accelerated drawer stick navigation"
This reverts commit 10f5506829.
2026-09-24 01:07:34 -04:00
xXJsonDeruloXx 9222f2e8e9 Improve accelerated drawer stick navigation 2026-09-24 01:04:29 -04:00
xXJsonDeruloXx 46eb874c3d Merge latest main and fix controller drawer focus 2026-09-24 00:54:32 -04:00
The412Banner c942cf2652 Merge pull request #26 from The412Banner/feat/armsx2
PS2: ARMSX2 replaces PCSX2 (ARM64 recompilers, full speed)
2026-09-24 00:12:18 -04:00
The412Banner 5b24a2c49f PS2 is ARMSX2 only: PCSX2 leaves the front end
On device NFS Underground 2 ran at 17 fps in PCSX2 (one core at 100%, the PS2's
CPUs interpreted) and 59.9 fps at 100% speed in ARMSX2 (EE thread ~15%, all
recompilers and fastmem on under proot). Existing PCSX2 configs are left alone;
bannerlator-pad-defaults still covers them.
2026-09-23 23:59:25 -04:00
The412Banner 1b44e5345f ARMSX2 (PS2 with ARM64 recompilers) in the front end, with Player 1 on the controller
Upstream PCSX2 interprets the PS2's CPUs on ARM64 (pcsx2/arm64/RecStubs.cpp):
NFS Underground 2 ran at 17 fps with one core at 100%. ARMSX2 is the PCSX2 fork
with ARM64 recompilers. It is listed beside PCSX2 for the ps2 folder, installs
from the desktop catalog (package armsx2), and bannerlator-pad-defaults gives it
the PCSX2 profile under its own folder (ARMSX2/inis/PCSX2.ini).
2026-09-23 23:55:20 -04:00
The412Banner 0812960935 Merge pull request #25 from The412Banner/feat/desktop-gpu
Desktop on the GPU (Vulkan by default), emulator controllers, PC keyboard, PS3 shader fixes
2026-09-23 23:16:51 -04:00
xXJsonDeruloXx f334738441 Merge remote-tracking branch 'origin/main' into controller-back-button-gestures
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/ui/FrontEndScreen.kt
2026-09-23 23:07:55 -04:00
xXJsonDeruloXx 06d9b7b1cc Replace Back inversion toggle with order menu 2026-09-23 23:05:12 -04:00
The412Banner 351882f0ea Compositor: xdg_positioner, xdg_popup and wl_output requests no longer abort
Positioners and outputs were created without an implementation, and get_popup
created nothing. libwayland aborts on the first request to a resource with no
implementation - the compositor and the app with it. On device: a gamescope
started from the pixman desktop (droiddeck-gpu) beside labwc took the app down
(tombstone: abort in libwayland-server under wl_event_loop_dispatch). Positioner
requests are accepted and ignored, popups are dismissed at once (popup_done),
wl_output.release destroys the resource.
2026-09-23 22:59:44 -04:00
The412Banner 6192d5be2c Desktop: Vulkan by default; a renderer that fails to start is remembered
vulkan is the default desktop renderer (settings list it first). When labwc
cannot start on a GPU renderer the desktop falls back to pixman as before, and
now records the renderer with the patched wlroots and Linux driver it failed
on, so later desktops go straight to pixman instead of waiting 20 s each time;
an app update, a different driver, or choosing a renderer again in the settings
(which deletes the record) retries.
2026-09-23 22:49:50 -04:00
The412Banner 76300aa8b7 PS3: one shader cache, no interpreter precompile
- A program from the rail draws with the desktop's Linux driver instead of the
  Steam session's. The driver's shader cache is keyed on its build, so with two
  drivers an emulator compiled everything once per way of starting it; seen on
  device: desktop on the imported Turnip 26.3.0, rail on the runtime's own.
- RPCS3's default shader mode builds 6650 interpreter pipelines every boot.
  bannerlator-pad-defaults starts a new install on Async Recompiler
  (multi-threaded) and moves an existing one still on the default once; a
  marker leaves any later choice in RPCS3's settings alone. Enum strings from
  rpcs3/Emu/system_config_types.cpp.
2026-09-23 22:35:28 -04:00
The412Banner ae863cc613 Controller profiles for every desktop emulator; PC keyboard under half the screen
bannerlator-pad-defaults now covers DuckStation, PCSX2, Dolphin (GameCube and
Wii), Cemu, melonDS, PPSSPP and RetroArch as well as RPCS3, from each project's
source: files an emulator would otherwise fill with keyboard-only defaults are
seeded when missing (Dolphin, Cemu, PPSSPP, RPCS3), or given SDL bindings in the
config the emulator wrote itself when nothing binds a controller yet
(DuckStation, PCSX2 with its SettingsVersion, melonDS). RetroArch gets an Xbox
360 profile for its sdl2 driver and is moved off udev, which finds no pads in the
runtime. The desktop re-runs it every 30 s, so an emulator's first config is
covered for its next start.

The PC keyboard takes 47% of the screen height, header included.
2026-09-23 22:20:04 -04:00
The412Banner 1d71e8c74d PC keyboard: shorter rows, so the program stays visible above it 2026-09-23 22:13:59 -04:00
The412Banner a6bab67508 Desktop programs: controller seen by every emulator, Player 1 profiles, big-core pinning
- The pad keeps Steam Input's virtual-gamepad identity only in Steam sessions.
  SDL ignores a Steam virtual gamepad unless run under Steam, so on the desktop
  and from the rail every SDL emulator saw no controller at all; there it is a
  plain Xbox 360 pad now, which SDL and evdev map by themselves.
- bannerlator-pad-defaults writes Player 1 on the controller for emulators that
  have no profile yet (RPCS3 first: without one it takes the keyboard). Run at
  desktop start and before a program from the rail; never overwrites.
- The desktop and everything started from it, and a program from the rail, run
  on the game cores when chosen, else every core but the slowest cluster
  (program_cores); BL_PIN_CORES=0 turns it off. Game cores reach every mode.
2026-09-23 22:06:47 -04:00
maxjivi05 b8fcef622e proot: build the runtime's proot from source with WinNative's performance fixes
The runtime carries Termux's proot 5.1.107-70 as a prebuilt binary, built from
termux/proot 4dba3afb (every file:line site in it matches that commit). The apk
now builds that same commit with the NDK, with three patches ported from
WinNative's feature/proot-enhancements, and prefers it over the runtime's copy:

- Tracees are hashed by pid, so each ptrace stop finds its tracee without
  walking a list of every thread, and terminated tracees are swept only after
  a termination.
- A clean absolute path three or more directories deep under the rootfs alone
  has its parent opened once and taken as canonical when the kernel's path for
  it is the one proot would build, instead of an lstat per component; the final
  component of a call that does not follow it is no longer lstat'ed. Extensions
  still see the parent's host path, and the f2fs workaround keeps the old walk.
- A clone3 thread keeps its inherited flags when clone_args cannot be read.

talloc is linked in statically from Samba's release tarball, so libproot.so
needs only libc and libdl. On an x86_64 Android 15 emulator the same build ran
a path and thread test suite with output identical to the unpatched commit
(fake id, fake root, link2symlink, with and without seccomp), and a stat-heavy
benchmark ran 25-27% faster, 31-36% with 1500 live threads.
2026-09-23 22:03:57 -04:00
The412Banner 3e8bcf6e97 Session: on-screen PC keyboard (Hardware) beside the Android keyboard
The drawer's Keyboard row has two buttons: Hardware opens ui/PcKeyboard, a full
PC layout - Esc, F1-F12, Ins/Del/Home/End/PgUp/PgDn, the number and letter rows,
Tab, Caps, both Shifts, Ctrl, Super, Alt, AltGr, Space and the arrows - whose
keys are evdev codes pressed and released on the compositor's wl_keyboard, as a
USB keyboard would; Android opens the Android keyboard as before. Keys go down
on touch and up on lift (the program's key repeat works); modifiers are sticky
(tap: next key, tap again: lock, again: off). A controller moves over the keys
with the d-pad and presses with A; B closes it.
2026-09-23 21:52:47 -04:00
The412Banner 61836fd532 Compositor: declare send_toplevel_configure before surface_commit 2026-09-23 21:41:23 -04:00
The412Banner b9ec8458f8 Desktop: remote runs from ~/.cache/droiddeck/run behind ~/.droiddeck-remote-run
For driving the desktop from a debugger: a script dropped there runs once inside
the session. Off unless the marker exists.
2026-09-23 21:37:16 -04:00
The412Banner 9263160f5c Compositor: answer a toplevel's initial commit with configure
xdg-shell has the compositor reply to the initial commit; we only sent one at
get_toplevel. wlroots' Wayland backend (the desktop's labwc) moves its toplevel
to a private queue after the initial commit and waits there; on the vulkan
renderer a buffer import in between read the early configure onto the default
queue, and labwc waited for ever (WAYLAND_DEBUG: commit, then nothing). The
early configure stays for Wine's driver.
2026-09-23 21:36:08 -04:00
The412Banner 60d53fda47 Desktop GPU: full labwc log on the GPU renderers; a labwc that hangs at start falls back to pixman
The vulkan desktop sat on the loading screen: labwc connected and never opened
its window, and it did not exit, so the exit-based fallback never fired. The
launcher now asks labwc's socket for an answer (wayland-info, 3 s) for up to
20 s, then stops it and starts the desktop on pixman. labwc runs with -d on
vulkan/gles2 so desktop.log shows where it stopped.
2026-09-23 21:24:24 -04:00
The412Banner f0d6638d26 CI: listing the apk's assets no longer breaks the pipe past 30 entries 2026-09-23 21:09:57 -04:00
The412Banner 4e7fb1664f wlroots: stage the published wlroots-0.20.2-p1 build 2026-09-23 21:04:16 -04:00
The412Banner 29597c3fbe Desktop GPU: menu games run in their own gamescope; labwc on the patched wlroots
droiddeck-gpu runs a program in a gamescope of its own on the app's compositor
(wayland-0), full screen over the desktop - the path the rail's play chips use,
where Vulkan works. droiddeck-desktop-gpu gives every Game/Emulator menu entry a
per-user copy through it (auto: while the desktop is on pixman), keeping 'Run in
a desktop window' as an action.

droiddeck-desktop loads /usr/local/lib/droiddeck-wlroots for the vulkan/gles2
renderers and falls back to pixman when labwc stops at start. The build stages
the wlroots release named by tools/wlroots/release.env, once published.
2026-09-23 21:02:24 -04:00
The412Banner b921ef6245 Desktop GPU: patched wlroots with a Vulkan DMA-BUF allocator; new windows take the keys
wlroots 0.20.2 (Arch's wlroots0.20, what the desktop's labwc links) gains an
allocator that exports Vulkan images as DMA-BUFs for a GPU node that is not a
DRM device - the Adreno KGSL stand-in - where gbm and the DRM lease path fail
('unable to create allocator'). linux-dmabuf skips its GEM import check on such
a node so client buffers are not all refused. Built by build-wlroots.yml.

Compositor: without a Wine desktop, a window that opens on top takes keyboard
focus (a gamescope started from the Linux desktop).
2026-09-23 20:59:57 -04:00
The412Banner 890fefc63f Merge pull request #23 from The412Banner/release/0.1.6
0.1.6
2026-09-23 19:20:02 -04:00
The412Banner 9e066a9b62 0.1.6 notes: say exactly what is fetched and what is bundled 2026-09-23 19:19:56 -04:00
The412Banner 0b4648e04d 0.1.6
versionCode 7, versionName 0.1.6; release notes in docs/releases/0.1.6.md.
2026-09-23 19:19:45 -04:00
The412Banner be45c66bd3 Merge pull request #22 from The412Banner/feat/first-login-proton
First sign-in fetches the ARM64 Proton and restarts the client once
2026-09-23 19:08:29 -04:00
The412Banner 85d555b44e Front end: credits and the primary button show a focus outline
With a controller, credits only showed the ripple's faint box on the
dark rail, and the primary button (Play) only grew slightly with a shine
that barely shows on its light fill. Both now get the 2 dp signal-blue
outline the other controls use; credits' text turns blue as well.
2026-09-23 19:00:52 -04:00
The412Banner c673cb41f5 Session drawer: opens focused on its first control, and the controller drives it
With the drawer open the controller still fed the game - PadBridge
consumed every pad key and axis - and nothing in the drawer had focus.
Now, while it is open, pad events go to Android's own handling instead:
the d-pad and stick move focus, and the framework's fallbacks make A
select and B Back (which closes the drawer), as on the app's main
screen. Opening it releases the game's pad (PadBridge.releaseAll), so a
button or stick held at that moment is not left down in the game.

It opens with focus on the STEAM button (in a desktop session, the first
setting), and the STEAM and ... buttons show a focus outline like the
rest of the app's controls. ToggleRow gains a modifier for its box.
2026-09-23 18:52:50 -04:00
The412Banner 9f33bdd094 First sign-in fetches the ARM64 Proton and restarts the client once, as Bannerlator does
On a fresh install the client starts signed out and drops the
steam://install/4427310 the session hands it, so the Proton Experimental
(ARM64) dialog only appeared after the user restarted the client by
hand. Ported from Bannerlator's session (device-proven there 2026-09-22):

- With no remembered sign-in, a watcher on logs/connection_log.txt waits
  for the sign-in to complete and asks the running client again.
- A watcher on appmanifest_4427310.acf waits for StateFlags 4, registers
  the compatibility tool again, leaves ~/.bl-restart-once and asks the
  client to -shutdown; the start loop sees the marker and starts it once
  more (not counted against the update cap), so the tool lists and
  defaults are current before the user installs a game.
- The client-branch check runs before every start, not once, since the
  client rewrites package/beta during its run.

The loading screen no longer says Steam keeps running in the background:
sessions pause when the app leaves the screen (Background behavior).
2026-09-23 18:48:19 -04:00
The412Banner 26ada56ad6 Merge pull request #21 from The412Banner/rename/droiddeck
Rename the app SteamDeck → DroidDeck
2026-09-23 18:42:42 -04:00
The412Banner ae6dcc9798 Rename the app SteamDeck → DroidDeck
App name, app ID and code package: DroidDeck, com.droiddeck.launcher
(sources moved to com/droiddeck/launcher, the 68 JNI functions renamed
with them). The new app ID installs beside SteamDeck rather than over it.

Everywhere the name was the app's own: the sidebar title, the session
report, share zips, the shell title, wake-lock and log tags, User-Agents,
the Compose theme, the Download folder and switch files (droiddeck-env,
-osc, -driver, -tu-debug, -no-pad, -pad-log, -no-hud, -wlr-renderer; the
old names are not read), and inside the runtime (droiddeck-desktop,
Firefox's droiddeck.js, the shortcuts tag, the rumble socket, debug and
cache paths). Build scripts, CI artifact (droiddeck-apk), CMake project,
README and the progress log follow.

Kept on purpose: Valve's Steam Deck (the -steamdeck flag,
steamdeck_publicbeta/stable, 'Steam Deck mode', the steamos.cloud
package URLs) and names inside the hosted desktop package (steamdeck.png,
steamdeck-steam, steamdeck-bigpicture). 'Desktop installed' now tests for
labwc instead of the launcher the app stages itself. A committed
__pycache__ .pyc is removed.
2026-09-23 18:28:04 -04:00
xXJsonDeruloXx e40b5ced7d Expose Back actions in app settings 2026-09-23 17:26:27 -04:00
xXJsonDeruloXx 3fa05f6c18 Add Back shortcuts for session menu and Steam QAM 2026-09-23 17:16:02 -04:00
The412Banner bd51571530 Merge pull request #16 from The412Banner/feat/qcom-driver-and-deck-fixes
Deck mode performance overlay, killed-session cleanup, NIS
2026-09-23 16:44:57 -04:00
The412Banner 2a679af9aa Driver page: d-pad right reaches a driver's trash can
The trash can sat inside the row's single clickable area, so with a
driver focused there was nothing to the right for a controller to move
to. Each row is now two focus targets side by side - the driver and its
trash can - linked both ways (right/left); the row's outline still
follows the driver part, so it looks the same.
2026-09-23 16:33:17 -04:00
The412Banner c2953f2c1f Session settings open with the first control focused
Opened from the cog, the Steam/Desktop session page had nothing focused:
the cog that was focused is gone and a controller had to find its way
back in. The page now puts focus on its first control (Resolution) as it
opens, so the d-pad works at once; coming back from a driver page still
returns focus to the driver box that opened it. ChoiceRow gains a
modifier for its box.
2026-09-23 16:33:16 -04:00
The412Banner acc04bfabb Driver pages: back returns to where you left the settings page
Backing out of the Runtime or Display driver page rebuilt the session's
settings page from the top with nothing focused. Its scroll position is
now held above the page (SettingsPage takes an optional ScrollState) and
survives the driver page, and controller focus returns to the driver box
that was pressed to open it (ValueChip takes a modifier for its
FocusRequester).
2026-09-23 16:33:16 -04:00
The412Banner c5a2dee234 Driver page: controller navigation and focus throughout
- Every control shows a focus outline: the driver rows (the list at the
  top had none), their trash cans (in red), the refresh button, Restore
  built-in drivers, the confirmation's buttons, and the page's Back
  (shared by every settings page).
- D-pad up from the first driver moves to the refresh button beside the
  title; the page opens with the driver in use focused.
- Back (the controller's B too) closes the driver page only, back to the
  session's settings, instead of leaving settings altogether.
- The delete confirmation opens on Cancel, so a stray A press never
  deletes a driver.
2026-09-23 16:33:16 -04:00
The412Banner 2cd88855f4 Driver selection as a full page with refresh and delete for every driver
Runtime driver and Display driver open a full page instead of a
drop-down, built from the settings widgets (as previewed): INSTALLED
lists every driver as a tappable row with a radio mark and a tag
(BUNDLED / DOWNLOADED / IMPORTED); AVAILABLE TO DOWNLOAD lists what the
last check of Banners-Turnip and WinNative found, with a Download button
and progress; Import stays at the bottom. The refresh button sits beside
the title (SettingsPage gains an action slot) and is the only thing that
goes online; it spins while checking.

Every driver gets a trash can with a confirmation, the bundled ones too:
deleting a bundled display driver removes its unpacked copy and hides it
(TurnipDriver.hideBundled), with 'Restore built-in drivers' to bring
them back. It stays in the apk and Auto still unpacks it on a GPU that
needs it, so a deletion can never leave a device without a driver. Auto
and Runtime default are settings, not drivers, and have no trash can.
2026-09-23 16:33:16 -04:00
The412Banner 97d74e3748 Driver menus: WinNative drivers, newest release per variant, scrolling menus
- WinNative-Emu/Drivers joins Banners-Turnip as a source: WN-Turnip
  (Android, v1.x line) and WN-Linux-Turnip (Linux, linux-v0.x line), each
  in its Balanced and Performance builds for every Adreno.
- Each source's recent releases are read newest first and every variant
  is offered from the newest release carrying it. Banners-Turnip stopped
  publishing its A8xx Linux build after v26.3.0-20260923, so the runtime
  menu listed no 8xx driver at all; it now offers that last one. The
  refresh line names each source's newest release and any that could not
  be reached.
- Drop-down menus are capped at the window's height and their entries
  scroll under the title: a driver menu with its downloads ran off the
  bottom of the screen, hiding the refresh line and Import.
2026-09-23 16:33:16 -04:00
The412Banner 9db6b09ecd Driver menus: latest Banners-Turnip release as downloads, refresh button, trash-can delete
Both driver menus (Runtime and Display) now offer the latest release of
The412Banner/Banners-Turnip: each asset that fits the menu and is not
installed yet is a 'Download Turnip <tag>' entry labelled by GPU
(plain / -A8xx / -710-720-Test zips for the display driver, -Linux ones
for the runtime driver; -Wayland ones are never offered). Nothing goes
online on its own: a refresh line checks GitHub when tapped and says
which release it found and when; the result is remembered. A download
goes through the same importer as a picked zip and lands as an ordinary
driver.

Imported and downloaded drivers get a trash-can button with a
confirmation instead of the small cross. Deleting the driver a mode is
set to puts that mode back on its default (Runtime default / Auto), and
a deleted release download is offered again.
2026-09-23 16:33:16 -04:00
The412Banner 88aab47da5 Deck mode performance overlay: stage Valve's aarch64 mangoapp
The QAM offers Performance Overlay levels but nothing drew them: the
runtime has no mangoapp. CI now fetches Valve's mangohud-20260902.1 and
the five libraries mangoapp needs that the runtime lacks, at the versions
it was built against (fmt 10.2.0, spdlog 1.13.0, glfw 3.4, libtraceevent
1.8.2, libtracefs 1.8.0 from Valve's mash-20240705.2 snapshot), each
pinned by sha256 in tools/mangoapp/packages.txt. They are staged under
usr/local/lib/mangoapp with a wrapper on PATH; the runtime image is not
touched.
2026-09-23 16:33:16 -04:00
The412Banner d21f102482 Session: clear a killed session's leftovers, offer NIS in Deck mode, mangoapp hook
Before the client starts, remove .crash, steam.pid and the web helper's
htmlcache Singleton* locks when no client is running - Android ends
sessions without warning and those files leave the next start on the
update spinner or a blank Gamepad UI (SteamOS-ARM-SM8550's RUNSTEAM.sh
does the same). Deck mode exports STEAM_GAMESCOPE_NIS_SUPPORTED=1 so the
QAM's scaling filter lists NIS, and passes gamescope --mangoapp when
mangoapp is present (BL_MANGOAPP=0 turns it off).
2026-09-23 16:33:16 -04:00
Kurt Himebauch 45dd582a2c Merge pull request #19 from The412Banner/remove-shade-default-home 2026-09-23 16:11:53 -04:00
xXJsonDeruloXx 0da2f0025c Remove Home app chooser from in-game shade 2026-09-23 16:06:49 -04:00
Kurt Himebauch 73e0a3b35d Merge pull request #18 from The412Banner/launcher-panel-refresh
Simplify launcher panels and manage compatibility tools in place
2026-09-23 16:03:26 -04:00
xXJsonDeruloXx 5b301246da Simplify launcher panels and package management 2026-09-23 15:57:44 -04:00
Kurt Himebauch 0a912db34b Merge pull request #13 from The412Banner/steamdeck-second-screen 2026-09-23 14:29:31 -04:00
xXJsonDeruloXx 964c876a3e Merge remote-tracking branch 'origin/main' into fix/termux-system-runtime
# Conflicts:
#	app/src/main/java/com/steamdeck/launcher/SessionActivity.kt
#	app/src/main/java/com/steamdeck/launcher/ui/SessionOverlay.kt
2026-09-23 14:22:49 -04:00
Kurt Himebauch 6c2e88a763 Merge pull request #17 from The412Banner/feat/android-home-launcher
Add Android home app integration
2026-09-23 14:17:56 -04:00
xXJsonDeruloXx 86092b32f8 Hide home setting from session drawer when inactive 2026-09-23 14:12:32 -04:00
xXJsonDeruloXx cd677f0125 Add Android home app integration 2026-09-23 14:08:16 -04:00
xXJsonDeruloXx 81bb9d2213 fix: rebuild Termux PTY helper with the app NDK 2026-09-23 14:05:27 -04:00
The412Banner 1ab25f0919 Merge pull request #15 from The412Banner/fix/play-without-runtime
Play on a fresh install: don't close the session screen before it installs the runtime
2026-09-23 13:49:47 -04:00
xXJsonDeruloXx 341561c570 ci: allow Android system libstdc++ 2026-09-23 13:48:23 -04:00
The412Banner 8e1db35f98 Play on a fresh install: the session screen no longer closes before it can install the runtime
SessionActivity finished at once when no runtime was installed - a guard from before the loading
screen could install it - so Play (and Desktop) opened a screen that closed before anything
happened. It now logs and carries on to installThenStart.
2026-09-23 13:38:35 -04:00
The412Banner e54010e9b9 Progress log: onboarding, Desktop & apps page, 4:3 display, PR #11 audio 2026-09-23 13:30:27 -04:00
The412Banner 0c48ec0954 Merge pull request #14 from The412Banner/fix/exact-panel-shape
Display: Exactly this panel shape + custom resolution per mode
2026-09-23 13:27:13 -04:00
xXJsonDeruloXx 96d3132431 feat: add Steam second-screen controls 2026-09-23 13:23:49 -04:00
The412Banner 92df14bb6e Custom resolution per mode, as in Bannerlator
Resolution gains Custom…: a width × height box (320×240 to 3840×2160, evened) with one-tap
presets for 4:3 (960×720, 1024×768, 1280×960), 16:10 (1280×800) and 16:9. A custom size replaces
the cap and the shape for that mode - the Shape row greys out and says so - and the compositor fits
it to the panel with bars where the shapes differ. Choosing any cap clears it. The device report
lists it for both modes.
2026-09-23 13:18:15 -04:00
The412Banner e3fe4ed94e Display shape: Exactly this panel, for 4:3 and 3:2 handhelds
The panel's shape was never allowed narrower than 16:9 (the foldable guard), so on a 4:3 panel
such as the Retroid Pocket's both choices gave 16:9 with bars. A third choice, Exactly this panel,
presents the panel's own aspect with no floor; the other two are unchanged and foldables still
default to 16:9. Offered in the mode settings and the in-session menu.
2026-09-23 13:14:36 -04:00
The412Banner c597dee0ae Merge pull request #12 from The412Banner/feat/play-installs-runtime
First-run onboarding: Play and Desktop install what they need; Desktop & apps as a page
2026-09-23 13:10:54 -04:00
The412Banner bde8dc0089 Desktop & apps is a page with a dropdown per tier, not a dialog
The package list opens in the front end's pane like the Settings pages. Each tier (Native ARM64,
ARM64 experimental, source builds) is a dropdown row with the rail's caret and an installed/total
pill; Native ARM64 starts open. Each package has its own Install/Remove button, and the one that is
installing shows its own step line and bar in its row (its tier opens when the install starts).
Leaving the page does not stop an install. The rail's Install emulators and Setup's Desktop & apps
highlight while the page is open.
2026-09-23 13:04:02 -04:00
The412Banner 0ea4b584b4 Desktop installs the runtime and the desktop on a fresh install, on the loading screen
Desktop is enabled without a runtime or the desktop package. The session's loading screen installs
whatever is missing first - the runtime, then the desktop ("downloading the desktop · 212 of 426
MB", checking, unpacking) - and starts the session when both are in. A failure ends on the loading
screen with the reason. Desktop UI and Desktop now give the same non-Adreno warning as Play before
any download.
2026-09-23 12:53:36 -04:00
The412Banner 2b5aae9100 Play installs the Linux runtime on a fresh install, on the loading screen
Play and Desktop UI are enabled without a runtime. The session's loading screen downloads and
unpacks it first, reporting on its usual step line and bar ("downloading the Linux runtime · 332
of 791 MB", then checking, then unpacking), and starts the session when it is in. A failure ends
on the loading screen with the reason. Non-Adreno devices still get the warning before any
download. Setup > Linux runtime is unchanged.
2026-09-23 12:48:49 -04:00
The412Banner 1e1160006c Merge pull request #11 from The412Banner/fix/directaudio-default
Audio defaults: classic client sink, DirectAudio for games and as a client option; mic on; Share latest logs
2026-09-23 12:20:07 -04:00
The412Banner ed569f675e Steam client audio: the classic sink by default, DirectAudio as a choice; audio diagnostics on
- The client's sound goes back to the adaptive AAudio sink 0.1.5 shipped (bundled unchanged as
  module-aaudio-classic-sink, sink name AAudioSink). Through the DirectAudio relay it stayed
  choppy on an AYN Thor (Android 13, 20 ms bursts) even with a two-burst buffer.
- Steam cog > Audio: DirectAudio for games (on by default, unchanged) and Steam client audio
  (Classic by default, or DirectAudio). The relay also starts when only the client wants it.
- The relay runs with --log and the daemon at info level, so audio.log carries the stream each
  path was granted and, on the relay, Android's xrun count every 1000 callbacks.
- Device report shows both settings.
2026-09-23 12:10:34 -04:00
The412Banner 0dfdc91425 Audio bundle re-unpacks whenever its contents change; the DirectAudio sink logs the buffer it asked for and what the relay settled at
The unpack check compared a hand-bumped stamp only. Two sink changes shipped without a bump, so a
device that had run the earlier build kept the old module: the 40 ms ring floor and the 24 ms
device buffer never reached the AYN Thor they were for. The stamp now carries a CRC-32 of
pulseaudio.tzst. The sink's connect line names the buffer it asked for, and one line ten seconds
in reports the relay's device buffer, ring target and underruns.
2026-09-23 11:50:24 -04:00
The412Banner 53a2d800e9 DirectAudio sink: ask the relay for a 24 ms device buffer
The relay's default is 12 ms rounded up to whole bursts. On a device where Android grants a fast
stream (4 ms bursts) that is three bursts; on one where it grants a legacy stream (20 ms bursts,
an AYN Thor on Android 13) it is a single burst, and the Thor's client audio stuttered. 24 ms is
two bursts there and 24 ms on the fast devices. buffer_ms= overrides it.
2026-09-23 11:38:46 -04:00
The412Banner 019928e783 Audio: keep at least 40 ms queued for the DirectAudio relay; logs: Share latest logs
- module-directaudio-sink keeps the relay's ring at least 40 ms deep. The relay starts at 8 ms,
  which the producer inside proot missed while the client loads (two underruns on the FIT's fresh
  install, steady stutter reported on another phone).
- Setup > Session logs > Share latest logs zips the newest session folder, wherever it was written
  (Downloads, or app-private files when Downloads is not writable), and opens the share sheet via
  a FileProvider scoped to that zip.
2026-09-23 11:27:25 -04:00
The412Banner fe3b9892a7 Fresh installs get DirectAudio and the microphone on; the old armhf sink leaves the audio bundle
- Microphone defaults to on, and the app asks for RECORD_AUDIO once at first launch, together with
  the storage prompt. A refusal is not asked again at start-up.
- modules/armhf held the inherited Winlator AAudio sink and two protocol modules; the app is
  arm64-only and nothing loads that folder. Removed, bundle stamp bumped so installs re-unpack.
2026-09-23 11:09:01 -04:00
The412Banner 097b67d7e1 DirectAudio on by default
Everyone who never touched the toggle gets it; anyone who turned it off keeps it off (the setting is
only stored when changed). Off, the client's sound came from AAudio driven inside proot, which
stutters on some devices.
2026-09-23 11:08:39 -04:00
Kurt Himebauch 5956154051 Merge pull request #7 from The412Banner/feature/session-suspend-audio-recovery 2026-09-23 11:01:00 -04:00
xXJsonDeruloXx c03e62d583 Add session suspend modes and audio recovery 2026-09-23 10:51:10 -04:00
The412Banner 0af137a06b Merge pull request #9 from The412Banner/feat/armada-and-lineage
Deck mode, added games with art, own gamescope and audio sinks, client sound via DirectAudio, Winlator lineage removed
2026-09-23 10:03:35 -04:00
The412Banner d338f73eec Merge remote-tracking branch 'origin/main' into feat/armada-and-lineage
# Conflicts:
#	app/src/main/java/com/steamdeck/launcher/ui/MainScreen.kt
#	app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt
#	app/src/main/java/com/steamdeck/launcher/ui/PerformanceDialog.kt
2026-09-23 09:56:51 -04:00
Kurt Himebauch 103287a279 Merge pull request #10 from The412Banner/chore/frontend-cleanup 2026-09-23 09:33:00 -04:00
xXJsonDeruloXx af8edfed25 chore: trim remaining frontend copy 2026-09-23 09:26:44 -04:00
The412Banner 5a33870a2d Rebase follow-up: main's copy of the credits and file manager with the Winlator lines removed; progress log entry for the branch 2026-09-23 09:13:12 -04:00
The412Banner a70f7b28da session: gamescope forces game windows to fill the screen in Steam mode (a game that shrank its window while the Steam menu was open came back in the top-left corner) 2026-09-23 09:12:39 -04:00
The412Banner 1b1fbd41da audio: the sink is named DirectAudio when the relay carries the sound, so the client's Audio settings show which road is in use 2026-09-23 09:12:39 -04:00
The412Banner 7f062818d3 settings: the DirectAudio toggle says it now carries the client's sound too 2026-09-23 09:12:39 -04:00
The412Banner bac5d0231a audio: the client's sound through the DirectAudio relay - module-directaudio-sink feeds the relay's render ring
A second sink module: connects to the relay's socket (lazily, the relay comes up after the
daemon; a clocked null sink until then), sends the hello, maps the render ring it gets back and
keeps it topped up to the relay's target with a futex wait per burst. Every AAudio call, the
adaptive buffer and the route handling stay in the relay, outside proot. With DirectAudio on the
daemon loads this sink instead of module-aaudio-sink; the bundle carries both.
2026-09-23 09:12:39 -04:00
The412Banner 6a52d4d58c aaudio sink: open the stream at the device's own rate unless rate= is given (44.1 kHz got a legacy stream: 20 ms bursts, three of them); the priority refusal is debug noise 2026-09-23 09:12:39 -04:00
The412Banner 168ef41ebf aaudio sink: a 30 ms starting buffer, half-buffer writes, the IO thread at audio priority, and underrun counts in the log
Two bursts (8 ms) of buffer with a write per burst left the client's menu sounds choppy on
device: the daemon runs under proot, where every system call is traced. The buffer now starts at
four bursts or 30 ms, whichever is more, each write hands over half of it, the IO thread asks for
nice -16 like Android's own audio threads, and the log says how big the buffer is and how many
underruns the device counted.
2026-09-23 09:12:39 -04:00
The412Banner 798ba5d6f2 front end: the shelf is laid out whole so the pad reaches every tile (a lazy grid only composes the rows on screen, and a press towards the next row bounced back among the visible tiles); added games' store art lookup starts on resume 2026-09-23 09:12:38 -04:00
The412Banner eb2bfa57aa steam shortcuts: art the user set inside the client is never recorded as ours, so it survives when a game's own art goes away 2026-09-23 09:12:26 -04:00
The412Banner 44ea7bae78 added games: artwork - the game folder's own images first, else Steam's store art for the title, into the client's grid
frontend/AddedGameArt: cover/poster/boxart/folder-name images, header, hero, logo and icon from
the game's folder (or its art subfolder); otherwise a one-time store search by folder name and
the capsule, header, hero and logo from Steam's CDN into the app's cache (a miss retried after a
week); an icon pulled from the .exe. The rail shows the portrait. The session's listing carries
the paths; bannerlator-steam-shortcuts copies them into userdata/<acct>/config/grid under the
client's names, never over a file it did not write itself, and sets the shortcut's icon. A toggle
in the cog turns the store lookup off.
2026-09-23 09:12:26 -04:00
The412Banner b834101cad added games: any number of Games folders from anywhere, each bound under /root/Games on its own
Folders are a list (the single folder from before carries over); each gets /root/Games/<name>,
with a short hash of its path only when two chosen folders share a name, so adding or removing
one never moves another's games (their appids follow the guest exe path). Per-game exe choices
are keyed by the folder's path now. The cog lists each folder with its game count and a Forget,
plus an Add row.
2026-09-23 09:12:26 -04:00
The412Banner 8f299d8752 deck mode: jupiter-dock-updater --check answers 7 (up to date); exit 0 read as an update waiting and showed a Dock Firmware Update row 2026-09-23 09:12:25 -04:00
The412Banner 76ba054a84 added games: bind the chosen Games folder into the session at /root/Games, so a folder on an SD card or anywhere else is seen (it was skipped unless it sat inside internal storage, the ROMs folder or the app's own SD library) 2026-09-23 09:12:25 -04:00
The412Banner 0e9460bedb aaudio sink: the sink is AAudioSink, as the daemon config expects; acknowledge rewind requests before rendering (assertion on device) 2026-09-23 09:12:15 -04:00
The412Banner 0ea06a3f05 audio: the APK build compiles the AAudio sink into the bundle; the committed bundle carries no sink module any more
The inherited module is gone from app/src/main/assets/pulseaudio.tzst (74 → 73 modules); CI adds
the app's own at build time and refuses a bundle that already has one. BUNDLE_STAMP bumped so an
installed app re-unpacks.
2026-09-23 09:12:15 -04:00
The412Banner b4c89b6f63 aaudio sink: declare pa__done ahead of use; tolerate the int-conversion in PulseAudio's own atomic.h 2026-09-23 09:12:15 -04:00
The412Banner 32178e6e4a audio: a fresh AAudio sink module for the daemon, built from upstream PulseAudio 13 headers
Blocking-write design: the IO thread pulls one burst at a time from the sink and hands it to a
blocking AAudio output stream, whose buffer paces the thread; latency comes from the stream's
playback timestamp; with adaptive=1 the buffer grows a burst per underrun up to a cap. Same
module arguments as before, so the daemon's config line is unchanged. build.sh compiles it with
the NDK against the app's own libpulse* and a small config.h; a workflow checks it on every push.
2026-09-23 09:12:15 -04:00
The412Banner c232274157 build: fetch the gamescope asset through the API (private repository), and key the preload cache on its pin 2026-09-23 09:12:15 -04:00
The412Banner 5b54bcfa49 gamescope: the apk carries build gamescope-3.16.29-p1 2026-09-23 09:12:15 -04:00
The412Banner cde82be161 steam shortcuts: name the source loader for the suffix-less registrar so the added games get their Proton mapping 2026-09-23 09:12:15 -04:00
The412Banner a6eb2ba1dd steam shortcuts: extend the buffer instead of rebinding it inside the nested writer (UnboundLocalError on device) 2026-09-23 09:12:15 -04:00
The412Banner f764885ecf gamescope build: apply the patches from the checkout Arch's prepare() leaves us in; concrete mirrors ahead of the redirector 2026-09-23 09:12:15 -04:00
The412Banner b0a52ef71f gamescope build: makepkg -A, the PKGBUILD names x86_64 only 2026-09-23 09:12:15 -04:00
The412Banner 93b3d49575 gamescope: the apk stages its patched build over /usr/local/bin when it carries one, the APK build fetches it from the release release.env names, and the session asks for realtime Vulkan queues 2026-09-23 09:12:15 -04:00
The412Banner 9841899cff gamescope build: pacman without its download sandbox inside the container 2026-09-23 09:12:15 -04:00
The412Banner 23f3e3ccf7 added games: plain null check instead of continue inside a lambda; gamescope build also runs on pushes touching its inputs 2026-09-23 09:12:15 -04:00
The412Banner 687b882983 gamescope: a build of the runtime's 3.16.29 with Armada's ARM64 client fixes and a realtime-queue switch, from Arch's own package in CI
Four of Armada's patches apply to 3.16.29 as they are (appid-focus fallback, ARM64 night mode,
ARM64 colour-temperature slider, P3 red); two more are ported by hand: realtime-priority Vulkan
queues on GAMESCOPE_FORCE_VULKAN_REALTIME=1 without CAP_SYS_NICE, and the gamepad cursor sprite
following the X pointer. build-gamescope.yml builds Arch Linux ARM's package with them in an
Arch container, checks every NEEDED library against the runtime's own list, and publishes the
binary as a release asset for the apk to stage over /usr/local/bin/gamescope.
2026-09-23 09:12:15 -04:00
The412Banner 65b00242d9 Added games: the user's own Windows games in the client's library
A Games folder chosen in the Steam cog is scanned one level deep, one subfolder per game; the
program to launch is the .exe named after the folder, else the largest, never an installer,
redistributable or crash reporter, and the settings page lets the user pick another for any
game. Each session the app writes the list, and bannerlator-steam-shortcuts puts the games into
the client's shortcuts.vdf as non-Steam games (the client's own entries kept) with the appid the
client itself derives, mapped to the ARM64 Proton through the compat registrar. They appear in
the rail under Steam and launch by steam://rungameid/ like any title.
2026-09-23 09:12:15 -04:00
The412Banner cc3b963dd0 Deck mode takes the Steam Deck channel unless a branch was chosen (device: publicbeta + Deck mode reinstalls every start) 2026-09-23 09:12:14 -04:00
The412Banner 773f4a6e6d Deck mode: the four helpers the client really calls, at the path it calls them
On device the client's console log showed the calls: /usr/bin/steamos-polkit-helpers/steamos-update,
steamos-select-branch, jupiter-biosupdate and jupiter-dock-updater - the polkit-helpers path, not
/usr/bin. The missing steamos-update there was the 'Update Error' dialog. Stubs at both paths now.

Also seen on device: -steamdeck alone opts the install into the steamdeck_stable branch too, so the
branch guard is what keeps a session from reinstalling; and on the publicbeta channel Deck mode
reports installed version 0 at every start and reinstalls the same client (rc=42, the launch URL
lost with it), while on steamdeck_publicbeta the second launch comes up with no restart at all.
2026-09-23 09:12:14 -04:00
The412Banner 3492229a00 lineage: the pad bridge's hat and Quick Access chord lines, and the audio component's context, on the new names 2026-09-23 09:12:14 -04:00
The412Banner ad21a8147f battery: time to full while charging, so the Quick Access Menu stops saying calculating 2026-09-23 09:12:14 -04:00
The412Banner a604dce6cb The last of the Winlator lineage rewritten out; rumble on the phone
The five files the audit measured as Winlator's are replaced: a one-method callback interface by
java.util.function.Consumer, the component base by SessionPart (attach an application context,
start, stop), the environment list by HostEnvironment, the process starter by HostProcess, and
the pad's state holder by PadState with named sticks, triggers, directions and buttons. The
frame-generation tree is framegen/ (its contents were never Winlator's: lsfg-vk's port and
Bannerlator's Win-FG), the log tags are the app's, and the fake pad's rumble socket is now one
the app actually serves: RumbleComponent turns a game's force-feedback effects into the phone's
vibrator, which used to go nowhere. The two design notes describing Bannerlator's X11 path are
gone, and the README and credits say what is whose.
2026-09-23 09:12:14 -04:00
The412Banner 35dbef0536 Deck mode made safe, a client-branch choice, and a battery the client can see
Deck mode passes -steamdeck alone: -steamos3 opted the install into the steamdeck_stable
branch and started the restart loop Bannerlator hit on device (38996ea1). The SteamOS helpers
the client calls in that mode - the two Armada found it needs under steamos-polkit-helpers and
the three under /usr/bin - are staged from the apk as no-op stubs. The client branch on disk is
kept in step with the one forced on the command line, and an update that cannot install stops
the session after three restarts instead of looping for ever.

The Steam cog gets a Client branch row: public beta (every session so far) or Steam Deck
public beta, the channel Armada bootstraps from.

The client reads /sys/class/power_supply/BAT<n>/; Android's supply is called battery and laid
out differently, so the Quick Access Menu had no battery. BatteryComponent writes BAT0 and BAT1
from Android's battery API every five seconds - status, percentage, charge, energy, power and
time to empty - into a directory bound over /sys/class/power_supply.
2026-09-23 09:11:54 -04:00
Kurt Himebauch f2768e33a8 Merge pull request #8 from The412Banner/chore/cleanup
chore: simplify UI copy and repository text
2026-09-23 08:32:51 -04:00
xXJsonDeruloXx 9de0d01c63 docs: move progress log under docs 2026-09-23 08:27:09 -04:00
xXJsonDeruloXx 7adce2bfd9 Merge remote-tracking branch 'origin/main' into chore/cleanup
# Conflicts:
#	README.md
2026-09-23 08:25:15 -04:00
xXJsonDeruloXx 40c42944f4 chore: remove lineage audit document 2026-09-23 08:23:55 -04:00
xXJsonDeruloXx 28ed06f18d chore: remove generated contributor ledger 2026-09-23 08:22:53 -04:00
xXJsonDeruloXx 751f8ca114 chore: simplify UI copy and repo text 2026-09-23 08:22:11 -04:00
The412Banner af796545f6 docs: lineage audit — inherited from Winlator vs written fresh in the forks; the AAudio sink is the one inherited piece that does real work 2026-09-22 23:58:25 -04:00
The412Banner c64f00eb8e docs: the lineage audit — what is measurably Winlator's, what only carries the name, and what leaving it would take 2026-09-22 23:54:07 -04:00
Kurt Himebauch 384a67f8e1 Merge pull request #6 from The412Banner/steam-qam-buttons 2026-09-22 23:39:23 -04:00
The412Banner 3451624b44 PROGRESS_LOG: after the 0.1.5 cut, and what comes next 2026-09-22 23:32:34 -04:00
xXJsonDeruloXx be54cafa81 Start drawer QAM chord on button press 2026-09-22 23:26:13 -04:00
The412Banner 377191d0ed PROGRESS_LOG: 0.1.5 released 2026-09-22 23:18:08 -04:00
The412Banner 2560edf0cf 0.1.5 notes 2026-09-22 23:17:28 -04:00
xXJsonDeruloXx 39cb09f33b Move Steam shortcuts to the drawer header 2026-09-22 23:17:14 -04:00
The412Banner de74abf10a 0.1.5 2026-09-22 23:13:14 -04:00
xXJsonDeruloXx b79650a2d1 Add QAM shortcut to the session drawer 2026-09-22 23:10:44 -04:00
xXJsonDeruloXx b3cdc14800 Add Steam and QAM touch controls 2026-09-22 23:05:29 -04:00
The412Banner a7aee50ea8 Merge remote-tracking branch 'origin/main' into feat/frontend-motion 2026-09-22 23:02:38 -04:00
The412Banner 21460ca35a front end: every category starts folded when the app opens 2026-09-22 22:55:38 -04:00
The412Banner 587acb4a75 themes: every screen colour comes from the palette; Setup › Theme switches at once 2026-09-22 22:49:21 -04:00
The412Banner 6c7587ec49 themes: the icon's colours three ways, chosen under Setup › Theme, Paper on black by default
Palette (ui/Theme.kt): background, surfaces, lines, type, a primary with its gradient partner,
and a separate signal colour for rules, rings, dots and the eyebrow - the blue in every theme,
while the primary is white in Paper and blue in the other two. Every colour the screens used to
hard-code (the purple gradient end, the green, the two line greys) now comes from the palette,
so a change under Setup applies at once, and the session drawer and the file manager pick the
saved theme up when they open.
2026-09-22 22:43:55 -04:00
Kurt Himebauch cba64a4e77 Merge pull request #5 from The412Banner/build/local-build-deploy 2026-09-22 22:42:59 -04:00
xXJsonDeruloXx a1514ab5ad Add reproducible local build and deploy helpers 2026-09-22 22:31:10 -04:00
The412Banner b010c0ad8e logo: the app's in-app mark is the new launcher icon (its two layers composed and cut to the launcher's circle), in the rail and the loading screen 2026-09-22 22:26:14 -04:00
Kurt Himebauch aca591bc74 Merge pull request #4 from The412Banner/update-app-icon 2026-09-22 22:16:47 -04:00
xXJsonDeruloXx 859ed20ac0 Update app launcher icon 2026-09-22 22:15:06 -04:00
The412Banner 8b2c6451c3 gitignore: the Kotlin compiler's session files 2026-09-22 21:54:33 -04:00
The412Banner 92b1a33471 PROGRESS_LOG: pull-request CI and the contributions ledger 2026-09-22 21:46:47 -04:00
Kurt Himebauch f1175e819f Merge pull request #3 from The412Banner/fix/session-back-key-routing 2026-09-22 21:43:54 -04:00
1922 changed files with 347857 additions and 40995 deletions

No files matched your search

+67
View File
@@ -0,0 +1,67 @@
language: en-US
early_access: false
tone_instructions: >-
Prioritize actionable correctness, security, and runtime regressions. Keep
feedback concise. Do not request explanatory code comments; preserve license
headers and compiler-consumed annotations.
reviews:
profile: chill
request_changes_workflow: false
high_level_summary: true
high_level_summary_in_walkthrough: true
poem: false
sequence_diagrams: false
tools:
github-checks:
enabled: false
auto_review:
enabled: true
auto_incremental_review: true
auto_pause_after_reviewed_commits: 0
drafts: false
pre_merge_checks:
docstrings:
mode: "off"
title:
mode: "off"
description:
mode: "off"
finishing_touches:
docstrings:
enabled: false
unit_tests:
enabled: false
simplify:
enabled: false
autofix:
enabled: false
fix_ci:
enabled: false
resolve_merge_conflict:
enabled: false
path_instructions:
- path: "**/*"
instructions: >-
For cleanup changes, compare executable behavior against the PR merge
base. Check for removed guards, changed return values, weakened compiler
flags, and deleted regression tests. A passing APK build alone does not
establish behavior preservation. Avoid style-only or comment-coverage
requests.
- path: "app/src/main/**"
instructions: >-
Review Android lifecycle, external-display visibility, input cancellation,
permissions, threading, and Compose state. For JNI, native, and Rust
changes, check ABI compatibility, resource ownership, synchronization,
and error paths. Preserve persisted user settings and existing runtime
contracts.
- path: "tools/**"
instructions: >-
Check shell quoting, error handling, path and filesystem semantics, and
Python helper behavior. Preserve MSI same-file protection and strict
compiler checks. Verify downstream patches apply to their pinned sources
and retain intended executable behavior, including embedded scripts.
- path: ".github/workflows/**"
instructions: >-
Check job permissions, triggers, merge validation, build variants,
signing, artifact paths, and release conditions. Verify checks apply to
the current PR head and keep review configuration advisory.
+12
View File
@@ -0,0 +1,12 @@
tools/linuxfs/** text=auto eol=lf
tools/mangoapp/** text=auto eol=lf
tools/comet/LICENSE whitespace=-blank-at-eof
tools/comet/droiddeck.patch whitespace=-blank-at-eol,-blank-at-eof
# These helpers are packaged verbatim and executed inside the Linux runtime.
tools/linuxfs/overlay/** text eol=lf
tools/linuxfs/desktop/** text eol=lf
tools/mangoapp/mangoapp text eol=lf
app/src/main/assets/desktop-removal/*.json text eol=lf
app/src/main/assets/desktop-removal/*.txt text eol=lf
*.pdf binary
+40
View File
@@ -0,0 +1,40 @@
name: Build validation
on:
pull_request_target:
branches: [main]
types: [opened, synchronize, reopened, edited, ready_for_review]
push:
branches: [main]
workflow_run:
workflows: [Build APK]
types: [completed]
workflow_dispatch:
inputs:
pr_number:
description: Pull request to validate; empty for main
required: false
permissions:
contents: read
actions: read
pull-requests: read
statuses: write
jobs:
validate:
name: Build APK
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.workflow_sha }}
fetch-depth: 0
persist-credentials: false
- name: Validate source and APK compilation
env:
GH_TOKEN: ${{ github.token }}
run: |
gh auth setup-git
python3 tools/release/validate_apk.py
-2
View File
@@ -1,7 +1,5 @@
name: Build AAudio sink module
# A quick check of tools/aaudio-sink on its own; the APK build compiles the same module into the
# audio bundle. Runs on pushes that touch the module.
on:
push:
paths:
+96
View File
@@ -0,0 +1,96 @@
name: Build desktop package (KDE Plasma)
on:
push:
paths:
- 'tools/desktop-kde/**'
- '.github/workflows/build-desktop-kde.yml'
workflow_dispatch:
inputs:
tag:
description: 'Release tag to publish as (e.g. desktop-kde-r1); empty = artifact only'
required: false
default: ''
permissions:
contents: write
jobs:
kwin:
runs-on: ubuntu-24.04-arm
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- name: Build KWin with nested desktop scaling
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq zstd xz-utils gpgv patch binutils >/dev/null
bash tools/desktop-kde/build-kwin.sh out/kwin
- uses: actions/upload-artifact@v4
with:
name: desktop-kde-kwin
retention-days: 7
path: out/kwin/
build:
needs: kwin
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: desktop-kde-kwin
path: out/kwin
- uses: actions/create-github-app-token@v2
id: components-token
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-Components
- name: Refuse a published tag
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
env:
GH_TOKEN: ${{ steps.components-token.outputs.token }}
run: |
if gh release view '${{ inputs.tag }}' -R Droid-Deck/DroidDeck-Components >/dev/null 2>&1; then
echo "::error::${{ inputs.tag }} is already published, and published tags are never replaced: use a new tag"
exit 1
fi
- name: Build
run: |
set -euo pipefail
sudo apt-get update -qq && sudo apt-get install -y -qq zstd xz-utils gpgv >/dev/null
KDE_KWIN_OVERRIDE="$PWD/out/kwin" tools/desktop-kde/build.sh out
cp out/kwin/desktop-kde.kwin.txt out/
echo "size $(stat -c %s out/desktop-kde.tar.zst) sha256 $(cat out/desktop-kde.sha256)"
echo "language packs $(ls out/desktop-kde-i18n-*.tar.zst | wc -l)"
- uses: actions/upload-artifact@v4
with:
name: desktop-kde
retention-days: 7
compression-level: 0
path: |
out/desktop-kde.tar.zst
out/desktop-kde.packages.txt
out/desktop-kde.kwin.txt
out/desktop-kde.sha256
out/desktop-kde.i18n.txt
out/desktop-kde-i18n-*.tar.zst
- name: Publish
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
env:
GH_TOKEN: ${{ steps.components-token.outputs.token }}
GH_REPO: Droid-Deck/DroidDeck-Components
run: |
set -euo pipefail
TAG='${{ inputs.tag }}'
if gh release view "$TAG" >/dev/null 2>&1; then
echo "::error::$TAG is already published in DroidDeck-Components, and published tags are never replaced: use a new tag"
exit 1
fi
gh release create "$TAG" --latest=false --prerelease=false --title "$TAG" \
--notes "KDE Plasma desktop package for DroidDeck's Linux runtime ($GITHUB_REPOSITORY tools/desktop-kde, $GITHUB_SHA). size $(stat -c %s out/desktop-kde.tar.zst), sha256 $(cat out/desktop-kde.sha256). desktop-kde.packages.txt lists the signature-checked Arch Linux ARM packages it was built from." \
out/desktop-kde.tar.zst out/desktop-kde.sha256 out/desktop-kde.packages.txt out/desktop-kde.kwin.txt \
out/desktop-kde.i18n.txt out/desktop-kde-i18n-*.tar.zst
gh release edit "$TAG" --latest=false --prerelease=false
+49
View File
@@ -0,0 +1,49 @@
name: Build FEX (x86-64 Steam client)
# Checks tools/fex: the same build DroidDeck-Components' build-fex.yml runs from this repository to publish FEX
# (its single `fex` release and fex.json, which the app follows). Nothing is published from here.
on:
push:
paths:
- 'tools/fex/**'
- '.github/workflows/build-fex.yml'
workflow_dispatch:
inputs:
thunks:
description: 'Build the host thunks (Vulkan/GL/X11/... to the runtime arm64 libraries)'
required: false
default: 'false'
cxxflags:
description: 'Extra CXXFLAGS for a diagnostic build (e.g. -DDEBUG_STRACE)'
required: false
default: ''
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04-arm
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build in Arch Linux ARM
# Inputs reach the shell as environment variables, never pasted into the script.
env:
FEX_EXTRA_CXXFLAGS: ${{ inputs.cxxflags }}
FEX_THUNKS: ${{ inputs.thunks == 'true' && '1' || '0' }}
run: |
set -euo pipefail
docker run --rm -e FEX_EXTRA_CXXFLAGS="$FEX_EXTRA_CXXFLAGS" -e FEX_THUNKS="$FEX_THUNKS" -v "$PWD:/work" -w /work menci/archlinuxarm:base-devel bash tools/fex/build-in-arch.sh
sudo chown -R "$(id -u):$(id -g)" .
- uses: actions/upload-artifact@v4
with:
name: fex
path: |
fex.tzst
fex.tzst.sha256
fex-needed.txt
fex-files.txt
+2 -5
View File
@@ -1,9 +1,5 @@
name: Build gamescope (patched)
# The runtime's own gamescope (Arch Linux ARM's 3.16.29 package) rebuilt with the patches under
# tools/gamescope/patches, in an Arch Linux ARM container on an arm64 runner, then published as a
# release asset the apk build fetches (tools/gamescope/release.env names the tag and sha). The
# hosted runtime image is never changed: the apk stages the binary over /usr/local/bin.
on:
push:
paths:
@@ -42,4 +38,5 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh release create "${{ inputs.tag }}" --title "${{ inputs.tag }}" --notes "gamescope 3.16.29 with tools/gamescope/patches; staged by the apk over /usr/local/bin/gamescope. sha256 $(cut -d' ' -f1 gamescope.tzst.sha256)" gamescope.tzst gamescope.tzst.sha256 || gh release upload "${{ inputs.tag }}" gamescope.tzst gamescope.tzst.sha256 --clobber
gh release create "${{ inputs.tag }}" --latest=false --prerelease=false --title "${{ inputs.tag }}" --notes "gamescope 3.16.29 with tools/gamescope/patches; staged by the apk over /usr/local/bin/gamescope. sha256 $(cut -d' ' -f1 gamescope.tzst.sha256)" gamescope.tzst gamescope.tzst.sha256 || gh release upload "${{ inputs.tag }}" gamescope.tzst gamescope.tzst.sha256 --clobber
gh release edit "${{ inputs.tag }}" --latest=false --prerelease=false
-46
View File
@@ -1,46 +0,0 @@
name: Build wlroots (patched)
# The desktop's wlroots (Arch Linux ARM's wlroots0.20, what the desktop package's labwc links)
# rebuilt with tools/wlroots/patches: a Vulkan DMA-BUF allocator for the Adreno node, which is not
# a DRM device, so labwc's vulkan and gles2 renderers can start. Built in an Arch Linux ARM
# container on an arm64 runner and published as a release asset the apk build fetches
# (tools/wlroots/release.env names the tag and sha). The hosted desktop package is never changed.
on:
push:
paths:
- 'tools/wlroots/**'
- '.github/workflows/build-wlroots.yml'
workflow_dispatch:
inputs:
tag:
description: 'Release tag to publish as (e.g. wlroots-0.20.2-p1); empty = artifact only'
required: false
default: ''
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-24.04-arm
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- name: Build in Arch Linux ARM
run: |
set -euo pipefail
docker run --rm -v "$PWD:/work" -w /work menci/archlinuxarm:base-devel bash tools/wlroots/build-in-arch.sh
sudo chown -R "$(id -u):$(id -g)" .
- uses: actions/upload-artifact@v4
with:
name: wlroots-patched
path: |
wlroots.tzst
wlroots.tzst.sha256
- name: Publish
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh release create "${{ inputs.tag }}" --title "${{ inputs.tag }}" --notes "wlroots 0.20.2 with tools/wlroots/patches (Vulkan DMA-BUF allocator for non-DRM GPU nodes); staged by the apk at /usr/local/lib/droiddeck-wlroots for the desktop's GPU renderers. sha256 $(cut -d' ' -f1 wlroots.tzst.sha256)" wlroots.tzst wlroots.tzst.sha256 || gh release upload "${{ inputs.tag }}" wlroots.tzst wlroots.tzst.sha256 --clobber
+574 -84
View File
@@ -22,23 +22,105 @@ permissions:
pull-requests: write
concurrency:
group: build-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
group: build-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
PRELOAD_CACHE_VERSION: v1
PRELOAD_CACHE_VERSION: v6
jobs:
build:
name: Build APK
xbox:
name: Xbox ARM64 backend
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ubuntu-24.04
timeout-minutes: 45
runs-on: ubuntu-24.04-arm
timeout-minutes: 30
outputs:
base-sha: ${{ steps.merge.outputs.base-sha }}
tree-sha: ${{ steps.source.outputs.tree-sha }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Pin and merge the APK source
id: merge
if: github.event_name == 'pull_request'
run: |
set -euo pipefail
git fetch --no-tags origin main
echo "base-sha=$(git rev-parse origin/main)" >> "$GITHUB_OUTPUT"
git config user.name "ci"
git config user.email "[email protected]"
git merge --no-ff --no-edit origin/main
- name: Record backend source tree
id: source
run: echo "tree-sha=$(git rev-parse 'HEAD^{tree}')" >> "$GITHUB_OUTPUT"
- uses: actions/cache@v4
id: xbox-cache
with:
path: |
app/src/main/assets/linuxfs/usr/local/lib/droiddeck-xbox
app/build/xbox/xodus-droiddeck-source.tar.xz
app/build/xbox/runtime/*-source.tar.xz
key: xbox-arm64-${{ hashFiles('tools/xbox/**') }}
- name: Build Xbox backend
if: steps.xbox-cache.outputs.cache-hit != 'true'
run: |
rustup toolchain install 1.99.0 --profile minimal
sudo apt-get update -qq
sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler cmake xz-utils
bash tools/xbox/build.sh
- uses: actions/upload-artifact@v4
with:
name: xbox-arm64
path: app/src/main/assets/linuxfs/usr/local/lib/droiddeck-xbox
retention-days: 14
- uses: actions/upload-artifact@v4
with:
name: xbox-corresponding-source
path: |
app/build/xbox/xodus-droiddeck-source.tar.xz
app/build/xbox/runtime/*-source.tar.xz
if-no-files-found: error
retention-days: 14
build:
needs: xbox
name: Compile and test APK
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
runs-on: ubuntu-24.04
timeout-minutes: 45
outputs:
variants: ${{ steps.variants.outputs.list }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Check human authorship
env:
EVENT_NAME: ${{ github.event_name }}
BEFORE: ${{ github.event.before }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = pull_request ]; then
git fetch --no-tags origin main
base=$(git merge-base origin/main HEAD)
checker="$RUNNER_TEMP/droiddeck-check-authorship.py"
git show origin/main:tools/release/check_authorship.py > "$checker"
python3 "$checker" "$base..HEAD" --event "$GITHUB_EVENT_PATH"
elif [ "$EVENT_NAME" = push ] && [ "$BEFORE" != 0000000000000000000000000000000000000000 ]; then
python3 tools/release/check_authorship.py "$BEFORE..HEAD"
else
python3 tools/release/check_authorship.py HEAD
fi
- name: The packages a signed build ships as
id: variants
run: echo "list=$(awk '!/^#/ && NF { print $1 }' tools/release/variants.txt | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
- name: No private key in the tree
run: |
@@ -49,16 +131,35 @@ jobs:
exit 1
fi
- name: Version bumps are isolated
if: github.event_name == 'pull_request'
run: |
set -euo pipefail
git fetch --no-tags origin main
base=$(git merge-base origin/main HEAD)
diff=$(git diff -U0 "$base..HEAD" -- app/build.gradle)
grep -Eq '^[+-][[:space:]]*version(Code|Name)[[:space:]]' <<<"$diff" || exit 0
other_files=$(git diff --name-only "$base..HEAD" | grep -v '^app/build.gradle$' || true)
other_gradle=$(grep -E '^[+-]' <<<"$diff" | grep -Ev '^(---|\+\+\+)' | grep -Ev '^[+-][[:space:]]*version(Code|Name)[[:space:]]' || true)
if [[ -n "$other_files" || -n "$other_gradle" ]]; then
echo "::error::versionCode/versionName changes must be isolated in their own PR"
echo "A version PR may only change the versionCode/versionName lines in app/build.gradle." >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
- name: Merge onto main, or say what conflicts
if: github.event_name == 'pull_request'
env:
BUILD_BASE_SHA: ${{ needs.xbox.outputs.base-sha }}
run: |
set -uo pipefail
git config user.name "ci"
git config user.email "[email protected]"
git fetch --no-tags origin main
BASE=$(git rev-parse --short origin/main)
git fetch --no-tags origin "$BUILD_BASE_SHA"
BASE=$(git rev-parse --short FETCH_HEAD)
HEAD=$(git rev-parse --short HEAD)
if git merge --no-ff --no-edit origin/main >/tmp/merge.out 2>&1; then
if git merge --no-ff --no-edit FETCH_HEAD >/tmp/merge.out 2>&1; then
{
echo "### Mergeable"
echo
@@ -105,6 +206,11 @@ jobs:
echo "::error::the pull request conflicts with main: $(echo $FILES | tr '\n' ' ')"
exit 1
- name: Verify backend and APK source match
env:
XBOX_SOURCE_TREE: ${{ needs.xbox.outputs.tree-sha }}
run: test "$(git rev-parse 'HEAD^{tree}')" = "$XBOX_SOURCE_TREE"
- uses: actions/setup-java@v4
with:
distribution: temurin
@@ -140,6 +246,8 @@ jobs:
label="${BUILD_REF} at ${BUILD_SHA:0:7}"
fi
echo "label=$label" >> "$GITHUB_OUTPUT"
echo "DROIDDECK_SOURCE_COMMIT=${PR_HEAD_SHA:-$BUILD_SHA}" >> "$GITHUB_ENV"
echo "DROIDDECK_PR_NUMBER=${PR_NUMBER:-0}" >> "$GITHUB_ENV"
echo "Build: \`$label\`" >> "$GITHUB_STEP_SUMMARY"
- name: Cache the preloads
@@ -147,26 +255,39 @@ jobs:
uses: actions/cache@v4
with:
path: app/src/main/assets/linuxfs
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/mangoapp/*') }}
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'app/src/main/cpp/adrenotools/src/hook/kgsl.h', 'tools/quest/*', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/build-x86-preloads.sh', 'tools/linuxfs/fex/*', 'tools/linuxfs/clipboard/*.c', 'tools/linuxfs/ssbs/*.c', 'tools/proot/fastpath/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/linuxfs/uruntime.env', 'tools/linuxfs/licenses/*', 'tools/mangoapp/*', 'tools/msitools/**', 'tools/comet/**', 'tools/xbox/**', 'tools/linuxfs/debian-image.env', 'tools/vr/**') }}
- name: Build libfakeinput.so and libblsession.so (glibc aarch64)
if: steps.preloads.outputs.cache-hit != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
sudo apt-get update -qq
sudo apt-get install -y -qq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu >/dev/null
sudo apt-get -o Dir::Etc::sourcelist=sources.list.d/ubuntu.sources \
-o Dir::Etc::sourceparts=- -o Acquire::Retries=3 update -qq
sudo apt-get install -y -qq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libvulkan1 mesa-vulkan-drivers >/dev/null
D=app/src/main/assets/linuxfs
mkdir -p "$D"
aarch64-linux-gnu-g++ -shared -fPIC -O2 -Wall -Wno-attributes -Wno-nonnull-compare \
-pthread -std=c++17 -static-libstdc++ -static-libgcc \
-pthread -std=c++17 -static-libstdc++ -static-libgcc -Wl,--exclude-libs,ALL \
-o "$D/libfakeinput.so" app/src/main/cpp/fakeinput_steam.cpp -ldl
aarch64-linux-gnu-strip --strip-unneeded "$D/libfakeinput.so"
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
-o "$D/libblsession.so" tools/linuxfs/preload/*.c -ldl
aarch64-linux-gnu-strip --strip-unneeded "$D/libblsession.so"
for script in tools/linuxfs/overlay/usr/local/bin/bannerlator-*; do
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
-o "$D/libblfastpath.so" tools/proot/fastpath/fastpath.c -ldl
aarch64-linux-gnu-strip --strip-unneeded "$D/libblfastpath.so"
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -nostdlib -ffreestanding -fno-stack-protector \
-fno-tree-loop-distribute-patterns -Wl,-z,defs -o "$D/libblaudit.so" tools/proot/fastpath/audit.c
aarch64-linux-gnu-strip --strip-unneeded "$D/libblaudit.so"
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
-o "$D/libssbs.so" tools/linuxfs/ssbs/ssbs_adapter.c -ldl
aarch64-linux-gnu-strip --strip-unneeded "$D/libssbs.so"
mkdir -p "$D/usr/local/bin"
aarch64-linux-gnu-gcc -O2 -Wall -Wextra -o "$D/usr/local/bin/droiddeck-clipboard" tools/linuxfs/clipboard/clipboard.c -ldl
aarch64-linux-gnu-strip --strip-unneeded "$D/usr/local/bin/droiddeck-clipboard"
VK_INCLUDE=$(ls -d "${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"/toolchains/llvm/prebuilt/*/sysroot/usr/include | head -1) \
tools/vr/build.sh --native "$D/usr/local/lib/droiddeck-vr"
for script in tools/linuxfs/overlay/usr/local/bin/droiddeck-* tools/linuxfs/overlay/usr/local/bin/steam-compatibility; do
install -Dm644 "$script" "$D/usr/local/bin/$(basename "$script")"
done
for f in tools/linuxfs/overlay/usr/bin/* tools/linuxfs/overlay/usr/bin/steamos-polkit-helpers/*; do
@@ -174,20 +295,18 @@ jobs:
done
if [ -f tools/gamescope/release.env ]; then
. tools/gamescope/release.env
gh release download "${GAMESCOPE_TAG}" -R "${GITHUB_REPOSITORY}" -p gamescope.tzst -O gamescope.tzst
curl -fsSL --retry 3 -o gamescope.tzst "https://github.com/${GAMESCOPE_REPO:-${GITHUB_REPOSITORY}}/releases/download/${GAMESCOPE_TAG}/gamescope.tzst"
echo "${GAMESCOPE_SHA256} gamescope.tzst" | sha256sum -c -
tar --use-compress-program=unzstd -xf gamescope.tzst -C "$D"
test -f "$D/usr/local/bin/gamescope"
rm gamescope.tzst
fi
if [ -f tools/wlroots/release.env ]; then
. tools/wlroots/release.env
gh release download "${WLROOTS_TAG}" -R "${GITHUB_REPOSITORY}" -p wlroots.tzst -O wlroots.tzst
echo "${WLROOTS_SHA256} wlroots.tzst" | sha256sum -c -
tar --use-compress-program=unzstd -xf wlroots.tzst -C "$D"
test -f "$D/usr/local/lib/droiddeck-wlroots/libwlroots-0.20.so"
rm wlroots.tzst
fi
. tools/linuxfs/uruntime.env
curl -fsSL --retry 3 -o uruntime "https://github.com/VHSgunzo/uruntime/releases/download/${URUNTIME_VERSION}/${URUNTIME_ASSET}"
echo "${URUNTIME_SHA256} uruntime" | sha256sum -c -
install -Dm644 uruntime "$D/usr/local/lib/droiddeck/uruntime"
install -Dm644 tools/linuxfs/licenses/uruntime-LICENSE "$D/usr/local/share/licenses/uruntime/LICENSE"
rm uruntime
M="$D/usr/local/lib/mangoapp"
mkdir -p "$M" mango-pkgs
grep -v '^#' tools/mangoapp/packages.txt | while read -r sum url; do
@@ -198,21 +317,122 @@ jobs:
rm mango-pkgs/pkg.tar.zst
done
install -m644 mango-pkgs/usr/bin/mangoapp "$M/mangoapp"
for so in libfmt.so.10 libspdlog.so.1.13 libglfw.so.3 libtraceevent.so.1 libtracefs.so.1; do
for so in libfmt.so.10 libspdlog.so.1.13 libglfw.so.3 libtraceevent.so.1; do
cp -L "mango-pkgs/usr/lib/$so" "$M/$so"
done
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -Wl,-soname,libtracefs.so.1 \
-o "$M/libtracefs.so.1" tools/mangoapp/libtracefs-shim.c
aarch64-linux-gnu-strip --strip-unneeded "$M/libtracefs.so.1"
install -Dm644 tools/mangoapp/mangoapp "$D/usr/local/bin/mangoapp"
rm -rf mango-pkgs
ls -l "$M"
mkdir -p pw-pkgs
grep -v '^#' tools/pipewire/packages.txt | while read -r sum url; do
[ -n "$url" ] || continue
curl -fsSL --retry 3 -o pw-pkgs/pkg.tar.zst "$url"
echo "$sum pw-pkgs/pkg.tar.zst" | sha256sum -c -
tar --use-compress-program=unzstd -xf pw-pkgs/pkg.tar.zst -C pw-pkgs
rm pw-pkgs/pkg.tar.zst
done
tools/pipewire/assemble.sh pw-pkgs "$D/usr/local/lib/droiddeck-pipewire.tzst"
rm -rf pw-pkgs
ls -l "$D/usr/local/lib/droiddeck-pipewire.tzst"
T="$D/usr/local/lib/droiddeck-msitools"
mkdir -p "$T" msi-pkgs
grep -v '^#' tools/msitools/packages.txt | while read -r sum url; do
[ -n "$url" ] || continue
curl -fsSL --retry 3 -o msi-pkgs/pkg.tar.xz "$url"
echo "$sum msi-pkgs/pkg.tar.xz" | sha256sum -c -
tar -xJf msi-pkgs/pkg.tar.xz -C msi-pkgs
rm msi-pkgs/pkg.tar.xz
done
install -m644 msi-pkgs/usr/bin/msiinfo "$T/msiinfo"
install -m644 msi-pkgs/usr/bin/cabextract "$T/cabextract"
install -m644 msi-pkgs/usr/lib/7zip/7z "$T/7z"
install -m644 msi-pkgs/usr/lib/7zip/7z.so "$T/7z.so"
for so in libmsi-1.0.so.0 libgsf-1.so.114 libgcab-1.0.so.0; do
cp -L "msi-pkgs/usr/lib/$so" "$T/$so"
done
for f in NOTICE GPL-2 GPL-3 LGPL-2.1 7zip-License 7zip-unRarLicense; do
install -Dm644 "tools/msitools/licenses/$f" "$D/usr/local/share/licenses/droiddeck-msitools/$f"
done
rm -rf msi-pkgs
ls -l "$T"
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop "$D/usr/local/bin/droiddeck-desktop"
install -Dm644 tools/linuxfs/desktop/droiddeck-gpu "$D/usr/local/bin/droiddeck-gpu"
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop-gpu "$D/usr/local/bin/droiddeck-desktop-gpu"
install -Dm644 tools/linuxfs/desktop/autostart "$D/etc/xdg/labwc/autostart"
install -Dm644 tools/linuxfs/desktop/rc.xml "$D/etc/xdg/labwc/rc.xml"
install -Dm644 tools/linuxfs/desktop/panel.conf "$D/etc/xdg/lxqt/panel.conf"
install -Dm644 tools/linuxfs/desktop/kwin_wayland_wrapper "$D/usr/local/bin/kwin_wayland_wrapper"
install -Dm644 tools/linuxfs/desktop/firefox-droiddeck.js "$D/usr/lib/firefox/defaults/pref/droiddeck.js"
ls -lR "$D"
- name: Fetch the Debian image the preloads build in
if: steps.preloads.outputs.cache-hit != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
. tools/linuxfs/debian-image.env
image=public.ecr.aws/docker/library/debian:bullseye
if gh release download "$DEBIAN_IMAGE_TAG" -R "$GITHUB_REPOSITORY" -p "$DEBIAN_IMAGE_ASSET" -O /tmp/debian-image.tar.zst \
&& { [ -z "$DEBIAN_IMAGE_SHA256" ] || echo "$DEBIAN_IMAGE_SHA256 /tmp/debian-image.tar.zst" | sha256sum -c -; }; then
zstd -dc /tmp/debian-image.tar.zst | docker load
rm -f /tmp/debian-image.tar.zst
else
echo "::warning::the hosted Debian image could not be fetched; pulling from the registry instead"
for attempt in 1 2 3 4 5; do
docker pull "$image" && break
[ "$attempt" -lt 5 ] || { echo "::error::could not pull $image after $attempt attempts"; exit 1; }
echo "pull attempt $attempt failed (registry busy?); retrying in $((attempt * 30)) s"
sleep $((attempt * 30))
done
fi
docker image ls "$image"
- name: Build the x86 preloads FEX guests load (glibc 2.31)
if: steps.preloads.outputs.cache-hit != 'true'
run: |
set -euo pipefail
docker run --rm -v "$PWD:/src" -w /src public.ecr.aws/docker/library/debian:bullseye bash -c '
set -euo pipefail
install_compilers() {
apt-get -o Acquire::Check-Valid-Until=false update -qq &&
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends gcc g++ gcc-multilib g++-multilib binutils >/dev/null
}
printf "deb http://deb.debian.org/debian bullseye main\ndeb http://security.debian.org/debian-security bullseye-security main\n" > /etc/apt/sources.list
if ! install_compilers; then
printf "deb http://archive.debian.org/debian bullseye main\ndeb http://archive.debian.org/debian-security bullseye-security main\n" > /etc/apt/sources.list
install_compilers
fi
tools/linuxfs/build-x86-preloads.sh app/src/main/assets/linuxfs
'
sudo chown -R "$(id -u):$(id -g)" app/src/main/assets/linuxfs
ls -l app/src/main/assets/linuxfs/x86_64 app/src/main/assets/linuxfs/i386
- name: Bundle the droiddeck-esync packs
run: |
set -euo pipefail
if [ -f tools/droiddeck-esync/release.env ]; then
. tools/droiddeck-esync/release.env
release_url="https://github.com/${SYNC_BUNDLE_REPO}/releases/download/${SYNC_BUNDLE_TAG}"
curl -fsSL --retry 3 -o sync-bundle.tzst "$release_url/$SYNC_BUNDLE_ASSET"
echo "$SYNC_BUNDLE_SHA256 sync-bundle.tzst" | sha256sum -c -
mkdir -p app/src/main/assets/droiddeck-esync
tar --use-compress-program=unzstd -xf sync-bundle.tzst -C app/src/main/assets/droiddeck-esync
rm sync-bundle.tzst
curl -fsSL --retry 3 -o sync-index.json "$release_url/index.json"
revoked=$(python3 -c 'import json, sys; print(" ".join(p["id"] for p in json.load(open(sys.argv[1]))["packs"] if p.get("revoked") is True))' sync-index.json)
for id in $revoked; do
if [ -e "app/src/main/assets/droiddeck-esync/packs/$id.tzst" ]; then
echo "::warning::$SYNC_BUNDLE_ASSET carries revoked pack $id; it is left out of the APK (point tools/droiddeck-esync/release.env at a newer bundle)"
rm -f "app/src/main/assets/droiddeck-esync/packs/$id.tzst"
fi
done
rm sync-index.json
test -f app/src/main/assets/droiddeck-esync/index.json
test -f app/src/main/assets/droiddeck-esync/index.json.sig
ls -lR app/src/main/assets/droiddeck-esync
fi
- name: Cache PulseAudio 13.0 source
id: pa-src
uses: actions/cache@v4
@@ -225,6 +445,8 @@ jobs:
set -euo pipefail
curl -fsSL -o pa.tar.gz https://github.com/pulseaudio/pulseaudio/archive/refs/tags/v13.0.tar.gz
mkdir -p pa13 && tar -xzf pa.tar.gz -C pa13 --strip-components=1 && rm pa.tar.gz
- name: Fetch DirectAudio (tools/directaudio/release.env)
run: tools/directaudio/fetch.sh "$PWD" "$PWD/sink-out"
- name: Build the AAudio sink into the audio bundle
run: |
set -euo pipefail
@@ -234,12 +456,12 @@ jobs:
tar --use-compress-program=unzstd -xf app/src/main/assets/pulseaudio.tzst -C pa-bundle
test ! -f pa-bundle/modules/arm64/module-aaudio-sink.so
install -m755 sink-out/module-aaudio-sink.so pa-bundle/modules/arm64/module-aaudio-sink.so
install -m755 sink-out/module-directaudio-sink.so pa-bundle/modules/arm64/module-directaudio-sink.so
install -m755 sink-out/module-directaudio-native-sink.so pa-bundle/modules/arm64/module-directaudio-native-sink.so
(cd pa-bundle && tar --use-compress-program='zstd -19' -cf ../pulseaudio.tzst .)
mv pulseaudio.tzst app/src/main/assets/pulseaudio.tzst
tar --use-compress-program=unzstd -tf app/src/main/assets/pulseaudio.tzst | grep -c '\.so$'
mkdir -p app/build/prepared-assets
mv pulseaudio.tzst app/build/prepared-assets/pulseaudio.tzst
tar --use-compress-program=unzstd -tf app/build/prepared-assets/pulseaudio.tzst | grep -c '\.so$'
rm -rf pa-bundle sink-out
- name: Cache proot
id: proot
uses: actions/cache@v4
@@ -258,24 +480,60 @@ jobs:
- name: Check the preloads
run: |
set -euo pipefail
D=app/src/main/assets/linuxfs
NEED=$(readelf -d "$D/libfakeinput.so" | sed -n 's/.*NEEDED.*\[\(.*\)\]/\1/p')
for bad in libstdc++.so.6 libgcc_s.so.1; do
if printf '%s\n' "$NEED" | grep -qx "$bad"; then
echo "::error::libfakeinput.so links $bad - the C++ runtime must stay static"; exit 1
fi
done
syms() { readelf -Ws "$1" | awk '$4 == "FUNC" && $5 == "GLOBAL" {sub(/@.*/, "", $8); print $8}'; }
FAKE=$(syms "$D/libfakeinput.so")
for sym in open openat ioctl read close poll ppoll select stat fstat access scandir; do
grep -qx "$sym" <<<"$FAKE" || { echo "::error::libfakeinput.so does not export $sym"; exit 1; }
done
SESSION=$(syms "$D/libblsession.so")
for sym in socket bind getsockname setsockopt statfs statvfs; do
grep -qx "$sym" <<<"$SESSION" || { echo "::error::libblsession.so does not export $sym"; exit 1; }
done
test -f "$D/usr/local/bin/bannerlator-session"
test -f "$D/usr/local/bin/bannerlator-proton-extra"
bash tools/linuxfs/check-preloads.sh app/src/main/assets/linuxfs
- name: Check Quest tracking IPC
run: |
set -euo pipefail
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
VK_INCLUDE=$(ls -d "$NDK"/toolchains/llvm/prebuilt/*/sysroot/usr/include | head -1)
mkdir -p build/vr/include
cp -R "$VK_INCLUDE/vulkan" build/vr/include/
if [ -d "$VK_INCLUDE/vk_video" ]; then cp -R "$VK_INCLUDE/vk_video" build/vr/include/; fi
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_tracking.cpp -o /tmp/test-xr-tracking
/tmp/test-xr-tracking
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_xr_touch.cpp -o /tmp/test-xr-touch
/tmp/test-xr-touch
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_pad_controller_config.cpp -o /tmp/test-pad-controller-config
/tmp/test-pad-controller-config
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_vsync.cpp -o /tmp/test-xr-vsync
/tmp/test-xr-vsync
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_frames.cpp -o /tmp/test-xr-frames
/tmp/test-xr-frames
cc -Itools/vr/third_party/wayland -c tools/vr/src/wayland_client_stub.c -o /tmp/test-wayland-stub.o
cc -Itools/vr/third_party/wayland -c app/src/main/cpp/waylandcomp/generated/xdg-shell-protocol.c -o /tmp/test-xdg-shell.o
g++ -std=c++17 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-missing-field-initializers -pthread -Ibuild/vr/include \
-Itools/vr/third_party/wayland -Iapp/src/main/cpp/waylandcomp/generated \
tools/vr/tests/test_presenter_shutdown.cpp tools/vr/src/presenter.cpp /tmp/test-wayland-stub.o /tmp/test-xdg-shell.o \
-ldl -o /tmp/test-presenter-shutdown
timeout 10 /tmp/test-presenter-shutdown
- name: Rust toolchain for the store engines (aarch64-linux-android)
id: rust
run: |
set -euo pipefail
rustup target add aarch64-linux-android
echo "rustc=$(rustc -V | awk '{print $2}')" >> "$GITHUB_OUTPUT"
rustc -V; cargo -V
- name: Cache cargo registry and the store engines' target
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
~/.cargo/bin/cargo-ndk
app/src/main/rust/stores/target
key: stores-${{ steps.rust.outputs.rustc }}-${{ hashFiles('app/src/main/rust/stores/Cargo.lock') }}
restore-keys: |
stores-${{ steps.rust.outputs.rustc }}-
- name: cargo-ndk
run: |
set -euo pipefail
command -v cargo-ndk >/dev/null || cargo install cargo-ndk --locked
cargo ndk --version
- name: Cache the CMake build tree
uses: actions/cache@v4
@@ -283,38 +541,90 @@ jobs:
path: |
app/.cxx
app/build/intermediates/cxx
key: cxx-${{ env.NDK_VERSION }}-${{ hashFiles('app/src/main/cpp/**') }}
key: cxx-${{ env.NDK_VERSION }}-${{ hashFiles('app/src/main/cpp/**', 'tools/vr/src/xr_*.h') }}
restore-keys: |
cxx-${{ env.NDK_VERSION }}-
- uses: actions/download-artifact@v4
with:
name: xbox-arm64
path: app/src/main/assets/linuxfs/usr/local/lib/droiddeck-xbox
- name: Build GOG Galaxy companion
if: steps.preloads.outputs.cache-hit != 'true'
run: |
rustup toolchain install 1.90.0 --profile minimal
rustup target add --toolchain 1.90.0 aarch64-unknown-linux-gnu
RUSTUP_TOOLCHAIN=1.90.0 bash tools/comet/build.sh
- name: Build the add-on packs
run: bash tools/packs/build.sh app/build/packs
- name: Build
id: build
env:
DROIDDECK_BUILD_LABEL: ${{ steps.build-label.outputs.label }}
SGDB_API_KEY: ${{ secrets.SGDB_API_KEY }}
run: |
set -o pipefail
python3 tools/quest/build-driver.py "$ANDROID_HOME/ndk/$NDK_VERSION"
python3 tools/release/runtime_inputs.py record
./gradlew assembleRelease --console=plain -PndkVersion="$NDK_VERSION" 2>&1 | tee /tmp/build.log
python3 tools/release/check_session_assets.py app/build/outputs/apk/release/app-release.apk
python3 tools/release/runtime_inputs.py apk app/build/outputs/apk/release/app-release.apk
- name: Check the native dependency closure
- name: Check the store engines' library
run: |
set -euo pipefail
APK=$(find app/build/outputs/apk -name '*.apk' | head -1)
WORK=$(mktemp -d)
unzip -q "$APK" 'lib/arm64-v8a/*' -d "$WORK"
cd "$WORK/lib/arm64-v8a"
SYSTEM="libc.so libm.so libdl.so liblog.so libandroid.so libz.so libvulkan.so libstdc++.so
libGLESv2.so libEGL.so libnativewindow.so libjnigraphics.so libaaudio.so
libOpenSLES.so libmediandk.so libcamera2ndk.so libsync.so libneuralnetworks.so"
fail=0
for so in *.so; do
for need in $(readelf -d "$so" | sed -n 's/.*NEEDED.*\[\(.*\)\]/\1/p'); do
[ -f "$need" ] && continue
case " $(echo $SYSTEM) " in *" $need "*) continue ;; esac
echo "::error::$so needs $need, which is neither in the apk nor a platform library"
fail=1
done
done
exit $fail
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
python3 tools/release/check_android_libraries.py app/build/outputs/apk/release/app-release.apk \
--readelf "$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readelf"
git diff --stat -- app/src/main/rust/stores/Cargo.lock
git diff --exit-code -- app/src/main/rust/stores/Cargo.lock \
|| { echo "::error::Cargo.lock changed during the build; commit the regenerated lockfile (uploaded as stores-cargo-lock)"; exit 1; }
- name: Store engines host unit tests
timeout-minutes: 10
continue-on-error: true
run: |
cd app/src/main/rust/stores
cargo test --locked 2>&1 | tail -60
- name: Upload the regenerated Cargo.lock
if: failure()
uses: actions/upload-artifact@v4
with:
name: stores-cargo-lock
path: app/src/main/rust/stores/Cargo.lock
if-no-files-found: ignore
- name: Install Python test dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq python3-yaml
- name: Steam import and game environment tests
run: python3 -m unittest discover -s tools/tests -p 'test_*.py'
- name: Unit tests
id: unit-tests
run: |
set -o pipefail
mkdir -p app/build
./gradlew testReleaseUnitTest --console=plain --info --stacktrace -Drobolectric.logging=stderr -PndkVersion="$NDK_VERSION" 2>&1 | tee app/build/unit-tests.log
- name: Upload unit test diagnostics
id: unit-test-diagnostics
if: always() && steps.unit-tests.outcome != 'skipped'
uses: actions/upload-artifact@v4
with:
name: unit-test-results
path: |
app/build/unit-tests.log
app/build/test-results/testReleaseUnitTest/
app/build/reports/tests/testReleaseUnitTest/
if-no-files-found: ignore
retention-days: 14
- name: Sign with the testkey (v1 + v2 + v3)
run: |
@@ -344,6 +654,23 @@ jobs:
grep -qi "CN=Android, OU=Android, O=Android" /tmp/sig.txt \
|| { echo "::error::apk is not signed with the AOSP testkey"; exit 1; }
- uses: actions/upload-artifact@v4
id: apk-upload
with:
name: droiddeck-apk
path: app/build/outputs/apk/release/*.apk
if-no-files-found: error
retention-days: 30
compression-level: 0
- uses: actions/upload-artifact@v4
with:
name: droiddeck-packs
path: app/build/packs/*.tar.zst
if-no-files-found: error
retention-days: 30
compression-level: 0
- name: Report on the pull request
if: always() && github.event_name == 'pull_request'
continue-on-error: true
@@ -352,6 +679,10 @@ jobs:
PR: ${{ github.event.pull_request.number }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
BUILD_OUTCOME: ${{ steps.build.outcome }}
UNIT_TEST_OUTCOME: ${{ steps.unit-tests.outcome }}
TEST_DIAGNOSTICS_URL: ${{ steps.unit-test-diagnostics.outputs.artifact-url }}
APK_OUTCOME: ${{ steps.apk-upload.outcome }}
SAME_REPO: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
run: |
set -uo pipefail
MARK="<!-- droiddeck-pr-build -->"
@@ -361,13 +692,42 @@ jobs:
echo
[ -f /tmp/pr-merge.md ] && cat /tmp/pr-merge.md
echo
case "$BUILD_OUTCOME" in
success)
case "$BUILD_OUTCOME:$UNIT_TEST_OUTCOME:$APK_OUTCOME" in
success:success:success)
echo "### Build passed"
echo
echo "The \`droiddeck-apk\` artifact is [on this run]($RUN_URL), signed with the public test key. After approval, a maintainer can run **PR Release Publish** from \`main\` to create an APK that installs over the release."
echo "The \`droiddeck-apk\` artifact is [on this run]($RUN_URL), signed with the public test key."
echo
if [ "$SAME_REPO" = true ]; then
echo "A release-signed tester build that installs over Stable or Preview is being published now and will appear in DroidDeck's **Test builds** list. Label the PR \`no-test-build\` to skip that."
else
echo "**Maintainer:** add the \`publish-test\` label to publish a release-signed tester build that installs over Stable or Preview and appears in DroidDeck's **Test builds** list. The label is removed automatically after publishing."
fi
echo
echo "Fallback: [open **PR Release Publish**]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/workflows/pr-release-publish.yml) on \`main\` and enter PR \`$PR\`."
;;
failure)
success:success:*)
echo "### APK publication incomplete"
echo
echo "Compilation and unit tests passed, but APK publication did not complete. See [the run]($RUN_URL)."
;;
success:failure:*)
echo "### ERROR: Unit tests failed"
echo
echo "The APK compiled, but unit tests failed. No installable artifact was published."
echo
if [ -n "$TEST_DIAGNOSTICS_URL" ]; then
echo "The [unit-test-results]($TEST_DIAGNOSTICS_URL) artifact contains the test log and any generated reports."
else
echo "The test diagnostics upload did not complete. See [the test log]($RUN_URL)."
fi
;;
success:*)
echo "### Build incomplete"
echo
echo "The APK compiled, but unit tests did not complete successfully. See [the run]($RUN_URL)."
;;
failure:*)
echo "### ERROR: Build failed"
echo
echo "The compiler's errors ([full log]($RUN_URL)):"
@@ -385,7 +745,7 @@ jobs:
echo '```'
echo "</details>"
;;
skipped|cancelled)
skipped:*|cancelled:*)
echo "### Not built"
echo
echo "The build did not run (see above, or [the run]($RUN_URL))."
@@ -401,14 +761,6 @@ jobs:
gh api -X POST "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" -F body=@/tmp/pr-report.md >/dev/null
fi
- uses: actions/upload-artifact@v4
with:
name: droiddeck-apk
path: app/build/outputs/apk/release/*.apk
if-no-files-found: error
retention-days: 30
compression-level: 0
sign:
needs: build
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
@@ -418,5 +770,143 @@ jobs:
uses: ./.github/workflows/sign.yml
with:
environment: signing-main
variants: '["standard"]'
variants: ${{ needs.build.outputs.variants }}
secrets: inherit
packs:
needs: build
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
permissions:
contents: read
actions: read
uses: ./.github/workflows/publish-packs.yml
secrets: inherit
announce:
needs: [build, sign, packs]
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
pull-requests: read
env:
CI_REPO: Droid-Deck/DroidDeck-CI
KEEP: 10
steps:
- uses: actions/download-artifact@v4
with:
pattern: droiddeck-signed-*
merge-multiple: true
path: signed
- uses: actions/create-github-app-token@v2
id: ci-token
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-CI
- name: Describe the change
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
pr=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" \
--jq '[.[] | select(.merged_at != null and .base.ref == "main")] | first // empty')
if [ -n "$pr" ]; then
number=$(jq -r .number <<<"$pr")
title=$(jq -r .title <<<"$pr")
merged=$(jq -r '.merged_at[:10]' <<<"$pr")
summary=$(jq -r '.body // ""' <<<"$pr" | tr -d '\r' | { grep -v '^🤖' || true; } |
awk '!p && /^#/ { next } NF { p = 1; print; next } p { exit }' | head -n 6 | cut -c1-400)
{
echo "**Merged to main** - [PR #$number]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$number): $title (merged $merged)"
if [ -n "$summary" ]; then
echo
echo "$summary"
fi
} > notes-head.md
else
subject=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA" --jq '.commit.message | split("\n")[0]')
echo "**Pushed to main** without a PR - $subject" > notes-head.md
fi
cat notes-head.md
- name: Publish to DroidDeck-CI
id: publish
env:
GH_TOKEN: ${{ steps.ci-token.outputs.token }}
run: |
set -euo pipefail
tag=main-${GITHUB_SHA::7}
cat signed/SHA256SUMS-*.txt > signed/SHA256SUMS.txt
rm signed/SHA256SUMS-*.txt
notes="$(cat notes-head.md)
Signed build of $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA - in main, not yet in a DroidDeck release; for testing.
Commit: $GITHUB_SHA
VersionCode: $(sed -n 's/^ *versionCode \([0-9][0-9]*\)$/\1/p' app/build.gradle)
Each apk installs over a DroidDeck release and keeps its data. \`standard\` is the normal app; the others are the same app under another package name, installed beside it.
SHA-256:
\`\`\`
$(cat signed/SHA256SUMS.txt)
\`\`\`"
gh release delete "$tag" -R "$CI_REPO" --yes --cleanup-tag 2>/dev/null || true
gh release create "$tag" -R "$CI_REPO" --target main --prerelease \
--title "main ${GITHUB_SHA::7}" --notes "$notes" signed/*
gh release list -R "$CI_REPO" -L 200 --json tagName,publishedAt \
--jq '[.[] | select(.tagName | startswith("main-"))] | sort_by(.publishedAt) | reverse | .['"$KEEP"':][] | .tagName' |
while IFS= read -r old; do
if [ "$old" = "$tag" ]; then
continue
fi
if ! gh release delete "$old" -R "$CI_REPO" --yes --cleanup-tag 2>/dev/null; then
if gh release view "$old" -R "$CI_REPO" >/dev/null 2>&1; then
echo "::error::Could not delete old release $old"
exit 1
fi
fi
done
published=$(gh release view "$tag" -R "$CI_REPO" --json tagName,assets)
jq -e --arg tag "$tag" '.tagName == $tag and (.assets | length) > 0' <<< "$published" >/dev/null
url="$GITHUB_SERVER_URL/$CI_REPO/releases/tag/$tag"
apk=$(cd signed && ls DroidDeck-*.apk | awk 'length(a) == 0 || length($0) < length(a) { a = $0 } END { print a }')
echo "release=$url" >> "$GITHUB_OUTPUT"
echo "apk=$GITHUB_SERVER_URL/$CI_REPO/releases/download/$tag/$apk" >> "$GITHUB_OUTPUT"
echo "Published $url"
- name: Post the Preview build to Discord
env:
WEBHOOK: ${{ secrets.DISCORD_BUILDS_WEBHOOK }}
GH_TOKEN: ${{ github.token }}
RELEASE: ${{ steps.publish.outputs.release }}
APK: ${{ steps.publish.outputs.apk }}
run: |
set -euo pipefail
if [ -z "$WEBHOOK" ]; then
echo "::notice::No DISCORD_BUILDS_WEBHOOK secret; nothing posted"
exit 0
fi
commit=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA")
subject=$(jq -r '.commit.message | split("\n")[0]' <<<"$commit")
title=$(jq -r '.commit.message | split("\n") | map(select(length > 0)) | .[1] // ""' <<<"$commit")
pr=$(sed -n 's/^Merge pull request #\([0-9]*\).*/\1/p' <<<"$subject")
if [ -n "$pr" ]; then
what="[#$pr]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$pr): ${title:-$subject}"
else
what="[\`${GITHUB_SHA::7}\`]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA): $subject"
fi
get=$(printf 'Download: [standard APK](<%s>) · [all variants](<%s>)' "$APK" "$RELEASE")
content=$(printf '**New signed Preview build** `main-%s`\n%s\nIn the app: **Updates → Preview**\n%s' "${GITHUB_SHA::7}" "$what" "$get")
jq -n --arg content "$content" '{content: $content, allowed_mentions: {parse: []}}' |
curl -fsS -H 'Content-Type: application/json' -d @- "$WEBHOOK" -o /dev/null
echo "Posted: $content"
catalog:
needs: announce
if: ${{ always() && github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && needs.announce.result != 'skipped' }}
uses: ./.github/workflows/update-catalog.yml
secrets: inherit
+51
View File
@@ -0,0 +1,51 @@
name: Publish the CI Debian image
on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag to publish to (a new -rN tag for a refresh; builds pin the one in tools/linuxfs/debian-image.env)'
required: true
default: 'ci-debian-bullseye-r2'
replace:
description: 'Overwrite the asset if the release already has one (changes its checksum: re-pin it in debian-image.env)'
type: boolean
default: false
permissions:
contents: write
jobs:
publish:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Pull, save and publish
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ inputs.tag }}
REPLACE: ${{ inputs.replace }}
run: |
set -euo pipefail
image=public.ecr.aws/docker/library/debian:bullseye
if [ "$REPLACE" != true ] && gh release view "$TAG" -R "$GITHUB_REPOSITORY" --json assets -q '.assets[].name' 2>/dev/null | grep -qx debian-bullseye.tar.zst; then
echo "::error::$TAG already carries debian-bullseye.tar.zst; publish to a new tag, or set replace and re-pin tools/linuxfs/debian-image.env"
exit 1
fi
for attempt in 1 2 3 4 5 6; do
docker pull "$image" && break
[ "$attempt" -lt 6 ] || { echo "::error::could not pull $image"; exit 1; }
echo "pull attempt $attempt failed (registry busy?); retrying in $((attempt * 30)) s"
sleep $((attempt * 30))
done
docker image inspect "$image" --format 'image id {{.Id}} created {{.Created}}'
docker save "$image" | zstd -19 -T0 -o debian-bullseye.tar.zst
sha256sum debian-bullseye.tar.zst | tee SHA256SUMS
ls -l debian-bullseye.tar.zst
if ! gh release view "$TAG" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release create "$TAG" -R "$GITHUB_REPOSITORY" --title "CI Debian 11 image ($TAG)" --latest=false \
--notes "The debian:bullseye image build.yml compiles the x86 preloads in (glibc 2.31), saved with docker save and compressed with zstd. Pulled from public.ecr.aws/docker/library/debian:bullseye on $(date -u +%Y-%m-%d); image id $(docker image inspect "$image" --format '{{.Id}}'). Pinned in tools/linuxfs/debian-image.env."
fi
gh release upload "$TAG" -R "$GITHUB_REPOSITORY" debian-bullseye.tar.zst SHA256SUMS --clobber
{ echo "Published \`$TAG\`:"; echo; echo '```'; cat SHA256SUMS; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
+77
View File
@@ -0,0 +1,77 @@
name: PR Release Cleanup
run-name: remove PR
on:
pull_request_target:
types: [ closed ]
branches: [ main ]
permissions:
contents: read
pull-requests: read
concurrency:
group: pr-release-${{ github.event.pull_request.number }}
cancel-in-progress: false
queue: max
jobs:
cleanup:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Check the current PR state
id: state
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
state=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq .state)
echo "closed=$([ "$state" = closed ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
- uses: actions/create-github-app-token@v2
if: ${{ steps.state.outputs.closed == 'true' }}
id: ci-token
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-CI
- name: Delete the test build
if: ${{ steps.state.outputs.closed == 'true' }}
env:
GH_TOKEN: ${{ steps.ci-token.outputs.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
found=0
for tag in "pr-$PR_NUMBER" "pr-$PR_NUMBER.0"; do
if gh release view "$tag" -R Droid-Deck/DroidDeck-CI >/dev/null 2>&1; then
gh release delete "$tag" -R Droid-Deck/DroidDeck-CI --yes --cleanup-tag
echo "Deleted $tag"
found=1
fi
done
[ "$found" = 1 ] || echo "No test build for PR #$PR_NUMBER"
- uses: actions/checkout@v4
if: ${{ steps.state.outputs.closed == 'true' }}
with:
ref: main
persist-credentials: false
- name: Retire the Discord PR announcement
if: ${{ steps.state.outputs.closed == 'true' }}
env:
WEBHOOK: ${{ secrets.DISCORD_PR_BUILDS_WEBHOOK }}
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: python3 tools/release/pr_discord.py retire
catalog:
needs: cleanup
if: ${{ always() }}
uses: ./.github/workflows/update-catalog.yml
secrets: inherit
+256 -59
View File
@@ -1,99 +1,148 @@
name: PR Release Publish
run-name: sign PR #${{ inputs.pr_number }} with the release key
run-name: "sign PR #${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }} with the release key"
on:
workflow_dispatch:
inputs:
pr_number:
description: Open, approved pull request
description: Open pull request number
required: true
type: number
type: string
pull_request_target:
types: [labeled]
branches: [main]
workflow_run:
workflows: [Build APK]
types: [completed]
permissions:
actions: read
contents: read
pull-requests: read
issues: write
pull-requests: write
concurrency:
group: pr-release-${{ inputs.pr_number }}
group: pr-release-${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }}
cancel-in-progress: false
queue: max
jobs:
authorize:
if: github.ref == 'refs/heads/main'
if: >-
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') ||
(github.event_name == 'pull_request_target' && github.event.label.name == 'publish-test') ||
(github.event_name == 'workflow_run' && github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_repository.full_name == github.repository &&
github.event.workflow_run.pull_requests[0] != null)
runs-on: ubuntu-24.04
timeout-minutes: 45
outputs:
pr_number: ${{ steps.gate.outputs.pr_number }}
run_id: ${{ steps.gate.outputs.run_id }}
head_sha: ${{ steps.gate.outputs.head_sha }}
base_sha: ${{ steps.gate.outputs.base_sha }}
version_code: ${{ steps.gate.outputs.version_code }}
label: ${{ steps.gate.outputs.label }}
skip: ${{ steps.gate.outputs.skip }}
automatic: ${{ steps.gate.outputs.automatic }}
variants: ${{ steps.variants.outputs.list }}
steps:
- name: Check the PR, review, and build
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: The packages a signed build ships as
id: variants
run: echo "list=$(awk '!/^#/ && NF { print $1 }' tools/release/variants.txt | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
- name: Check the requester, PR, and build
id: gate
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ inputs.pr_number }}
EVENT_NAME: ${{ github.event_name }}
REQUESTER: ${{ github.actor }}
PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }}
BUILT_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
[[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] || { echo "::error::Invalid PR number '$PR_NUMBER'"; exit 1; }
skip() { echo "::notice::Not publishing PR #$PR_NUMBER: $1"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; }
if [ "$EVENT_NAME" = workflow_run ]; then
echo "automatic=true" >> "$GITHUB_OUTPUT"
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
[ "$(jq -r .state <<<"$pr")" = open ] || skip "it is not open"
[ "$(jq -r .draft <<<"$pr")" = false ] || skip "it is a draft"
[ "$(jq -r .base.ref <<<"$pr")" = main ] || skip "it does not target main"
[ "$(jq -r .head.repo.full_name <<<"$pr")" = "$GITHUB_REPOSITORY" ] || skip "it comes from a fork"
jq -e 'any(.labels[]; .name == "no-test-build")' <<<"$pr" >/dev/null && skip "it is labelled no-test-build"
[ "$(jq -r .head.sha <<<"$pr")" = "$BUILT_SHA" ] || skip "a newer push is building"
fi
if [ "$EVENT_NAME" = pull_request_target ]; then
permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$REQUESTER/permission" --jq .permission)
case "$permission" in
admin|maintain|write) ;;
*) echo "::error::@$REQUESTER does not have permission to publish release-signed test builds"; exit 1 ;;
esac
fi
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
jq -e '.state == "open" and .draft == false and .base.ref == "main"' <<<"$pr" >/dev/null
jq -e '.state == "open" and .draft == false and .base.ref == "main"' <<<"$pr" >/dev/null ||
{ echo "::error::PR #$PR_NUMBER must be open, ready for review, and target main"; exit 1; }
head=$(jq -r '.head.sha' <<<"$pr")
base=$(jq -r '.base.sha' <<<"$pr")
run_id=$(gh run list -R "$GITHUB_REPOSITORY" --workflow build.yml \
--event pull_request --commit "$head" \
--json databaseId,headSha,status,conclusion,createdAt,attempt -L 100 |
jq -r --arg head "$head" '[.[] | select(.headSha == $head)] |
sort_by([.createdAt, .attempt]) | last |
select(.status == "completed" and .conclusion == "success") | .databaseId // empty')
[[ -n "$run_id" ]] || { echo '::error::The latest PR build is not green'; exit 1; }
run_id=
for attempt in $(seq 1 60); do
latest=$(gh run list -R "$GITHUB_REPOSITORY" --workflow build.yml \
--event pull_request --commit "$head" \
--json databaseId,headSha,status,conclusion,createdAt,attempt -L 100 |
jq -c --arg head "$head" '[.[] | select(.headSha == $head)] |
sort_by([.createdAt, .attempt]) | last // empty')
if [[ -n "$latest" && $(jq -r .status <<<"$latest") == completed ]]; then
conclusion=$(jq -r .conclusion <<<"$latest")
[[ "$conclusion" == success ]] || { echo "::error::The latest PR build is not green ($conclusion)"; exit 1; }
run_id=$(jq -r .databaseId <<<"$latest")
break
fi
status=not-started
[[ -z "$latest" ]] || status=$(jq -r .status <<<"$latest")
echo "Waiting for the PR build of ${head:0:7} to finish ($status)"
sleep 30
done
[[ -n "$run_id" ]] || { echo '::error::The PR build did not finish within 30 minutes'; exit 1; }
run=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id")
jq -e --arg head "$head" --arg base "$base" --argjson number "$PR_NUMBER" '
jq -e --arg head "$head" --arg repo "$(jq -r .head.repo.full_name <<<"$pr")" --arg ref "$(jq -r .head.ref <<<"$pr")" '
.event == "pull_request" and .head_sha == $head and .conclusion == "success" and
any(.pull_requests[]?; .number == $number and .head.sha == $head and .base.sha == $base)
' <<<"$run" >/dev/null
approved=$(gh api --paginate --slurp \
"repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews?per_page=100" |
jq -r --arg head "$head" '[.[][] | select(.commit_id == $head)] |
group_by(.user.login) | map(max_by(.id)) | .[] |
select(.state == "APPROVED") | .user.login')
maintainer_approved=false
while IFS= read -r reviewer; do
[[ -n "$reviewer" ]] || continue
permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$reviewer/permission" --jq .permission)
[[ "$permission" == write || "$permission" == admin ]] && maintainer_approved=true
done <<<"$approved"
[[ "$maintainer_approved" == true ]] || { echo '::error::A maintainer must approve this commit'; exit 1; }
.head_repository.full_name == $repo and .head_branch == $ref
' <<<"$run" >/dev/null ||
{ echo "::error::Build run $run_id is not a green build of PR #$PR_NUMBER at ${head:0:7}"; exit 1; }
sdk=${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}
tools=$(find "$sdk/build-tools" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -1)
stable_tag=$(gh api repos/The412Banner/DroidDeck/releases/latest --jq .tag_name)
mkdir -p "$RUNNER_TEMP/stable" "$RUNNER_TEMP/pr"
gh release download "$stable_tag" -R The412Banner/DroidDeck \
-p "DroidDeck-$stable_tag.apk" -D "$RUNNER_TEMP/stable"
main_code=$(sed -n 's/^[[:space:]]*versionCode[[:space:]][[:space:]]*\([0-9][0-9]*\)[[:space:]]*$/\1/p' app/build.gradle)
[[ "$main_code" =~ ^[0-9]+$ ]] || { echo "::error::No numeric versionCode in main's app/build.gradle"; exit 1; }
mkdir -p "$RUNNER_TEMP/pr"
gh run download "$run_id" -R "$GITHUB_REPOSITORY" -n droiddeck-apk -D "$RUNNER_TEMP/pr"
mapfile -t apks < <(find "$RUNNER_TEMP/pr" -type f -name '*.apk')
[[ ${#apks[@]} == 1 ]] || { echo '::error::Expected one PR APK'; exit 1; }
package_line() {
"$tools/aapt" dump badging "$1" | sed -n "s/^package: name='\\([^']*\\)' versionCode='\\([^']*\\)'.*/\\1 \\2/p"
}
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;
[[ "$candidate" =~ ^com\.droiddeck\.launcher\ [0-9]+$ && ${candidate##* } -ge $main_code ]] || {
echo "::error::PR package/versionCode '$candidate' cannot install over main (versionCode $main_code)"; exit 1;
}
current=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
jq -e --arg head "$head" --arg base "$base" '
.state == "open" and .draft == false and .base.ref == "main" and
.head.sha == $head and .base.sha == $base
' <<<"$current" >/dev/null
[[ $(gh pr view "$PR_NUMBER" -R "$GITHUB_REPOSITORY" --json reviewDecision --jq .reviewDecision) == APPROVED ]] || {
echo '::error::The current PR revision still needs approval'; exit 1;
}
' <<<"$current" >/dev/null ||
{ echo "::error::PR #$PR_NUMBER moved on while its build was checked; publish again"; exit 1; }
{
echo "pr_number=$PR_NUMBER"
echo "run_id=$run_id"
echo "head_sha=$head"
echo "base_sha=$base"
echo "version_code=${candidate##* }"
echo "label=PR.$PR_NUMBER-${head:0:7}"
} >> "$GITHUB_OUTPUT"
@@ -102,11 +151,12 @@ jobs:
uses: ./.github/workflows/sign.yml
with:
environment: signing-main
variants: '["standard"]'
variants: ${{ needs.authorize.outputs.variants }}
run_id: ${{ needs.authorize.outputs.run_id }}
label: ${{ needs.authorize.outputs.label }}
artifact: droiddeck-pr-signed
secrets: inherit
if: ${{ needs.authorize.outputs.skip != 'true' }}
publish:
needs: [authorize, sign]
@@ -114,37 +164,184 @@ jobs:
permissions:
actions: read
contents: read
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
ref: main
persist-credentials: false
- uses: actions/download-artifact@v4
with:
name: droiddeck-pr-signed-standard
pattern: droiddeck-pr-signed-*
merge-multiple: true
path: signed
- name: Publish to DroidDeck-CI when configured
- name: The add-on packs main has not published
env:
GH_TOKEN: ${{ secrets.DROIDDECK_CI_TOKEN }}
PR_NUMBER: ${{ inputs.pr_number }}
GH_TOKEN: ${{ github.token }}
RUN: ${{ needs.authorize.outputs.run_id }}
run: |
set -euo pipefail
gh run download "$RUN" -R "$GITHUB_REPOSITORY" -n droiddeck-packs -D packs
mkdir -p pr-packs
for file in packs/*.tar.zst; do
name=$(basename "$file" .tar.zst)
sha=$(sha256sum "$file" | cut -d' ' -f1)
tag="pack-$name-${sha:0:16}"
digest=$({ gh api "repos/Droid-Deck/DroidDeck-Components/releases/tags/$tag" 2>/dev/null || echo '{}'; } |
jq -r --arg n "$name.tar.zst" '.assets // [] | .[] | select(.name == $n) | .digest // empty')
if [ "$digest" = "sha256:$sha" ]; then
echo "$name: served from DroidDeck-Components $tag"
else
cp "$file" "pr-packs/$tag.tar.zst"
echo "$name: attached to the PR release as $tag.tar.zst"
fi
done
- name: Describe the change
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
run: |
set -euo pipefail
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
title=$(jq -r .title <<<"$pr")
summary=$(jq -r '.body // ""' <<<"$pr" | tr -d '\r' | { grep -v '^🤖' || true; } |
awk '!p && /^#/ { next } NF { p = 1; print; next } p { exit }' | head -n 6 | cut -c1-400)
{
echo "**Not merged** - a test build of open [PR #$PR_NUMBER]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$PR_NUMBER): $title"
if [ -n "$summary" ]; then
echo
echo "$summary"
fi
} > notes-head.md
cat notes-head.md
- uses: actions/create-github-app-token@v2
id: ci-token
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-CI
- name: Publish to DroidDeck-CI
id: publish
env:
GH_TOKEN: ${{ steps.ci-token.outputs.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
LABEL: ${{ needs.authorize.outputs.label }}
BASE_SHA: ${{ needs.authorize.outputs.base_sha }}
VERSION_CODE: ${{ needs.authorize.outputs.version_code }}
run: |
set -euo pipefail
if [[ -z "$GH_TOKEN" ]]; then
echo '::warning::No CI token; download the signed APK from this run'
exit 0
fi
repo=Droid-Deck/DroidDeck-CI
tag="pr-$PR_NUMBER"
apk="DroidDeck-$LABEL.apk"
sha=$(sha256sum "signed/$apk" | cut -d ' ' -f 1)
notes="Source: https://github.com/$GITHUB_REPOSITORY/pull/$PR_NUMBER
cat signed/SHA256SUMS-*.txt > signed/SHA256SUMS.txt
rm signed/SHA256SUMS-*.txt
notes="$(cat notes-head.md)
Source: https://github.com/$GITHUB_REPOSITORY/pull/$PR_NUMBER
Commit: $HEAD_SHA
APK SHA-256: $sha"
Base: $BASE_SHA
VersionCode: $VERSION_CODE
Each apk installs over a DroidDeck release and keeps its data. \`standard\` is the normal app; the others are the same app under another package name, installed beside it.
SHA-256:
\`\`\`
$(cat signed/SHA256SUMS.txt)
\`\`\`"
first=false
if ! gh release view "$tag" -R "$repo" >/dev/null 2>&1; then
gh release create "$tag" -R "$repo" --target main --title "PR #$PR_NUMBER" --notes "$notes" --prerelease
first=true
fi
gh release upload "$tag" "signed/$apk" -R "$repo" --clobber
echo "first=$first" >> "$GITHUB_OUTPUT"
cp pr-packs/* signed/ 2>/dev/null || true
gh release upload "$tag" signed/* -R "$repo" --clobber
gh release edit "$tag" -R "$repo" --title "PR #$PR_NUMBER" --notes "$notes" --prerelease
keep=$(cd signed && ls | jq -R . | jq -sc .)
gh api "repos/$repo/releases/tags/$tag" |
jq -r --arg keep "$apk" '.assets[] | select(.name | endswith(".apk")) | select(.name != $keep) | .id' |
jq -r --argjson keep "$keep" '.assets[] | select(.name as $n | $keep | index($n) | not) | .id' |
while IFS= read -r id; do
gh api --method DELETE "repos/$repo/releases/assets/$id" >/dev/null
done
published=$(gh release view "$tag" -R "$repo" --json tagName,assets)
jq -e --arg tag "$tag" '.tagName == $tag and (.assets | length) > 0' <<<"$published" >/dev/null
release="$GITHUB_SERVER_URL/$repo/releases/tag/$tag"
apk=$(cd signed && ls DroidDeck-*.apk | awk 'length(a) == 0 || length($0) < length(a) { a = $0 } END { print a }')
echo "release=$release" >> "$GITHUB_OUTPUT"
echo "apk=$GITHUB_SERVER_URL/$repo/releases/download/$tag/$apk" >> "$GITHUB_OUTPUT"
- name: Whether the PR's branch is in this repo
id: same-repo
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
run: |
head_repo=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq .head.repo.full_name)
echo "same=$([ "$head_repo" = "$GITHUB_REPOSITORY" ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
- name: Clear the publish request label
if: ${{ github.event_name == 'pull_request_target' && steps.publish.outcome == 'success' }}
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
run: |
gh api --method DELETE "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/labels/publish-test"
- name: Report the signed build on the PR
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
RELEASE: ${{ steps.publish.outputs.release }}
APK: ${{ steps.publish.outputs.apk }}
SAME_REPO: ${{ steps.same-repo.outputs.same }}
run: |
set -euo pipefail
short=${HEAD_SHA:0:7}
mark='<!-- droiddeck-pr-release -->'
if [ "$SAME_REPO" = true ]; then
NEXT='Each green build of a new push is signed and published here on its own. Label the PR `no-test-build` to stop that.'
else
NEXT='Pushes to this PR do not re-sign automatically. After a new green build, a maintainer can add the `publish-test` label again.'
fi
body=$(cat <<EOF
$mark
## Signed test build
A release-signed build of this PR at \`$short\` is ready. It installs over Stable or Preview and keeps app data.
[Download the standard APK]($APK) · [All variants and checksums]($RELEASE)
It is also available in **DroidDeck → Updates → Test builds → PR #$PR_NUMBER**.
$NEXT
EOF
)
comment=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --paginate --slurp | \
jq "[.[][] | select(.user.login == \"github-actions[bot]\") | select(.body | startswith(\"$mark\"))] | first // {}")
id=$(jq -r '.id // empty' <<<"$comment")
if [ -n "$id" ]; then
state=$(jq -r '.body | split("\n")[] | select(startswith("<!-- droiddeck-discord: ") and endswith(" -->"))' <<<"$comment")
if [ -n "$state" ]; then body=$(printf '%s\n%s' "$body" "$state"); fi
gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$id" -f body="$body" >/dev/null
else
gh api -X POST "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" -f body="$body" >/dev/null
fi
- name: Update the Discord PR announcement
if: ${{ always() && steps.publish.outcome == 'success' }}
env:
WEBHOOK: ${{ secrets.DISCORD_PR_BUILDS_WEBHOOK }}
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
RELEASE: ${{ steps.publish.outputs.release }}
APK: ${{ steps.publish.outputs.apk }}
run: python3 tools/release/pr_discord.py publish
catalog:
needs: publish
if: ${{ always() && needs.publish.result != 'skipped' }}
uses: ./.github/workflows/update-catalog.yml
secrets: inherit
+83
View File
@@ -0,0 +1,83 @@
name: Publish add-on packs
# The add-on packs (tools/packs/build.sh) a Build APK run made, published to DroidDeck-Components under
# content-addressed tags (pack-<name>-<first 16 hex of sha256>). The APK from that run pins each pack's
# SHA-256 and downloads it from that tag. A tag is created once and never replaced.
# Only main and release builds publish here; a PR test build attaches the packs main has not published
# to its pr-N release on DroidDeck-CI (pr-release-publish.yml), which is deleted when the PR closes.
on:
workflow_call:
inputs:
run_id:
description: The Build APK run whose packs to publish; empty for the calling run's own
type: string
default: ''
permissions:
contents: read
actions: read
jobs:
publish:
name: publish add-on packs
runs-on: ubuntu-24.04
timeout-minutes: 15
concurrency:
group: publish-packs
cancel-in-progress: false
steps:
- name: The packs (this run's build)
if: inputs.run_id == ''
uses: actions/download-artifact@v4
with:
name: droiddeck-packs
path: packs
- name: The packs (another Build APK run)
if: inputs.run_id != ''
env:
GH_TOKEN: ${{ github.token }}
RUN: ${{ inputs.run_id }}
run: gh run download "$RUN" -R "$GITHUB_REPOSITORY" -n droiddeck-packs -D packs
- uses: actions/create-github-app-token@v2
id: components-token
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-Components
- name: Publish
env:
GH_TOKEN: ${{ steps.components-token.outputs.token }}
REPO: Droid-Deck/DroidDeck-Components
BUILD_RUN: ${{ inputs.run_id || github.run_id }}
run: |
set -euo pipefail
published_digest() {
{ gh api "repos/$REPO/releases/tags/$1" 2>/dev/null || echo '{}'; } |
jq -r --arg n "$2" '.assets // [] | .[] | select(.name == $n) | .digest // empty'
}
commit=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$BUILD_RUN" --jq .head_sha 2>/dev/null || echo "$GITHUB_SHA")
for name in proot directaudio tracking xbox comet; do
file="packs/$name.tar.zst"
[ -f "$file" ] || { echo "::error::$name.tar.zst is missing from Build APK run $BUILD_RUN"; exit 1; }
sha=$(sha256sum "$file" | cut -d' ' -f1)
tag="pack-$name-${sha:0:16}"
digest=$(published_digest "$tag" "$name.tar.zst")
if [ -z "$digest" ]; then
notes="DroidDeck add-on pack \`$name\`, built by [Build APK run $BUILD_RUN]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$BUILD_RUN) from \`$commit\` with tools/packs/build.sh and published by [run $GITHUB_RUN_ID]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID).
Size: $(stat -c %s "$file") bytes. SHA-256: $sha. APKs built in the same run pin this digest."
gh release create "$tag" -R "$REPO" --title "$tag" --notes "$notes" --latest=false "$file" \
|| echo "::warning::creating $tag failed; checking whether another run published it"
digest=$(published_digest "$tag" "$name.tar.zst")
fi
if [ "$digest" != "sha256:$sha" ]; then
echo "::error::$tag holds ${digest:-nothing} for $name.tar.zst, not sha256:$sha"
exit 1
fi
echo "- \`$name\`: [$tag]($GITHUB_SERVER_URL/$REPO/releases/tag/$tag) \`$sha\`" >> "$GITHUB_STEP_SUMMARY"
done
+67
View File
@@ -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" \
@@ -58,3 +62,66 @@ jobs:
label: ${{ inputs.version }}
artifact: droiddeck-release
secrets: inherit
packs:
needs: find
permissions:
contents: read
actions: read
uses: ./.github/workflows/publish-packs.yml
with:
run_id: ${{ needs.find.outputs.run_id }}
secrets: inherit
publish:
name: publish the release
needs: [find, sign, packs]
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
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"
catalog:
needs: publish
uses: ./.github/workflows/update-catalog.yml
permissions:
contents: read
pull-requests: read
secrets: inherit
+42
View File
@@ -0,0 +1,42 @@
name: Update app catalog
on:
workflow_call:
workflow_dispatch:
release:
types: [published, edited, deleted, unpublished]
permissions:
contents: read
pull-requests: read
concurrency:
group: update-app-catalog
cancel-in-progress: false
queue: max
jobs:
catalog:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
ref: main
persist-credentials: false
- uses: actions/create-github-app-token@v2
id: ci-token
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-CI
- name: Regenerate catalog.json
env:
SOURCE_GITHUB_TOKEN: ${{ github.token }}
CI_GITHUB_TOKEN: ${{ steps.ci-token.outputs.token }}
run: |
set -euo pipefail
python3 tools/release/update_catalog.py
+9 -5
View File
@@ -8,17 +8,17 @@ captures/
.externalNativeBuild/
.cxx/
.kotlin/
# Built in CI from tools/linuxfs and app/src/main/cpp/fakeinput.cpp, never committed, so the two
# copies cannot drift.
app/src/main/assets/linuxfs/
app/src/main/jniLibs/arm64-v8a/libproot.so
app/src/main/jniLibs/arm64-v8a/.proot-inputs
app/src/main/jniLibs/arm64-v8a/.proot-outputs
app/src/main/jniLibs/arm64-v8a/libproot-loader.so
app/src/main/jniLibs/arm64-v8a/libdirectaudiorelay.so
app/src/main/rust/stores/target/
app/src/main/assets/droiddeck-esync/
# Kotlin compiler session files (build-tool state, never source).
.kotlin/
# Signing keys never enter the repo (DroidDeck's lives in GitHub environment secrets; see
# tools/release/make-release-key.sh). keystore/testkey.p12 is the public AOSP testkey.
*.jks
*.keystore
*.p12
@@ -29,3 +29,7 @@ app/src/main/jniLibs/arm64-v8a/libproot-loader.so
droiddeck-release-key/
password.txt
__pycache__/
.signing.env
app/src/main/assets/directaudio/
/tools/vr/out/
+3
View File
@@ -0,0 +1,3 @@
# Rules
- Comments should be rare, factual, and explain only non-obvious external constraints or business invariants.
+38 -6
View File
@@ -5,22 +5,54 @@
</picture>
</p>
Run Valve's native ARM64 Steam client on an Adreno Android device. Steam runs in a Linux runtime under proot, with gamescope and an in-app Vulkan compositor. Windows games use Valve's ARM64 Proton and FEX. A desktop with LXQt, Firefox, and emulators is also available.
DroidDeck brings the SteamOS experience to Android: Valve's Steam client in Big Picture on your Adreno handheld, with Windows games through Valve's ARM64 Proton.
<p align="center"><a href="https://discord.gg/JRGAvawjsm"><img src="https://img.shields.io/badge/Discord-Join%20the%20community-5865F2?logo=discord&logoColor=white" alt="Join the DroidDeck Discord"></a></p>
<p align="center"><img src="docs/releases/media/0.2.0/launch-into-steam.gif" width="80%" alt="Tapping DroidDeck on the Android home screen and landing in Steam Big Picture"></p>
> Note: DroidDeck does not have a stand-alone website. Do not click on any download links from websites claiming to be the DroidDeck team.
## Requirements and install
Use Android 9 or newer on a supported Adreno device (730 or newer, or 8xx). Mali, Xclipse, PowerVR, and Adreno 710 are unsupported. No root is required. Allow about 3 GB for the runtime and 1.1 GB more for the desktop and emulators. Install the APK from [Releases](https://github.com/The412Banner/DroidDeck/releases), install the Linux runtime, then press **Play** and sign in. Steam downloads on first launch. Install **Desktop & apps** to use the desktop and emulators.
Use Android 9 or newer on a supported Adreno device (730 or newer, or 8xx). Adreno 6xx is experimental: DirectX 11 uses DXVK 2 and may run, and DirectX 12 games can still crash. Mali, Xclipse, PowerVR, and Adreno 710 are unsupported. No root is required. Allow about 3 GB for the runtime and 1.1 GB more for the desktop and emulators. Install the APK from [Releases](https://github.com/Droid-Deck/DroidDeck/releases), install the Linux runtime, then press **Play** and sign in. Steam downloads on first launch. Install **Desktop & apps** to use the desktop and emulators. The **Store** installs Linux apps and games from Flathub (ARM64 builds) with Flatpak; with additional options to install Appimages and set up scripts. **Stores** (Setup › Stores › "Show Stores in the rail") signs in to GOG, Epic Games and Amazon Games and downloads games into the Games storage. Epic keeps its direct API install path. Opening Epic Stores prepares Epic’s official Linux launcher preview. When installed, Epic games launched from DroidDeck use that client, and games installed in the client are imported into DroidDeck’s library and shortcuts. The x86-64 client runs through FEX on ARM and DroidDeck retains its Proton hooks; this preview and ARM setup are outside Epic’s listed Linux support. The cog on a store’s chip row sets its opening tab and download speed tier.
On Android 12+, if Steam exits without a log, turn off **Restrict child processes** in Developer options.
On Android 14 and newer, before Steam launches, you must turn on **Disable child process restrictions** in Developer options. If this option is not available in developer settings (Android 12 and 13 devices), first launch of Steam will present a "Fix it for me" button, which will help automate the setup process.
## Smart glasses
Use **Setup → Smart glasses** for app-wide virtual-screen settings across DroidDeck menus, launchers, desktop sessions, and games. Supported VITURE, RayNeo, XREAL, and Rokid connections provide 3DoF; Luma Ultra adds optional experimental 6DoF and stereo-assisted hand/Joy-Con tracking. Model support and SDK requirements differ: see the [smart-glasses support matrix](docs/smart-glasses-support.md) for supported devices, controls, setup, and limitations.
## Community
Join the [DroidDeck Discord](https://discord.gg/JRGAvawjsm) for help, Preview builds, and device reports. Bug reports go in its **#bug-reports** forum; attach the zip from **Share logs** (Setup, or the session drawer) so the logs come with it.
## Build
Run `tools/build_local.sh` with Docker, Java 17, the Android SDK/NDK, and `zstd` installed. It builds the ARM64 audio sinks from PulseAudio 13.0 and packages them into the APK at `app/build/outputs/apk/release/app-release.apk`. Set `DROIDDECK_PA13_SOURCE_DIR` to an existing PulseAudio 13.0 source directory to skip downloading it. To install the APK on an attached device, run `tools/deploy_local.sh`.
Run `tools/build_local.sh` with Docker, Java 17, the Android SDK/NDK, Rust through rustup, GitHub CLI, Python 3, and `zstd` installed. It prepares the same native libraries, pinned components and audio sinks as CI, then checks the APK's assets, JNI exports and dependencies. The APK is at `app/build/outputs/apk/release/app-release.apk`. Set `DROIDDECK_PA13_SOURCE_DIR` to an existing PulseAudio 13.0 source directory to skip downloading it. Set `DROIDDECK_SIGNING_ENV` to a signing environment file to install over a release build; relative keystore paths resolve beside that file. To install on an attached device, run `tools/deploy_local.sh`.
After preparation, ordinary `./gradlew :app:assembleRelease` builds can reuse the verified inputs. If native sources, component pins or staged files change, packaging stops with instructions to rerun the helper. Generated audio stays under `app/build/`; the tracked base bundle is never modified. `./gradlew -PskipRust=true :app:testDebugUnitTest` needs no native preparation. For an APK without store engines, use `DROIDDECK_SKIP_RUST=1 tools/build_local.sh`; it omits cached engine libraries too.
For the prepared Windows checkout, run `./tools/run_windows.ps1` (optionally with `-Serial <adb-serial>`), or select **DroidDeck XR** in Android Studio and run the **app** module on the connected phone. Machine-specific options live in the ignored `local.properties`: `droiddeck.python` selects a Python executable, `droiddeck.skipRust=true` omits the optional store download engines, and `droiddeck.debugApplicationIdSuffix=.xr` installs **DroidDeck XR Dev** alongside the signed release. The development app has its own runtime and Steam login. The local Java selection is in `.gradle/config.properties`.
`python tools/prepare_windows_runtime.py` restores this checkout's audited runtime inputs from the pinned CI APK, three locally rebuilt preload libraries, and the checksum-pinned Gamescope archive in `build/windows-setup/`. Start Docker Desktop's Linux engine first: preparation now rebuilds DroidDeck's ARM64 VR driver and Vulkan layer from current source and runs their native regression tests. The compiler image is built automatically on first use. Vulkan headers come from the NDK beside `--llvm-strip`, or from an explicit `--vk-include <NDK-sysroot-include>` directory.
To reuse that build, pass `--rebuilt-vr build/windows-setup/vr`. Preparation verifies its `vr-build.json` against the current Git revision, source files, Vulkan headers, output checksums, and ARM64 architecture; missing or stale builds fail instead of falling back to the donor's VR libraries. The script checks the actual donor CI merge tree, still rejects unrelated unaudited runtime changes, and records component provenance in `app/build/windows-runtime-provenance.json`. Preserve these ignored preparation files when rebuilding locally; `clean` removes the prepared assets and their verification manifest.
Run the helper and preload regression suite with `bash tools/test_local.sh`. On macOS it runs in Linux through Docker, matching the helpers' runtime and CI instead of compiling Linux preloads against macOS headers.
## Limits
Compatibility and performance vary by device; hardware validation is limited. Desktop compositing uses software rendering. Firefox sandboxing is reduced under proot. See the session logs in `Download/DroidDeck/` when diagnosing problems.
Compatibility and performance vary by device; hardware validation is limited. Desktop compositing uses software rendering. Firefox sandboxing is reduced under proot. Logs are kept app-private (`files/logs/`, the last 30 sessions); **Share logs** packs the newest session, with the stores and tool logs, into one scrubbed zip.
## Credits and licence
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). See [LICENSE](LICENSE). Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). Epic Games and GOG support build on [Phobos’s](https://github.com/phobos665) adaptation of [Legendary](https://github.com/legendary-gl/legendary) and [gogdl](https://github.com/Heroic-Games-Launcher/heroic-gogdl), respectively. The registry entries a GOG game's install script (`goggame-<id>.script`) sets are read the way [Heroic Games Launcher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher)'s GOG setup (GPL-3.0) reads them. LSFG frame generation is from the work of Camille LaVey and the [Eden](https://eden-emu.dev) emulator project, following [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk), ported to WinNative and DroidDeck by [@maxjivi05](https://github.com/maxjivi05); it needs your own copy of [Lossless Scaling](https://store.steampowered.com/app/993090/) and ships none of its shaders. x86 AppImages, and any whose own runtime cannot unpack them, are unpacked with [uruntime](https://github.com/VHSgunzo/uruntime) by VHSgunzo (MIT), shipped unmodified with its licence, which also starts the arm64 emulator AppImages whose own runtime cannot run its extractor on Android. The Steam client's audio goes through [@The412Banner](https://github.com/The412Banner)'s [DirectAudio](https://github.com/The412Banner/directaudio) PulseAudio sink (LGPL-2.1-or-later), built from `tools/directaudio/sink` with its request size kept to its ring so clients are not asked to queue 100 ms ahead. mmap ring-buffer controller input was developed by ribbit384 ([WinNative #345](https://github.com/WinNative-Emu/WinNative/pull/345)). Why the x86-64 Steam client refused the arm64 client's sign-in was checked by reading Steam's saved token with the `ConnectCache` format from [deadlock-api-ingest](https://github.com/deadlock-api/deadlock-api-ingest). x86 programs that link OpenSSL 3 under FEX use the copy in the [Steam Linux Runtime 4.0](https://gitlab.steamos.cloud/steamrt/steamrt) platform. Hooks written with `WriteProcessMemory` into code [FEX](https://github.com/FEX-Emu/FEX) has translated go through `/proc/<pid>/mem` again when the kernel refuses `process_vm_writev`, as [Wine](https://gitlab.winehq.org/wine/wine)'s `server/ptrace.c` wrote them before Wine 11. x86 Linux games under FEX get the same `/etc/asound.conf` that [pressure-vessel](https://gitlab.steamos.cloud/steamrt/steam-runtime-tools) writes into the Steam Linux Runtime, so ALSA plays through PulseAudio. Games render with [Mesa](https://gitlab.freedesktop.org/mesa/mesa) Turnip's own per-render-pass choice between tile memory and system memory, while the Steam client keeps `TU_DEBUG=sysmem`; [FEX](https://github.com/FEX-Emu/FEX)'s code disk cache was checked in its source and is already on through Proton. The proot fast path picks the guest name of a host file bound under several names in the order [PRoot](https://github.com/proot-me/proot)'s `path/binding.c` (GPL-2.0-or-later) sorts its bindings, so links and `/proc/self/fd` read back as they do through proot. See [LICENSE](LICENSE). Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
XR glasses support builds on WinNative's VITURE glasses work. VITURE protocol details were checked against [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver) by [@wheaney](https://github.com/wheaney), including the VITURE protocol header it ships, [ar-drivers-rs](https://github.com/badicsalex/ar-drivers-rs) by [@badicsalex](https://github.com/badicsalex) and the [go-macos/iokit VITURE package](https://pkg.go.dev/github.com/go-macos/iokit/viture); the optional VITURE SDK belongs to VITURE and is not included. RayNeo support adapts [@informalTechCode](https://github.com/informalTechCode)'s [RayNeo-Air-Series-3DoF-Scaffold](https://github.com/informalTechCode/RayNeo-Air-Series-3DoF-Scaffold) (Apache-2.0) and references [@verncat](https://github.com/verncat)'s [RayNeo-Air-3S-Pro-OpenVR](https://github.com/verncat/RayNeo-Air-3S-Pro-OpenVR) (MIT) and [Monado](https://gitlab.freedesktop.org/monado/monado) (BSL-1.0); see [docs/third-party/rayneo](docs/third-party/rayneo/README.md). XREAL 1S support references [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver) and [xrealOneDeviceKit](https://github.com/wheaney/xrealOneDeviceKit) by @wheaney, [xreal_one_driver](https://github.com/rohitsangwan01/xreal_one_driver) by [@rohitsangwan01](https://github.com/rohitsangwan01) (MIT) and [xr-tools](https://github.com/0xcaff/xr-tools) by [@0xcaff](https://github.com/0xcaff). Rokid support follows the Rokid protocol notes in [ar-drivers-rs](https://github.com/badicsalex/ar-drivers-rs) and @badicsalex's [Void Computing write-ups](https://voidcomputing.hu/blog/worse-better-prettier/), and [Monado](https://gitlab.freedesktop.org/monado/monado)'s Rokid driver. The REDMAGIC glasses-display findings draw on [@mekhontsev](https://github.com/mekhontsev)'s [MagicDesk investigation](https://github.com/mekhontsev/magicdesk/blob/main/docs/xr-resolution-fix.md) and [@Coding-BR](https://github.com/Coding-BR)'s [decompiled Nubia kernel](https://github.com/Coding-BR/android_kernel_nubia_sm8850_qwjujube). VITURE, RayNeo, XREAL, Rokid and REDMAGIC are trademarks of their owners; this project is not affiliated with them.
Joy-Con VR controllers port the arm model of Google's [Google VR SDK for Unity](https://github.com/googlevr/gvr-unity-sdk) (Apache-2.0), adapt the hand skeleton of Valve's [OpenVR](https://github.com/ValveSoftware/openvr) `handskeletonsimulation` sample (BSD-3-Clause), which follows Moshi Turner's hand simulation in [Monado](https://gitlab.freedesktop.org/monado/monado), and use the Index controller offsets from Monado's `vive_poses.c` (BSL-1.0). Joy-Con behaviour was researched in Linux's [hid-nintendo](https://github.com/torvalds/linux/blob/master/drivers/hid/hid-nintendo.c) driver by Daniel J. Ogorchock, [@dekuNukem](https://github.com/dekuNukem)'s [Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering), [SDL](https://github.com/libsdl-org/SDL) and the Android Open Source Project, and checked against [ALVR](https://github.com/alvr-org/ALVR), [OpenComposite](https://gitlab.com/znixian/OpenOVR), [PSMoveSteamVRBridge](https://github.com/HipsterSloth/PSMoveSteamVRBridge), [@F8LEFT](https://github.com/F8LEFT)'s [JoyConVr](https://github.com/F8LEFT/JoyConVr), [@krnelpan1c](https://github.com/krnelpan1c)'s [Daydream-Controller-SteamVR](https://github.com/krnelpan1c/Daydream-Controller-SteamVR), [@yoyobuae](https://github.com/yoyobuae)'s [joyconvr](https://github.com/yoyobuae/joyconvr), [Moonlight](https://github.com/moonlight-stream/moonlight-android) and [Dolphin](https://github.com/dolphin-emu/dolphin); see [docs/third-party/joycon](docs/third-party/joycon/README.md). Nintendo Switch and Joy-Con are trademarks of Nintendo; this project is not affiliated with Nintendo.
Camera hand tracking runs Google's [MediaPipe](https://github.com/google-ai-edge/mediapipe) Tasks Vision library with its Pose Landmarker (Lite) model, BlazePose GHUM 3D, and its Hand Landmarker model (all Apache-2.0), on frames from AndroidX [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) (Apache-2.0); MediaPipe's usage reports are switched off. The design was checked against [@F8LEFT](https://github.com/F8LEFT)'s [JoyConVr](https://github.com/F8LEFT/JoyConVr) camera app (MIT), and the short Joy-Con movements follow the accelerometer mode of the [Google VR SDK for Unity](https://github.com/googlevr/gvr-unity-sdk)'s arm model, Eric Foxlin's zero-velocity updates and Frosio, Pedersini and Borghese's accelerometer autocalibration, and the camera and Joy-Con are joined by a Kalman filter (R. E. Kalman, 1960). Bare camera hands take their gestures after [ALVR](https://github.com/alvr-org/ALVR)'s `hand_gestures.rs` (MIT) and Meta Quest's menu pinch, with distances checked on photos from the [HaGRID](https://github.com/hukenovs/hagrid) gesture set (CC BY-SA 4.0, not included), fit the palm with Horn's quaternion method (1987) and smooth it with the 1€ filter (Casiez, Roussel and Vogel, 2012); the white glove model follows Valve's OpenVR render model reference and Index controller poses, is meshed with surface nets (Gibson, 1998; Mikola Lysenko) and blends and shades it with Inigo Quilez's smooth minimum and distance-field ambient occlusion, porting his polygon distance (MIT); see [docs/third-party/handtracking](docs/third-party/handtracking/README.md).
The Store installs and runs apps with [Flatpak](https://github.com/flatpak/flatpak) (LGPL-2.1). Its sandbox goes through DroidDeck's own bubblewrap shim, which follows how Flatpak hands bwrap its environment. Chromium and Electron apps from Flathub start their browser processes through [zypak](https://github.com/refi64/zypak) by [@refi64](https://github.com/refi64) (BSD-3-Clause); its source was read to see that it always spawns them through Flatpak's spawn portal, which is why DroidDeck starts them directly without Chromium's sandbox instead.
+202 -30
View File
@@ -4,6 +4,32 @@ plugins {
apply plugin: 'kotlin-android'
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
// Machine-specific tooling and optional CI-prepared inputs stay out of version control.
def localBuildOptions = new Properties()
def localBuildOptionsFile = rootProject.layout.projectDirectory.file('local.properties')
if (localBuildOptionsFile.asFile.exists()) {
localBuildOptions.load(new StringReader(providers.fileContents(localBuildOptionsFile).asText.get()))
}
def pythonExecutable = project.findProperty('pythonExecutable')
?: localBuildOptions.getProperty('droiddeck.python', 'python3')
def vitureSdkDir = project.hasProperty('vitureSdkDir') ? project.property('vitureSdkDir') : localBuildOptions.getProperty('droiddeck.vitureSdk', '')
def uvcDir = project.hasProperty('uvcDir') ? project.property('uvcDir') : localBuildOptions.getProperty('droiddeck.uvcDir', '')
if (uvcDir) {
['arm64-v8a/libusb.so', 'arm64-v8a/libuvc.a', 'include/libusb.h', 'include/libuvc/libuvc.h',
'licenses/libusb-COPYING', 'licenses/libuvc-LICENSE.txt'].each { required ->
if (!new File(uvcDir, required).isFile()) throw new GradleException("Incomplete UVC build: missing ${required}")
}
}
if (vitureSdkDir) {
['android/arm64-v8a/libglasses.so', 'android/arm64-v8a/libcarina_vio.so',
'android/arm64-v8a/libcloud_protocol.so', 'include/viture_glasses_provider.h',
'android/LICENSE', 'android/NOTICE'].each { required ->
if (!new File(vitureSdkDir, required).isFile()) {
throw new GradleException("Incomplete VITURE Android SDK: missing ${required}")
}
}
}
def gitValue = { List<String> arguments ->
try {
def output = providers.exec {
@@ -49,23 +75,71 @@ if (!(treeState in ['clean', 'dirty'])) {
buildLabel = "${buildOrigin} ${treeState} · ${buildLabel}"
def escapedBuildLabel = buildLabel.replace('\\', '\\\\').replace('"', '\\"')
def sourceCommit = providers.environmentVariable('DROIDDECK_SOURCE_COMMIT').getOrNull()
?: providers.environmentVariable('GITHUB_SHA').getOrNull() ?: gitValue(['rev-parse', 'HEAD']) ?: ''
def prNumber = (providers.environmentVariable('DROIDDECK_PR_NUMBER').getOrNull() ?: '0').with { it.isInteger() ? it : '0' }
def ciBuild = providers.environmentVariable('GITHUB_ACTIONS').getOrNull() == 'true'
def releaseSigner = rootProject.file('keystore/release-signer.sha256').text.trim()
def commitTime = (gitValue(['show', '-s', '--format=%ct', 'HEAD']) ?: '0').with { it.isLong() ? it : '0' }
def desktopKde = rootProject.file('tools/desktop-kde/release.env').readLines()
.findAll { it.contains('=') && !it.startsWith('#') }
.collectEntries { def kv = it.split('=', 2); [(kv[0].trim()): kv[1].trim()] }
def trackingPack = rootProject.file('tools/packs/tracking/release.env').readLines()
.findAll { it.contains('=') && !it.startsWith('#') }
.collectEntries { def kv = it.split('=', 2); [(kv[0].trim()): kv[1].trim()] }
def addonPacks = ['proot', 'directaudio', 'tracking', 'xbox', 'comet'].collectEntries { name ->
def pack = file("build/packs/${name}.tar.zst")
if (!pack.isFile()) {
if (ciBuild) throw new GradleException("Add-on pack ${pack} is missing: run tools/packs/build.sh app/build/packs")
return [(name): [sha256: '', size: 0L]]
}
def digest = java.security.MessageDigest.getInstance('SHA-256')
pack.withInputStream { input ->
byte[] buffer = new byte[1 << 20]
int n
while ((n = input.read(buffer)) > 0) digest.update(buffer, 0, n)
}
[(name): [sha256: digest.digest().encodeHex().toString(), size: pack.length()]]
}
def desktopRemovalSources = new groovy.json.JsonSlurper().parse(file('src/main/assets/desktop-removal/sources.json'))
if (desktopRemovalSources.archives[desktopKde.DESKTOP_KDE_TAG] != desktopKde.DESKTOP_KDE_SHA256) {
throw new GradleException('Desktop removal inventories do not match the pinned archive')
}
if (!file('src/main/assets/desktop-removal/versions.txt').readLines().contains(desktopKde.DESKTOP_KDE_TAG)) {
throw new GradleException('Regenerate desktop removal inventories for ' + desktopKde.DESKTOP_KDE_TAG)
}
def variantSuffixes = rootProject.file('tools/release/variants.txt').readLines()
.findAll { it.trim() && !it.startsWith('#') }
.collect { def f = it.trim().split(/\s+/); "${f[1]}=${f[2] == '-' ? '' : f[2]}" }
.join(';')
android {
namespace 'com.droiddeck.launcher'
compileSdk 34
// The NDK is pinned by property so CI can hand in whatever the runner already has rather than
// downloading a second copy; see gradle.properties.
ndkVersion project.findProperty('ndkVersion') ?: '27.3.13750724'
defaultConfig {
applicationId "com.droiddeck.launcher"
minSdkVersion 26
// 28 is not a leftover. An app on targetSdk 29+ runs in a domain that may not execute a
// 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 14
versionName "0.5.0"
buildConfigField "String", "BUILD_LABEL", "\"${escapedBuildLabel}\""
buildConfigField "String", "SOURCE_COMMIT", "\"${sourceCommit}\""
buildConfigField "int", "PR_NUMBER", "${prNumber}"
buildConfigField "boolean", "CI_BUILD", "${ciBuild}"
buildConfigField "String", "RELEASE_SIGNER", "\"${releaseSigner}\""
buildConfigField "long", "COMMIT_TIME", "${commitTime}L * 1000L"
buildConfigField "String", "VARIANT_SUFFIXES", "\"${variantSuffixes}\""
buildConfigField "String", "SGDB_API_KEY", "\"${System.getenv("SGDB_API_KEY") ?: project.findProperty("sgdbApiKey") ?: ""}\""
buildConfigField "String", "DESKTOP_KDE_TAG", "\"${desktopKde.DESKTOP_KDE_TAG}\""
buildConfigField "String", "DESKTOP_KDE_URL", "\"https://github.com/${desktopKde.DESKTOP_KDE_REPO}/releases/download/${desktopKde.DESKTOP_KDE_TAG}/desktop-kde.tar.zst\""
buildConfigField "String", "DESKTOP_KDE_SHA256", "\"${desktopKde.DESKTOP_KDE_SHA256}\""
buildConfigField "long", "DESKTOP_KDE_SIZE", "${desktopKde.DESKTOP_KDE_SIZE}L"
addonPacks.each { name, pack ->
buildConfigField "String", "PACK_${name.toUpperCase()}_SHA256", "\"${pack.sha256}\""
buildConfigField "long", "PACK_${name.toUpperCase()}_SIZE", "${pack.size}L"
}
ndk {
abiFilters 'arm64-v8a'
@@ -73,6 +147,8 @@ android {
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_shared'
arguments "-DDROIDDECK_VITURE_SDK_DIR=${vitureSdkDir.toString().replace('\\', '/')}"
arguments "-DDROIDDECK_UVC_DIR=${uvcDir.toString().replace('\\', '/')}"
}
}
}
@@ -85,10 +161,6 @@ android {
}
signingConfigs {
// AOSP testkey ([email protected]), the same key Bannerlator ships with. It is publicly
// known, so it proves nothing about who built the apk - what it buys is ONE stable
// signature: every build installs over the last one instead of asking the user to
// uninstall first. v1 (JAR) + v2 + v3 so every installer and Android version accepts it.
testkey {
storeFile file("${rootDir}/keystore/testkey.p12")
storeType "PKCS12"
@@ -105,6 +177,11 @@ android {
debug {
minifyEnabled false
signingConfig signingConfigs.testkey
def localSuffix = localBuildOptions.getProperty('droiddeck.debugApplicationIdSuffix')
if (localSuffix) {
applicationIdSuffix localSuffix
resValue 'string', 'app_name', 'DroidDeck XR Dev'
}
}
release {
minifyEnabled false
@@ -113,33 +190,43 @@ android {
}
lint {
// targetSdk 28 is deliberate and structural: an app on 29+ may not execute a file out of
// its own data directory, and executing a rootfs is the whole of what this app does. Lint
// fails a release build over it because Google Play will not accept an sdk that old -
// true, and not something this app can trade away. Every other check stays on.
disable 'ExpiredTargetSdkVersion'
}
androidResources {
// aapt's default ignore list, plus the trees shipped as add-on packs (tools/packs).
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~:<dir>droiddeck-xbox:<dir>droiddeck-comet:<dir>directaudio'
}
packagingOptions {
resources {
// Kadb's Bouncy Castle artifacts repeat the same multi-release JAR manifest.
excludes += ['META-INF/versions/9/OSGI-INF/MANIFEST.MF']
}
jniLibs {
// The PulseAudio daemon is executed from the native library directory and its
// libraries are read out of the apk by the class loader: both want the old,
// uncompressed-and-extracted layout.
useLegacyPackaging = true
// Prefer the PTY helper built by this app's NDK target over Termux's prebuilt copy.
pickFirsts += ['**/libtermux.so']
// Shipped in the proot and tracking packs (tools/packs) and run or loaded from there.
excludes += ['**/libproot.so', '**/libproot-loader.so', '**/libopencv_java4.so', '**/libmediapipe_tasks_jni.so']
}
}
buildFeatures {
prefab true
compose true
buildConfig true
}
testOptions {
unitTests.returnDefaultValues = true
unitTests.includeAndroidResources = true
unitTests.all {
// Robolectric keeps a sandbox per SDK level the tests ask for; seven no longer fit in Gradle's default 512 MB.
maxHeapSize = '2g'
def logTarget = providers.systemProperty('robolectric.logging').orNull
if (logTarget != null) systemProperty 'robolectric.logging', logTarget
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
@@ -149,24 +236,109 @@ android {
}
}
// Vendor binaries remain in the user's SDK directory; never vendor them into Git.
if (vitureSdkDir) {
android.sourceSets.main.jniLibs.srcDir(new File(vitureSdkDir, 'android'))
def stageVitureNotices = tasks.register('stageVitureNotices', Copy) {
from(new File(vitureSdkDir, 'android')) { include 'LICENSE', 'NOTICE' }
into(layout.buildDirectory.dir('vitureAssets/licenses/viture'))
}
tasks.named('preBuild').configure { dependsOn(stageVitureNotices) }
}
def stageSessionScripts = tasks.register('stageSessionScripts', Copy) {
from(rootProject.file('tools/linuxfs/overlay')) {
include 'usr/local/bin/droiddeck-*', 'usr/local/bin/steam-compatibility', 'usr/bin/**'
}
into(layout.projectDirectory.dir('src/main/assets/linuxfs'))
}
tasks.named('preBuild').configure { dependsOn(stageSessionScripts) }
def runtimeAssetsDir = layout.buildDirectory.dir('runtimeAssets')
def stageRuntimeAssets = tasks.register('stageRuntimeAssets', Sync) {
dependsOn(stageSessionScripts)
from(layout.projectDirectory.dir('src/main/assets')) { exclude 'pulseaudio.tzst' }
from(layout.buildDirectory.dir('prepared-assets'))
into(runtimeAssetsDir)
}
android.sourceSets.main.assets.setSrcDirs([runtimeAssetsDir])
if (vitureSdkDir) android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('vitureAssets'))
if (uvcDir) {
def stageUvcNotices = tasks.register('stageUvcNotices', Copy) {
from(new File(uvcDir, 'licenses'))
into(layout.buildDirectory.dir('uvcAssets/licenses/uvc'))
}
tasks.named('preBuild').configure { dependsOn(stageUvcNotices) }
android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('uvcAssets'))
}
tasks.named('preBuild').configure { dependsOn(stageRuntimeAssets) }
def verifyRuntimeInputs = tasks.register('verifyRuntimeInputs', Exec) {
mustRunAfter(stageSessionScripts, stageRuntimeAssets)
workingDir rootProject.projectDir
commandLine pythonExecutable, 'tools/release/runtime_inputs.py', 'verify'
}
tasks.configureEach { task ->
if (task.name in ['packageDebug', 'packageRelease']) task.dependsOn(verifyRuntimeInputs)
}
def rustCrateDir = layout.projectDirectory.dir('src/main/rust/stores')
def rustJniLibsDir = layout.buildDirectory.dir('rustJniLibs')
def skipRust = (project.findProperty('skipRust')
?: localBuildOptions.getProperty('droiddeck.skipRust', 'false')).toString().toBoolean()
def buildRustStores = tasks.register('buildRustStores', Exec) {
description = 'Cross-compiles the store download engines (libdroiddeckstores.so) with cargo-ndk.'
onlyIf { !skipRust }
inputs.dir(rustCrateDir.dir('src'))
inputs.files(rustCrateDir.file('Cargo.toml'), rustCrateDir.file('Cargo.lock'))
inputs.property('ndkVersion', android.ndkVersion)
if (!skipRust) inputs.property('rustToolchain', providers.exec {
environment 'PATH', "${System.getProperty('user.home')}/.cargo/bin${File.pathSeparator}${System.getenv('PATH')}"
commandLine 'rustup', 'run', 'stable', 'rustc', '-Vv'
}.standardOutput.asText)
outputs.dir(rustJniLibsDir)
workingDir rustCrateDir
def cargoBin = "${System.getProperty('user.home')}/.cargo/bin"
environment 'PATH', "${cargoBin}${File.pathSeparator}${System.getenv('PATH')}"
environment 'ANDROID_NDK_HOME', android.ndkDirectory.absolutePath
environment 'CARGO_TERM_COLOR', 'never'
if (!skipRust) environment 'RUSTC', providers.exec {
commandLine 'rustup', 'which', '--toolchain', 'stable', 'rustc'
}.standardOutput.asText.get().trim()
commandLine 'rustup', 'run', 'stable', 'cargo', 'ndk', '--target', 'arm64-v8a', '--platform', '26',
'--output-dir', rustJniLibsDir.get().asFile.absolutePath,
'build', '--release', '--locked'
}
if (!skipRust) android.sourceSets.main.jniLibs.srcDir(rustJniLibsDir)
tasks.named('preBuild').configure { dependsOn(buildRustStores) }
dependencies {
// The runtime tarball is a zstd-compressed tar.
implementation 'com.github.luben:zstd-jni:1.5.2-3@aar'
implementation 'org.apache.commons:commons-compress:1.20'
// The UI: every menu, dialog and overlay is Compose. The session's SurfaceView and the
// on-screen pad are Views underneath it, which is what they are in every Compose app.
implementation "org.opencv:opencv:${trackingPack.OPENCV_VERSION}"
implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.49'
implementation 'com.github.luben:zstd-jni:1.5.7-4@aar'
implementation 'org.apache.commons:commons-compress:1.27.1'
implementation 'org.tukaani:xz:1.10'
implementation platform('androidx.compose:compose-bom:2024.02.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.ui:ui-tooling-preview'
debugImplementation 'androidx.compose.ui:ui-tooling'
implementation 'androidx.activity:activity-compose:1.8.2'
// The File Manager, Bannerlator's: its icon set, image thumbnails and the coroutines it
// copies on. Same versions as Bannerlator so the screen stays a straight copy.
implementation 'androidx.compose.material:material-icons-extended'
implementation 'io.coil-kt:coil-compose:2.6.0'
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
// ADB-over-Wi-Fi pairing and authenticated shell client. Users still approve pairing from
// Android's Wireless debugging screen; DroidDeck only runs its one fixed setting command.
implementation 'com.flyfishxu:kadb-android:1.2.1'
// Android terminal view/emulator used by the in-session secondary-display Linux shell.
implementation 'com.termux.termux-app:terminal-view:0.118.0'
implementation 'androidx.camera:camera-camera2:1.4.2'
implementation 'androidx.camera:camera-lifecycle:1.4.2'
implementation 'androidx.webkit:webkit:1.12.1'
implementation("com.google.mediapipe:tasks-vision:${trackingPack.MEDIAPIPE_VERSION}") {
exclude group: 'com.google.auto.value', module: 'auto-value'
}
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.16.1'
testImplementation 'androidx.compose.ui:ui-test-junit4'
testRuntimeOnly 'com.github.luben:zstd-jni:1.5.7-4@jar'
}
-19
View File
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<!-- Debug builds only. Android grants DUMP to adb shell; release APKs have no bridge. -->
<activity
android:name=".agent.AgentStartActivity"
android:exported="true"
android:excludeFromRecents="true"
android:noHistory="true"
android:permission="android.permission.DUMP"
android:theme="@style/SessionTheme" />
<provider
android:name=".agent.AgentBridgeProvider"
android:authorities="${applicationId}.agent"
android:exported="true"
android:permission="android.permission.DUMP"
android:grantUriPermissions="false" />
</application>
</manifest>
@@ -1,166 +0,0 @@
package com.droiddeck.launcher.agent
import android.Manifest
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.content.pm.PackageManager
import android.database.Cursor
import android.net.Uri
import android.os.Binder
import android.os.Bundle
import android.os.Process
import com.droiddeck.launcher.BuildConfig
import com.droiddeck.launcher.runtime.LinuxRuntime
import com.droiddeck.launcher.runtime.LinuxRuntimeInstaller
import com.droiddeck.launcher.session.SessionArtifacts
import com.droiddeck.launcher.session.SessionEvents
import com.droiddeck.launcher.session.SessionPhase
import com.droiddeck.launcher.session.SessionPaths
import com.droiddeck.launcher.session.SessionService
import com.droiddeck.launcher.session.SessionState
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
/** A shell-only control surface, installed only by debug builds. */
class AgentBridgeProvider : ContentProvider() {
override fun onCreate(): Boolean {
val appContext = context?.applicationContext ?: return false
Thread({
try {
SessionArtifacts.finishAbandoned(appContext)
} finally {
recoveryComplete.countDown()
}
}, "agent-recover-artifacts").start()
return true
}
override fun call(method: String, arg: String?, extras: Bundle?): Bundle {
val context = requireNotNull(context)
if (Binder.getCallingUid() != Process.myUid() &&
context.checkCallingPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED
) {
throw SecurityException("android.permission.DUMP is required")
}
val response = try {
when (method) {
"state" -> state(context).put("ok", true)
"start" -> error("USE_DROIDDECKCTL", "Start sessions with tools/droiddeckctl so Android launches a visible Activity")
"stop" -> stop(context)
"resume" -> resume(context)
else -> error("UNKNOWN_COMMAND", "Unknown agent command '$method'")
}
} catch (e: Exception) {
error("COMMAND_FAILED", e.message ?: e.javaClass.simpleName)
}
return Bundle().apply { putString(RESULT_JSON, response.toString()) }
}
private fun state(context: Context): JSONObject {
val runtimeVersion = LinuxRuntimeInstaller.installedVersion(context)
val dir = SessionState.logDirectory ?: SessionState.logFile?.parentFile ?: latestSessionDirectory()
val session = JSONObject()
.put("id", SessionState.sessionId ?: dir?.name ?: JSONObject.NULL)
.put("phase", SessionState.phase.name)
.put("running", SessionState.running)
.put("mode", SessionState.mode)
.put("program", SessionState.program ?: JSONObject.NULL)
.put("steamUi", SessionState.steamUi ?: JSONObject.NULL)
.put("steamUrl", SessionState.steamUrl ?: JSONObject.NULL)
.put("suspended", SessionState.suspended)
.put("firstFrame", SessionState.firstFrameSeen)
.put("output", JSONArray().put(SessionState.outputSize.first).put(SessionState.outputSize.second))
.put("refreshHz", SessionState.refreshHz.toDouble())
.put("lastTransitionAt", SessionState.lastTransitionAt)
.put("guestPid", SessionState.guestPid.takeIf { it > 1 } ?: JSONObject.NULL)
.put("installing", SessionState.installing ?: JSONObject.NULL)
.put("logDir", dir?.absolutePath ?: JSONObject.NULL)
.put("eventsFile", dir?.let { File(it, "events.jsonl").absolutePath } ?: JSONObject.NULL)
.put("artifactsAvailable", dir?.isDirectory ?: false)
.put("artifactsComplete", dir?.let { File(it, ".complete").isFile } ?: false)
val failure = if (SessionState.failureCode == null) JSONObject.NULL else JSONObject()
.put("code", SessionState.failureCode)
.put("message", SessionState.failureMessage ?: JSONObject.NULL)
.put("status", SessionState.failureStatus ?: JSONObject.NULL)
session.put("failure", failure)
return JSONObject()
.put("schema", 1)
.put("build", BuildConfig.BUILD_LABEL)
.put("appVersion", context.packageManager.getPackageInfo(context.packageName, 0).versionName)
.put("runtime", JSONObject()
.put("installed", LinuxRuntime.isInstalled(context))
.put("version", runtimeVersion ?: JSONObject.NULL))
.put("session", session)
}
private fun latestSessionDirectory(): File? {
val parent = LinuxRuntime.debugLogDir()
return parent.listFiles { file -> file.isDirectory && file.name.startsWith("session-") }
?.maxWithOrNull(compareBy<File> { it.lastModified() }.thenBy { it.name })
}
private fun stop(context: Context): JSONObject {
if (SessionState.running) {
SessionService.stop(context)
return JSONObject().put("ok", true).put("command", "stop")
}
if (SessionState.phase == SessionPhase.STOPPING) {
return JSONObject().put("ok", true).put("command", "stop")
}
if (SessionState.phase in STARTING_PHASES) {
SessionState.stopRequested = true
SessionEvents.transition(SessionPhase.STOPPING, "session.stop_requested")
return JSONObject().put("ok", true).put("command", "stop")
}
return error("NO_ACTIVE_SESSION", "There is no session to stop")
}
private fun resume(context: Context): JSONObject {
if (!SessionState.running || !SessionState.suspended) {
return error("SESSION_NOT_SUSPENDED", "There is no suspended session to resume")
}
SessionService.resume(context)
return JSONObject().put("ok", true).put("command", "resume")
}
private fun error(code: String, message: String) = JSONObject()
.put("ok", false)
.put("error", JSONObject().put("code", code).put("message", message))
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = null
override fun getType(uri: Uri): String? = null
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int = 0
companion object {
const val RESULT_JSON = "json"
private val recoveryComplete = CountDownLatch(1)
fun awaitRecovery() {
try {
if (!recoveryComplete.await(90, TimeUnit.SECONDS)) {
throw IllegalStateException("Session artifact recovery did not finish")
}
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
throw IllegalStateException("Interrupted while recovering session artifacts", e)
}
}
private val STARTING_PHASES = setOf(
SessionPhase.PREPARING,
SessionPhase.INSTALLING_RUNTIME,
SessionPhase.STARTING_COMPOSITOR,
SessionPhase.STARTING_GUEST,
SessionPhase.STARTING_STEAM,
)
}
}
+129 -42
View File
@@ -1,33 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Session logs go to Downloads so a failed run can be handed over as one folder. -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- The session keeps running while the app is not on screen: a foreground service holds the
process at perceptible priority, and a high-performance WiFi lock keeps a backgrounded
download from being throttled to nothing by the radio's power-save. -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Ends background processes of another app that holds the Steam login. GameHub starts
itself with the phone via boot receivers and signs this client out seconds after every
login; a normal permission, and it cannot touch anything in the foreground. -->
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<!-- The microphone for the Linux session, only when the user turns it on: the DirectAudio
relay helper opens the input stream under this uid, and PulseAudio exposes it to the
Steam client as DirectAudioMic. -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.vr.headtracking" android:version="1" android:required="false" />
<application
android:name=".App"
android:localeConfig="@xml/locales_config"
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@@ -38,27 +43,71 @@
android:appCategory="game"
android:isGame="true">
<!-- A game, to the OS: Android 12+ routes the category through GameManager and the OEM
performance frameworks (Game Booster, Game Turbo, Game Space) key off it. Without it
a regular flagship runs the session under its "app" power profile - a conservative
governor and GPU DVFS - which is why the same chipset feels slower here than in a
gaming phone whose mode is on for everything. The config says what the OS may do. -->
<meta-data android:name="android.game_mode_config" android:resource="@xml/game_mode_config" />
<service android:name=".glasses.LumaRgbCaptureService" android:exported="false"
android:process=":rgb_diagnostic" />
<meta-data android:name="com.oculus.supportedDevices" android:value="quest2|quest3|quest3s" />
<activity android:name=".QuestXrActivity" android:exported="true" android:launchMode="singleTask"
android:permission="android.permission.DUMP"
android:resizeableActivity="false" android:screenOrientation="landscape"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard|navigation|uiMode"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<meta-data android:name="android.app.lib_name" android:value="droiddeckxr" />
<intent-filter>
<action android:name="com.droiddeck.launcher.XR" />
<category android:name="com.oculus.intent.category.VR" />
<category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="sensorLandscape">
android:screenOrientation="sensorLandscape"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density|keyboard|keyboardHidden|navigation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.CAR_LAUNCHER" />
<data android:scheme="droiddeck" android:host="game" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="com.droiddeck.launcher.LAUNCH_GAME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.CAR_LAUNCHER" />
</intent-filter>
</activity>
<!-- Android owns the Home key. This small translucent Home handler routes the resulting
Home launch either back into a running Steam session or to the regular front end.
Off until the user turns on "Use as a Home screen" (Setup): while it is disabled the
app is not a Home app at all - not offered, not in the "choose Home" list. -->
<activity-alias
android:name=".LauncherDark"
android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.CAR_LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias
android:name=".LauncherLight"
android:targetActivity=".MainActivity"
android:icon="@mipmap/ic_launcher_light"
android:enabled="false"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.CAR_LAUNCHER" />
</intent-filter>
</activity-alias>
<activity
android:name=".HomeActivity"
android:enabled="false"
@@ -75,16 +124,7 @@
</intent-filter>
</activity>
<!--
A session keeps a Surface, a compositor thread and a proot tree alive; a configuration
change must not tear that down and rebuild it, so the activity handles them itself.
Landscape only: gamescope is told one output size when it starts.
Resizable, deliberately: a foldable opening or closing changes the window, and the
compositor re-fits the picture with bars. Declared non-resizable, the system keeps a
fixed-shape window instead and STRETCHES it onto the other panel - the app never sees a
size change, so nothing it does can help.
-->
<activity
android:name=".input.ControllerEditorActivity"
android:exported="false"
@@ -97,35 +137,70 @@
android:name=".files.FileManagerActivity"
android:exported="false"
android:screenOrientation="sensorLandscape"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
android:theme="@style/AppTheme" />
<activity
android:name=".files.FilePickerActivity"
android:exported="false"
android:screenOrientation="sensorLandscape"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
android:theme="@style/AppTheme" />
<activity android:name=".stores.xbox.XboxLoginActivity" android:exported="false"
android:process=":xboxlogin" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
android:theme="@style/AppTheme" />
<activity
android:name=".stores.StoreLoginActivity"
android:exported="false"
android:screenOrientation="sensorLandscape"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
android:theme="@style/AppTheme" />
<activity
android:name=".agent.AgentStartActivity"
android:exported="true"
android:excludeFromRecents="true"
android:noHistory="true"
android:permission="android.permission.DUMP"
android:theme="@style/SessionTheme" />
<activity
android:name=".SessionActivity"
android:exported="false"
android:launchMode="singleTop"
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
android:theme="@style/SessionTheme"
android:preferMinimalPostProcessing="true"
android:screenOrientation="sensorLandscape"
android:resizeableActivity="true"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
<!--
Owns the session - gamescope, the Steam client, audio - so it survives the activity
going away. Without it Android demotes the process as soon as the last visible
activity goes, and the low-memory killer reaps the guest: leaving Big Picture to
answer a message would come back to a dead Steam and a half-finished download.
-->
<activity
android:name=".gpu.SteamVrRestartActivity"
android:exported="false"
android:process=":vr_recovery"
android:taskAffinity="${applicationId}.vr_recovery"
android:excludeFromRecents="true"
android:screenOrientation="sensorLandscape"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density"
android:theme="@style/SessionTheme" />
<service
android:name=".session.SessionService"
android:exported="false"
android:stopWithTask="false" />
<!-- Hands a zipped session folder to the share sheet; nothing else is exposed. -->
<service
android:name=".runtime.RuntimeInstallService"
android:exported="false" />
<service
android:name=".core.WirelessAdbPairingService"
android:exported="false" />
<service
android:name=".stores.download.StoreDownloadService"
android:exported="false"
android:foregroundServiceType="dataSync" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.logs"
@@ -135,8 +210,6 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/log_share_paths" />
</provider>
<!-- The app's data folder in Android's file picker ("Open from" > DroidDeck). Only the
system's documents UI may bind it; apps get a file only when the user picks it. -->
<provider
android:name=".files.AppFilesProvider"
android:authorities="${applicationId}.documents"
@@ -147,5 +220,19 @@
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
<provider
android:name=".agent.AgentBridgeProvider"
android:authorities="${applicationId}.agent"
android:exported="true"
android:permission="android.permission.DUMP"
android:grantUriPermissions="false" />
<activity android:name=".ui.SpatialTrackingActivity" android:exported="false" />
<service
android:name="com.google.android.datatransport.runtime.backends.TransportBackendDiscovery"
tools:node="merge">
<meta-data
android:name="backend:com.google.android.datatransport.cct.CctBackendFactory"
tools:node="remove" />
</service>
</application>
</manifest>
@@ -0,0 +1,102 @@
{
"version": 1,
"identity": "luma-ultra-base-v1",
"width": 640,
"height": 480,
"fisheye": false,
"error": 1.1209395390660726,
"verified": false,
"k1": {
"rows": 3,
"cols": 3,
"data": [
226.0499636040297,
0,
321.62348950766386,
0,
228.36987999988767,
242.2674105020684,
0,
0,
1
]
},
"d1": {
"rows": 5,
"cols": 1,
"data": [
-0.28622915867944043,
0.1194613930041054,
0.007984341063494766,
-0.0008220922923559441,
-0.025579805296512992
]
},
"k2": {
"rows": 3,
"cols": 3,
"data": [
240.3338202496282,
0,
320.8790079027563,
0,
241.14047924325277,
260.45566640784716,
0,
0,
1
]
},
"d2": {
"rows": 5,
"cols": 1,
"data": [
-0.27073208928390136,
0.08065508519805098,
-0.001460778493930769,
-0.0003694718992635806,
-0.011452505620454932
]
},
"r": {
"rows": 3,
"cols": 3,
"data": [
0.900501479835182,
0.04473903344740378,
-0.43254537762047496,
-0.08255720039597626,
0.9941919046988283,
-0.06904176485356042,
0.4269442510181227,
0.0978819468416306,
0.8989648107712929
]
},
"t": {
"rows": 3,
"cols": 1,
"data": [
-0.14117171913441395,
0.0014051874404824838,
-0.007555740272006347
]
},
"cameraToHead": [
-0.05607918,
0.023585627,
-0.07998472,
0.01024512,
0.9945703,
0.005859665,
-0.10339546
],
"deviceModel": "VITURE Luma Ultra",
"cameraPair": "left0-right0",
"sourceValidation": {
"exportDate": "2026-10-16",
"sourceDeviceVerified": true,
"capturedViews": 25,
"scope": "Starting geometry from one calibrated Luma Ultra; other units have not been individually validated."
}
}
Binary file not shown.
@@ -0,0 +1,49 @@
{
"archives": {
"lxqt-r1": "c2bb860b3b1688287451e2495be5db313ec07f8c8af501dfac2768a9f417bc86",
"desktop-kde-r1": "55747e75d0ec055ab7ea1e92a757f3be227cc3205bb9101f5b53cadef6318579",
"desktop-kde-r2": "9ba832677b498654136c69d8c8330434d17e4d53cd099acac9bde3ba429b35bf",
"emulators-r1": "a1f4806a9663d28f424b4cab1277e0ede80a92e69de5bc063cdc8b45af220164"
},
"desktopApplications": {
"ark-26.08.1-1-aarch64.pkg.tar.xz": "93e2c2b75ff20cd29b4f312ed1979ef6f27255b93232b93bfcdb31239903c1d4",
"breeze-6.7.5-1-aarch64.pkg.tar.xz": "1d7f4eb9d8d234caaea23621909f5233d19462c2f058afeeb54bed7b81bd03bd",
"breeze-gtk-6.7.5-1-any.pkg.tar.xz": "a0f59bc4fc426879c8f8a97f6dcc707fb7d1b52913da5eb7e9db83777b6957b7",
"breeze-icons-6.30.0-1-aarch64.pkg.tar.xz": "4bae4689032501156687a227024e56554820747644953c67fc8d2e2b6b0b045a",
"dolphin-26.08.1-1-aarch64.pkg.tar.xz": "fef738ab8132d3eba1156b98c116699ce0a5efcb4cb462efbacdcf4c47f79a23",
"firefox-157.0.1-1-aarch64.pkg.tar.xz": "d142fdb8a339a65eca7cea6a2bba5b29202386166abd9d59851d6757c9eb2163",
"gwenview-26.08.1-1-aarch64.pkg.tar.xz": "a9dc7d5ab73fa677905d5a57248b1b9b404b6b16e862958cc7443d387f684a47",
"kate-26.08.1-1-aarch64.pkg.tar.xz": "acf50710042746f243a3b219a1ae0f788e338f3dd9d75093e8bb671a618b83b6",
"kde-cli-tools-6.7.5-1-aarch64.pkg.tar.xz": "af466398a912a994a97fadfe46aff401d88a90d7fdce7e843dc4218098fa38ac",
"kde-gtk-config-6.7.5-1-aarch64.pkg.tar.xz": "ba6cb4cb01dbb3f2c247f2f6973ede3b64c0849f89340217b51dfbf11cb8cb5c",
"kinfocenter-6.7.5-1-aarch64.pkg.tar.xz": "f3d5416db96b50f9a5b8a284d82ae21d834a8cce646010abdd7087491c399b0b",
"konsole-26.08.1-1-aarch64.pkg.tar.xz": "1ecf87c0d11d620ebe4aeafb02788e0d5f4fe9019ad9c5d9f1011c43237dd5ec",
"kscreen-6.7.5-1-aarch64.pkg.tar.xz": "8fbaa6fedf93edeba021a5416b551663654919b27c2ee237d696ec0b0ce7d5b7",
"kwin-6.7.5-1-aarch64.pkg.tar.xz": "a7535710c74b90359f80306134b2b7581132cbac508dc84b7e07ef275cc8c3fe",
"mpv-1:0.41.0-6-aarch64.pkg.tar.xz": "6c89d00736820210a3c7f32af4cf95d155191d463f9df8bd22137c45f30a554a",
"plasma-desktop-6.7.5-1-aarch64.pkg.tar.xz": "b266dd42cea965b5ab1cc1ae600877e9d3bc83ec642c61fe1669c4457e641a55",
"plasma-integration-6.7.5-1-aarch64.pkg.tar.xz": "daa96da14d9a4d12c0d9c0341e1be6836a9a437c469d61da5e9844d51e2606cf",
"plasma-pa-6.7.5-1-aarch64.pkg.tar.xz": "02dbefe9d3e0069b2bb37cac2a6be18b8f0a1887c87704a0716e7a9268eadcd8",
"plasma-workspace-6.7.5-1-aarch64.pkg.tar.xz": "3a1455857afaa9ccf342ab6167f0f15794ee9d248b0925398ea6f62f12730a86",
"spectacle-1:6.7.5-1-aarch64.pkg.tar.xz": "0c9837412ea6119edef18bcb0a8fceb74634cb35be0ca78b3e1dd1ff2443866e",
"systemsettings-6.7.5-1-aarch64.pkg.tar.xz": "b02b6a726857d5125c92d1ec95379216c7a3bc1f187a3b46e88c0d056b29ce70",
"xdg-desktop-portal-kde-6.7.5-1-aarch64.pkg.tar.xz": "2c57d17d34d577023eb9cd7b0fca1ba9163549afe60450f6271aa148af099ef2"
},
"legacyApplications": {
"featherpad-1.6.3-1-aarch64.pkg.tar.xz": "fcac55215815c0b712453f9bcc09abfe4b518e36025a111a7a7598ef04913f3d",
"labwc-0.20.2-1-aarch64.pkg.tar.xz": "bcd4cc3945165b0fdd33c0117cd7b9dfd05cf8c327326c37f3c3a27ef23ee5ca",
"lximage-qt-2.4.0-1-aarch64.pkg.tar.xz": "5d6bdd5472ea6bcb4b993b36df3ab7010bb2e62b36a78a649708fc9a52cb0928",
"lxqt-config-2.4.0-1-aarch64.pkg.tar.xz": "4b601d798fc7689ee76787dbef8c8958c02429ee60248cd99d950c919dbefba5",
"lxqt-panel-2.4.1-1-aarch64.pkg.tar.xz": "a141e0f8049e73f859d843510c6f45d6ed9087c990bcebf44f5f2430621024c4",
"lxqt-qtplugin-2.4.0-3-aarch64.pkg.tar.xz": "61c8f828be73111ee0bd5e3163b0d64c2098dd0189710a190f42be11137695cf",
"lxqt-runner-2.4.0-1-aarch64.pkg.tar.xz": "5afa74652a37366ea4e7774cf33379977582fbdca599cef1005ebf637cbcae4b",
"lxqt-session-2.4.0-2-aarch64.pkg.tar.xz": "e9c7c8a0011859029622ad202e0d3826e4e5bc8d64659031f6efa211456ff6eb",
"lxqt-themes-2.4.0-1-any.pkg.tar.xz": "7ea16c62a1fa70cdac13cb3f8fdcdaad299ae1f5bd514acfda671484755787bf",
"lxqt-wayland-session-0.4.1-1-any.pkg.tar.xz": "cd8dc11f5035469d23b6bbd937a90593402428c43de37517412aa43fe1fc6b10",
"papirus-icon-theme-20260801-1-any.pkg.tar.xz": "4aae3f47b800a60d6fd360e55adc581408ceaafc481c5a420a681741a3966510",
"pcmanfm-qt-2.4.1-1-aarch64.pkg.tar.xz": "7ec1a6b39dd0e4055b39f0e2c0549ea263d83004e1b821fb9e42f1d7f5fff0f9",
"qterminal-2.4.0-1-aarch64.pkg.tar.xz": "aa4adc9c9d47641b9452ec8d879038a9cb4762c3b6b5299045fac968fd0ed22d",
"swaybg-1.2.2-1-aarch64.pkg.tar.xz": "a26caa16ca7d581b5ca05758677058b4b60af10dc8d83e447ad6824b762b48b8",
"wlr-randr-0.5.0-1-aarch64.pkg.tar.xz": "58c3651c6a15dc8f95a310c7e225f759aa766be2fb3c0981a9a8b567854675b3"
}
}
@@ -0,0 +1,2 @@
desktop-kde-r1
desktop-kde-r2
@@ -1,115 +0,0 @@
# DirectAudio for the Linux Steam client (relay build)
This is the driver for games that run under **Valve's ARM64 Proton inside a Linux
runtime on Android** - Bannerlator's linuxfs session with the native Steam client -
where the game process is a glibc process and cannot touch Android's audio
libraries itself.
Two pieces, both required:
| piece | what it is | where it runs |
|---|---|---|
| `aarch64-unix/winedirectaudio.so` + the two PE shells | the Wine `mmdevapi` driver, glibc build, **no AAudio inside** | in the game, inside the Linux rootfs |
| `directaudio-relay` | a small bionic program that owns the AAudio output and microphone streams | on the Android side, under the app's uid |
They meet over a unix socket. The driver connects, sends its launch config, and
gets back two shared-memory rings: one it fills with mixed game audio, one the
helper fills with microphone audio. The helper plays and records; the driver
keeps doing everything else it always did.
## Which Proton
Built inside `ValveSoftware/wine` at `proton_11.0`. The private `mmdevapi`
interface it implements is byte-identical across **Proton 11.0 (ARM64)**,
**Proton Experimental (ARM64)** and our own Wine-11 layers (checked 2026-09-19),
so this one build serves any of them. A future Proton on a Wine 12 base will
need a rebuild - the interface is not versioned, and a mismatch is silence, not
an error.
## Installing the driver next to Proton (not into it)
Steam verifies and updates its Proton depots, so files added under
`steamapps/common/Proton .../files/lib/wine/` will not survive. Put the driver
in a directory of its own and point Wine at it:
```
<somewhere>/directaudio/lib/wine/aarch64-unix/winedirectaudio.so
<somewhere>/directaudio/lib/wine/aarch64-windows/winedirectaudio.drv
<somewhere>/directaudio/lib/wine/i386-windows/winedirectaudio.drv
```
and set, for the game process:
```
WINEDLLPATH=<somewhere>/directaudio/lib/wine
```
Wine searches `WINEDLLPATH` entries with the per-arch subdirectories appended,
so both the PE shell and the unixlib are found there. In Bannerlator's runtime
the natural place to export it is the `bannerlator-proton` compatibility-tool
wrapper, which every Proton launch goes through.
## Selecting the driver
`mmdevapi` only tries the drivers named in the registry (or its built-in
`pulse,alsa,oss,coreaudio` list, which does not know about this one). Set, in the
game's prefix:
```
wine reg add "HKCU\Software\Wine\Drivers" /v Audio /d directaudio /f
```
Under Proton the prefix is `compatdata/<appid>/pfx`; run the command with that
prefix as `WINEPREFIX` using Proton's own `bin-arm64/wine`, or write the key from
the wrapper before it hands off to Proton.
## Running the helper
```
directaudio-relay --socket <path> [--log]
```
- Run it **under the app's uid**, the way the app already runs its PulseAudio
daemon. Android checks `RECORD_AUDIO` against the calling uid, so a helper
started any other way can play but cannot record.
- Start it **before** the game: the driver asks "is a helper there?" once, when
Wine picks an audio driver, and falls back to unavailable if not.
- The socket path must be reachable from inside the Linux rootfs. Bannerlator
binds the app's files directory and its runtime directory into the session at
their own paths, so any socket under either works unchanged on both sides.
- The helper serves any number of game processes; each gets its own streams.
It exits on `SIGTERM`; a game exiting tears down that game's streams only.
## Telling the driver where the helper is
```
BANNER_AUDIO_DIRECT_RELAY=<the same socket path>
```
Unset, the driver looks for `$XDG_RUNTIME_DIR/directaudio-relay`.
## Everything else is unchanged
All `BANNER_AUDIO_DIRECT_*` knobs mean what they mean in the in-process build:
`_PERF`, `_ADAPTIVE`, `_DECAY`, `_MS`, `_MAXMS`, `_BF`, `_MBF`, `_EXCLUSIVE`,
`_WATCHDOG`, `_STALL_MS`, `_DECAY_*`, `_LOG`, `_PERIOD_MS`, `_MINPERIOD_MS`, and
the live-config mailbox `_RUNTIME` (the helper watches the file; it must be a
path the helper can read on the Android side). **`BANNER_AUDIO_DIRECT_MIC=1`**
exposes the capture endpoint exactly as before: the helper opens the input
stream (`VOICE_COMMUNICATION` preset) when the game connects and makes it hot on
the game's first capture start.
One thing is different: there is one extra hand-off, the ring. The helper's
callback pulls a burst from it, so the driver keeps two bursts queued (about 8 ms
on a 192-frame device) on top of the AAudio buffer the presets control, and the
helper raises that if a callback ever finds the ring short. `get_latency` reports
the sum, so a game that sizes its own buffers from it still sees the truth.
## Proving it is running
- Helper side: `logcat -s DA-Relay:I` shows `hello from <exe>`, then `open: buffer
... ring target ...`, then `capture open` / `capture start` for the mic.
- Game side: the session log carries `DirectAudio: relay: connected - ...`.
- The game process's `/proc/<pid>/maps` shows `winedirectaudio.so` and **no**
`libaaudio.so` (it is in the helper's maps instead). The AudioFlinger track is
owned by the helper's pid.
@@ -1,4 +0,0 @@
directaudio linux-wine11 relay build
source 3548a4a730e8d01cb0cb0268d51a2fb8f23dcd33
wine ValveSoftware/wine@proton_11.0
unixlib.h sha256 fdbed263338d0719c4d12e02b045c23721de33219ec8878e4c2d7ebc9efcbe09
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Holger Bartnick
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+44 -28
View File
@@ -1,49 +1,65 @@
# The app's native side: the Wayland compositor the session presents into, the adrenotools loader
# it opens Turnip with, and one small library for the fake-input ring's memory fence.
cmake_minimum_required(VERSION 3.22.1)
project(droiddeck C CXX)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -Wno-unused-function -Wimplicit-function-declaration")
# Every library the app builds is linked with 16 KB-aligned LOAD segments. A 16 KB-page kernel
# (Android 15+ devices) refuses to load a 4 KB-only ELF; a 4 KB kernel loads a 16 KB-aligned one
# unchanged (the alignment is a multiple of its page), so this costs nothing on today's devices
# but a little padding in the file. Prebuilt libraries under jniLibs/ carry their own alignment.
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
add_subdirectory(adrenotools)
# DXVK's DXBC->SPIR-V translator (zlib licence), vendored under thirdparty/dxbc from the same
# subset upstream lsfg-vk uses. Native LSFG frame generation needs it: the Lossless Scaling
# shader chain is DXBC in every build downloadable from Steam.
# DXVK's DXBC->SPIR-V translator is zlib licensed (vendored under thirdparty/dxbc).
add_subdirectory(thirdparty/dxbc)
# The Win-FG chain self-identifies by two halves: the upstream tag it was copied from (hand-set,
# UPDATE IT when re-copying) and a hash over the sources themselves, computed here, which cannot
# rot. The compositor's frame-generation leg (waylandcomp/CMakeLists.txt) stamps both in.
set(WINFG_UPSTREAM "v0.3.0")
set(WINFG_CHAIN_SRC
framegen/winfg/framegen.cpp
framegen/winfg/framegen.hpp
framegen/winfg/record_impl.inc
framegen/winfg/config.hpp
framegen/winfg/embedded_shaders.hpp
framegen/winfg/winfg_engine.cpp
set(DDFG_UPSTREAM "v0.3.0")
set(DDFG_CHAIN_SRC
framegen/ddfg/framegen.cpp
framegen/ddfg/framegen.hpp
framegen/ddfg/record_impl.inc
framegen/ddfg/config.hpp
framegen/ddfg/embedded_shaders.hpp
framegen/ddfg/ddfg_engine.cpp
)
set(WINFG_HASH_INPUT "")
foreach(f ${WINFG_CHAIN_SRC})
set(DDFG_HASH_INPUT "")
foreach(f ${DDFG_CHAIN_SRC})
file(SHA256 "${CMAKE_CURRENT_SOURCE_DIR}/${f}" _h)
string(APPEND WINFG_HASH_INPUT "${_h}")
string(APPEND DDFG_HASH_INPUT "${_h}")
endforeach()
string(SHA256 WINFG_CHAIN_HASH_FULL "${WINFG_HASH_INPUT}")
string(SUBSTRING "${WINFG_CHAIN_HASH_FULL}" 0 12 WINFG_CHAIN_HASH)
message(STATUS "win-fg chain: ${WINFG_UPSTREAM} (src ${WINFG_CHAIN_HASH})")
string(SHA256 DDFG_CHAIN_HASH_FULL "${DDFG_HASH_INPUT}")
string(SUBSTRING "${DDFG_CHAIN_HASH_FULL}" 0 12 DDFG_CHAIN_HASH)
message(STATUS "dd-fg chain: win-fg ${DDFG_UPSTREAM} (src ${DDFG_CHAIN_HASH})")
add_subdirectory(waylandcomp)
add_subdirectory(questxr)
add_library(sdnative SHARED sdnative/sdnative.c)
target_link_libraries(sdnative log)
# Rebuild Termux terminal-emulator's PTY JNI helper with the app's NDK instead of carrying its
# prebuilt libtermux.so with a redundant libstdc++.so dependency.
add_library(deviceinfo SHARED deviceinfo/vkinfo.c)
target_link_libraries(deviceinfo dl)
add_library(termux SHARED termux/termux.c)
add_library(ddvenc SHARED venc/venc_server.cpp)
target_compile_features(ddvenc PRIVATE cxx_std_17)
target_link_libraries(ddvenc mediandk log)
# The optional proprietary SDK is supplied locally; its headers/libraries are not vendored.
if(DROIDDECK_VITURE_SDK_DIR)
add_library(droiddeck_viture SHARED viture/sdk_bridge.cpp)
target_compile_features(droiddeck_viture PRIVATE cxx_std_17)
target_include_directories(droiddeck_viture PRIVATE "${DROIDDECK_VITURE_SDK_DIR}/include")
target_link_directories(droiddeck_viture PRIVATE "${DROIDDECK_VITURE_SDK_DIR}/android/${ANDROID_ABI}")
target_link_libraries(droiddeck_viture PRIVATE glasses log)
endif()
if(DROIDDECK_UVC_DIR)
add_library(rgb_usb SHARED IMPORTED)
set_target_properties(rgb_usb PROPERTIES IMPORTED_LOCATION "${DROIDDECK_UVC_DIR}/${ANDROID_ABI}/libusb.so")
add_library(rgb_uvc STATIC IMPORTED)
set_target_properties(rgb_uvc PROPERTIES IMPORTED_LOCATION "${DROIDDECK_UVC_DIR}/${ANDROID_ABI}/libuvc.a")
add_library(droiddeck_uvc SHARED viture/uvc_bridge.cpp)
target_compile_features(droiddeck_uvc PRIVATE cxx_std_17)
target_include_directories(droiddeck_uvc PRIVATE "${DROIDDECK_UVC_DIR}/include")
target_link_libraries(droiddeck_uvc PRIVATE rgb_uvc rgb_usb log)
target_link_options(droiddeck_uvc PRIVATE -Wl,-z,max-page-size=16384)
endif()
@@ -3,7 +3,6 @@
aarch64-linux-gnu-gcc -c src/bcenabler_patch.s -o /tmp/patch.o
aarch64-linux-gnu-objcopy -O binary -j .text /tmp/patch.o /tmp/patch.bin
# Must be smaller than a single page
bin2c -o gen/bcenabler_patch.h -n PatchRawData /tmp/patch.bin
rm /tmp/patch.o
@@ -1,6 +1,4 @@
/* Generated by bin2c, do not edit manually */
/* Contents of file /tmp/patch.bin */
const long int PatchRawData_size = 264;
const unsigned char PatchRawData[264] = {
0x1F, 0x0C, 0x02, 0x71, 0x61, 0x00, 0x00, 0x54, 0xE0, 0x08, 0x80, 0x52, 0x3E, 0x00, 0x00, 0x14,
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#ifdef __cplusplus
@@ -11,29 +12,14 @@ extern "C" {
#include <stdint.h>
/**
* @brief Describes the level of BCeNabler support for a driver
*/
enum adrenotools_bcn_type {
ADRENOTOOLS_BCN_INCOMPATIBLE, //!< Driver isn't supported by BCeNabler
ADRENOTOOLS_BCN_BLOB, //!< Driver already supports BCn textures so BCeNabler isn't necessary
ADRENOTOOLS_BCN_PATCH //!< Driver can be patched with BCeNabler to support BCn textures
ADRENOTOOLS_BCN_INCOMPATIBLE,
ADRENOTOOLS_BCN_BLOB,
ADRENOTOOLS_BCN_PATCH
};
/**
* @brief Checks the status of BCn support in the supplied driver
* @param major The major part of VkPhysicalDeviceProperties::driverVersion
* @param minor The minor part of VkPhysicalDeviceProperties::driverVersion
* @param vendorId VkPhysicalDeviceProperties::vendorId
*/
enum adrenotools_bcn_type adrenotools_get_bcn_type(uint32_t major, uint32_t minor, uint32_t vendorId);
/**
* @brief Patches the Adreno graphics driver to enable support for BCn compressed formats
* @note adrenotools_get_bcn_type MUST be checked to equal ADRENOTOOLS_BCN_PATCH before calling this
* @param vkGetPhysicalDeviceFormatPropertiesFn A pointer to vkGetPhysicalDeviceFormatProperties obtained through vkGetInstanceProcAddr. This is used to find the correct function to patch
* @return If the patching succeeded, if false the driver will be in an undefined state
*/
bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn);
#ifdef __cplusplus
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#ifdef __cplusplus
@@ -10,60 +11,16 @@ extern "C" {
#include <stdbool.h>
#include "priv.h"
/**
* @brief Opens a new libvulkan.so instance according to `flags`
* @remark IMPORTANT: The app MUST be packaged w/ useLegacyPackaging = true. The reason for this is
* that we install the hook in hookLibDir; but this folder won't be set correctly when useLegacyPackaging == false
* because Android expects to read the *.so directly from the apk (instead of decompressing them into nativeLibraryDir).
* @param dlopenMode The dlopen mode to use when opening libvulkan
* @param featureFlags Which adrenotools driver features to enable
* @param tmpLibDir A writable directory to hold patched libraries, only used on api < 29 due to the lack of memfd support. If nullptr is passed and the API version is < 29 memfd usage will be attempted and if unsupported nullptr will be returned
* @param hookLibDir The directory holding the built hooks.
* IMPORTANT: This path MUST point to whatever `getApplicationInfo().nativeLibraryDir` (Kotlin code) returns.
* Failing to do so will result in this function returning a valid ptr but then the hook will fail and either the original driver is loaded as fallback, or vkEnumeratePhysicalDevices returns 0 devices.
* @param customDriverDir The directory to load a custom GPU driver named according to `customDriverName` from. Only used if ADRENOTOOLS_DRIVER_CUSTOM is set in `featureFlags`.
* IMPORTANT: This path MUST NOT be on sdcard/storage. i.e. use ANativeActivity::internalDataPath and often looks like "/data/user/0/com.example.app/files/".
* Otherwise you will get permission denied because dlopen() can't be done on *.so libraries that any random user or application can manipulate (which would be a security risk).
* @param customDriverName The soname of the custom driver to load. Only used if ADRENOTOOLS_DRIVER_CUSTOM is set in `featureFlags`
* @param fileRedirectDir The directory which to redirect all file accesses performed by the driver to. Only used if ADRENOTOOLS_DRIVER_FILE_REDIRECT is set in `featureFlags`
* @param userMappingHandle A pointer to a void* which will be set to the mapping handle if ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT is set in `featureFlags`
*/
void *adrenotools_open_libvulkan(int dlopenMode, int featureFlags, const char *tmpLibDir, const char *hookLibDir, const char *customDriverDir, const char *customDriverName, const char *fileRedirectDir, void **userMappingHandle);
/**
* @brief Imports the given CPU mapped memory range into the GSL allocator. This should then be followed by a call to vkAllocateMemory with a matching size which will return a VkDeviceMemory view over the input region
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
* @param hostPtr The host pointer to import
* @param size The size of the region to import
* @return True on success
*/
bool adrenotools_import_user_mem(void *handle, void *hostPtr, uint64_t size);
/**
* @brief Maps GPU memory and imports it into the GSL allocator. This should then be followed by a call to adrenotools_mem_cpu_map to map the memory on the CPU, then finally vkAllocateMemory with a matching size
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
* @param size Pointer to a variable containing the size of the region to import, will be updated to contain the required size of the region to allocate CPU side
* @return true on success
*/
bool adrenotools_mem_gpu_allocate(void *handle, uint64_t *size);
/**
* @brief Maps the last mapping allocated using adrenotools_mem_gpu_allocate into the given host memory region, such that vkAllocateMemory can then be called
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
* @param hostPtr A pointer to where the mapping should be mapped
* @param size The size of the mapping. MUST be equal to the size returned by adrenotools_mem_gpu_allocate
*/
bool adrenotools_mem_cpu_map(void *handle, void *hostPtr, uint64_t size);
/**
* @note This function should be called after adrenotools_open_libvulkan and Vulkan driver init to check if the mapping import hook loaded successfully
* @return True if the mapping was successfully imported (or initialization succeeded)
*/
bool adrenotools_validate_gpu_mapping(void *handle);
/**
* @brief Provides a way to force the GPU to run at the maximum possible clocks (thermal constraints will still be applied)
*/
void adrenotools_set_turbo(bool turbo);
#ifdef __cplusplus
@@ -1,13 +1,11 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#include <stdint.h>
/**
* @brief Bitfield enum of additional driver features that can be used with adrenotools_open_libvulkan
*/
enum {
ADRENOTOOLS_DRIVER_CUSTOM = 1 << 0,
ADRENOTOOLS_DRIVER_FILE_REDIRECT = 1 << 1,
@@ -16,12 +14,9 @@ enum {
#define ADRENOTOOLS_GPU_MAPPING_SUCCEEDED_MAGIC 0xDEADBEEF
/**
* @brief A replacement GPU memory mapping for use with ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT
*/
struct adrenotools_gpu_mapping {
void *host_ptr;
uint64_t gpu_addr; //!< The GPU address of the mapping to import, if mapping import/init succeeds this will be set to ADRENOTOOLS_GPU_MAPPING_SUCCEEDED_MAGIC
uint64_t gpu_addr;
uint64_t size;
uint64_t flags;
};
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#include <array>
#include <cstdio>
#include <cstdlib>
@@ -21,7 +22,6 @@ static loader_android_create_namespace_t loader_android_create_namespace;
static bool lib_loaded;
/* Public API */
bool linkernsbypass_load_status() {
return lib_loaded;
}
@@ -55,8 +55,6 @@ android_link_namespaces_all_libs_t android_link_namespaces_all_libs;
android_link_namespaces_t android_link_namespaces;
bool linkernsbypass_link_namespace_to_default_all_libs(android_namespace_t *to) {
// Creating a shared namespace with the default parent will give a copy of the default namespace that we can actually access
// This is needed since there is no way to access a direct handle to the default namespace as it's not exported
static auto defaultNs{android_create_namespace_escape("default_copy", nullptr, nullptr, ANDROID_NAMESPACE_TYPE_SHARED, nullptr, nullptr)};
return android_link_namespaces_all_libs(to, defaultNs);
}
@@ -81,7 +79,6 @@ void *linkernsbypass_namespace_dlopen(const char *filename, int flags, android_n
void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *libTargetDir, int flags, android_namespace_t *ns) {
static std::array<char, PATH_MAX> PathBuf{};
// Used as a unique ID for overwriting soname and creating target lib files
static uint16_t TargetId{};
int libTargetFd{[&] () {
@@ -89,7 +86,6 @@ void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *li
snprintf(PathBuf.data(), PathBuf.size(), "%s/%d_patched.so", libTargetDir, TargetId);
return open(PathBuf.data(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
} else {
// If memfd isn't supported errno will contain ENOSYS after calling
errno = 0;
int fd{static_cast<int>(syscall(__NR_memfd_create, libPath, 0))};
if (errno == ENOSYS || fd < 0)
@@ -101,21 +97,18 @@ void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *li
if (libTargetFd == -1)
return nullptr;
// Partially overwrite soname with 3 digits (replacing lib...) with to make sure a cached so isn't loaded
std::array<char, 3> sonameOverwrite{};
snprintf(sonameOverwrite.data(), sonameOverwrite.size(), "%03u", TargetId++);
if (!elf_soname_patch(libPath, libTargetFd, sonameOverwrite.data()))
return nullptr;
// Load our patched library into the hook namespace
android_dlextinfo hookExtInfo{
.flags = ANDROID_DLEXT_USE_NAMESPACE | ANDROID_DLEXT_USE_LIBRARY_FD,
.library_fd = libTargetFd,
.library_namespace = ns
};
// Make a path that looks about right
snprintf(PathBuf.data(), PathBuf.size(), "/proc/self/fd/%d", libTargetFd);
return android_dlopen_ext(PathBuf.data(), flags, &hookExtInfo);
@@ -125,21 +118,19 @@ static void *align_ptr(void *ptr) {
return reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(ptr) & ~(getpagesize() - 1));
}
/* Private */
__attribute__((constructor)) static void resolve_linker_symbols() {
using loader_dlopen_t = void *(*)(const char *, int, const void *);
if (android_get_device_api_level() < 28)
return;
// ARM64 specific function walking to locate the internal dlopen handler
auto loader_dlopen{[]() {
union BranchLinked {
uint32_t raw;
struct {
int32_t offset : 26; //!< 26-bit branch offset
uint8_t sig : 6; //!< 6-bit signature
int32_t offset : 26;
uint8_t sig : 6;
};
bool Verify() {
@@ -148,10 +139,8 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
};
static_assert(sizeof(BranchLinked) == 4, "BranchLinked is wrong size");
// Some devices ship with --X mapping for exexecutables so work around that
mprotect(align_ptr(reinterpret_cast<void *>(&dlopen)), getpagesize(), PROT_WRITE | PROT_READ | PROT_EXEC);
// dlopen is just a wrapper for __loader_dlopen that passes the return address as the third arg hence we can just walk it to find __loader_dlopen
auto blInstr{reinterpret_cast<BranchLinked *>(&dlopen)};
while (!blInstr->Verify())
blInstr++;
@@ -159,10 +148,8 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
return reinterpret_cast<loader_dlopen_t>(blInstr + blInstr->offset);
}()};
// Protect the loader_dlopen function to remove the BTI attribute (since this is an internal function that isn't intended to be jumped indirectly to)
mprotect(align_ptr(reinterpret_cast<void *>(&loader_dlopen)), getpagesize(), PROT_WRITE | PROT_READ | PROT_EXEC);
// Passing dlopen as a caller address tricks the linker into using the internal unrestricted namespace letting us access libraries that are normally forbidden in the classloader namespace imposed on apps
auto ldHandle{loader_dlopen("ld-android.so", RTLD_LAZY, reinterpret_cast<void *>(&dlopen))};
if (!ldHandle)
return;
@@ -187,6 +174,5 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
if (!android_get_exported_namespace)
return;
// Lib is now safe to use
lib_loaded = true;
}
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#ifdef __cplusplus
@@ -9,7 +10,6 @@ extern "C" {
#include <stdint.h>
// https://cs.android.com/android/platform/superproject/+/0a492a4685377d41fef2b12e9af4ebfa6feef9c2:art/libnativeloader/include/nativeloader/dlext_namespaces.h;l=25;bpv=1;bpt=1
enum {
ANDROID_NAMESPACE_TYPE_REGULAR = 0,
ANDROID_NAMESPACE_TYPE_ISOLATED = 1,
@@ -19,14 +19,8 @@ enum {
ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED = ANDROID_NAMESPACE_TYPE_SHARED | ANDROID_NAMESPACE_TYPE_ISOLATED,
};
/**
* @brief Checks if linkernsbypass loaded successfully and is safe to use
* @note IMPORTANT: This should be called before any calls to the rest of the library are made
* @return true if loading succeeded
*/
bool linkernsbypass_load_status();
// https://cs.android.com/android/platform/superproject/+/0a492a4685377d41fef2b12e9af4ebfa6feef9c2:art/libnativeloader/include/nativeloader/dlext_namespaces.h;l=86;bpv=1;bpt=1
struct android_namespace_t *android_create_namespace(const char *name,
const char *ld_library_path,
const char *default_library_path,
@@ -41,39 +35,19 @@ struct android_namespace_t *android_create_namespace_escape(const char *name,
const char *permitted_when_isolated_path,
struct android_namespace_t *parent_namespace);
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=3554
typedef struct android_namespace_t *(*android_get_exported_namespace_t)(const char *);
extern android_get_exported_namespace_t android_get_exported_namespace;
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=2499
typedef bool (*android_link_namespaces_all_libs_t)(struct android_namespace_t *, struct android_namespace_t *);
extern android_link_namespaces_all_libs_t android_link_namespaces_all_libs;
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=2473
typedef bool (*android_link_namespaces_t)(struct android_namespace_t *, struct android_namespace_t *, const char *);
extern android_link_namespaces_t android_link_namespaces;
/**
* @brief Like android_link_namespaces_all_libs but links from the default namespace
*/
bool linkernsbypass_link_namespace_to_default_all_libs(struct android_namespace_t *to);
/**
* @brief Loads a library into a namespace
* @note IMPORTANT: If `filename` is compiled with the '-z global' linker flag and RTLD_GLOBAL is supplied in `flags` the library will be added to the namespace's LD_PRELOAD list
* @param filename The name of the library to load
* @param flags The rtld flags for `filename`
* @param ns The namespace to dlopen into
*/
void *linkernsbypass_namespace_dlopen(const char *filename, int flags, struct android_namespace_t *ns);
/**
* @brief Force loads a unique instance of a library into a namespace
* @param libPath The path to the library to load with hooks applied
* @param libTargetDir A temporary directory to hold the soname patched library at `libPath`, will attempt to use memfd if nullptr
* @param flags The rtld flags for `libName`
* @param ns The namespace to dlopen into
*/
void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *libTargetDir, int flags, struct android_namespace_t *ns);
#ifdef __cplusplus
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#include <initializer_list>
#include <cstdint>
#include <errno.h>
@@ -20,7 +21,6 @@ bool elf_soname_patch(const char *libPath, int targetFd, const char *sonamePatch
if (ftruncate(targetFd, libStat.st_size) == -1)
return false;
// Map the memory so we can read our elf into it
auto mappedLib{reinterpret_cast<uint8_t *>(mmap(nullptr, libStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, targetFd, 0))};
if (!mappedLib)
return false;
@@ -29,30 +29,25 @@ bool elf_soname_patch(const char *libPath, int targetFd, const char *sonamePatch
if (libFd == false)
return false;
// Read lib elf into target file
if (read(libFd, mappedLib, libStat.st_size) != libStat.st_size)
return false;
// No longer needed
close(libFd);
auto eHdr{reinterpret_cast<ElfW(Ehdr) *>(mappedLib)};
auto sHdrEntries{reinterpret_cast<ElfW(Shdr) *>(mappedLib + eHdr->e_shoff)};
// Iterate over section headers to find the .dynamic section
for (ElfW(Half) i{}; i < eHdr->e_shnum; i++) {
auto &sHdr{sHdrEntries[i]};
if (sHdr.sh_type == SHT_DYNAMIC) {
auto strTab{reinterpret_cast<char *>(mappedLib + sHdrEntries[sHdr.sh_link].sh_offset)};
auto dynHdrEntries{reinterpret_cast<ElfW(Dyn) *>(mappedLib + sHdr.sh_offset)};
// Iterate over .dynamic entries to find DT_SONAME
for (ElfW(Xword) k{}; k < (sHdr.sh_size / sHdr.sh_entsize); k++) {
auto &dynHdrEntry{dynHdrEntries[k]};
if (dynHdrEntry.d_tag == DT_SONAME) {
char *soname{strTab + dynHdrEntry.d_un.d_val};
// Partially replace the old soname with the soname patch
size_t charIdx{};
for (; soname[charIdx] != 0 && sonamePatch[charIdx] != 0; charIdx++)
soname[charIdx] = sonamePatch[charIdx];
@@ -1,19 +1,13 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Overwrites a portion of the soname in an elf by loading it into shared memory and modifying .dynstr
* @note IMPORTANT: The supplied soname patch will overwrite the first strlen(sonamePatch) chars of the soname
* @param elfPath Full path to the elf to patch
* @param targetFd FD to use for storing the patched library
* @return True on success
*/
bool elf_soname_patch(const char *elfPath, int targetFd, const char *newSoname);
#ifdef __cplusplus
@@ -11,8 +11,3 @@ linkernsbypass_lib = static_library (
include_directories: ([linkernsbypass_inc, adrenotools_inc]),
dependencies: ([libandroid_dep, libdl_dep])
)
# linkernsbypass_include = static_library('linkernsbypass', sources)
# target_compile_options(['linkernsbypass', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_libraries(['linkernsbypass', 'android', 'dl'])
# target_include_directories(['linkernsbypass', 'PUBLIC', '.'])
-15
View File
@@ -33,18 +33,3 @@ adrenotools_lib = shared_library(
)
subdir('src/hook')
# target_include_directories(['adrenotools', 'PUBLIC', 'include'])
# target_include_directories(['adrenotools', 'PRIVATE', '.'])
# target_compile_options(['adrenotools', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_libraries(['adrenotools', 'android', 'linkernsbypass'])
# hooks_deb = subproject('src/hook')
# if build_shared_libs
# target_link_options(['adrenotools', 'PRIVATE', '-Wl,-s,--exclude-libs,liblinkernsbypass.a'])
# endif
# if gen_install_target
# configure_file(['adrenotools.pc.in', 'adrenotools.pc', '@ONLY'])
# install(['FILES', cmake_binary_dir, '/adrenotools.pc', 'DESTINATION', 'lib/pkgconfig'])
# install(['TARGETS', 'adrenotools', 'main_hook', 'file_redirect_hook', 'gsl_alloc_hook', 'hook_impl'])
# install(['DIRECTORY', 'include/', 'DESTINATION', 'include', 'FILES_MATCHING', 'PATTERN', '*.h*'])
# endif
+3 -18
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#include <fstream>
#include <string>
#include <cstring>
@@ -19,7 +20,6 @@ enum adrenotools_bcn_type adrenotools_get_bcn_type(uint32_t major, uint32_t mino
return ADRENOTOOLS_BCN_PATCH;
}
// Searches /proc/self/maps for the first free page after the given address
static void *find_free_page(uintptr_t address) {
std::ifstream procMaps("/proc/self/maps");
@@ -45,62 +45,48 @@ static void *align_ptr(void *ptr) {
bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn) {
union Branch {
struct {
int32_t offset : 26; //!< 26-bit branch offset
uint8_t sig : 6; //!< 6-bit signature (0x25 for linked, 0x5 for jump)
int32_t offset : 26;
uint8_t sig : 6;
};
uint32_t raw{};
};
static_assert(sizeof(Branch) == 4, "Branch size is invalid");
// Find the nearest unmapped page where we can place patch code
void *patchPage{find_free_page(reinterpret_cast<uintptr_t>(vkGetPhysicalDeviceFormatPropertiesFn))};
if (!patchPage)
return false;
// Map patch region
void *ptr{mmap(patchPage, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0)};
if (ptr != patchPage)
return false;
// Allow reading from the blob's .text section since some devices enable ---X
// Protect two pages just in case we happen to land on a page boundary
if (mprotect(align_ptr(vkGetPhysicalDeviceFormatPropertiesFn), getpagesize() * 2, PROT_WRITE | PROT_READ | PROT_EXEC))
return false;
// First branch in this function is targeted at the function we want to patch
Branch *blInst{reinterpret_cast<Branch *>(vkGetPhysicalDeviceFormatPropertiesFn)};
constexpr uint8_t BranchLinkSignature{0x25};
// Search for first instruction with the BL signature
while (blInst->sig != BranchLinkSignature)
blInst++;
// Internal QGL format conversion function that we need to patch
uint32_t *convFormatFn{reinterpret_cast<uint32_t *>(blInst) + blInst->offset};
// See mprotect call above
// This time we also set PROT_WRITE so we can write our patch to the page
if (mprotect(align_ptr(convFormatFn), getpagesize() * 2, PROT_WRITE | PROT_READ | PROT_EXEC))
return false;
// This would normally set the default result to 0 (error) in the format not found case
constexpr uint32_t ClearResultSignature{0x2a1f03e0};
// We replace it with a branch to our own extended if statement which adds in the extra things for BCn
uint32_t *clearResultPtr{convFormatFn};
while (*clearResultPtr != ClearResultSignature)
clearResultPtr++;
// Ensure we don't write out of bounds
if (PatchRawData_size > getpagesize())
return false;
// Copy the patch function to our mapped page
memcpy(patchPage, PatchRawData, PatchRawData_size);
// Fixup the patch code so it correctly returns back to the driver after running
constexpr uint32_t PatchReturnFixupMagic{0xffffffff};
constexpr uint8_t BranchSignature{0x5};
@@ -130,6 +116,5 @@ bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn) {
asm volatile("ISB");
// Done!
return true;
}
@@ -80,4 +80,4 @@
.L18:
mov w0, 0
.L3:
.word 0xffffffff // Branch fixup
.word 0xffffffff
+3 -13
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#include <string>
#include <string_view>
#include <sys/stat.h>
@@ -17,17 +18,14 @@
#include <unistd.h>
void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *tmpLibDir, const char *hookLibDir, const char *customDriverDir, const char *customDriverName, const char *fileRedirectDir, void **userMappingHandle) {
// Bail out if linkernsbypass failed to load, this probably means we're on api < 28
if (!linkernsbypass_load_status()) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Could not load linkernsbypass\n");
return nullptr;
}
// Use memfd on Q+ since it's guaranteed to work, but only if tmpLibDir wasn't set
if (android_get_device_api_level() >= 29 && !tmpLibDir)
tmpLibDir = nullptr;
// Verify that params for specific features are only passed if they are enabled
if (!(featureFlags & ADRENOTOOLS_DRIVER_FILE_REDIRECT) && fileRedirectDir) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "ADRENOTOOLS_DRIVER_FILE_REDIRECT present but no file redirect folder found\n");
return nullptr;
@@ -43,7 +41,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
return nullptr;
}
// Verify that params for enabled features are correct
struct stat buf{};
if (featureFlags & ADRENOTOOLS_DRIVER_CUSTOM) {
@@ -58,7 +55,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
}
}
// Verify that params for enabled features are correct
if (featureFlags & ADRENOTOOLS_DRIVER_FILE_REDIRECT) {
if (!fileRedirectDir) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "ADRENOTOOLS_DRIVER_REDIRECT_DIR present but no folder parameter was found\n");
@@ -71,22 +67,18 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
}
}
// Create a namespace that can isolate our hook from the classloader namespace
auto hookNs{android_create_namespace("adrenotools-libvulkan", hookLibDir, nullptr, ANDROID_NAMESPACE_TYPE_SHARED, nullptr, nullptr)};
// Link it to the default namespace so the hook can use libandroid etc
if (!linkernsbypass_link_namespace_to_default_all_libs(hookNs)) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Failed to hook our namespace to the default one\n");
return nullptr;
}
// Preload the hook implementation, otherwise we get a weird issue where despite being in NEEDED of the hook lib the hook's symbols will overwrite ours and cause an infinite loop
auto hookImpl{linkernsbypass_namespace_dlopen("libhook_impl.so", RTLD_NOW, hookNs)};
if (!hookImpl) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Couldn't preload the hook implementation\n");
return nullptr;
}
// Pass parameters to the hook implementation
auto initHookParam{reinterpret_cast<void (*)(const void *)>(dlsym(hookImpl, "init_hook_param"))};
if (!initHookParam) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Couldn't init hook params\n");
@@ -95,7 +87,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
auto importMapping{[&]() -> adrenotools_gpu_mapping * {
if (featureFlags & ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT) {
// This will be leaked, but it's not a big deal since it's only a few bytes
adrenotools_gpu_mapping *mapping{new adrenotools_gpu_mapping{}};
*userMappingHandle = mapping;
return mapping;
@@ -107,7 +98,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
initHookParam(new HookImplParams(featureFlags, tmpLibDir, hookLibDir, customDriverDir, customDriverName, fileRedirectDir, importMapping));
// Load the libvulkan hook into the isolated namespace
if (!linkernsbypass_namespace_dlopen("libmain_hook.so", RTLD_GLOBAL, hookNs)) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Failed to load libvulkan into the isolated namespace\n");
return nullptr;
@@ -150,7 +140,7 @@ bool adrenotools_import_user_mem(void *handle, void *hostPtr, uint64_t size) {
importMapping->host_ptr = hostPtr;
importMapping->gpu_addr = info.gpuaddr;
importMapping->size = size;
importMapping->flags = 0xc2600; //!< Unknown flags, but they are required for the mapping to work
importMapping->flags = 0xc2600;
close(kgslFd);
return true;
@@ -189,7 +179,7 @@ bool adrenotools_mem_gpu_allocate(void *handle, uint64_t *size) {
mapping->host_ptr = nullptr;
mapping->gpu_addr = info.gpuaddr;
mapping->size = *size;
mapping->flags = 0xc2600; //!< Unknown flags, but they are required for the mapping to work
mapping->flags = 0xc2600;
close(kgslFd);
return true;
@@ -15,7 +15,7 @@
#define TAG "hook_impl"
#define LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##__VA_ARGS__)
const HookImplParams *hook_params; //!< Bunch of info needed to load/patch the driver
const HookImplParams *hook_params;
int (*gsl_memory_alloc_pure_sym)(uint32_t, uint32_t, void *);
int (*gsl_memory_alloc_pure_64_sym)(uint64_t, uint32_t, void *);
int (*gsl_memory_free_pure_sym)(void *);
@@ -43,7 +43,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
LOGI("hook_android_dlopen_ext: filename: %s", filename);
// Ignore non-vulkan libraries
if (!strstr(filename, "vulkan."))
return android_dlopen_ext(filename, flags, extinfo);
@@ -52,7 +51,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
return fallback();
}
// customDriverDir will be empty if ADRENOTOOLS_DRIVER_CUSTOM isn't set therefore it's fine to have either way
auto driverNs{android_create_namespace(filename, hook_params->customDriverDir.c_str(),
hook_params->hookLibDir.c_str(), ANDROID_NAMESPACE_TYPE_SHARED,
nullptr, extinfo->library_namespace)};
@@ -61,16 +59,12 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
return fallback();
}
// We depend on libandroid which is unlikely to be in the supplied driver namespace so we have to link it over
android_link_namespaces(driverNs, nullptr, "libandroid.so");
// Preload ourself, a new instance will be created since we have different linker ancestory
// If we don't preload we get a weird issue where despite being in NEEDED of the hook lib the hook's symbols will overwrite ours and cause an infinite loop
auto hookImpl{linkernsbypass_namespace_dlopen("libhook_impl.so", RTLD_NOW, driverNs)};
if (!hookImpl)
return nullptr;
// Pass parameters to ourself
auto initHookParam{reinterpret_cast<void (*)(const void *)>(dlsym(hookImpl, "init_hook_param"))};
if (!initHookParam)
return nullptr;
@@ -86,7 +80,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
LOGI("hook_android_dlopen_ext: applied libfile_redirect_hook");
}
// Use our new namespace to load the vulkan driver
auto newExtinfo{*extinfo};
newExtinfo.library_namespace = driverNs;
@@ -122,8 +115,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
LOGI("hook_android_dlopen_ext: hook failed: failed to apply libgsl_alloc_hook!");
}
// TODO: If there is already an instance of a Vulkan driver loaded hooks won't be applied, this will only be the case for skiavk generally
// To fix this we would need to search /proc/self/maps for the file to a loaded instance of the library in order to read it to patch the soname and load it uniquely
if (hook_params->featureFlags & ADRENOTOOLS_DRIVER_CUSTOM) {
LOGI("hook_android_dlopen_ext: loading custom driver: %s%s", hook_params->customDriverDir.c_str(), hook_params->customDriverName.c_str());
void *handle{android_dlopen_ext(hook_params->customDriverName.c_str(), flags, &newExtinfo)};
@@ -142,7 +133,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
__attribute__((visibility("default"))) void *hook_android_load_sphal_library(const char *filename, int flags) {
LOGI("hook_android_load_sphal_library: filename: %s", filename);
// https://android.googlesource.com/platform/system/core/+/master/libvndksupport/linker.cpp
for (const char *name : {"sphal", "vendor", "default"}) {
if (auto vendorNs{android_get_exported_namespace(name)}) {
android_dlextinfo dlextinfo{
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#ifdef __cplusplus
@@ -1,15 +1,12 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#include <string>
#include <adrenotools/priv.h>
/**
* @brief Holds the parameters needed for all hooks
* @note See comments for adrenotools_open_libvulkan as a reference for member variables
*/
struct HookImplParams {
int featureFlags;
std::string tmpLibDir;
File diff suppressed because it is too large. Load diff
@@ -1,32 +1,12 @@
hook_impl_files = files(['hook_impl.cpp', 'hook_impl.h', 'hook_impl_params.h'])
hook_impl_inc = include_directories('../../include')
# hook_impl_lib = shared_library('hook_impl', 'hook_impl.cpp', 'hook_impl.h', 'hook_impl_params.h')
# target_compile_options(['hook_impl', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_libraries(['hook_impl', 'linkernsbypass', 'log'])
# target_include_directories(['hook_impl', 'PRIVATE', '../../include'])
# set_target_properties(['hook_impl', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
main_hook_files = files(['main_hook.c'])
# main_hook_lib = shared_library('main_hook', 'main_hook.c')
# target_compile_options(['main_hook', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_options(['main_hook', 'PRIVATE', '-z', 'global'])
# target_link_libraries(['main_hook', 'hook_impl'])
# set_target_properties(['main_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
file_redirect_hook_files = files(['file_redirect_hook.c'])
# file_redirect_hook_lib = shared_library('file_redirect_hook', 'file_redirect_hook.c')
# target_compile_options(['file_redirect_hook', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_options(['file_redirect_hook', 'PRIVATE', '-z', 'global'])
# target_link_libraries(['file_redirect_hook', 'hook_impl'])
# set_target_properties(['file_redirect_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
gsl_alloc_hook_files = files(['gsl_alloc_hook.c'])
# gsl_alloc_hook_lib = shared_library('gsl_alloc_hook', 'gsl_alloc_hook.c')
# target_compile_options(['gsl_alloc_hook', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_options(['gsl_alloc_hook', 'PRIVATE', '-z', 'global'])
# target_link_libraries(['gsl_alloc_hook', 'hook_impl'])
# set_target_properties(['gsl_alloc_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
hook_impl_lib = shared_library(
'hook_impl',
@@ -5,7 +5,6 @@
thread_local int satisfy_driver_emutls;
// Check libllvm-glnext.so in older drivers for a full list of these, newer ones are slightly more awkward due to the split
enum class OptionType : uint32_t {
ArgsStringList = 1,
QcArgsString = 4,
@@ -21,7 +20,6 @@ struct StringListVal {
struct Option
{
OptionType type;
// PAD
union {
bool boolVal;
int intVal;
@@ -49,7 +47,6 @@ int CompileHook(void *state, OptionsSet *opts) {
Option replacementOpts[ReplacementOptsNum];
uint32_t patchedOptNum{};
// Insert your desired args here - results will be written to stderr
const char *argv[] = {"-help", "-help-hidden", "", NULL };
StringListVal listVal{
.len = 3,
@@ -83,7 +80,6 @@ int CompileHook(void *state, OptionsSet *opts) {
struct LlvmInterface {
uint32_t unk0[3];
// PAD
void *makeContext;
void *delContext;
decltype(CompileHook) *compile;
@@ -30,53 +30,31 @@
#ifndef __QTIGRALLOCDEFS_H__
#define __QTIGRALLOCDEFS_H__
// From gralloc_priv.h
/* Gralloc usage bits indicating the type of allocation that should be used */
/* Refer to BufferUsage in hardware/interfaces/graphics/common/<ver>/types.hal */
/* The bits below are in officially defined vendor space
* i.e bits 28-31 and 48-63*/
/* Non linear, Universal Bandwidth Compression */
#define GRALLOC_USAGE_PRIVATE_ALLOC_UBWC (UINT32_C(1) << 28)
/* Set this for allocating uncached memory (using O_DSYNC),
* cannot be used with noncontiguous heaps */
#define GRALLOC_USAGE_PRIVATE_UNCACHED (UINT32_C(1) << 29)
/* This flag is used to indicate 10 bit format.
* When both GRALLOC_USAGE_PRIVATE_ALLOC_UBWC & GRALLOC_USAGE_PRIVATE_10BIT
* are set then it will indicate UBWC_TP10 format.
* When only GRALLOC_USAGE_PRIVATE_10BIT is set it will indicate linear P010 format.
*/
#define GRALLOC_USAGE_PRIVATE_10BIT (UINT32_C(1) << 30)
/* This flag is used for SECURE display usecase */
#define GRALLOC_USAGE_PRIVATE_SECURE_DISPLAY (UINT32_C(1) << 31)
/* This flag is used to indicate video NV21 format */
#define GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER 1ULL << 48
/* unused legacy flags */
#define GRALLOC_USAGE_PRIVATE_MM_HEAP 0
#define GRALLOC_USAGE_PRIVATE_IOMMU_HEAP 0
/* This flag is set for WFD usecase */
#define GRALLOC_USAGE_PRIVATE_WFD 1ULL << 51
/* This flag is set for HEIF usecase */
#define GRALLOC_USAGE_PRIVATE_HEIF (UINT32_C(1) << 27)
/* TODO(user): Remove when clients stop referencing this flag */
#define GRALLOC_USAGE_PRIVATE_10BIT_TP 0
/* This flag indicates PI format is being used */
#define GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI 1ULL << 49
/* This flag is set while CDSP accesses the buffer */
#define GRALLOC_USAGE_PRIVATE_CDSP 1ULL << 50
// OEM specific HAL formats
#define HAL_PIXEL_FORMAT_RGBA_5551 6
#define HAL_PIXEL_FORMAT_RGBA_4444 7
#define HAL_PIXEL_FORMAT_NV12_ENCODEABLE 0x102
@@ -98,7 +76,6 @@
#define HAL_PIXEL_FORMAT_RAW8 0x123
#define HAL_PIXEL_FORMAT_NV12_HEIF 0x116
// 10 bit
#define HAL_PIXEL_FORMAT_ARGB_2101010 0x117
#define HAL_PIXEL_FORMAT_RGBX_1010102 0x118
#define HAL_PIXEL_FORMAT_XRGB_2101010 0x119
@@ -106,7 +83,7 @@
#define HAL_PIXEL_FORMAT_ABGR_2101010 0x11B
#define HAL_PIXEL_FORMAT_BGRX_1010102 0x11C
#define HAL_PIXEL_FORMAT_XBGR_2101010 0x11D
#define HAL_PIXEL_FORMAT_YCbCr_420_P010 0x36 // HAL_PIXEL_FORMAT_YCBCR_P010
#define HAL_PIXEL_FORMAT_YCbCr_420_P010 0x36
#define HAL_PIXEL_FORMAT_YCbCr_420_P010_UBWC 0x124
#define HAL_PIXEL_FORMAT_YCbCr_420_P010_VENUS 0x7FA30C0A
@@ -115,7 +92,6 @@
#define HAL_PIXEL_FORMAT_INTERLACE 0x180
// Camera utils format
#define HAL_PIXEL_FORMAT_NV12_LINEAR_FLEX 0x125
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX 0x126
#define HAL_PIXEL_FORMAT_MULTIPLANAR_FLEX 0x127
@@ -123,22 +99,16 @@
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX_4_BATCH 0x129
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX_8_BATCH 0x130
// FSC Formats
#define HAL_PIXEL_FORMAT_RGB888_UBWC_FSC 0x131
#define HAL_PIXEL_FORMAT_RGB101010_UBWC_FSC 0x132
// v4l2_fourcc('Y', 'U', 'Y', 'L'). 24 bpp YUYV 4:2:2 10 bit per component
#define HAL_PIXEL_FORMAT_YCbCr_422_I_10BIT 0x4C595559
// v4l2_fourcc('Y', 'B', 'W', 'C'). 10 bit per component. This compressed
// format reduces the memory access bandwidth
#define HAL_PIXEL_FORMAT_YCbCr_422_I_10BIT_COMPRESSED 0x43574259
// UBWC aligned Venus format
#define HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC 0x7FA30C06
#define HAL_PIXEL_FORMAT_YCbCr_420_TP10_UBWC 0x7FA30C09
// Khronos ASTC formats
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2
@@ -170,4 +140,4 @@
enum { BUFFER_TYPE_UI = 0, BUFFER_TYPE_VIDEO };
#endif //__QTIGRALLOCDEFS_H__
#endif
@@ -26,7 +26,6 @@
using namespace android::hardware;
using namespace android;
/* Simplified version of hidl::base::V1_0::IBase */
struct IBase : virtual public RefBase {
virtual bool isRemote() const;
@@ -80,8 +80,6 @@ AdrenoMemInfo::AdrenoMemInfo() {
ALOGE(" Failed to load libadreno_utils.so");
}
// Check if the overriding property debug.gralloc.gfx_ubwc_disable
// that disables UBWC allocations for the graphics stack is set
char property[PROPERTY_VALUE_MAX];
property_get(DISABLE_UBWC_PROP, property, "0");
if (!(strncmp(property, "1", PROPERTY_VALUE_MAX)) ||
@@ -123,16 +121,13 @@ void AdrenoMemInfo::AlignUnCompressedRGB(int width, int height, int format, int
break;
}
int raster_mode = 0; // Adreno unknown raster mode.
int padding_threshold = 512; // Threshold for padding surfaces.
// the function below computes aligned width and aligned height
// based on linear or macro tile mode selected.
int raster_mode = 0;
int padding_threshold = 512;
if (LINK_adreno_compute_fmt_aligned_width_and_height) {
// We call into adreno_utils only for RGB formats. So plane_id is 0 and
// num_samples is 1 always. We may have to add uitility function to
// find out these if there is a need to call this API for YUV formats.
LINK_adreno_compute_fmt_aligned_width_and_height(
width, height, 0 /*plane_id*/, GetGpuPixelFormat(format), 1 /*num_samples*/, tile_enabled,
width, height, 0 , GetGpuPixelFormat(format), 1 , tile_enabled,
raster_mode, padding_threshold, reinterpret_cast<int*>(aligned_w),
reinterpret_cast<int*>(aligned_h));
} else if (LINK_adreno_compute_aligned_width_and_height) {
@@ -140,7 +135,7 @@ void AdrenoMemInfo::AlignUnCompressedRGB(int width, int height, int format, int
width, height, bpp, tile_enabled, raster_mode, padding_threshold,
reinterpret_cast<int*>(aligned_w), reinterpret_cast<int*>(aligned_h));
} else if (LINK_adreno_compute_padding) {
int surface_tile_height = 1; // Linear surface
int surface_tile_height = 1;
*aligned_w = UINT(LINK_adreno_compute_padding(width, bpp, surface_tile_height, raster_mode,
padding_threshold));
ALOGW("%s: Warning!! Old GFX API is used to calculate stride", __FUNCTION__);
@@ -157,9 +152,9 @@ void AdrenoMemInfo::AlignCompressedRGB(int width, int height, int format, unsign
unsigned int* aligned_h) {
if (LINK_adreno_compute_compressedfmt_aligned_width_and_height) {
int bytesPerPixel = 0;
int raster_mode = 0; // Adreno unknown raster mode.
int padding_threshold = 512; // Threshold for padding
// surfaces.
int raster_mode = 0;
int padding_threshold = 512;
LINK_adreno_compute_compressedfmt_aligned_width_and_height(
width, height, format, 0, raster_mode, padding_threshold, reinterpret_cast<int*>(aligned_w),
@@ -341,4 +336,4 @@ bool AdrenoMemInfo::IsPISupportedByGPU(int format, uint64_t usage) {
return false;
}
} // namespace gralloc
}
@@ -35,11 +35,10 @@
namespace gralloc {
// Adreno Pixel Formats
typedef enum {
ADRENO_PIXELFORMAT_UNKNOWN = 0,
ADRENO_PIXELFORMAT_R16G16B16A16_FLOAT = 10,
ADRENO_PIXELFORMAT_R10G10B10A2_UNORM = 24, // Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
ADRENO_PIXELFORMAT_R10G10B10A2_UNORM = 24,
ADRENO_PIXELFORMAT_R8G8B8A8 = 28,
ADRENO_PIXELFORMAT_R8G8B8A8_SRGB = 29,
ADRENO_PIXELFORMAT_D32_FLOAT = 40,
@@ -55,140 +54,87 @@ typedef enum {
ADRENO_PIXELFORMAT_P010 = 104,
ADRENO_PIXELFORMAT_YUY2 = 107,
ADRENO_PIXELFORMAT_B4G4R4A4 = 115,
ADRENO_PIXELFORMAT_NV12_EXT = 506, // NV12 with non-std alignment and offsets
ADRENO_PIXELFORMAT_R8G8B8X8 = 507, // GL_RGB8 (Internal)
ADRENO_PIXELFORMAT_R8G8B8 = 508, // GL_RGB8
ADRENO_PIXELFORMAT_A1B5G5R5 = 519, // GL_RGB5_A1
ADRENO_PIXELFORMAT_R8G8B8X8_SRGB = 520, // GL_SRGB8
ADRENO_PIXELFORMAT_R8G8B8_SRGB = 521, // GL_SRGB8
ADRENO_PIXELFORMAT_NV12_EXT = 506,
ADRENO_PIXELFORMAT_R8G8B8X8 = 507,
ADRENO_PIXELFORMAT_R8G8B8 = 508,
ADRENO_PIXELFORMAT_A1B5G5R5 = 519,
ADRENO_PIXELFORMAT_R8G8B8X8_SRGB = 520,
ADRENO_PIXELFORMAT_R8G8B8_SRGB = 521,
ADRENO_PIXELFORMAT_A2B10G10R10_UNORM = 532,
// Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
ADRENO_PIXELFORMAT_R10G10B10X2_UNORM = 537,
ADRENO_PIXELFORMAT_D24_UNORM_X8_UINT = 548,
ADRENO_PIXELFORMAT_D24_UNORM = 549,
ADRENO_PIXELFORMAT_D32_FLOAT_X24S8_UINT = 551,
ADRENO_PIXELFORMAT_S8_UINT = 552,
ADRENO_PIXELFORMAT_ASTC_4X4 = 568, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_5X4 = 569, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_5X5 = 570, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_6X5 = 571, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_6X6 = 572, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X5 = 573, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X6 = 574, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X8 = 575, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X5 = 576, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X6 = 577, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X8 = 578, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X10 = 579, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_12X10 = 580, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_12X12 = 581, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_4X4_SRGB = 582, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_5X4_SRGB = 583, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_5X5_SRGB = 584, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_6X5_SRGB = 585, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_6X6_SRGB = 586, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X5_SRGB = 587, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X6_SRGB = 588, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X8_SRGB = 589, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X5_SRGB = 590, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X6_SRGB = 591, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X8_SRGB = 592, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X10_SRGB = 593, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_12X10_SRGB = 594, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_12X12_SRGB = 595, // ASTC Compressed
// Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
ADRENO_PIXELFORMAT_R5G6B5 = 610, // RGBA version of B5G6R5
ADRENO_PIXELFORMAT_R5G5B5A1 = 611, // RGBA version of B5G5R5A1
ADRENO_PIXELFORMAT_R4G4B4A4 = 612, // RGBA version of B4G4R4A4
ADRENO_PIXELFORMAT_UYVY = 614, // YUV 4:2:2 packed progressive (1 plane)
ADRENO_PIXELFORMAT_ASTC_4X4 = 568,
ADRENO_PIXELFORMAT_ASTC_5X4 = 569,
ADRENO_PIXELFORMAT_ASTC_5X5 = 570,
ADRENO_PIXELFORMAT_ASTC_6X5 = 571,
ADRENO_PIXELFORMAT_ASTC_6X6 = 572,
ADRENO_PIXELFORMAT_ASTC_8X5 = 573,
ADRENO_PIXELFORMAT_ASTC_8X6 = 574,
ADRENO_PIXELFORMAT_ASTC_8X8 = 575,
ADRENO_PIXELFORMAT_ASTC_10X5 = 576,
ADRENO_PIXELFORMAT_ASTC_10X6 = 577,
ADRENO_PIXELFORMAT_ASTC_10X8 = 578,
ADRENO_PIXELFORMAT_ASTC_10X10 = 579,
ADRENO_PIXELFORMAT_ASTC_12X10 = 580,
ADRENO_PIXELFORMAT_ASTC_12X12 = 581,
ADRENO_PIXELFORMAT_ASTC_4X4_SRGB = 582,
ADRENO_PIXELFORMAT_ASTC_5X4_SRGB = 583,
ADRENO_PIXELFORMAT_ASTC_5X5_SRGB = 584,
ADRENO_PIXELFORMAT_ASTC_6X5_SRGB = 585,
ADRENO_PIXELFORMAT_ASTC_6X6_SRGB = 586,
ADRENO_PIXELFORMAT_ASTC_8X5_SRGB = 587,
ADRENO_PIXELFORMAT_ASTC_8X6_SRGB = 588,
ADRENO_PIXELFORMAT_ASTC_8X8_SRGB = 589,
ADRENO_PIXELFORMAT_ASTC_10X5_SRGB = 590,
ADRENO_PIXELFORMAT_ASTC_10X6_SRGB = 591,
ADRENO_PIXELFORMAT_ASTC_10X8_SRGB = 592,
ADRENO_PIXELFORMAT_ASTC_10X10_SRGB = 593,
ADRENO_PIXELFORMAT_ASTC_12X10_SRGB = 594,
ADRENO_PIXELFORMAT_ASTC_12X12_SRGB = 595,
ADRENO_PIXELFORMAT_R5G6B5 = 610,
ADRENO_PIXELFORMAT_R5G5B5A1 = 611,
ADRENO_PIXELFORMAT_R4G4B4A4 = 612,
ADRENO_PIXELFORMAT_UYVY = 614,
ADRENO_PIXELFORMAT_NV21 = 619,
ADRENO_PIXELFORMAT_Y8U8V8A8 = 620, // YUV 4:4:4 packed (1 plane)
ADRENO_PIXELFORMAT_Y8 = 625, // Single 8-bit luma only channel YUV format
ADRENO_PIXELFORMAT_TP10 = 654, // YUV 4:2:0 planar 10 bits/comp (2 planes)
ADRENO_PIXELFORMAT_Y8U8V8A8 = 620,
ADRENO_PIXELFORMAT_Y8 = 625,
ADRENO_PIXELFORMAT_TP10 = 654,
} ADRENOPIXELFORMAT;
typedef enum {
SURFACE_TILE_MODE_DISABLE = 0x0, // used for linear surface
SURFACE_TILE_MODE_ENABLE = 0x1 // used for tiled surface
SURFACE_TILE_MODE_DISABLE = 0x0,
SURFACE_TILE_MODE_ENABLE = 0x1
} surface_tile_mode_t;
class AdrenoMemInfo {
public:
/*
* Function to compute aligned width and aligned height based on
* width, height, format and usage flags.
*
* @return aligned width, aligned height
*/
void GetAlignedWidthAndHeight(int width, int height, int format, int usage,
unsigned int* aligned_w, unsigned int* aligned_h, bool ubwc_enabled,
bool tile_enabled);
/*
* Function to compute the adreno aligned width and aligned height
* based on the width and format.
*
* @return aligned width, aligned height
*/
void AlignUnCompressedRGB(int width, int height, int format, int tileEnabled,
unsigned int* aligned_w, unsigned int* aligned_h);
/*
* Function to compute the adreno aligned width and aligned height
* based on the width and format.
*
* @return aligned width, aligned height
*/
void AlignCompressedRGB(int width, int height, int format, unsigned int* aligned_w,
unsigned int* aligned_h);
/*
* Function to compute the pixel alignment requirement.
*
* @return alignment
*/
uint32_t GetGpuPixelAlignment();
/*
* Function to query whether GPU supports UBWC for given HAL format
* @return > 0 : supported
* 0 : not supported
*/
bool IsUBWCSupportedByGPU(int format);
/*
* Function to check if GPU supports PI or not
*/
bool IsPISupportedByGPU(int format, uint64_t usage);
/*
* Function to get the corresponding Adreno format for given HAL format
*/
ADRENOPIXELFORMAT GetGpuPixelFormat(int hal_format);
/*
* Function to get graphics metadata blob size
* @return graphics metadata size
*/
uint32_t AdrenoGetMetadataBlobSize();
/*
* Function to populate the graphics metadata blob
* @return 1 : Successful
* 2 : Unsuccessful
*/
int AdrenoInitMemoryLayout(void* metadata_blob, int width, int height, int depth, int format,
int num_samples, int isUBWC, uint64_t usage, uint32_t num_planes);
/*
* Function to get buffer size for based on graphcis metadata
* @return buffer size
*/
uint32_t AdrenoGetAlignedGpuBufferSize(void* metadata_blob);
/*
* Function to check if adreno size calculation APIs are avaliable
* @return true : Avaliable
* false : Unavaliable
*/
bool AdrenoSizeAPIAvaliable();
static AdrenoMemInfo* GetInstance();
@@ -196,7 +142,6 @@ class AdrenoMemInfo {
private:
AdrenoMemInfo();
~AdrenoMemInfo();
// link(s)to adreno surface padding library.
int (*LINK_adreno_compute_padding)(int width, int bpp, int surface_tile_height,
int screen_tile_height, int padding_threshold) = NULL;
void (*LINK_adreno_compute_aligned_width_and_height)(int width, int height, int bpp,
@@ -229,6 +174,6 @@ class AdrenoMemInfo {
static AdrenoMemInfo* s_instance;
};
} // namespace gralloc
}
#endif // __GR_ADRENO_INFO_H__
#endif
@@ -57,29 +57,27 @@ struct private_handle_t {
PRIV_FLAGS_CAMERA_READ = 0x00040000,
PRIV_FLAGS_HW_COMPOSER = 0x00080000,
PRIV_FLAGS_HW_TEXTURE = 0x00100000,
PRIV_FLAGS_ITU_R_601 = 0x00200000, // Unused from display
PRIV_FLAGS_ITU_R_601_FR = 0x00400000, // Unused from display
PRIV_FLAGS_ITU_R_709 = 0x00800000, // Unused from display
PRIV_FLAGS_ITU_R_601 = 0x00200000,
PRIV_FLAGS_ITU_R_601_FR = 0x00400000,
PRIV_FLAGS_ITU_R_709 = 0x00800000,
PRIV_FLAGS_SECURE_DISPLAY = 0x01000000,
PRIV_FLAGS_TILE_RENDERED = 0x02000000,
PRIV_FLAGS_CPU_RENDERED = 0x04000000,
PRIV_FLAGS_UBWC_ALIGNED = 0x08000000,
PRIV_FLAGS_DISP_CONSUMER = 0x10000000,
PRIV_FLAGS_CLIENT_ALLOCATED = 0x20000000, // Ion buffer allocated outside of gralloc
PRIV_FLAGS_UBWC_ALIGNED_PI = 0x40000000, // PI format
PRIV_FLAGS_CLIENT_ALLOCATED = 0x20000000,
PRIV_FLAGS_UBWC_ALIGNED_PI = 0x40000000,
};
// file-descriptors dup'd over IPC
int fd;
int fd_metadata;
// values sent over IPC
int magic;
int flags;
int width; // holds width of the actual buffer allocated
int height; // holds height of the actual buffer allocated
int unaligned_width; // holds width client asked to allocate
int unaligned_height; // holds height client asked to allocate
int width;
int height;
int unaligned_width;
int unaligned_height;
int format;
int buffer_type;
unsigned int layer_count;
@@ -126,7 +124,6 @@ struct private_handle_t {
numFds = kNumFds;
}
// Legacy constructor used by some clients
private_handle_t(int fd, unsigned int size, int usage, int buf_type, int format, int w, int h)
: private_handle_t(fd, -1, PRIV_FLAGS_CLIENT_ALLOCATED, w, h, 0, 0, format, buf_type, size,
static_cast<uint64_t>(usage)) {}
@@ -176,7 +173,6 @@ struct private_handle_t {
unsigned int GetLayerCount() const { return layer_count; }
int GetStride() const {
// In handle we currently store aligned width after allocation.
return width;
}
@@ -187,4 +183,4 @@ struct private_handle_t {
};
#pragma pack(pop)
#endif // __GR_PRIV_HANDLE_H__
#endif
@@ -162,7 +162,6 @@ bool CpuCanRead(uint64_t usage) {
bool CpuCanWrite(uint64_t usage) {
if (usage & BufferUsage::CPU_WRITE_MASK) {
// Application intends to use CPU for rendering
return true;
}
@@ -177,7 +176,6 @@ uint32_t GetDataAlignment(int format, uint64_t usage) {
if (usage & BufferUsage::PROTECTED) {
if ((usage & BufferUsage::CAMERA_OUTPUT) || (usage & GRALLOC_USAGE_PRIVATE_SECURE_DISPLAY)) {
// The alignment here reflects qsee mmu V7L/V8L requirement
align = SZ_2M;
} else {
align = SECURE_ALIGN;
@@ -202,7 +200,6 @@ bool IsGPUFlagSupported(uint64_t usage) {
return ret;
}
// Returns the final buffer size meant to be allocated with ion
unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int alignedh) {
unsigned int size = 0;
int format = info.format;
@@ -223,7 +220,6 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
} else if (IsCompressedRGBFormat(format)) {
size = alignedw * alignedh * ASTC_BLOCK_SIZE;
} else {
// Below switch should be for only YUV/custom formats
switch (format) {
case HAL_PIXEL_FORMAT_RAW16:
case HAL_PIXEL_FORMAT_Y16:
@@ -237,15 +233,15 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
case HAL_PIXEL_FORMAT_Y8:
size = alignedw * alignedh * 1;
break;
// adreno formats
case HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO: // NV21
case HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO:
size = ALIGN(alignedw * alignedh, SIZE_4K);
size += (unsigned int)ALIGN(2 * ALIGN(width / 2, 32) * ALIGN(height / 2, 32), SIZE_4K);
break;
case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED: // NV12
// The chroma plane is subsampled,
// but the pitch in bytes is unchanged
// The GPU needs 4K alignment, but the video decoder needs 8K
case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED:
size = ALIGN(alignedw * alignedh, SIZE_8K);
size += ALIGN(alignedw * (unsigned int)ALIGN(height / 2, 32), SIZE_8K);
break;
@@ -296,7 +292,6 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
return size;
}
// Explicitly defined UBWC formats
bool IsUBwcFormat(int format) {
switch (format) {
case HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC:
@@ -309,7 +304,6 @@ bool IsUBwcFormat(int format) {
}
bool IsUBwcSupported(int format) {
// Existing HAL formats with UBWC support
switch (format) {
case HAL_PIXEL_FORMAT_BGR_565:
case HAL_PIXEL_FORMAT_RGBA_8888:
@@ -336,7 +330,6 @@ bool IsUBwcPISupported(int format, uint64_t usage) {
return false;
}
// As of now only two formats
switch (format) {
case HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC:
case HAL_PIXEL_FORMAT_YCbCr_420_TP10_UBWC: {
@@ -354,27 +347,21 @@ bool IsUBwcPISupported(int format, uint64_t usage) {
}
bool IsUBwcEnabled(int format, uint64_t usage) {
// Allow UBWC, if client is using an explicitly defined UBWC pixel format.
if (IsUBwcFormat(format)) {
return true;
}
// Allow UBWC, if an OpenGL client sets UBWC usage flag and GPU plus MDP
// support the format. OR if a non-OpenGL client like Rotator, sets UBWC
// usage flag and MDP supports the format.
if (((usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC) ||
(usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI) ||
(usage & BufferUsage::COMPOSER_CLIENT_TARGET)) &&
IsUBwcSupported(format)) {
bool enable = true;
// Query GPU for UBWC only if buffer is intended to be used by GPU.
if ((usage & BufferUsage::GPU_TEXTURE) || (usage & BufferUsage::GPU_RENDER_TARGET)) {
if (AdrenoMemInfo::GetInstance()) {
enable = AdrenoMemInfo::GetInstance()->IsUBWCSupportedByGPU(format);
}
}
// Allow UBWC, only if CPU usage flags are not set
if (enable && !(CpuCanAccess(usage))) {
return true;
}
@@ -418,13 +405,10 @@ unsigned int GetRgbUBwcMetaBufferSize(int width, int height, uint32_t bpp) {
return size;
}
// Align meta buffer height to 16 blocks
meta_height = ALIGN(((height + block_height - 1) / block_height), 16);
// Align meta buffer width to 64 blocks
meta_width = ALIGN(((width + block_width - 1) / block_width), 64);
// Align meta buffer size to 4K
size = (unsigned int)ALIGN((meta_width * meta_height), 4096);
return size;
@@ -500,12 +484,10 @@ bool UseUncached(int format, uint64_t usage) {
return true;
}
// CPU read rarely
if ((usage & BufferUsage::CPU_READ_MASK) == static_cast<uint64_t>(BufferUsage::CPU_READ_RARELY)) {
return true;
}
// CPU write rarely
if ((usage & BufferUsage::CPU_WRITE_MASK) ==
static_cast<uint64_t>(BufferUsage::CPU_WRITE_RARELY)) {
return true;
@@ -576,8 +558,6 @@ uint64_t GetHandleFlags(int format, uint64_t usage) {
int GetImplDefinedFormat(uint64_t usage, int format) {
int gr_format = format;
// If input format is HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED then based on
// the usage bits, gralloc assigns a format.
if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED ||
format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
if ((usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC || usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI) &&
@@ -585,33 +565,30 @@ int GetImplDefinedFormat(uint64_t usage, int format) {
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC;
} else if (usage & BufferUsage::VIDEO_ENCODER) {
if (usage & GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER) {
gr_format = HAL_PIXEL_FORMAT_NV21_ENCODEABLE; // NV21
gr_format = HAL_PIXEL_FORMAT_NV21_ENCODEABLE;
} else if (usage & GRALLOC_USAGE_PRIVATE_HEIF) {
gr_format = HAL_PIXEL_FORMAT_NV12_HEIF;
} else if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS;
} else {
gr_format = HAL_PIXEL_FORMAT_NV12_ENCODEABLE; // NV12
gr_format = HAL_PIXEL_FORMAT_NV12_ENCODEABLE;
}
} else if (usage & BufferUsage::CAMERA_INPUT) {
if (usage & BufferUsage::CAMERA_OUTPUT) {
// Assumed ZSL if both producer and consumer camera flags set
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL; // NV21
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
} else {
gr_format = HAL_PIXEL_FORMAT_YCrCb_420_SP; // NV21
gr_format = HAL_PIXEL_FORMAT_YCrCb_420_SP;
}
} else if (usage & BufferUsage::CAMERA_OUTPUT) {
if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL; // NV21
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
} else {
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS; // NV12 preview
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS;
}
} else if (usage & BufferUsage::COMPOSER_OVERLAY) {
// XXX: If we still haven't set a format, default to RGBA8888
gr_format = HAL_PIXEL_FORMAT_RGBA_8888;
} else if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
// If no other usage flags are detected, default the
// flexible YUV format to NV21_ZSL
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
}
}
@@ -637,7 +614,7 @@ bool HasAlphaComponent(int32_t format) {
}
void GetRGBPlaneInfo(const BufferInfo& info, int32_t format, int32_t width, int32_t height,
int32_t /* flags */, int* plane_count, PlaneLayoutInfo* plane_info) {
int32_t , int* plane_count, PlaneLayoutInfo* plane_info) {
uint64_t usage = info.usage;
*plane_count = 1;
uint32_t bpp = 0;
@@ -659,4 +636,4 @@ void GetRGBPlaneInfo(const BufferInfo& info, int32_t format, int32_t width, int3
plane_info->scanlines = height;
}
} // namespace gralloc
}
@@ -42,7 +42,7 @@
#ifdef SLAVE_SIDE_CP
#define SECURE_ALIGN SZ_1M
#else // MASTER_SIDE_CP
#else
#define SECURE_ALIGN SZ_4K
#endif
@@ -68,59 +68,38 @@ inline Type1 ALIGN(Type1 x, Type2 align) {
}
enum PlaneComponent {
/* luma */
PLANE_COMPONENT_Y = 1 << 0,
/* chroma blue */
PLANE_COMPONENT_Cb = 1 << 1,
/* chroma red */
PLANE_COMPONENT_Cr = 1 << 2,
/* red */
PLANE_COMPONENT_R = 1 << 10,
/* green */
PLANE_COMPONENT_G = 1 << 11,
/* blue */
PLANE_COMPONENT_B = 1 << 12,
/* alpha */
PLANE_COMPONENT_A = 1 << 20,
/* raw data plane */
PLANE_COMPONENT_RAW = 1 << 30,
/* meta information plane */
PLANE_COMPONENT_META = 1 << 31,
};
struct PlaneLayoutInfo {
/** Components represented the type of plane. */
PlaneComponent component;
/** horizontal subsampling. Must be a positive power of 2. */
uint32_t h_subsampling;
/** vertical subsampling. Must be a positive power of 2. */
uint32_t v_subsampling;
/** offset to the first byte of the top-left pixel of the plane
* and it is calculated from the start of the buffer.
* Add base of the handle with offset to get the first byte of the plane.
*/
uint32_t offset;
/** step is the distance in bytes from one pixel value to the next. */
int32_t step;
/** stride of the plane in pixels */
int32_t stride;
/** stride of the plane in in bytes */
int32_t stride_bytes;
/** plane height or vertical stride */
int32_t scanlines;
/** size of the plane in bytes */
uint32_t size;
};
@@ -151,6 +130,6 @@ int GetImplDefinedFormat(uint64_t usage, int format);
int GetCustomFormatFlags(int format, uint64_t usage, int* custom_format, uint64_t* priv_flags);
bool IsGPUFlagSupported(uint64_t usage);
bool HasAlphaComponent(int32_t format);
} // namespace gralloc
}
#endif // __GR_UTILS_H__
#endif
@@ -26,7 +26,6 @@
#include "QtiGrallocDefs.h"
#include "gr_priv_handle.h"
//#pragma message "Warning: gralloc_priv.h is deprecated"
#define GRALLOC_PROP_PREFIX "vendor.gralloc."
#define GRALLOC_PROP(prop_name) GRALLOC_PROP_PREFIX prop_name
@@ -42,8 +41,6 @@ inline int roundUpToPageSize(int x) {
return (x + (getpagesize() - 1)) & ~(getpagesize() - 1);
}
/* Legacy gralloc1 definitions */
/* Some clients may still be using the old flags */
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_ADSP_HEAP GRALLOC_USAGE_PRIVATE_ADSP_HEAP
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_ALLOC_UBWC GRALLOC_USAGE_PRIVATE_ALLOC_UBWC
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_UNCACHED GRALLOC_USAGE_PRIVATE_UNCACHED
@@ -54,7 +51,6 @@ inline int roundUpToPageSize(int x) {
#define GRALLOC1_CONSUMER_USAGE_PRIVATE_10BIT_TP GRALLOC_USAGE_PRIVATE_10BIT_TP
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_VIDEO_NV21_ENCODER GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER
// for PERFORM API :
#define GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER 1
#define GRALLOC_MODULE_PERFORM_GET_STRIDE 2
#define GRALLOC_MODULE_PERFORM_GET_CUSTOM_STRIDE_FROM_HANDLE 3
@@ -72,11 +68,9 @@ inline int roundUpToPageSize(int x) {
#define GRALLOC1_MODULE_PERFORM_GET_INTERLACE_FLAG 15
#define GRALLOC_MODULE_PERFORM_GET_GRAPHICS_METADATA 16
/* possible values for inverse gamma correction */
#define HAL_IGC_NOT_SPECIFIED 0
#define HAL_IGC_s_RGB 1
/* Color Space: Values maps to ColorSpace_t in qdMetadata.h */
#define HAL_CSC_ITU_R_601 0
#define HAL_CSC_ITU_R_601_FR 1
#define HAL_CSC_ITU_R_709 2
@@ -84,20 +78,16 @@ inline int roundUpToPageSize(int x) {
#define HAL_CSC_ITU_R_2020 4
#define HAL_CSC_ITU_R_2020_FR 5
/* possible formats for 3D content*/
enum {
HAL_NO_3D = 0x0,
HAL_3D_SIDE_BY_SIDE_L_R = 0x1,
HAL_3D_SIDE_BY_SIDE_R_L = 0x2,
HAL_3D_TOP_BOTTOM = 0x4,
HAL_3D_IN_SIDE_BY_SIDE_L_R = 0x10000, // unused legacy format
HAL_3D_IN_SIDE_BY_SIDE_L_R = 0x10000,
};
/* Flag to determine interlaced content
* Value maps to Flags presents in types.hal of QtiMapperextensions
*/
enum {
LAYOUT_INTERLACED_FLAG = 1 << 0,
};
#endif // __GRALLOC_PRIV_H__
#endif
@@ -60,43 +60,43 @@ struct IMapper : public IBase {
int32_t height __attribute__((aligned(4)));
};
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
static sp<IMapper> getService(const std::string& , const bool );
};
} // namespace V2_0
}
namespace V3_0 {
struct IMapper : public IBase {
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
static sp<IMapper> getService(const std::string& , const bool );
};
} // namespace V3_0
}
namespace V4_0 {
struct IMapper : public IBase {
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
static sp<IMapper> getService(const std::string& , const bool );
};
} // namespace V4_0
} // namespace android::hardware::graphics::mapper
}
}
namespace vendor::qti::hardware::display::mapper {
namespace V4_0 {
struct IQtiMapper : public IBase {
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V4_0::IMapper>& /* parent */,
bool /* emitError */);
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V4_0::IMapper>& ,
bool );
};
} // namespace V4_0
}
namespace V3_0 {
struct IQtiMapper : public IBase {
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V3_0::IMapper>& /* parent */,
bool /* emitError */);
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V3_0::IMapper>& ,
bool );
};
} // namespace V3_0
}
namespace V2_0 {
struct IQtiMapper : public IBase {
@@ -175,9 +175,9 @@ struct IQtiMapper : public IBase {
V1_0::IQtiMapperExtensions>& extensions)>;
virtual Return<void> getMapperExtensions(getMapperExtensions_cb _hidl_cb);
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V2_0::IMapper>& /* parent */,
bool /* emitError */);
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V2_0::IMapper>& ,
bool );
};
} // namespace V2_0
} // namespace vendor::qti::hardware::display::mapper
}
}
@@ -45,7 +45,7 @@ struct IQtiMapperExtensions : public IBase {
IQtiMapperExtensions(){};
};
} // namespace V1_0
}
namespace V1_1 {
using Error = V1_0::Error;
using PlaneLayout = V1_0::PlaneLayout;
@@ -64,7 +64,6 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, passthrough
using getInterlacedFlag_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, bool interlaced)>;
virtual ::android::hardware::Return<void> getInterlacedFlag(void* buffer,
@@ -79,7 +78,6 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, passthrough
using getRgbDataAddress_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, void* rgb_data)>;
virtual ::android::hardware::Return<void> getRgbDataAddress(void* buffer,
@@ -95,7 +93,6 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, passhthrough
using getColorSpace_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t color_space)>;
virtual ::android::hardware::Return<void> getColorSpace(void* buffer, getColorSpace_cb _hidl_cb) {
@@ -103,7 +100,6 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, passthrough with hidl fixups
using getYuvPlaneInfo_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error,
const ::android::hardware::hidl_vec<
@@ -130,27 +126,22 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, cast private data with no passthrough
using getFd_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t fd)>;
virtual ::android::hardware::Return<void> getFd(void* buffer, getFd_cb _hidl_cb);
// Used, cast private data with no passthrough
using getWidth_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t width)>;
virtual ::android::hardware::Return<void> getWidth(void* buffer, getWidth_cb _hidl_cb);
// Used, cast private data with no passthrough
using getHeight_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t height)>;
virtual ::android::hardware::Return<void> getHeight(void* buffer, getHeight_cb _hidl_cb);
// Used, cast private data with no passthrough
using getOffset_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t offset)>;
virtual ::android::hardware::Return<void> getOffset(void* buffer, getOffset_cb _hidl_cb);
// Used, cast private data with no passthrough
using getSize_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t size)>;
virtual ::android::hardware::Return<void> getSize(void* buffer, getSize_cb _hidl_cb);
@@ -179,12 +170,10 @@ struct IQtiMapperExtensions : public IBase {
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t usage)>;
virtual ::android::hardware::Return<void> getUsageFlags(void* buffer, getUsageFlags_cb _hidl_cb);
// Used, cast private data with no passthrough
using getFormat_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t format)>;
virtual ::android::hardware::Return<void> getFormat(void* buffer, getFormat_cb _hidl_cb);
// Used, cast private data with no passthrough
using getPrivateFlags_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t flags)>;
virtual ::android::hardware::Return<void> getPrivateFlags(void* buffer,
@@ -198,7 +187,6 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, PAIN
using getFormatLayout_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t size,
const ::android::hardware::hidl_vec<
@@ -208,7 +196,6 @@ struct IQtiMapperExtensions : public IBase {
int32_t height,
getFormatLayout_cb _hidl_cb);
// V1.1, Used, passthrough
virtual ::android::hardware::Return<
::vendor::qti::hardware::display::mapperextensions::V1_0::Error>
getSurfaceMetadata_V1(void* buffer, void* metadata) {
@@ -217,8 +204,8 @@ struct IQtiMapperExtensions : public IBase {
}
static Return<sp<IQtiMapperExtensions>> castFrom(
android::sp<V1_0::IQtiMapperExtensions> const& /* parent */, bool /* emitError */);
android::sp<V1_0::IQtiMapperExtensions> const& , bool );
};
} // namespace V1_1
} // namespace vendor::qti::hardware::display::mapperextensions
}
}
File diff suppressed because it is too large. Load diff
@@ -12,7 +12,6 @@ namespace graphics {
namespace mapper {
namespace V2_0 {
// Forward declaration for forward reference support:
enum class Error : int32_t;
struct YCbCrLayout;
@@ -25,34 +24,8 @@ enum class Error : int32_t {
UNSUPPORTED = 7,
};
/**
* A buffer descriptor is an implementation-defined opaque data returned by
* createDescriptor. It describes the properties of a buffer and is consumed
* by the allocator.
*/
typedef ::android::hardware::hidl_vec<uint32_t> BufferDescriptor;
/**
* Structure for describing YCbCr formats for consumption by applications.
* This is used with PixelFormat::YCBCR_*_888.
*
* Buffer chroma subsampling is defined in the format.
* e.g. PixelFormat::YCBCR_420_888 has subsampling 4:2:0.
*
* Buffers must have a 8 bit depth.
*
* y, cb, and cr point to the first byte of their respective planes.
*
* Stride describes the distance in bytes from the first value of one row of
* the image to the first value of the next row. It includes the width of the
* image plus padding.
* yStride is the stride of the luma plane.
* cStride is the stride of the chroma planes.
*
* chromaStep is the distance in bytes from one chroma pixel value to the
* next. This is 2 bytes for semiplanar (because chroma values are interleaved
* and each chroma value is one byte) and 1 for planar.
*/
struct YCbCrLayout final {
void* y;
void* cb;
@@ -62,9 +35,6 @@ struct YCbCrLayout final {
uint32_t chromaStep;
};
//
// type declarations for package
//
template <typename>
static inline std::string toString(int32_t o);
@@ -107,11 +77,7 @@ constexpr int32_t& operator&=(int32_t& v,
static inline std::string toString(
const ::android::hardware::graphics::mapper::V2_0::YCbCrLayout& o);
// operator== and operator!= are not generated for YCbCrLayout
//
// type header definitions for package
//
template <>
inline std::string toString<::android::hardware::graphics::mapper::V2_0::Error>(int32_t o) {
@@ -218,17 +184,17 @@ static inline std::string toString(
return os;
}
// operator== and operator!= are not generated for YCbCrLayout
} // namespace V2_0
} // namespace mapper
} // namespace graphics
} // namespace hardware
} // namespace android
//
// global type declarations for package
//
}
}
}
}
}
namespace android {
namespace hardware {
@@ -243,8 +209,8 @@ constexpr std::array<::android::hardware::graphics::mapper::V2_0::Error, 6>
::android::hardware::graphics::mapper::V2_0::Error::NO_RESOURCES,
::android::hardware::graphics::mapper::V2_0::Error::UNSUPPORTED,
};
} // namespace details
} // namespace hardware
} // namespace android
}
}
}
#endif // HIDL_GENERATED_ANDROID_HARDWARE_GRAPHICS_MAPPER_V2_0_TYPES_H
#endif
@@ -13,7 +13,6 @@ namespace display {
namespace mapperextensions {
namespace V1_0 {
// Forward declaration for forward reference support:
enum class Error : int32_t;
struct YCbCrLayout;
enum class PlaneComponent : uint32_t;
@@ -36,61 +35,22 @@ struct YCbCrLayout final {
uint32_t chromaStep;
};
/**
* Plane Component type definitions.
*/
enum class PlaneComponent : uint32_t {
PLANE_COMPONENT_Y = 1u /* (1 << 0) */,
PLANE_COMPONENT_Cb = 2u /* (1 << 1) */,
PLANE_COMPONENT_Cr = 4u /* (1 << 2) */,
PLANE_COMPONENT_R = 1024u /* (1 << 10) */,
PLANE_COMPONENT_G = 2048u /* (1 << 11) */,
PLANE_COMPONENT_B = 4096u /* (1 << 12) */,
PLANE_COMPONENT_A = 1048576u /* (1 << 20) */,
PLANE_COMPONENT_RAW = 1073741824u /* (1 << 30) */,
PLANE_COMPONENT_META = 2147483648u /* (1 << 31) */,
PLANE_COMPONENT_Y = 1u ,
PLANE_COMPONENT_Cb = 2u ,
PLANE_COMPONENT_Cr = 4u ,
PLANE_COMPONENT_R = 1024u ,
PLANE_COMPONENT_G = 2048u ,
PLANE_COMPONENT_B = 4096u ,
PLANE_COMPONENT_A = 1048576u ,
PLANE_COMPONENT_RAW = 1073741824u ,
PLANE_COMPONENT_META = 2147483648u ,
};
/**
* Flags to send with getFormatLayout for extra information for e.g. interlaced,
* otherwise send 0.
*/
enum class Flags : int32_t {
LAYOUT_INTERLACED_FLAG = 1 /* (1 << 0) */,
LAYOUT_INTERLACED_FLAG = 1 ,
};
/**
* Structure for describing plane layout information for YUVA/RGBA/RAW formats
* YUV formats can be planar or semi-planar.
*
* This layout structure is used with any pixel format that can be
* represented by it, such as:
* - HAL_PIXEL_FORMAT_YCbCr_*
* - HAL_PIXEL_FORMAT_RGB*_*
* - even implementation defined formats that can be represented by
* the structures
*
* Each plane can be made up of one or more PlaneComponents depending on
* the format.
*
* Each plane can be subsampled either vertically or horizontally by
* a power-of-two factor.
*
* Pixel increment describes the distance in bytes from one pixel to the
* next pixel (to the right) on the same row for the component plane.
* This can be negative.
*
* Stride is provided in both pixels and bytes.
* Stride_bytes describes the distance in bytes from the first pixel of
* one row to the first pixel of the next row (below) for the component plane.
* This can be negative.
*
* Scanlines describes the height of the plane in terms of number of rows.
*
* Size of plane is stride_bytes * scanlines + any padding required for
* alignment as per format specification.
*
*/
struct PlaneLayout final {
::android::hardware::hidl_bitfield<
::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneComponent>
@@ -137,9 +97,6 @@ static_assert(sizeof(::vendor::qti::hardware::display::mapperextensions::V1_0::P
static_assert(__alignof(::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout) == 4,
"wrong alignment");
//
// type declarations for package
//
template <typename>
static inline std::string toString(int32_t o);
@@ -185,7 +142,6 @@ constexpr int32_t& operator&=(
static inline std::string toString(
const ::vendor::qti::hardware::display::mapperextensions::V1_0::YCbCrLayout& o);
// operator== and operator!= are not generated for YCbCrLayout
template <typename>
static inline std::string toString(uint32_t o);
@@ -284,9 +240,6 @@ static inline bool operator!=(
const ::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout& lhs,
const ::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout& rhs);
//
// type header definitions for package
//
template <>
inline std::string toString<::vendor::qti::hardware::display::mapperextensions::V1_0::Error>(
@@ -379,7 +332,6 @@ static inline std::string toString(
return os;
}
// operator== and operator!= are not generated for YCbCrLayout
template <>
inline std::string
@@ -642,16 +594,13 @@ static inline bool operator!=(
return !(lhs == rhs);
}
} // namespace V1_0
} // namespace mapperextensions
} // namespace display
} // namespace hardware
} // namespace qti
} // namespace vendor
}
}
}
}
}
}
//
// global type declarations for package
//
namespace android {
namespace hardware {
@@ -664,9 +613,9 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::E
::vendor::qti::hardware::display::mapperextensions::V1_0::Error::NO_RESOURCES,
::vendor::qti::hardware::display::mapperextensions::V1_0::Error::UNSUPPORTED,
};
} // namespace details
} // namespace hardware
} // namespace android
}
}
}
namespace android {
namespace hardware {
@@ -688,9 +637,9 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::P
::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneComponent::
PLANE_COMPONENT_META,
};
} // namespace details
} // namespace hardware
} // namespace android
}
}
}
namespace android {
namespace hardware {
@@ -700,8 +649,8 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::F
hidl_enum_values<::vendor::qti::hardware::display::mapperextensions::V1_0::Flags> = {
::vendor::qti::hardware::display::mapperextensions::V1_0::Flags::LAYOUT_INTERLACED_FLAG,
};
} // namespace details
} // namespace hardware
} // namespace android
}
}
}
#endif // HIDL_GENERATED_VENDOR_QTI_HARDWARE_DISPLAY_MAPPEREXTENSIONS_V1_0_TYPES_H
#endif
@@ -46,14 +46,13 @@
using namespace android::hardware;
using namespace android;
// These two functions are used by the blob and aren't present on Q so weakly stub them
extern "C" uint64_t __attribute__((weak)) atrace_get_enabled_tags() {
return 0;
}
namespace android::hardware::details {
void __attribute__((weak)) return_status::onValueRetrieval() const {}
} // namespace android::hardware::details
}
bool IBase::isRemote() const {
return false;
@@ -146,7 +145,7 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
return nullptr;
}
} // namespace V2_0
}
namespace V3_0 {
sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getStub) {
@@ -154,7 +153,7 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
return nullptr;
}
} // namespace V3_0
}
namespace V4_0 {
sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getStub) {
@@ -162,8 +161,8 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
return nullptr;
}
} // namespace V4_0
} // namespace android::hardware::graphics::mapper
}
}
namespace vendor::qti::hardware::display {
namespace mapper {
namespace V4_0 {
@@ -174,7 +173,7 @@ Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V4_0::IMa
return nullptr;
}
} // namespace V4_0
}
namespace V3_0 {
Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V3_0::IMapper>& parent,
@@ -183,7 +182,7 @@ Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V3_0::IMa
return nullptr;
}
} // namespace V3_0
}
namespace V2_0 {
Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V2_0::IMapper>& parent,
@@ -200,8 +199,8 @@ Return<void> IQtiMapper::getMapperExtensions(getMapperExtensions_cb _hidl_cb) {
return Void();
}
} // namespace V2_0
} // namespace mapper
}
}
namespace mapperextensions {
namespace V1_1 {
@@ -467,6 +466,6 @@ Return<void> IQtiMapperExtensions::getFormatLayout(int32_t format, uint64_t usag
return Void();
}
} // namespace V1_1
} // namespace mapperextensions
} // namespace vendor::qti::hardware::display
}
}
}
+107
View File
@@ -0,0 +1,107 @@
#include <dlfcn.h>
#include <jni.h>
#include <stdio.h>
#include <string.h>
#define VK_NO_PROTOTYPES
#include <vulkan/vulkan.h>
#define LINE(...) do { if (used < sizeof(out)) used += snprintf(out + used, sizeof(out) - used, __VA_ARGS__); } while (0)
static const char *const EXTENSIONS[] = {
"VK_EXT_robustness2", "VK_EXT_transform_feedback", "VK_EXT_custom_border_color",
"VK_KHR_dynamic_rendering", "VK_EXT_extended_dynamic_state3", "VK_EXT_descriptor_indexing",
"VK_KHR_timeline_semaphore", "VK_EXT_mesh_shader", "VK_KHR_ray_query",
};
static const char *type_name(VkPhysicalDeviceType type) {
switch (type) {
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return "integrated GPU";
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return "discrete GPU";
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return "virtual GPU";
case VK_PHYSICAL_DEVICE_TYPE_CPU: return "CPU";
default: return "other";
}
}
JNIEXPORT jstring JNICALL
Java_com_droiddeck_launcher_gpu_VulkanInfo_nativeQuery(JNIEnv *env, jclass clazz) {
static char out[4096];
size_t used = 0;
out[0] = 0;
void *lib = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
if (!lib) { LINE("error=no Vulkan loader\n"); return (*env)->NewStringUTF(env, out); }
PFN_vkGetInstanceProcAddr gipa = (PFN_vkGetInstanceProcAddr) dlsym(lib, "vkGetInstanceProcAddr");
PFN_vkCreateInstance create = gipa ? (PFN_vkCreateInstance) gipa(NULL, "vkCreateInstance") : NULL;
PFN_vkEnumerateInstanceVersion instanceVersion = gipa ? (PFN_vkEnumerateInstanceVersion) gipa(NULL, "vkEnumerateInstanceVersion") : NULL;
if (!create) { LINE("error=no vkCreateInstance\n"); dlclose(lib); return (*env)->NewStringUTF(env, out); }
uint32_t loader = VK_API_VERSION_1_0;
if (instanceVersion) instanceVersion(&loader);
LINE("loader=%u.%u.%u\n", VK_API_VERSION_MAJOR(loader), VK_API_VERSION_MINOR(loader), VK_API_VERSION_PATCH(loader));
VkApplicationInfo app = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "DroidDeck device info",
.apiVersion = loader >= VK_API_VERSION_1_1 ? VK_API_VERSION_1_1 : VK_API_VERSION_1_0 };
VkInstanceCreateInfo info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app };
VkInstance instance = VK_NULL_HANDLE;
if (create(&info, NULL, &instance) != VK_SUCCESS) { LINE("error=vkCreateInstance failed\n"); dlclose(lib); return (*env)->NewStringUTF(env, out); }
#define GET(name) PFN_##name name = (PFN_##name) gipa(instance, #name)
GET(vkDestroyInstance); GET(vkEnumeratePhysicalDevices); GET(vkGetPhysicalDeviceProperties);
GET(vkGetPhysicalDeviceProperties2); GET(vkGetPhysicalDeviceMemoryProperties);
GET(vkEnumerateDeviceExtensionProperties); GET(vkGetPhysicalDeviceFeatures);
uint32_t count = 1;
VkPhysicalDevice device = VK_NULL_HANDLE;
VkResult listed = vkEnumeratePhysicalDevices ? vkEnumeratePhysicalDevices(instance, &count, &device) : VK_ERROR_INITIALIZATION_FAILED;
if ((listed != VK_SUCCESS && listed != VK_INCOMPLETE) || count == 0 || !device || !vkGetPhysicalDeviceProperties) {
LINE("error=no Vulkan device\n");
} else {
VkPhysicalDeviceProperties p;
vkGetPhysicalDeviceProperties(device, &p);
LINE("device=%s\n", p.deviceName);
LINE("type=%s\n", type_name(p.deviceType));
LINE("api=%u.%u.%u\n", VK_API_VERSION_MAJOR(p.apiVersion), VK_API_VERSION_MINOR(p.apiVersion), VK_API_VERSION_PATCH(p.apiVersion));
LINE("driver_version=%u.%u.%u\n", p.driverVersion >> 22, (p.driverVersion >> 12) & 0x3ff, p.driverVersion & 0xfff);
LINE("vendor_id=0x%04x\ndevice_id=0x%08x\n", p.vendorID, p.deviceID);
LINE("max_image_2d=%u\n", p.limits.maxImageDimension2D);
uint32_t extCount = 0;
if (vkEnumerateDeviceExtensionProperties) vkEnumerateDeviceExtensionProperties(device, NULL, &extCount, NULL);
static VkExtensionProperties exts[512];
if (extCount > 512) extCount = 512;
if (extCount && vkEnumerateDeviceExtensionProperties) vkEnumerateDeviceExtensionProperties(device, NULL, &extCount, exts);
int driverProps = p.apiVersion >= VK_API_VERSION_1_2;
for (uint32_t i = 0; i < extCount; i++)
if (!strcmp(exts[i].extensionName, "VK_KHR_driver_properties")) driverProps = 1;
if (driverProps && vkGetPhysicalDeviceProperties2) {
VkPhysicalDeviceDriverProperties driver = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES };
VkPhysicalDeviceProperties2 p2 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = &driver };
vkGetPhysicalDeviceProperties2(device, &p2);
LINE("driver_name=%s\n", driver.driverName);
LINE("driver_info=%s\n", driver.driverInfo);
LINE("conformance=%u.%u.%u.%u\n", driver.conformanceVersion.major, driver.conformanceVersion.minor,
driver.conformanceVersion.subminor, driver.conformanceVersion.patch);
}
LINE("extension_count=%u\n", extCount);
for (size_t e = 0; e < sizeof(EXTENSIONS) / sizeof(*EXTENSIONS); e++) {
int has = 0;
for (uint32_t i = 0; i < extCount && !has; i++) has = !strcmp(exts[i].extensionName, EXTENSIONS[e]);
LINE("ext.%s=%s\n", EXTENSIONS[e], has ? "yes" : "no");
}
if (vkGetPhysicalDeviceFeatures) {
VkPhysicalDeviceFeatures f;
vkGetPhysicalDeviceFeatures(device, &f);
LINE("geometry_shader=%s\ntessellation_shader=%s\ntexture_bc=%s\n",
f.geometryShader ? "yes" : "no", f.tessellationShader ? "yes" : "no", f.textureCompressionBC ? "yes" : "no");
}
if (vkGetPhysicalDeviceMemoryProperties) {
VkPhysicalDeviceMemoryProperties m;
vkGetPhysicalDeviceMemoryProperties(device, &m);
VkDeviceSize largest = 0;
for (uint32_t i = 0; i < m.memoryHeapCount; i++)
if ((m.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) && m.memoryHeaps[i].size > largest) largest = m.memoryHeaps[i].size;
LINE("device_heap_mb=%llu\n", (unsigned long long) (largest >> 20));
}
}
if (vkDestroyInstance) vkDestroyInstance(instance, NULL);
dlclose(lib);
return (*env)->NewStringUTF(env, out);
}
File diff suppressed because it is too large. Load diff
+90 -248
View File
@@ -84,12 +84,11 @@ struct VkTable {
PFN_vkCmdSetScissor CmdSetScissor;
PFN_vkCmdPipelineBarrier CmdPipelineBarrier;
PFN_vkCmdCopyImage CmdCopyImage;
PFN_vkCmdBlitImage CmdBlitImage; // frame-gen capture resolution: composite -> swapchain upscale
// Compute: the native LSFG chain is 25 compute dispatches.
PFN_vkCmdBlitImage CmdBlitImage;
PFN_vkCmdDispatch CmdDispatch;
PFN_vkCreateComputePipelines CreateComputePipelines;
PFN_vkUnmapMemory UnmapMemory;
// win-fg native: clears its flow scratch, resets its scratch pool on resize.
PFN_vkCmdClearColorImage CmdClearColorImage;
PFN_vkResetDescriptorPool ResetDescriptorPool;
PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage;
@@ -125,103 +124,94 @@ struct VkTable {
#include <condition_variable>
#include <chrono>
// Renderer-neutral direct-scanout impl (owns the SurfaceControl/AHB state).
// Included after SCANOUT_LOG is defined above; its own definition is guarded.
#include "../scanout/ScanoutContext.h"
// Native (compositor-side) LSFG frame generation - capability gate.
#include "lsfg/lsfg_probe.h"
#include <memory>
#include <string>
namespace lsfg { class Engine; }
namespace winfg { class Engine; }
namespace ddfg { class Engine; }
static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
// Push-constant layouts for the spatial-upscaler post passes. The leading vec4
// `ndc` matches upscale.vert; the remaining members are read only by the
// fragment stage. std430 offsets line up with these C structs.
struct SgsrPushConstants { // 36 bytes
struct SgsrPushConstants {
float ndc[4];
float viewportInfo[4]; // xy = 1/inputSize, zw = inputSize px
float edgeSharpness; // SGSR EdgeSharpness, from the slider
float viewportInfo[4];
float edgeSharpness;
};
struct NisPushConstants { // 36 bytes
struct NisPushConstants {
float ndc[4];
float viewportInfo[4]; // xy = 1/inputSize, zw = inputSize px
float sharpness; // NIS sharpness [0..1], from the slider
float viewportInfo[4];
float sharpness;
};
struct EasuPushConstants { // 88 bytes
struct EasuPushConstants {
float ndc[4];
uint32_t con0[4], con1[4], con2[4], con3[4];
float outW, outH;
};
struct RcasPushConstants { // 40 bytes
struct RcasPushConstants {
float ndc[4];
uint32_t con[4]; // con.x = bit-packed sharpness
uint32_t con[4];
float outW, outH;
};
// Composable post effects (AMD CAS sharpen + fake-HDR). Both lead with vec4 ndc
// (offset 0) like the upscaler PCs and stay well under the 88-byte PC range.
struct CasPushConstants { // 32 bytes
struct CasPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px
float sharpness; // CAS SHARPNESS term [0..1]
float resolution[2];
float sharpness;
float _pad;
};
struct HdrPushConstants { // 24 bytes
struct HdrPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px
float resolution[2];
};
// Phase 2 screen effects ported from the GL EffectComposer. Each leads with vec4
// ndc (offset 0) like the upscaler PCs and stays well under the 88-byte PC range.
struct FxaaPushConstants { // 24 bytes
struct FxaaPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px
float resolution[2];
};
struct ToonPushConstants { // 24 bytes
struct ToonPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px
float resolution[2];
};
struct ColorPushConstants { // 32 bytes
struct ColorPushConstants {
float ndc[4];
float brightness; // additive [-1,1]
float contrast; // [0,2]
float gamma; // [0.1,5]
float saturation; // [0,2]; 1 = unchanged, 0 = greyscale.
// APPENDED last so the three offsets above
// stay where color.frag already had them.
float brightness;
float contrast;
float gamma;
float saturation;
};
struct NtscPushConstants { // 28 bytes
struct NtscPushConstants {
float ndc[4];
float resolution[2]; // screen size in px (TextureSize/resolution)
float frameCount; // animated chroma-phase counter
float resolution[2];
float frameCount;
};
struct CrtPushConstants { // 24 bytes
struct CrtPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px (unused by shader)
float resolution[2];
};
struct DebandPushConstants { // 28 bytes
struct DebandPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px (layout parity)
float strength; // dither amplitude in LSBs (1.0 = +/-1/255)
float resolution[2];
float strength;
};
class VulkanRendererContext {
public:
// `lsfgVk11Compat` (experimental) lets the LSFG capability probe accept a
// Vulkan 1.1/1.2 device that offers the extensions the chain needs; it has
// to be known before vkCreateDevice, hence a constructor argument.
VulkanRendererContext(ANativeWindow* window, int cWidth, int cHeight, void* adrenotoolsHandle = nullptr,
bool lsfgVk11Compat = false);
~VulkanRendererContext();
void onSurfaceResized(int width, int height);
void setTransform(float ox, float oy, float sx, float sy);
// #413: game-region clip rect (surface px). Stored and applied as the swapchain scissor in
// recordCmdBuf so a FILL/STRETCH game confined to a half (TOP/BOTTOM alignment) can't bleed into
// the on-screen-controls half. A full-surface (or w<=0) rect means "no clip" (CENTER).
void setClipRegion(int x, int y, int w, int h);
void updatePointerPosition(short x, short y);
void updateWindowContent(int64_t id, void* pixels, short w, short h, short stride, int x, int y);
@@ -241,13 +231,7 @@ public:
void scanoutSetBuffer(AHardwareBuffer* ahb, int x, int y, int w, int h, int fenceFd = -1);
void scanoutSetCursorImage(void* pixels, short w, short h, short stride);
void scanoutSetCursorPos(short x, short y, short hotX, short hotY);
// The renderer-neutral scanout implementation. The scanout methods above are
// thin forwarders to this (see VulkanRendererScanout.cpp); the cursor render-
// thread signalling stays in the forwarders, not in ScanoutContext.
ScanoutContext scanout;
// Bound to scanout's atomics so the existing JNI ABI (r->scanoutActive /
// r->gameFrameDelivered direct member access) keeps compiling against a
// single source of truth. Must be declared after `scanout`.
std::atomic<bool>& scanoutActive = scanout.scanoutActive;
std::atomic<bool>& gameFrameDelivered = scanout.gameFrameDelivered;
std::atomic<bool> surfaceDetached{false};
@@ -286,48 +270,17 @@ public:
void setPresentMode(VkPresentModeKHR mode);
std::vector<int> getSupportedPresentModes() const;
// --- Native LSFG frame generation -------------------------------------
// Capability verdict for the compositor-side LSFG engine. Filled at device
// creation and completed once the swapchain format is known; read by the
// UI (through JNI) to grey the engine out with a reason.
const lsfg::Caps& lsfgCaps() const { return lsfgCaps_; }
// Arm/disarm native LSFG frame generation for this session. Changing the
// armed state recreates the swapchain, because the composite path needs
// TRANSFER_DST usage and a deeper image queue that a normal session does
// not pay for.
void setFrameGenArmed(bool armed, int multiplier);
bool frameGenArmed() const { return fgArmed_.load(std::memory_order_relaxed); }
// Live frame-gen telemetry for the in-game readout:
// [0] generations the governor currently trusts
// [1] generations actually planned for the last source frame
// [2] measured source (real) frames per second
// [3] measured presented frames per second
// [4] thermal status, -1 when the device gives no signal
// [5] GPU milliseconds the chain spends per generated frame, -1 unknown
void frameGenStats(float out[6]) const;
// Why the selected native frame-gen engine cannot run here, for the UI:
// -1 not known yet (caps not probed), 0 fine, 1 this driver lacks what the
// engine needs (lsfgCaps().reason says which gate), 2 the engine failed to start.
int frameGenProblem() const;
// Path to the SPIR-V cache built from the user's Lossless.dll. Setting it
// drops any existing engine so the next armed frame rebuilds from it.
void setLsfgCachePath(const char* path);
// Flow scale (0.25-1.0) and the panel's real refresh rate. The pacer never
// generates above the refresh rate.
// Experimental: `captureHeight` sizes the composite ring the chain runs on,
// the width following the swapchain's aspect, so a phone GPU generates at
// game-like resolution and the result is blitted up. 0 = panel;
// kFgCaptureGame = the X screen's height (containerHeight), which already
// carries the per-game override and render scale; otherwise a pixel height.
static constexpr int32_t kFgCaptureGame = -1;
void setFrameGenTuning(float flowScale, float refreshHz, int32_t captureHeight = 0);
// Which native engine generates: 0 = LSFG (needs the cache built from the
// user's Lossless.dll), 1 = win-fg (our own chain, embedded, needs nothing).
// Switching drops the other engine so only one ever holds GPU resources.
void setFrameGenEngine(int kind);
// win-fg only: interpolation model (3/4) and performance preset (0..2).
void setWinFgTuning(int model, int perfPreset);
void setDdFgTuning(int model, int perfPreset);
private:
struct WinTex {
@@ -359,11 +312,9 @@ private:
ANativeWindow* window;
int surfaceWidth, surfaceHeight, containerWidth, containerHeight;
// #413 compositor clip rect (surface px). clipRegionW<=0 => disabled (whole swapchain). Written from
// the renderer thread (setClipRegion), read from the render thread (recordCmdBuf) -> atomic.
std::atomic<int> clipRegionX{0}, clipRegionY{0}, clipRegionW{0}, clipRegionH{0};
void* adrenotoolsHandle = nullptr;
bool lsfgVk11Compat_ = false; // see the constructor
bool lsfgVk11Compat_ = false;
int filterMode = 0;
bool swapRB = false;
float maxAnisotropy = 1.0f;
@@ -386,9 +337,6 @@ private:
std::vector<VkImageMemoryBarrier> framePreUpload;
std::vector<VkImageMemoryBarrier> framePostUpload;
// [P0] All SurfaceControl / AHardwareBuffer scanout state moved to
// ScanoutContext (member `scanout` above). Dead members (lastDst*,
// ScanoutPending/scanoutPending*, fnSTSetBackPressure) were dropped.
std::atomic<int> pointerX{0}, pointerY{0};
float sceneOffsetX=0.f, sceneOffsetY=0.f, sceneScaleX=1.f, sceneScaleY=1.f;
@@ -428,138 +376,69 @@ private:
VkPipeline pipeline = VK_NULL_HANDLE;
// ---- Spatial upscaler (SGSR / FSR1) ----
// upscalerMode enum (mirrored by JNI nativeSetUpscaler):
// 0 = none (passthrough; current sampler governs scaling)
// 1 = linear (rebuild base sampler LINEAR; no shader pass)
// 2 = nearest (rebuild base sampler NEAREST; no shader pass)
// 3 = sgsr (Snapdragon GSR 1.0; single pass; aspect-fit / letterbox)
// 4 = fsr (AMD FSR1 EASU+RCAS; two passes; fill / stretch)
// 5 = fsr_fit (AMD FSR1 EASU+RCAS; two passes; aspect-fit / letterbox)
// 6 = sharpen (RCAS-only; any resolution; aspect-fit / letterbox)
// 7 = nis (NVIDIA Image Scaling NVScaler; single pass; aspect-fit)
// 8 = sgsr_quality (SGSR 1 edge-direction variant; single pass; aspect-fit)
// Shader upscaling only engages for modes 3-8 AND when the game render
// resolution (container) is smaller than the swapchain. Otherwise the
// existing direct-to-swapchain path is used unchanged.
int upscalerMode = 0;
// Independent of upscalerMode: high-quality Lanczos downscale for the
// supersampling case (game render res > display res). When enabled and the
// render res exceeds the swapchain, replaces the bilinear minify with a
// proper downscale pass. Supersampling and the upscale modes are mutually
// exclusive in practice (one is render>display, the other render<display).
bool hqDownscale = false;
// Composable post effects, independent of the scaling mode. CAS layers a sharpen
// on top of any mode (incl. native res); HDR is a binary fake-HDR. The upscaler
// sharpness (RCAS lobe scale / SGSR EdgeSharpness) is driven by its own slider as a
// linear 0..1 value: 0 = neutral (no sharpening; the upscale still runs), 1 = max.
bool casEnabled = false;
int casSharpness = 60; // slider 0..100 -> CAS SHARPNESS
int casSharpness = 60;
bool hdrEnabled = false;
float upscaleSharpness01 = 0.75f; // slider/100; RCAS lobe scale, SGSR edge derived
// Debanding: terminal TPDF/IGN dither pass before the 8-bit swapchain quantize.
float upscaleSharpness01 = 0.75f;
bool debandEnabled = false;
int debandStrength = 100; // slider 0..200 -> strength/100 LSBs
int debandStrength = 100;
// Phase 2 screen effects (GL EffectComposer parity). FXAA/Toon/CRT/NTSC are
// binary; Color is the brightness/contrast/gamma grade (always-applied via the
// sliders, no toggle) and is "enabled" only when not at neutral (0,0,1).
bool fxaaEnabled = false;
bool toonEnabled = false;
bool crtEnabled = false;
bool ntscEnabled = false;
bool colorEnabled = false; // derived: !neutral grade
float colorBrightness = 0.0f; // [-1,1] (slider/100, clamped)
float colorContrast = 0.0f; // [0,2] (slider/100, clamped)
float colorGamma = 1.0f; // [0.1,5]
float colorSaturation = 1.0f; // [0,2] (slider/100, clamped)
uint32_t ntscFrameCounter = 0; // animates NTSC chroma phase
bool colorEnabled = false;
float colorBrightness = 0.0f;
float colorContrast = 0.0f;
float colorGamma = 1.0f;
float colorSaturation = 1.0f;
uint32_t ntscFrameCounter = 0;
VkSampler upscaleSampler = VK_NULL_HANDLE; // linear clamp; offscreen/mid input
VkSampler upscaleSampler = VK_NULL_HANDLE;
// === Native LSFG: composite target ring ===================================
// Frame generation cannot composite straight into a swapchain image: the
// finished frame has to be READABLE (it becomes the next frame's LSFG
// input) and generated frames have to be STORAGE-WRITABLE by a compute
// dispatch. Android swapchain images are COLOR_ATTACHMENT only, and
// storage support on a swapchain format is not something a driver owes us.
//
// So when frame gen is armed the whole existing recording is redirected at
// a composite image we own - format-identical to the swapchain, so every
// existing pipeline stays render-pass compatible - and a copy moves it into
// the acquired swapchain image at the end. With frame gen off, not one of
// these objects is created and the direct-to-swapchain path is untouched.
struct CompositeTarget {
VkImage img = VK_NULL_HANDLE;
VkDeviceMemory mem = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE; // colour attachment + sampled
VkImageView storageView = VK_NULL_HANDLE; // compute writes (generate)
VkImageView view = VK_NULL_HANDLE;
VkImageView storageView = VK_NULL_HANDLE;
VkFramebuffer fb = VK_NULL_HANDLE;
VkDescriptorSet ds = VK_NULL_HANDLE; // sampled, for later passes
VkDescriptorSet ds = VK_NULL_HANDLE;
};
// Hard ceiling: (max generations + 1) presentable frames per source frame,
// times a queue depth of 2, capped so the footprint stays bounded.
static constexpr uint32_t kMaxCompositeTargets = 7;
std::vector<CompositeTarget> compositeTargets;
VkRenderPass compositeRenderPass = VK_NULL_HANDLE; // CLEAR -> GENERAL
VkRenderPass compositeRenderPass = VK_NULL_HANDLE;
uint32_t compositeW = 0, compositeH = 0;
uint32_t compositeIndex = 0; // rotates per composite; gives history for free
bool compositeArmed = false; // targets exist AND this frame uses them
bool swapchainTransferDst = false; // swapchain was created with TRANSFER_DST
uint32_t compositeIndex = 0;
bool compositeArmed = false;
bool swapchainTransferDst = false;
// Set from the app when the native LSFG engine is selected for this
// session. Read on the render thread; false keeps every path as it was.
std::atomic<bool> fgArmed_{false};
std::atomic<int> fgMultiplier_{0};
// Tuning is written by the UI thread and consumed by the render thread, so
// it is published through atomics and applied to the engine at the top of
// the frame-gen block. The UI thread never touches the engine itself.
std::atomic<float> fgFlowScale_{1.0f};
std::atomic<float> fgRefreshHz_{0.0f};
std::atomic<bool> fgConfigDirty_{true};
// Experimental (see setFrameGenTuning): capture height of the composite
// ring. 0 = panel resolution, kFgCaptureGame = the X screen's height.
std::atomic<int32_t> fgCaptureHeight_{0};
// Extent the composite ring should have for the current swapchain and
// capture height (width follows the swapchain aspect, both even).
void compositeExtentFor(uint32_t& w, uint32_t& h) const;
// The extent the scene is rendered at this frame: the composite ring while
// frame gen runs on a ring smaller than the swapchain (experimental capture
// resolution), otherwise the swapchain. Every full-target pass (the effect
// chain, the spatial upscalers, the render-scale downscale) sizes itself by
// this, so those keep working below panel resolution and their
// intermediates shrink with the ring.
VkExtent2D renderExtent() const {
if (compositeActive() && (compositeW != swapchainExt.width || compositeH != swapchainExt.height))
return VkExtent2D{compositeW, compositeH};
return swapchainExt;
}
// Copy (same extent) or blit (composite smaller than the swapchain) a
// composite-ring image, already in TRANSFER_SRC, into a swapchain image
// already in TRANSFER_DST.
void recordCompositeToSwapchainTransfer(VkCommandBuffer cb, VkImage src, uint32_t imgIdx);
bool createCompositeRenderPass();
bool ensureCompositeTargets(uint32_t w, uint32_t h, uint32_t count);
void destroyCompositeTargets();
// True when this frame should composite off-swapchain. Call on the render
// thread; every gate must hold or we fall back to the untouched path.
bool compositeActive() const;
// Render pass / framebuffer this frame draws its FINAL pass into.
VkRenderPass targetRenderPass() const;
VkFramebuffer targetFramebuffer(uint32_t imgIdx) const;
// Copy the finished composite into the acquired swapchain image and leave
// it in PRESENT_SRC. No-op when the composite path is not active.
void copyCompositeToSwapchain(VkCommandBuffer cb, uint32_t imgIdx);
// === Native LSFG: the software cursor ====================================
// LSFG interpolates whatever it is given, so a cursor composited into the
// frame gets warped along the flow field and smears. It is therefore
// excluded from the composite while frame gen is armed and drawn once into
// EVERY presented image instead - real and generated alike - through a
// load-op render pass that leaves the image ready to present.
VkRenderPass cursorOverlayRenderPass = VK_NULL_HANDLE;
struct CursorOverlay {
bool draw = false;
@@ -569,19 +448,16 @@ private:
CursorOverlay cursorOverlay_{};
bool createCursorOverlayRenderPass();
// Draw the cursor into an already-copied swapchain image and leave it in
// PRESENT_SRC. Takes over the final transition from the copy helpers.
void recordCursorOverlay(VkCommandBuffer cb, uint32_t imgIdx);
// True when the cursor is being drawn per present rather than composited.
bool cursorDrawnPerPresent() const;
// === Native LSFG: the per-source-frame present plan =======================
// Interpolation produces frames that belong BETWEEN N-1 and N, so the
// generated frames are presented FIRST and real frame N is held back one
// slot. All presents for one source frame are queued together: with FIFO
// the driver then shows them on consecutive vblanks, so the pacing falls
// out of the present mode for free - no sleeps, no render-mode change.
static constexpr uint32_t kMaxPresentsPerFrame = 4; // 1 real + up to 3 generated
static constexpr uint32_t kMaxPresentsPerFrame = 4;
struct FrameGenPlan {
uint32_t generations = 0;
uint32_t presents = 1;
@@ -592,32 +468,19 @@ private:
uint64_t fgSourceFrames_ = 0;
std::string lsfgCachePath_;
bool lsfgEngineTried_ = false;
std::unique_ptr<winfg::Engine> winfgEngine_;
bool winfgEngineTried_ = false;
std::atomic<int> fgEngineKind_{0}; // 0 = lsfg, 1 = win-fg
std::unique_ptr<ddfg::Engine> ddfgEngine_;
bool ddfgEngineTried_ = false;
std::atomic<int> fgEngineKind_{0};
std::atomic<int> fgModel_{4};
std::atomic<int> fgPerfPreset_{1};
bool ensureWinFgEngine();
// Capability gate for the SELECTED engine. win-fg's shaders need only a
// storage-capable swapchain format; the LSFG chain also needs the fp16 /
// memory-model feature set, which some otherwise capable GPUs lack.
bool ensureDdFgEngine();
bool fgCapsOk() const;
// Sync objects are indexed per PRESENT, not per composite: each pending
// present needs its own image-available and render-finished semaphore.
uint32_t syncSlot(uint32_t k) const { return currentFrame * kMaxPresentsPerFrame + k; }
// Destroy and rebuild every acquire/present semaphore. Called with the
// swapchain, so no stale pending signal can survive into the new one.
void recreateSyncObjects();
uint32_t fgAcquireFailLog_ = 0;
// Measured PRESENTS per second - the rate that actually reaches the panel,
// counting generated frames. The pacer's own "loop rate" cannot serve this
// purpose: it is sampled once per SOURCE frame, so it always equals the
// guest rate and contains no evidence that generation happened at all.
float fgPresentedRate_ = 0.0f;
// Measured SOURCE frames per second over the same window, for engines that
// do not measure it themselves (win-fg).
float fgSourceRate_ = 0.0f;
uint32_t fgSourceAccum_ = 0;
uint32_t fgPresentAccum_ = 0;
@@ -626,22 +489,10 @@ private:
void trackPresentedRate(uint32_t presents);
bool ensureLsfgEngine();
// One command buffer per pending present: slot 0 carries the composite,
// the chain's shared passes and generated frame 0; each later generated
// frame and the real frame are recorded and submitted on their own, so a
// finished frame reaches the presentation engine without waiting for the
// rest of the chain.
uint32_t cmdSlot(uint32_t k) const { return currentFrame * kMaxPresentsPerFrame + k; }
// Shared chain passes (the 24 shaders every generated frame depends on).
void recordFrameGenProcess(VkCommandBuffer cb);
// Generated frame g: synthesise into a spare composite target and copy it
// into the swapchain image reserved for it.
void recordFrameGenGeneration(VkCommandBuffer cb, uint32_t g);
// Chain cost: a timestamp pair per frame slot, start before the shared
// passes, end after the last generation's compute (before its copy, so a
// vblank wait on the swapchain image is not counted). Read back after the
// slot's fence wait, one frame later.
VkQueryPool fgQueryPool_ = VK_NULL_HANDLE;
bool fgTimestampsOk_ = false;
float fgTimestampPeriodNs_ = 0.0f;
@@ -654,22 +505,21 @@ private:
void readFgQueryResult();
VkFormat offscreenFmt = VK_FORMAT_R8G8B8A8_UNORM;
VkRenderPass offscreenRenderPass = VK_NULL_HANDLE; // CLEAR -> SHADER_READ_ONLY
VkRenderPass offscreenRenderPass = VK_NULL_HANDLE;
VkPipelineLayout postPipeLayout = VK_NULL_HANDLE;
VkPipeline sgsrPipeline = VK_NULL_HANDLE;
VkPipeline nisPipeline = VK_NULL_HANDLE; // NVIDIA Image Scaling (mode 7)
VkPipeline sgsrQualityPipeline = VK_NULL_HANDLE; // SGSR 1 edge-direction (mode 8)
VkPipeline nisPipeline = VK_NULL_HANDLE;
VkPipeline sgsrQualityPipeline = VK_NULL_HANDLE;
VkPipeline easuPipeline = VK_NULL_HANDLE;
VkPipeline rcasPipeline = VK_NULL_HANDLE;
VkPipeline downscalePipeline = VK_NULL_HANDLE;
// CAS/HDR can be either a non-final pass (writes an fx target via
// offscreenRenderPass) or the final pass (writes the swapchain via renderPass);
// a pipeline is render-pass-specific, so keep one variant of each.
VkPipeline casPipelineOff = VK_NULL_HANDLE; // -> fx target (offscreenRenderPass)
VkPipeline casPipelineSwap = VK_NULL_HANDLE; // -> swapchain (renderPass)
VkPipeline casPipelineOff = VK_NULL_HANDLE;
VkPipeline casPipelineSwap = VK_NULL_HANDLE;
VkPipeline hdrPipelineOff = VK_NULL_HANDLE;
VkPipeline hdrPipelineSwap = VK_NULL_HANDLE;
// Phase 2 effects: same Off (-> fx target) / Swap (-> swapchain) variant pair.
VkPipeline fxaaPipelineOff = VK_NULL_HANDLE;
VkPipeline fxaaPipelineSwap = VK_NULL_HANDLE;
VkPipeline toonPipelineOff = VK_NULL_HANDLE;
@@ -680,10 +530,8 @@ private:
VkPipeline ntscPipelineSwap = VK_NULL_HANDLE;
VkPipeline crtPipelineOff = VK_NULL_HANDLE;
VkPipeline crtPipelineSwap = VK_NULL_HANDLE;
// Debanding is always the LAST effect (terminal) -> only a swapchain variant.
VkPipeline debandPipelineSwap = VK_NULL_HANDLE;
// offscreen composite target @ game/container resolution
VkImage offscreenImg = VK_NULL_HANDLE;
VkDeviceMemory offscreenMem = VK_NULL_HANDLE;
VkImageView offscreenView = VK_NULL_HANDLE;
@@ -691,7 +539,6 @@ private:
VkDescriptorSet offscreenDS = VK_NULL_HANDLE;
int offscreenW = 0, offscreenH = 0;
// FSR intermediate (EASU output) @ upscale output resolution
VkImage midImg = VK_NULL_HANDLE;
VkDeviceMemory midMem = VK_NULL_HANDLE;
VkImageView midView = VK_NULL_HANDLE;
@@ -699,8 +546,6 @@ private:
VkDescriptorSet midDS = VK_NULL_HANDLE;
int midW = 0, midH = 0;
// Effect-chain intermediates @ swapchain resolution. fx1 holds the scaled (or
// composited) image fed into CAS/HDR; fx2 ping-pongs when both effects run.
VkImage fx1Img = VK_NULL_HANDLE;
VkDeviceMemory fx1Mem = VK_NULL_HANDLE;
VkImageView fx1View = VK_NULL_HANDLE;
@@ -715,21 +560,18 @@ private:
VkDescriptorSet fx2DS = VK_NULL_HANDLE;
int fx2W = 0, fx2H = 0;
// Per-frame upscale plan, computed in renderFrame, consumed by recordCmdBuf.
// upFrame.mode reuses the upscalerMode enum (3=sgsr,4=fsr,5=fsr_fit,6=sharpen,7=nis,8=sgsr_quality)
// plus an internal sentinel (UPMODE_DOWNSCALE) for the supersampling path.
struct UpscaleFrame {
bool active = false;
int mode = 0;
int outX = 0, outY = 0, outW = 0, outH = 0;
bool cas = false; // run CAS this frame (snapshot of casEnabled)
bool hdr = false; // run HDR this frame (snapshot of hdrEnabled)
bool fxaa = false; // snapshot of fxaaEnabled
bool toon = false; // snapshot of toonEnabled
bool color = false; // snapshot of colorEnabled (non-neutral grade)
bool ntsc = false; // snapshot of ntscEnabled
bool crt = false; // snapshot of crtEnabled
bool deband = false;// snapshot of debandEnabled (terminal dither)
bool cas = false;
bool hdr = false;
bool fxaa = false;
bool toon = false;
bool color = false;
bool ntsc = false;
bool crt = false;
bool deband = false;
SgsrPushConstants sgsrPC{};
NisPushConstants nisPC{};
EasuPushConstants easuPC{};
+295
View File
@@ -0,0 +1,295 @@
#pragma once
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <string>
#include <fstream>
#include <sstream>
namespace ddfg {
struct Config {
bool enabled = false;
bool debug = false;
bool pacing = false;
int model = 4;
int multiplier = 2;
int extraImages = 2;
int perfPreset = 1;
float flowScale = 1.0f;
int gmMode = 0;
int frMode = 0;
int frIters = 4;
float frLambda = 2.0f;
float frDt = 0.25f;
float frEdge = 8.0f;
float frEps = 0.05f;
float beta = 8.0f;
float lambda = 0.6f;
float epsilon = 0.07f;
float photoScale = 7.5f;
float hudX0 = 0.0f;
float hudY0 = 0.0f;
float hudX1 = 0.0f;
float hudY1 = 0.0f;
bool capture = false;
int captureW = 1280;
int captureH = 720;
int capMode = 0;
int capPatches = 3;
int capPatchSize = 256;
float capMotion = 2.0f;
std::string capDir;
int capShardMB = 1024;
void sanitize() {
if (model < 3) model = 3; if (model > 4) model = 4;
if (multiplier < 2) multiplier = 2; if (multiplier > 4) multiplier = 4;
if (extraImages < 0) extraImages = 0; if (extraImages > 8) extraImages = 8;
if (perfPreset < 0 || perfPreset > 2) perfPreset = 1;
if (gmMode < 0) gmMode = 0; if (gmMode > 2) gmMode = 2;
if (frMode < 0) frMode = 0; if (frMode > 2) frMode = 2;
if (frIters < 0) frIters = 0; if (frIters > 16) frIters = 16;
if (frLambda < 0.0f) frLambda = 0.0f;
if (frDt < 0.01f) frDt = 0.01f; if (frDt > 4.0f) frDt = 4.0f;
if (frEdge < 0.0f) frEdge = 0.0f;
if (frEps < 1e-3f) frEps = 1e-3f;
if (flowScale < 0.05f) flowScale = 0.05f; if (flowScale > 4.0f) flowScale = 4.0f;
if (beta < 0.0f) beta = 0.0f; if (lambda < 0.0f) lambda = 0.0f;
if (captureW < 16) captureW = 16; if (captureW > 7680) captureW = 7680;
if (captureH < 16) captureH = 16; if (captureH > 4320) captureH = 4320;
if (capMode < 0) capMode = 0; if (capMode > 1) capMode = 1;
if (capPatches < 1) capPatches = 1; if (capPatches > 16) capPatches = 16;
if (capPatchSize < 16) capPatchSize = 16; if (capPatchSize > 1024) capPatchSize = 1024;
if (capMotion < 0.0f) capMotion = 0.0f;
if (capShardMB < 16) capShardMB = 16; if (capShardMB > 65536) capShardMB = 65536;
}
};
static inline float envf(const char* k, float d) {
const char* v = std::getenv(k); return v ? std::strtof(v, nullptr) : d;
}
static inline int envi(const char* k, int d) {
const char* v = std::getenv(k); return v ? std::atoi(v) : d;
}
static inline int parse_tristate(const std::string& v, int dflt) {
if (v.empty()) return dflt;
if (v == "auto") return 0;
if (v == "on" || v == "1" || v == "force" || v == "true" || v == "yes") return 1;
if (v == "off" || v == "0" || v == "false" || v == "no") return 2;
return dflt;
}
static inline bool parse_bool(const std::string& v, bool dflt) {
int t = parse_tristate(v, dflt ? 1 : 2);
return t == 1;
}
static inline int parse_capmode(const std::string& v, int dflt) {
if (v == "patch" || v == "patches" || v == "0") return 0;
if (v == "frame" || v == "frames" || v == "1") return 1;
return dflt;
}
static inline void apply_toml(Config& c, const std::string& path) {
std::ifstream f(path);
if (!f) return;
std::string line;
while (std::getline(f, line)) {
auto h = line.find('#'); if (h != std::string::npos) line = line.substr(0, h);
auto eq = line.find('='); if (eq == std::string::npos) continue;
auto trim = [](std::string s){
size_t a = s.find_first_not_of(" \t\r\n"); size_t b = s.find_last_not_of(" \t\r\n");
return (a == std::string::npos) ? std::string() : s.substr(a, b - a + 1); };
std::string k = trim(line.substr(0, eq)), v = trim(line.substr(eq + 1));
if (k.empty() || v.empty()) continue;
if (k == "enabled") c.enabled = (v == "1" || v == "true");
else if (k == "debug") c.debug = parse_bool(v, c.debug);
else if (k == "pacing") c.pacing = parse_bool(v, c.pacing);
else if (k == "model") c.model = std::atoi(v.c_str());
else if (k == "multiplier") c.multiplier = std::atoi(v.c_str());
else if (k == "extra_images") c.extraImages = std::atoi(v.c_str());
else if (k == "perf_preset") c.perfPreset = std::atoi(v.c_str());
else if (k == "global_motion") c.gmMode = parse_tristate(v, c.gmMode);
else if (k == "flow_reg") c.frMode = parse_tristate(v, c.frMode);
else if (k == "fr_iters") c.frIters = std::atoi(v.c_str());
else if (k == "fr_lambda") c.frLambda = std::strtof(v.c_str(), nullptr);
else if (k == "fr_dt") c.frDt = std::strtof(v.c_str(), nullptr);
else if (k == "fr_edge") c.frEdge = std::strtof(v.c_str(), nullptr);
else if (k == "fr_eps") c.frEps = std::strtof(v.c_str(), nullptr);
else if (k == "flowScale") c.flowScale = std::strtof(v.c_str(), nullptr);
else if (k == "beta") c.beta = std::strtof(v.c_str(), nullptr);
else if (k == "lambda") c.lambda = std::strtof(v.c_str(), nullptr);
else if (k == "epsilon") c.epsilon = std::strtof(v.c_str(), nullptr);
else if (k == "photoScale") c.photoScale = std::strtof(v.c_str(), nullptr);
else if (k == "capture") c.capture = parse_bool(v, c.capture);
else if (k == "capture_dir") c.capDir = v;
else if (k == "capture_width") c.captureW = std::atoi(v.c_str());
else if (k == "capture_height") c.captureH = std::atoi(v.c_str());
else if (k == "capture_mode") c.capMode = parse_capmode(v, c.capMode);
else if (k == "capture_patches") c.capPatches = std::atoi(v.c_str());
else if (k == "capture_patch_size") c.capPatchSize = std::atoi(v.c_str());
else if (k == "capture_motion") c.capMotion = std::strtof(v.c_str(), nullptr);
else if (k == "capture_shard_mb") c.capShardMB = std::atoi(v.c_str());
else if (k == "hudRect") {
float r[4] = {0,0,0,0}; int n = 0;
size_t start = 0; std::string s = v;
for (; n < 4; ++n) {
size_t comma = s.find(',', start);
std::string tok = s.substr(start, comma == std::string::npos ? std::string::npos : comma - start);
r[n] = std::strtof(tok.c_str(), nullptr);
if (comma == std::string::npos) break;
start = comma + 1;
}
c.hudX0 = r[0]; c.hudY0 = r[1]; c.hudX1 = r[2]; c.hudY1 = r[3];
}
}
}
static inline std::string conf_path() {
if (const char* p = std::getenv("DD_FG_CONF")) return p;
const char* home = std::getenv("HOME");
return home ? std::string(home) + "/.config/dd-fg/conf.toml" : std::string();
}
static inline std::string capture_root(const Config& c) {
if (!c.capDir.empty()) return c.capDir;
if (const char* home = std::getenv("HOME")) return std::string(home) + "/.cache/ddfg-capture";
return std::string("/tmp/ddfg-capture");
}
static inline Config load_config() {
Config c;
c.enabled = envi("DD_FG_ENABLE", 0) != 0;
if (const char* d = std::getenv("DD_FG_DEBUG")) c.debug = parse_bool(d, c.debug);
if (const char* pc = std::getenv("DD_FG_PACING")) c.pacing = parse_bool(pc, c.pacing);
c.model = envi("DD_FG_MODEL", c.model);
c.multiplier = envi("DD_FG_MULT", c.multiplier);
c.extraImages = envi("DD_FG_EXTRA_IMAGES", c.extraImages);
c.perfPreset = envi("DD_FG_PERF_PRESET", c.perfPreset);
if (const char* g = std::getenv("DD_FG_GM")) c.gmMode = parse_tristate(g, c.gmMode);
if (const char* r = std::getenv("DD_FG_FLOWREG")) c.frMode = parse_tristate(r, c.frMode);
c.frIters = envi("DD_FG_FR_ITERS", c.frIters);
c.frLambda = envf("DD_FG_FR_LAMBDA", c.frLambda);
c.frDt = envf("DD_FG_FR_DT", c.frDt);
c.frEdge = envf("DD_FG_FR_EDGE", c.frEdge);
c.frEps = envf("DD_FG_FR_EPS", c.frEps);
c.flowScale = envf("DD_FG_FLOWSCALE", c.flowScale);
c.beta = envf("DD_FG_BETA", c.beta);
c.lambda = envf("DD_FG_LAMBDA", c.lambda);
c.epsilon = envf("DD_FG_EPSILON", c.epsilon);
c.photoScale = envf("DD_FG_PHOTOSCALE", c.photoScale);
if (const char* cc = std::getenv("DD_FG_CAPTURE")) c.capture = parse_bool(cc, c.capture);
c.captureW = envi("DD_FG_CAPTURE_W", c.captureW);
c.captureH = envi("DD_FG_CAPTURE_H", c.captureH);
if (const char* cm = std::getenv("DD_FG_CAPTURE_MODE")) c.capMode = parse_capmode(cm, c.capMode);
c.capPatches = envi("DD_FG_CAPTURE_PATCHES", c.capPatches);
c.capPatchSize = envi("DD_FG_CAPTURE_PATCH", c.capPatchSize);
c.capMotion = envf("DD_FG_CAPTURE_MOTION", c.capMotion);
c.capShardMB = envi("DD_FG_CAPTURE_SHARD_MB", c.capShardMB);
if (const char* cd = std::getenv("DD_FG_CAPTURE_DIR")) c.capDir = cd;
if (const char* h = std::getenv("DD_FG_HUD_RECT")) {
float r[4] = {0,0,0,0}; int n = 0;
std::string s = h; size_t start = 0;
for (; n < 4; ++n) {
size_t comma = s.find(',', start);
r[n] = std::strtof(s.substr(start, comma == std::string::npos ? std::string::npos : comma - start).c_str(), nullptr);
if (comma == std::string::npos) break;
start = comma + 1;
}
c.hudX0 = r[0]; c.hudY0 = r[1]; c.hudX1 = r[2]; c.hudY1 = r[3];
}
const char* home = std::getenv("HOME");
if (home) apply_toml(c, std::string(home) + "/.config/dd-fg/conf.toml");
if (const char* p = std::getenv("DD_FG_CONF")) apply_toml(c, p);
c.sanitize();
return c;
}
struct SynthUBO {
float flowScale, alpha, beta, lambda, epsilon, photoScale, pad0, pad1;
float hudX0, hudY0, hudX1, hudY1;
};
struct FlowUBO { float flowScale; uint32_t level; float occlLo; float occlHi; };
struct GMUBO {
float lin[4];
float linT[4];
float app[4];
float appT[4];
};
struct FlowRegUBO {
float dt;
float lambda;
float edgeAlpha;
float epsTV;
};
}
@@ -1,18 +1,12 @@
// See winfg_engine.h.
#include "winfg_engine.h"
#include "winfg_vkd.h"
#include "ddfg_engine.h"
#include "ddfg_vkd.h"
#include "log.hpp"
#include <algorithm>
#include <cstring>
namespace winfg {
namespace ddfg {
namespace {
// 2x: the guest layer synthesises at 0.35, not 0.5 - biased towards prev to
// cut the double-exposure ghost on HUD/text ("iterative bring-up knob" in
// layer.cpp). Kept for parity, since that is the tuning that was proven.
// 3x/4x: evenly spaced; never exercised by the guest layer, so this is the
// new ground native opens up and the first thing to judge by eye.
float alphaFor(uint32_t k, uint32_t n) {
if (n <= 1) return 0.35f;
return (float)(k + 1) / (float)(n + 1);
@@ -30,25 +24,25 @@ VkImageMemoryBarrier bar(VkImage img, VkImageLayout from, VkImageLayout to,
return b;
}
} // namespace
}
Engine::Engine() = default;
Engine::~Engine() { destroyRing(); fg_.destroy(); }
bool Engine::init(const VkTable& table, VkPhysicalDevice phys, VkDevice dev,
uint32_t queueFamily, VkQueue queue) {
if (!winfgVkdInit(table, dd_, id_)) {
WFG_LOGE("native: dispatch incomplete; frame generation unavailable");
if (!ddfgVkdInit(table, dd_, id_)) {
DDFG_LOGE("native: dispatch incomplete; frame generation unavailable");
return false;
}
phys_ = phys; dev_ = dev; qf_ = queueFamily; queue_ = queue;
id_.GetPhysicalDeviceMemoryProperties(phys_, &memProps_);
if (!fg_.init(&dd_, &id_, phys_, dev_, qf_, queue_)) {
WFG_LOGE("native: FrameGen::init failed");
DDFG_LOGE("native: FrameGen::init failed");
unavailable_ = true;
return false;
}
WFG_LOGI("native: chain ready (10 pipelines, embedded shaders)");
DDFG_LOGI("native: chain ready (10 pipelines, embedded shaders)");
return true;
}
@@ -65,9 +59,9 @@ bool Engine::prepare(uint32_t w, uint32_t h, VkFormat fmt) {
if (unavailable_ || w == 0 || h == 0 || fmt == VK_FORMAT_UNDEFINED) return false;
if (builtW_ == w && builtH_ == h && builtFmt_ == fmt && fg_.valid()) return true;
// onResize rebuilds the pyramids/scratch; our own input ring follows it.
if (!fg_.onResize(VkExtent2D{w, h}, fmt, /*force=*/false)) {
WFG_LOGE("native: onResize %ux%u failed; frame generation unavailable", w, h);
if (!fg_.onResize(VkExtent2D{w, h}, fmt, false)) {
DDFG_LOGE("native: onResize %ux%u failed; frame generation unavailable", w, h);
unavailable_ = true;
return false;
}
@@ -77,27 +71,25 @@ bool Engine::prepare(uint32_t w, uint32_t h, VkFormat fmt) {
}
builtW_ = w; builtH_ = h; builtFmt_ = fmt;
history_ = 0; curr_ = 0; frames_ = 0;
WFG_LOGI("native: built %ux%u fmt=%d model=%d preset=%d flow=%.2f",
DDFG_LOGI("native: built %ux%u fmt=%d model=%d preset=%d flow=%.2f",
w, h, (int)fmt, cfg_.model, cfg_.perfPreset, (double)cfg_.flowScale);
return true;
}
static_assert(kMaxGenerations <= (uint32_t)FrameGen::kSynthSlots, "each generation needs its own synth slot");
uint32_t Engine::plan(uint32_t capacity) const {
if (!valid() || history_ < 2) return 0; // need prev AND curr
if (!valid() || history_ < 2) return 0;
const uint32_t want = (uint32_t)std::max(cfg_.multiplier - 1, 0);
return std::min({want, capacity, kMaxGenerations});
}
void Engine::process(VkCommandBuffer cmd, VkImage source, uint32_t w, uint32_t h,
uint32_t /*generations*/) {
uint32_t ) {
if (!valid()) return;
// Rotate: the slot we are about to overwrite is the OLD prev; after the
// copy it is the new curr and the other slot is prev.
curr_ = 1 - curr_;
Slot& dst = ring_[curr_];
// source is GENERAL (composite render pass leaves it there); the slot may
// be in any layout from last use - UNDEFINED is legal as "discard".
VkImageMemoryBarrier pre[2] = {
bar(source, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT),
@@ -115,8 +107,6 @@ void Engine::process(VkCommandBuffer cmd, VkImage source, uint32_t w, uint32_t h
dd_.CmdCopyImage(cmd, source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
dst.img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
// source back to GENERAL for the real-frame copy that follows; the new
// curr becomes SHADER_READ_ONLY, which is what the chain samples it as.
VkImageMemoryBarrier post[2] = {
bar(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT),
@@ -138,18 +128,13 @@ void Engine::generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t cou
const Slot& curr = ring_[curr_];
if (!prev.valid || !curr.valid) return;
// The chain writes the output through a STORAGE descriptor in GENERAL.
// The composite ring already keeps its targets in GENERAL; make the
// prior contents' writes visible and take ownership for the compute write.
VkImageMemoryBarrier toWrite = bar(targetImage, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT);
dd_.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toWrite);
// gmSlot indexes the C1 global-motion SSBO read back on the CPU next
// frame; kGmSlots (3) >= our frames in flight (2), so rotating per source
// frame keeps a slot from being read while its reduce is still in flight.
fg_.record(cmd, prev.view, curr.view, targetView, alphaFor(generation, count), gmSlot_);
fg_.record(cmd, prev.view, curr.view, targetView, alphaFor(generation, count), gmSlot_,
generation, generation > 0);
if (generation + 1 == count) gmSlot_ = (gmSlot_ + 1) % (uint32_t)FrameGen::kGmSlots;
(void)w; (void)h;
@@ -157,7 +142,6 @@ void Engine::generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t cou
void Engine::reset() { history_ = 0; frames_ = 0; }
// ---- input ring --------------------------------------------------------------
uint32_t Engine::memType(uint32_t bits, VkMemoryPropertyFlags props) const {
for (uint32_t i = 0; i < memProps_.memoryTypeCount; i++)
@@ -201,4 +185,4 @@ void Engine::destroyRing() {
history_ = 0;
}
} // namespace winfg
}
@@ -0,0 +1,69 @@
#pragma once
#include <cstdint>
#include <vulkan/vulkan.h>
#include "framegen.hpp"
#include "vk_dispatch.hpp"
struct VkTable;
namespace ddfg {
constexpr uint32_t kMaxGenerations = 3;
class Engine {
public:
Engine();
~Engine();
Engine(const Engine&) = delete;
Engine& operator=(const Engine&) = delete;
bool init(const VkTable& table, VkPhysicalDevice phys, VkDevice dev,
uint32_t queueFamily, VkQueue queue);
bool valid() const { return fg_.valid() && !unavailable_; }
bool unavailable() const { return unavailable_; }
void configure(uint32_t multiplier, int model, int perfPreset, float flowScale);
bool prepare(uint32_t width, uint32_t height, VkFormat format);
uint32_t plan(uint32_t capacity) const;
void process(VkCommandBuffer cmd, VkImage source, uint32_t width, uint32_t height,
uint32_t generations);
void generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t count,
VkImage targetImage, VkImageView targetView,
uint32_t width, uint32_t height);
void reset();
private:
struct Slot { VkImage img = VK_NULL_HANDLE; VkDeviceMemory mem = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE; bool valid = false; };
bool makeSlot(Slot& s, uint32_t w, uint32_t h, VkFormat fmt);
void destroySlot(Slot& s);
void destroyRing();
uint32_t memType(uint32_t bits, VkMemoryPropertyFlags props) const;
DeviceDispatch dd_{};
InstanceDispatch id_{};
VkPhysicalDevice phys_ = VK_NULL_HANDLE;
VkDevice dev_ = VK_NULL_HANDLE;
uint32_t qf_ = 0;
VkQueue queue_ = VK_NULL_HANDLE;
VkPhysicalDeviceMemoryProperties memProps_{};
FrameGen fg_;
Config cfg_;
bool unavailable_ = false;
Slot ring_[2];
int curr_ = 0;
uint32_t history_ = 0;
uint32_t gmSlot_ = 0;
uint32_t builtW_ = 0, builtH_ = 0;
VkFormat builtFmt_ = VK_FORMAT_UNDEFINED;
uint64_t frames_ = 0;
};
}
@@ -1,15 +1,14 @@
// See winfg_vkd.h.
#include "winfg_vkd.h"
#include "ddfg_vkd.h"
#include "../VulkanRendererContext.h"
#include <android/log.h>
bool winfgVkdInit(const VkTable& table, winfg::DeviceDispatch& dd, winfg::InstanceDispatch& id) {
bool ddfgVkdInit(const VkTable& table, ddfg::DeviceDispatch& dd, ddfg::InstanceDispatch& id) {
bool ok = true;
#define COPY(dst, fn) \
do { \
dst.fn = table.fn; \
if (!dst.fn) { \
__android_log_print(ANDROID_LOG_ERROR, "WinFgVkd", \
__android_log_print(ANDROID_LOG_ERROR, "DdFgVkd", \
"entry point vk" #fn " did not resolve"); \
ok = false; \
} \
@@ -0,0 +1,4 @@
#pragma once
#include "vk_dispatch.hpp"
struct VkTable;
bool ddfgVkdInit(const VkTable& table, ddfg::DeviceDispatch& dd, ddfg::InstanceDispatch& id);
@@ -1,9 +1,7 @@
// GENERATED by tools/embed_spv.py - do not edit by hand.
// win-fg embedded SPIR-V: our optical-flow front end + synthesis only.
#pragma once
#include <cstdint>
#include <cstddef>
namespace winfg { namespace embedded {
namespace ddfg { namespace embedded {
static const uint32_t OF3_LUMA_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000054u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000000du,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00030005u,0x00000009u,0x00646967u,0x00080005u,0x0000000du,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000012u,0x006d6964u,0x00040005u,0x00000016u,0x74734475u,0x00000000u,0x00030005u,0x00000030u,0x00007675u,0x00030005u,0x0000003bu,0x00626772u,0x00040005u,0x0000003fu,0x63725375u,0x00000000u,0x00030005u,0x00000047u,0x00005966u,0x00040047u,0x0000000du,0x0000000bu,0x0000001cu,0x00030047u,0x00000016u,0x00000019u,0x00040047u,0x00000016u,0x00000021u,0x00000030u,0x00040047u,0x00000016u,0x00000022u,0x00000000u,0x00040047u,0x0000003fu,0x00000021u,0x00000020u,0x00040047u,0x0000003fu,0x00000022u,0x00000000u,0x00040047u,0x00000053u,0x0000000bu,0x00000019u,0x00020013u,0x00000002u,0x00030021u,0x00000003u,0x00000002u,0x00040015u,0x00000006u,0x00000020u,0x00000001u,0x00040017u,0x00000007u,0x00000006u,0x00000002u,0x00040020u,0x00000008u,0x00000007u,0x00000007u,0x00040015u,0x0000000au,0x00000020u,0x00000000u,0x00040017u,0x0000000bu,0x0000000au,0x00000003u,0x00040020u,0x0000000cu,0x00000001u,0x0000000bu,0x0004003bu,0x0000000cu,0x0000000du,0x00000001u,0x00040017u,0x0000000eu,0x0000000au,0x00000002u,0x00030016u,0x00000013u,0x00000020u,0x00090019u,0x00000014u,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000002u,0x00000003u,0x00040020u,0x00000015u,0x00000000u,0x00000014u,0x0004003bu,0x00000015u,0x00000016u,0x00000000u,0x00020014u,0x00000019u,0x0004002bu,0x0000000au,0x0000001au,0x00000000u,0x00040020u,0x0000001bu,0x00000007u,0x00000006u,0x0004002bu,0x0000000au,0x00000024u,0x00000001u,0x00040017u,0x0000002eu,0x00000013u,0x00000002u,0x00040020u,0x0000002fu,0x00000007u,0x0000002eu,0x0004002bu,0x00000013u,0x00000033u,0x3f000000u,0x00040017u,0x00000039u,0x00000013u,0x00000003u,0x00040020u,0x0000003au,0x00000007u,0x00000039u,0x00090019u,0x0000003cu,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000001u,0x00000000u,0x0003001bu,0x0000003du,0x0000003cu,0x00040020u,0x0000003eu,0x00000000u,0x0000003du,0x0004003bu,0x0000003eu,0x0000003fu,0x00000000u,0x0004002bu,0x00000013u,0x00000042u,0x00000000u,0x00040017u,0x00000043u,0x00000013u,0x00000004u,0x00040020u,0x00000046u,0x00000007u,0x00000013u,0x0004002bu,0x00000013u,0x00000049u,0x3e59b3d0u,0x0004002bu,0x00000013u,0x0000004au,0x3f371759u,0x0004002bu,0x00000013u,0x0000004bu,0x3d93dd98u,0x0006002cu,0x00000039u,0x0000004cu,0x00000049u,0x0000004au,0x0000004bu,0x0004002bu,0x0000000au,0x00000052u,0x00000010u,0x0006002cu,0x0000000bu,0x00000053u,0x00000052u,0x00000052u,0x00000024u,0x00050036u,0x00000002u,0x00000004u,0x00000000u,0x00000003u,0x000200f8u,0x00000005u,0x0004003bu,0x00000008u,0x00000009u,0x00000007u,0x0004003bu,0x00000008u,0x00000012u,0x00000007u,0x0004003bu,0x0000002fu,0x00000030u,0x00000007u,0x0004003bu,0x0000003au,0x0000003bu,0x00000007u,0x0004003bu,0x00000046u,0x00000047u,0x00000007u,0x0004003du,0x0000000bu,0x0000000fu,0x0000000du,0x0007004fu,0x0000000eu,0x00000010u,0x0000000fu,0x0000000fu,0x00000000u,0x00000001u,0x0004007cu,0x00000007u,0x00000011u,0x00000010u,0x0003003eu,0x00000009u,0x00000011u,0x0004003du,0x00000014u,0x00000017u,0x00000016u,0x00040068u,0x00000007u,0x00000018u,0x00000017u,0x0003003eu,0x00000012u,0x00000018u,0x00050041u,0x0000001bu,0x0000001cu,0x00000009u,0x0000001au,0x0004003du,0x00000006u,0x0000001du,0x0000001cu,0x00050041u,0x0000001bu,0x0000001eu,0x00000012u,0x0000001au,0x0004003du,0x00000006u,0x0000001fu,0x0000001eu,0x000500afu,0x00000019u,0x00000020u,0x0000001du,0x0000001fu,0x000400a8u,0x00000019u,0x00000021u,0x00000020u,0x000300f7u,0x00000023u,0x00000000u,0x000400fau,0x00000021u,0x00000022u,0x00000023u,0x000200f8u,0x00000022u,0x00050041u,0x0000001bu,0x00000025u,0x00000009u,0x00000024u,0x0004003du,0x00000006u,0x00000026u,0x00000025u,0x00050041u,0x0000001bu,0x00000027u,0x00000012u,0x00000024u,0x0004003du,0x00000006u,0x00000028u,0x00000027u,0x000500afu,0x00000019u,0x00000029u,0x00000026u,0x00000028u,0x000200f9u,0x00000023u,0x000200f8u,0x00000023u,0x000700f5u,0x00000019u,0x0000002au,0x00000020u,0x00000005u,0x00000029u,0x00000022u,0x000300f7u,0x0000002cu,0x00000000u,0x000400fau,0x0000002au,0x0000002bu,0x0000002cu,0x000200f8u,0x0000002bu,0x000100fdu,0x000200f8u,0x0000002cu,0x0004003du,0x00000007u,0x00000031u,0x00000009u,0x0004006fu,0x0000002eu,0x00000032u,0x00000031u,0x00050050u,0x0000002eu,0x00000034u,0x00000033u,0x00000033u,0x00050081u,0x0000002eu,0x00000035u,0x00000032u,0x00000034u,0x0004003du,0x00000007u,0x00000036u,0x00000012u,0x0004006fu,0x0000002eu,0x00000037u,0x00000036u,0x00050088u,0x0000002eu,0x00000038u,0x0Line truncated
static const size_t OF3_LUMA_LEN = 481;
static const uint32_t OF3_DOWNSAMPLE_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x0000008au,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000000du,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00030005u,0x00000009u,0x00646967u,0x00080005u,0x0000000du,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000012u,0x006d6964u,0x00040005u,0x00000016u,0x74734475u,0x00000000u,0x00040005u,0x00000030u,0x44637273u,0x00006d69u,0x00040005u,0x00000034u,0x63725375u,0x00000000u,0x00040005u,0x0000003au,0x65736162u,0x00000000u,0x00030005u,0x00000044u,0x00000068u,0x00030005u,0x0000004au,0x00000061u,0x00030005u,0x00000059u,0x00000062u,0x00030005u,0x00000065u,0x00000063u,0x00030005u,0x00000071u,0x00000064u,0x00040047u,0x0000000du,0x0000000bu,0x0000001cu,0x00030047u,0x00000016u,0x00000019u,0x00040047u,0x00000016u,0x00000021u,0x00000030u,0x00040047u,0x00000016u,0x00000022u,0x00000000u,0x00040047u,0x00000034u,0x00000021u,0x00000020u,0x00040047u,0x00000034u,0x00000022u,0x00000000u,0x00040047u,0x00000089u,0x0000000bu,0x00000019u,0x00020013u,0x00000002u,0x00030021u,0x00000003u,0x00000002u,0x00040015u,0x00000006u,0x00000020u,0x00000001u,0x00040017u,0x00000007u,0x00000006u,0x00000002u,0x00040020u,0x00000008u,0x00000007u,0x00000007u,0x00040015u,0x0000000au,0x00000020u,0x00000000u,0x00040017u,0x0000000bu,0x0000000au,0x00000003u,0x00040020u,0x0000000cu,0x00000001u,0x0000000bu,0x0004003bu,0x0000000cu,0x0000000du,0x00000001u,0x00040017u,0x0000000eu,0x0000000au,0x00000002u,0x00030016u,0x00000013u,0x00000020u,0x00090019u,0x00000014u,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000002u,0x00000003u,0x00040020u,0x00000015u,0x00000000u,0x00000014u,0x0004003bu,0x00000015u,0x00000016u,0x00000000u,0x00020014u,0x00000019u,0x0004002bu,0x0000000au,0x0000001au,0x00000000u,0x00040020u,0x0000001bu,0x00000007u,0x00000006u,0x0004002bu,0x0000000au,0x00000024u,0x00000001u,0x00040017u,0x0000002eu,0x00000013u,0x00000002u,0x00040020u,0x0000002fu,0x00000007u,0x0000002eu,0x00090019u,0x00000031u,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000001u,0x00000000u,0x0003001bu,0x00000032u,0x00000031u,0x00040020u,0x00000033u,0x00000000u,0x00000032u,0x0004003bu,0x00000033u,0x00000034u,0x00000000u,0x0004002bu,0x00000006u,0x00000036u,0x00000000u,0x0004002bu,0x00000013u,0x0000003du,0x40000000u,0x0004002bu,0x00000013u,0x0000003fu,0x3f800000u,0x0004002bu,0x00000013u,0x00000045u,0x3f000000u,0x00040020u,0x00000049u,0x00000007u,0x00000013u,0x0004002bu,0x00000013u,0x00000055u,0x00000000u,0x00040017u,0x00000056u,0x00000013u,0x00000004u,0x0004002bu,0x00000013u,0x00000085u,0x3e800000u,0x0004002bu,0x0000000au,0x00000088u,0x00000010u,0x0006002cu,0x0000000bu,0x00000089u,0x00000088u,0x00000088u,0x00000024u,0x00050036u,0x00000002u,0x00000004u,0x00000000u,0x00000003u,0x000200f8u,0x00000005u,0x0004003bu,0x00000008u,0x00000009u,0x00000007u,0x0004003bu,0x00000008u,0x00000012u,0x00000007u,0x0004003bu,0x0000002fu,0x00000030u,0x00000007u,0x0004003bu,0x0000002fu,0x0000003au,0x00000007u,0x0004003bu,0x0000002fu,0x00000044u,0x00000007u,0x0004003bu,0x00000049u,0x0000004au,0x00000007u,0x0004003bu,0x00000049u,0x00000059u,0x00000007u,0x0004003bu,0x00000049u,0x00000065u,0x00000007u,0x0004003bu,0x00000049u,0x00000071u,0x00000007u,0x0004003du,0x0000000bu,0x0000000fu,0x0000000du,0x0007004fu,0x0000000eu,0x00000010u,0x0000000fu,0x0000000fu,0x00000000u,0x00000001u,0x0004007cu,0x00000007u,0x00000011u,0x00000010u,0x0003003eu,0x00000009u,0x00000011u,0x0004003du,0x00000014u,0x00000017u,0x00000016u,0x00040068u,0x00000007u,0x00000018u,0x00000017u,0x0003003eu,0x00000012u,0x00000018u,0x00050041u,0x0000001bu,0x0000001cu,0x00000009u,0x0000001au,0x0004003du,0x00000006u,0x0000001du,0x0000001cu,0x00050041u,0x0000001bu,0x0000001eu,0x00000012u,0x0000001au,0x0004003du,0x00000006u,0x0000001fu,0x0000001eu,0x000500afu,0x00000019u,0x00000020u,0x0000001du,0x0000001fu,0x000400a8u,0x00000019u,0x00000021u,0x00000020u,0x000300f7u,0x00000023u,0x00000000u,0x000400fau,0x00000021u,0x00000022u,0x00000023u,0x000200f8u,0x00000022u,0x00050041u,0x0000001bu,0x00000025u,0x00000009u,0x00000024u,0x0004003du,0x00000006u,0x00000026u,0x00000025u,0x00050041u,0x0000001bu,0x00000027u,0x00000012u,0x00000024u,0x0004003du,0x00000006u,0x00000028u,0x00000027u,0x000500afu,0x00000019u,0x00000029u,0x00000026u,0x00000028u,0x000200f9u,0x00000023u,0x000200f8u,0x00000023u,0x000700f5u,0x00000019u,0x0000002au,0x00000020u,0x00000005u,0x00000029u,0x00000022u,0x000300f7u,0x0000002cu,0x00000000u,0x000400fau,0x0000002au,0x0000002bu,0x0000002cu,0x000200f8u,0x0000002bu,0x000100fdu,0x000200f8u,0x0000002cu,0x0004003du,0x00000032u,0x00000035u,0x00000034u,0x00040064u,0x00000031u,0x00000037u,0x00000035u,0x0005006Line truncated
@@ -22,10 +20,10 @@ static const uint32_t OF3_GM_PREWARP_SPV[] = {0x07230203u,0x00010300u,0x0008000b
static const size_t OF3_GM_PREWARP_LEN = 706;
static const uint32_t OF3_FLOWREG_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000144u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x00000037u,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00060005u,0x0000000eu,0x63746566u,0x76285568u,0x763b3269u,0x003b3269u,0x00030005u,0x0000000cu,0x00000070u,0x00030005u,0x0000000du,0x006d6964u,0x00060005u,0x00000013u,0x63746566u,0x76284c68u,0x763b3269u,0x003b3269u,0x00030005u,0x00000011u,0x00000070u,0x00030005u,0x00000012u,0x006d6964u,0x00040005u,0x00000018u,0x6f6c4675u,0x006e4977u,0x00040005u,0x00000026u,0x6d754c75u,0x00000061u,0x00030005u,0x00000034u,0x00646967u,0x00080005u,0x00000037u,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x0000003cu,0x006d6964u,0x00030005u,0x00000055u,0x00007464u,0x00030005u,0x00000056u,0x00000055u,0x00050006u,0x00000056u,0x00000000u,0x61726170u,0x0000736du,0x00030005u,0x00000058u,0x00000000u,0x00040005u,0x0000005cu,0x626d616cu,0x00006164u,0x00050005u,0x0000005fu,0x65676465u,0x68706c41u,0x00000061u,0x00040005u,0x00000063u,0x54737065u,0x00000056u,0x00030005u,0x00000068u,0x00004375u,0x00040005u,0x00000069u,0x61726170u,0x0000006du,0x00040005u,0x0000006bu,0x61726170u,0x0000006du,0x00030005u,0x0000006eu,0x00004c75u,0x00040005u,0x00000073u,0x61726170u,0x0000006du,0x00040005u,0x00000074u,0x61726170u,0x0000006du,0x00030005u,0x00000077u,0x00005275u,0x00040005u,0x0000007bu,0x61726170u,0x0000006du,0x00040005u,0x0000007cu,0x61726170u,0x0000006du,0x00030005u,0x0000007fu,0x00005575u,0x00040005u,0x00000083u,0x61726170u,0x0000006du,0x00040005u,0x00000084u,0x61726170u,0x0000006du,0x00030005u,0x00000087u,0x00004475u,0x00040005u,0x0000008bu,0x61726170u,0x0000006du,0x00040005u,0x0000008cu,0x61726170u,0x0000006du,0x00040005u,0x0000008fu,0x6d69646cu,0x00000000u,0x00030005u,0x00000093u,0x00004c6cu,0x00040005u,0x00000096u,0x61726170u,0x0000006du,0x00040005u,0x00000097u,0x61726170u,0x0000006du,0x00030005u,0x0000009au,0x0000526cu,0x00040005u,0x0000009du,0x61726170u,0x0000006du,0x00040005u,0x0000009eu,0x61726170u,0x0000006du,0x00030005u,0x000000a1u,0x0000556cu,0x00040005u,0x000000a4u,0x61726170u,0x0000006du,0x00040005u,0x000000a5u,0x61726170u,0x0000006du,0x00030005u,0x000000a8u,0x0000446cu,0x00040005u,0x000000abu,0x61726170u,0x0000006du,0x00040005u,0x000000acu,0x61726170u,0x0000006du,0x00040005u,0x000000afu,0x67616d67u,0x00000000u,0x00030005u,0x000000bbu,0x00000067u,0x00030005u,0x000000c4u,0x00004c64u,0x00030005u,0x000000c8u,0x00005264u,0x00030005u,0x000000ccu,0x00005564u,0x00030005u,0x000000d0u,0x00004464u,0x00030005u,0x000000d4u,0x00003265u,0x00030005u,0x000000d8u,0x00004c77u,0x00030005u,0x000000e1u,0x00005277u,0x00030005u,0x000000eau,0x00005577u,0x00030005u,0x000000f3u,0x00004477u,0x00040005u,0x000000fcu,0x576d7573u,0x00000000u,0x00040005u,0x00000104u,0x576d7573u,0x00000055u,0x00040005u,0x00000114u,0x66694475u,0x00000066u,0x00030005u,0x00000120u,0x00003066u,0x00050005u,0x00000121u,0x6f6c4675u,0x73624f77u,0x00000000u,0x00040005u,0x0000012au,0x74656874u,0x00000061u,0x00030005u,0x0000012eu,0x00000072u,0x00040005u,0x00000132u,0x77654e75u,0x00000000u,0x00050005u,0x0000013eu,0x6f6c4675u,0x74754f77u,0x00000000u,0x00040047u,0x00000018u,0x00000021u,0x00000020u,0x00040047u,0x00000018u,0x00000022u,0x00000000u,0x00040047u,0x00000026u,0x00000021u,0x00000022u,0x00040047u,0x00000026u,0x00000022u,0x00000000u,0x00040047u,0x00000037u,0x0000000bu,0x0000001cu,0x00030047u,0x00000056u,0x00000002u,0x00050048u,0x00000056u,0x00000000u,0x00000023u,0x00000000u,0x00040047u,0x00000058u,0x00000021u,0x00000000u,0x00040047u,0x00000058u,0x00000022u,0x00000000u,0x00040047u,0x00000121u,0x00000021u,0x00000021u,0x00040047u,0x00000121u,0x00000022u,0x00000000u,0x00030047u,0x0000013eu,0x00000019u,0x00040047u,0x0000013eu,0x00000021u,0x00000030u,0x00040047u,0x0000013eu,0x00000022u,0x00000000u,0x00040047u,0x00000143u,0x0000000bu,0x00000019u,0x00020013u,0x00000002u,0x00030021u,0x00000003u,0x00000002u,0x00040015u,0x00000006u,0x00000020u,0x00000001u,0x00040017u,0x00000007u,0x00000006u,0x00000002u,0x00040020u,0x00000008u,0x00000007u,0x00000007u,0x00030016u,0x00000009u,0x00000020u,0x00040017u,0x0000000au,0x00000009u,0x00000004u,0x00050021u,0x0000000bu,0x0000000au,0x00000008u,0x00000008u,0x00050021u,0x00000010u,0x00000009u,0x00000008u,0x00000008u,0x00090019u,0x00000015u,0x00000009u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000001u,0x00000000u,0x0003001bu,0x00000016u,0x00000015u,0x00040020u,0x00000017u,0x00000000u,0x00000016u,0x0004003bu,0x00000017u,0x00000018u,0x00000000u,0x0004002bu,0x00000006u,0x0000001bu,0x00000000u,0x0005002cu,0x00000007u,0x0000001cu,0x0000001bu,0x0000001bu,0x0004002bu,0x00000006u,0x0000001eu,0x00000001u,0x0004003bu,0x00000017u,0x00000026u,0x00000000u,Line truncated
static const size_t OF3_FLOWREG_LEN = 1936;
static const uint32_t WFG_SYNTH_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000208u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000001cu,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00050005u,0x0000000bu,0x616d756cu,0x33667628u,0x0000003bu,0x00030005u,0x0000000au,0x00000063u,0x00030005u,0x00000018u,0x00646967u,0x00080005u,0x0000001cu,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000021u,0x006d6964u,0x00040005u,0x00000024u,0x74754f75u,0x00000000u,0x00040005u,0x0000003eu,0x44766e69u,0x00006d69u,0x00030005u,0x00000044u,0x00007675u,0x00030005u,0x0000004du,0x00000055u,0x00060006u,0x0000004du,0x00000000u,0x776f6c66u,0x6c616353u,0x00000065u,0x00050006u,0x0000004du,0x00000001u,0x68706c61u,0x00000061u,0x00050006u,0x0000004du,0x00000002u,0x61746562u,0x00000000u,0x00050006u,0x0000004du,0x00000003u,0x626d616cu,0x00006164u,0x00050006u,0x0000004du,0x00000004u,0x69737065u,0x006e6f6cu,0x00060006u,0x0000004du,0x00000005u,0x746f6870u,0x6163536fu,0x0000656cu,0x00050006u,0x0000004du,0x00000006u,0x30646170u,0x00000000u,0x00050006u,0x0000004du,0x00000007u,0x31646170u,0x00000000u,0x00050006u,0x0000004du,0x00000008u,0x52647568u,0x00746365u,0x00030005u,0x0000004fu,0x00000000u,0x00040005u,0x0000008cu,0x72754375u,0x00000072u,0x00030005u,0x00000098u,0x00676264u,0x00030005u,0x0000009fu,0x00007366u,0x00040005u,0x000000a9u,0x64774273u,0x00000000u,0x00050005u,0x000000aau,0x6f6c4675u,0x64774277u,0x00000000u,0x00040005u,0x000000aeu,0x64774673u,0x00000000u,0x00050005u,0x000000afu,0x6f6c4675u,0x64774677u,0x00000000u,0x00030005u,0x000000b3u,0x00313046u,0x00030005u,0x000000bau,0x00303146u,0x00040005u,0x000000c1u,0x31306663u,0x00000000u,0x00040005u,0x000000c5u,0x30316663u,0x00000000u,0x00030005u,0x000000c9u,0x0031306du,0x00030005u,0x000000d5u,0x0030316du,0x00030005u,0x000000e0u,0x00004170u,0x00030005u,0x000000e8u,0x00004270u,0x00030005u,0x000000f0u,0x00003043u,0x00040005u,0x000000f1u,0x65725075u,0x00000076u,0x00030005u,0x000000f6u,0x00003143u,0x00030005u,0x000000fbu,0x00004165u,0x00040005u,0x000000fcu,0x61726170u,0x0000006du,0x00040005u,0x0000010cu,0x61726170u,0x0000006du,0x00030005u,0x00000111u,0x00004265u,0x00040005u,0x00000112u,0x61726170u,0x0000006du,0x00040005u,0x00000122u,0x61726170u,0x0000006du,0x00040005u,0x00000127u,0x76556266u,0x00000000u,0x00040005u,0x0000012bu,0x65526266u,0x00646973u,0x00040005u,0x00000138u,0x736e6f63u,0x00747369u,0x00030005u,0x00000149u,0x0000305au,0x00030005u,0x00000157u,0x0000315au,0x00040005u,0x00000163u,0x30756174u,0x00000000u,0x00040005u,0x00000167u,0x31756174u,0x00000000u,0x00040005u,0x0000016au,0x78616d7au,0x00000000u,0x00030005u,0x0000016eu,0x00003057u,0x00030005u,0x00000179u,0x00003157u,0x00040005u,0x00000183u,0x6d757357u,0x00000000u,0x00040005u,0x00000187u,0x70726177u,0x00006465u,0x00050005u,0x000001a0u,0x736f7263u,0x64616673u,0x00000065u,0x00050005u,0x000001adu,0x746f6870u,0x7272456fu,0x00000000u,0x00050005u,0x000001b1u,0x746f6870u,0x7572546fu,0x00007473u,0x00040005u,0x000001b7u,0x73757274u,0x00000074u,0x00040005u,0x000001c3u,0x76657270u,0x00000043u,0x00040005u,0x000001c8u,0x72727563u,0x00000043u,0x00050005u,0x000001cdu,0x746e6f63u,0x44746e65u,0x00666669u,0x00050005u,0x000001d2u,0x72746e75u,0x65747375u,0x00000064u,0x00040005u,0x000001d7u,0x44676962u,0x00666669u,0x00050005u,0x000001dcu,0x70616e73u,0x75436f54u,0x00007272u,0x00040005u,0x000001e5u,0x6374756fu,0x00000000u,0x00030005u,0x000001eeu,0x0067616du,0x00030005u,0x000001f2u,0x0000006eu,0x00030005u,0x000001f6u,0x00746173u,0x00040047u,0x0000001cu,0x0000000bu,0x0000001cu,0x00030047u,0x00000024u,0x00000019u,0x00040047u,0x00000024u,0x00000021u,0x00000030u,0x00040047u,0x00000024u,0x00000022u,0x00000000u,0x00030047u,0x0000004du,0x00000002u,0x00050048u,0x0000004du,0x00000000u,0x00000023u,0x00000000u,0x00050048u,0x0000004du,0x00000001u,0x00000023u,0x00000004u,0x00050048u,0x0000004du,0x00000002u,0x00000023u,0x00000008u,0x00050048u,0x0000004du,0x00000003u,0x00000023u,0x0000000cu,0x00050048u,0x0000004du,0x00000004u,0x00000023u,0x00000010u,0x00050048u,0x0000004du,0x00000005u,0x00000023u,0x00000014u,0x00050048u,0x0000004du,0x00000006u,0x00000023u,0x00000018u,0x00050048u,0x0000004du,0x00000007u,0x00000023u,0x0000001cu,0x00050048u,0x0000004du,0x00000008u,0x00000023u,0x00000020u,0x00040047u,0x0000004fu,0x00000021u,0x00000000u,0x00040047u,0x0000004fu,0x00000022u,0x00000000u,0x00040047u,0x0000008cu,0x00000021u,0x00000021u,0x00040047u,0x0000008cu,0x00000022u,0x00000000u,0x00040047u,0x000000aau,0x00000021u,0x00000023u,0x00040047u,0x000000aau,0x00000022u,0x00000000u,0x00040047u,0x000000afu,0x00000021u,0x00000022u,0x00040047u,0x000000afu,0x00000022u,0x00000000u,0x00040047u,0x000000f1u,0x00000021u,0x00000020u,0x00040047u,0x000000f1u,0xLine truncated
static const size_t WFG_SYNTH_LEN = 3082;
static const uint32_t DDFG_SYNTH_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000208u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000001cu,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00050005u,0x0000000bu,0x616d756cu,0x33667628u,0x0000003bu,0x00030005u,0x0000000au,0x00000063u,0x00030005u,0x00000018u,0x00646967u,0x00080005u,0x0000001cu,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000021u,0x006d6964u,0x00040005u,0x00000024u,0x74754f75u,0x00000000u,0x00040005u,0x0000003eu,0x44766e69u,0x00006d69u,0x00030005u,0x00000044u,0x00007675u,0x00030005u,0x0000004du,0x00000055u,0x00060006u,0x0000004du,0x00000000u,0x776f6c66u,0x6c616353u,0x00000065u,0x00050006u,0x0000004du,0x00000001u,0x68706c61u,0x00000061u,0x00050006u,0x0000004du,0x00000002u,0x61746562u,0x00000000u,0x00050006u,0x0000004du,0x00000003u,0x626d616cu,0x00006164u,0x00050006u,0x0000004du,0x00000004u,0x69737065u,0x006e6f6cu,0x00060006u,0x0000004du,0x00000005u,0x746f6870u,0x6163536fu,0x0000656cu,0x00050006u,0x0000004du,0x00000006u,0x30646170u,0x00000000u,0x00050006u,0x0000004du,0x00000007u,0x31646170u,0x00000000u,0x00050006u,0x0000004du,0x00000008u,0x52647568u,0x00746365u,0x00030005u,0x0000004fu,0x00000000u,0x00040005u,0x0000008cu,0x72754375u,0x00000072u,0x00030005u,0x00000098u,0x00676264u,0x00030005u,0x0000009fu,0x00007366u,0x00040005u,0x000000a9u,0x64774273u,0x00000000u,0x00050005u,0x000000aau,0x6f6c4675u,0x64774277u,0x00000000u,0x00040005u,0x000000aeu,0x64774673u,0x00000000u,0x00050005u,0x000000afu,0x6f6c4675u,0x64774677u,0x00000000u,0x00030005u,0x000000b3u,0x00313046u,0x00030005u,0x000000bau,0x00303146u,0x00040005u,0x000000c1u,0x31306663u,0x00000000u,0x00040005u,0x000000c5u,0x30316663u,0x00000000u,0x00030005u,0x000000c9u,0x0031306du,0x00030005u,0x000000d5u,0x0030316du,0x00030005u,0x000000e0u,0x00004170u,0x00030005u,0x000000e8u,0x00004270u,0x00030005u,0x000000f0u,0x00003043u,0x00040005u,0x000000f1u,0x65725075u,0x00000076u,0x00030005u,0x000000f6u,0x00003143u,0x00030005u,0x000000fbu,0x00004165u,0x00040005u,0x000000fcu,0x61726170u,0x0000006du,0x00040005u,0x0000010cu,0x61726170u,0x0000006du,0x00030005u,0x00000111u,0x00004265u,0x00040005u,0x00000112u,0x61726170u,0x0000006du,0x00040005u,0x00000122u,0x61726170u,0x0000006du,0x00040005u,0x00000127u,0x76556266u,0x00000000u,0x00040005u,0x0000012bu,0x65526266u,0x00646973u,0x00040005u,0x00000138u,0x736e6f63u,0x00747369u,0x00030005u,0x00000149u,0x0000305au,0x00030005u,0x00000157u,0x0000315au,0x00040005u,0x00000163u,0x30756174u,0x00000000u,0x00040005u,0x00000167u,0x31756174u,0x00000000u,0x00040005u,0x0000016au,0x78616d7au,0x00000000u,0x00030005u,0x0000016eu,0x00003057u,0x00030005u,0x00000179u,0x00003157u,0x00040005u,0x00000183u,0x6d757357u,0x00000000u,0x00040005u,0x00000187u,0x70726177u,0x00006465u,0x00050005u,0x000001a0u,0x736f7263u,0x64616673u,0x00000065u,0x00050005u,0x000001adu,0x746f6870u,0x7272456fu,0x00000000u,0x00050005u,0x000001b1u,0x746f6870u,0x7572546fu,0x00007473u,0x00040005u,0x000001b7u,0x73757274u,0x00000074u,0x00040005u,0x000001c3u,0x76657270u,0x00000043u,0x00040005u,0x000001c8u,0x72727563u,0x00000043u,0x00050005u,0x000001cdu,0x746e6f63u,0x44746e65u,0x00666669u,0x00050005u,0x000001d2u,0x72746e75u,0x65747375u,0x00000064u,0x00040005u,0x000001d7u,0x44676962u,0x00666669u,0x00050005u,0x000001dcu,0x70616e73u,0x75436f54u,0x00007272u,0x00040005u,0x000001e5u,0x6374756fu,0x00000000u,0x00030005u,0x000001eeu,0x0067616du,0x00030005u,0x000001f2u,0x0000006eu,0x00030005u,0x000001f6u,0x00746173u,0x00040047u,0x0000001cu,0x0000000bu,0x0000001cu,0x00030047u,0x00000024u,0x00000019u,0x00040047u,0x00000024u,0x00000021u,0x00000030u,0x00040047u,0x00000024u,0x00000022u,0x00000000u,0x00030047u,0x0000004du,0x00000002u,0x00050048u,0x0000004du,0x00000000u,0x00000023u,0x00000000u,0x00050048u,0x0000004du,0x00000001u,0x00000023u,0x00000004u,0x00050048u,0x0000004du,0x00000002u,0x00000023u,0x00000008u,0x00050048u,0x0000004du,0x00000003u,0x00000023u,0x0000000cu,0x00050048u,0x0000004du,0x00000004u,0x00000023u,0x00000010u,0x00050048u,0x0000004du,0x00000005u,0x00000023u,0x00000014u,0x00050048u,0x0000004du,0x00000006u,0x00000023u,0x00000018u,0x00050048u,0x0000004du,0x00000007u,0x00000023u,0x0000001cu,0x00050048u,0x0000004du,0x00000008u,0x00000023u,0x00000020u,0x00040047u,0x0000004fu,0x00000021u,0x00000000u,0x00040047u,0x0000004fu,0x00000022u,0x00000000u,0x00040047u,0x0000008cu,0x00000021u,0x00000021u,0x00040047u,0x0000008cu,0x00000022u,0x00000000u,0x00040047u,0x000000aau,0x00000021u,0x00000023u,0x00040047u,0x000000aau,0x00000022u,0x00000000u,0x00040047u,0x000000afu,0x00000021u,0x00000022u,0x00040047u,0x000000afu,0x00000022u,0x00000000u,0x00040047u,0x000000f1u,0x00000021u,0x00000020u,0x00040047u,0x000000f1u,0Line truncated
static const size_t DDFG_SYNTH_LEN = 3082;
struct Blob { const uint32_t* code; size_t words; };
enum Shader { OF3_LUMA, OF3_DOWNSAMPLE, OF3_FLOW, OF3_EXPAND, OF3_FLOW_M4, OF3_EXPAND_M4, OF3_GM_REDUCE, OF3_GM_PREWARP, OF3_FLOWREG, WFG_SYNTH, SHADER_COUNT };
enum Shader { OF3_LUMA, OF3_DOWNSAMPLE, OF3_FLOW, OF3_EXPAND, OF3_FLOW_M4, OF3_EXPAND_M4, OF3_GM_REDUCE, OF3_GM_PREWARP, OF3_FLOWREG, DDFG_SYNTH, SHADER_COUNT };
static inline Blob blob(Shader s) {
switch (s) {
case OF3_LUMA: return { OF3_LUMA_SPV, OF3_LUMA_LEN };
@@ -37,8 +35,8 @@ static inline Blob blob(Shader s) {
case OF3_GM_REDUCE: return { OF3_GM_REDUCE_SPV, OF3_GM_REDUCE_LEN };
case OF3_GM_PREWARP: return { OF3_GM_PREWARP_SPV, OF3_GM_PREWARP_LEN };
case OF3_FLOWREG: return { OF3_FLOWREG_SPV, OF3_FLOWREG_LEN };
case WFG_SYNTH: return { WFG_SYNTH_SPV, WFG_SYNTH_LEN };
case DDFG_SYNTH: return { DDFG_SYNTH_SPV, DDFG_SYNTH_LEN };
default: return { nullptr, 0 };
}
}
} } // namespace winfg::embedded
} }
@@ -1,13 +1,12 @@
// win-fg - compute frame-generation engine implementation.
#include "framegen.hpp"
#include "embedded_shaders.hpp"
#include "log.hpp"
#include <cmath>
#include <cstdio>
namespace winfg {
namespace ddfg {
#define VKOK(x) do { if ((x) != VK_SUCCESS) { WFG_LOGE("vk fail: %s", #x); return false; } } while (0)
#define VKOK(x) do { if ((x) != VK_SUCCESS) { DDFG_LOGE("vk fail: %s", #x); return false; } } while (0)
uint32_t FrameGen::findMemType(uint32_t bits, VkMemoryPropertyFlags props) const {
for (uint32_t i = 0; i < memProps_.memoryTypeCount; ++i)
@@ -49,10 +48,6 @@ void FrameGen::destroyImage(Img& i) {
i = Img{};
}
// C1: allocate the per-slot host-visible SSBOs the LK reduce writes partials into.
// Host-visible + coherent so the CPU can read them back after the owning frame's
// fence signals (no staging copy needed; the buffers are tiny - kGmThreads*kGmStride
// floats each, ~64 KB). Persistently mapped.
bool FrameGen::makeGmBuffers() {
const VkDeviceSize sz = (VkDeviceSize)kGmThreads * kGmStride * sizeof(float);
const VkMemoryPropertyFlags want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
@@ -75,8 +70,6 @@ bool FrameGen::makeGmBuffers() {
bool FrameGen::makePipe(Pipe& p, int shaderId,
const std::vector<VkDescriptorType>&) {
// binding layout is derived per shader below in init(); here we only build
// the module + a generic layout supplied by caller via setLayout already set.
embedded::Blob b = embedded::blob((embedded::Shader)shaderId);
VkShaderModuleCreateInfo mi{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
mi.codeSize = b.words * 4; mi.pCode = b.code;
@@ -101,7 +94,6 @@ void FrameGen::destroyPipe(Pipe& p) {
p = Pipe{};
}
// Build a descriptor-set layout from an explicit (binding, type) list.
static bool buildSetLayout(const DeviceDispatch* dd, VkDevice dev,
const std::vector<std::pair<uint32_t, VkDescriptorType>>& binds,
VkDescriptorSetLayout& out) {
@@ -123,12 +115,11 @@ bool FrameGen::init(const DeviceDispatch* dd, const InstanceDispatch* id,
si.addressModeU = si.addressModeV = si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
VKOK(dd_->CreateSampler(device_, &si, nullptr, &linear_));
// descriptor pool sized generously for our per-frame sets.
VkDescriptorPoolSize sizes[] = {
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 256},
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 256},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 64},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16}, // C1 LK-reduce SSBOs
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16},
};
VkDescriptorPoolCreateInfo pci{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
pci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
@@ -141,19 +132,15 @@ bool FrameGen::init(const DeviceDispatch* dd, const InstanceDispatch* id,
const auto U = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
const auto SB = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
// per-shader binding sets (match the GLSL layout(binding=...) declarations)
if (!buildSetLayout(dd_, dev, {B{32,S},B{48,W}}, pLuma_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{32,S},B{48,W}}, pDown_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{48,W}}, pFlow_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{48,W}}, pFlowM4_.setLayout)) return false;
// C1: expand sets gain a second UBO (binding 1 = GMUBO) for the global-motion add-back.
if (!buildSetLayout(dd_, dev, {B{0,U},B{1,U},B{32,S},B{48,W},B{49,W}}, pExpand_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{0,U},B{1,U},B{32,S},B{48,W},B{49,W}}, pExpandM4_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{35,S},B{48,W}}, pSynth_.setLayout)) return false;
// C1: LK-reduce (UBO, SSBO, prev, curr) and curr-luma stabilization prewarp (UBO, src, dst).
if (!buildSetLayout(dd_, dev, {B{1,U},B{16,SB},B{32,S},B{33,S}}, pGmReduce_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{1,U},B{32,S},B{48,W}}, pGmPrewarp_.setLayout)) return false;
// C2: flow-reg (UBO, u_in, f0_obs, luma, out).
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{48,W}}, pFlowReg_.setLayout)) return false;
std::vector<VkDescriptorType> unused;
@@ -166,9 +153,9 @@ bool FrameGen::init(const DeviceDispatch* dd, const InstanceDispatch* id,
if (!makePipe(pGmReduce_, embedded::OF3_GM_REDUCE, unused)) return false;
if (!makePipe(pGmPrewarp_, embedded::OF3_GM_PREWARP, unused)) return false;
if (!makePipe(pFlowReg_, embedded::OF3_FLOWREG, unused)) return false;
if (!makePipe(pSynth_, embedded::WFG_SYNTH, unused)) return false;
if (!makeGmBuffers()) { WFG_LOGE("framegen: GM SSBO alloc failed - C1 disabled"); }
WFG_LOGI("framegen init ok (queueFamily=%u, 10 pipelines, model=%d, C1 gm=%d, C2 flow_reg=%d iters=%d)",
if (!makePipe(pSynth_, embedded::DDFG_SYNTH, unused)) return false;
if (!makeGmBuffers()) { DDFG_LOGE("framegen: GM SSBO alloc failed - C1 disabled"); }
DDFG_LOGI("framegen init ok (queueFamily=%u, 10 pipelines, model=%d, C1 gm=%d, C2 flow_reg=%d iters=%d)",
queueFamily_, cfg_.model, cfg_.gmMode, cfg_.frMode, cfg_.frIters);
return true;
}
@@ -177,23 +164,19 @@ static VkExtent2D levelExtent(VkExtent2D e, int l) {
return { e.width >> l ? e.width >> l : 1u, e.height >> l ? e.height >> l : 1u };
}
bool FrameGen::onResize(VkExtent2D extent, VkFormat /*colorFormat*/, bool force) {
// force skips the same-extent early-out so a perf_preset change (which alters
// flowFinest_ and therefore the per-size flow-image resolutions) rebuilds them.
bool FrameGen::onResize(VkExtent2D extent, VkFormat , bool force) {
if (!force && ready_ && extent.width == extent_.width && extent.height == extent_.height) return true;
// tear down previous size
for (auto& i : pyrA_) destroyImage(i); pyrA_.clear();
for (auto& i : pyrB_) destroyImage(i); pyrB_.clear();
for (auto& i : pyrAs_) destroyImage(i); pyrAs_.clear();
for (auto& i : flowLvl_) destroyImage(i); flowLvl_.clear();
destroyImage(flowExpA_); destroyImage(flowExpB_);
destroyImage(flowRegA_); destroyImage(flowRegB_); // C2
destroyImage(flowRegA_); destroyImage(flowRegB_);
extent_ = extent; ready_ = false;
const VkImageUsageFlags lumaUsage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
// flowLvl_ is cleared every frame (per-frame reset) and C2 copies the cleaned
// finest level back into it - both need TRANSFER_DST. The C2 ping-pong images
// are the copy SOURCE (TRANSFER_SRC).
const VkImageUsageFlags flowUsage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
const VkImageUsageFlags regUsage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
pyrA_.resize(kLevels); pyrB_.resize(kLevels); flowLvl_.resize(kLevels);
@@ -203,17 +186,15 @@ bool FrameGen::onResize(VkExtent2D extent, VkFormat /*colorFormat*/, bool force)
if (!makeImage(pyrB_[l], e, VK_FORMAT_R32_SFLOAT, lumaUsage)) return false;
if (!makeImage(flowLvl_[l], e, VK_FORMAT_R16G16B16A16_SFLOAT, flowUsage)) return false;
}
// C1: stabilized curr luma - only the levels the dense flow reads (finest..coarsest).
pyrAs_.resize(kLevels);
for (int l = flowFinest_; l < kLevels; ++l)
if (!makeImage(pyrAs_[l], levelExtent(extent, l), VK_FORMAT_R32_SFLOAT, lumaUsage)) return false;
if (!makeImage(flowExpA_, extent, VK_FORMAT_R16G16B16A16_SFLOAT, flowUsage)) return false;
if (!makeImage(flowExpB_, extent, VK_FORMAT_R16G16B16A16_SFLOAT, flowUsage)) return false;
// C2: TV-L1 ping-pong scratch at the finest solved flow level (perf_preset-driven res).
if (!makeImage(flowRegA_, levelExtent(extent, flowFinest_), VK_FORMAT_R16G16B16A16_SFLOAT, regUsage)) return false;
if (!makeImage(flowRegB_, levelExtent(extent, flowFinest_), VK_FORMAT_R16G16B16A16_SFLOAT, regUsage)) return false;
ready_ = true;
WFG_LOGI("framegen resized to %ux%u (%d pyramid levels, kFlowFinest=%d perf_preset=%d)",
DDFG_LOGI("framegen resized to %ux%u (%d pyramid levels, kFlowFinest=%d perf_preset=%d)",
extent.width, extent.height, kLevels, flowFinest_, cfg_.perfPreset);
return true;
}
@@ -229,13 +210,6 @@ void FrameGen::barrier(VkCommandBuffer cmd, VkImage img, VkImageLayout from, VkI
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
}
// NOTE: descriptor-set allocation/update per record() and the exact per-level
// flow wiring are implemented in record_impl.inc for readability; the graph is:
// luma(curr)->pyrA[0], luma(prev)->pyrB[0]
// downsample pyrA/pyrB to kLevels
// coarse->fine flow (per level) into flowLvl
// expand flowLvl[0] -> flowExpA/flowExpB
// synth(prev,curr,flowExpA,flowExpB, alpha) -> out
void FrameGen::destroy() {
if (!device_) return;
@@ -245,11 +219,11 @@ void FrameGen::destroy() {
for (auto& i : pyrAs_) destroyImage(i); pyrAs_.clear();
for (auto& i : flowLvl_) destroyImage(i); flowLvl_.clear();
destroyImage(flowExpA_); destroyImage(flowExpB_);
destroyImage(flowRegA_); destroyImage(flowRegB_); // C2
destroyImage(flowRegA_); destroyImage(flowRegB_);
destroyPipe(pLuma_); destroyPipe(pDown_); destroyPipe(pFlow_); destroyPipe(pFlowM4_);
destroyPipe(pExpand_); destroyPipe(pExpandM4_); destroyPipe(pSynth_);
destroyPipe(pGmReduce_); destroyPipe(pGmPrewarp_);
destroyPipe(pFlowReg_); // C2
destroyPipe(pFlowReg_);
for (int s = 0; s < kGmSlots; ++s) {
if (gmSsboMem_[s]) dd_->UnmapMemory(device_, gmSsboMem_[s]);
if (gmSsbo_[s]) dd_->DestroyBuffer(device_, gmSsbo_[s], nullptr);
@@ -262,8 +236,6 @@ void FrameGen::destroy() {
descPool_ = VK_NULL_HANDLE; linear_ = VK_NULL_HANDLE; device_ = VK_NULL_HANDLE; ready_ = false;
}
} // namespace winfg
}
// record() + its scratch helpers reopen namespace winfg themselves, so include
// this OUTSIDE the namespace block above (keeps FrameGen:: members attached).
#include "record_impl.inc"
+112
View File
@@ -0,0 +1,112 @@
#pragma once
#include "vk_dispatch.hpp"
#include "config.hpp"
#include <vector>
namespace ddfg {
class FrameGen {
public:
bool init(const DeviceDispatch* dd, const InstanceDispatch* id,
VkPhysicalDevice phys, VkDevice dev, uint32_t queueFamily, VkQueue queue);
void configure(const Config& c);
bool onResize(VkExtent2D extent, VkFormat colorFormat, bool force = false);
void record(VkCommandBuffer cmd, VkImageView prevView, VkImageView currView,
VkImageView outView, float alpha, uint32_t gmSlot = 0,
uint32_t synthSlot = 0, bool reuseFlow = false);
void destroy();
bool valid() const { return device_ != VK_NULL_HANDLE && ready_; }
const Config& config() const { return cfg_; }
struct Img { VkImage image = VK_NULL_HANDLE; VkDeviceMemory mem = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE; VkExtent2D extent{}; VkFormat fmt{}; };
struct Pipe { VkPipeline pipeline = VK_NULL_HANDLE; VkPipelineLayout layout = VK_NULL_HANDLE;
VkDescriptorSetLayout setLayout = VK_NULL_HANDLE; VkShaderModule module = VK_NULL_HANDLE; };
static constexpr int kGmSlots = 3;
static constexpr int kSynthSlots = 3;
static constexpr int kGmLevel = 3;
static constexpr int kGmThreads = 512;
static constexpr int kGmStride = 32;
static constexpr int kFlowFinestDefault = 2;
static int flowFinestForPreset(int preset) {
switch (preset) {
case 0: return 1;
case 2: return 3;
case 1: default: return kFlowFinestDefault;
}
}
int flowFinest() const { return flowFinest_; }
private:
uint32_t findMemType(uint32_t bits, VkMemoryPropertyFlags props) const;
bool makeImage(Img& out, VkExtent2D ext, VkFormat fmt, VkImageUsageFlags usage);
void destroyImage(Img& i);
bool makeGmBuffers();
bool updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent);
bool makePipe(Pipe& p, int shaderId,
const std::vector<VkDescriptorType>& bindings);
void destroyPipe(Pipe& p);
void barrier(VkCommandBuffer cmd, VkImage img, VkImageLayout from, VkImageLayout to);
const DeviceDispatch* dd_ = nullptr;
const InstanceDispatch* id_ = nullptr;
VkPhysicalDevice phys_ = VK_NULL_HANDLE;
VkDevice device_ = VK_NULL_HANDLE;
VkQueue queue_ = VK_NULL_HANDLE;
uint32_t queueFamily_ = 0;
VkPhysicalDeviceMemoryProperties memProps_{};
VkSampler linear_ = VK_NULL_HANDLE;
VkDescriptorPool descPool_ = VK_NULL_HANDLE;
Config cfg_;
VkExtent2D extent_{};
bool ready_ = false;
int flowFinest_ = kFlowFinestDefault;
Pipe pLuma_, pDown_, pFlow_, pFlowM4_, pExpand_, pExpandM4_, pSynth_;
Pipe pGmReduce_, pGmPrewarp_;
Pipe pFlowReg_;
static const int kLevels = 7;
std::vector<Img> pyrA_, pyrB_;
std::vector<Img> pyrAs_;
std::vector<Img> flowLvl_;
Img flowExpA_, flowExpB_;
Img flowRegA_, flowRegB_;
VkBuffer gmSsbo_[kGmSlots] {};
VkDeviceMemory gmSsboMem_[kGmSlots]{};
void* gmSsboPtr_[kGmSlots]{};
bool gmBuffersReady_ = false;
double gmRun_[6] {};
double gmLinAt_[kGmSlots][6] {};
bool gmSlotValid_[kGmSlots]{};
bool gmHaveEst_ = false;
int gmStableCount_ = 0;
bool gmEngaged_ = false;
float gmLastTransPx_ = 0.0f;
unsigned long long gmFrames_ = 0;
};
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#if defined(__ANDROID__)
#include <android/log.h>
#define DDFG_LOGI(...) __android_log_print(ANDROID_LOG_INFO, "dd-fg", __VA_ARGS__)
#define DDFG_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "dd-fg", __VA_ARGS__)
#else
#include <cstdio>
#define DDFG_LOGI(...) do { std::fprintf(stderr, "[dd-fg] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
#define DDFG_LOGE(...) do { std::fprintf(stderr, "[dd-fg][E] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
#endif
#define DDFG_LOGD(cond, ...) do { if (cond) DDFG_LOGI(__VA_ARGS__); } while(0)
@@ -0,0 +1,538 @@
namespace {
struct ScratchState {
VkBuffer synthUbo[ddfg::FrameGen::kSynthSlots]{};
VkDeviceMemory synthUboMem[ddfg::FrameGen::kSynthSlots]{};
void* synthUboPtr[ddfg::FrameGen::kSynthSlots]{};
std::vector<VkBuffer> flowUbo; std::vector<VkDeviceMemory> flowUboMem; std::vector<void*> flowUboPtr;
VkBuffer expandUbo = VK_NULL_HANDLE; VkDeviceMemory expandUboMem = VK_NULL_HANDLE; void* expandUboPtr = nullptr;
VkBuffer flowRegUbo = VK_NULL_HANDLE; VkDeviceMemory flowRegUboMem = VK_NULL_HANDLE; void* flowRegUboPtr = nullptr;
VkBuffer gmUbo[ddfg::FrameGen::kGmSlots]{};
VkDeviceMemory gmUboMem[ddfg::FrameGen::kGmSlots]{};
void* gmUboPtr[ddfg::FrameGen::kGmSlots]{};
VkDescriptorSet sLumaCurr = VK_NULL_HANDLE, sLumaPrev = VK_NULL_HANDLE;
std::vector<VkDescriptorSet> sDownA, sDownB, sFlow;
VkDescriptorSet sExpand[ddfg::FrameGen::kGmSlots]{};
VkDescriptorSet sFlowReg0 = VK_NULL_HANDLE, sFlowRegAB = VK_NULL_HANDLE, sFlowRegBA = VK_NULL_HANDLE;
VkDescriptorSet sSynth[ddfg::FrameGen::kSynthSlots]{};
VkDescriptorSet sGmReduce[ddfg::FrameGen::kGmSlots]{};
std::vector<VkDescriptorSet> sGmPrewarp[ddfg::FrameGen::kGmSlots];
VkExtent2D builtFor{0,0};
bool built = false;
bool flowInited = false;
bool flowReady = false;
};
ScratchState g_scratch;
}
static bool makeUbo(const ddfg::DeviceDispatch* dd, VkDevice dev,
const VkPhysicalDeviceMemoryProperties& mp,
VkDeviceSize size, VkBuffer& buf, VkDeviceMemory& mem, void*& ptr) {
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
bi.size = size; bi.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (dd->CreateBuffer(dev, &bi, nullptr, &buf) != VK_SUCCESS) return false;
VkMemoryRequirements mr; dd->GetBufferMemoryRequirements(dev, buf, &mr);
uint32_t idx = 0; VkMemoryPropertyFlags want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
for (uint32_t i = 0; i < mp.memoryTypeCount; ++i)
if ((mr.memoryTypeBits & (1u<<i)) && (mp.memoryTypes[i].propertyFlags & want) == want) { idx = i; break; }
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; ai.allocationSize = mr.size; ai.memoryTypeIndex = idx;
if (dd->AllocateMemory(dev, &ai, nullptr, &mem) != VK_SUCCESS) return false;
if (dd->BindBufferMemory(dev, buf, mem, 0) != VK_SUCCESS) return false;
return dd->MapMemory(dev, mem, 0, size, 0, &ptr) == VK_SUCCESS;
}
static VkDescriptorSet allocSet(const ddfg::DeviceDispatch* dd, VkDevice dev,
VkDescriptorPool pool, VkDescriptorSetLayout layout) {
VkDescriptorSetAllocateInfo ai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
ai.descriptorPool = pool; ai.descriptorSetCount = 1; ai.pSetLayouts = &layout;
VkDescriptorSet s = VK_NULL_HANDLE; dd->AllocateDescriptorSets(dev, &ai, &s); return s;
}
static void wImg(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
VkDescriptorType type, VkImageView view, VkSampler samp, VkImageLayout layout) {
VkDescriptorImageInfo ii{}; ii.imageView = view; ii.sampler = samp; ii.imageLayout = layout;
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
w.dstSet = set; w.dstBinding = binding; w.descriptorCount = 1; w.descriptorType = type; w.pImageInfo = &ii;
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
}
static void wUbo(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
VkBuffer buf, VkDeviceSize range) {
VkDescriptorBufferInfo bi{}; bi.buffer = buf; bi.range = range;
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
w.dstSet = set; w.dstBinding = binding; w.descriptorCount = 1;
w.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; w.pBufferInfo = &bi;
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
}
static void wSsbo(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
VkBuffer buf, VkDeviceSize range) {
VkDescriptorBufferInfo bi{}; bi.buffer = buf; bi.range = range;
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
w.dstSet = set; w.dstBinding = binding; w.descriptorCount = 1;
w.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; w.pBufferInfo = &bi;
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
}
static bool cholSolve6(double A[6][6], const double b[6], double x[6]) {
double L[6][6] = {{0}};
for (int i = 0; i < 6; ++i) {
for (int j = 0; j <= i; ++j) {
double s = A[i][j];
for (int k = 0; k < j; ++k) s -= L[i][k] * L[j][k];
if (i == j) {
if (s <= 1e-20) return false;
L[i][j] = std::sqrt(s);
} else {
L[i][j] = s / L[j][j];
}
}
}
double y[6];
for (int i = 0; i < 6; ++i) { double s = b[i]; for (int k = 0; k < i; ++k) s -= L[i][k]*y[k]; y[i] = s / L[i][i]; }
for (int i = 5; i >= 0; --i) { double s = y[i]; for (int k = i+1; k < 6; ++k) s -= L[k][i]*x[k]; x[i] = s / L[i][i]; }
return true;
}
static void affFromParams(const double p[6], double M[3][3]) {
M[0][0]=1.0+p[0]; M[0][1]=p[2]; M[0][2]=p[4];
M[1][0]=p[1]; M[1][1]=1.0+p[3]; M[1][2]=p[5];
M[2][0]=0.0; M[2][1]=0.0; M[2][2]=1.0;
}
static void paramsFromAff(const double M[3][3], double p[6]) {
p[0]=M[0][0]-1.0; p[2]=M[0][1]; p[4]=M[0][2];
p[1]=M[1][0]; p[3]=M[1][1]-1.0; p[5]=M[1][2];
}
static bool invAff(const double M[3][3], double out[3][3]) {
double a=M[0][0], b=M[0][1], tx=M[0][2], c=M[1][0], d=M[1][1], ty=M[1][2];
double det = a*d - b*c;
if (std::fabs(det) < 1e-12) return false;
double id = 1.0/det;
out[0][0]= d*id; out[0][1]= -b*id; out[0][2]= (b*ty - d*tx)*id;
out[1][0]= -c*id; out[1][1]= a*id; out[1][2]= (c*tx - a*ty)*id;
out[2][0]=0.0; out[2][1]=0.0; out[2][2]=1.0;
return true;
}
static void mulAff(const double A[3][3], const double B[3][3], double out[3][3]) {
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 3; ++j)
out[i][j] = A[i][0]*B[0][j] + A[i][1]*B[1][j] + A[i][2]*B[2][j];
out[2][0]=0.0; out[2][1]=0.0; out[2][2]=1.0;
}
namespace ddfg {
static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
const VkPhysicalDeviceMemoryProperties& mp, VkDescriptorPool pool,
VkSampler samp, VkExtent2D ext,
std::vector<FrameGen::Img>& pyrA, std::vector<FrameGen::Img>& pyrB,
std::vector<FrameGen::Img>& pyrAs,
std::vector<FrameGen::Img>& flowLvl, FrameGen::Img& expA, FrameGen::Img& expB,
const FrameGen::Pipe& pLuma, const FrameGen::Pipe& pDown,
const FrameGen::Pipe& pFlow, const FrameGen::Pipe& pExpand,
const FrameGen::Pipe& pSynth, const FrameGen::Pipe& pGmReduce,
const FrameGen::Pipe& pGmPrewarp, const FrameGen::Pipe& pFlowReg,
FrameGen::Img& flowRegA, FrameGen::Img& flowRegB, VkBuffer* gmSsbo,
int kLevels, const Config& cfg);
static void destroyScratch(const DeviceDispatch* dd, VkDevice dev, VkDescriptorPool pool) {
auto freeUbo = [&](VkBuffer& b, VkDeviceMemory& m, void*& p) {
if (m) dd->UnmapMemory(dev, m);
if (b) dd->DestroyBuffer(dev, b, nullptr);
if (m) dd->FreeMemory(dev, m, nullptr);
b = VK_NULL_HANDLE; m = VK_NULL_HANDLE; p = nullptr;
};
auto& S = g_scratch;
for (int s = 0; s < FrameGen::kSynthSlots; ++s) freeUbo(S.synthUbo[s], S.synthUboMem[s], S.synthUboPtr[s]);
freeUbo(S.expandUbo, S.expandUboMem, S.expandUboPtr);
freeUbo(S.flowRegUbo, S.flowRegUboMem, S.flowRegUboPtr);
for (size_t i = 0; i < S.flowUbo.size(); ++i) freeUbo(S.flowUbo[i], S.flowUboMem[i], S.flowUboPtr[i]);
for (int s = 0; s < FrameGen::kGmSlots; ++s) freeUbo(S.gmUbo[s], S.gmUboMem[s], S.gmUboPtr[s]);
if (pool && dd->ResetDescriptorPool) dd->ResetDescriptorPool(dev, pool, 0);
S = ScratchState{};
}
void FrameGen::configure(const Config& c) {
cfg_ = c; cfg_.sanitize();
int want = flowFinestForPreset(cfg_.perfPreset);
const int maxFinest = kLevels - 2;
if (want > maxFinest) {
DDFG_LOGE("perf-preset=%d wants kFlowFinest=%d but only %d pyramid levels exist "
"(need >=2 solved) - clamping to %d", cfg_.perfPreset, want, kLevels, maxFinest);
want = maxFinest;
}
if (want < 1) want = 1;
const int prevFinest = flowFinest_;
flowFinest_ = want;
const bool live = (flowFinest_ != prevFinest) && ready_;
DDFG_LOGI("perf-preset=%d -> kFlowFinest=%d (was %d) live-rebuild=%d ready=%d",
cfg_.perfPreset, flowFinest_, prevFinest, live ? 1 : 0, ready_ ? 1 : 0);
if (!live) return;
dd_->DeviceWaitIdle(device_);
destroyScratch(dd_, device_, descPool_);
if (!onResize(extent_, VK_FORMAT_UNDEFINED, true))
DDFG_LOGE("perf-preset live rebuild: onResize FAILED at %ux%u - FG disabled until next resize",
extent_.width, extent_.height);
for (int i = 0; i < 6; ++i) gmRun_[i] = 0.0;
for (int s = 0; s < kGmSlots; ++s) { gmSlotValid_[s] = false; for (int i = 0; i < 6; ++i) gmLinAt_[s][i] = 0.0; }
gmHaveEst_ = false; gmEngaged_ = false; gmStableCount_ = 0; gmLastTransPx_ = 0.0f;
DDFG_LOGI("perf-preset live rebuild DONE (kFlowFinest=%d, flow predictor reset)", flowFinest_);
}
bool FrameGen::updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent) {
++gmFrames_;
if (!gmBuffersReady_ || cfg_.gmMode == 2 ) {
gmEngaged_ = false; gmStableCount_ = 0;
for (int i = 0; i < 6; ++i) gmRun_[i] = 0.0;
for (int i = 0; i < 6; ++i) gmLinAt_[gmSlot][i] = gmRun_[i];
gmLastTransPx_ = 0.0f;
return false;
}
bool good = false;
if (gmSlotValid_[gmSlot]) {
const float* pt = (const float*)gmSsboPtr_[gmSlot];
double sums[32]; for (int k = 0; k < 32; ++k) sums[k] = 0.0;
for (int t = 0; t < kGmThreads; ++t) {
const float* row = pt + (size_t)t * kGmStride;
for (int k = 0; k < kGmStride; ++k) sums[k] += (double)row[k];
}
double coverage = sums[27];
double lvlpx = double((fullExtent.width >> kGmLevel) * (fullExtent.height >> kGmLevel));
double minCov = 0.03 * (lvlpx > 0 ? lvlpx : 1.0);
if (coverage > minCov && coverage > 64.0) {
double H[6][6]; int idx = 0;
for (int r = 0; r < 6; ++r) for (int c = r; c < 6; ++c) { H[r][c] = H[c][r] = sums[idx]; ++idx; }
double b[6]; for (int i = 0; i < 6; ++i) b[i] = sums[21 + i];
for (int i = 0; i < 6; ++i) H[i][i] += 1e-3 * H[i][i] + 1e-9;
double dp[6];
if (cholSolve6(H, b, dp)) {
bool bounded = std::fabs(dp[0]) < 0.5 && std::fabs(dp[1]) < 0.5 &&
std::fabs(dp[2]) < 0.5 && std::fabs(dp[3]) < 0.5 &&
std::fabs(dp[4]) < 0.25 && std::fabs(dp[5]) < 0.25;
if (bounded) {
double Mlin[3][3], Md[3][3], Mdi[3][3], Mnew[3][3];
affFromParams(gmLinAt_[gmSlot], Mlin);
affFromParams(dp, Md);
if (invAff(Md, Mdi)) {
mulAff(Mlin, Mdi, Mnew);
double pnew[6]; paramsFromAff(Mnew, pnew);
bool sane = std::fabs(pnew[0]) < 0.6 && std::fabs(pnew[1]) < 0.6 &&
std::fabs(pnew[2]) < 0.6 && std::fabs(pnew[3]) < 0.6 &&
std::fabs(pnew[4]) < 0.5 && std::fabs(pnew[5]) < 0.5;
if (sane) { for (int i = 0; i < 6; ++i) gmRun_[i] = pnew[i]; good = true; gmHaveEst_ = true; }
}
}
}
}
}
if (good) ++gmStableCount_; else gmStableCount_ = 0;
switch (cfg_.gmMode) {
case 1: gmEngaged_ = gmHaveEst_; break;
case 2: gmEngaged_ = false; break;
default: gmEngaged_ = (gmStableCount_ >= 3); break;
}
for (int i = 0; i < 6; ++i) gmLinAt_[gmSlot][i] = gmRun_[i];
gmSlotValid_[gmSlot] = true;
gmLastTransPx_ = (float)std::sqrt(
(gmRun_[4]*fullExtent.width) * (gmRun_[4]*fullExtent.width) +
(gmRun_[5]*fullExtent.height) * (gmRun_[5]*fullExtent.height));
return gmEngaged_;
}
void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView currView,
VkImageView outView, float alpha, uint32_t gmSlot, uint32_t synthSlot, bool reuseFlow) {
if (!valid()) return;
if (gmSlot >= (uint32_t)kGmSlots) gmSlot = 0;
if (synthSlot >= (uint32_t)kSynthSlots) synthSlot = 0;
const bool m4 = (cfg_.model == 4);
const Pipe& flowP = m4 ? pFlowM4_ : pFlow_;
const Pipe& expandP = m4 ? pExpandM4_ : pExpand_;
if (!g_scratch.built || g_scratch.builtFor.width != extent_.width || g_scratch.builtFor.height != extent_.height) {
ensureScratch(this, dd_, device_, memProps_, descPool_, linear_, extent_,
pyrA_, pyrB_, pyrAs_, flowLvl_, flowExpA_, flowExpB_,
pLuma_, pDown_, flowP, expandP, pSynth_, pGmReduce_, pGmPrewarp_,
pFlowReg_, flowRegA_, flowRegB_, gmSsbo_, kLevels, cfg_);
}
// Every generation of one real frame is recorded into the same command buffer, so the flow
// between prev and curr is computed once and each generation gets its own synth UBO and set.
if (reuseFlow && !g_scratch.flowReady) return;
if (!reuseFlow) {
bool gmEngaged = updateGlobalMotion(gmSlot, extent_);
if (g_scratch.gmUboPtr[gmSlot]) {
GMUBO g{};
g.lin[0]=(float)gmRun_[0]; g.lin[1]=(float)gmRun_[1]; g.lin[2]=(float)gmRun_[2]; g.lin[3]=(float)gmRun_[3];
g.linT[0]=(float)gmRun_[4]; g.linT[1]=(float)gmRun_[5]; g.linT[2]=gmEngaged?1.0f:0.0f; g.linT[3]=0.0f;
if (gmEngaged) {
g.app[0]=(float)gmRun_[0]; g.app[1]=(float)gmRun_[1]; g.app[2]=(float)gmRun_[2]; g.app[3]=(float)gmRun_[3];
g.appT[0]=(float)gmRun_[4]; g.appT[1]=(float)gmRun_[5];
}
std::memcpy(g_scratch.gmUboPtr[gmSlot], &g, sizeof(g));
}
{
static int lastEng = -1;
if (gmEngaged != (lastEng == 1) || (gmFrames_ % 300ull) == 0) {
DDFG_LOGI("C1 global-motion: engaged=%d mode=%d stable=%d transPx=%.1f (model=%d)",
gmEngaged ? 1 : 0, cfg_.gmMode, gmStableCount_, gmLastTransPx_, cfg_.model);
lastEng = gmEngaged ? 1 : 0;
}
}
wImg(dd_, device_, g_scratch.sLumaCurr, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
wImg(dd_, device_, g_scratch.sLumaPrev, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
SynthUBO su{cfg_.flowScale, alpha, cfg_.beta, cfg_.lambda, cfg_.epsilon, cfg_.photoScale, 0, 0,
cfg_.hudX0, cfg_.hudY0, cfg_.hudX1, cfg_.hudY1};
if (g_scratch.synthUboPtr[synthSlot]) std::memcpy(g_scratch.synthUboPtr[synthSlot], &su, sizeof(su));
const VkDescriptorSet synthSet = g_scratch.sSynth[synthSlot];
wImg(dd_, device_, synthSet, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
wImg(dd_, device_, synthSet, 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
wImg(dd_, device_, synthSet, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outView, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL);
auto disp = [&](const Pipe& p, VkDescriptorSet set, VkExtent2D e) {
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, p.pipeline);
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, p.layout, 0, 1, &set, 0, nullptr);
dd_->CmdDispatch(cmd, (e.width + 15) / 16, (e.height + 15) / 16, 1);
};
auto bar = [&](FrameGen::Img& i){ barrier(cmd, i.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL); };
if (!reuseFlow) {
for (auto& i : pyrA_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
for (auto& i : pyrB_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
for (int l = flowFinest_; l < kLevels; ++l) barrier(cmd, pyrAs_[l].image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
barrier(cmd, flowExpA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
barrier(cmd, flowExpB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
barrier(cmd, flowRegA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
barrier(cmd, flowRegB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
{
VkClearColorValue zero{};
VkImageSubresourceRange rng{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
for (int l = flowFinest_; l < kLevels; ++l) {
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
b.srcAccessMask = 0; b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
b.image = flowLvl_[l].image; b.subresourceRange = rng;
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
dd_->CmdClearColorImage(cmd, flowLvl_[l].image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &zero, 1, &rng);
b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_GENERAL;
b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
}
}
disp(pLuma_, g_scratch.sLumaCurr, levelExtent(extent_, 0));
disp(pLuma_, g_scratch.sLumaPrev, levelExtent(extent_, 0));
bar(pyrA_[0]); bar(pyrB_[0]);
for (int l = 1; l < kLevels; ++l) {
disp(pDown_, g_scratch.sDownA[l - 1], levelExtent(extent_, l));
disp(pDown_, g_scratch.sDownB[l - 1], levelExtent(extent_, l));
bar(pyrA_[l]); bar(pyrB_[l]);
}
if (gmBuffersReady_ && g_scratch.sGmReduce[gmSlot]) {
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.pipeline);
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.layout, 0, 1, &g_scratch.sGmReduce[gmSlot], 0, nullptr);
dd_->CmdDispatch(cmd, (uint32_t)(kGmThreads / 64), 1, 1);
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
mb.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; mb.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
0, 1, &mb, 0, nullptr, 0, nullptr);
}
for (int l = flowFinest_; l < kLevels; ++l) {
disp(pGmPrewarp_, g_scratch.sGmPrewarp[gmSlot][l], levelExtent(extent_, l));
bar(pyrAs_[l]);
}
for (int l = kLevels - 1; l >= flowFinest_; --l) {
disp(flowP, g_scratch.sFlow[l], levelExtent(extent_, l));
bar(flowLvl_[l]);
}
const bool frEngaged = (cfg_.frMode != 2) && (cfg_.frIters > 0);
if (frEngaged) {
const int F = flowFinest_;
const VkExtent2D fe = levelExtent(extent_, F);
if (g_scratch.flowRegUboPtr) {
FlowRegUBO fu{cfg_.frDt, cfg_.frLambda, cfg_.frEdge, cfg_.frEps};
std::memcpy(g_scratch.flowRegUboPtr, &fu, sizeof(fu));
}
FrameGen::Img* last = nullptr;
for (int it = 0; it < cfg_.frIters; ++it) {
VkDescriptorSet set; FrameGen::Img* outImg;
if (it == 0) { set = g_scratch.sFlowReg0; outImg = &flowRegA_; }
else if (it % 2 == 1) { set = g_scratch.sFlowRegAB; outImg = &flowRegB_; }
else { set = g_scratch.sFlowRegBA; outImg = &flowRegA_; }
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pFlowReg_.pipeline);
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pFlowReg_.layout, 0, 1, &set, 0, nullptr);
dd_->CmdDispatch(cmd, (fe.width + 15) / 16, (fe.height + 15) / 16, 1);
bar(*outImg);
last = outImg;
}
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
mb.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
mb.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 1, &mb, 0, nullptr, 0, nullptr);
VkImageCopy region{};
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.extent = {fe.width, fe.height, 1};
dd_->CmdCopyImage(cmd, last->image, VK_IMAGE_LAYOUT_GENERAL,
flowLvl_[F].image, VK_IMAGE_LAYOUT_GENERAL, 1, &region);
VkMemoryBarrier mb2{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
mb2.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
mb2.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, 1, &mb2, 0, nullptr, 0, nullptr);
}
{
static int lastFr = -1;
int fe = frEngaged ? 1 : 0;
if (fe != lastFr || (gmFrames_ % 300ull) == 0) {
DDFG_LOGI("C2 flow-reg: engaged=%d mode=%d iters=%d lambda=%.2f dt=%.2f edge=%.1f eps=%.3f (model=%d)",
fe, cfg_.frMode, cfg_.frIters, cfg_.frLambda, cfg_.frDt, cfg_.frEdge, cfg_.frEps, cfg_.model);
lastFr = fe;
}
}
disp(expandP, g_scratch.sExpand[gmSlot], levelExtent(extent_, 0));
bar(flowExpA_); bar(flowExpB_);
g_scratch.flowReady = true;
}
disp(pSynth_, synthSet, levelExtent(extent_, 0));
}
static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
const VkPhysicalDeviceMemoryProperties& mp, VkDescriptorPool pool,
VkSampler samp, VkExtent2D ext,
std::vector<FrameGen::Img>& pyrA, std::vector<FrameGen::Img>& pyrB,
std::vector<FrameGen::Img>& pyrAs,
std::vector<FrameGen::Img>& flowLvl, FrameGen::Img& expA, FrameGen::Img& expB,
const FrameGen::Pipe& pLuma, const FrameGen::Pipe& pDown,
const FrameGen::Pipe& pFlow, const FrameGen::Pipe& pExpand,
const FrameGen::Pipe& pSynth, const FrameGen::Pipe& pGmReduce,
const FrameGen::Pipe& pGmPrewarp, const FrameGen::Pipe& pFlowReg,
FrameGen::Img& flowRegA, FrameGen::Img& flowRegB, VkBuffer* gmSsbo,
int kLevels, const Config& cfg) {
auto& S = g_scratch;
S = ScratchState{};
const auto GEN = VK_IMAGE_LAYOUT_GENERAL;
for (int s = 0; s < FrameGen::kSynthSlots; ++s)
makeUbo(dd, dev, mp, sizeof(SynthUBO), S.synthUbo[s], S.synthUboMem[s], S.synthUboPtr[s]);
makeUbo(dd, dev, mp, sizeof(FlowUBO), S.expandUbo, S.expandUboMem, S.expandUboPtr);
if (S.expandUboPtr) { FlowUBO u{cfg.flowScale, 0, 0.6f, 1.6f}; std::memcpy(S.expandUboPtr, &u, sizeof(u)); }
makeUbo(dd, dev, mp, sizeof(FlowRegUBO), S.flowRegUbo, S.flowRegUboMem, S.flowRegUboPtr);
if (S.flowRegUboPtr) { FlowRegUBO fu{cfg.frDt, cfg.frLambda, cfg.frEdge, cfg.frEps}; std::memcpy(S.flowRegUboPtr, &fu, sizeof(fu)); }
S.flowUbo.resize(kLevels); S.flowUboMem.resize(kLevels); S.flowUboPtr.resize(kLevels);
for (int l = 0; l < kLevels; ++l) {
makeUbo(dd, dev, mp, sizeof(FlowUBO), S.flowUbo[l], S.flowUboMem[l], S.flowUboPtr[l]);
if (S.flowUboPtr[l]) { FlowUBO u{cfg.flowScale, (uint32_t)l, 0.6f, 1.6f}; std::memcpy(S.flowUboPtr[l], &u, sizeof(u)); }
}
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
makeUbo(dd, dev, mp, sizeof(GMUBO), S.gmUbo[s], S.gmUboMem[s], S.gmUboPtr[s]);
if (S.gmUboPtr[s]) { GMUBO g{}; std::memcpy(S.gmUboPtr[s], &g, sizeof(g)); }
}
S.sLumaCurr = allocSet(dd, dev, pool, pLuma.setLayout);
S.sLumaPrev = allocSet(dd, dev, pool, pLuma.setLayout);
wImg(dd, dev, S.sLumaCurr, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrA[0].view, VK_NULL_HANDLE, GEN);
wImg(dd, dev, S.sLumaPrev, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrB[0].view, VK_NULL_HANDLE, GEN);
S.sDownA.resize(kLevels - 1); S.sDownB.resize(kLevels - 1);
for (int l = 1; l < kLevels; ++l) {
S.sDownA[l-1] = allocSet(dd, dev, pool, pDown.setLayout);
S.sDownB[l-1] = allocSet(dd, dev, pool, pDown.setLayout);
wImg(dd, dev, S.sDownA[l-1], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[l-1].view, samp, GEN);
wImg(dd, dev, S.sDownA[l-1], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrA[l].view, VK_NULL_HANDLE, GEN);
wImg(dd, dev, S.sDownB[l-1], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[l-1].view, samp, GEN);
wImg(dd, dev, S.sDownB[l-1], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrB[l].view, VK_NULL_HANDLE, GEN);
}
S.sFlow.resize(kLevels);
for (int l = 0; l < kLevels; ++l) {
S.sFlow[l] = allocSet(dd, dev, pool, pFlow.setLayout);
int pred = (l + 1 < kLevels) ? l + 1 : l;
VkImageView currLvl = (l >= fg->flowFinest()) ? pyrAs[l].view : pyrA[l].view;
wUbo(dd, dev, S.sFlow[l], 0, S.flowUbo[l], sizeof(FlowUBO));
wImg(dd, dev, S.sFlow[l], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[l].view, samp, GEN);
wImg(dd, dev, S.sFlow[l], 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currLvl, samp, GEN);
wImg(dd, dev, S.sFlow[l], 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[pred].view, samp, GEN);
wImg(dd, dev, S.sFlow[l], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowLvl[l].view, VK_NULL_HANDLE, GEN);
}
if (gmSsbo) {
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
if (!gmSsbo[s]) continue;
S.sGmReduce[s] = allocSet(dd, dev, pool, pGmReduce.setLayout);
wUbo (dd, dev, S.sGmReduce[s], 1, S.gmUbo[s], sizeof(GMUBO));
wSsbo(dd, dev, S.sGmReduce[s], 16, gmSsbo[s], VK_WHOLE_SIZE);
wImg (dd, dev, S.sGmReduce[s], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[FrameGen::kGmLevel].view, samp, GEN);
wImg (dd, dev, S.sGmReduce[s], 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[FrameGen::kGmLevel].view, samp, GEN);
}
}
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
S.sGmPrewarp[s].assign(kLevels, VK_NULL_HANDLE);
for (int l = fg->flowFinest(); l < kLevels; ++l) {
S.sGmPrewarp[s][l] = allocSet(dd, dev, pool, pGmPrewarp.setLayout);
wUbo(dd, dev, S.sGmPrewarp[s][l], 1, S.gmUbo[s], sizeof(GMUBO));
wImg(dd, dev, S.sGmPrewarp[s][l], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[l].view, samp, GEN);
wImg(dd, dev, S.sGmPrewarp[s][l], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrAs[l].view, VK_NULL_HANDLE, GEN);
}
}
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
S.sExpand[s] = allocSet(dd, dev, pool, pExpand.setLayout);
wUbo(dd, dev, S.sExpand[s], 0, S.expandUbo, sizeof(FlowUBO));
wUbo(dd, dev, S.sExpand[s], 1, S.gmUbo[s], sizeof(GMUBO));
wImg(dd, dev, S.sExpand[s], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[fg->flowFinest()].view, samp, GEN);
wImg(dd, dev, S.sExpand[s], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, expA.view, VK_NULL_HANDLE, GEN);
wImg(dd, dev, S.sExpand[s], 49, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, expB.view, VK_NULL_HANDLE, GEN);
}
{
const int F = fg->flowFinest();
S.sFlowReg0 = allocSet(dd, dev, pool, pFlowReg.setLayout);
S.sFlowRegAB = allocSet(dd, dev, pool, pFlowReg.setLayout);
S.sFlowRegBA = allocSet(dd, dev, pool, pFlowReg.setLayout);
VkDescriptorSet all3[3] = {S.sFlowReg0, S.sFlowRegAB, S.sFlowRegBA};
for (VkDescriptorSet s : all3) {
wUbo(dd, dev, s, 0, S.flowRegUbo, sizeof(FlowRegUBO));
wImg(dd, dev, s, 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[F].view, samp, GEN);
wImg(dd, dev, s, 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[F].view, samp, GEN);
}
wImg(dd, dev, S.sFlowReg0, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[F].view, samp, GEN);
wImg(dd, dev, S.sFlowReg0, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegA.view, VK_NULL_HANDLE, GEN);
wImg(dd, dev, S.sFlowRegAB, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowRegA.view, samp, GEN);
wImg(dd, dev, S.sFlowRegAB, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegB.view, VK_NULL_HANDLE, GEN);
wImg(dd, dev, S.sFlowRegBA, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowRegB.view, samp, GEN);
wImg(dd, dev, S.sFlowRegBA, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegA.view, VK_NULL_HANDLE, GEN);
}
for (int s = 0; s < FrameGen::kSynthSlots; ++s) {
S.sSynth[s] = allocSet(dd, dev, pool, pSynth.setLayout);
wUbo(dd, dev, S.sSynth[s], 0, S.synthUbo[s], sizeof(SynthUBO));
wImg(dd, dev, S.sSynth[s], 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expA.view, samp, GEN);
wImg(dd, dev, S.sSynth[s], 35, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expB.view, samp, GEN);
}
S.builtFor = ext; S.built = true;
return true;
}
}
@@ -1,61 +1,39 @@
// win-fg - minimal Vulkan layer dispatch tables.
// Structure follows the public Khronos loader/layer interface and the
// renderdoc "Vulkan layer guide" skeleton (learning references, Apache-2.0 /
// CC-BY). No code from bionic-fg / lsfg / GameScope.
#pragma once
#include <vulkan/vulkan.h>
#include <vulkan/vk_layer.h>
#include <cstring>
namespace winfg {
namespace ddfg {
// The loader sets the first pointer-sized word of a dispatchable handle to the
// dispatch table pointer; layers key their own maps on that value so that all
// handles sharing a chain resolve to the same table.
static inline void* dispatch_key(void* handle) { return *reinterpret_cast<void**>(handle); }
// Instance-level functions we call from the layer.
struct InstanceDispatch {
PFN_vkGetInstanceProcAddr GetInstanceProcAddr = nullptr;
PFN_vkDestroyInstance DestroyInstance = nullptr;
PFN_vkGetPhysicalDeviceMemoryProperties GetPhysicalDeviceMemoryProperties = nullptr;
PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties = nullptr;
// Device-context logging (affected-device fingerprint). Properties2 is core 1.1;
// may be null on a 1.0 instance - always null-check before calling.
PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties = nullptr;
PFN_vkGetPhysicalDeviceProperties2 GetPhysicalDeviceProperties2 = nullptr;
// Surface caps - used to clamp the extra-image-headroom request to the surface's
// maxImageCount so the bump never exceeds what the driver can grant.
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr;
};
// Device-level functions we call. Populated by walking vkGetDeviceProcAddr on
// the next layer down at device-create time.
struct DeviceDispatch {
PFN_vkGetDeviceProcAddr GetDeviceProcAddr = nullptr;
PFN_vkDestroyDevice DestroyDevice = nullptr;
PFN_vkGetDeviceQueue GetDeviceQueue = nullptr;
PFN_vkQueueSubmit QueueSubmit = nullptr;
// Synchronization2 submit path (core 1.3 + KHR alias). DXVK may use either the
// legacy QueueSubmit or QueueSubmit2/2KHR; both are hooked for the guest-submit
// diagnostic. May be null if the device did not enable them - always null-check.
PFN_vkQueueSubmit2 QueueSubmit2 = nullptr;
PFN_vkQueueSubmit2KHR QueueSubmit2KHR = nullptr;
PFN_vkQueueWaitIdle QueueWaitIdle = nullptr;
PFN_vkDeviceWaitIdle DeviceWaitIdle = nullptr;
// swapchain / present
PFN_vkCreateSwapchainKHR CreateSwapchainKHR = nullptr;
PFN_vkDestroySwapchainKHR DestroySwapchainKHR = nullptr;
PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR = nullptr;
PFN_vkAcquireNextImageKHR AcquireNextImageKHR = nullptr;
// Alternate acquire entry (VK_KHR_swapchain 1.1+ / device-group). DXVK may use
// either AcquireNextImageKHR or AcquireNextImage2KHR - both hooked for the
// guest-acquire diagnostic. May be null - always null-check.
PFN_vkAcquireNextImage2KHR AcquireNextImage2KHR = nullptr;
PFN_vkQueuePresentKHR QueuePresentKHR = nullptr;
// resources
PFN_vkCreateImage CreateImage = nullptr;
PFN_vkDestroyImage DestroyImage = nullptr;
PFN_vkCreateImageView CreateImageView = nullptr;
@@ -73,7 +51,6 @@ struct DeviceDispatch {
PFN_vkCreateSampler CreateSampler = nullptr;
PFN_vkDestroySampler DestroySampler = nullptr;
// pipeline / descriptors
PFN_vkCreateShaderModule CreateShaderModule = nullptr;
PFN_vkDestroyShaderModule DestroyShaderModule = nullptr;
PFN_vkCreateDescriptorSetLayout CreateDescriptorSetLayout = nullptr;
@@ -84,15 +61,10 @@ struct DeviceDispatch {
PFN_vkDestroyPipeline DestroyPipeline = nullptr;
PFN_vkCreateDescriptorPool CreateDescriptorPool = nullptr;
PFN_vkDestroyDescriptorPool DestroyDescriptorPool = nullptr;
// Frees every set in the pool at once - used by FrameGen::configure() to reclaim
// the old scratch descriptor sets when a perf_preset change forces a live rebuild
// (otherwise repeated preset switches would exhaust the pool). May be null in
// theory; the rebuild path null-checks before calling.
PFN_vkResetDescriptorPool ResetDescriptorPool = nullptr;
PFN_vkAllocateDescriptorSets AllocateDescriptorSets = nullptr;
PFN_vkUpdateDescriptorSets UpdateDescriptorSets = nullptr;
// command recording
PFN_vkCreateCommandPool CreateCommandPool = nullptr;
PFN_vkDestroyCommandPool DestroyCommandPool = nullptr;
PFN_vkAllocateCommandBuffers AllocateCommandBuffers = nullptr;
@@ -104,7 +76,7 @@ struct DeviceDispatch {
PFN_vkCmdDispatch CmdDispatch = nullptr;
PFN_vkCmdPipelineBarrier CmdPipelineBarrier = nullptr;
PFN_vkCmdCopyImage CmdCopyImage = nullptr;
PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer = nullptr; // capture readback
PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer = nullptr;
PFN_vkCmdBlitImage CmdBlitImage = nullptr;
PFN_vkCmdClearColorImage CmdClearColorImage = nullptr;
PFN_vkCreateFence CreateFence = nullptr;
@@ -120,4 +92,4 @@ static inline void load(PFN& slot, PFN_vkGetDeviceProcAddr gdpa, VkDevice dev, c
slot = reinterpret_cast<PFN>(gdpa(dev, name));
}
} // namespace winfg
}
@@ -3,11 +3,6 @@
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
#include "lsfg_alpha.hpp"
#include "lsfg_shaders.hpp"
@@ -3,11 +3,6 @@
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
#pragma once
@@ -3,11 +3,6 @@
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
#include "lsfg_beta.hpp"
#include "lsfg_shaders.hpp"
@@ -3,11 +3,6 @@
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
#pragma once
@@ -3,11 +3,6 @@
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
#include "lsfg_chain.hpp"
#include "lsfg_shaders.hpp"
@@ -3,11 +3,6 @@
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
#pragma once
@@ -3,11 +3,6 @@
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
#include "lsfg_common.hpp"
#include "lsfg_shaders.hpp"
@@ -3,11 +3,6 @@
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
#pragma once
@@ -3,11 +3,6 @@
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
#include "lsfg_delta.hpp"
#include "lsfg_shaders.hpp"
@@ -3,11 +3,6 @@
// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Ported into Bannerlator's compositor from WinNative (GPL-3.0-or-later),
// whose LSFG port is credited to Camille LaVey / the Eden Emulator Project and
// follows upstream lsfg-vk. Only the Vulkan dispatch differs: Bannerlator
// resolves entry points through the renderer's own table (see lsfg_vkd.h).
#pragma once
+17 -71
View File
@@ -1,9 +1,5 @@
// See lsfg_dll.h. PE resource walk + on-device SPIR-V cache for the Lossless
// Scaling frame-generation chain.
//
// Derived from WinNative's lsfg_dll.c (GPL-3.0-or-later), LSFG port credited to
// Camille LaVey / the Eden Emulator Project, following upstream lsfg-vk
// (PancakeTAS). Bannerlator is GPL-3.0.
// LSFG from Camille LaVey / the Eden Emulator Project, following upstream lsfg-vk (PancakeTAS).
// Ported to WinNative and DroidDeck by @maxjivi05. GPL-3.0-or-later.
#include "lsfg_dll.h"
#include "lsfg_dxbc.h"
@@ -25,9 +21,9 @@
namespace lsfg {
namespace {
// ---- PE constants ---------------------------------------------------------
constexpr uint16_t kDosMagic = 0x5A4Du; // "MZ"
constexpr uint32_t kPeSignature = 0x00004550u; // "PE\0\0"
constexpr uint16_t kDosMagic = 0x5A4Du;
constexpr uint32_t kPeSignature = 0x00004550u;
constexpr uint16_t kPe32Magic = 0x010Bu;
constexpr uint16_t kPe32PlusMagic = 0x020Bu;
@@ -47,7 +43,6 @@ constexpr size_t kResourceEntrySize = 8u;
constexpr uint32_t kResourceSubdirectoryBit = 0x80000000u;
constexpr uint32_t kResourceTypeRcData = 10u;
// ---- SPIR-V constants -----------------------------------------------------
constexpr uint32_t kSpirvMagic = 0x07230203u;
constexpr size_t kSpirvHeaderWords = 5u;
constexpr uint32_t kSpirvWordCountShift = 16u;
@@ -64,7 +59,7 @@ constexpr uint32_t kVariantFp32Offset = 98u;
constexpr uint32_t kMaxResourceId = 512u;
constexpr size_t kMaxSpirvWords = 16u * 1024u * 1024u;
constexpr uint32_t kCacheMagic = 0x4C534642u; // "BFSL" little-endian
constexpr uint32_t kCacheMagic = 0x4C534642u;
constexpr uint32_t kCacheVersion = 1u;
struct CacheHeader {
@@ -83,7 +78,6 @@ struct PeSection {
uint32_t rawSize = 0;
};
// Read-only mapping of the DLL. Closes itself; the DLL is never executed.
class PeImage {
public:
~PeImage() { close(); }
@@ -168,7 +162,6 @@ private:
std::vector<PeSection> sections_;
};
// RCDATA id -> blob, for the ids we care about.
struct ResourceTable {
const uint8_t* data[kMaxResourceId] = {};
uint32_t size[kMaxResourceId] = {};
@@ -217,7 +210,6 @@ bool resourceReadLeaf(const PeImage& img, size_t leafOffset,
return true;
}
// Walk type -> name -> language and keep the first language of every RCDATA id.
bool collectRcData(const PeImage& img, size_t resourceBase, ResourceTable& out) {
size_t typeTotal = 0;
if (!resourceEntryCount(img, resourceBase, typeTotal)) return false;
@@ -256,7 +248,7 @@ bool collectRcData(const PeImage& img, size_t resourceBase, ResourceTable& out)
if (!resourceReadLeaf(img, resourceBase + langOffset, blob, blobSize)) continue;
out.data[nameId] = blob;
out.size[nameId] = blobSize;
break; // first language wins
break;
}
}
}
@@ -289,7 +281,6 @@ DllStatus parseResources(PeImage& img, ResourceTable& table) {
return DllStatus::Ok;
}
// ---- SPIR-V adoption (precompiled-variant path) ---------------------------
bool isSpirvModule(const uint8_t* blob, uint32_t size) {
if (!blob || size < kSpirvHeaderWords * sizeof(uint32_t)) return false;
@@ -315,9 +306,6 @@ uint32_t lookupDescriptorSet(const std::vector<uint32_t>& w, uint32_t targetId)
return 0;
}
// Precompiled blobs are renumbered into set/binding order - that is the
// convention they were built with. The DXBC path uses encounter order instead
// (see lsfg_dxbc), which is what DXVK's output pairs with. Do not merge them.
bool renumberBindingsSetOrder(std::vector<uint32_t>& w) {
struct Slot { uint32_t set; uint32_t binding; size_t literalOffset; };
std::vector<Slot> slots;
@@ -399,8 +387,6 @@ bool writeCache(const std::string& cachePath, const CacheHeader& header, const M
if (ok) ok = fsync(fileno(f)) == 0;
fclose(f);
// Temp file + rename, so a failed or interrupted build can never leave a
// half-written cache that would later be loaded as if it were complete.
if (!ok || rename(tempPath.c_str(), cachePath.c_str()) != 0) {
unlink(tempPath.c_str());
return false;
@@ -416,9 +402,8 @@ bool readCacheHeader(const std::string& cachePath, CacheHeader& out) {
return ok && out.magic == kCacheMagic && out.version == kCacheVersion;
}
} // namespace
}
// ---- public API -----------------------------------------------------------
const std::vector<uint32_t>& shaderIds() {
static const std::vector<uint32_t> ids = [] {
@@ -478,20 +463,6 @@ DllStatus validateDll(const std::string& dllPath) {
return status;
}
Variant dllVariant(const std::string& dllPath, bool preferFp16) {
if (dllPath.empty()) return Variant::None;
PeImage img;
if (!img.open(dllPath)) return Variant::None;
ResourceTable table;
if (parseResources(img, table) != DllStatus::Ok) return Variant::None;
Variant v = selectVariant(table, preferFp16);
if (v == Variant::None && hasBaseChain(table)) v = Variant::DxbcTranslated;
return v;
}
DllStatus buildCache(const std::string& dllPath, const std::string& cachePath, bool preferFp16) {
if (dllPath.empty() || cachePath.empty()) return DllStatus::NotInstalled;
@@ -547,22 +518,6 @@ DllStatus buildCache(const std::string& dllPath, const std::string& cachePath, b
return DllStatus::Ok;
}
DllStatus cacheMatchesSource(const std::string& cachePath, const std::string& dllPath,
bool& outMatches) {
outMatches = false;
if (cachePath.empty() || dllPath.empty()) return DllStatus::CacheUnusable;
CacheHeader header{};
if (!readCacheHeader(cachePath, header)) return DllStatus::CacheUnusable;
PeImage img;
if (!img.open(dllPath)) return DllStatus::NotInstalled;
outMatches = header.sourceSize == (uint64_t)img.size()
&& header.sourceHash == fnv1a64(img.data(), img.size());
return DllStatus::Ok;
}
DllStatus cacheVariant(const std::string& cachePath, Variant& outVariant) {
outVariant = Variant::None;
CacheHeader header{};
@@ -614,7 +569,6 @@ DllStatus loadModules(const std::string& cachePath, ModuleSet& outSet) {
return status;
}
// ---- SPIR-V version lowering (Vulkan 1.1/1.2 compat) ----------------------
namespace {
@@ -623,14 +577,14 @@ constexpr uint32_t kSpirvOpExtInstImport= 11u;
constexpr uint32_t kSpirvOpMemoryModel = 14u;
constexpr uint32_t kSpirvOpCapability = 17u;
// Capabilities that are core in the version the translator emits but not in
// the versions we lower to.
constexpr uint32_t kCapVulkanMemoryModel = 5345u; // core from 1.5
constexpr uint32_t kCapVulkanMemoryModelDeviceScope = 5346u; // core from 1.5
constexpr uint32_t kCapDemoteToHelperInvocation = 5379u; // core from 1.6
constexpr uint32_t kCapVulkanMemoryModel = 5345u;
constexpr uint32_t kCapVulkanMemoryModelDeviceScope = 5346u;
constexpr uint32_t kCapDemoteToHelperInvocation = 5379u;
std::vector<uint32_t> encodeOpExtension(const char* name) {
const size_t len = strlen(name) + 1; // incl. NUL
const size_t len = strlen(name) + 1;
const size_t strWords = (len + 3) / 4;
std::vector<uint32_t> ins(1 + strWords, 0u);
ins[0] = ((uint32_t)(1 + strWords) << kSpirvWordCountShift) | kSpirvOpExtension;
@@ -654,17 +608,13 @@ bool hasOpExtension(const std::vector<uint32_t>& w, const char* name) {
return false;
}
} // namespace
}
bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
if (w.size() < kSpirvHeaderWords || w[0] != kSpirvMagic) return false;
if (targetVersion < kSpirv14) return false;
if (w[1] <= targetVersion) return true; // already low enough
if (w[1] <= targetVersion) return true;
// Walk the preamble first and only touch the module once it is known to
// be lowerable: note which extension-gated capabilities are declared and
// where the capability block ends (OpExtension must follow every
// OpCapability and precede OpExtInstImport / OpMemoryModel).
bool needMemoryModel = false;
size_t insertAt = kSpirvHeaderWords;
size_t off = kSpirvHeaderWords;
@@ -675,9 +625,6 @@ bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
if (opcode == kSpirvOpCapability && length >= 2) {
const uint32_t cap = w[off + 1];
if (cap == kCapVulkanMemoryModel || cap == kCapVulkanMemoryModelDeviceScope) needMemoryModel = true;
// Core only in 1.6, and VK_EXT_shader_demote_to_helper_invocation
// is never enabled on the compat path: refuse rather than hand the
// driver a module that is not legal at the lowered version.
if (cap == kCapDemoteToHelperInvocation) return false;
insertAt = off + length;
} else if (opcode == kSpirvOpExtension) {
@@ -689,7 +636,6 @@ bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
off += length;
}
// 1.5 has the memory model as core; only 1.4 needs it declared.
if (targetVersion < kSpirv15 && needMemoryModel
&& !hasOpExtension(w, "SPV_KHR_vulkan_memory_model")) {
const auto e = encodeOpExtension("SPV_KHR_vulkan_memory_model");
@@ -699,4 +645,4 @@ bool downgradeSpirv(std::vector<uint32_t>& w, uint32_t targetVersion) {
return true;
}
} // namespace lsfg
}
+13 -77
View File
@@ -1,35 +1,7 @@
// LSFG from Camille LaVey / the Eden Emulator Project, following upstream lsfg-vk (PancakeTAS).
// Ported to WinNative and DroidDeck by @maxjivi05. GPL-3.0-or-later.
#pragma once
// ============================================================================
// lsfg_dll - extract the Lossless Scaling frame-generation shader chain from
// the user's own Lossless.dll, and cache it on device as SPIR-V.
//
// The 25 compute shaders that make up LSFG 3.1 are NOT redistributable. They
// ship as RCDATA resources inside Lossless.dll from the user's own paid copy
// of Lossless Scaling (Steam app 993090). Every implementation - upstream
// lsfg-vk, LSFG-Android, eden, WinNative - requires the user to supply their
// own DLL and extracts the resources on device. Bannerlator does the same, and
// bundles nothing.
//
// The DLL is mmap'd READ-ONLY and parsed as data. It is never loaded as a
// library and never executed.
//
// Resource layout, measured against real installs:
// * The base chain lives at RCDATA ids 255, 256 and 280-302 (25 modules),
// and is DXBC. It has always been DXBC.
// * Lossless Scaling MAY additionally carry precompiled SPIR-V copies at
// base+49 (native fp16) and base+98 (native fp32). No build currently
// downloadable from Steam carries them - public buildId 19655272 was
// measured with zero SPIR-V anywhere in the 311 MB install - so the DXBC
// path is the normal path and the translator is not optional. The SPIR-V
// path is kept because it costs nothing and a future build may ship them.
//
// Both producers fill the same shader-id -> SPIR-V words map, so everything
// downstream of this file is source-agnostic.
//
// Derived from WinNative's lsfg_dll.c (GPL-3.0-or-later), whose LSFG port is
// credited to Camille LaVey / the Eden Emulator Project, and which follows
// upstream lsfg-vk (PancakeTAS). Bannerlator is GPL-3.0; see THIRD-PARTY-LICENSES.md.
// ============================================================================
#include <cstdint>
#include <string>
@@ -39,44 +11,37 @@ namespace lsfg {
enum class DllStatus {
Ok = 0,
NotInstalled, // no such file / cannot open
UnreadableFile, // opens but cannot be mapped or is empty
NotInstalled,
UnreadableFile,
NotPortableExecutable,
MissingShaders, // parses, but the base chain is not all there
TranslationFailed, // a shader failed DXBC->SPIR-V or SPIR-V adoption
CacheUnusable // cache could not be written or read back
MissingShaders,
TranslationFailed,
CacheUnusable
};
// SPIR-V version words. The translator emits 1.6; a Vulkan 1.1 device takes
// 1.4 (via VK_KHR_spirv_1_4) and a 1.2 device 1.5. One definition for the
// probe, the engine and the shader loader.
constexpr uint32_t kSpirv14 = 0x00010400u;
constexpr uint32_t kSpirv15 = 0x00010500u;
constexpr uint32_t kSpirv16 = 0x00010600u;
enum class Variant {
None = 0,
SpirvFp16, // precompiled fp16 blobs at base+49
SpirvFp32, // precompiled fp32 blobs at base+98
DxbcTranslated // base chain, translated on device (the normal case)
SpirvFp16,
SpirvFp32,
DxbcTranslated
};
// The 25 shader ids that make up the chain.
constexpr uint32_t kShaderMipmaps = 255u;
constexpr uint32_t kShaderGenerate = 256u;
constexpr uint32_t kShaderChainFirst= 280u;
constexpr uint32_t kShaderChainLast = 302u;
constexpr uint32_t kShaderCount = 25u;
// Upstream spelling for the two ids the ported chain files refer to by name.
// Kept so those files stay byte-close to their upstream form; these alias the
// constants above rather than being a second source of truth.
constexpr uint32_t LSFG_SHADER_MIPMAPS = kShaderMipmaps;
constexpr uint32_t LSFG_SHADER_GENERATE = kShaderGenerate;
struct Module {
uint32_t id = 0;
std::vector<uint32_t> words; // SPIR-V
std::vector<uint32_t> words;
};
struct ModuleSet {
@@ -87,48 +52,19 @@ struct ModuleSet {
bool complete() const;
};
// The canonical shader-id list, in dispatch-independent order.
const std::vector<uint32_t>& shaderIds();
const char* statusName(DllStatus s);
const char* variantName(Variant v);
// Does this file look like a usable Lossless.dll? Checks the base chain only,
// so a DLL without the SPIR-V variants (i.e. every build on Steam today) is
// reported as valid - the translator handles it.
DllStatus validateDll(const std::string& dllPath);
// Which producer would be used for this DLL.
Variant dllVariant(const std::string& dllPath, bool preferFp16);
// Parse the DLL, produce all 25 SPIR-V modules, and write them to cachePath
// (via temp file + rename, so a failed build cannot leave a half-written
// cache behind). Records which producer ran in the cache header.
DllStatus buildCache(const std::string& dllPath, const std::string& cachePath, bool preferFp16);
// Is the cache current for this DLL? Compares source size + content hash.
DllStatus cacheMatchesSource(const std::string& cachePath, const std::string& dllPath,
bool& outMatches);
// Load a previously built cache.
DllStatus loadModules(const std::string& cachePath, ModuleSet& outSet);
// Which producer built an existing cache, without loading the modules.
DllStatus cacheVariant(const std::string& cachePath, Variant& outVariant);
// Experimental Vulkan 1.1 / 1.2 compat: lower a DXBC-translated module's
// declared SPIR-V version to `targetVersion` (kSpirv14 or kSpirv15). Below 1.5
// the Vulkan memory model is not core, so SPV_KHR_vulkan_memory_model is
// declared for it (the device side enables VK_KHR_vulkan_memory_model). The
// cache is built without a device in hand, always at the translator's native
// 1.6, so this runs at load time against whatever the device can take. A
// module already at or below the target is left untouched. Returns false,
// leaving the module untouched, for a malformed module, a target below 1.4, or
// a module declaring DemoteToHelperInvocation: that capability is core only in
// 1.6 and its extension is never enabled on the compat path, so such a module
// cannot be made legal here (DXVK emits it for pixel-shader discard only, so
// the 25 compute shaders never carry it). Whether the device then accepts the
// result is vkCreateShaderModule's / vkCreateComputePipelines' verdict.
bool downgradeSpirv(std::vector<uint32_t>& words, uint32_t targetVersion);
} // namespace lsfg
}
Loaded 100 of 1922 files, more files were not shown because too many files have changed in this diff. Show more