The first test showed 240 games: catalog.public.list stops at its default limit.
Load 1000, show NVIDIA's total, search through the core's searchQuery so every game
can be found, and page through the account's library for the in-library marks.
cargo-ndk sets bindgen's per-target clang arguments itself, so the environment
workaround never reached the mtu build script. mtu 0.3.0 is vendored with one
build.rs change and patched in for the GFN crate and the OpenNOW workspace.
- GfnCore: OpenNOW's core service as a child process (newline-delimited JSON); turns
OpenNOW's analytics and crash reports off.
- GfnStreamer: the JNI streamer engine with a polling thread for answers and events.
- GfnActivity: NVIDIA device sign-in (code + link), NVIDIA's full game list with search,
games in the account's library marked.
- GfnPlayerActivity: catalog.launch.inspect -> session.create -> queue polling ->
streamer.prepare -> start, onto a SurfaceView; controllers mapped to XInput like
OpenNOW; leaving asks first and ends the session.
- Stores page: a GeForce NOW chip opens it. Both screens run in their own :gfn process.
Not compiled locally (no Android SDK here); CI is the first compile.
buildGfnHost builds libdroiddeckgfn.so; buildOpenNowCore builds OpenNOW's core
service and stages it as libopennowcore.so so the app can run it from its native
library directory.
Runs OpenNOW's native streamer engine on its own thread with the Android media
backend. JNI for com.droiddeck.launcher.stores.gfn.GfnStreamer: open/close, JSON
commands in, one poll queue for responses and events out, the player Surface, and
gamepad (XInput layout) and key input into the engine's input queue.
Builds for aarch64-linux-android and links only Android system libraries.
The platform crate gets an android module with the same public types the streamer core
uses (MediaRuntime, MediaSession, MediaSink, MediaControl, ...). Video frames go through
OpenNOW's own VideoQueue into a hardware MediaCodec decoder that renders onto the Surface
the app sets; frames are acknowledged to the server as they are queued (flow control).
Opus audio is decoded into a short buffer played by a low-latency AAudio stream.
Desktop backends stay upstream's code (desktop_lib.rs); shared types moved to
media_types.rs. opennow-streamer-core now compiles for aarch64-linux-android.
From OpenCloudGaming/OpenNOW a49b124c (2026-10-18): native/opennow-core and the
native/opennow-streamer crates DroidDeck's GeForce NOW store builds on, unmodified,
with OpenNOW's MIT license and a NOTICE crediting Zortos and the OpenNOW contributors.
The Crew 2 loads D3DGear64.dll from its folder. Its DXGI Present hook
faults over a thousand times within seconds under Wine and the game
never presents again: black screen while the sound keeps playing.
droiddeck-game-env now disables the DLL when it finds it beside the
game, and DLL overrides from several engine fixes are kept together
instead of the last one replacing the others.
Steam's native QAM sliders now control Android brightness and media
volume, with device changes reflected back in Steam. Holding Android
Back for 700 ms sends a real held QAM button for Steam shortcuts. Single
and double taps keep the existing menu/QAM order and swap setting; the
double-tap window remains 500 ms.
- Discover native brightness through a guest backlight descriptor and
use Android's Modify system settings permission. Retain the latest
pending request until permission is granted or the session stops, with a
notification action for hidden sessions.
- Give DirectAudio and fallback AAudio sinks Android media volume/mute
callbacks, keeping software gain at unity. Recover when initial volume
state is absent or invalid, brightness acknowledgements differ or never
arrive, or the Display bridge is not ready yet.
- Forward other buttons while QAM is held in Steam Deck and Steam
Controller modes. Consume release after a hold and clear pending input
on overlays, focus loss, pause, disconnect, and teardown. The session
menu retains its compact Back choice without an explanatory paragraph.
Validation:
- Local ARM64 release build passed. The latest timing change passed 20
targeted input tests, including release at 600 ms remaining a tap. The
preceding full feature check passed 61 Android input/device-control
tests, with permission cases on API 28 and 33.
- 66 Linux Python/Node/C tests passed for device controls, audio
recovery, Steam compatibility, and file dialogs. Both audio sink modules
rebuilt with Android NDK 27.2.
- Release-signed integration build 14d12cf0 is installed on the unrooted
AYN Thor, carrying the Android/input changes through 749ade4a plus the
existing portrait changes. Device checks confirmed the configured
QAM/menu tap order, Back dismissal, removal of the inline menu
description, and held QAM overlapping X with clean release. Hands-on
testing confirmed this installed build works.
- Earlier Thor testing covered brightness permission and pending
requests, brightness synchronization in both directions, native volume
changes, hardware volume key readback, and both sink backends. Failure
recovery is covered by the automated tests above.
The subsequent queued-notification fix in 4d7ceb23 is covered by the
Linux regression suite and has not replaced the Thor install.
Bluetooth/wired output switching and other device models remain
untested. CI and CodeRabbit monitoring continue against the pushed head.
A tag that matches no longer vouches for the build: FEX and FEXServer must
also load (ld.so --list), otherwise the session reinstalls it and x86
programs use the Steam or system FEX meanwhile.
The pin moved there from droiddeck-steam-x64, so the client settings sheet
and its test read it from the new script. The x86-64 client notes now name
the updater and Components' FEX watcher.
A failed catalog install is retried after an hour or when the session names
another build, a missing updater falls back to the FEX already here, and the
archive download gives up on a stalled connection.
The Sentry GDExtension was turned off for every Godot game that ships it,
because its crash handler takes the .NET runtime down under FEX. GDScript
games such as Pathogenic call SentrySDK from their scripts, so without the
extension their autoloads fail to parse and the game quits. Only Godot .NET
builds have it turned off now.
The launch view lives under the app's files folder, so games saw an
install path like Z:\data\user\0\com.droiddeck.launcher\files\xbox-runtime
\views\launch-<n>\droiddeck-xbox-<id>. Forza Horizon checks that length and
quits with "Installation path is too long". The view is now mapped to a
free drive letter in the prefix (X: first) for the length of the launch.
A frame limit only paced what reached the screen: a game without vsync
kept drawing hundreds of frames a second that were thrown away (AIO at
a 60 fps limit drew 155 to 2,446 fps), so the limit saved no power.
With Performance mode on and a frame limit set, the session now sets
gamescope's per-game limit over gamescope_control (gamescopectl
debug_set_fps_limit). gamescope's limiter then has its WSI layer hold
Vulkan games, DXVK and VKD3D included, to the limit. gamescope's own
--framerate-limit option is reset on its first frame in 3.16.29, and
Steam resets the per-game limit while it starts, so the session watches
gamescope's limiter state file and sets the limit again whenever it is
off (a once-a-second check with shell builtins, no process started).
With Performance mode off, a limit paces the screen only, as before.
The session also gives gamescope a fixed limiter state file path, so
gamescope stops leaving a new one behind on every start, and removes
the old ones.
Device-proven on the AYANEO Pocket FIT (Turnip, AIO D3D11, 45 s runs on
battery, three runs each): at a 60 fps limit the game held exactly
60.0 fps and the device drew 7.08 W instead of 9.45 W, with 1.3 instead
of 2.6 busy cores and 5 instead of 11 % GPU load. No limit: unchanged.
Once Blizzard's Agent has removed a game it answers GET /game/<uid> with
an empty object, not installed false, so a finished uninstall read as a
failure and opened Battle.net.