Skip to content

feat(remote-shell): PTY-over-mesh terminal with retro-CRT UI#5131

Draft
jamesarich wants to merge 4 commits intomainfrom
feat/remote-shell
Draft

feat(remote-shell): PTY-over-mesh terminal with retro-CRT UI#5131
jamesarich wants to merge 4 commits intomainfrom
feat/remote-shell

Conversation

@jamesarich
Copy link
Copy Markdown
Collaborator

@jamesarich jamesarich commented Apr 14, 2026

Summary

  • Adds a RemoteShell terminal screen (portnum REMOTE_SHELL_APP = 13) implementing PTY-over-mesh with a retro-CRT UI
  • Protocol implementation matches dmshell_client.py from firmware PR RemoteShell SSH-like experience over PKI firmware#10123 — reliability layer with seq/ack, single-frame replay, PING/PONG heartbeat, PKI-only encryption
  • Uses structured proto fields (last_tx_seq/last_rx_seq) for replay and heartbeat metadata per Jonathan's firmware refactor and Add two more RemoteShell fields protobufs#894
  • Feature gated to UNRELEASED (9.9.9) in Capabilities.kt — no user impact until firmware ships

What's included

Terminal UI (feature/node/.../terminal/)

  • RemoteShellScreen — hidden BasicTextField for raw keyboard capture (always focused), phosphor colour picker
  • TerminalCanvas — monospace grid with glow bloom, pending input rendered dim
  • CRT effects — scanlines, phosphor glow, screen curvature (AGSL on Android, no-op on JVM), flicker animation, cursor blink
  • PhosphorPreset — GREEN / AMBER / WHITE colour schemes

Protocol (RemoteShellViewModel)

  • Full reliability layer: incrementing seq, piggybacked ack_seq, single-frame replay on request, tx history ring buffer (50 frames)
  • PING/PONG heartbeat (5s idle, 15s repeat) with last_tx_seq/last_rx_seq proto fields for gap detection and replay triggers
  • ACK replay requests via last_rx_seq proto field (no payload byte-stuffing)
  • Input batching: 500ms flush window, early flush on \r/\t/buffer-full (64 bytes)
  • PKI: DataPacket.channel = PKC_CHANNEL_INDEX (8) triggers Curve25519 encryption
  • Sender origin verification via ReceivedShellFrame wrapper (filters by node number, not just session ID)

Infrastructure

  • ReceivedShellFrame data class propagates sender node number for origin verification
  • RemoteShellPacketHandlerImpl with SharedFlow (not StateFlow) for frame delivery
  • supportsRemoteShell capability gate (UNRELEASED)
  • Navigation wiring in NodesNavigation.kt, gating in AdministrationSection.kt

Dependencies

Testing

Not yet buildable — needs proto dependency (meshtastic/protobufs#894) to land first. Protocol audited against Python reference client and firmware source.

@github-actions github-actions bot added enhancement New feature or request needs-review labels Apr 14, 2026
@jp-bennett
Copy link
Copy Markdown
Contributor

I did a bit of a refactor, to get rid of the ugly stuffing values into payload. Much happier with the Firmware source now. Should be pretty straightforward to update. Looking forward to playing with the Android support.

@jamesarich
Copy link
Copy Markdown
Collaborator Author

I did a bit of a refactor, to get rid of the ugly stuffing values into payload.

Pushed up this change as well, just needs the fresh proto fields (not yet in the protobufs repo).

jamesarich and others added 4 commits April 15, 2026 13:13
Adds a full RemoteShell (portnum=13) implementation matching Jonathan's
dmshell_client.py protocol:

Protocol layer:
- Seq/ack reliability: incrementing seq on every non-ACK frame, piggybacked
  ack_seq, out-of-order frame buffering, gap detection and replay requests
- TX history ring buffer (last 50 frames) for retransmission on request
- ACK frames carry optional 4-byte big-endian REPLAY_REQUEST payload
- PING/PONG heartbeat with 8-byte status payload (lastTxSeq, lastRxSeq);
  PONG handler triggers replay if peer is behind
- PKI: DataPacket.PKC_CHANNEL_INDEX so CommandSenderImpl applies
  Curve25519 encryption (firmware rejects non-PKI DMShell packets)
- Input batching: 500ms debounce (matches Python client), immediate flush
  on \r, \t, buffer-full (64 bytes), or Enter

Terminal UI:
- Retro-CRT composables: TerminalCanvas (phosphor glow, two-pass bloom),
  ScanlinesOverlay, FlickerEffect (animated brightness variation),
  CrtCurvatureModifier (AGSL barrel distortion on Android 12+, no-op on JVM)
- PhosphorPreset enum: GREEN (P1), AMBER (P3), WHITE (P4)
- Pending-input rendered inline in preset.dim colour; snaps to confirmed on flush
- Hidden zero-size BasicTextField captures soft and hardware keyboard input
- Phosphor colour picker dropdown in top bar

Capabilities gate:
- supportsRemoteShell gated to UNRELEASED (9.9.9)
- Entry only visible in AdministrationSection when node.capabilities.supportsRemoteShell
…ures

- Import kotlin.concurrent.Volatile instead of JVM-auto-resolved variant
- Replace synchronized() with Mutex.withLock for KMP compatibility
- Replace String.format() with DataPacket.nodeNumToDefaultId()
- Add iosMain actual for CrtCurvatureModifier (no-op stub)
- Extract magic numbers to named constants (UINT32_BYTES, MAX_FLUSH_WINDOW_MS, CRT_STRENGTH_SCALE)
- Add detekt suppressions for protocol-inherent complexity
- Remove unnecessary safe calls on non-nullable Wire proto fields
- Fix spotless formatting in RemoteShellHandler.kt

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align with firmware refactor (meshtastic/firmware#10123) and protobufs
PR meshtastic/protobufs#894 which adds last_tx_seq and last_rx_seq
fields to the RemoteShell message.

- ACK: use last_rx_seq proto field instead of encoding replay-from
  seq into payload bytes
- PING: use last_tx_seq/last_rx_seq proto fields instead of encoding
  heartbeat status as 8-byte payload
- PONG: read last_tx_seq/last_rx_seq from proto fields instead of
  decoding payload
- SentFrame: store flags/lastTxSeq/lastRxSeq for faithful replay
- Remove unused encodeUint32BE, encodeHeartbeatStatus,
  decodeHeartbeatStatus helpers and associated constants

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request needs-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants