A left and a right stick (a base and a knob that follows the finger, clamped) write the pad's
thumb axes the way a physical stick does, and only while they own them, so a physical pad's
sticks are not zeroed by a tap on A. The layout follows where the compositor letterboxes the
picture: on a wide panel showing 16:9 the clusters go in the side bars, on a squarer panel
showing a wide picture they go in the bottom band, and only when neither bar is wide enough do
they overlay the picture as before. The activity hands the picture rectangle over whenever the
surface or the output size changes.
- libpci: an empty file bound over /proc/bus/pci/devices, created at session start, so
Chromium's GPU process stops dying on its way up and CEF stops falling back to SwiftShader
(WinNative deff1ac6 via Bannerlator 8fb668d1: 44 -> 85 fps in the Big Picture library on a
OnePlus 15; not measured here yet).
- Client interface, as toggles on the Performance page and lines in the device report:
mesa_glthread, ZINK_DESCRIPTORS=lazy (now on by default) and MESA_NO_ERROR on; Steam Deck
mode (-steamdeck -steamos3, the script builds the line) off.
- A non-Adreno device is told before the runtime download that a session would be sound over a
black screen, and may still go ahead.
Not carried: Bannerlator's proot fixes live in its proot source, which this repo does not
build (the runtime tarball ships its own); its apk-proot -i guard is moot here (the apk carries
no proot); its session-log head keep is moot (Steam's logs are copied whole).
The drawer Back opens over a session slides in from the right and uses the same rows and
anchored menus as the settings pages: Now (HUD, frame generation, touch, the virtual pad,
keyboard, the client's menu) and Next session (display shape, FEX preset, compatibility tools),
with Send to background and Stop at the end. Steam-only rows show only in a Steam session.
FEXCore presets, Bannerlator's table carried over (core/FexPreset): a Games row in the Steam
settings page and the drawer; the chosen preset's FEX_* variables go into the Steam session's
environment so the games the client launches inherit them. Default = FEX's own defaults, which
is what every session ran on before.
The file manager and the picker stay in landscape like the rest of the app.
The cog's settings window and the Performance dialog become pages in the front end's pane:
one row per setting, its value in a chip, and a small faded menu that opens under the chip
(SettingsWidgets: AnchoredMenu on a Popup with its own position provider, MenuItem, ValueChip,
Choice/Toggle/Multi/Action rows). One menu at a time; the page dims while it is open; the back
key, the rail and the page's Back all leave it. Steam and Desktop each get a Settings entry in
the rail that lights while its page shows, as does Performance under Setup. Frame generation,
Session logs and Start offline open their menu right on their rail row; the frame-generation
dialog is gone.
The manifest now declares the app a game (appCategory, isGame) with a game_mode_config that
welcomes Performance mode and refuses the OS-side FPS cap and resolution downgrade — the
interventions that make a regular flagship's Big Picture crawl while a gaming phone's does not.
The session window asks for sustained performance mode and the panel's fastest mode at its size,
tells GameManager it is in gameplay, and opens a PerformanceHintManager session over the
compositor thread fed with each presented frame's interval against the panel's period. All
version-guarded (6/7/12/13); the session log carries one 'perf:' line saying what took.
The app's own libraries link with 16 KB-aligned LOAD segments (loads unchanged on 4 KB kernels).
Six prebuilt PulseAudio libraries under jniLibs are still 4 KB-only and need a rebuild.
The rail's selection is one pill that springs between rows; sub-lists unfold with
their children staggering in. A page change sinks the old page out and cascades the
new one in row by row over a blurred wash of the selection's own art. Tiles lift,
ring and shine when the pad lands on them and pop a play badge; the launch button
sweeps a sheen on focus and squashes on press. The session activity rises over the
front end and sinks back on finish. Every duration follows the system animator
scale, so 'no animations' in developer options snaps everything.
The activity forwarded mode, program and the Steam extras to the service and dropped
EXTRA_PROGRAM_ARGS, so RPCS3 opened with no game. device.txt names run mode too.
The activity forwarded mode, program and the Steam extras to the service and dropped
EXTRA_PROGRAM_ARGS, so RPCS3 opened with no game. device.txt names run mode too.
The scan goes one folder deeper (a dump comes as a folder named for the game with the image
inside), treats a folder with PS3_GAME as a disc, drops the ".dec" suffix, and lists what RPCS3
has installed on its own HDD (dev_hdd0/game/<ID> with a PARAM.SFO title, ICON0 art and an EBOOT
to boot) - a PSN package installed through RPCS3 is not in the ROMs folder at all.
The emulators' icons are bundled (the runtime keeps them as Papirus SVGs the app cannot draw):
the projects' own from their repositories, PPSSPP's from the runtime, RetroArch's flat icon
from libretro's assets. ROM tiles carry their emulator's icon.
FrontEndScreen replaces the main screen on this branch. Steam's installed games come from the
client's own appmanifests (both libraries, with the library-cache art) and launch straight
into a Steam session with steam://rungameid; the emulators list their games from the ROMs
folder (a system folder as a hint, by extension) and launch under gamescope with the game on
the command line (EXTRA_PROGRAM_ARGS). A session running in the background is the first thing
on the rail and tapping it goes back to it. Landscape: rail beside content; narrow: above.
Every dialog and setting from the main screen is reachable from the rail's lower list.
Handing off from the desktop, the new session claimed the folder the desktop was still
finishing, and the desktop's collector then stopped the new session's app.log capture. The
folder is taken from the shared slot as the session stops and handed to its collector; the
next session's beginOrCurrent finds the slot empty and makes a new folder.
The replaced desktop's proot exits on SIGTERM within a tenth of a second - before the new
session's start thread had numbered itself - so its exit still matched the current number and
ended the new session (4 s on the ended card, then the main menu, with the client audible in
the background). The number is taken in onStartCommand now, ahead of any thread.
Hand-off from the desktop: the new session started, and a second later the old proot - killed
by its own teardown - reported its exit, which the service took as the current session ending
("The session ended 137"). Exits are matched to the session that started the process. The same
race took the new session's log capture and its folder (stopFor / release by folder), and a
finishing activity unhooked the replacement's end listener (compared by identity now).
Hand-off from the desktop to a Steam session: the new activity attached its Surface, then the
old one's surfaceDestroyed detached it - the compositor presented nothing while gamescope sent
frames by the thousand (black screen, sound only). detach() now takes the Surface being destroyed
and ignores one that is not attached. BL_LIBRARY_LABEL was added to the guest list after the
script's mode and reached the Steam client as an argument; it is an environment variable now.
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.
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.
The choice belongs inside the client - the drive drop-down on Install, Settings > Storage - so
the app no longer needs a setting for the common case. Default = automatic (the first SD card
present); the cog keeps Internal only and a chosen folder.
The runtime already knew how to register /mnt/bannerlator-sd with the client; the app never
bound anything there, so the client only ever saw internal storage. The Steam cog's Game storage
section offers every removable volume (through the app's own folder on it, the one place a card
lets a targetSdk-28 app write) and any folder from the File Manager, proven writable first. The
session binds the choice and names it (BL_LIBRARY_LABEL); bannerlator-steam-library registers it,
or forgets it when nothing is bound. The client does the rest: asks where to install, shows both
on Storage, moves games between them. device.txt records the choice.
Two field bundles: an S25 running with Turnip sysmem on (it turns tiled rendering off - a
corruption fix, not a speed setting, and the text did not say so), and a Fold 5 ending, again,
in dozens of 'Function not implemented' lines from proot's seccomp acceleration failing Steam's
xalia helper. The ended-session message now reads the log's tail off the main thread and, when
it finds that storch, names 'Run proot without seccomp' / 'Skip xalia' in Performance.
One chip per emulator the desktop packages installed, starting it fullscreen under gamescope;
the ? says what they are, how to use them (ROMs at /root/ROMs, the phone at /root/Storage) and
why they are here rather than on the desktop (labwc on pixman offers no dma-buf).
RPCS3 on the desktop died at its first frame with VK_ERROR_SURFACE_LOST: labwc composites in
software (pixman) and initialises no dma-buf, so no Vulkan program on the desktop can get a
swapchain. The GPU path that works is gamescope's, which Steam's games already render through.
MODE_RUN starts a program inside the runtime as a session of its own under gamescope (the
script's run mode: AppImage, script or binary), taking Steam's display/driver/HDR settings; the
apps dialog offers ▶ for every installed emulator (AppImages, PPSSPP, RetroArch).
Volume Up/Down/Mute were forwarded to the guest as keys and never reached Android, so the
device's volume never changed during a session; they now fall through, on the media stream.
Display, HDR, Runtime driver, Display driver, Touch & controls, Audio (Steam) / Renderer
(desktop), each found by name instead of by scrolling past the others.
The file picker is now Bannerlator's File Manager itself (files/FileManagerScreen.kt, carried over
whole with its rail, favourites, grid/list, copy/cut/paste, rename, delete, properties) minus what
has no meaning without a Wine container: run, add to shortcuts, C:/Z: drives, the archive
unpacker. StorageRoots, CollapsibleRail, FavoritesStore, PeIconExtractor, OutlinedAlertDialog and
MenuStyle come with it unchanged; FileOps carries the progress copy/move. A Files tile opens it;
its pick mode chooses the ROMs folder and imports drivers.
Play and Desktop each get a cog: resolution cap (1080p default, 900p, 720p, panel), shape, HDR10,
the mode's Linux runtime driver, the display driver, touch, and per mode the on-screen controls +
audio (Steam) or the renderer (desktop). The Graphics drivers and Audio tiles are gone into them.
HDR10: HdrSupport reads the panel's HDR types; the switch is greyed with the reason unless HDR10 is
listed. On, the activity feeds the compositor's gate (nativeSetHdrDisplay + nativeSetHdrRequest),
turns zero-copy on, sets BANNER_WAYLAND_HDR=1, and the session gets BL_HDR=1 (gamescope
--hdr-enabled) and DXVK_HDR=1.
OrphanReaper kills every process of our uid but the app at session start and on an app crash: a
tree left by a killed app held the rootfs, the GPU and Steam's lock, and the next session never
came up (Thor Pro report). The drawer gains Steam menu, which sends the Guide button.
A program's file dialog inside the session never showed the phone: internal storage was bound
at its own path, but proot binds do not appear in /proc/mounts and nothing under home pointed at
it. Every session now binds internal storage at /root/Storage and the folder chosen with the new
ROMs folder tile at /root/ROMs - a bind, so a folder on an SD card works the same.
Folder and file choosing is the app's own picker (files/), Bannerlator's File Manager pick mode
carried over: StorageRoots verbatim for the drive menu, the same intent API (InAppFilePicker),
folders first, an up-arrow floored at the volume root, the last folder remembered. Both driver
imports use it instead of the system document picker.
A session folder is finished whichever way the session ends: SessionArtifacts is shared by the
ordinary stop, an uncaught-exception handler (App/CrashHandler, writes crash-app.txt and finishes
the folder before the process dies) and a sweep at the next app start for a folder without its
.complete marker (a kill or a native crash), which gets ended-without-teardown.txt and the crash
buffer. A Session logs tile turns the folder off: it then lives in the cache and is deleted at
the end.
The main menu is two columns of tiles, each with its setting's value under its title.
The Steam client builds Settings > Internet and its connection indicator on
NetworkManager and looks for one on the system bus as it starts. This runtime
had neither, so the page stopped with "StartScanningForNetworks is not a
function" under a header that showed no connection while downloads ran. A user
on an Odin 2 reported exactly that, and I told him it was cosmetic and unfixed -
wrongly: Max wrote the answer for WinNative (e7a224ae) and Bannerlator has
carried his port for days. This app was the only one without it.
The session now starts a small system bus and his stand-in, which reports one
connection that is up while Android has a route out and refuses what it cannot
do, since nothing here can scan for or join a network. Both the script and the
bus config come from the apk at every launch, so an installed runtime gets this
without a re-host; dbus-daemon and python-gi are already in r9.
Named bannerlator-netmanager rather than steamdeck-: CI copies
overlay/usr/local/bin/bannerlator-* by glob, so anything else would have been
built, committed and never packaged - the same shape as the proot artifact that
was produced and discarded on every run for a week.
Version 0.1.3, and the README gains a section for it.
The README had grown a section per feature in the order they were written, and
the parts that describe the whole had fallen behind: it still said version 0.1,
the ledger listed none of the drivers, audio, cores, session fixes or logs, and
the architecture diagram showed neither the audio relay nor that there are two
Turnips with two import lists.
Now: the ledger covers everything the app does with an honest state against
each - and says outright where something sets what it claims but has never been
measured; the diagram shows both driver slots and where a game's audio can go;
the session-log folder has a section of its own instead of being an appendix to
the device switches; and Installing mentions the one Android setting that
decides whether a session stays up at all, since that has cost two people an
afternoon.
Leaving the desktop hung the app until Android ANR'd it, and the log I had just
added is what showed why: stopSession runs on the main thread - the
notification's Stop action arrives there - and it called the collector
synchronously. That meant copying the compositor's log, scrubbing every Steam
log line by line (42 files on the measured run) and then waiting on logcat, all
on the main thread: about four and a half seconds, ending in signal 3 and
stack traces written to tombstoned.
20:45:32.721 session ended: 137
20:45:34.280 collected 42 Steam log(s), scrubbed <- main thread
20:45:34.282 started logcat -b crash <- main thread
20:45:36.537 reacting to signal 3 ... Wrote stack traces
The collector now runs on its own thread. Nothing in it touches the service's
state after the components have stopped, and the app process outlives the
service, so there is nothing to race.
The crash dump was also wrong twice over: it polled for the output being
non-empty, but the lines arrive through a callback on another thread, so it
declared the buffer empty every time and still paid the wait; and it printed
exec()'s return value as "rc" when that is a pid, never an exit status. It now
waits on the process ending, with a real timeout, and says which of the three
things happened - could not start, did not finish, or genuinely nothing has
crashed.
Nothing else in that session had gone wrong: labwc and Firefox's shutdown noise
is ordinary, and proot needing SIGKILL on the desktop path is expected.
Two events the log was silent about, both of which a report needed this week.
A window that changes the size of the frames it commits has rebuilt its
swapchain, and that is what a game suddenly filling one corner of the screen
looks like from the compositor's side: FlatOut came back from the Steam menu in
a smaller swapchain and the log said nothing at all, so the size had to be
inferred from a screenshot. It now says both sizes on the line.
And a window already on screen being renamed is something else taking it over:
gamescope keeps one toplevel and retitles it as the game takes the screen. That
was silent, which left the log showing a window "closed" under a name it had
never been logged as opening - reading like a missing event when it was really
a rename. Both are rare by nature; there is nothing to rate-limit.
Closes the four gaps that were left after the session folder: everything the
app itself decided was reaching logcat and nowhere a user can get at, sound had
no record at all, the network had none either, and a session the system killed
left its only trace in a buffer nobody was reading.
app.log mirrors this process's logcat for the length of the session - which
driver was chosen and why, the audio line, a rival client stopped, a helper
missing, the exit status. An app may always read back its own entries, so this
needs no permission; where a ROM refuses, the file says so rather than being
absent and looking like silence.
crash.log is Android's crash buffer at teardown. proot once died before main
over a missing library and said so there and in no other log, and a session the
OS kills for spawning children says it there too.
audio.log takes the PulseAudio daemon's output and the relay helper's, both of
which went to logcat only. The 17.0-module failure that cost the microphone a
whole round of builds would have been one line in this file.
network.txt records the transport, whether the link validated, the address
families, the DNS the system offered and the resolv.conf the runtime was
actually given - never the network's name, which is identifying. It also states
plainly that the client's own "wifi is off" is cosmetic, since that reading has
now cost two people an afternoon.
Left out on purpose: a Vulkan dump. It needs a new native entry point for
information wayland.log already carries on its first lines.
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".
xalia is an x86 Windows program Proton launches to give Windows programs
gamepad navigation. Under FEX it cannot load the session's aarch64 preload
shim, so its socket() and memfd calls reach the vendor's seccomp filter raw -
and where that answers ENOSYS, measured on a Galaxy Fold, it storms: 89
refusals of each in a few seconds, then a broken Wayland pipe and a session
that is gone 6-12 seconds after Big Picture appeared. Fourteen sessions in
eighteen minutes died that way on one device while the same build ran for
minutes on another.
Proton has its own gate for it, PROTON_USE_XALIA, so the switch sets that
rather than interfering with the helper. Off by default: xalia is Valve's, and
on a device whose seccomp answers its syscalls normally there is no reason to
take it away.