Commit Graph
48 Commits
Author SHA1 Message Date
Kurt Himebauch 2848484a99 feat: fuck proot and proots mama (#2300)
* feat: experimental native userland that runs the Linux side without proot

* fix(native): keep the visible name of link2symlink binaries

* fix(ci): run the native hook build through bash

* fix(native): address review findings in the hook and the ctl fallback

* fix(ctl): use the broadcast fallback only when content is missing

* fix(native): survive Android's seccomp filter in ld.so, libc and posix_spawn children

* docs(native): how to test under Android's seccomp filter

* fix(session): hide an unlistable /sys/bus/pci/devices so libpci does not abort Chromium's GPU init

* fix(native): nested syscall() redirects no longer loop with libblsession's openat2

* fix(native): controllers reach Steam and the install-script evaluator no longer blocks game moves

* fix(native): write the hook's NUL character literals as escapes

* fix(native): proot fast path turns off and greys out while running without proot

* fix(native): D-Bus logins work and Xwayland still admits root once peers read as root

* fix(native): scripts on session mounts outside the rootfs run with the rootfs shell

* fix(native): games on the SD card library start in their own folder

* fix(native): Wine drive links proot made point at their real places

* fix(native): at-calls, uid lookups and relative programs follow the session's mounts

* fix(native): close-from in spawned programs covers what is open at the spawn

* fix(native): service names resolve and the DNS fallback answers what was asked

* build(native): the hook is built with its Go support and tracked by the preload recipe

* fix(native): drive links into identity mounts stay, and relink and seccomp patch failures are logged

* fix(native): seccomp-sim's filter holds every allowed call

* fix(ctl): the broadcast fallback only stands in for a missing content command

* fix(native): pre-2.33 glibc stat calls present owners as root too

* fix(native): res_init and res_ninit call glibc, so Steam's store resolves names

* fix(ci): a preload lock older than a new output rebuilds that group, and generated headers are not hashed

* fix(native): a second seccomp patch run leaves other system calls alone

* fix(native): the terminal and bottom desktop keep proot's environment in a native session

* fix(native): any number of session mounts, and openat2 relative to a mounted folder

* fix(agent): a native command's stdin is written beside its timeout

* fix(ctl): native rejects --env without = and timeouts outside 0-600 s

* fix(native): the resolver fallback has the network's IPv6 servers, and lookups keep the fallback's validation errors

* fix(native): a spawn whose dup2 source close-from closed fails with EBADF

* fix(native): __getcwd_chk checks its buffer, and seccomp-sim -a takes only call numbers

* feat(native): guests see the session hostname from uname and gethostname

* feat(native): the terminal and bottom desktop run without proot

* fix(components): an origin APK without a newer preload output still proves the rest

* fix(native): seccomp-sim -a rejects empty fields

* fix(native): gethostbyname no longer answers failed lookups with Ubuntu and Debian addresses

* feat(native): store installers, root helpers, Comet and Xodus run without proot

* fix(native): the hook finds libraries, preloads and itself through a sandbox's mounts, and ldconfig -C makes its cache

* feat(native): Flatpak sandboxes run without proot, and apply_extra runs without a runtime

* fix(native): remove, glob and nftw take guest paths, and links in a mount are followed as the guest sees them

* fix(native): Flatpak sandboxes get the session's mounts, their libraries' RUNPATHs, and no rootfs-only settings

* fix(native): gethostbyname asks the network's DNS servers, nftw of / gives single slashes, and the library path fits mapped directories

* fix(native): a native process starts in its working directory's real place

* fix(native): the library path given to ld.so holds whole directories only

* fix(native): Wine drive links keep their guest paths, so proot finds the library after a native session

* fix(native): droiddeckctl native prepares the rootfs with the setting off

* fix(native): a SysV segment stays while any process has it attached

* fix(native): DNS lookups ask the network's own servers before public ones

* fix(native): a program's own seccomp traps reach its own SIGSYS handler

* feat(native): run native sessions under a device's own app seccomp filter for testing

* fix(native): SysV attaches and collection take a lock, a failed mark fails the attach, and collection closes its directory

* fix(native): shmat fails when it cannot take the segment lock

* fix(native): the SysV segment lock is retried when a signal interrupts it

* fix(native): process tools see the session's processes as root's

* fix(native): native processes are named after their program, not ld.so

* fix(native): a session's helpers keep the mode the session started in

* fix(native): a keyed SysV segment stays until IPC_RMID

* fix(native): a rootfs link to an absolute guest path is followed in the guest, so Wine finds its z: drive

* fix(native): a sandbox whose loader can't be patched runs under proot

* fix(native): creating a private SysV segment also collects stale ones

* fix(native): a session stopped during startup forgets its mode

* fix(xbox): a native backend is authorized by its process ancestry

* fix(native): Wine's drive links are followed in the guest from any prefix

* fix(native): only the startup that owns the running session publishes its mode

* fix(native): a SysV segment stays until IPC_RMID even after its maker ends

* fix(native): a sandbox mount nested in another mount gets its own place

* fix(native): a sandbox whose mount places can't be made runs under proot
2026-11-11 09:13:17 -05:00
Kurt Himebauch 3c96df4ab1 feat(ubisoft): Ubisoft Connect store with native sign-in, library, downloads and launch (#2322)
* feat(ubisoft): sign in to Ubisoft Connect and list the owned library

* feat(ubisoft): download games natively through Demux and Ubisoft's CDN

* fix(ubisoft): show the owned games on the store page

* fix(ctl): reach the agent bridge by broadcast when the shell has no content command

* feat(ubisoft): run Ubisoft games in one prefix with Ubisoft Connect installed and the game registered

* feat(ubisoft): launch games through a native stand-in for Ubisoft Connect's local service

* feat(ubisoft): document the launcher pipes, log why owned games have no download, drop the installer after extraction

* feat(ubisoft): Steam-owned Ubisoft games install through Steam, and the launch session carries the game's application id

* feat(ubisoft): answer Denuvo activations through Ubisoft's activation service and log unanswered pipe traffic

* feat(ubisoft): report installed chunks to the game and skip add-ons the account does not own

* fix(ubisoft): the overlay stays off, Ubisoft's install steps pick Windows components, and shared-prefix games keep their own settings

* fix(ubisoft): write the manifest's registry values, and pick the game's exe over Ubisoft's installer stubs

* fix(ubisoft): stream download slices, guard the launch hook, and keep per-game components in the shared prefix

* fix(ubisoft): direct launches in the shared prefix keep their own game id

* ci(ubisoft): build the launcher service after the prepared components are verified

* fix(ubisoft): verify reused files, drop syncs from a signed-out account, and clear activation requests before launch

* fix(ubisoft): fetch again a reused file whose slices cannot be checked
2026-11-10 15:08:37 -05:00
+1 3733c50902 feat: non-Adreno graphics stack (Venus, open driver pairs, gamescope patches) (#2029)
* Retain compositor resources when GPU completion is unconfirmed

* Stop quarantined GPU callers before retries and mapped writes

* Support Android hardware-buffer presentation over Wayland

* Support PowerVR swapchains and linear image imports in Gamescope

* Bundle verified PowerVR graphics components through existing build preparation

* Detect and select the system Vulkan backend on PowerVR

* Apply PowerVR Steam and game compatibility during normal launches

* Show PowerVR support through existing driver and component controls

* Update existing hardware guidance for system Vulkan support

* Import the shared system Vulkan readiness check

* Discover and propagate the actual PowerVR render node

* Prune obsolete PowerVR runtimes after guest processes exit

* Retain external DMA-BUF ownership after GPU quarantine

* Name the PowerVR HAL selection explicitly

* Isolate component UI tests from application startup

* Cover the inherited FEX preset in PowerVR profile tests

* Log test worker failures for CI diagnosis

* Remove temporary test logging after diagnostic CI

* Prefer system dependencies for the Android Vulkan loader

* Capture raw stderr before test worker termination

* Remove the temporary test worker crash probe

* Import pinned DXVK recipe prerequisite from PowerVR PR570

Copy only the standalone build recipe, source pin, BC-format prerequisite
and presentation-sync patch/check from wallentx/DroidDeck at d0f7264ea8.
Source: https://github.com/Droid-Deck/DroidDeck/pull/570
Preserve that proposal attribution; no Android app, compositor, managed
pack, private data or fork development history is imported.

* Add opt-in low32 GPU mapping adapter and integrity probes

* Add opt-in D3D9 single-sample workaround and fix HUD texel format

* Document verified production game workaround and publication scope

* Restrict reset diagnostics and create private output atomically

* Target official runtime review and document PR570 prerequisite provenance

* Fix runtime tool output isolation, mmap64 chaining and DST ambiguity

* Normalize swapchain backbuffers for the opt-in D3D9 single-sample path

* Experimental Samsung Xclipse support on the system Vulkan path

Detect Samsung's Vulkan HAL (vulkan.samsung.so) and its sgpu render node
alongside PowerVR, and run Xclipse through the same libhybris bridge:

- VK_LAYER_DROIDDECK_xclipse (tools/linuxfs/xclipse-layer), built with the
  glibc preloads and enabled only on Xclipse: sets bionic's environ for the
  driver's shader compiler, avoids immutable YCbCr samplers in compute
  layouts, forwards the KHR calibrated-timestamp entry points to the EXT
  ones, and reports textureCompressionBC and BC4/BC5 filtering over the
  compat layer's BC emulation.
- Upstream DXVK 2.6.2, pinned as a tar.gz component and selected on Xclipse:
  the driver has 128 bytes of push constants, DXVK 3 needs 256.
- android_wlegl accepts a zero stride and passes it through unchanged:
  Samsung's gralloc records none for GPU-only buffers.

Tested on a Galaxy A56 (Xclipse 540, driver 24.0.560): Steam Big Picture
and PICO PARK (D3D9).

* Xclipse layer: copy the device chain and track samplers per device

- Clear textureCompressionBC in a copy of the device-create chain (every
  structure up to the VkPhysicalDeviceFeatures2 that enables it) instead of
  writing the caller's structure; a chain led by a structure of unknown size
  is passed on unchanged.
- Track YCbCr samplers per device in a growable list, drop them on
  vkDestroySampler and device teardown, and fail sampler creation rather
  than leave one untracked when the list cannot grow.
- Fail descriptor set layout creation with VK_ERROR_OUT_OF_HOST_MEMORY when
  a replacement sampler array cannot be allocated, instead of passing the
  original YCbCr samplers to the driver.

* Xclipse layer: copy any device-chain structure the headers define

The chain copy that clears the emulated textureCompressionBC only knew a
handful of structure sizes, so a valid chain such as
VkPhysicalDeviceTimelineSemaphoreFeatures -> VkPhysicalDeviceFeatures2 was
passed on unchanged and the compat layer refused the bit. Size every
structure that may extend VkDeviceCreateInfo from a table generated from the
Vulkan registry (gen_device_chain.py, Vulkan-Headers 1.3.275, the headers
the build compiles with), and copy onto the heap instead of a fixed buffer.
Only a structure newer than those headers still passes the chain unchanged.

Add a regression test that runs the layer between a simulated loader and a
mock driver: the timeline-semaphore chain, pEnabledFeatures, an unknown
structure, the caller's structures left untouched, per-device YCbCr sampler
tracking with handle reuse, and the KHR calibrated-timestamp forwarding. CI
installs libvulkan-dev for it.

* Mali bridge (#517, by ASEbrahim) on the non-Adreno test branch

Squash of Droid-Deck/DroidDeck#517 (ASEbrahim/DroidDeck mali-bridge @ 6b823341): the Vulkan
bridge (tools/vkbridge) that runs the guest's Vulkan on the phone's own driver, with BC1-BC7,
clip/cull distance, vertex-divisor and depth-clip emulation and dma-heap memory sharing for Mali.
All of the bridge is ASEbrahim's work; squashed only to carry it on this test branch.

Combined with #570 on this branch, a device draws through exactly one path:
- Adreno: Turnip, unchanged (checked first; the bridge setting is ignored there).
- PowerVR / Xclipse with the system HAL and a render node: the system-Vulkan path (#570).
- Anything else with the Setup toggle on (Mali): the bridge.

* Test branch: keep #570's DXVK build recipe so its pinned PowerVR components verify

#602 adds output checks to tools/dxvk/build.sh; tools/powervr/fetch.py pins the exact
recipe the published PowerVR DXVK was built from, so the branch carries #570's copy.

* Test branch: make #570, #602 and #560/#561 pass each other's tests

- tools/dxvk/build.sh: #602's copy (its output checks) and the PowerVR pin in
  tools/powervr/release.json moved to that file's hash; the recipe's outputs are unchanged.
- droiddeck-session: the presentation block sets _bl_cef for #570's PowerVR path, and the UI
  block keeps main's order, defaulting to -cef-use-angle=vulkan when the block has not run.
- Compositor test harnesses: AHardwareBuffer stubs for #570's AHB import, and a stub for
  vkp_effects_forget_image (main #597) that #561's caller harness predates.

* Auto drivers for Mali and Xclipse on the non-Adreno test branch

Auto now picks an open driver pair for the GPU, the way it does for Adreno:
- Mali (no KGSL, /dev/mali0): the GPU's name ("Mali-G720-Immortalis MC12", from the vendor
  Vulkan driver or kbase's gpuinfo) picks the PanVK build: G52-G78 (Job Manager), G610/G710,
  G615 or G720. A Mali none of them covers (G925...), or one whose pair is not published yet,
  draws through the Mali bridge, switched on by Auto.
- Xclipse keeps the system-driver path as Auto; the RADV pair is offered as a manual choice.
- The pairs come from The412Banner/droiddeck-nonadreno-drivers (one pre-release per driver,
  each a DriverBundle zip). Only a device without an Adreno reads that repo.
- The importers accept PanVK and RADV libraries (libvulkan_panfrost / libvulkan_radeon).
- An open driver pair and the Mali bridge are alternatives: a selected pair turns the bridge off.
- TU_DEBUG is only set for Turnip, on an Adreno.

Every new branch is reached only when the device has no Adreno; an Adreno's detection, release
sources, Auto choice and session environment are unchanged.

* Auto: the one-binary PanVK kbase build for Mali v9-v13 on Android 15+

The412Banner/droiddeck-nonadreno-drivers adds panvk-mali-kbase: one PanVK build for
G57/G68/G77/G78, G610/G710, G615/G715, G720/G725 and G925, whose display half needs Android 15.
On Android 15+ Auto prefers it for every Mali it covers; below 15, and whenever its release is
not listed, Auto falls back to the GPU generation's own pair. G52/G76 keep the Job Manager
build; a G715/G725/G925 below Android 15, or any Mali no pair covers, draws through the bridge.
Every pair stays selectable by hand.

* Test: the G925 is in the kbase build's family now

* Offer faux123's device PanVK builds (RP4 Pro, Ace, RG556) as manual pairs for their Mali

* Detailed graphics logs for non-Adreno test reports

- graphics-report.txt in every session folder: the graphics path the session draws with and why
  (Turnip, system Vulkan #570, Mali bridge #517, or an open driver pair with its release), the
  driver mode and choices, what Auto would pick, the GPU (family, Vulkan device name, kbase
  gpuinfo, Android, SoC, kernel), the GPU nodes the app can see (access, device numbers, SELinux
  label; nothing is opened) and the guest's graphics environment. At the end it adds the
  compositor's driver lines from app.log and the game launches that were logged.
- vulkan-summary.txt: the session's own vulkaninfo --summary with the driver as exported, in the
  background with a 10 s timeout.
- Per-game logs under games/<time>-<appid>/: Proton's log, DXVK's and vkd3d-proton's, Mesa's and
  PanVK's startup messages and the launch environment, from droiddeck-game-env. Each file is cut
  to 20 MB and the folder to 120 MB at the session's end.
- The per-game logs and the Vulkan summary are on for a GPU that is not an Adreno, and on an
  Adreno only with Setup's new "Detailed graphics logs" switch. Share logs already takes the
  whole session folder, so all of it is in the zip, scrubbed as before.

* Mali pairs: a stand-in render node, the driver's real library name, and a Mali known by its chip

- Auto on Jim's Dimensity 9300+ chose the bridge: the GPU had no name (the app had not asked
  Vulkan yet and kbase's gpuinfo is unreadable under SELinux), so a Mali-G720 counted as a Mali
  no build covers. The name now falls back to the GPU the chip is known to carry (Dimensity,
  Helio, Kompanio, Tensor); and when nothing names it, Auto asks the vendor Vulkan driver off the
  main thread before it settles for the bridge. Auto logs what it decided and why.
- An open Mali driver pair gets /dev/mali0 as the guest's render node, as the bridge does
  (bindGpuNode, BL_DRM_STANDIN): gamescope needs the node the driver reports. The session shim
  only answers libdrm's PRIME and GEM-close calls on it, which PanVK on kbase never makes.
- A PanVK or RADV runtime half is installed as libvulkan_panfrost.so / libvulkan_radeon.so rather
  than under Turnip's name; Turnip keeps libvulkan_freedreno.so.
- graphics-report.txt names the render node the guest got, adds the session script's driver lines
  and flags a failed Mali-bridge self-test at the top.

* Auto takes the RADV pair on a Samsung Xclipse, with the system route as its fallback

A tester's Galaxy S24 FE (Xclipse 940) boots to Steam with the RADV-Xclipse pair, where the
system-driver route fails at Steam's OpenGL (glamor and GLX cannot load a driver). Auto now
downloads and selects RADV on an Xclipse like the Mali pairs, and falls back to the system route
only when RADV is not published. #570's reset of both drivers at each start no longer undoes an
open driver pair. PowerVR (vulkan.powervr.so) keeps the system driver as before.

* An Exynos with a Mali is a Mali, not an Xclipse

The system route took vulkan.samsung.so as Samsung's Xclipse, but an Exynos 1280/1380 (Galaxy Tab
S9 FE: Mali-G68) may name its HAL the same; with /dev/mali0 present that HAL is not the system
route's, and the device is detected as the Mali it is. The chip table adds the Exynos 1380 and
1280 (s5e8835, s5e8825: Mali-G68).

* Auto fetches the open drivers itself; Xclipse named as such; sound and frame-gen in the logs

- Auto looks online again when its saved release list predates this device's open drivers, so a
  Samsung or Mali phone gets its pair without pressing refresh first.
- A Samsung Xclipse is labelled "Samsung Xclipse" (not PowerVR), with its own support line.
- Per-game logs add audio-streams.txt: PulseAudio's sink inputs 20 s and 60 s into the launch,
  so a silent game shows whether its stream reached Pulse.
- graphics-report.txt states the frame-generation setting and keeps the LSFG gate, FrameGen and
  audio lines from app.log.

* Client sound retries a briefly disconnected AAudio; kbase ioctl refusals explained in the report

A Galaxy S24 FE's session had no sound at all: the DirectAudio sink's AAudio stream opened with
AAUDIO_ERROR_DISCONNECTED while the output route was still changing at session start, and the
plain AAudio fallback failed the same moment. The sink guard now retries DirectAudio, then the
plain sink, five times over ten seconds and says which one it got. graphics-report.txt keeps the
client-sink and kbase lines, and explains a KBASE_IOCTL_MEM_EXEC_INIT EPERM as the kbase driver's
own answer rather than the session's sandbox.

* Mali pairs draw the display on the phone's own Mali driver; Xwayland falls back without glamor

- Testers' Mali pairs got the guest drawing (gamescope on the stand-in node, Zink on PanVK) but
  the compositor's PanVK Android half failed on Mali gralloc: it probes AHB formats 0x38/0x3b that
  mali_gralloc does not have, and "vkCreateImage w/ native buffer failed". Under Auto a Mali pair
  now leaves the display on the phone's own Vulkan driver (dma-buf, modifiers and AHB all pass the
  testers' checks) and runs the pair's PanVK in the guest; the Android half stays installed for a
  manual pick. "Pair in use" now follows the runtime half.
- A session after one whose Xwayland crashed (segfault, then gamescope's X11 I/O error) starts
  Xwayland with XWAYLAND_NO_GLAMOR=1 on a non-Adreno device, and says so in app.log.
- graphics-report.txt names the display driver the compositor used, and keeps gralloc, dma-buf,
  modifier and import lines from app.log and Xwayland's own lines from the session log.

* Non-Adreno sound: the daemon falls back on its own; the build's commit in device.txt and the report

The S24 FE's session still had no sound: its AAudio refuses DirectAudio's low-latency stream with
AAUDIO_ERROR_DISCONNECTED, and the sink guard never got to retry because pactl did not reach the
daemon in time ("the daemon did not answer in 15 s"). On a GPU that is not an Adreno the daemon's
own start-up script now asks DirectAudio again without low latency and loads the plain AAudio sink
underneath it, so a default sink exists whatever pactl does. device.txt and graphics-report.txt
name the build label and source commit, so a log says which build it came from.

* Non-Adreno sound: no second DirectAudio load (it hung the daemon); the plain AAudio sink underneath

On the S24 FE the daemon stopped after DirectAudio's failed low-latency load: the second
DirectAudio load in the same daemon never returned, so nothing after it ran and pactl never
connected. The daemon now loads only the plain AAudio sink (no low latency) after DirectAudio,
inside .nofail, and sets whichever exists as the default.

* gamescope: explicit sync off where the kernel or the stand-in node cannot signal its timelines

Adds tools/gamescope/patches/0126 (GAMESCOPE_DISABLE_EXPLICIT_SYNC=1 for the nested backends) and
sets it on a GPU that is not an Adreno when the kernel is older than 6.6 (no
DRM_IOCTL_SYNCOBJ_EVENTFD) or the guest's render node is Mali's stand-in. The graphics report
lists it with the rest of the guest's graphics environment.

* Test branch: gamescope with the explicit-sync switch (0126), pinned by sha

Built by build-gamescope.yml run 38335744351 from 0136fb61 and published as a component release
of The412Banner/droiddeck-nonadreno-drivers; the apk build fetches it by tag and checks the sha.

* Sound: nothing that can block in the daemon's start-up; Steam's web compositing in software on open pairs

- The S24 FE (US) daemon hung loading the plain AAudio sink from default.pa (its AAudio open
  blocks rather than fails), so it never finished starting. default.pa is back to DirectAudio
  alone inside .nofail; the fallbacks are loaded from the app once the daemon answers, each pactl
  bounded by its timeout (DirectAudio, then the plain sink with and without low latency). The
  guard waits 30 s and logs, with timestamps, how long the daemon took, whether its socket and
  pactl exist, and what it finally got.
- With an open driver pair on a GPU that is not an Adreno, Steam's CEF composites in software
  (-cef-disable-gpu-compositing) with GPU raster kept: RADV on Xclipse showed stale, half-written
  tiles, as #570 saw on PowerVR. The session log says so.

* Mali under Auto: the display always on Android's own Vulkan loader; a reboot hint for refused audio

Axel's Tab S9 FE still drew its display with the PanVK Android half, set by an earlier build, and
stalled at "Waiting for Steam's first frame" on Mali gralloc's format probes. Under Auto a Mali's
display now goes to Android's own loader whatever was set before (DriverMenus resets it, and the
compositor's driver choice ignores a pair's Android half), with no HAL path to find first. A pair
counts as in use when its runtime half is. A manual pick of the PanVK display is kept.
When audio.log shows AAUDIO_ERROR_DISCONNECTED, the report and the client-sink line say Android
refused the output and that rebooting the phone usually fixes it.

* Fix: call SessionPrefs.gpuDriverMode through its instance from Java

* Compositor: log each new dma-buf format/modifier/size it imports (for colour and swizzle reports)

Steam's software web compositing already covers every open driver pair, Mali included.

* Open driver pairs: vkd3d-proton reports the driver's caps; buffer attach/release tracing; Mesa log

- With a PanVK or RADV pair (BL_OPEN_DRIVER=1), droiddeck-game-env drops the app-wide
  VKD3D_FEATURE_LEVEL=12_2 and VKD3D_SHADER_MODEL=6_9 so vkd3d-proton reports what the driver has
  (an Unreal DX12 game crashed at start on PanVK with the overrides); a game's own setting still
  wins, and launch.txt records DROIDDECK_VKD3D_CAPS=driver.
- The compositor traces, on a GPU that is not an Adreno, the first 400 dma-buf attaches and buffer
  releases by wl_buffer id and sending line, and logs any release of a buffer already released
  and not attached again (gamescope's "Compositor released us but we were not acquired" on G925).
- Per-game logs no longer set MESA_DEBUG=silent, which kept mesa.log empty.

* Tests: the dma-buf quarantine harness follows send_buffer_release's tracing macro

* Mali (Venus): David Cimo's Venus route sources and recipes

From DavidCimo/droiddeck-mali, branch mali-venus @ 4f838891 (prototype 13; his 12 commits on top
of DroidDeck 0.3.1, GPL-3.0), by David Cimo:
- tools/venus: Mesa Venus vtest patch (incl. the per-queue submit_mutex fix and the cached-memory
  frame readback), virglrenderer Android patch, the Mali compatibility layer (the six features
  DXVK needs: fill modes, multi-viewport, robustness2, clip/cull distance, BC1-BC7) and his tools.
- tools/linuxfs/preload/venus.c: keeps Zink's kopper under Venus.
- tools/gamescope/patches/0127 (his 0114): shm buffers uploaded on every commit.
- docs/development/mali-venus.md: his design notes.
- tools/venus/ci-build.sh and build-venus.yml: his build recipes as one CI job that packs the
  component (vtest server, ICD, layer) as venus.tzst for the apk.

Co-authored-by: Dersim Abbas <derabb0620@gmail.com>

* Mali (Venus) as a selectable route, by David Cimo

Ports David Cimo's Venus route (DavidCimo/droiddeck-mali, mali-venus @ 4f838891) onto this branch
as "Mali (Venus)", one tap in Setup next to the Mali bridge:
- VenusComponent (his): stages the Venus ICD and runs virglrenderer's vtest server over the
  phone's own Vulkan driver; the guest gets VK_ICD_FILENAMES, VN_DEBUG=vtest, VTEST_SOCKET_NAME and
  MESA_VK_WSI_DEBUG=sw.
- Selection rule: Venus when Setup's "Mali (Venus)" is on, on a GPU that is neither an Adreno nor
  a system-driver one (PowerVR, Xclipse), and the apk carries it. It then replaces the Mali bridge
  and any open driver pair for the session, and the display stays on the phone's own driver.
  Auto is unchanged (the PanVK pair). Adreno never takes any of it.
- The compositor enables only the dma-buf extensions the driver has, logs a vendor driver's
  extension list, and gives a zero row pitch a packed row (his vk_present.c changes).
- The Mali compatibility layer is staged into the runtime on non-Adreno devices only.
- The component (vtest server, ICD, layer) comes from build-venus.yml as venus.tzst, published on
  The412Banner/droiddeck-nonadreno-drivers and pinned by sha in tools/venus/release.env.
- gamescope 3.16.29-p15 adds his shm upload patch (0127) next to 0126; pinned by sha.
- device.txt and graphics-report.txt name the route.

Co-authored-by: Dersim Abbas <derabb0620@gmail.com>

* Mali: Venus gamescope without robustness2, the bridge switch honoured and kept, clean driver switches

- gamescope 0128: VK_EXT_robustness2 is optional. A Mali driver through Venus has none, and
  gamescope stopped at "Missing required extension: VK_EXT_robustness2" (the wayland_surface error
  after it was only the knock-on). Without it, unused descriptor slots point at a 1x1 texture
  instead of VK_NULL_HANDLE. (David Cimo's Pixel 9 driver has robustness2, so his build never
  needed this.) gamescope component rebuilt and pinned by sha.
- Open-files limit: on a GPU that is not an Adreno the session script raises the soft limit to the
  hard limit for the whole session tree (a PanVK-kbase session froze on "Too many open files"
  after ten minutes) and graphics-report.txt shows before and after. A stopgap; the driver leak is
  fixed separately.
- The Mali bridge switch is honoured: when on and usable it wins over a PanVK pair for the
  session (Venus still wins over it). Selecting a pair no longer switches the bridge off, which is
  why the switch did not survive an app restart (Auto re-selected the pair at start). Setup says
  when this phone cannot use the bridge at all.
- Driver switching: picking a Mali pair by hand keeps the display on the phone's own driver, as
  Auto does (a PanVK Android display stalls on Mali gralloc at "Waiting for Steam's first frame",
  and stayed set after switching back); installing a bundle again replaces it instead of adding a
  "-2" copy beside the old one.
- graphics-report.txt names the runtime driver's folder and libraries, and the previous session's
  drivers, flagged when they changed.

* gamescope p16 (0128 robustness2 optional), pinned by sha; graphics-report says who chose the display driver

* Bundle replace only for open non-Adreno pairs; Adreno bundles keep their numbered copies

* Rebuilt open drivers reach phones: re-downloaded when their digest changes, release list hourly on non-Adreno

The non-Adreno driver releases replace their assets in place (the PanVK kbase sync-file fd leak
fix). A download is now remembered with its GitHub sha256, and an asset whose digest changed
counts as not installed, so Auto fetches it again and the bundle replaces the old one; the release
list is re-read after an hour instead of a day on a GPU that is not an Adreno.

* Mali compatibility layer: multiDrawIndirect, depthClipEnable and robustness2 for DXVK on Venus

A Galaxy A15 (Mali-G57) on Venus got gamescope and Steam up, but DXVK refused the GPU:
"Device does not support required feature 'multiDrawIndirect'". DXVK 3.1 stops at the first
missing feature, so the rest of its required list (dxvk_device_info.cpp) was checked against what
the testers' Mali drivers cannot have, and covered in the same pass, on top of David Cimo's six:
- multiDrawIndirect: reported, taken off the device, and an indirect draw with drawCount > 1 is
  recorded as single draws at offset + i * stride; maxDrawIndirectCount reported as 2^30.
  (drawIndirectCount is optional for DXVK and stays as the driver says.)
- VK_EXT_depth_clip_enable (required): offered when the driver lacks it; depthClipEnable off in a
  pipeline becomes depthClampEnable, and the extension's structures never reach the driver.
- VK_EXT_robustness2 missing entirely (gamescope saw the same on these phones): offered with
  robustBufferAccess2 (as robustBufferAccess, as before) and nullDescriptor, emulated by small
  stand-ins (a zeroed buffer, a 1x1 image) for VK_NULL_HANDLE buffers, image views and vertex
  buffers in plain, template and push descriptor writes and vertex-buffer binds. A null
  texel-buffer view stays null (its format is not known).
The layer logs every feature it emulates per device.

* Venus component 22694a38 (layer: multiDrawIndirect, depthClipEnable, robustness2), pinned by sha

* Non-Adreno round 3: bridge images, Venus GL, shared buffers, Steam GPU drawing

- vkbridge: the memory model skips lazily allocated types. Mali-G615 lists
  DEVICE_LOCAL|LAZILY_ALLOCATED first; taken as the device-local type it left
  images with no memory type (self-test "image clear/draw: setup" FAIL,
  gamescope "No compatible image memory type").
- Session: a bridge self-test that fails images or draws falls back to the
  runtime's driver pair for that session (BL_VKBRIDGE_FALLBACK_ICD); Setup
  and the graphics report say so.
- Venus: the Mali compatibility layer runs in every process but gamescope
  (DROIDDECK_MALI_COMPAT=1), so Zink in Steam and Xwayland gets
  nullDescriptor; null texel-buffer views get stand-ins too. Without
  nullDescriptor the session's GL goes to llvmpipe.
- gamescope 0129: a refused compute pipeline is named with its SPIR-V
  capabilities.
- Compositor: a buffer on two surfaces at once (gamescope's planes while a
  notification is up) is released only when the last one lets go.
- Open driver pairs: processes.txt timeline for the first three minutes;
  Setup "Steam interface: GPU drawing" turns off software web compositing.

* Pin gamescope p17 (0129) and Venus 55b30fbf

* Non-Adreno round 4: Mali GPU-drawing block, gamescope YCbCr retry, GL probe, bridge draw sync

- Setup's "Steam interface: GPU drawing" is hidden on Mali and a saved "on" is
  cleared: with PanVK it rebooted an Edge 60 (G615) as steamwebhelper started.
  Kept for Xclipse.
- gamescope 0130: a composite pipeline refused at start-up is retried with a
  layout without immutable YCbCr samplers (G615 through Venus refused every
  composite shader; their SPIR-V has no Float16, so fp16 was not the cause).
- Venus: droiddeck-glprobe opens GL through GLX the way the Steam client does,
  with the loader's reasons logged; when it fails the client's GL goes to
  llvmpipe.
- vkbridge self-test: the draw check waits for the render pass's stores before
  copying out (a tiler read zeros without the dependency).

* Pin gamescope p18 (0130)

* Non-Adreno round 5: gamescope memory fallback, Venus destroy guard, OpenAL depot

- gamescope 0131: DEVICE_LOCAL|HOST_VISIBLE requests fall back to host-visible
  memory (the bridge's unified-memory model lists none with both), with the
  request, type bits and memory types logged on failure; 0130's probe pipeline
  is kept and skipped when forced.
- Venus: GAMESCOPE_NO_YCBCR_SAMPLERS=1 from the start; virglrenderer ignores
  the destroy of a pipeline it never created instead of marking the context
  fatal (vtest server exit 137 on a Mali-G615).
- Steam's OpenAL depot (229020) maps to oalinst_dll, so games that list it
  (Hotline Miami) get OpenAL in their prefix.

* Pin gamescope p19 (0131) and Venus 6199a93f

* Non-Adreno round 6: PowerVR PRIME on old kernels, thermal, hang-up names, bridge flippable images, Venus Zink variants

- drm.c: PR #570's current PRIME ownership check (F_DUPFD_QUERY, then kcmp,
  then the F_SETOWN_EX owner marker): on a kernel before 6.10 with kcmp
  blocked, drmPrimeFDToHandle answered EOPNOTSUPP for every PowerVR import
  (Pixel 10, Android 17: Zink "failed drmPrimeFDToHandle", Xwayland error 7,
  gamescope X11 I/O error). The Mali stand-in node is kept.
- A /sys/class/thermal the kernel will not list gets an empty directory, so
  mangoapp no longer aborts on it.
- gamescope 0132: a hung-up waitable is named before the abort; 0133: the
  bridge's flippable images use supported-but-unexportable modifiers instead
  of asserting.
- Venus: when Zink does not start, the loader's view is logged and Zink is
  retried without ZINK_DEBUG=compact, with ZINK_DESCRIPTORS=auto and without
  kopper before falling back to llvmpipe.

* Pin gamescope p20 (0132, 0133)

* Non-Adreno round 7: bridge command-buffer guard, Venus Zink trace, PowerVR mangoapp, Xclipse label

- vkbridge server: every command buffer a device hands out is tracked; Begin,
  End (stream replay) and QueueSubmit refuse one it never allocated or has
  freed (VK_ERROR_DEVICE_LOST, logged) instead of passing it to the driver,
  where Mali's vkBeginCommandBuffer crashed the app (SIGSEGV 0x10, Steam's
  Zink on an Edge 60 and a Tab S11).
- vkbridge server: a fault inside the driver while serving a request cuts
  that client off (SIGSEGV/SIGBUS guard per request) and leaves its objects
  alone; the app and the other clients carry on. Other faults go to the
  previous handler.
- vkbridge server: the pipeline cache directory is copied; the JNI string it
  pointed at was freed (garbage in the cache path).
- Venus: DROIDDECK_MALI_COMPAT_TRACE=1 makes the Mali compatibility layer log
  what an application queries before vkCreateDevice; a failed glprobe adds a
  traced run and vulkaninfo --summary under the same environment.
- PowerVR: mangoapp draws with llvmpipe (Zink could not make it a swapchain
  and it crashed at every start).
- Setup: an Xclipse GPU reads "Experimental: RADV or the system Xclipse
  driver", not the PowerVR line.

* Pin Venus e40cd361 (layer trace)

* Non-Adreno round 8: bridge sharing fallback and crash detail, Venus diagnostics and 1.2 gate, compositor disconnect reasons, RADV upstream

- Mali bridge: the self-test fails "memory sharing" when the server offers
  no host-visible memory type (no sharing method passed, RP4 Pro G77); the
  session then falls back to the PanVK pair like a failed draw, and Setup
  says the bridge cannot share memory with that driver. Without a pair to
  fall back on, the log says so.
- Mali bridge: a guarded driver crash now logs the sub-command it was in,
  the fault address and pc (library + offset via dladdr), the command
  buffer's level, begin flags, pool flags and state, and the last 32
  replayed commands. Begin on an executable buffer from a pool without
  RESET_COMMAND_BUFFER_BIT is logged once (implicit reset).
- Venus: vulkan-summary.txt gets the device extension list and key
  features (full vulkaninfo in vulkan-full.txt); a failed GL probe runs
  once with VN_DEBUG=init,result, ZINK_DEBUG=mem, MESA_LOG_LEVEL=debug and
  the layer trace into glprobe-trace.txt. The Mali compatibility layer's
  trace covers every pre-device query (devices, memory, queue families,
  formats, image formats, external handles) and names the extensions.
- Venus: off when the phone's driver reports Vulkan below 1.2 (gamescope
  skips such a device); Setup and the graphics report say why and the
  session uses the PanVK pair.
- Compositor: libwayland's own disconnect reasons go to wayland.log, and a
  client's buffer may grow to 1 MB before a slow reader is dropped. A
  session that ended without teardown records Android's exit reasons.
- Drivers: RADV-Xclipse upstream (radv-xclipse-upstream) is a manual
  choice for Xclipse; Auto keeps radv-xclipse.

* Tests: the terminal-callers harness stubs the Quest layer and Cardboard modules and Android's system properties

* Pin gamescope p21 (main's patches + 0120-0133) and Venus a4d47cf3 (layer trace)

* Display driver Auto: Quest's patched Turnip first; the system driver only for a Mali, other GPUs keep the generic fallback

* Open driver pairs: the installed build's version, its upload date, and an Update when the release is rebuilt in place

- The release check records each asset's GitHub id and updated_at; a
  download records them (with the digest) for an open non-Adreno pair.
- Components > GPU drivers: an installed open pair shows its manifest
  version ("android <tag> + linux <commit>"), one not yet installed shows
  "built YYYY-MM-DD". A different digest, asset id or upload time on the
  same tag marks the installed pair "Update available" with an Update
  action: the pair is downloaded again and replaced in place, and stays
  selected only if it was. Adreno and DD-Turnip pairs are unchanged.
- graphics-report.txt: the pair release line gives the installed and the
  listed upload dates and flags a newer build; an exact bundle id match is
  preferred (RADV-Xclipse-upstream is not RADV-Xclipse).

* Session preload: drop oom_score_adj raises so lmkd does not treat games as cached apps

steam-launch-wrapper starts every game with --oom-score-adjust 900; on
Android 900 is CACHED_APP_MIN_ADJ, so lmkd kills the running game first
when memory runs short (and on Linux 6.1 the raise becomes the process's
permanent minimum). A process in the app cannot go below the score it
inherits, so the writes that succeed from inside are raises: every
write-open of /proc/<pid|self|thread-self>[/task/<tid>]/oom_score_adj (or
oom_adj), through open/openat or fopen, goes to /dev/null, logged once.

Based on the oom_score_adj redirect in the preload's opens.c by jdmg28 (https://github.com/jdmg28/DroidDeck-Mali/commit/6be00e00)

* Display: why presenting failed on the session-ended screen, and the display driver that really loaded

- The compositor records why it cannot present (no Vulkan driver loaded,
  vkCreateInstance/vkCreateDevice failed, no GPU or graphics queue, no
  swapchain on the screen with the driver's reason, device lost, GPU work
  unconfirmed). Each is a DD_PRESENT_FAULT line in wayland.log, read back
  over JNI or from wayland.log; the session-ended screen shows it instead
  of a black screen, and graphics-report.txt gets "!! DISPLAY FAILED". A
  first frame or a working swapchain clears it.
- After the display device is created, the driver that answered is logged
  from VkPhysicalDeviceDriverProperties (device, driverName, driverInfo,
  driver id, Vulkan version) and reported in graphics-report.txt. When a
  driver pair's display half was requested but a vendor driver answered
  (adrenotools falls back to the system driver when the requested one does
  not load), it says so.

Based on DD_PRESENT_FAULT in vk_present.c / waylandcomp_jni.c / SessionActivity by jdmg28 (https://github.com/jdmg28/DroidDeck-Mali/blob/arena/cd8ab18a-droiddeck-mali/app/src/main/cpp/waylandcomp/src/vk_present.c)
Based on the adrenotools silent-fallback finding by ryanistr (https://github.com/ryanistr/DroidDeck-Mali/blob/main/docs/development/panvk-mali-g77-bringup.md)

* tests: steam presentation test sets 0.5.0's steam_client

* Non-Adreno: find every driver pair, Venus before the bridge, fix the bridge's begin info

From the logs testers sent since c9ebb247:
- The app read the drivers repo's newest 15 releases only; the component releases (gamescope,
  Venus) pushed every PanVK pair and RADV-Xclipse past them, so Auto found no pair on any Mali
  and switched the Mali bridge on. The non-Adreno repo is now read in one page of 100.
- Auto's stand-in for a missing pair: Mali (Venus) where the apk carries it (Steam runs on it
  from the G57 to the G1-Ultra), the bridge otherwise, nothing on a driver below Vulkan 1.2. It
  no longer clears the Linux driver, does nothing when the list could not be read, records that
  it chose the route, and turns it off again once a pair is installed. A bridge that earlier
  builds left on under Auto is taken as Auto's.
- Mali bridge: vkBeginCommandBuffer sent its begin info without the count the server decodes, so
  Mali got a zeroed begin info with stale flags and crashed at 0x10 (Pixel 8 Pro G715, Lenovo
  G615, Xiaomi G925, Tab S11).
- Mali-G1 series (MT6993, arch v14) known by its chip; its label says Venus, not the bridge.
- A session Android killed for memory is said so on the next start (the S24 FE "crashes" were
  low-memory kills, not driver faults).
- The in-app updater never installs an official (Adreno-only) build over this test build.

* Venus: destroy the device's object mutex after the objects that lock it

Bionic aborts on locking a destroyed mutex ("pthread_mutex_lock called on a destroyed mutex",
Tab S9 FE G68, Pixel 9a), so a client that exited holding objects took the render worker down.
From BattleTempest's DroidDeck-Pixel (kevinbryant551-ux/DroidDeck-Pixel, GPL-3.0).

* release check: a session helper may be extracted to another runtime path

droiddeck-steam-taskset (#570) goes to usr/local/libexec/droiddeck-steam/taskset, where PATH puts
it in front of Steam's own taskset for the client only; 0.5.0's check wanted every droiddeck-*
helper at its own path.

* Venus component 93038b7d (destroyed-mutex fix)

* Update MaliBridge to v0.5.1 (#1135)

* Mali Auto: never leave a session on the runtime's Turnip

Logs from a Retroid Pocket 4 Pro (G77), Galaxy S21 Ultra (G78), Tab S11 Ultra
(G925) and Xiaomi G610 showed Auto ending on the runtime's Turnip, where
gamescope finds no GPU ("failed to find physical device"): after a pair's
trial rolled back, when the pair is on the bad list, and when the driver
list could not be read. In each case Auto now switches on its stand-in
(Venus, or the bridge) when no open driver, Venus or bridge is set.
Under Auto the Mali display also leaves any imported driver, not only a
pair's PanVK half (a lone PanVK_G720 import gave a black screen).

* Mali bridge: MaliBridge 224b6e36 (v0.5.3 source, by ASEbrahim)

From c65062b6 (v0.5.1). v0.5.2/v0.5.3: Steam mode on older Mali drivers
(G77/G710/G610/G57) gets past "starting display and audio", OpenGL 4.6 on
most Malis through dynamic-rendering and vertex-format emulation, Dimensity
9500 texture fixes, async pipeline compile, RE2 flicker on G925, DirectX on
the Pixel 8 family. JNI entry and session env names are unchanged.

* gamescope 0134: linear when the backend offers no modifier; Venus on Mesa 26.2.5

0134: CVulkanTexture::BInit asserted !modifiers.empty() when the nested Wayland
backend offered no modifier for a flippable image's format (NieR Replicant on a
Pixel 9a under Venus, session exit 134). It now tries linear and logs it.
Venus ICD: mesa-26.2.5 (venus TLS teardown from an unloaded driver, virtio-gpu
devices without 3D skipped); virglrenderer stays on 1.3.0.

* gamescope p22-nonadreno (0134 + main's 0116 cursor fix; build run 40159674290)

* CI: build the non-Adreno additions before the preload check

* build.gradle: drop verifyHandModel (main moved the hand models into the tracking pack)

* Venus component venus-proto13-e24ed8ab (Mesa 26.2.5; build run 40161447324)

* CI: fetch the Mali (Venus) component again (lost when build.yml took main's jobs)

Build a7a1ebf1 (run 40161734101) had no assets/venus or libvirgl_test_server.so, so
VenusComponent.available() was false and Auto's stand-in would have been the bridge.

* Auto: the Mali bridge stands in on a Vulkan 1.1 Mali driver

Auto refused both Venus and the bridge when the phone's driver reports Vulkan
below 1.2, so on the G77, G78 and G610 (r32p1/r38, Vulkan 1.1) a rolled-back or
missing pair left the runtime's Turnip: "failed to find physical device"
(Retroid Pocket 4 Pro after the JM pair's trial, Xiaomi 13T). Only Venus needs
1.2; the bridge raises the driver for gamescope and runs on these phones. A
Venus switch that cannot start on such a driver no longer counts as a route.

* Tests: stub main's g_release_unpaced in the DMA-BUF quarantine probe

The probe copies release_buffer out of compositor.c; since the merge it reads
g_release_unpaced, which the harness did not declare (setUpClass failed to compile).

* Update MaliBridge to v0.5.4 (ASEbrahim)

48477fe6 (tag v0.5.4): Dimensity 9500 green squares and red-tinted textures,
Balatro and other OpenGL text, fewer crashes when memory runs low, one screen
update per game frame, picture kept after a long background pause (SIGHUP no
longer stops the server), wrong colours after changing graphics settings on
the G720, more in the session report. The app-side changes of his DroidDeck
Mali app (Steam menus setting, per-game setup, Send logs, updates) are not
part of the MaliBridge repository and are not in this build.

* gamescope 0135: linear when the driver rejects every offered modifier

An r32p1 Mali-G610 (Vulkan 1.1) behind the Mali bridge answered both of the
backend's modifiers for AR24 with VK_ERROR_FORMAT_NOT_SUPPORTED, so the first
flippable image failed and gamescope asserted in vulkan_create_flat_texture
(Xiaomi 13T). Linear, when offered, is tried anyway; the bridge makes such
images without the handle type and exports them through a dma-heap. If the
image cannot be made, BInit fails as before.

* gamescope: pin the non-Adreno p23 build (0135)

* Tests: the terminal-callers harness stubs take_shm's game-frame detection

Main's take_shm now reads surface damage, parent/role/mapped and the HUD state
to spot game frames; the harness declares them so the copied function compiles
(setUpClass failed in Build APK run 40505286127). No damage, so it stays off.

* gamescope: number the test branch's patches 0920-0935

Main now has its own 0121-0125 (window override walk, host-event wake, commit fences, done at
import, HUD frame times), which sorted in between this branch's 0120-0135. The branch's sixteen
patches move to 0920-0935 with the same last two digits, so they apply after main's set and stay
clear of its next numbers. Applying all 38 to gamescope 3.16.29 in the old order and the new one
gives the same tree (checked with patch -p1, as build-in-arch.sh does). PATCHES.md and the one
code comment that named 0130 follow the new numbers.

* check_session_assets: helpers count in usr/local/bin or the Steam taskset folder only

The merge kept this branch's pattern (any usr/ target), which main's new test rejects: a helper
extracted into a subfolder of usr/local/bin must not count. The pattern now takes usr/local/bin
under any name, as on main, plus usr/local/libexec/droiddeck-steam for droiddeck-steam-taskset.
Every helper in the overlay is still found extracted in SessionFiles.kt.

* gamescope: pin 3.16.29 p24 (non-Adreno)

Built from this branch @d13b9d88 (run 41119284267): main a5b035b2's patches plus this branch's,
now 0920-0935. All 38 applied, gamescope's tests 2/2. sha256 1d1088d4.

* Update MaliBridge to v0.5.4.2 (ASEbrahim)

95a1e600 (tag v0.5.4.2), was v0.5.4 (48477fe6). For the bridge: memory sharing without the handle
type where that is all the self-test passed (unflagged buffers, client/memory.c, server_memory.c),
the older-Mali (G52-G77) game-mode start fixes, the r38p1 beta geometry-shader erratum (gs_hide),
present timeouts and an API cap per process. Client, server and the in-app library all build from
the submodule with MaliBridge's own scripts, which did not change; the protocol only gained
fields an older server sends as 0.

* Session end: the compositor fault query no longer fails the hint when the native library is absent

presentationFault() (6778a224) called WaylandCompositor.compositorFault() directly. Main's new
SessionEndHintTest runs sessionEndHint() in Robolectric, where libdroiddeckwayland cannot load, so
all six cases ended in UnsatisfiedLinkError (Build APK run 41125749537). The call is now wrapped
like runtimeDamageHint()'s, and without an answer the hint reads wayland.log as before.

* test: stub the GPU busy reader in the caller harness

* fix: release build is not debuggable, Mali uses the system display driver, staging failure ends its own session

* fix: address non-Adreno review findings

* fix: preserve current gamescope and GPU warning behavior

* fix: retain graphics failure hints and launch diagnostics

* fix: identify non-Adreno GPUs in driver failure hints

* test: harnesses follow the external client barrier and effects upscaling changes

* fix: client sources return to foreign ownership after the overlay draws

---------

Co-authored-by: Leask Wong <i@leaskh.com>
Co-authored-by: wallentx <william.allentx@gmail.com>
Co-authored-by: Stefan Zijlstra <6730062+stefanz92@users.noreply.github.com>
Co-authored-by: William Allen <wallentx@users.noreply.github.com>
Co-authored-by: The412Banner <the412banner@users.noreply.github.com>
Co-authored-by: Dersim Abbas <derabb0620@gmail.com>
Co-authored-by: root <root@localhost.localdomain>
Co-authored-by: ASEbrahim <ahmadsmain@gmail.com>
Co-authored-by: The412Banner <the412banner@gmail.com>
Co-authored-by: The412Banner <205237651+The412Banner@users.noreply.github.com>
2026-11-05 22:06:16 -05:00
Kurt Himebauch 430af5e22d feat: desktop and scrolling wall live wallpapers (#2012)
* feat: desktop and scrolling wall live wallpapers selectable from Android settings

* fix: render the wall wallpaper off the main thread and give it its own thumbnail

* fix: serialize wall wallpaper visibility and wait out in-flight draws on surface destroy

* fix: restart the wall loop on surface recreation and keep queued visibility changes

* fix: keep the wall loop alive across a failed canvas lock and finish redraws before returning

* fix: post the wall redraw asynchronously on surface change

* fix: redraw the desktop wallpaper logo from the real mark and show game covers on the wall

* chore: mount the git dir in build containers and seed new worktrees from the main checkout

* fix: decode wall covers as RGB_565 and seed worktree assets only into empty dirs

* feat: drop the desktop wallpaper and add frame rate and speed settings to the wall

* feat: wall settings over the live wallpaper with a match-screen rate, and drop the center vignette

* perf: pace the wall on vsync and draw it on a hardware canvas

* fix: stage worktree seed copies and publish them only after they succeed

* fix: point the wall settings link at the activity class so the picker shows the gear

* fix: dedupe wall covers by app and space repeats like the in-app wall, keep the frame loop alive across surface gaps

* fix: show the picker preview behind wall settings and drop the display-rate lookup

* fix: stop wall frame callbacks when the surface goes and only replace an empty seed destination

* fix: skip a seed when the destination reappears before the staged copy is moved in
2026-11-04 11:38:54 -05:00
DroidDeck Merge 8171bf20fd Merge latest origin/main and resolve conflicts 2026-11-02 10:44:24 +08:00
MaxsTechReview c5577478ca chore: remove source comments and unused code, translate every missing string (#1965)
* chore(i18n): translate every missing string in all locales

Adds the 87 strings the bottom-screen Dashboard and in-game stats pages
introduced to all 32 locales, plus the 51 older settings strings Japanese
and the 50 Chinese locales were still missing. Every locale now has all
3456 translatable strings with the same placeholders as English.

Also translates 36 entries that were still English where the language
does not use the English word, and makes es-419 touch_app_id use the same
%1$s placeholder as English.

* chore(patches): drop comments from the lines downstream patches add

Removes comment lines and trailing comments from lines our patches add,
with hunk headers recounted. Lines that a later patch uses as context
and the upstream backport 0123 are left as they are.

The gamescope, FEX, proot and xodus series still apply to their pinned
sources, and the patched trees differ only in those comments.
test_hud_frames now finds the HUD writer by its first declaration.

* docs: credit Greenlight, Better xCloud, viture-v2, GameNative and DiRTTelemetryErrorFix

These were credited only in source headers and NOTICE files.

* chore: remove source comments and unused functions

Removes explanatory comments from first-party Kotlin, Java, C, C++,
Rust, TypeScript, Python, shell, Gradle, CMake and workflow files.
License and attribution headers stay, and vendored third-party trees,
generated files and license files are untouched. A shellcheck directive
is kept, and udevmon's fallthrough comments became fallthrough attributes.

Syntax trees and token streams match the previous sources apart from the
comments and Python docstrings. Module docstrings that argparse prints
are kept, and device-smoke.sh prints the same --help text from a heredoc.

Also removes 37 functions and 4 fields that nothing referenced.

* chore: remove comments from the Syncthing integration and the latest main sources

* docs: credit Syncthing and the syncthing-android fork

* chore(i18n): translate the Syncthing strings in all locales

The 107 strings the Syncthing integration added are now in all 32 locales.
sync_web_path_locked names the Syncthing tab by its label.

* docs: credit @xXJSONDeruloXx for the HUD metric readers

The CPU, GPU load, thermal and battery readers come from their
PerformanceHudView pull requests #777, #877, #991, #1406 and #1573.
The FusionHUD credit line is unchanged.

* docs: credit the HUD metric readers to @xXJSONDeruloXx's work
2026-11-01 16:27:27 -05:00
The412Banner 8cb5c2168e feat(hud): GPU load and clock without root where Android blocks kgsl (#1969)
On some Adreno devices (the Retroid Pocket Duo) SELinux hides kgsl's sysfs
nodes from apps, so the HUD showed no GPU % and no GPU clock. Devices whose
nodes are readable keep using them; nothing changes there or on GPUs
without kgsl.

Where the nodes are blocked:

- GPU %: a native reader maps kgsl's memstore read-only (any kgsl client
  may) and samples the ringbuffer start/end timestamps once a millisecond
  while a HUD is shown. That covers all GPU work, Android's own
  composition included.
- Backup for GPU %: an implicit Vulkan layer in the guest
  (tools/gpuload) and the compositor time their own GPU work with
  timestamp queries. It runs until the memstore stamps are seen moving,
  and for the whole session if they never move.
- GPU clock: a small GPU job of our own reads the always-on and RBBM
  cycle counters (libgpuclock).

The HUD diagnostics export lists which source each GPU value came from.
tools/gpuload/build.sh takes the Vulkan headers from build_local.sh's NDK
first and keeps NDK paths with spaces intact.
2026-11-01 20:22:51 +00:00
superboo07 8c09b2c6a8 Intergrated syncthing (#1442) 2026-11-01 12:58:09 -05:00
maxjivi05 0a5955d6fa Merge remote-tracking branch 'droiddeck/main' into feat/malibridge
# Conflicts:
#	README.md
#	app/src/main/java/com/droiddeck/launcher/gpu/TurnipDriver.java
#	app/src/main/java/com/droiddeck/launcher/runtime/BwrapSpawner.kt
#	app/src/main/res/values-ar/strings.xml
#	app/src/main/res/values-b+es+419/strings.xml
#	app/src/main/res/values-bg/strings.xml
#	app/src/main/res/values-cs/strings.xml
#	app/src/main/res/values-da/strings.xml
#	app/src/main/res/values-de/strings.xml
#	app/src/main/res/values-el/strings.xml
#	app/src/main/res/values-es/strings.xml
#	app/src/main/res/values-fi/strings.xml
#	app/src/main/res/values-fr/strings.xml
#	app/src/main/res/values-hi/strings.xml
#	app/src/main/res/values-hu/strings.xml
#	app/src/main/res/values-in/strings.xml
#	app/src/main/res/values-it/strings.xml
#	app/src/main/res/values-ja/strings.xml
#	app/src/main/res/values-ko/strings.xml
#	app/src/main/res/values-ms/strings.xml
#	app/src/main/res/values-nb/strings.xml
#	app/src/main/res/values-nl/strings.xml
#	app/src/main/res/values-pl/strings.xml
#	app/src/main/res/values-pt-rPT/strings.xml
#	app/src/main/res/values-pt/strings.xml
#	app/src/main/res/values-ro/strings.xml
#	app/src/main/res/values-ru/strings.xml
#	app/src/main/res/values-sv/strings.xml
#	app/src/main/res/values-th/strings.xml
#	app/src/main/res/values-tr/strings.xml
#	app/src/main/res/values-uk/strings.xml
#	app/src/main/res/values-vi/strings.xml
#	app/src/main/res/values-zh-rCN/strings.xml
#	app/src/main/res/values-zh-rHK/strings.xml
#	app/src/main/res/values-zh-rTW/strings.xml
#	app/src/main/res/values/strings.xml
#	tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env
2026-10-30 11:58:55 -04:00
Kurt Himebauch 0516d023fa feat: art-first library cards with a display menu (#1744)
* feat: art-first library cards with a display menu for size, title and storage

* build: use native arm64 docker on Apple Silicon and add a fast deploy loop

* fix: address review feedback on library cards and local build scripts
2026-10-28 22:40:53 -04:00
Kurt Himebauch 27bdc68867 ci: faster pipeline and fewer network flakes (#1719) 2026-10-28 11:31:20 -04:00
Ahmad Sayed Alsayed Ebrahim a69ce00b6a Build and package the Mali bridge from external/MaliBridge
The server is built with the NDK into libvkbridge_server.so (a program
the app execs from its native library directory); the Linux ICD, its
manifest and the self-test go into the runtime assets with the other
glibc preloads, locally and in CI.
2026-10-26 13:55:13 -04:00
greg 99136ea23b Add support for Java 17 via mise (#1231) 2026-10-21 18:30:34 +00: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 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 a63f3b1fbc Add experimental Xbox and PC Game Pass store support (#1156) 2026-10-19 18:44:10 -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 aecd2ebe1d cleanup (#764) 2026-10-13 21:27:42 -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
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
Phobos665 513ae84612 chore(): Fix the MacOS build process locally. (#637) 2026-10-12 17:11:01 +00: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
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
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
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 <the412banner@users.noreply.github.com>
2026-10-08 12:35:00 -04: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 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
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
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
xXJsonDeruloXx 37316f4677 Add game launcher shortcuts 2026-10-02 23:49:26 -04:00
xXJsonDeruloXx e23cada4ea Add Android clipboard text paste into Steam sessions 2026-10-02 17:29:41 -04:00
Kurt Himebauch 42aff89ffd Merge branch 'main' into feat/performance-fixes-2 2026-10-01 14:38:33 -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
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
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
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
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
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
xXJsonDeruloXx 5110cb6b47 Add optional Steam startup 2026-09-27 09:27:30 -04:00
Kurt Himebauch 93b9be67fc Build local APK with CI payload parity (#20) 2026-09-25 13:20:51 -04:00
maxjivi05 531087be16 Merge Droid-Deck main into fix/proot-performance 2026-09-24 21:27:31 -04:00
xXJsonDeruloXx 3345f1c32b Show build source and worktree state 2026-09-24 09:40:50 -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 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 c03e62d583 Add session suspend modes and audio recovery 2026-09-23 10:51:10 -04:00
xXJsonDeruloXx a1514ab5ad Add reproducible local build and deploy helpers 2026-09-22 22:31:10 -04:00