Adding and managing your own (non-Steam) games moves out of the Steam
settings into the Games tab, and works like Steam's own non-Steam
Properties.
- A + beside "Games N" opens a compact in-app picker: the storage first,
then that storage's root, every folder, .exe files only. Picking an exe
adds the game with its name, art, Custom badge and Steam shortcut.
- "Add all games in this folder" adds every game folder inside at once.
A summary opens immediately and fills in as each game is found, with a
"?" where the exe was a close call and a red X to remove a game.
- An edit button on each Custom game opens its editor: Name, Target
(ranked exes, Other exe…), Start in, Launch options, the four pieces of
art with thumbnails, and Remove. Edits go through the same Steam
shortcut, so the appid never changes and nothing duplicates.
- Art chooser per piece: images in the game folder, Steam's official art,
SteamGridDB alternatives, your own image, or back to automatic.
- Finding the exe: up to five folders deep, every exe scored (name match,
Unreal -Shipping builds, bin folders and size up; launchers, crash
reporters, installers, redistributables and depth down).
- Real titles from the game's files (Steam manifest, GOG info, the exe's
version info), keeping the folder's spacing when both spell the same
title, underscores read as spaces.
- Art: the exact Steam appid when the game names one, strict name
matches otherwise, Steam's hashed asset paths for newer games,
SteamGridDB as a fallback (built-in key from the SGDB_API_KEY secret,
optional own key in Setup, sealed with the Keystore), and a cover made
from the exe's icon when nothing else has one.
- Removing a game is instant and keeps its files; adding it again brings
it back. Steam's shortcut list is rewritten once, in the background.
- The Games section is gone from Steam settings; folders added before
keep listing their games. Artwork switch and SteamGridDB key are in
Setup.
The stores' sign-ins (files/stores/<store>/credentials.json: access and refresh
tokens, account id and name) were plain JSON. Other apps could not read them, but
every program in the Linux session runs as the app and could. They are now sealed
with AES-256-GCM under a non-exportable AndroidKeyStore key (StrongBox when the
device has one), so the session only ever sees ciphertext.
- Format: a versioned envelope {v, alg, iv, ct}, written atomically and readable
only by the app. No new dependencies (javax.crypto and KeyStore).
- Migration: an existing plaintext file is sealed at the next start, checked by
decrypting it, then the plaintext is removed; users stay signed in.
- No Keystore on the device: the file stays plain and the log says so once per
start.
- A sealed file that can never be opened (tampered, key gone or invalidated,
broken envelope) is removed and that store asks for sign-in again. A transient
Keystore error keeps the file and retries.
Session folders, tool and installer logs and the stores' log now all live under the
app's private files/logs instead of Download/DroidDeck, and every existing "Share
logs" action bundles them into one zip that goes through a final scrub.
- Where: LinuxRuntime.logDir() = files/logs; sessions keep their dated folders and
30-session pruning, tools/ and stores/ beside them. The "Logs" setting keeps its
meaning. Game save backups stay in Download/DroidDeck/Saves.
- Migration: at first start the old session folders, tools/ and loose logs move from
Download/DroidDeck into files/logs (Saves untouched), and every moved folder is
scrubbed under the current rules.
- Scrubbing: one rule set (SecretScrub) for the stores' log and the zip. The scrub
marker carries the rules version, so a rules change re-scrubs every folder at the
next start (in the background, a few at a time). Steam login account names and
emails, account-name fields, OAuth codes, tokens in URLs and private LAN
addresses are removed; Windows exception codes and server addresses are kept.
- Share: one zip of the current session folder plus the last 7 days of stores/ and
tools/, written to cache/share and handed to the share sheet.
A Stores section (rail, under Games; Setup › Stores gate) with each store's sign-in,
library, storefront shelves and a download queue. Each store has two tabs, Store and
Library, and Library filters to All or Installed; libraries open from a saved copy
and refresh in the background, with install sizes on every card. Games install to internal storage,
or a card chosen at Install time, and are registered in the Steam client as non-Steam
games, so they launch through Steam and droiddeck-proton from either the front end or
the client.
- Downloads: GOG, Epic and Amazon engines in Rust (app/src/main/rust/stores, built by
cargo-ndk, nothing compiled in the tree). One adaptive fetch core with a latency
brake, stalled-host handoff and every CDN a store offers. Per-stage progress,
pause/resume, two-step cancel that deletes, failed downloads kept for retry, a
foreground service with Wi-Fi and wake locks and notification controls.
- Games tab: store source chips (Steam, GOG, Epic, Amazon, Custom) and store art.
- Epic: per-launch exchange code from the app and from Steam's Play button,
"Epic sign-in (EOS)", "Launch offline", and a game's web links opened in Android's
browser for EOS consent. The EOS overlay crashed games and is left out.
- Cloud saves (GOG, Epic): pulled before launch, pushed after the game or the session
ends, no upload before a first download, conflicts never overwritten, backups both
ways.
- Logs: every store line redacted; the engine log goes to daily files.
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.