Approach B: peerspeak spawns pixelpass --host --output json, scrapes the ticket from its JSON stdout, and distributes it over the existing gossip plane as a ScreenShareOffer; peers get a one-click pixelpass viewer. Mutually optional, runtime-only coupling -- neither tool is a Cargo dependency of the other; the contract is pixelpass's CLI + JSON protocol. Video-only, separate viewer window, PATH binary discovery. Not yet built. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.5 KiB
4.5 KiB
Screen-share integration (peerspeak ↔ pixelpass)
Design decisions for surfacing pixelpass screen-sharing from inside a peerspeak room. Locked 2026-06-06; not yet implemented. peerspeak owns voice, pixelpass owns pixels — this doc is the seam between them.
Core principle: mutually optional, runtime-only coupling
Neither tool is a Cargo dependency of the other, in either direction.
- peerspeak works fully without pixelpass installed; the screen-share control just degrades (greyed / "needs pixelpass — install it").
- pixelpass works fully without peerspeak; nothing here makes it require voice.
- The integration is runtime subprocess + a stable contract, never a crate import. The contract peerspeak depends on is pixelpass's CLI flags + JSON event stream, treated as a public API.
- Keep the contract symmetric so the reverse direction (pixelpass launching peerspeak for a "call these viewers" button) stays possible later. No master/slave assumption.
Chosen approach: B — auto ticket-exchange over peerspeak's gossip plane
- User clicks Share screen in a peerspeak room.
- peerspeak spawns
pixelpass --host --output jsonas a child process (video only — see audio decision below). - peerspeak scrapes the
{"event":"ticket","value":"..."}line from the child's stdout (emitted once at host startup). - peerspeak broadcasts a new typed gossip message,
ScreenShareOffer { from, ticket }, over the same gossip plane that already carries chat + presence. - Each peer's peerspeak shows " is sharing — [View]".
- Clicking View spawns
pixelpass <ticket> --output jsonas a viewer; it opens in its own pixelpass window (not embedded in iced). - When the sharer stops, peerspeak kills the host child and broadcasts a
ScreenShareEnded { from }(or equivalent) so viewers' indicators clear.
Rejected: A (manual ticket via chat — barely integrated) and C (deep embed / shared iroh identity — large, fights pixelpass's window model and the two-tools philosophy).
Why this is cheap
pixelpass was built to be driven as a child process:
--output jsonexists explicitly for a front-end (its own--guire-execspixelpass --host --output jsonand parses the stream). Seepixelpass/src/common/output.rs.- Host emits:
ticket,host_info,viewer_joined,viewer_left,capture,viewer_refused. Viewer emits:connected { url }. Host reads akickcommand on stdin. - The ticket is the capability. The data-plane host (
serve.rs) gates viewers only bymax_viewers— there is no friends-list ACL on the stream (friends is a separate control-plane/presence concept the headless CLI ignores). So distributing the ticket over gossip = granting room access, with no ACL to fight. - pixelpass is 1 host → N viewers (asymmetric), which maps cleanly onto "I share, the room watches."
Locked sub-decisions
- Audio: video only on the room-launch path. Do not pass pixelpass's audio capture, so screen-share audio can't echo/double with peerspeak's voice mix.
- Separate window for the viewer (pixelpass's own), not embedded in iced.
- Binary discovery: look up
pixelpasson$PATH; allow a config override for a non-standard location. Absence is a normal, handled state, not an error.
Open questions for implementation
- Concurrency / who can share: one sharer at a time, or multiple simultaneous shares (N independent host processes + N offers + a list UI)?
- Subprocess supervision: how peerspeak tracks child lifetime, surfaces
viewer_refused/ capture failures, and cleans up on room-leave or crash (pixelpass has--repairfor orphaned PipeWire state). - Offer lifecycle on the gossip plane: late joiners — do we re-announce active shares to someone who joins mid-share?
- UI placement: where the Share button + incoming-offer indicators live across the three room layouts (3-Column / Bottom Dock / Drawer).
Build sketch (peerspeak side, for next session)
- New gossip message variant(s):
ScreenShareOffer/ScreenShareEndedin the gossip wire type; bump/relax parsing compatibly. - A
screensharesubprocess supervisor module (spawn host, parse JSON stdout, track child, kill on stop) — keep the JSON-parsing a pure testable seam. AppMessage+ state:StartScreenShare,StopScreenShare,ViewShare(EndpointId), active-share map, "pixelpass present?" probe.- UI: Share button + per-peer "View" affordance; graceful disabled state when the binary is absent.