The display process now serves a Unix socket, /run/ledmatrix/control.sock,
carrying versioned newline-delimited JSON commands that are acknowledged.
Stage 1 moves on-demand start/stop (plus status, hello and ping) onto it;
the cache-file mailbox stays as the fallback for one release.
- src/ipc/contract.py: typed request/response envelopes, command args,
error codes, NDJSON framing with a 64 KiB limit, socket path rules.
- src/ipc/server.py: threaded server owned by the display. Handlers only
queue onto a bounded queue and ack with the request id; the render
thread drains it where it reads the mailbox. Bounded clients, timeouts,
garbage/oversize/disconnect handling; 0660 socket in the cache dir's
group plus SO_PEERCRED checks; skips cleanly on Windows or when off.
- src/ipc/client.py: one short-timeout request; any failure raises
ControlError(reason).
- api_v3/display.py: on-demand start/stop try the socket, fall back to
the mailbox exactly as before, and report transport/socket_error.
- display_controller.py: start/close the server; the mailbox handler body
is extracted into _handle_on_demand_request and shared by both paths.
- docs/IPC_CONTROL_SOCKET.md: protocol, security model, stage plan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>