DisplayMirrorManager reassembles FromRadio.display_palette chunks
(signature-keyed, defaults captured on the offset-0 chunk) into a
MirrorPalette; frames carry palette_signature and the renderer resolves
each pixel through the region table (last overlapping region wins,
matching firmware precedence) with RGB565 conversion, falling back to
monochrome when the referenced palette hasn't arrived. Renderer split
into MirrorFrameRenderer.kt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the flat button stack with the researched control surface:
cross-shaped 5-way cluster (56dp circular keys, 8dp gaps, Material
Symbols chevrons with contentDescriptions), directions auto-repeat on
hold (500ms then 10Hz — slow enough for the radio to render), OK
long-press sends SELECT_LONG, Back sits below per TV-remote convention.
The mirror itself becomes an input surface: click to capture the
keyboard (arrows / Enter / Esc, with a visible focus ring and hint) and
swipe in a cardinal direction to navigate. Consumes the new
DeviceMetadata.display: shows an e-ink hint steering toward one-shot
Refresh. Adds keyboard_arrow_left/right icons (Material Symbols rounded,
transposed from the existing up glyph).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Populates the new DisplayInfo during the handshake — dimensions from the
live display device, MONO_VLSB frame format, PanelClass from the panel
defines (EINK/HUB75/TFT/LCD/OLED), has_touch from HAS_TOUCHSCREEN — so
clients can gate mirroring UI and adapt to the panel. Absent on
display-less builds. Regenerates protos (DisplayInfo, DisplayFrame rect
fields reserved for dirty-rect streaming).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Manager: validate format (MONO_VLSB only) and geometry (width/height must
describe exactly total_size bytes) so malformed device input can't reach
aspectRatio and crash; unit tests for reassembly, drops, restarts.
AdminController: setDisplayMirror/sendInputEvent become non-suspend
immediate sends (teardown-safe, input never queues behind bulk admin) and
gain requestDisplayFrame. Mirror tab: render each frame once into a 1:1
ImageBitmap scaled with FilterQuality.None (kills the fractional-scale
seams), gate controls on connection state, reset the toggle on disconnect,
stop the stream on tab dispose and ViewModel clear, add a one-shot Refresh,
inject AdminController/ConnectionStateProvider instead of RadioController.
Dispatch: display_frame test coverage incl. revoked-session.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cap the mirror canvas at 512.dp (4x for a 128px OLED) instead of filling
the window width, fold the frame info into the toggle row, and pair
Down/Back so the controls fit without scrolling on desktop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Consumes the DisplayFrame PoC protobufs (2.99.0-screen-mirror-poc-SNAPSHOT
from mavenLocal, useMavenLocal committed on this branch): DisplayMirrorManager
reassembles FromRadio.display_frame chunks into MirrorFrame snapshots,
AdminController gains setDisplayMirror + sendInputEvent, and the Debug panel
gains a Mirror tab rendering the device's 1bpp framebuffer live with a D-pad
driving the firmware InputBroker. PoC: tab strings deliberately unlocalized.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>