feat(roku): add Roku platform support - #3
Open
namidan wants to merge 4 commits into
Open
Conversation
Port the Roku driver from namiml/Maestro (commit ff38700) and integrate it following the fork's Vega/tvOS platform patterns: - ECP client, SSDP/env device discovery, app-ui parser, and key mapping live in maestro-client's maestro/roku/ package (no separate Gradle module) - Devices surface through DeviceService.listRokuDevices() (MAESTRO_ROKU_HOST pin + opt-in MAESTRO_ROKU_DISCOVERY SSDP scan), so the standard --platform roku / --device <ip> selection works with no global CLI flags or TestCommand special-casing - RokuDriver: D-pad input, LIT_ text entry, SceneGraph view hierarchy with scene-absolute bounds, digest-auth screenshots, app-ui-based settle waits - New KeyCodes (Remote Info / Instant Replay / Search) mapped on Roku and Android; Remote Menu maps to Roku's * (options) button - MCP session wiring and Studio TV mode auto-on for Roku - Unit tests for the parser, key mapping, and SSDP parsing - BrightScript demo channel (e2e/roku_demo_app) with navigation/focus flows - FORK.md, README, and AGENTS.md entries Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validated against a physical Roku Streaming Stick 4K (Roku OS 14). Three fixes, each reproduced and verified on the device: - launchApp is now a cold launch: ECP's /launch resumes an already-running channel with its state intact, so exit to the home screen first (Maestro's launchApp contract; matches VegaDriver's terminate-then-launch) - Screenshot generation: the dev server's multipart parser silently ignores form parts carrying a per-part Content-Length header (which OkHttp's MultipartBody always adds) and also requires an empty 'archive' field — build the form body by hand and do the digest handshake explicitly with an empty-body probe, verifying the 'Screenshot ok' confirmation - Screenshot download: poll /pkgs/dev.jpg's ETag against the pre-generation value (the capture file is written asynchronously) and cache-bust the URL, so a stale prior capture is never returned as the result All three e2e demo-channel flows plus a screenshot flow pass against the real device; roku unit tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rebuild e2e/roku_demo_app to mirror the tvOS/Vega demo apps — same screens,
labels, and testIDs — and mirror all three flows:
- Home menu (menu-navigation / menu-text-input / menu-focus) opening one test
screen at a time, with Back returning Home
- Navigation Test: 2x2 grid (grid-top-left/right/bottom-left/right) covering
all four D-pad directions via an explicit focus-transition map
- Text Input Test: a native Keyboard node (text-field) focused on entry so
inputText (ECP LIT_) and eraseText (Backspace) land in it directly, with a
typed-label echoing the text
- Focus Test: programmatic focus (setFocus on entry) lands on focus-button-2
Driver fix shaken out by the text-input flow: ECP keypress path segments were
form-encoded, so a space became '+' and LIT_+ typed a literal plus — percent-
encode path segments instead ('Hello Roku' now types correctly).
Channel fix found on hardware: creating the hidden TextInputScreen's Keyboard
steals input focus after HomeScreen's init has claimed it, leaving D-pad keys
routed to the invisible keyboard — MainScene re-asserts the Home menu's focus
once all screens exist (focusDefault interface function).
All three mirrored flows pass against a physical Streaming Stick 4K; roku
unit tests (14) pass, including new coverage for the path-segment encoding
and the dev server's digest challenge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Credit the Roku platform contribution to the Nami team (nami.ml / rku.dev) in the README feature list and the FORK.md platform entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed changes
copilot:summary
Adds Roku as a supported platform (
--platform roku), following the integration patterns established by the tvOS and Vega ports. Contributed by teams Nami and rku.dev.drivers/RokuDriver.kt+maestro/roku/): Roku devices are driven over the External Control Protocol (an HTTP API on device port 8060) — no on-device agent. View hierarchy comes from/query/app-ui(SceneGraph XML parsed into scene-absolute bounds), input is D-pad keypresses (tapOnsends Select; swipes/scrolls become directional presses), text is typed via character-by-characterLIT_keypresses, and screenshots go through the dev web server (digest auth).launchAppis a cold launch: an already-running channel is exited to the home screen first.DeviceService.listRokuDevices()surfaces devices through the normal listing/selection path — aMAESTRO_ROKU_HOSTpin (fast reachability probe) plus an opt-in SSDP LAN scan (MAESTRO_ROKU_DISCOVERY=true). No new global CLI flags and noTestCommandspecial-casing.Platform.ROKU,DeviceSpec.Roku,RokuLocale, session managers (CLI + MCP), pickers,start-device,AppValidator.Remote Info(the*options button),Remote Instant Replay,Remote Search— mapped on Roku and Android;Remote Menumaps to Roku's options button.e2e/roku_demo_app/) mirroring the tvOS/Vega demo apps — same screens, labels, and testIDs (Home menu, 2×2 navigation grid, native-Keyboard text input, programmatic-focus test) — with three mirrored flows undere2e/workspaces/roku_demo_app/.Notable device-behavior quirks handled (all reproduced on hardware): the dev server's multipart parser ignores form parts carrying a per-part
Content-Lengthheader (so the screenshot form is assembled by hand with an explicit digest handshake), the screenshot file is written asynchronously (the download polls its ETag against the pre-generation value), and ECP keypress path segments must be percent-encoded (a form-encoded space would type a literal+).Testing
maestro-client/src/test/java/maestro/roku/): app-ui parser (bounds math, translation offsets, RowListItem duplicate-drop, focus state), key mapping, SSDP response parsing, ECP path-segment encoding, and the dev server's digest challenge../gradlew :maestro-client:test --tests "maestro.roku.*"maestro test --platform roku e2e/workspaces/roku_demo_app/), covering launch, all four D-pad directions, focus asserts (focused: true/falseby id), programmatic focus,inputTextwith spaces/capitals,eraseText, Back-driven screen returns, andtakeScreenshot(captures visually verified against the asserted screen state).list-devicesshows a pinned Roku (and degrades gracefully in ~5s when the host is unreachable);start-device --platform rokuwithoutMAESTRO_ROKU_HOSTproduces an actionable error.FlutterWebSemanticsIdentifierTestfailure exists onmainindependently of this change).Issues fixed
None — new platform capability.