752 Commits
Author SHA1 Message Date
Kurt Himebauch 0c79a742c4 fix(game-env): keep the x64 Visual C++ runtime in ARM64 prefixes (#2442) 2026-11-10 20:30:11 -05:00
Kurt Himebauch dd9363e5f2 fix: ship the Ubisoft launcher service as an add-on pack, scan only the standard APK on main (#2436)
* fix: ship the Ubisoft launcher service as an add-on pack, not inside the APK

* fix: treat the Ubisoft service as a packed tree, stage it when its pack lands

* fix: serialize staging of the Ubisoft service
2026-11-10 16:33:41 -05:00
Kurt Himebauch 055e2ec78f Fix Electron Node mode sandbox injection (#2418)
* Fix Electron Node mode sandbox injection

* fix(preload): treat any nonempty ELECTRON_RUN_AS_NODE as Electron Node mode
2026-11-10 16:10:42 -05:00
Kurt Himebauch 3c96df4ab1 feat(ubisoft): Ubisoft Connect store with native sign-in, library, downloads and launch (#2322)
* feat(ubisoft): sign in to Ubisoft Connect and list the owned library

* feat(ubisoft): download games natively through Demux and Ubisoft's CDN

* fix(ubisoft): show the owned games on the store page

* fix(ctl): reach the agent bridge by broadcast when the shell has no content command

* feat(ubisoft): run Ubisoft games in one prefix with Ubisoft Connect installed and the game registered

* feat(ubisoft): launch games through a native stand-in for Ubisoft Connect's local service

* feat(ubisoft): document the launcher pipes, log why owned games have no download, drop the installer after extraction

* feat(ubisoft): Steam-owned Ubisoft games install through Steam, and the launch session carries the game's application id

* feat(ubisoft): answer Denuvo activations through Ubisoft's activation service and log unanswered pipe traffic

* feat(ubisoft): report installed chunks to the game and skip add-ons the account does not own

* fix(ubisoft): the overlay stays off, Ubisoft's install steps pick Windows components, and shared-prefix games keep their own settings

* fix(ubisoft): write the manifest's registry values, and pick the game's exe over Ubisoft's installer stubs

* fix(ubisoft): stream download slices, guard the launch hook, and keep per-game components in the shared prefix

* fix(ubisoft): direct launches in the shared prefix keep their own game id

* ci(ubisoft): build the launcher service after the prepared components are verified

* fix(ubisoft): verify reused files, drop syncs from a signed-out account, and clear activation requests before launch

* fix(ubisoft): fetch again a reused file whose slices cannot be checked
2026-11-10 15:08:37 -05:00
Kurt Himebauch d8ed02f2cf feat: EA store with sign-in, library, native downloads, updates and launch without the EA app (#2259)
* docs: research and plan for EA and Ubisoft stores

* feat(ea): EA store with sign-in, library, native downloads, updates and launch without the EA app

* perf(ea): fetch large archive records as parallel ranges

* fix(ea): register game display names

* fix(ea): license legacy EA Core games and harden the LSX endpoint

* fix(ea): run legacy Origin SDK games with a stand-in Origin process

* fix(ea): trust only the prepared game on LSX and stop stale library syncs

* fix(ea): close LSX sessions on stop and drop clients that never hand-shake

* fix(ea): require every hashed field before licensing or caching a probe

* fix(ea): trust one LSX session per launch

* fix(ea): serialize the library cache write with sign-out and rerun a skipped sync

* fix(ea): hand off library sync requests under one lock

* fix(ea): clear the sign-in before invalidating the library

* fix(ea): serialize sign-out with token refresh and with the library cache

* fix(ea): LSX trusts only verified peers, new launch closes old sessions, exit stops its own game

* fix(ea): LSX hides profile and presence from unverified peers

* fix(ea): LSX handshake has a fixed deadline and untrusted sessions yield their slot

* fix(ea): LSX never registers a client after its listener was stopped

* fix(ea): LSX evicts each untrusted session at most once

* fix(ea): LSX admission and eviction share one lock and keep the cap

* fix(ea): LSX trust promotion excludes eviction

* fix(ea): LSX launch generation guards restarts and drops results after shutdown

* fix(ea): LSX publishes launch state under the admission lock and tags accepts by generation

* fix(ea): LSX restarts rebind its listener and promotion checks the launch generation

* fix(ea): record why EA refuses a game licence in the store log

* chore(ea): log owned offers and the full licence refusal

* chore(ea): log the account's entitlements when no game licence is granted

* chore(ea): log legacy entitlements when no game licence is granted

* chore(ea): log only the refused game's own entitlements

* fix(ea): skip trial launchers flagged 1 and license games whose LSX peer Android cannot name
2026-11-10 14:56:29 -05:00
Kurt Himebauch 40242fa082 ci: scan signed main and release APKs on VirusTotal (#2408)
* ci: scan signed main and release APKs on VirusTotal

* ci: upload to VirusTotal only for unknown hashes and escape annotations
2026-11-10 11:15:07 -05:00
MaxsTechReview 634cb390c9 fix(proton-run): register the wrapper for an installed Valve Proton depot (#2392)
* fix(proton-run): find Proton tools in Steam libraries

* fix(proton-run): register the wrapper for an installed Valve Proton depot

Valve's ARM64 Proton depots are not compatibility tools, and running
them directly bypasses the DroidDeck wrapper. Register the wrapper from
the depot when a store app is launched before Steam has started, instead
of scanning steamapps/common.

* test(proton): install complete tool fixtures in the auto-update tests

* fix(proton-run): skip incomplete Proton depots and keep listing tools when the wrapper cannot be written

* fix(proton-run): pick a runnable depot and rebuild a wrapper whose launcher is missing

* test(proton-run): remove redundant depot fixture write

* fix(proton-run): rebuild a wrapper whose manifest is missing

* test(proton-run): cover runnable depot selection
2026-11-10 07:09:56 -05:00
MaxsTechReviewandKurt Himebauch 13c8edb3d5 fix(game-env): repair foreign-architecture system DLLs in ARM64 prefixes (#2318)
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-11-10 06:24:03 -05:00
MaxsTechReview 1ec2b696b7 fix(flatpak): stop inheriting host Zink overrides (#2391)
* fix(flatpak): stop inheriting host zink overrides

* Check every cleared driver override in the Flatpak launcher test
2026-11-10 02:55:13 -05:00
MaxsTechReview 6bcd431ca6 fix(session): wait for Steam readiness before launch (#2362)
* fix(session): wait for Steam readiness before launch

* fix(session): keep the launch fallback when Steam readiness or the interstitial is unavailable

* fix(session): cap the Steam readiness wait by elapsed time and bound each check

* fix(session): keep waiting for Steam readiness through focus changes and never pass a zero timeout
2026-11-09 18:23:24 -05:00
Kurt Himebauch 1b19837fc6 fix(native): a guest's own seccomp filter gets its SIGSYS, so Firefox tabs stop crashing (#2360) 2026-11-09 16:42:38 -05:00
Kurt Himebauch c85b84a964 fix(input): fakeinput no longer aborts programs whose allocator calls a hooked function at startup (#2361)
* fix(input): fakeinput no longer aborts programs whose allocator calls a hooked function at startup

* fix(ctl): screenshot finds display ids that the device name carries inline

* fix(input): fakeinput registers its fork handlers before other threads see its config
2026-11-09 16:41:26 -05:00
Kurt Himebauch 7c89c14a84 fix(dual-screen): drop the Dashboard notifications page and its notification listener (#2342)
* fix(dual-screen): drop the Dashboard notifications page and its notification listener

* test(session): freeze the clock in the memory-pressure policy harness
2026-11-09 12:48:00 -05:00
MaxsTechReview 96a12465e0 fix(session): stop re-compressing game memory every 30 s when the game reads it straight back (#2312)
* fix(session): stop re-compressing game memory every 30 s when the game reads it straight back

On devices with 13 GB or less, the low-memory page-out skipped its
read-back backoff whenever memory was nearly exhausted. When the game
was actively using what was compressed, it read it straight back, so
the session pushed and re-read gigabytes every 30 seconds, pinning all
cores and freezing the game for 5-10 s each time. The read-back check
now applies on that path too: an effective page-out still repeats every
30 s, an ineffective one backs off as on the normal path.

* zRAM: keep the read-back backoff on the nearly-out-of-memory path

* zRAM: retry a skipped page-out after 30 s when nearly out of memory
2026-11-09 02:08:40 -05:00
Kurt Himebauch 88d6b70881 fix(ctl): reach the agent bridge by broadcast when the shell has no content command (#2321) 2026-11-09 00:00:20 -05:00
MaxsTechReview 29823bb1bf fix(steam): Linux-only tools keep their native platform (#2313)
* fix(steam): reconcile tool dependencies by supported platform

* fix(steam): refresh tool metadata and clear stale GE mappings

* fix(steam): clear any Proton mapping from Linux-only tools

* fix(steam): watch folders the client creates after the helper starts

* Watch each missing directory level as it appears

* Make the watcher level test wait on inotify readiness
2026-11-08 22:22:22 -05:00
Kurt Himebauch 1355cb3a2b fix(ci): handle empty commit lookup responses 2026-11-08 21:13:53 -05:00
Kurt Himebauch 51407155fc fix(ci): keep commit lookup failures from blocking notices 2026-11-08 21:12:58 -05:00
Kurt Himebauch b4a953c659 fix(ci): post new Discord notice for each PR build 2026-11-08 21:08:22 -05:00
MaxsTechReview 6b110581ac fix(gog): install scripts' support files reach the prefix (#2294)
* fix(gog): install scripts' support files reach the prefix

A goggame-<id>.script can copy files into the prefix with supportData
actions: default INI files, Plugins.txt and other settings that mods such
as one-click editions need. Only setRegistry actions were applied, so
those games started without their configuration and ran as the base game.

droiddeck-store-launch now applies supportData after the registry, once
per script revision: copies from {app} and {supportDir} into {userdocs}
and {localappdata}, matching paths without regard to case. Links are
copied as links, paths outside those folders are refused, and existing
files are replaced only when the action asks for it.

* Keep GOG support data copies inside the game and prefix folders

* Copy linked GOG support sources as links and keep a player's own links
2026-11-08 18:45:53 -05:00
MaxsTechReview 92742f37c1 fix(preload): answer netlink requests sent with write and writev (#2304) 2026-11-08 18:40:57 -05:00
Kurt Himebauch a7c10e3f51 fix(session): give Steam a session bus so launches stop hanging at the install script (#2303) 2026-11-08 16:53:01 -05:00
MaxsTechReview 1760f008ae fix(desktop): Flatpak and Snap apps get the game GPU setting (#2295)
The session's TU_DEBUG=sysmem is meant for the Steam client and
Chromium-based apps. Flatpak and Snap apps inherited it, so emulators
and other programs installed that way rendered through system memory.

droiddeck-game-env now holds the Chromium check next to game_gpu, and a
small droiddeck-game-gpu wrapper applies both to any program. The
AppImage, Flatpak and Snap launchers use it, so the rule lives in one
place.
2026-11-08 14:39:31 -05:00
MaxsTechReview 3bb279f635 fix(appimage): emulators and other AppImage programs get the game GPU setting (#2288)
The session sets TU_DEBUG=sysmem for the Steam client's interface and hands
games their own value in DROIDDECK_GAME_TU_DEBUG, which droiddeck-game-env
applies to Steam launches (#1188). droiddeck-appimage-run never did, so
emulators and other AppImage programs rendered every pass through system
memory instead of Turnip's per render pass choice.

Apply the game value in droiddeck-appimage-run the same way: an empty
value unsets TU_DEBUG, any other value replaces it. Chromium-based apps,
like the Steam client's own interface, keep the session's setting.
2026-11-08 12:23:30 -05:00
MaxsTechReview 3fef1f3fa1 fix(proton): a Proton picked for a game in Steam is kept (#2284)
fix(proton): a Valve Proton picked for a game in Steam stays on the ARM64 build made from it

Steam lists Valve's own Proton Experimental and Proton 11.0 next to the
DroidDeck tools. They are not runnable here, so a per-game pick of one was
replaced with the default. That used to be Proton Experimental ARM64, so the
pick looked kept; since an installed DroidDeck-Proton became the default, the
game is moved to DroidDeck-Proton instead.

When Steam records one of those tools for a title the sync already knows
(a title it mapped or one with a recorded pick), the title now gets the
DroidDeck tool built from the same Valve depot, stops following the default
and the choice is mirrored into the app's per-game setting. The same applies
to Steam's default. Titles nobody picked anything for still follow the
default, and Valve tools with no ARM64 build still fall back to it.
2026-11-08 12:03:21 -05:00
MaxsTechReview a4f26799ab fix(mesa): Zink on PowerVR shows colour again instead of grayscale (#2287)
Mesa 26.0.8, which the Ubuntu runtime builds, carries the backport of
"zink: create views for samplers lazily", which dropped the depth/stencil
aspect check in init_sampler_view(). On drivers with
needs_zs_shader_swizzle (Imagination) every colour view with a constant-1
alpha, such as RGBX/BGRX textures, then got the red-only depth swizzle
view, so Steam's interface and games showed red as R=G=B.

Carry Icenowy Zheng's upstream fix d55418dae7 (Mesa MR 41923), which
restores the aspect check. It applies on top of patch 0004.
2026-11-08 11:48:11 -05:00
MaxsTechReview 005fec2125 chore: pin gamescope to the DroidDeck-Components p23 release (#2285) 2026-11-08 11:37:54 -05:00
MaxsTechReview 2e70b8ed73 fix(gamescope): taps reach dialogs and windows away from the screen origin (#2280) 2026-11-08 10:44:00 -05:00
MaxsTechReview 74c0f2127a fix(proot): lay out ARM64 programs like the kernel so large reservations fit (#2279) 2026-11-08 10:43:57 -05:00
MaxsTechReview 51e5e3a2a4 ci: a finished build reports on pull requests from forks (#2277)
When a Build APK run finishes, Build validation finds its pull request with
commits/{sha}/pulls, which GitHub answers with an empty list for a commit that
only exists on a fork. The validator then took the build as superseded, so the
"Build APK" status of every fork pull request stayed at "awaiting APK
compilation" after a successful build. It now falls back to the open pull
request for the run's head owner and branch, still requiring the head commit
to match.
2026-11-08 09:07:36 -05:00
MaxsTechReview c755d05473 ci: cache the pinned Turnip archive and retry its download (#2276)
* ci: cache the pinned Turnip archive and retry its download

"Prepare graphics driver variants" fetched the same checksum-pinned Banners-Turnip
zip from GitHub on every run with a single 120 s attempt, so a 502 or a read
timeout failed whole PR builds three times today. The archive is now cached
between runs (it is still checked against its SHA-256 after a restore), and a
cold download is retried on timeouts, 429 and 5xx with a growing pause.

* ci: keep PowerVR's verified downloads between runs

tools/powervr/fetch.py already keeps checksum-verified downloads in
~/.cache/droiddeck-build, but CI never saved that folder, so every run fetched
the PowerVR components again and a read timeout failed "Build the add-on
packs". The folder joins the existing pack-inputs cache, keyed on
release.json too.
2026-11-08 09:07:31 -05:00
MaxsTechReview aa883830ca fix(session): give the runtime a machine ID so D-Bus programs such as melonDS start (#2275)
* fix(session): give the runtime a machine ID so D-Bus programs start

The Ubuntu runtime ships an empty /etc/machine-id for systemd to fill on first
boot, and nothing fills it in a DroidDeck session. libdbus aborts when it needs
the ID ("D-Bus library appears to be incorrectly set up"), so melonDS closed at
once from the desktop and GTK Flatpaks started outside the session bus failed.

The session now writes a fresh ID once when the file is empty or malformed and
never changes a valid one.

* fix(session): check the whole machine ID file and reject the all-zero ID
2026-11-08 08:54:56 -05:00
MaxsTechReview 45e39b7aaf fix(desktop): catalog AppImages such as RPCS3 start from the desktop menu again (#2274)
fix(desktop): catalog AppImages start from the desktop menu through the shipped runtime

RPCS3 from the desktop menu exited at once: its own AppImage runtime runs
dwarfsextract from a memfd, which PRoot cannot execute (EACCES), and it has no
fallback. Emulator rail launches already avoid this by starting arm64 images
through the shipped uruntime (#1191); the menu entries the catalog writes did not.

The rule now lives in one launcher, droiddeck-appimage-exec, used by the session
and by the catalog's menu entries. Entries written by older versions are moved
to it with the existing menu migration.
2026-11-08 08:39:23 -05:00
MaxsTechReview 39469e6e8f fix(proton): an installed DroidDeck-Proton replaces a Proton Experimental default nobody picked, and added shortcuts follow it (#2266)
* fix(proton): an installed DroidDeck-Proton replaces a Proton Experimental default nobody picked, and added shortcuts follow the default

* test: an owned shortcut left on the previous default follows a default change
2026-11-08 06:07:53 -05:00
MaxsTechReview 2ade269be3 fix(gamescope): direct-touch drags work again in games that confine a hidden cursor (#2220)
* gamescope: a confine is relative only while it holds the X pointer

Patch 0128 made every hidden-cursor confine relative. Games that hide the
system cursor, draw their own and confine the pointer to their window then
took the host lock, and a direct touch drag became camera motion with no
button held, so cards and items could not be dragged.

A confine now goes relative once the X pointer stays away from the seat
position while input moves, as when a game recentres it or clips it to a
small rectangle, and returns to absolute when the pointer follows again or
the confine ends. Locks and the warp follow rule are unchanged.

* gamescope: a constraint without a hint is relative only while the game holds the X pointer

Xwayland turns a hidden-cursor grab into a lock with no cursor position hint, so the confine-only rule never applied to games that draw their own cursor (Stardew Valley, Balatro). A hinted constraint is relative at once, as upstream. An unhinted lock or confine goes relative once the X pointer stops following the seat's motion, which a recentring or clipping game does and a drawn-cursor game does not.

* gamescope: a pointer constraint is relative only while the game holds the X pointer

Stardew Valley's grab with a hidden cursor becomes an Xwayland lock with a cursor position hint, which upstream made relative at once. Without relative mode that lock still follows gamescope's relative motion and touches still place the pointer, so the hold rule now decides for every constraint, hinted or not.

* gamescope: a hinted lock starts relative and is released once the X pointer follows

Releasing a hinted lock only through the hold rule let a recentring game's first touch swipe press the button. A hinted lock now starts relative as upstream and is released after 30 moving frames on which the X pointer follows the motion gamescope sends, counted from the relative motion while the seat is locked.

* gamescope: count away frames within one half-second window

* gamescope: a hint that arrives with the game's first warp starts the lock relative

* fix(input): end the touch press when a program locks the pointer mid-swipe

A game that only shows its pointer lock after its first warp took the first
swipe as an absolute drag, with the button held until the finger lifted.
The first locked move now ends the forwarded touch before sending deltas.

* gamescope: a new focus window or constraint starts its own pointer hold

A held state from one constrained window carried over to the next one
when focus moved before a reset frame. Track the input focus window and
the constraint, and restart the hold when either changes.

* gamescope: read the pointer constraint under the wlserver lock

GetCursorConstraint asserts the lock is held, so the hold tracker
aborted as soon as a program constrained the pointer.

* fix(input): a mid-swipe pointer lock ends the touch but keeps a held button

The first locked move cancelled the whole direct-touch hold, releasing a
right button held by a long press. It now cancels only the forwarded touch;
the held button is released when the finger lifts, as before.

* fix(input): extra fingers during a locked swipe stay out of the cancelled touch

Once a locked swipe has ended the forwarded touch, a second finger restarted
it with a new touch-down while the swipe carried on as relative motion. Extra
fingers are now ignored until the swipe ends; the final lift still releases a
held button.

* fix(input): a swipe that ended its touch stays closed until the finger lifts
2026-11-07 21:30:21 -05:00
Kurt Himebauch d29a1a2dc2 ci: build and test on root self-hosted runners (#2237) 2026-11-07 17:02:19 -05:00
MaxsTechReview a33954b8bd perf: answer Steam's missing-file checks on SD and USB libraries from the directory cache (#2212)
* perf: answer Steam's missing-file checks on FUSE libraries from the directory cache

Before creating and committing each file, the Steam client checks whether it
already exists under its exact, lower-cased and folded spellings. On an SD or
USB library reached through Android's FUSE layer every one of those misses is a
round trip to MediaProvider (Brawlhalla: about 65,000 misses, 92 of the 161 s
of preallocation and 60 of the 93 s of commit).

The client already caches the listings of its steamapps directories on FUSE and
keeps them exact through its own changes, inotify and a short TTL. access() now
answers a name that is absent from a live listing of its directory without
asking the filesystem. Directory paths match case-insensitively only when the
library's filesystem folds case, and names compare case-insensitively, so an
existing file is never reported missing.

* fix: reuse a listing for a differently cased folder only when the whole path folds case

Checking that steamapps alone folds case let a listing of one folder answer
for a different folder below it whose name differs only in case. The fill
now compares the listed folder with its fully case-swapped path below
steamapps, so a listing is reused across case only when the filesystem
folds every level.

* fix(dircache): match a folded listing only below the same prefix and skip it during a tracked change

* fix(linuxfs): a folder renamed or removed above a cached listing drops that listing

* fix(linuxfs): watch the folders above a cached listing so a rename or removal by any process drops it

* fix(linuxfs): also watch the folders above steamapps, and stop a stuck probe from hanging the tests

* fix(linuxfs): a listing under a symlink or an unwatchable folder no longer answers absent lookups

* fix(linuxfs): a symlink check that cannot be watched also stops absent answers

* fix(linuxfs): a differently cased lookup reuses a listing only for the same folder
2026-11-07 16:36:15 -05:00
MaxsTechReview be0fe35271 fix(proot): let the fast path fall back when Android 10 refuses statx (#2224) 2026-11-07 15:48:44 -05:00
MaxsTechReview 041c3b1b55 ci: run compile jobs on the org workstation runner when it is idle (#2234)
* ci: run compile jobs on the org workstation runner when it is idle

* ci: pick the compile runner right before each heavy job

* ci: cover the runner picks in the job dependency test
2026-11-07 15:00:39 -05:00
MaxsTechReview 5446497301 fix: uninstalling a Flatpak app that Flatpak no longer has removes its folder (#2216) 2026-11-07 12:46:52 -05:00
MaxsTechReview a123af7c13 fix(script-run): run x86 programs a native script starts through FEX (#2217) 2026-11-07 12:27:24 -05:00
MaxsTechReview d5f3d1a0d5 fix(proton-run): follow a removed DroidDeck-Proton build to the newest one (#2223)
Battle.net, store games and desktop programs start through droiddeck-proton-run
without the Steam client. When the Proton auto-update replaced the DroidDeck-Proton
build that Steam's default or a title's mapping named, the name no longer matched
an installed tool and the run fell back to Valve's Proton Experimental ARM64, which
lacks DroidDeck-Proton's patches. World of Warcraft then hung at launch.

A removed DroidDeck-Proton build now resolves to the newest installed one, the same
successor steam-compatibility moves Steam's mappings to.
2026-11-07 11:37:17 -05:00
Kurt Himebauch 22a675a664 Unify controller and screen input profiles for game and desktop sessions (#1439)
## Problem and intent

Desktop sessions need configurable mouse, keyboard and application
actions from a controller. On dual-screen devices, a lower-screen
trackpad may already handle the pointer, leaving the physical controls
available for other assignments. This PR gives physical controllers and
local touch controls a shared profile/action model instead of separate
configuration paths.

This follows the centralization direction discussed in
[#1202](https://github.com/Droid-Deck/DroidDeck/pull/1202#issuecomment-6299647180)
and addresses the controller-input portion of #860 and #1356. Those
discussions are context, **not prior maintainer approval** of this
implementation. I would appreciate architectural feedback and guidance
on splitting or coordinating this work if that would make review easier.

## Profiles and configuration

- Controller stays within the existing Setup tabs. Game and Desktop each
have a default-profile selector, followed by one profile list and device
settings.
- Named profiles reference three independently reusable parts: Physical
controller, Main screen and Second screen. Layout geometry and action
bindings are stored separately. Profiles can share physical controls
while assigning different touch actions.
- The session menu selects a complete profile, or expands to mix the
three parts. A mixed selection can be saved as a new profile; Game and
Desktop retain separate selections.
- Both screens use the same full-size editor, with add, move, resize,
snapping, undo and shared properties. The main-screen editor has
movable, collapsible tools and is transparent above a running session.
When editing the secondary display, its entire area is the canvas and
the main display hosts tools/properties.
- The old fixed gamepad, fixed Deck controls and separate Screen keys
editor are replaced with editable presets and the shared renderer.
Existing positions, bindings, shapes, symbols, imported images and
toggle behavior migrate; legacy Screen keys become additional profiles
without replacing the user's selected profile. Migration retains a
backup before removing old preferences.
- Saved layouts have thumbnails and screen targeting. Actions include
compatible native output/remapping, keys/combinations, mouse buttons,
pointer movement, vertical/horizontal scrolling, directional keys,
existing menu/keyboard commands, and disabled. Buttons can use symbols,
shapes and toggle behavior.

## Architecture and latency


```mermaid
flowchart LR
    Library[Profiles + reusable layouts and bindings] --> Router[InputRouter]
    Physical[Physical controller adapters] --> Router
    Touch[Main + secondary InputSurfaceView] --> Sources[Owned pad / Deck sources]
    Sources --> Router
    BLE[Steam Controller BLE adapter] --> Router
    Router --> Native[Existing controller publication]
    Native --> Steam[Steam Input / guest controller]
    Router --> Held[Shared key and mouse ownership]
    Held --> Guest[Existing guest input outputs]
    Router --> UI[Existing menu / keyboard hosts]
```

Bindings resolve when the profile changes, outside event dispatch.
Native forwarding has no additional frame/timer queue; identity routes
reuse existing state/reports. Only synthesized continuous stick
pointer/scroll motion uses a frame callback. One source releasing a
key/button does not release another source's hold. Menu, profile,
suspension and removal transitions release old outputs and gate held
controls until neutral.

Steam continues to own game-specific mappings and action sets. Steam
Mobile Touch and VR retain their specialized paths. Raw device adapters
remain responsible for transport/calibration and must join routing
before publishing consumed controls; this does not claim that every
adapter has been unified.

Historical Thor measurements on the native Game path found
kernel-event-to-guest-observer upper-bound medians of **3.061 ms
baseline versus 2.999–3.403 ms candidate**, with a subsequent
installed-APK check at **3.039 ms / 300 matched edges**. These are
relative software-path measurements, not button-to-photon latency. Some
tails increased, simultaneous BLE contention remains unmeasured, and
**this UI revision has not had a new latency measurement**. The previous
cursor fix changed visual update intervals from roughly 33.3 ms to 16.7
ms without changing sensitivity or adding smoothing.

[Full design, migration and
boundaries](https://github.com/Delete228/DroidDeck/blob/codex/unified-controller-input/docs/development/unified-input-proposal.md).

## Validation

Updated through upstream 542bbf89c; current PR head 08526f828. The
latest CodeRabbit review generated no actionable comments.

- **1,017 tests passed in 133 suites after the latest upstream merge**,
with no failures, errors or skips: all input/UI tests, global reset,
component updates, PiP policy, runtime auto-update and Proton menu
auto-selection. The pre-merge review-fix run passed **967
input/UI/global-reset tests**. The subsequent second-screen refresh fix
passed **35 focused surface/presentation tests**, including three new
state-preservation regressions.
- Full local run before the final review follow-ups: **3,598 tests,
3,573 passed, 25 failed**. The 25 are unchanged upstream macOS
limitations: log-export tests requiring SecureDirectoryStream (24) and a
case-sensitive WoW add-on test (1). The global-reset regression also
passes. Earlier disk-exhausted attempts were discarded; the completed
run bounded temporary resources with one worker and JVM recycling,
without excluding tests or changing repository test configuration.
- APK build, runtime/session assets, native dependencies, alignment and
v1/v2/v3 signatures passed. Installed over the separate Input Test app
on **AYN Thor** with its environment retained. Local packaging omits
Rust store engines; the [full Build APK workflow for 08526f828
passed](https://github.com/Droid-Deck/DroidDeck/actions/runs/42295403480),
including Android/JVM, Python/native, Rust store-engine and Xbox checks.
- Review follow-ups keep migration IDs stable, preserve unreadable
supported-version libraries before recovery, retain forward-version
protection, release keyboard owners on focus loss, handle unavailable
presentation displays, and allow controller confirmation/cancellation
after key capture. Deck router cleanup checks ownership; BLE teardown
synchronously blocks late routing/publication, and shared Triton slot
ownership protects a replacement controller from stale instances. Legacy
Select-to-Steam migrates to Desktop without replacing saved bindings;
mouse backend transitions are serialized with publication. Profile
refresh now updates custom controls in place, preserving
terminal/dashboard state, the field keyboard and WoW chat. No physical
BLE-controller test or new latency measurement was performed.
- The latest 08526f828 APK is installed on Thor. Profile preferences
stayed byte-for-byte identical and the retained Steam environment
reached READY at 1280×720 / 60 Hz. The earlier capture-dialog fix was
also exercised on Thor. APK SHA-256:
`ed6ef888648468b4ebeb94e8f1ae74a195bb1f0a3f27e4fb3646d5462a4beb6b`.
- Upstream integration preserves the new WoW chat panel,
platform-pointer and PiP fixes. The fast-double-tap stick-click rule and
its six regressions were adapted to the shared renderer; WoW field
selection also preserves Ctrl held by a profile.
- Latest Thor UI pass: existing Setup navigation, profile/layout
previews, action persistence, centered element creation, main-screen
editing over Steam, secondary canvas with primary tools/properties, and
X returning from the docked menu to controls.
- Across the documented device sessions, the user tested native physical
buttons/sticks/triggers, Deck pads/grips, desktop
pointer/clicks/scrolling/arrows, Android keyboard with physical input,
Ctrl+F and Ctrl+A/X/V/S, simultaneous physical/touch holds, menu release
and Home/resume. These historical checks are distinguished from the
latest UI-only pass.

[Revision-specific results, APK hashes and
limitations](https://github.com/Delete228/DroidDeck/blob/codex/unified-controller-input/docs/development/unified-input-validation.md).

## Coordination and remaining work

- #1202 (jmarti326): shortcut capture/device identity and the
shared-assignment discussion informed this work.
- #1305 (kingokksa): shared PC-key definitions were adapted from that
PR; this does not absorb its entire feature set.
- #1414, #581, #1226 and #1150 remain relevant for external-device
input, calibration, keyboard routing and DualSense integration. The
DualSense coordination discussion in this PR describes adapter
boundaries and duplicate-stream suppression.

Import/export, automatic per-app selection, macros, multi-button
activation chords and automatic text-field keyboard detection remain out
of scope. A single binding emitting a key combination is implemented.

The configuration UI is now implemented and exercised on Thor. I am
happy to continue UX/accessibility/localization refinements here before
merge, or in follow-up PRs, according to maintainer feedback. The main
review questions remain the shared model, source ownership/lifecycle
contract, migration policy and integration with other input work.


## UI walkthrough on AYN Thor

Actual captures from the installed Input Test build. In paired images,
the main display is above the second display. The test application name
and local build badge are not production branding changes.

<details>
<summary>Controller settings: separate defaults for Desktop and Game,
followed by the profile library.</summary>

<img width="960" alt="Controller settings: separate defaults for Desktop
and Game, followed by the profile library."
src="https://github.com/user-attachments/assets/a4c156d6-9ded-43df-b0f8-e5f34028806d"
/>

</details>

<details>
<summary>Physical controller: bindings grouped by control type, with
each current action visible.</summary>

<img width="960" alt="Physical controller: bindings grouped by control
type, with each current action visible."
src="https://github.com/user-attachments/assets/a8a840e3-19ac-473f-9f55-b06ae9c637b5"
/>

</details>

<details>
<summary>Reusable layouts: bounded previews and separate screen
targets.</summary>

<img width="960" alt="Reusable layouts: bounded previews and separate
screen targets."
src="https://github.com/user-attachments/assets/cf0cf7d1-42f3-46cd-b50b-f01e1fa8ed41"
/>

</details>

<details>
<summary>Main-screen editor over the live Steam UI, with the movable
tools expanded. The background is Steam artwork, not a running
game.</summary>

<img width="960" alt="Main-screen editor over the live Steam UI, with
the movable tools expanded. The background is Steam artwork, not a
running game."
src="https://github.com/user-attachments/assets/0781f7c8-fd10-47e5-8a23-817f945a6289"
/>

</details>

<details>
<summary>Dual-screen editor: tools and selection on the main display,
full-size placement canvas on the second display.</summary>

<img width="720" alt="Dual-screen editor: tools and selection on the
main display, full-size placement canvas on the second display."
src="https://github.com/user-attachments/assets/f13d4ef6-be6d-4d95-9d42-e047e0a63b8a"
/>

</details>

<details>
<summary>The selected lower-screen trackpad stays visible while its
properties open on the main display.</summary>

<img width="720" alt="The selected lower-screen trackpad stays visible
while its properties open on the main display."
src="https://github.com/user-attachments/assets/26baf597-f037-419e-9601-e48f086c0d4f"
/>

</details>

<details>
<summary>Session menu: expand Profile to select the physical,
main-screen and second-screen parts independently.</summary>

<img width="720" alt="Session menu: expand Profile to select the
physical, main-screen and second-screen parts independently."
src="https://github.com/user-attachments/assets/27ac4d9c-68b3-4257-8353-23104e2343de"
/>

</details>
2026-11-07 08:32:50 -05:00
Jordan Y 7c429f3a26 fix: droiddeckctl pacing reads timestats dumps with Windows line ends (#2186) 2026-11-07 08:23:06 -05:00
Kurt Himebauch aba55031c3 perf: answer chown and bound thermal paths through the proot fast path (#2203) 2026-11-07 01:08:06 -05:00
MaxsTechReview 0b4bc827e7 fix(battlenet): stop Battle.net's pages playing videos on the CPU (#2195) 2026-11-06 20:39:37 -05:00
MaxsTechReview 066834d17d feat: compress idle game memory before low-RAM devices run out (#2187) 2026-11-06 19:49:06 -05:00
Delete228 f44826ae3a Merge main and retain unified input behavior 2026-11-07 00:01:20 +03:00
Kurt Himebauch e1987ca785 chore: pin gamescope to the DroidDeck-Components p22 release (#2183) 2026-11-06 14:35:10 -05:00
Delete228 ba5876bf6d Merge latest upstream main before Controller UI integration
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/SessionActivity.kt
#	app/src/main/java/com/droiddeck/launcher/input/OnScreenControls.kt
#	app/src/main/java/com/droiddeck/launcher/input/PadBridge.java
#	app/src/main/java/com/droiddeck/launcher/ui/DeckControlsPanel.kt
#	app/src/test/java/com/droiddeck/launcher/input/PadBridgeQamTest.kt
2026-11-06 14:37:50 +03:00