mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
+13








e7bc4b03a5
* Add VITURE glasses support: hardware control, 3D, head tracking Glasses get their own controller over the MCU (permission, 120 Hz default, brightness, volume, sunblock, 2D/3D) and the game moves onto them when plugged in. The IMU drives Cardboard head tracking and the SteamVR driver. 3D falls back to one eye's view on phones that can't send the 3840x1080 signal. Options open from a glasses button in game and on the Steam page. * Drop the glasses settings like Steam's: Play-style button, Settings lip, tabs * Hang the glasses Settings lip over the top of the button * Space glasses MCU commands so the IMU rate sticks, fit fixed resolutions to the glasses * Keep Steam alive with SteamVR registered, run SteamVR at the glasses' rate, keep the glasses lip on top * Fit VR games on the glasses: full view, seated origin at the head, pad hands reach with the d-pad * Let the pad hands reach a metre out so buttons with deep colliders press * Show every VR frame in order at 120 fps: wait for SteamVR's GPU work, present only finished copies, skip the grace on idle shared counters * Glasses: SteamVR starts and stops with them, closer view with a field of view setting, Recenter on the pad * Glasses: SteamVR field of view defaults to 90° * VR pad hands: sticks move and aim the hands, left stick click switches the sticks to the game, d-pad pulls a held hand * Glasses: Play SteamVR beside the glasses button instead of starting SteamVR with them, pad hands drawn as Index controllers and off while other VR controllers are connected, glasses sheet tabs sized like Steam's * Track glasses mode, refresh-aware glasses window, align Play SteamVR with Play Steam * Fix 3D on the glasses: full-res SteamVR presenter, revert VR 3D at session end, coalesce vsync ticks, no session restart after end * Address review: glasses output after installs and session end, VR revert retry, display fallback, presenter swapchain retry, agent hex checks, drawer bumpers, glasses translations, docs * Harden VITURE stereo negotiation and output refresh tracking * Localize stereo recovery feedback and record Astra reconnect tests * Improve test failure diagnostics and refresh Astra firmware findings * Handle VITURE docks separately and reject truncated USB frames * Glasses: remember a 3D fallback per model, docks as display only, VR timing fixes - A 3D attempt that ends at the 640x480 fallback is remembered for that glasses model, so Play SteamVR no longer blacks out the glasses after every restart; Retry clears it and "3D for VR games" stays reachable - A VITURE Mobile Dock on its own no longer counts as glasses; through a dock Play SteamVR is hidden and the sheet shows the output and why there is no control - HDR is probed on the glasses when the session starts there - Glasses frames with a mismatched checksum are marked in the status frame log - SteamVR layer and driver vblank counters stay monotonic across refresh changes; one-eye render scale applies from activation and on mono/3D switches - Compositor binds announce the refresh clients were last told; the presenter retries a failed first swapchain - Dead strings and helpers removed; docs updated * Glasses: drop USB frames with a bad checksum * Glasses: count frames dropped for a bad checksum * Feat/viture xr (#907) * Add RayNeo Air and GT support to the XR glasses pipeline * Add RayNeo Air and GT support to the XR glasses pipeline * Checkpoint glasses tracking, calibration, and SteamVR launch integration * Checkpoint glasses tracking, calibration, and SteamVR launch integration * Add XREAL 1S support and correct GT and Pro 2 tracking * Add XREAL 1S support and correct GT and Pro 2 tracking * Fix mirrored glasses tracking and recover SteamVR after device loss * Fix mirrored glasses tracking and recover SteamVR after device loss * Document confirmed XREAL 1S brightness control * Document confirmed XREAL 1S brightness control * Fix SteamVR glasses defaults and rendering controls * Fix SteamVR glasses defaults and rendering controls * chore: keep only the 3 newest session log folders (#852) * chore: add agent comment rules (#748) Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> * Fix SteamVR polling, VITURE checksums, and fence tracking Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter. Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage. * Fix SteamVR polling, VITURE checksums, and fence tracking Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter. Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage. * fix: wireless ADB page names the child-process switch the way Android shows it (#848) * l10n: refresh stale and untranslated strings in es, fr, ko, ru (#850) * l10n: refresh stale and untranslated strings in es, fr, ko, ru - mode_added_art_hint: now mentions Steam then SteamGridDB (all four locales). - wincomp_needs_installer / wincomp_installers_note: es and ko still described the old "coming soon" behaviour; align with the current English text. - summary_remove_game: was still English in all four locales. Machine-assisted translation; native-speaker review is welcome. * l10n(es): say registro, not grabación, for the missing installer record --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> * fix: a failed cloud save recovery no longer ends the app (#846) The cloud-recovery thread started at every app start had nothing to catch an error, so one went to CrashHandler, which halts the process with status 2. In unit tests the thread can outlive the test that started it, fail on the torn-down preferences, and take the whole test run down. The error is now logged and the thread ends. Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> * fix: an interrupted Linux runtime download resumes instead of starting again (#847) * fix: an interrupted Linux runtime download resumes instead of starting again installOnce() asked the downloader to resume but always deleted the archive in its finally block, so the next attempt began at zero. The archive is now named after the release version and checksum, a failed download keeps what it fetched, and archives of other releases (and the old fixed name) are removed. A checksum or extraction failure still deletes the file. * test: Downloader resumes a cut-off download from the bytes it kept Starts a socket server that promises the whole file, hangs up part way and then answers a Range request, and checks that the second downloadFile call resumes with the right offset and ends with identical bytes. * fix: a resumed download discards a 206 that does not start where the kept bytes end Downloader appended every 206 response to the partial file without looking at Content-Range. A server or proxy that answers a Range request with a different offset would leave a corrupt file. Check that the range starts at the kept length; otherwise delete the partial file and start again without a Range header. Adds a test with a server that answers the Range request from offset 0. --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> * l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings (#849) * l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings Adds the strings values-de, values-it and values-pt were missing (14 each) and the six that values-fr, values-es and values-ru were missing. Machine-assisted drafts that follow each file's existing register and terms; native review is welcome. * l10n: correct missing active swap translations --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> * fix: droiddeckctl reaches variant-package builds (recover #842) (#854) fix: droiddeckctl reaches builds shipped under a variant package The provider authority and the start Activity's component were fixed to com.droiddeck.launcher, so droiddeckctl could not talk to the antutu, ludashi or pubg builds from tools/release/variants.txt. DROIDDECK_PACKAGE now picks the package; the Activity's class name stays the same in every variant. Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> * feat: droiddeckctl reports on-screen pacing (recover #843) (#855) feat: droiddeckctl pacing reports the frames that reached the screen Reads SurfaceFlinger's timestats for the game's layer (or the session's SurfaceView) over a capture: frames on screen, fps on screen, the present-to-present histogram and how many intervals were off the usual cadence. Records split by a refresh-rate change are summed. A game's own fps counter counts frames handed over, so it can't show dropped or doubled frames. Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> * fix: Steam can restart more than twice in a session (#754) * fix: Steam can restart more than twice in a session Every exit 42 counted toward the cap that stops an update which will not install, and the count was never cleared, so the third restart of a session ended it however far apart they were. A client that ran for a minute before asking is not that loop: the count starts again. * fix: measure the client's run time as it exits The time spent collecting logs and checking FEX after the client exits counted toward its run, so a slow cleanup could make a quick exit 42 look like a long one and clear the count. --------- Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> * fix: the front end keeps its page when the Fold is folded or unfolded (#832) * fix: the front end keeps its page when the Fold is folded or unfolded MainActivity, FileManagerActivity, FilePickerActivity and StoreLoginActivity did not declare the screen-size configuration changes, so folding or unfolding a foldable recreated them and dropped their in-memory state. They now handle orientation, screenSize, screenLayout, smallestScreenSize and density themselves, like SessionActivity already does. uiMode is left out on purpose. * fix: store login keeps rebuilding its toolbar when the display density changes StoreLoginActivity builds its toolbar in code with dp-to-pixel values taken at creation time and has no onConfigurationChanged. It no longer declares density in configChanges, so a density change recreates it as before this PR. Size and layout changes are still handled in place. --------- Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> * Release pointer buttons without replaying stale motion (#803) * Fix cursor warping on native touch down * Preserve input cleanup without replaying stale pointer motion --------- Co-authored-by: siyuan.qsy <siyuan.qsy@alibaba-inc.com> * l10n: translate the strings added since #284 into zh-CN, zh-HK, zh-TW and ja (#539) * l10n: translate the 184 strings added since #284 (zh-CN/HK/TW, ja) The stores, cloud saves, Windows components, Steam touch, desktop removal and background-downloads strings landed after #284's translation snapshot, so every one of them fell back to English. Translate all 184 into Simplified Chinese, Traditional Chinese (Taiwan and Hong Kong) and Japanese, keeping each locale file's own vocabulary (工作階段/會話, 解除安裝/卸載, 元件/組件, 設定). Also fix four Hong Kong strings that kept the simplified 着色器. * l10n(zh): fix stale and ambiguous strings found in review Severe: summary_remove_game was untranslated English in all three files; mode_added_art_hint, wincomp_installers_note and wincomp_needs_installer still described the pre-#480/#492 English. Also: distinguish the Setup wizard from Android system settings in zh-HK/zh-TW (初始設定 / 系統設定 / Android 設定), unify library as 收藏庫, component as 組件 in zh-HK, session as 會話 in zh-HK, account as 帳戶 in zh-HK, Trending as 趋势/趨勢, Forget as 清除, reword the re-applied-at-launch, action-failed and already-in-Games strings, and play as 執行/開啟遊戲 in zh-TW/zh-HK. * l10n(ja): fix stale and ambiguous strings found in review --------- Co-authored-by: Dantalian <Dantalian-dev@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> * Rebuild and verify Windows VR runtime components * Rebuild and verify Windows VR runtime components * Add top, bottom, and centered picture positions for Steam sessions (#871) * fix: dependency scan ignores folders above the game folder (#859) * DroidDeck-Proton: get releases from the Droid-Deck org repo DroidDeck-Proton is now built and released on Droid-Deck/DroidDeck-Proton (11-9.1 is Latest there), so the Components page, the update check and the guest installer read releases from the org repo instead of The412Banner's. * feat: low-latency controller and touch toggles in the session drawer (#808) * fix: SteamGridDB lookups no longer stick as "no match" after a failure; add a search to the art chooser (#876) * feat: Steam home wall fills from the signed-in library instead of store picks (#878) * test: cover GPU family and support classification (#880) * Fix local builds on Linux failing after the first Comet build (#866) * feat: sort the store library and installed tabs, and switch between card and list views (#798) * feat: three-finger tap toggles Steam's on-screen keyboard in game mode (#782) * fix: keep Quest-patched Turnip as the auto default (#853) * Fix deploy_local.sh and droiddeckctl not finding a phone connected over wireless debugging (#864) * fix: keep proot sessions alive when the kernel loses fork events (#457) * fix(quest): correct desktop and Steam display corruption across sessions (#733) fix(quest): stabilize desktop and Steam layer presentation Keep Quest SDR scenes and the black backdrop off the Android Vulkan swapchain regardless of session order or HDR preferences. Only report the first frame after client content is submitted. Add backdrop lifecycle and first-frame regression coverage. Record the user's successful Quest 3 Test 2 result. Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> * fix: keep SteamVR syscall hooks and report Steam crashes (#905) * fix: preserve SteamVR syscall hooks * fix: report Steam crashes through gamescope * fix: honor glasses pacing and resolution in SteamVR launches --------- Co-authored-by: connor g <connorsspam1992@gmail.com> Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Co-authored-by: Yassine Gherbi <y.gherbi.dev@gmail.com> Co-authored-by: Resul Övür <92828257+ovurrsl@users.noreply.github.com> Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> Co-authored-by: Kirk <kirtoy.junsay@gmail.com> Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> Co-authored-by: asdf17128 <asdf17128@gmail.com> Co-authored-by: siyuan.qsy <siyuan.qsy@alibaba-inc.com> Co-authored-by: Dantalian <wsu091227@qq.com> Co-authored-by: Dantalian <Dantalian-dev@users.noreply.github.com> Co-authored-by: The412Banner <the412banner@users.noreply.github.com> Co-authored-by: The412Banner <205237651+The412Banner@users.noreply.github.com> Co-authored-by: FX1922 <120828496+FX192@users.noreply.github.com> Co-authored-by: Muaz Kadan <32595066+muazkadan@users.noreply.github.com> Co-authored-by: kingokksa <47888451+kingokksa@users.noreply.github.com> Co-authored-by: Jamie Schenker <jam1esch3nk3r@gmail.com> Co-authored-by: Jagger15 <jagger2333@live.com> * Glasses: keep a pending 2D restore and the SteamVR recovery offer, show Play SteamVR only with a picture * Translate the glasses calibration, XREAL and SteamVR recovery strings * SteamVR recovery: give a finished teardown time to collect logs before restarting * Credit the projects the XR glasses support builds on * Joy-Cons: SteamVR hands from their gyro while SteamVR runs, one combined pad otherwise * Add experimental Luma tracking and stabilize SteamVR launches Add native Luma Ultra spatial tracking, optical controllers, and stereo calibration with DroidDeck styling, imperial/metric measurements, and pending recenter recovery. Include the pinned hand model and calibration board. Keep live guest and SDK files outside Android's evictable cache. Filter the Adreno 840 sysmem override in the shared SteamVR helper, serialize timeline mirroring, retain invalid optical controllers without event floods, and prevent an unprivileged scheduling fallback from lowering compositor RTTIME. Validation: latest APK build and runtime freshness checks pass; 247 affected glasses/calibration tests pass. Native VR, helper, and packaging regressions pass. Two earlier phone library launches sustained 60 fps for 308 and 188 seconds without recovery or restart. The newer calibration APK is installed and checksum-verified; calibrated-hand and sustained gameplay acceptance remain pending. One existing Android test requires Windows symlink privileges. * Headsets: settings badge on Play SteamVR instead of a top-right button, both shown only once connected * Hand tracking: camera-tracked Joy-Cons and bare hands in SteamVR, drawn as white gloves - Joy-Con movement from the accelerometer (report reordering, per-Joy-Con calibration, zero-velocity updates) - MediaPipe pose and hand landmarks from the device camera, placement check, per-hand Kalman filter with the Joy-Con - No controller connected: bare camera hands with Quest-style gestures, 1€ filtered, rendered as a white glove model built from a distance field - SteamVR safe mode cleared before each start so one crash no longer blocks the driver * Improve Luma Ultra tracking and isolate device-specific controls Refine stereo hand and Joy-Con tracking, gesture release, calibration, recovery UI, diagnostics, display modes, and compositor handling. Keep Ultra controls and input routing behind a live SDK-confirmed Carina connection; clear retained spatial mode when other glasses connect. Add optional UVC transport with pinned dependencies and packaged notices. Validation: 456 Android tests, ARM64 VR build and native regressions, 47 tooling tests, SDK/UVC-enabled and disabled builds, and 150 packaged runtime checksums. Two shader compilation tests skipped because the compiler is unavailable. Hardware interaction gates remain experimental. * Delay Luma Ultra 6DoF until SteamVR settles Suspend spatial SDK startup while SteamVR launches, then restore it two seconds after RUNNING is confirmed. Preserve saved tracking preferences and automatically recenter same-device restoration, including cancellation and early stop. Discard superseded launch requests and status replies after session teardown. Validation: 128 focused JVM tests passed on the isolated commit snapshot; 129 passed with the current local hand-tracking work. Debug build and runtime checks passed, and the reviewed APK was installed and hash-verified on the phone. * Clarify glasses and phone camera tracking options Separate Luma Ultra camera controls from phone-camera tracking so the two hand-tracking switches have clear sources. Label bare-hand controllers and Joy-Con position tracking explicitly, explain physical Joy-Con inputs and camera priority, and update the camera prompt and calibration guidance. Use fresh phone section and toggle labels so existing translations do not hide the distinction. Preserve tracking behavior and saved preferences. Validation: 39 existing UI tests, debug build, and packaged runtime verification passed. Reviewed changes and installed the APK on the connected phone. * Fix tracking handoff and shared runtime regressions Release spatial ownership and controller overrides when output returns to the phone. Preserve USB-loss recovery, keep Vulkan submissions asynchronous, clean persistent guest scratch on runtime removal, and clarify the 6DoF head-tracking control. * Apply smart-glasses virtual screens across DroidDeck menus * Finalize Luma hand baseline and document smart-glasses support Keep raw grayscale-to-RGB Full hand inference, wrist-only Joy-Con reconstruction, bounded visual holds, and the dead-zone rollback. Retain opt-in steady aim, recenter controls, reproducible Lite models, and the JPEG-padding fix. Remove unsuccessful benchmark-only and USB transport experiments. Document the cross-brand 3DoF matrix, Luma Ultra 6DoF, SDK requirements, and app-wide screen behavior. * Fix executable permission for SteamVR launcher * Address hand tracking and VR review findings * Bound stereo recovery test image comparison memory * Localize smart-glasses setup and tracking diagnostics Move calibration controls, status messages, validation errors, distance labels, and printed-board instructions into Android string resources. Add and update machine-assisted translations in all 32 supported non-English locale catalogs, with corrections for recentering, hand tracking, product names, and measurements. Keep print instructions within the page using text layout that supports wrapping and right-to-left text. Correct the Lite model toggle description to reflect its Lite palm detector and Lite landmark model. Add locale coverage, format-argument, line-break, and measurement checks, plus French and Arabic calibration UI tests. Preserve tracking calculations and the shared runtime storage behavior. Validation: Android resource and Kotlin compilation; 3 localization checks; 68 focused tests; 1,597 JVM tests at a 512 MB heap; French and Arabic UI renders. * Correct smart-glasses translations from PR review Fix the 42 localization findings covering hand and head terminology, disabled states, calibration dimensions, camera visibility, capture rejection, and menu references. Correct the repeated capture-error ambiguity in related locales and clarify its English source. Keep Romanian setup instructions consistent with the existing informal voice. Extend localization measurement validation to distance hints and retry guidance. Verify the recovery screen's palette-dependent text color with one row-sized pixel buffer and guaranteed bitmap cleanup. Validation: Android resource and Kotlin compilation; 3 localization checks across all supported locales; 68 focused JVM tests at a 512 MB heap. * Work around SteamVR device loss with Adreno 840 Turnip v0.2.0 * Withdraw ineffective SteamVR flushall workaround --------- Co-authored-by: maxjivi05 <maxjividen@gmail.com> Co-authored-by: MaxsTechReview <6025299+maxjivi05@users.noreply.github.com> Co-authored-by: connor g <connorsspam1992@gmail.com> Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Co-authored-by: Yassine Gherbi <y.gherbi.dev@gmail.com> Co-authored-by: Resul Övür <92828257+ovurrsl@users.noreply.github.com> Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com> Co-authored-by: Kirk <kirtoy.junsay@gmail.com> Co-authored-by: Kirk <12691869+KirkPatrickJunsay@users.noreply.github.com> Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com> Co-authored-by: angelrenefm <17342133+angelrenefm@users.noreply.github.com> Co-authored-by: asdf17128 <asdf17128@gmail.com> Co-authored-by: siyuan.qsy <siyuan.qsy@alibaba-inc.com> Co-authored-by: Dantalian <wsu091227@qq.com> Co-authored-by: Dantalian <Dantalian-dev@users.noreply.github.com> Co-authored-by: The412Banner <the412banner@users.noreply.github.com> Co-authored-by: The412Banner <205237651+The412Banner@users.noreply.github.com> Co-authored-by: FX1922 <120828496+FX192@users.noreply.github.com> Co-authored-by: Muaz Kadan <32595066+muazkadan@users.noreply.github.com> Co-authored-by: kingokksa <47888451+kingokksa@users.noreply.github.com> Co-authored-by: Jamie Schenker <jam1esch3nk3r@gmail.com> Co-authored-by: Jagger15 <jagger2333@live.com>
367 lines
24 KiB
Markdown
367 lines
24 KiB
Markdown
# Agent control
|
||
|
||
`tools/droiddeckctl` drives DroidDeck on a real device over ADB, for agents and scripts that test
|
||
the app end to end: start and stop sessions, launch and quit Steam games, wait for a game to have
|
||
focus, send input, inspect the app's UI and gamescope's windows, run commands inside the session,
|
||
and collect logs, screenshots and recordings. `tools/droiddeck-scenario` repeats a scripted test
|
||
and reports every run.
|
||
|
||
## How it works
|
||
|
||
- **The provider** `content://com.droiddeck.launcher.agent` (`agent/AgentBridgeProvider.kt`)
|
||
answers each command with JSON. It and the session-start Activity (`AgentStartActivity`) are in
|
||
every build and require `android.permission.DUMP`, held by the ADB shell and privileged or
|
||
explicitly granted callers. The provider checks the calling permission before decoding requests.
|
||
A build shipped under another package (`tools/release/variants.txt`) answers at
|
||
`content://<package>.agent`: set `DROIDDECK_PACKAGE` to that package for `droiddeckctl`.
|
||
- **The guest agent** `droiddeck-agent` runs inside every session (started by
|
||
`droiddeck-session`). It runs commands with the session's own environment, publishes gamescope's
|
||
focus, and talks to the Steam client's DevTools, which only accepts guest processes. The app and
|
||
the agent exchange request and answer files under the session root (`files/session/agent`).
|
||
- **The UI dump** reads the app's Compose semantics from inside the app (`agent/AgentUi.kt`), so it
|
||
works on every display and window. UI Automator, by contrast, dumps whichever window has focus,
|
||
which on a dual-screen device is often another app's.
|
||
|
||
## Access
|
||
|
||
Player controls and observations work over authorized ADB on any build: `state`, `start`, `stop`,
|
||
`resume`, `wait`, `launch`, `quit`, `focus`, `input`, `ui`, `logs`, `screenshot`, `record`, `pacing`,
|
||
`displays`, `wake`, `glasses` (except `glasses raw`), and `access`.
|
||
|
||
**Setup > Diagnostics > Debugging tools** is off by default in every build, including debug builds.
|
||
It enables `run`, `guest`, `cdp`, all `env` operations, `override`, `prefs`, and `glasses raw`. The
|
||
toggle switches directly without a confirmation dialog. The bridge refuses to change it through `ui`
|
||
or preference writes. The existing preference key, `setting-agent-commands` test tag, JSON command
|
||
names, and `AGENT_COMMANDS_DISABLED` error code are unchanged. Refusals exit 7; `droiddeckctl access`
|
||
reports whether commands are enabled.
|
||
|
||
Disabling the toggle clears queued and persistent agent environment settings and installed binary
|
||
overrides. Disabled settings cannot apply at the next session start. Already running commands and
|
||
effects already applied to a session are not undone; stop and restart that session to discard them.
|
||
|
||
Authorized command execution runs inside the runtime containing Steam's saved login. CDP evaluates
|
||
JavaScript in the authenticated Steam client, and preferences can contain private values. These
|
||
capabilities remain unrestricted when enabled: the toggle is an explicit opt-in, not credential
|
||
isolation or a sandbox against the authorized caller. Main already supported arbitrary guest
|
||
programs through `run`; restricting credential access would require addressing that route too.
|
||
ADB access remains a trust boundary, and ADB input can operate the device's own toggle.
|
||
|
||
The helper exchanges private files with the app and adds no network listener. The guarded Steam
|
||
DevTools port is bound to loopback and accepts only peers with a fresh private registration for a
|
||
live socket descriptor, matching socket inode, destination port, and address family. The existing
|
||
x86_64 Decky loader exception remains: that mode does not enable the guest-only acceptance guard.
|
||
|
||
Requests are limited to 256 KiB of encoded JSON. Guest responses and DevTools messages are limited
|
||
to 1 MiB; provider replies above 384 Ki characters return `RESPONSE_TOO_LARGE` to avoid overflowing
|
||
Binder. Command output retains at most 256 KiB per stream while continuing to drain both streams.
|
||
Timeouts must be finite, positive, and no greater than 600 seconds, and terminate the command's
|
||
process group. Override installation refuses path traversal and symbolic links.
|
||
|
||
Automatic process logs record environment keys and argument counts rather than values or full
|
||
argument lists. UI nodes marked as passwords redact text, editable text, descriptions, and state
|
||
text. This does not redact arbitrary authorized command results, CDP data, preferences, or screen
|
||
captures. Scenario directories are owner-only and JSON reports are written with mode 0600. Review
|
||
artifacts before sharing; a live `logs` pull copies the current folder without a fresh export scrub.
|
||
|
||
## Device selection and output
|
||
|
||
The CLI resolves one authorized device using `ADB_SERIAL` or `ANDROID_SERIAL` when set. Otherwise it
|
||
de-duplicates transports that report the same device serial and asks for an explicit serial if
|
||
multiple devices remain. Pass `--serial` before the command to choose directly; set `ADB` (or pass
|
||
`--adb`) to choose an adb executable. `tools/deploy_local.sh` uses the same resolver.
|
||
|
||
Every command writes one JSON object to stdout and its resolved serial to stderr. Exit codes: 0
|
||
success, 2 invalid or rejected command, 3 ADB or device error, 4 session error, 5 timeout, 6
|
||
artifact or file error, 7 debugging tools disabled.
|
||
|
||
## Commands
|
||
|
||
```sh
|
||
tools/droiddeckctl state
|
||
tools/droiddeckctl start steam --wait
|
||
tools/droiddeckctl start steam --wait --reuse
|
||
tools/droiddeckctl start steam --steamvr --wait
|
||
tools/droiddeckctl start desktop
|
||
tools/droiddeckctl run /usr/bin/foo -- arg1 arg2
|
||
tools/droiddeckctl wait ready|idle|failed
|
||
tools/droiddeckctl stop
|
||
tools/droiddeckctl resume
|
||
|
||
tools/droiddeckctl launch 489830 --wait
|
||
tools/droiddeckctl wait focused 489830
|
||
tools/droiddeckctl wait game
|
||
tools/droiddeckctl quit 489830
|
||
tools/droiddeckctl focus
|
||
|
||
tools/droiddeckctl input key ENTER
|
||
tools/droiddeckctl input text 'hello world'
|
||
tools/droiddeckctl input tap 640 360
|
||
tools/droiddeckctl input swipe 100 600 100 100 --duration 400
|
||
tools/droiddeckctl input pad a
|
||
tools/droiddeckctl input stick left 0 -1 --hold 500
|
||
tools/droiddeckctl input trigger right 1.0
|
||
|
||
tools/droiddeckctl ui dump
|
||
tools/droiddeckctl ui click rail-steam
|
||
tools/droiddeckctl ui click --match "Play"
|
||
tools/droiddeckctl ui set-text some-field --text abc
|
||
|
||
tools/droiddeckctl guest -- xprop -root GAMESCOPE_FOCUSED_APP
|
||
tools/droiddeckctl cdp targets
|
||
tools/droiddeckctl cdp eval 'SteamClient.Apps.GetMyRunningApps?.()'
|
||
tools/droiddeckctl env set DXVK_HUD=fps
|
||
tools/droiddeckctl env list
|
||
tools/droiddeckctl env clear [next|persistent|all]
|
||
tools/droiddeckctl override gamescope ./gamescope
|
||
tools/droiddeckctl prefs get session
|
||
tools/droiddeckctl prefs set session some_key true --type bool
|
||
|
||
tools/droiddeckctl logs latest ./session-artifacts
|
||
tools/droiddeckctl screenshot ./screen.png --display 4
|
||
tools/droiddeckctl xr-frame ./xr-capture
|
||
tools/droiddeckctl record ./clip.mp4 --seconds 8 --display 0
|
||
tools/droiddeckctl pacing --seconds 30
|
||
tools/droiddeckctl displays
|
||
tools/droiddeckctl wake
|
||
|
||
tools/droiddeckctl glasses status
|
||
tools/droiddeckctl glasses set refreshHz 120
|
||
tools/droiddeckctl glasses set 3d on
|
||
tools/droiddeckctl glasses simulate --seconds 6 --yaw 15
|
||
tools/droiddeckctl glasses stop-simulate
|
||
tools/droiddeckctl glasses mode 0x34
|
||
tools/droiddeckctl glasses raw 0x0007
|
||
```
|
||
|
||
Glasses status reports `deviceMode` from the MCU separately from `sbs`, which
|
||
requires a physical stereo display mode. `switching3d` marks the bounded
|
||
confirmation window. A successful mode-command reply alone does not confirm 3D.
|
||
`badFrames` counts reports and IMU packets dropped for a bad checksum.
|
||
|
||
`xr-frame` captures both app eye textures while SteamVR is open, saving `left.png`,
|
||
`right.png` and `capture.json` with the matching source frame, projection poses,
|
||
FOV and dimensions. It works over wired or wireless authorized ADB.
|
||
Newer builds also include `source.png`, the incoming stereo texture before the
|
||
Android eye draw, and sRGB attachment/write state in the metadata for color debugging.
|
||
Source PNGs use the incoming frame's color space and eye PNGs use sRGB. Transfers use Android's
|
||
gzip utility to avoid sending full uncompressed images over wireless ADB.
|
||
A unique request ID prevents stale images from passing as a fresh capture; no valid XR
|
||
layer produces an explicit error. Captures exclude Meta system overlays and
|
||
the headset compositor's final reprojection. GPU readback runs only on request
|
||
and can briefly affect frame timing. Remote files are overwritten each time.
|
||
|
||
A sleeping device suspends the session and hides the app's windows from `ui dump`; `wake` first.
|
||
|
||
`pacing` reads SurfaceFlinger's timestats for the game's layer (the session's SurfaceView when the
|
||
compositor has no layer of its own) and reports the frames that reached the panel, `fpsOnScreen`,
|
||
the present-to-present histogram and the share of intervals off the usual cadence. A game's own fps
|
||
counter counts frames handed over, not frames shown, so it can read 60 while one frame in four
|
||
stays on screen for two refreshes.
|
||
|
||
`input` needs a session that has drawn its first frame. Pad input goes through the session screen's
|
||
virtual pad, the same one the on-screen controls drive, so games that ignore touch still respond.
|
||
`launch`, `quit` and `cdp` need a Steam session; `focus` and `wait focused` need gamescope (every
|
||
mode except the desktop).
|
||
|
||
## State (schema 2)
|
||
|
||
`state` returns `schema`, `build`, `appVersion`, `runtime {installed, version}`,
|
||
`agent {guest, guestFocus, commands}` and `session`:
|
||
|
||
- `id`, `phase`, `running`, `mode`, `program`, `steamUi`, `steamUrl`, `steamVr`, `suspended`, `pip`,
|
||
`firstFrame`, `output` [w, h], `refreshHz`, `lastTransitionAt`, `guestPid`, `installing`
|
||
- `logDir`, `eventsFile`, `artifactsAvailable`, `artifactsComplete`, `failure {code, message, status}`
|
||
- `focus`: gamescope's focus as the guest agent last published it, or null:
|
||
`focusedApp`, `focusedWindow`, `focusableApps`, `baselayerAppIds`, `seq`, `t`.
|
||
|
||
Phases: `IDLE`, `PREPARING`, `INSTALLING_RUNTIME`, `STARTING_COMPOSITOR`, `STARTING_GUEST`,
|
||
`STARTING_STEAM`, `READY`, `SUSPENDED`, `STOPPING`, `FAILED`. Schema 2 only added `agent` and
|
||
`session.focus`; every schema 1 field is unchanged.
|
||
|
||
A game the player can see is `focusedApp == appId` **and** `appId in focusableApps`.
|
||
`GAMESCOPE_FOCUSABLE_WINDOWS` is what the Steam client orders the base layer from: a game missing
|
||
there stays behind the client's loading screen however healthy its window looks
|
||
(docs/development/steam-focus-debugging.md). `focus` returns the full snapshot, including each
|
||
server's windows with map state, override-redirect, app id, `WM_STATE`, pid, class, opacity and
|
||
`_NET_WM_STATE`.
|
||
|
||
## Hand tracking diagnostics
|
||
|
||
With debugging tools enabled, the glasses provider accepts `spatial-hand-options` with optional
|
||
boolean `lite`, `halfRate`, `parallel`, `adaptive`, and `scheduled` fields. These are temporary
|
||
tracking-session controls and do not change the CPU/GPU delegate. `parallel` selects concurrent
|
||
camera inference on CPU; GPU remains serial on its owning thread. `scheduled` enables persistent
|
||
one-hand and two-hand detector banks on CPU, using the one-hand bank after stable agreement
|
||
and periodically searching for another hand. GPU retains ordinary two-hand inference.
|
||
`adaptive` lets CPU input rise from 500 permille to 600 or 800 only after sustained processing
|
||
and observation-age headroom. Slow processing lowers the rate, and tracking loss resets it.
|
||
Explicit `halfRate` selects manual input and disables adaptation; if both fields are supplied,
|
||
`adaptive` is applied afterward. This allows restoring the manual preference and automatic mode
|
||
in one request. CPU, full model, parallel inference, scheduled discovery, and automatic input
|
||
starting at 500 permille are the defaults.
|
||
|
||
`handModel`, `handCpuParallel`, `handAdaptiveInput`, and `handScheduledTracking` identify the live
|
||
selection. `handInputRatePermille` reports the current requested frame fraction, while
|
||
`cameraCopyPermille` records the fraction used for the latest reported camera processing sample.
|
||
At a 25 Hz source, 500/600/800 correspond to input limits of about 12.5/15/20 Hz, not completed
|
||
tracking throughput. Calibration and explicit capture use 1000. `cameraFrameDivisor` remains
|
||
2 for 500, 1 for 1000, and 0 for intermediate fractions; use `cameraCopyPermille` for rate calculations.
|
||
`cameraHz` still describes the source cameras; `rateLimitedFrames` counts deliberate skips and
|
||
`dropped` counts eligible pairs replaced while processing is busy. These controls do not change
|
||
the glasses' sensor rate or native head tracking.
|
||
|
||
`spatial-hand-capture`, with debugging tools enabled, requests one local stereo pair in
|
||
`files/spatial/captures`. Use only for an explicitly requested diagnostic capture; it includes
|
||
the wearer's surroundings. It does not upload images.
|
||
|
||
`spatial-rgb-capture` requests one local JPEG from the Luma Ultra's separate UVC RGB camera.
|
||
It requires Android camera permission, USB permission for that camera, and debugging tools.
|
||
If USB permission is requested, approve it and explicitly repeat the capture request; permission
|
||
approval alone does not save an image. Poll `spatial-rgb-status` for completion and the local
|
||
`files/spatial/rgb-captures` path. The isolated worker stops the stream after the requested
|
||
diagnostic window or a timeout, then exits. The default `transport: "sdk"` uses the vendor's
|
||
fixed 1920×1080/30-fps stream. With the optional UVC build present, `transport: "uvc"` selects
|
||
the independent eight-buffer transport; `width`, `height`, `fps`, and `durationMs` default to
|
||
640, 480, 30, and 5000. Duration is bounded to 1000–30000 ms and camera modes must be supported
|
||
by the device. This remains a manual debugging command, not a source of live controller positions.
|
||
Both paths save `usb.log` and `usb-descriptors.bin`, including when acquisition fails.
|
||
Direct UVC reports `snapshotAcquired` separately from `streamHealthy`. Health requires the
|
||
full requested observation window, at least 80% of requested FPS, and no frame gap exceeding
|
||
the larger of 250 ms or three frame periods. `observedFps`, `maxFrameGapMs`, and
|
||
`tailFrameAgeMs` include silence at the end of the window. Status remains busy with
|
||
`phase: "finishing"` until the capture worker exits, so a new request cannot race its teardown.
|
||
`receiptNs` is the host callback time, not camera exposure time; the SDK timestamp can be zero.
|
||
This diagnostic does not supply controller positions or replace the stereo tracking stream.
|
||
|
||
`spatial-hand-benchmark` takes a numeric `capture` directory name from the app's existing
|
||
`files/spatial/captures` folder. It replays that saved stereo pair on CPU with full-model two-hand
|
||
inference, scheduled full, scheduled lite, then full-model two-hand inference again, all with
|
||
concurrent cameras. It clears live hand input during the comparison and never captures new images.
|
||
Poll `glasses.spatial.handBenchmark` for completion, timing, and geometric results. Every replay
|
||
row reports `parallel` and `scheduled`. `wristOnly` identifies the selected controller mode: bare-hand
|
||
replay estimates `hands`, while Joy-Con replay estimates `joyCons` from wrist 0. Both report
|
||
`wrists` after stereo geometry checks. Image preprocessing is raw grayscale replicated into RGB;
|
||
lens correction applies once to landmark coordinates before triangulation. `wrists` reports landmark 0 after the existing stereo depth
|
||
and reprojection checks, with the same unambiguous handedness pairing. Live Joy-Con tracking
|
||
also uses wrist 0 directly, with motion and freshness checks; its `positionEstimator` is
|
||
`joycon-wrist`. Samples identify the selected bank as `inferenceHandLimit`
|
||
(1 or 2). Timings exclude model construction. Saved-pair results are not live accuracy,
|
||
second-hand discovery delay, game performance, or motion-to-display latency measurements.
|
||
|
||
The live baseline is Full palm + Full landmarks. `handImagePreprocessing` is `raw-grayscale-rgb`;
|
||
`handLensCorrection` reports `pinhole-points-only`, `fisheye-points-only`, or `none`.
|
||
`steadyHandAim` is true only when bare hands and the optional shoulder-based aim setting are active.
|
||
|
||
`handTracking` includes `leftRawHands`/`rightRawHands` before handedness-confidence filtering,
|
||
`previousLeftRawHands`/`previousRightRawHands` from the previous inference on those detectors
|
||
(-1 after detector creation), and `leftInferenceMs`/`rightInferenceMs`. Existing
|
||
`leftCameraHands`/`rightCameraHands` count confident handedness results. Compare camera timing
|
||
against its previous raw count: the stock two-hand graph may repeat palm search when fewer
|
||
than two hand regions were tracked. `inferenceHandLimit` identifies the bank that produced these
|
||
counts; previous counts belong to that same persistent bank, not necessarily the preceding camera
|
||
frame. These counts approximate graph conditions; they do not directly expose internal graph
|
||
execution. Replay samples also include raw counts and camera timings. Accepted palms still
|
||
require stereo depth, while pinch and grip validity now use agreement between image-space
|
||
finger estimates from both cameras. Palm acceptance alone does not establish valid gesture input.
|
||
|
||
`handTracking.experimental` is always true. `observationTimeNs` identifies the processed camera
|
||
observation; `observationAgeMs` is its age when diagnostics were published. Per-side `left` and
|
||
`right` records expose `outcome`, `reconstructedPalmLandmarks`, raw trigger/grip estimates,
|
||
geometry validity, and final input validity. `observations` contains connection-lifetime
|
||
`processedPairs`, `inferencePairs`, and independent left/right acceptance, rejection-cause,
|
||
tracking-loss, and gesture-loss counters. Each processed camera sequence is counted once;
|
||
repeated status polling does not inflate these counters. Setup/off/clock/head failures count
|
||
as processed pairs but not completed inference pairs. Skipped callbacks are not fabricated as
|
||
observations. Sample deltas over a controlled hand-visible interval and inspect rejection
|
||
causes before changing thresholds; these counters do not prove external latency or accuracy.
|
||
|
||
`spatial.calibrationSource` is `device`, `luma-ultra-base`, or `none`.
|
||
`calibrationVerified` indicates an individual distance-validated profile; `calibrated` indicates
|
||
geometry is available for experimental tracking, including the bundled model base. A shared base
|
||
does not claim validation on every Luma Ultra. Matching local profiles take precedence.
|
||
|
||
`glasses set joyConTracking true` enables experimental Luma stereo-assisted Joy-Cons after
|
||
positional tracking is enabled. It defaults off, excludes bare-hand controllers, and appears in
|
||
both saved settings and spatial diagnostics. Driver logs identify each hand's transition between
|
||
`stereo hand` and `6DoF arm estimate`; camera positions are hand proxies, while Joy-Con sensors and
|
||
buttons supply orientation and input.
|
||
|
||
The shared-memory channel is protocol **4**, still 176 bytes. Snapshot flag bit 2 selects Luma
|
||
Joy-Con tracking; bit 1 selects bare-hand controllers. Its word at byte offset 36 is
|
||
`input_epochs`: four unsigned bytes in left-trigger, left-grip, right-trigger, right-grip order.
|
||
Each byte increments modulo 256 when that gesture becomes ineligible. Inspect these counters
|
||
when a loss may have happened between two reader samples: a later valid pose does not erase
|
||
the intervening loss. Versions 1 through 3 are rejected, so a decoder, app, and driver must agree
|
||
on version 4. Hand observation age remains limited to 200 ms, head age to 100 ms. For bare-hand
|
||
controllers, a gap beyond
|
||
200 ms from the driver's last accepted observation releases input state even when the next
|
||
read contains a fresh replacement. Duplicate observations cannot rearm an input; recovery
|
||
requires a newer valid released gesture before a subsequent press.
|
||
|
||
Driver lines containing `left optical input` or `right optical input` report state changes,
|
||
changed connection/origin/loss counters, and changed input API errors, rather than every frame.
|
||
`pose` is the displayed pose's validity and can remain true during its short held-pose window;
|
||
`input` is the current observation's eligibility for input processing. Read `flags` for independent
|
||
gesture validity. `raw` contains trigger strength/grip state; `out` contains submitted trigger
|
||
strength/click/grip; `armed` and `epochs` are trigger/grip pairs. `error` is ordered **grip, trigger
|
||
click, trigger scalar**, using OpenVR's `EVRInputError` codes (0 means `VRInputError_None`).
|
||
The line also includes `ageMs`, `connection`, and `origin`. `controller inputs cleared: error=...`
|
||
records neutralization on mode changes, disconnect, or deactivation; a nonzero value is the last
|
||
failed component update in that clearing pass.
|
||
|
||
The driver publishes these input updates before the controller pose, including invalid poses,
|
||
and clears components before deactivation. A zero API error establishes successful submission,
|
||
not delivery of a dashboard mouse-up event. The installed Vive compositor binding maps trigger
|
||
to dashboard `LeftClick` (0.65 activation, 0.60 release) and grip to `Back`. Its grab-handle
|
||
JavaScript attaches to the initiating hand on mouse-down and clears that attachment on mouse-up;
|
||
that component has no tracking-invalid cleanup. A stuck dashboard drag therefore needs the
|
||
driver submission and actual dashboard behavior checked separately. Do not synthesize a press
|
||
or a valid pose as a recovery shortcut.
|
||
|
||
Controller display smoothing is independent of these gesture logs: there is no stationary
|
||
dead zone. Poses converge with one 15 ms correction and bounded receipt-relative translation
|
||
prediction (80 ms / 2 cm). A missed observation holds the last pose for up to 350 ms from capture;
|
||
input freshness remains limited to 200 ms, and a detected miss releases inputs immediately.
|
||
Neither stabilization nor prediction refreshes camera timestamps or creates gesture input.
|
||
|
||
## Events
|
||
|
||
Each session writes `events.jsonl` beside its other artifacts: the lifecycle (`session.created`,
|
||
`compositor.started`, `guest.exited`, ...) plus `agent.ready` (the guest agent is up),
|
||
`focus.changed` (the focused app, focusable apps or base-layer order changed) and
|
||
`agent.launch_requested`.
|
||
|
||
## UI test tags
|
||
|
||
Compose test tags are exposed as resource ids (`ui/TestTags.kt`), so both `ui dump` and UI
|
||
Automator show them:
|
||
|
||
| Tag | Where |
|
||
|---|---|
|
||
| `rail-steam`, `rail-games`, `rail-desktop`, `rail-store`, `rail-components`, `rail-android-apps`, `rail-setup`, `rail-updates` | the front end's rail |
|
||
| `play-steam` | Play on the Steam and Games pages |
|
||
| `setting-<key>` | every toggle and choice row, by its key (`setting-logs`, `setting-agent-commands`, ...) |
|
||
| `tab-<n>` | a page's tab strip (Setup's Overview, Controller, Session, Launcher...), by position |
|
||
| `drawer-tab-controller`, `-second_screen`, `-display`, `-effects`, `-vr`, `-games`, `-session` | the session drawer's pages |
|
||
| `drawer-stop`, `drawer-stop-confirm`, `drawer-stop-cancel` | stopping a session from the drawer |
|
||
|
||
## Scenarios
|
||
|
||
`tools/droiddeck-scenario` runs a JSON scenario: `setup` once, `steps` `repeat` times, `teardown`
|
||
once. Each run starts with `before` (typically `start steam --wait --reuse`, so a session that died
|
||
in one run, to a crash or the low-memory killer, is started again for the next) and ends with
|
||
`after`, whether it passed or not, so a failed run cannot leave a game running into the next. A list step is a droiddeckctl command line; `{"assert": "dotted.path", "equals"|"in"|
|
||
"contains"|"not"|"exists": ...}` checks the last answer; `{"sleep"}`, `{"screenshot"}` and
|
||
`{"record"}` do what they say. A failing run keeps a screenshot, the focus snapshot and the
|
||
session's logs in its folder, and `report.json` sums up the runs.
|
||
|
||
```sh
|
||
tools/droiddeck-scenario tools/scenarios/steam-game-focus.json --var appId=489830 --repeat 10 --keep-going
|
||
```
|
||
|
||
`tools/scenarios/steam-game-focus.json` launches a game, checks it has focus the way a player
|
||
sees it, and quits it: the test that catches a game left behind Steam's loading screen, which only
|
||
shows up in some fraction of launches. A game with a launcher of its own needs the launcher pressed
|
||
through first, since the launcher window carries the game's app id too:
|
||
`tools/scenarios/skyrim-launcher-focus.json` taps Skyrim's Play before checking.
|