Skip to content

Add DisplayFrame/DisplayPalette screen mirroring and DisplayInfo device metadata - #1054

Draft
jamesarich wants to merge 6 commits into
masterfrom
screen-mirror-poc
Draft

Add DisplayFrame/DisplayPalette screen mirroring and DisplayInfo device metadata#1054
jamesarich wants to merge 6 commits into
masterfrom
screen-mirror-poc

Conversation

@jamesarich

@jamesarich jamesarich commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds the wire contract for mirroring a device's screen to its locally connected client, with remote control riding the existing AdminMessage.send_input_event (#702). Prior art: meshtastic/web#224 proposed exactly this and was parked pending a design — cc @mkgin, @todd-herbert (this follows the get_display_frame_request shape sketched there, plus a continuous mode).

  • DisplayFrame (FromRadio.display_frame, tag 20): the 1bpp framebuffer as chunks of ≤384 bytes, self-describing (width/height/format/frame_id/offset/total_size), with rect_x/y/width/height reserved so future dirty-rect RGB565 streaming (LVGL UIs) stays additive, and palette_signature keying color.
  • DisplayPalette (FromRadio.display_palette, tag 21): the device's color-region table (color-TFT/HUB75 devices paint the 1bpp UI through per-region on/off colors), signature-keyed and re-sent only when the layout or theme changes — a color-accurate mirror at mono bandwidth.
  • DisplayInfo (DeviceMetadata.display, tag 15): panel geometry, frame format, PanelClass (OLED/LCD/TFT/EINK/HUB75) and has_touch, sent during the handshake so clients can gate mirroring UI, expect e-ink refresh behavior, and map taps to touch events. Absent on display-less builds.
  • AdminMessage.get_display_frame_request = 50 (one-shot) and set_display_mirror = 51 (continuous). Both documented local-connection-only: frames ride FromRadio, which never crosses the mesh.

Size

bin/regen-protos.sh (nanopb 0.4.9): meshtastic_DisplayFrame_size = 419, meshtastic_DisplayPalette_size = 440, and meshtastic_FromRadio_size stays 510 ≤ 512 (MAX_TO_FROM_RADIO_SIZE) — MeshPacket remains the largest oneof member. Chunk bounds are budgeted in mesh.options alongside the fields.

Notes for reviewers

  • Pure addition — buf breaking --against master is clean; buf lint passes; protoc --proto_path=. -o /dev/null meshtastic/*.proto passes.
  • Every new bytes/repeated field carries a nanopb bound (data max_size:384, regions max_count:16).
  • Doc comments carry the full client contract: chunk ordering/contiguity, frame_id reboot semantics, palette override precedence across chunks, stale-partial discard, mid-frame-disable draining.

Firmware follow-up

Not included here — proto change only. Once merged, meshtastic/firmware needs a submodule bump and regenerated src/mesh/generated/; the implementation is ready in meshtastic/firmware#11681, and the Android client in meshtastic/Meshtastic-Android#6987. Verified end-to-end on real hardware (RAK4631 WisMesh Pocket mono, LILYGO T-Deck in color).

Checklist before merging

  • New top-level messages are commented
  • Enum values carry unique descriptions

🤖 Generated with Claude Code

jamesarich and others added 5 commits August 31, 2026 10:58
FromRadio gains display_frame (tag 20) carrying chunked 1bpp framebuffer
snapshots, bounded to 384 data bytes per chunk to respect the 512-byte
MAX_TO_FROM_RADIO_SIZE cap. AdminMessage gains get_display_frame_request
(one-shot) and set_display_mirror (continuous) to arm the stream.
Complements the existing send_input_event for full remote display/control.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FORMAT_UNSPECIFIED = 0 per the file's sentinel convention (MONO_VLSB moves
to 1); document chunk ordering, frame_id reboot/wraparound semantics,
mid-frame disable draining, local-only admin verbs and their ack story;
bound width/height with int_size:16; correct the options sizing arithmetic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DeviceMetadata.display (tag 15) describes the panel during the handshake —
dimensions, frame format, PanelClass (OLED/LCD/TFT/EINK/HUB75) and touch —
so clients can gate mirroring UI, expect e-ink refresh behavior, and map
taps to touch events; absence means no display. DisplayFrame gains
rect_x/y/width/height, reserved so future dirty-rect streaming (LVGL UIs)
stays additive; firmware sends full frames only for now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…panels

Streams the device's region color table (up to 48 rects with RGB565 on/off
pairs plus defaults) as FromRadio.display_palette, chunked by region index
and identified by a signature that DisplayFrame.palette_signature
references — palettes re-send only when the layout or theme changes, so a
color-TFT device mirrors in its true colors at mono bandwidth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restate the options sizing arithmetic for the fields added since it was
written (bound still holds at 447 <= 512); make palette override order
explicitly global table order across chunk boundaries; allow palette
chunks to interleave a frame's chunks; add the stale-partial-palette
discard rule; drop the behavior-freezing parenthetical from PanelClass.TFT.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jamesarich

Copy link
Copy Markdown
Contributor Author

Cross-platform feature spec (client-agnostic wire contract, reassembly rules, input mapping, UX and device requirements): meshtastic/design#142

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow pull-request / build (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedAug 31, 2026, 8:45 PM

Little-endian RGB565, rows tightly packed, carried in the partial-update
rect fields — each rectangle completes independently under its own
frame_id. Activates the reservation the rect fields were added for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant