1027 Commits
Author SHA1 Message Date
The412Banner aa7c71bfb6 Every menu, dialog and overlay on Jetpack Compose
The main screen, the loading overlay, the HUD line, the drawer and the three
dialogs are composables now, driven by state the activities own. The session's
SurfaceView and the on-screen pad stay Views underneath a single Compose layer,
which is what they are in any Compose app: a compositor surface and a canvas,
not menus. The XML layouts are gone; the activities extend ComponentActivity
and Back goes through its dispatcher.
2026-09-19 16:05:54 -04:00
The412Banner 18364845ad Display shape: the panel's own or a fixed 16:9, defaulting to 16:9 on a foldable
gamescope's display cannot change size once the session is up, so a foldable
that starts on one panel and opens onto the other can only be letterboxed
differently, never re-shaped. A fixed 16:9 sits with modest bars on either
panel; the panel's own shape fits one and leaves a strip on the other. A device
with a hinge sensor defaults to 16:9; the drawer toggles it for the next
session.
2026-09-19 15:54:25 -04:00
The412Banner d2ea9b898a Never hand the guest a display narrower than 16:9; letterbox in the compositor
A foldable's inner panel is nearly square, and the session sized its output to
match it. A game given a square display draws for the frame it was made for
and cuts the sides off itself — FlatOut on a Fold lost the edges of its own
menus. The output now keeps the panel's aspect only above 16:9 (a phone's 20:9
is fine), the compositor's scale mode is set to fit rather than its stretch
default so the picture keeps its proportions with bars, and touch is mapped
through the fitted rectangle instead of the whole view.
2026-09-19 15:50:48 -04:00
The412Banner d2f2aa9c23 Grey out LSFG until Lossless Scaling is installed in the client
The engine cannot run without the DLL, so a pickable LSFG row only produced a
setting that quietly did nothing. The rows are dimmed and unselectable until
the DLL is in the runtime's Steam library, and say what to install.
2026-09-19 15:40:41 -04:00
The412Banner 51303fa342 A drawer on Back: HUD, frame generation, on-screen controls, background, stop
Back used to background the app outright. It now opens a slim panel on the
right with the switches a player reaches for mid-game — the performance HUD,
the frame-generation picker (shared with the main screen), the on-screen
controls' mode (auto / always / never, replacing the file-only override) —
and the two things that leave the session: send it to the background, or stop
it. Back again, or a tap outside, closes it.
2026-09-19 15:37:37 -04:00
The412Banner 3582186951 A performance line in the corner: fps, and base → generated when frame generation runs
Whether frame generation is doing anything was only knowable from a log. The
top-right line counts the game's own frames from the compositor's per-frame
callback, and while an engine generates it shows the engine's telemetry beside
it — "LSFG 2×  60 → 118 fps" — or says the engine is starting, unsupported or
failed. Touches pass through it. steamdeck-no-hud in Downloads hides it.
2026-09-19 15:29:16 -04:00
The412Banner df61d1bbad Ship the scanout header VulkanRendererContext.h includes
Header only: the frame-generation engines take a VkTable out of that context
and never touch the direct-scanout side, so nothing links against it.
2026-09-19 15:17:20 -04:00
The412Banner 0425023063 Frame generation in the compositor: Win-FG Native and LSFG Native
The compositor was built with its frame-generation leg stubbed out. Bannerlator's
two engines and the DXBC translator they need now ride along, so extra frames
are generated on gamescope's final output — any game, nothing changed in the
runtime. Win-FG needs nothing; LSFG takes its shader chain from the user's own
Lossless Scaling, which in this app is installed from the Steam client and read
straight out of the runtime's library. One picker on the main screen: Off, or an
engine at 2x/3x/4x, applied live on the next return to the session.
2026-09-19 15:14:45 -04:00
The412Banner d46bd4a0c3 Start a session on every Play, not only on the compositor's first start
The compositor lives for the whole process, so the second Play after a session
had ended re-attached the Surface, started nothing, and left the loading panel
counting up over a dead session — it looked like a login that never came. The
service now starts whenever no session is running; the compositor's one-shot
first-frame notice is re-armed for the new session, or the panel would never
leave; and whatever the previous session left behind (gamescopereaper, the
session script, a dead client's helpers, the audio server) is killed first,
since it is our uid and still holds the socket the new one needs.
2026-09-19 14:59:12 -04:00
The412Banner ebae834e36 A loading panel that proves the session is moving
Between Play and the client's first frame there was a line of text in a corner
of a black screen, for minutes on a first run. The panel in the middle now
shows the runtime's milestone, the client's own download percentage lifted out
of its log (its bootstrap prints "Downloading update (a of b KB)", which the
milestones never reflected), a clock, and a rotating hint about what is
happening. It reads the log's tail only, twice a second; a session that has
already drawn a frame does not get the panel again.
2026-09-19 14:45:22 -04:00
The412Banner b27a4f5b31 Main screen: scroll, and size from the window rather than the panel
On a landscape phone the install button sat under the navigation bar with no
way to reach it. The screen now scrolls, everything on it is smaller, and the
side gutter grows with the window width (24dp, 96dp from 600dp, 240dp from
960dp) so the buttons stay a sensible width on a wide panel instead of spanning
it. The logo is the icon clipped to a circle; shown raw, an adaptive icon is a
bare square.
2026-09-19 14:32:37 -04:00
The412Banner f86b64b18e Verify the signature from minSdk 21, or apksigner never looks at v1
It only checks the schemes an apk's own minSdk requires, so at 26 it reported
v1 as false while the JAR signature was sitting in META-INF the whole time.
The file check beside it says the same thing a second way.
2026-09-19 14:27:51 -04:00
The412Banner 432e197454 On-screen controls for a device with no pad, gone the moment one is attached
A phone had no way to answer Big Picture: touch moved the pointer and nothing
pressed a button. The on-screen pad writes into the same rings the physical one
does, so the client sees a single Xbox pad whichever is in use and both work at
once. An InputManager listener takes the controls away when a controller
appears and brings them back when it goes; steamdeck-osc in Downloads pins it
either way. Virtual input devices no longer count as controllers — the system's
own synthetic devices would otherwise hide the controls with nothing to
replace them.
2026-09-19 14:27:05 -04:00
The412Banner cc868496d1 Sign the apk with apksigner so v1 is actually there
AGP drops JAR signing at minSdk 24 and above and the DSL flag does not move it,
release build or not. zipalign then apksigner with the three schemes named
explicitly is the only version of this that can be checked.
2026-09-19 14:25:16 -04:00
The412Banner 71f84b93cc Tell lint that targetSdk 28 is the point
A release build fails on ExpiredTargetSdkVersion, which exists because Play
will not take an sdk that old. It is right about the rule and wrong about this
app: 29+ forbids executing a file from the app's own data directory, which is
the one thing this app exists to do.
2026-09-19 14:23:08 -04:00
The412Banner 4b43a617d4 Build the release variant so the apk carries v1 signing too
AGP drops JAR signing from a debug build when minSdk is 24 or higher, whatever
the signing config says, so the check added with the testkey failed on its
first run. Nothing is minified in either variant; this only changes which one
CI hands over.
2026-09-19 14:20:07 -04:00
The412Banner 344e70afc6 Keep the session alive in the background, rename the package, sign with the testkey, and draw the app a logo
The session now belongs to a foreground service rather than the activity.
Android demotes a process as soon as its last visible activity goes and the
low-memory killer reaps the guest, so leaving Big Picture to answer a message
came back to a dead Steam; the service holds the process at perceptible
priority, a partial wake lock keeps the CPU from dropping the guest's threads,
and a high-performance WiFi lock keeps a backgrounded download from being
throttled to nothing. The notification is silent (IMPORTANCE_LOW), carries a
Stop action, and returns to the session when tapped. Back now puts the app away
and leaves Steam running; a swipe out of recents ends the session.

The compositor moved with it: it is process-wide, started once, and merely
swaps the Surface it presents into as the activity comes and goes.

Package is com.steamdeck.launcher, signed with the AOSP testkey (v1+v2+v3) so
every build installs over the last one, and CI asserts all three schemes.

The launcher icon is our own controller on a purple glow — the spirit of the
Steam tile the client entry uses, none of its geometry.
2026-09-19 14:17:25 -04:00
The412Banner 4031f512fc Cache everything CI can, and credit both authors
The repo is private, so each run is billed. Three caches now carry a rebuild:
Gradle's own through setup-gradle (dependencies, build cache, configuration
cache), the CMake/ninja tree keyed on the C sources and the NDK, and the two
cross-compiled preloads — the last of which also skips installing the cross
toolchain, since a run that changes only Kotlin has no reason to touch a C
compiler. Superseded pushes are cancelled, documentation pushes do not build,
and the apk is stored rather than recompressed.

The preload assertions moved from the cross binutils to readelf, which reads
any ELF whatever the host, so they keep running on a cache hit.

Credits name The412Banner and maxjivi05: the runtime under all of this is
Max's gamescope work from WinNative.
2026-09-19 14:07:06 -04:00
The412Banner 97ee688eff Size the session from the panel, not the app window
resources.displayMetrics is the window after the system bars and the cutout are
subtracted, so the first session on a 1080p device came up 1920x968 and
gamescope patched its EDID to suit. The panel's own bounds are what a client
should be told the display is.
2026-09-19 14:01:40 -04:00
The412Banner 814ab2cac0 Add to the process environment rather than replacing it
Matches the exec path this was ported from; the guest command already starts
with env -i, so clearing the host side changed nothing except how far the code
sits from a configuration proven on a device.
2026-09-19 14:01:08 -04:00
The412Banner 085f663347 Ship libsndfile.so, which the PulseAudio core links
Caught by the closure check the previous commit added, before a device had to
find it: the daemon would have failed to start and the session would have run
without audio.
2026-09-19 13:57:01 -04:00
The412Banner 20a3b1ad63 Ship libffi.so, and check the whole native dependency closure in CI
libwayland-server.so links libffi, so loading the compositor threw
UnsatisfiedLinkError the moment Play started a session. The library was there,
its dependency was not — which is exactly the kind of gap a build cannot see
and a device finds immediately, so CI now walks every NEEDED of every .so we
package and fails on anything that resolves to neither the apk nor the
platform.
2026-09-19 13:54:41 -04:00
The412Banner 83cc1c4ec8 README: how to skip the 790 MB download on a device that already has the runtime 2026-09-19 13:53:33 -04:00
The412Banner c22cbc9f43 Type into the session: hardware keys to the compositor, and a log path that always exists
The session script redirects its own output into BL_LOG with exec, and a
redirection a non-interactive shell cannot open ends that shell — so a denied
storage permission would have killed the session rather than just losing the
log. Downloads is used once proven writable, files/logs otherwise.

Keyboard events now reach wl_keyboard, which is what the client's first-run
login needs on a device with a keyboard attached.
2026-09-19 13:49:29 -04:00
The412Banner 9b3dd05f4f Ship every shader header the effect chain includes
effects_chain.c runs the X11 renderer's committed SPIR-V; copying only the ones
named in the first screenful left deband_frag.h behind.
2026-09-19 13:45:20 -04:00
The412Banner 9119eb0045 Import the downloader the installer uses, and package libwayland-server.so
The compositor links libwayland-server.so as an imported target, so CMake never
copies it into the apk; it has to travel in jniLibs like the PulseAudio set.
2026-09-19 13:42:45 -04:00
The412Banner 4022351e22 SteamDeck: a standalone launcher for the native ARM64 Linux Steam client
Lifts Bannerlator's Linux/gamescope runtime out into an app of its own: the
Wayland compositor, the proot session, the runtime installer, the fake-evdev
controller path and the PulseAudio server, with none of the Wine, container or
store machinery around them.

One activity installs the runtime from the same winlator-contents catalog and a
second one runs the session: compositor on a SurfaceView, gamescope and Valve's
native arm64 client under proot, a physical pad republished into the fake-input
rings, and the session's milestones on screen until the first frame arrives.
2026-09-19 13:39:17 -04:00