Commit Graph
38 Commits
Author SHA1 Message Date
Kurt Himebauch e4a7e52f81 Fix nested KWin rendering on Android (#1330)
Select the explicitly configured Android render node for nested KWin 6.8, apply OpenGL settings before Plasma starts, and pin the original signed Mesa 26.2.4 package that passed Thor rendering. Remove build-directory runtime search paths and fail closed on ELF inspection errors.

Native ARM64 build passed. Thor validated a visible nested Wayland terminal, active OpenGL ES/Zink on Turnip, 1280x720 output, and keyboard input. Mesa pin regression tests, launcher and ELF failure controls, shell syntax, and human authorship checks passed. Published desktop pin remains r2; full latest-head CI is pending and maintainer authorized this merge after device validation.
2026-10-22 13:10:16 -04:00
The412Banner db8d1c9910 feat: optional x86-64 Steam client (Valve's Linux client under FEX) beside the arm64 one (#152)
An opt-in second Steam client (Steam settings, Steam client: x86-64): Valve's
x86-64 Linux client, unchanged, runs under FEX in its own home with its own
game library and the same sign-in. Linux builds run directly, so titles such
as Team Fortress 2 and Half-Life use their Linux versions; Windows games use
DroidDeck-Proton as on arm64. The arm64 client stays the default.

- droiddeck-steam-x64: FEX and the x86-64 rootfs from DroidDeck-Components
  (sha256-pinned), client install and sync from Valve's CDN, sign-in shared
  with the arm64 client, its web helper on the GPU (ANGLE GL over FEX's GL
  thunk, zink, Turnip), Proton wrappers for the arm64 client's tools,
  DroidDeck direct for Linux builds, SD-card library (x86-64/ beside the
  arm64 one), repair.
- FEX 2610 built by build-fex.yml with thunks and three patches: an openat2
  fallback without openat2 (proot), newer-clang thunkgen, MAP_32BIT
  exhaustion returning ENOMEM. libblsession resolves RESOLVE_IN_ROOT paths
  inside the root.
- Session: x86-64 gating, shortcuts and compatibility tools per client,
  logs from the client that ran.
- App: client setting with the FEX version, an x86-64 chip on the Games
  header, the active client's library, art, storage, downloads, repair,
  Offline Mode, Decky, desktop mode, optional VAC diagnostics; per-client
  state for art, the content log, repair and leftovers.
- Strings in every locale.
- Review fixes: the library cache is saved only by the newest scan; an
  abandoned session's Steam logs come from the client it recorded; distinct
  VAC console names; FEXInterpreter in CPU pinning; clearer VAC hint in
  pl, ru, uk, cs and bg.
2026-10-17 23:57:52 -04:00
Kurt Himebauch aecd2ebe1d cleanup (#764) 2026-10-13 21:27:42 -04:00
Kurt Himebauch 486032f5db fix: keep the Steam desktop client in KDE (#610) 2026-10-12 06:56:56 -04:00
MaxsTechReview 2a06204eb4 Desktop: run programs, scripts and AppImages; Switch to Desktop without Deck mode (#549)
* Run programs, scripts and AppImages from the desktop

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

* Switch to Desktop from Big Picture without Deck mode

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

* Run x86 programs from shared storage where they lie

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

A Steam library's games are never copied, and a Linux game's launch program
opened from the desktop goes to Steam like a Windows one.
2026-10-10 19:12:20 -04:00
Kurt Himebauch d99ed8e00b fix(desktop): address KDE feedback and keep local builds in sync with CI (#488) 2026-10-09 21:35:37 +00:00
Kurt Himebauch 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 28da3dcbde Desktop: Firefox skips shared-memory seals again (Android 16 tab crash) (#460)
Desktop: Firefox skips shared-memory seals again (Android 16 tab crash, #280 line lost in #252)

Co-authored-by: The412Banner <the412banner@users.noreply.github.com>
2026-10-08 19:45:44 -04:00
0x142857andxXJsonDeruloXx b56c44bd0b Share font defaults across runtime sessions (#252)
* Support CJK text with the device's own fonts

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

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

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

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

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

---------

Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
2026-10-08 04:06:23 +00:00
MaxsTechReview d1760cd49f Draw Steam's CJK and Thai text with the device's fonts (#374)
Steam ships no CJK font and the rootfs only has DejaVu, so a phone set to
Chinese, Japanese or Korean got Steam in that language with every character
drawn as a box. Bind Android's /system/fonts into the guest, index it again
whenever it changes, and prefer the matching Noto CJK face for each language.
2026-10-07 02:23:27 -04:00
The412BannerandThe412Banner 6f6bd35b96 Firefox: skip shared-memory seals (tabs crash on Android 16) (#280)
On Android 16 phones (Galaxy S25 SM-S931B, SM-S948B, RedMagic NP05J)
every Firefox and LibreWolf tab dies at once: Firefox seals the size of
the shared memory it hands a tab, adding the seal fails there, Firefox
only warns, and the tab refuses the unsealed memory ("IPDL protocol
error: Shared memory PlatformHandle is not safe to map") and exits on
signal 11. The window opens; no page ever loads.

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

Co-authored-by: The412Banner <the412banner@users.noreply.github.com>
2026-10-05 13:37:08 -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 HimebauchandMaxsTechReview 4a234b2824 feat: Flatpak apps and a Flathub store (#77)
* feat: run Flatpak apps through a proot bwrap stand-in and add a Flathub store

* feat: give Flatpak apps the KGSL GPU, the controllers and a CPU LLVM can target

* feat: Flatpak games on the desktop, Store apps on the Desktop page, safer remove

* test: cover the sandbox planner and store helpers; docs: explain Flatpak on DroidDeck

* fix: keep controller focus through Store tabs and app pages; show progress per Flatpak step

* fix: let Chromium, Electron and Firefox Flatpaks start without their own sandboxes

* fix: fetch Flatpak's supporting libraries when the runtime lacks them

* fix: add Flathub without a download, log store commands, keep Electron apps on X11 under gamescope

* feat: import ARM64 AppImages from storage and run them from the Desktop page

* fix: report failed Flatpak installs as failures and deploy runtimes that ship a resolv.conf link

* fix: let Flatpak create its repo where Android denies hard links

* fix: point store commands at the current network's DNS

* fix: keep failed Flatpak extensions non-fatal, give Electron AppImages a bus, X11 and time to relaunch

* docs: AppImage import and what x86 apps would take

* feat: put the Flathub Store and AppImage import behind beta toggles in Setup

---------

Co-authored-by: MaxsTechReview <maxjividen@gmail.com>
2026-09-30 00:35:49 -04:00
xXJSONDeruloXx f87289f75b Desktop: hand labwc's cursor to the app, so full-screen games scan out
The desktop forced a software cursor (WLR_NO_HARDWARE_CURSORS=1) because
labwc's cursor never showed when handed to the app's compositor. With a
software cursor wlroots never scans out: every frame of a full-screen game
was composited by labwc first ("Direct scan-out disabled by software
cursor", 2284 times in one Adreno 825 report).

Why the cursor never showed: wlroots' Wayland backend attaches and commits
its cursor surface before wl_pointer.set_cursor gives it the cursor role,
and sets it again with nothing new committed each time the pointer enters.
The compositor released a role-less surface's buffer on commit, so the
cursor surface had no image by the time it became the cursor.

- A role-less surface's last buffer (cursor-sized only) is held until the
  next commit; set_cursor publishes it, and a repeat set_cursor of the
  surface already shown unhides its last image.
- A dma-buf cursor is read back after the client's implicit render fence,
  as the zero-copy path waits for it.
- The desktop no longer forces a software cursor.

Device-tested on the AYN Thor (Adreno 740, 1920x1080): labwc's own cursor
is drawn, with its shapes (I-beam over a terminal); wlroots logs "Direct
scan-out enabled" for a full-screen glxgears. At the same 60 fps on screen
the GPU is 3.0 % busy against 4.6 % with the software cursor, both at
550 MHz.
2026-09-28 14:33:08 -04:00
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
The412Banner 7c6df51bfa Steam desktop UI on the desktop; Big Picture's Switch to Desktop works
- The front end's "Steam Desktop UI" started a Steam session without -gamepadui, and the client
  put itself into Big Picture under gamescope anyway. It now opens the desktop with Steam's
  desktop client started in it (BL_DESKTOP_STEAM, the desktop's autostart, the same
  bannerlator-steam-launch the menu's Steam entry uses).
- Big Picture's power menu "Switch to Desktop" runs steamos-session-select, which did not exist,
  so it sat on "Switching to Desktop..." for ever. The new stub leaves the app a request
  (session=desktop in the steam-launch file); the app ends the Steam session and starts the
  desktop with Steam's desktop client open.
2026-09-25 08:59:06 -04:00
The412Banner 09e5172052 Desktop: centre melonDS's 800x600 window
AutoPlace on its own left the resized window in the top-left corner; with policy="center" it
opens in the middle of the desktop. Tried in a running desktop (labwc --reconfigure).
2026-09-25 08:36:04 -04:00
The412Banner 132b434d78 Desktop: melonDS opens as an 800x600 window, centred
melonDS remembers a 1x window for the front end's full-screen toggle, too small on a desktop.
A labwc window rule (only the desktop reads rc.xml) resizes it to 800x600 on open and places
it again by the desktop's centre policy.
2026-09-25 08:28:56 -04:00
The412Banner 347a5a4dcb Desktop: Qt falls back to X11, so melonDS opens from the menu
The desktop set QT_QPA_PLATFORM=wayland for every program. The melonDS AppImage bundles only
Qt's xcb plugin, so from the desktop it exited at once: "Could not find the Qt platform plugin
wayland ... Available platform plugins are: xcb" (exit 127). "wayland;xcb" keeps LXQt on
Wayland and lets such an AppImage use Xwayland. Proven in a running desktop: labwc mapped
"melonDS 1.1".
2026-09-25 08:18:43 -04:00
The412Banner 6192d5be2c Desktop: Vulkan by default; a renderer that fails to start is remembered
vulkan is the default desktop renderer (settings list it first). When labwc
cannot start on a GPU renderer the desktop falls back to pixman as before, and
now records the renderer with the patched wlroots and Linux driver it failed
on, so later desktops go straight to pixman instead of waiting 20 s each time;
an app update, a different driver, or choosing a renderer again in the settings
(which deletes the record) retries.
2026-09-23 22:49:50 -04:00
The412Banner ae863cc613 Controller profiles for every desktop emulator; PC keyboard under half the screen
bannerlator-pad-defaults now covers DuckStation, PCSX2, Dolphin (GameCube and
Wii), Cemu, melonDS, PPSSPP and RetroArch as well as RPCS3, from each project's
source: files an emulator would otherwise fill with keyboard-only defaults are
seeded when missing (Dolphin, Cemu, PPSSPP, RPCS3), or given SDL bindings in the
config the emulator wrote itself when nothing binds a controller yet
(DuckStation, PCSX2 with its SettingsVersion, melonDS). RetroArch gets an Xbox
360 profile for its sdl2 driver and is moved off udev, which finds no pads in the
runtime. The desktop re-runs it every 30 s, so an emulator's first config is
covered for its next start.

The PC keyboard takes 47% of the screen height, header included.
2026-09-23 22:20:04 -04:00
The412Banner a6bab67508 Desktop programs: controller seen by every emulator, Player 1 profiles, big-core pinning
- The pad keeps Steam Input's virtual-gamepad identity only in Steam sessions.
  SDL ignores a Steam virtual gamepad unless run under Steam, so on the desktop
  and from the rail every SDL emulator saw no controller at all; there it is a
  plain Xbox 360 pad now, which SDL and evdev map by themselves.
- bannerlator-pad-defaults writes Player 1 on the controller for emulators that
  have no profile yet (RPCS3 first: without one it takes the keyboard). Run at
  desktop start and before a program from the rail; never overwrites.
- The desktop and everything started from it, and a program from the rail, run
  on the game cores when chosen, else every core but the slowest cluster
  (program_cores); BL_PIN_CORES=0 turns it off. Game cores reach every mode.
2026-09-23 22:06:47 -04:00
The412Banner b9ec8458f8 Desktop: remote runs from ~/.cache/droiddeck/run behind ~/.droiddeck-remote-run
For driving the desktop from a debugger: a script dropped there runs once inside
the session. Off unless the marker exists.
2026-09-23 21:37:16 -04:00
The412Banner 60d53fda47 Desktop GPU: full labwc log on the GPU renderers; a labwc that hangs at start falls back to pixman
The vulkan desktop sat on the loading screen: labwc connected and never opened
its window, and it did not exit, so the exit-based fallback never fired. The
launcher now asks labwc's socket for an answer (wayland-info, 3 s) for up to
20 s, then stops it and starts the desktop on pixman. labwc runs with -d on
vulkan/gles2 so desktop.log shows where it stopped.
2026-09-23 21:24:24 -04:00
The412Banner 29597c3fbe Desktop GPU: menu games run in their own gamescope; labwc on the patched wlroots
droiddeck-gpu runs a program in a gamescope of its own on the app's compositor
(wayland-0), full screen over the desktop - the path the rail's play chips use,
where Vulkan works. droiddeck-desktop-gpu gives every Game/Emulator menu entry a
per-user copy through it (auto: while the desktop is on pixman), keeping 'Run in
a desktop window' as an action.

droiddeck-desktop loads /usr/local/lib/droiddeck-wlroots for the vulkan/gles2
renderers and falls back to pixman when labwc stops at start. The build stages
the wlroots release named by tools/wlroots/release.env, once published.
2026-09-23 21:02:24 -04:00
The412Banner 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
The412Banner b28e91128f The desktop uses the client's own game entries; a steam shim hands them to the app
The client already writes one menu entry per installed game (steam steam://rungameid/<id>, with
the game's icon); they were dead for want of a steam command, and r45 added a second entry per
game beside them. Now: steam on the desktop is a shim that hands off to bannerlator-steam-launch
(and is the client itself anywhere else), and bannerlator-desktop-games writes an entry only for
a game the client wrote none for.
2026-09-22 10:48:26 -04:00
The412Banner d0ca41d160 The client's games on the desktop, launching Steam's desktop UI under gamescope
bannerlator-desktop-games writes one menu entry per installed game (both libraries, the
client's own artwork) and links Steam Games / SD Games in the home folder. The entries, and the
menu's Steam / Big Picture ones, run bannerlator-steam-launch: the client cannot draw on the
desktop (labwc, no dma-buf), so it leaves a request in the session directory; the service
watches it (FileObserver), ends the desktop session and hands the activity a Steam session to
start - desktop UI (BL_STEAM_UI=desktop drops -gamepadui) with the game's rungameid URL.
2026-09-22 09:46:19 -04:00
The412Banner aa047ce942 Logs: one folder per session, a device report, and credentials scrubbed
A session used to leave a loose session-<stamp>.log in Download/SteamDeck and
a compositor log in a different folder entirely, with Steam's own logs copied
beside the first one only if the session exited cleanly - which the sessions
worth reading never do. Reports arrived needing three questions answered
before anything could be read: what device, which drivers, which settings.

Now each session claims Download/SteamDeck/session-<stamp>/ and everything
lands in it: device.txt, session.log, wayland.log, steam.log plus steam/ in
Steam mode, desktop.log in desktop mode. The folder is claimed by whoever
starts first, since the activity starts the compositor before the service
starts the session, and the compositor is told where to write through its
environment - it opens that file once, so the service also copies it in at
teardown, which covers the second and later sessions of one process.

device.txt is written before the session starts, so a session that dies in its
first second still says what it ran on: model, SoC, Android, kernel, cores and
ceilings, RAM, the GPU as KGSL names it, the panel, the driver each half of the
session chose, the runtime version, every switch in effect, and whether Android
is set to kill the session's child processes.

Nothing identifying is collected - no serial, no device or advertising id, no
account name, no network names - and Steam's logs are scrubbed line by line on
the way in, with Bannerlator's patterns: session tokens (including the bare
numeric "Using JWT" form), machine-auth GUIDs, WebAPI keys, Guard codes,
emails, and SteamIDs masked to their last four digits so lines still correlate.
loginusers.vdf, config.vdf and the ssfn files are never copied.

Also from Max: a switch to run proot without its seccomp acceleration
(PROOT_NO_SECCOMP=1), for kernels that handle that filter badly - ENOSYS from
calls that plainly exist is the signature, which is what the Fold's storm looks
like; and the phantom-process warning above, which is his "your OS killed it".
2026-09-21 12:56:25 -04:00
The412Banner c18fe1a86e Desktop: turn Firefox's child-process sandboxes off
The screenshot shows the tab crashing while the chrome around it draws text
perfectly, and the 'no fonts' line is an annotation on that crash, logged by
the child. The parent reads the fonts; the child, sandboxed with seccomp and
namespaces that do not behave under proot, cannot, and Gecko takes a fatal
path with no usable face. The prefs for this are advisory and a profile can
override them, so the sandboxes are turned off in the environment the session
hands every program it starts.
2026-09-20 05:33:14 -04:00
The412Banner ce9cf2bdf3 Desktop: name a default font family, and rebuild fontconfig's cache once
Firefox's own network log shows it fully online - DNS answering for both
families, sockets connecting, HTTP/2 and HTTP/3 sessions carrying data - so
the blank pages were never the network. They are the fonts: Gecko reports
'no fonts' with 13 families found, which is what it says when no generic
family resolves to a face. The linked rules map sans-serif onto faces a full
distribution installs; this rootfs has DejaVu and nothing else, so local.conf
names it outright for every generic, and the cache built into the image is
rebuilt once so the new rules are seen.
2026-09-20 05:24:21 -04:00
The412Banner 6c380528f3 Desktop: give fontconfig its default rules
/etc/fonts/conf.d in the rootfs holds only the README: none of fontconfig's
rules that map sans-serif, serif and monospace onto installed faces. Programs
find the fonts and no default font; Firefox logs 'no fonts' and draws pages
without text. The desktop launcher links the 33 default rules in when the
directory has none.
2026-09-20 05:00:24 -04:00
The412Banner 5417587ac4 Desktop: Firefox writes its network log in debug runs
The session reaches the internet over both families (r32's probe), so a
browser saying otherwise has to explain itself: with the debug marker set,
Firefox logs its name lookups, connects, HTTP transactions and link/offline
decisions to ~/.cache/steamdeck/firefox-net.log.
2026-09-20 04:59:35 -04:00
The412Banner 3d4cfededf Desktop: keep Firefox out of Work Offline, and probe the network in debug runs
Firefox decides whether it is online by dumping the routing table over
netlink, which Android denies to apps; it sees no default route, switches to
Work Offline and every page fails while the network is fine. A defaults file
staged into Firefox's pref directory turns that off, and drops the process
sandboxes that have nothing to set up under proot. Debug runs now log a name
lookup and a fetch over IPv4 and IPv6 from inside the session.
2026-09-20 04:51:50 -04:00
The412Banner 36b0bea470 Compositor: tell a nested toplevel the output's size
labwc's Wayland backend, configured with 0x0, opened wlroots' default
1280x720 window and the whole desktop was upscaled from it - the r29 and r30
logs both show the output at 1280x720 whatever rc.xml and wlr-randr asked.
The configure now carries the output's size; gamescope is told the same
numbers on its command line, so nothing changes for the Steam client.
2026-09-20 04:49:13 -04:00
The412Banner 8362f48750 Desktop: name the panel in panel.conf, size the nested output to the app's surface
The panel was there all along, a bare strip at the top: the first package's
panel.conf described a panel it never named in [General] panels=, so
lxqt-panel built itself a default one with no plugins. panel.conf now travels
with the app like the rest of the desktop's defaults, and the old nameless
file on a device is replaced once. The nested labwc output was 1280x720 -
wlroots' default window - with the desktop upscaled from that; rc.xml now
carries the app's surface size and the autostart sets it again via wlr-randr.
Debug runs keep each program's full output under ~/.cache/steamdeck/.
2026-09-20 04:40:23 -04:00
The412Banner f10919d4be Desktop: give pcmanfm-qt's desktop the wallpaper, and a debug marker that logs window mapping
The desktop came up black with the panel alive but unseen. pcmanfm-qt's
desktop surface, unconfigured, paints solid black over swaybg's wallpaper; it
now gets the wallpaper. With ~/.steamdeck-desktop-debug present, labwc logs at
debug level and the panel and file manager log their shell, layer and output
protocol traffic, capped, so where a window went can be read off the session
log.
2026-09-20 04:28:46 -04:00
The412Banner 07dadb13f3 Desktop: clear last session's single-instance state, run under a session bus, stage the launcher from the app
The desktop came up with a wallpaper and nothing on it the second time. The
runtime directory outlives a session, and lxqt-panel and pcmanfm-qt are
single-instance programs that leave a lock and a socket there; finding last
session's, they decided they were already running and exited without a word.
The launcher now clears everything but the compositor's own socket, runs labwc
under dbus-run-session so the panel's menus and the file manager have the bus
they expect, and logs what the panel and the file manager say. The launcher
and labwc's defaults are staged by the app over the desktop package's copies,
so a fix here no longer costs a 426 MB re-download.
2026-09-20 04:16:07 -04:00