Twice today a session's Xwayland aborted from one thread ("The futex
facility returned an unexpected error code"), proot lost the tracee, and
the process lived on reparented to init: proot's seccomp filter, with no
tracer left, answers ENOSYS, so Xwayland's main loop logged
"WaitForSomething(): poll: Function not implemented" until something killed
it - 9 GB after Dolphin, 13 GB after PPSSPP. --kill-on-exit and the tree
sweep only reach what proot still traces, and the straggler sweep ran only
at the next session's start.
Now a session stop also kills every process of ours whose executable lives
in the Linux runtime, and bannerlator-session keeps only the first
BL_LOG_CAP bytes (256 MB) of session.log, draining the rest so a writer
that filled it is not killed by SIGPIPE.
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.
The session's proot runs with -i <app uid>, and the rootfs names that uid
root, so the client calls itself root while geteuid() returns the app's
uid. The read-only retry required geteuid() == 0 and never ran: the
Proton Experimental (ARM64) update still failed with Missing file
permissions on wineopenxr.dll (2026-09-26 16:07, 2,471 read-only files).
Also recognise the client by the path it was started by: the kernel's
/proc/self/exe names proot's loader for every process in the session.
The client runs as proot's root, which the kernel does not see, and
leaves many content files read-only; an update that rewrites one was
refused (EACCES) and cancelled with "Missing file permissions"
(Proton Experimental (ARM64), wineopenxr.dll). For the client's own
processes, a refused write-open of a file the app owns now gets the
owner's write bit - on the file, or on the directory a new file goes
into - and one more try. Games and Wine keep ordinary permissions.
With Steam publicbeta 1790380355 the client spent 27 s in its post-logon
compat phase and dropped the install request the session sent 15 s after
sign-in, so a clean install never offered Proton Experimental (ARM64).
Wait for the console log's "Waiting for compat in post-logon took" (90 s
at most), then ask, up to three times a minute apart until the install
starts.
With the check reached from the client's own runs right after login, the Steam client segfaulted
within seconds, four sessions out of four; the same build without the check (test/bisect-no-launch-check)
and the last build before it both reach Big Picture. Now every run that is not a game launch -
the client's probes, app id 0 - is left exactly as before. Each check logs one line.
Steam runs its graphics probe through every registered Proton at once after login, all in
compatdata/0. Prefix system32 links point into the Proton that set it up last, and the other
Protons' Wines, running outside Proton's pfx.lock, wrote their builtin DLLs through them into that
Proton's tree (device: GE's FEX DLLs became Proton Experimental's at session start).
- bannerlator-steam-compat: both wrappers (Valve's depot launcher and the third-party
bannerlator-proton-wrap) give a tool-0 run STEAM_COMPAT_DATA_PATH=.../compatdata/0-<tool>, and
before every other launch check the Proton's files against the app's desired.tsv and put back
anything that differs (same Proton build only), logging to reapplied.log.
- ComponentsManager writes desired.tsv plus unpacked copies of the packages in use into the
runtime after every change; the page and the drawer show 're-applied at launch'.
Tried on device: with a session bus the client still sat on "Switching to Desktop..." and
never reached its legacy path (no "No SteamOSManager support" line, steamos-session-select not
called), so the bus was not what it waited on. Sign-in was unaffected.
This reverts commit 40fc5463f3.
Big Picture's Switch to Desktop asks SteamOS Manager over the session bus before falling back to
steamos-session-select (steamui.so). The Steam session had no session bus, the ask never
finished, and the client sat on "Switching to Desktop..."; the new stub was never called. A
bus with nothing on it answers at once, and the client takes its legacy path.
- 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.
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).
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.
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".
The guide button's toggle returns to melonDS's saved window size. A 512x288 window (1x wide,
16:9) is stretched by gamescope to fill the panel, menu bar and cursor with it; at 2x and up
the menus stay at their real size, tiny on a handheld. Written once, with the layout.
Hybrid repeats the big screen, small, above the bottom one. Horizontal with the top screen
emphasised fills the panel's height with the top screen and centres the bottom screen in the
rest. Installs set up with Hybrid (or still on melonDS's defaults) move once.
- bannerlator-pad-defaults: new installs start on the Hybrid layout with sharp pixels; an
existing install is moved once, only where still on melonDS's defaults (marker keeps later
choices). Guide = HK_FullscreenToggle. The user's DS/DSi BIOS, firmware and NAND are found in
ROMs/nds/{bios,firmware} and filled in only where melonDS has no path.
- Front end starts melonDS with -f, at the panel's own resolution unless the user chose one.
- From the rail: -batch -bigpicture -fullscreen (the same flags as PCSX2's,
src/duckstation-qt/qthost.cpp) - straight into the game, gone when it is quit.
- bannerlator-pad-defaults: a new player starts on DuckStation's first-time setup
with the BIOS folder in the ROMs folder (psx/bios, created, or an existing one),
psx as the game list, Vulkan, the controller on Player 1 and the guide button
on the pause menu (not the analog toggle). An existing install gets the guide
and game-list changes, and once, its own empty BIOS folder moved to the ROMs
folder and Automatic made Vulkan; later choices are kept. Users bring their own
BIOS and games.
Quake III Arena, Team Arena, Return to Castle Wolfenstein and Jedi Academy set their own
resolution and switch the display to it. Under gamescope with the ARM64 Proton that switch is
refused ("calling CDS: failed, bad mode") and the game never gets OpenGL - device-seen on a
Retroid Pocket 5 on Proton Experimental build 25406135 - and a window left at the game's own size
draws small in a corner. Both wrappers (Valve's depot and an adopted GE or CachyOS) now add
+set r_fullscreen 0 +set r_mode -1 +set r_customwidth/height at BL_WIDTH x BL_HEIGHT for those
app ids, unless the user's launch options already set the video mode; the choice is logged.
The same choice as the Steam cog's, in the drawer's Now group, changed while the client runs:
the app writes it to ~/.droiddeck-fill (and again at every session start, so a file an earlier
session left never disagrees), and a watcher in the Steam session hands each change to gamescope
as GAMESCOPE_FORCE_WINDOWS_FULLSCREEN on the root window, which gamescope reads whenever it
changes. The session log says so when it does.
- From the rail ARMSX2 starts the game in its full-screen, controller-driven UI
and exits with it (-batch -bigpicture -fullscreen).
- bannerlator-pad-defaults: a PCSX2 player's settings and memory cards are
copied into ARMSX2 once; a new player starts on ARMSX2's first-time setup with
the BIOS folder in the ROMs folder (ps2/bios, created, or ps2/firmware), the
ps2 folder as the game list and the controller on Player 1; the guide button
opens the pause menu (how a rail game is left) instead of toggling analog.
Users bring their own BIOS and games.
- The catalog can name a file per memory page size ("pages"); the device's own
size picks it (ARMSX2 has 4K and 16K builds).
- On-screen controls: Auto keeps them off the desktop (the screen is a touchpad
there) and shows them for rail games and Steam when no controller is
connected; the drawer offers the setting in every session.
Upstream PCSX2 interprets the PS2's CPUs on ARM64 (pcsx2/arm64/RecStubs.cpp):
NFS Underground 2 ran at 17 fps with one core at 100%. ARMSX2 is the PCSX2 fork
with ARM64 recompilers. It is listed beside PCSX2 for the ps2 folder, installs
from the desktop catalog (package armsx2), and bannerlator-pad-defaults gives it
the PCSX2 profile under its own folder (ARMSX2/inis/PCSX2.ini).
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.
- A program from the rail draws with the desktop's Linux driver instead of the
Steam session's. The driver's shader cache is keyed on its build, so with two
drivers an emulator compiled everything once per way of starting it; seen on
device: desktop on the imported Turnip 26.3.0, rail on the runtime's own.
- RPCS3's default shader mode builds 6650 interpreter pipelines every boot.
bannerlator-pad-defaults starts a new install on Async Recompiler
(multi-threaded) and moves an existing one still on the default once; a
marker leaves any later choice in RPCS3's settings alone. Enum strings from
rpcs3/Emu/system_config_types.cpp.
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.
- 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.
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.
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.
On a fresh install the client starts signed out and drops the
steam://install/4427310 the session hands it, so the Proton Experimental
(ARM64) dialog only appeared after the user restarted the client by
hand. Ported from Bannerlator's session (device-proven there 2026-09-22):
- With no remembered sign-in, a watcher on logs/connection_log.txt waits
for the sign-in to complete and asks the running client again.
- A watcher on appmanifest_4427310.acf waits for StateFlags 4, registers
the compatibility tool again, leaves ~/.bl-restart-once and asks the
client to -shutdown; the start loop sees the marker and starts it once
more (not counted against the update cap), so the tool lists and
defaults are current before the user installs a game.
- The client-branch check runs before every start, not once, since the
client rewrites package/beta during its run.
The loading screen no longer says Steam keeps running in the background:
sessions pause when the app leaves the screen (Background behavior).
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.
Before the client starts, remove .crash, steam.pid and the web helper's
htmlcache Singleton* locks when no client is running - Android ends
sessions without warning and those files leave the next start on the
update spinner or a blank Gamepad UI (SteamOS-ARM-SM8550's RUNSTEAM.sh
does the same). Deck mode exports STEAM_GAMESCOPE_NIS_SUPPORTED=1 so the
QAM's scaling filter lists NIS, and passes gamescope --mangoapp when
mangoapp is present (BL_MANGOAPP=0 turns it off).
frontend/AddedGameArt: cover/poster/boxart/folder-name images, header, hero, logo and icon from
the game's folder (or its art subfolder); otherwise a one-time store search by folder name and
the capsule, header, hero and logo from Steam's CDN into the app's cache (a miss retried after a
week); an icon pulled from the .exe. The rail shows the portrait. The session's listing carries
the paths; bannerlator-steam-shortcuts copies them into userdata/<acct>/config/grid under the
client's names, never over a file it did not write itself, and sets the shortcut's icon. A toggle
in the cog turns the store lookup off.