* Front end: draw the capsule wall without clipping it every frame The tilted wall clipped each capsule to a rounded rect and recomposed its columns on every drift step, and a rotated rounded clip is rasterised in software on the render threads: the idle main page held about 1.7 cores. The art is now loaded at the capsule's size with its corners already rounded, each column draws its capsules itself in one layer moved by the drift, and the drift advances at most every 15 ms. The idle page takes about a third of a core. * libfakeinput: let calls on descriptors it does not serve skip its lock Every read, write, ioctl, close and poll of every process in a Proton session went through the controller lock and the three map lookups, Wine's wineserver pipes and the game's files alike. A bitmap of the descriptors the maps hold, set before one is handed out and cleared when it is closed, lets everything else go straight to the kernel. * Pad motion: ask the display for its rotation four times a second The rotation was looked up for every sensor reading, every 2 ms, and cost more than the rest of the reading; it is now refreshed every 250 ms, and the readings no longer allocate pairs. * Session: keep the library passes out of a game's launch The redist seeder stamped each prefix with the checksum of the whole set of names, which differs between the session's pass and the game launch's (the game's own names are added), so every launch re-appended the markers to every prefix in the library while the game loaded. Each prefix's marker now lists the names it was given and only the missing ones are added. The beat's seed and compatibility passes, and the Lossless Scaling ownership probe (which loads steamclient.so), also wait while a wineserver runs: a launch rewrites the manifest and made them due at exactly that moment. The game's own launch seeds its prefix regardless. * Agent: keep the focus watcher's X connection open while nothing changes The connect timeout stayed on the socket, so with no property change the read timed out, and the watcher dropped the connection, reconnected and took a fresh snapshot every few seconds. * Game environment: start Cyberpunk 2077 without REDlauncher The prelauncher opens REDlauncher, a Qt WebEngine window that hands over to the game half a minute later and then stays up beside it, well over a core busy for as long as the game runs; its own --launcher-skip started the game twice. The game's executable is started directly instead. * netif: answer Wine's network driver from a reading of the link Mortal Kombat X has Wine's network driver enumerate every interface, address and route table hundreds of times a second. Each enumeration read /etc/droiddeck-net several times, asked the kernel for its interface dump again (a netlink socket bound under proot, refused each time) and looked the real functions up with dlsym. The link file is now read at most once a second, a refused dump is not asked for again within that second, and the real functions are looked up once. * Fast path: create memfds without a stop in proot proot looks at a memfd's name only to refuse the few that are then executed or locked; every other memfd_create is now issued from the trampoline. The session's /proc/net and status copies made one per read, a stop each. Without memfd in the kernel the call still goes to proot's ashmem. * Logs: run each redaction rule only on a line that could match it A session's end copies some 70 MB of the Steam client's logs and puts every line through all twenty-odd rules, most of a minute of a core on the phone after every session. Each rule now runs only on a line holding what it cannot match without: its keyword, the five separators of a MAC, the four dashes of a GUID, a long enough hex or token run, the learned account or address text. Lines with non-ASCII text still meet every case-insensitive rule. Over 895,000 lines of real client logs, with the device's accounts and addresses learned, the output is identical and the pass takes a twentieth of the time.
DroidDeck brings the SteamOS experience to Android: Valve's Steam client in Big Picture on your Adreno handheld, with Windows games through Valve's ARM64 Proton.
Note: DroidDeck does not have a stand-alone website. Do not click on any download links from websites claiming to be the DroidDeck team.
Requirements and install
Use Android 9 or newer on a supported Adreno device (730 or newer, or 8xx). Adreno 6xx is experimental: DirectX 11 uses DXVK 2 and may run, and DirectX 12 games can still crash. Mali, Xclipse, PowerVR, and Adreno 710 are unsupported. No root is required. Allow about 3 GB for the runtime and 1.1 GB more for the desktop and emulators. Install the APK from Releases, install the Linux runtime, then press Play and sign in. Steam downloads on first launch. Install Desktop & apps to use the desktop and emulators. The Store installs Linux apps and games from Flathub (ARM64 builds) with Flatpak; with additional options to install Appimages and set up scripts. Stores (Setup › Stores › "Show Stores in the rail") signs in to GOG, Epic Games and Amazon Games, browses their catalogs and libraries, downloads games into the Games storage and adds each one to the Steam client as a non-Steam game, so it launches through Proton like everything else; the cog on its chip row sets which tab a store opens on and the download speed tier.
Before Steam launches, you must turn off Restrict child processes in Developer options. If this option is not available in developer settings (Android 12 and 13 devices), first launch of Steam will present a "Fix it for me" button, which will help automate the setup process.
Community
Join the DroidDeck Discord for help, Preview builds, and device reports. Bug reports go in its #bug-reports forum; attach the zip from Share logs (Setup, or the session drawer) so the logs come with it.
Build
Run tools/build_local.sh with Docker, Java 17, the Android SDK/NDK, Rust through rustup, GitHub CLI, Python 3, and zstd installed. It prepares the same native libraries, pinned components and audio sinks as CI, then checks the APK's assets, JNI exports and dependencies. The APK is at app/build/outputs/apk/release/app-release.apk. Set DROIDDECK_PA13_SOURCE_DIR to an existing PulseAudio 13.0 source directory to skip downloading it. Set DROIDDECK_SIGNING_ENV to a signing environment file to install over a release build; relative keystore paths resolve beside that file. To install on an attached device, run tools/deploy_local.sh.
After preparation, ordinary ./gradlew :app:assembleRelease builds can reuse the verified inputs. If native sources, component pins or staged files change, packaging stops with instructions to rerun the helper. Generated audio stays under app/build/; the tracked base bundle is never modified. ./gradlew -PskipRust=true :app:testDebugUnitTest needs no native preparation. For an APK without store engines, use DROIDDECK_SKIP_RUST=1 tools/build_local.sh; it omits cached engine libraries too.
Run the helper and preload regression suite with bash tools/test_local.sh. On macOS it runs in Linux through Docker, matching the helpers' runtime and CI instead of compiling Linux preloads against macOS headers.
Limits
Compatibility and performance vary by device; hardware validation is limited. Desktop compositing uses software rendering. Firefox sandboxing is reduced under proot. Logs are kept app-private (files/logs/, the last 30 sessions); Share logs packs the newest session, with the stores and tool logs, into one scrubbed zip.
Credits and licence
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). LSFG frame generation is from the work of Camille LaVey and the Eden emulator project, following lsfg-vk, ported to WinNative and DroidDeck by @maxjivi05; it needs your own copy of Lossless Scaling and ships none of its shaders. x86 AppImages, and any whose own runtime cannot unpack them, are unpacked with uruntime by VHSgunzo (MIT), shipped unmodified with its licence. See LICENSE. Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
