EpicNative.run passes chunkCacheDir after installDir, as the contract now
has it: "" keeps the cache beside the game, a path is the scratch cache in
the app's own storage that an install to a card uses. EpicNative.assemble
wraps the new nativeAssemble the same blocking way: the pending files are
written from the cache and each chunk goes right after its last use, then
the folder. The manager hands both runs the same path, uses the assembler
when it starts and keeps its own write loop as the fallback; the engine's
cache no longer has to sit beside a game on a card.
The default root for all three stores is the app's own storage, whatever
the session's Game storage says. With a card in the device, Install asks
where first - internal or the card, free space per target, pad-focusable,
last pick only as the dialog's default. A card that is not the Steam
library is bound at its own fixed place, so a game's guest path and appid
hold from one session to the next; a library card keeps the library's
bind and the installs already registered there keep their appid. Every
root is scanned and uninstall works for either.
The Downloads row carries the game's art as a small thumbnail and names
where the game is going; the game page names where it went. Epic installs
on a card keep their in-flight chunks in the app's cache (the Java pool
and the assembly; the engine's own cache stays beside the game until its
contract takes a cache path), cleaned on cancel and uninstall.
The engine's speed is a burst rate at the network side: on a card that
writes at 26 MB/s the fetchers wait on the writer, its windows jump between
0.4 and 20 MB/s while the bar advances at the write rate. The queue now
measures the shown speed from the byte deltas itself - an exponential
average over three seconds, sampled at most every quarter second - and
derives the ETA from it, for all three stores; the engine's figure stays
in its log lines and is stripped from the detail line under the bar.
The speed tier and downloads-at-a-time controls sit on one compact row
with a single caption, on the Downloads page and in the cog's popup.
The user's call: adding a store game to Steam is not optional. The setting
is gone from the cog, Setup and the prefs; the sidecar has no addToSteam
field (an older sidecar's is read past and ignored), the scan lists every
store install, and the game page shows a passive In Steam chip instead of
a button.
The Games hero's eyebrow for a never-played store game now reads Games
storage (the chip names the store) and Custom game for a hand-added
folder, in step with the Custom chip; Steam titles keep their label.
A card opening its page took the pad's focus with it, so the next press
landed on the rail. Each move on the page now says where focus goes:
the game page's main action (or its back link), the card the page was
opened from on the way back, the first card after a tab or chip change;
the page is a focus group, so Left is the way to the rail.
GOG's account endpoints return template keys (product_description_<id>)
for some products, and the v2 record repeats them. Store text now has
those keys dropped, tags stripped and entities decoded; the library
tries the product's lead, then the v2 overview and description, and shows
nothing when all are templates. Epic's and Amazon's text is cleaned too.
GOG's library drew the product's images.background, the store page's
fade-out backdrop, which ends in white on a card. The library now takes
the Galaxy library art from api.gog.com/v2/games (galaxyBackgroundImage,
boxArtImage), cached with the sync; the catalog's product-id filter was
tried and is ignored by the service. Epic cards prefer the DieselGameBox
images, Amazon the second background and never the logo. Cards draw over
a dark surface with a fade into the card at the foot.
On install the store's cover and hero are placed in the game folder as
opaque JPEGs under the names AddedGameArt looks for, so the Games tab and
the client's grid get real art for a store game.
A new Stores rail item (GOG, Epic Games, Amazon Games), off by default behind
Setup > Stores. The page is full width: four chips and a cog whose popup
holds the section's settings (which tab a store opens on, add installs to
Steam, speed tier); a signed-out store shows its sign-in card.
A store install is an added game: its folder sits under the Games storage
with a .droiddeck-store.json sidecar that AddedGames.scanGame reads for the
exe, the title and the source, so the existing listing -> shortcuts writer
path registers it in Steam; a running client gets it live over its DevTools
port. Games that need arguments or environment get a launcher .bat the
shortcut runs. Every Games row and hero now carries a source chip.
The download queue, its foreground service and the Downloads page are in
place for the store clients that follow.
DirectAudio from its own release: driver set per Proton audio interface (CachyOS fix), the Steam client's own DirectAudio sink, binaries out of tree
Games
- Two driver sets ship from the pinned DirectAudio release (tools/directaudio/release.env =
directaudio-linux-v1.1.0, sha256-checked by tools/directaudio/fetch.sh in CI and
build_local.sh): linux-wine11 for Valve Proton 11 / Experimental / GE-Proton, and
linux-wine11-systhread for Proton-CachyOS, whose Wine carries the system-thread mmdevapi
interface. steam-compatibility picks by whether the Proton's own winepulse.so imports
PsCreateSystemThread, never by the version string (both say wine-11.0; the wrong set is
silence). No matching set: DirectAudio off, Proton's audio runs, logged.
- Proton-CachyOS's launcher pre-sets WINE_AUDIO_DRIVER=pulse,alsa and its mmdevapi reads that
before the registry (Valve's and GE's do not read it): the wrapper exports
WINE_AUDIO_DRIVER=directaudio,pulse as well as writing the registry. On CachyOS this also
makes DirectAudio engage on a game's first launch.
- A wineserver from an earlier Steam verb can save its in-memory registry over the appended
Audio value; the wrapper says so when one is live. Writing through it (wine reg add) and
creating a missing prefix first (proton run wineboot) were tried on the device and withdrawn:
a second Proton/Wine run inside Steam's launch sequence got launches aborted.
- Tests: both sets, no set, no prefix (34 pass).
Steam client audio
- DirectAudio = module-directaudio-native-sink, the game driver's AAudio engine inside the
PulseAudio daemon, one step from Android (adaptive buffer, two-burst floor, primed ring with
fades, reopen on route change, watchdog, limiter). Default. Shared output = the previous relay
route, kept. New pref clientAudioRoute; the old boolean is not read. If the native sink cannot
open its stream the daemon keeps running (.nofail) and ensureClientSink loads the plain AAudio
sink instead, logged to audio.log. Audio bundle stamp bumped.
Out of tree
- app/src/main/assets/directaudio/ is generated and ignored; tools/directaudio-relay/ and the
vendored module-directaudio-sink.c + da_relay_proto.h are gone; tools/aaudio-sink keeps only
module-aaudio-sink (the fallback). The release carries the relay and sink field fixes by
MaxsTechReview (#338), credited there and in its AUTHORS.
Device-proven on an AYANEO Pocket FIT: client sink 12 ms / 4 ms bursts, 0 underruns; FEZ on
cachyos-11.0-20261005-slr on DirectAudio (took pulse before); FEZ on Proton Experimental
unchanged; Curse of the Dead Gods on CachyOS from a cold prefix, DirectAudio on first launch.
Co-authored-by: The412Banner <the412banner@users.noreply.github.com>
* feat: stand in for /dev/uinput so Steam Input's virtual pads reach games
* feat: present the pad to the Steam client as a Steam Deck controller over hidraw
* docs: log the uinput stand-in and the Steam Deck controller
* feat: feed the handheld's gyro and accelerometer to the Steam Deck controller
* feat: let the Steam page choose between the Steam Deck controller and the Xbox 360 pad
* feat: Deck back grips and trackpads on the second screen
* fix: serialize ring teardown with its writers, fall back to the Xbox pad when the Deck cannot be set up, and read motion only for a Deck
* fix: keep the second screen's choice through sleep and bring it back on wake
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.