Add a host-global quality knob (Discord-style) so the sharer can trade
resolution + bitrate for upload bandwidth. Quality is host-global by
design: one encode pipeline fans out to every viewer, so per-viewer
quality is out of scope (it would kill the broadcast fanout).
- New `--quality source|high|medium|low|auto` (ValueEnum) bundling a
(max-height, bitrate, fps) tuple per preset; `auto` derives the preset
from the saved bandwidth pre-flight (safe_mbps / viewer cap), falling
back to `medium` when unmeasured. Default is auto; the interactive
Host branch shows a picker when --quality is omitted (mirrors pick_app).
- `--max-height N` raw override; `--bitrate`/`--framerate` changed to
Option so an explicit flag overrides just that field of the preset
(precedence rule), leaving the rest of the preset intact.
- host/quality.rs: Preset table + resolve(); pure resolve_auto() split
from the config read for testability. 5 unit tests lock preset
pass-through, the Auto ladder, the unmeasured fallback, and override
precedence.
- pipeline::build_args inserts `videoscale ! video/x-raw,height=N,
pixel-aspect-ratio=1/1,width=[2,8192,2]` only for non-Source presets.
PAR 1/1 forces a proportional downscale (without it videoscale keeps
full width and squashes PAR — no bandwidth win); the even-stepped width
range + even-rounded height satisfy H.264 4:2:0. EffectiveQuality is
threaded capture -> wayland/x11 -> pipeline; max_viewers is now sized
against the effective (post-preset) bitrate.
- Banner gains a quality line (preset label + ≤Np/kbps/fps + provenance).
- deps.rs checks `videoscale`; smoke-pipeline.sh adds a 1080->480
downscale check asserting an even width below source.
- README: --quality preset table, Auto behavior, host-global note,
--max-height/--bitrate/--framerate override precedence.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extract the display-agnostic encode/mux tail out of wayland.rs into a new
host/pipeline.rs: CaptureHandle + lifecycle, audio routing setup, the gst
arg builder, the spawn, and Serve::bind now live there. Backends supply
only their video-source element args plus a post-spawn hook (Wayland uses
it to close its leaked pipewire fd; X11 passes a no-op). capture.rs
collapses to a thin dispatcher; its CaptureHandle enum is gone.
Add host/x11.rs: ximagesrc (use-damage=false show-pointer=true), whole
root window by default or a single window via --window (xwininfo
click-picker → xid). x11rb reads geometry for an info log, justifying the
previously-vestigial dep. No portal, no fd dance — capture starts
silently when the first viewer connects (the ticket is the access
control). Viewer is display-agnostic and unchanged.
Wire --no-hwencode for real (was a no-op): the shared tail now selects
x264enc(tune=zerolatency,ultrafast)/I420 vs vah264enc/NV12 and switches
the videoconvert target format to match. Applies to both backends.
deps.rs: check_host_binaries now takes &HostOpts and checks shared
elements for both backends, encoder by --no-hwencode, source per backend
(pipewiresrc/ximagesrc), and xwininfo only when X11 + --window. Install
hints added for x264enc, ximagesrc, xwininfo.
Verified: warning-free build; smoke test still passes (tail unchanged);
ximagesrc + both encoder tails produce mpv-decodable H.264 against an
Xwayland root. Interactive cross-machine end-to-end pending.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements the Wayland host pipeline from plan §4.5:
ashpd ScreenCast portal
-> CreateSession + SelectSources + Start + OpenPipeWireRemote
-> (pipewire fd, node_id, width, height)
gst-launch-1.0 pipewiresrc fd=N path=NODE_ID ! videoconvert
! video/x-raw,format=NV12 ! fdsink fd=1
ffmpeg
-f rawvideo -pix_fmt nv12 -video_size WxH -i pipe:0
-f pulse -i default
-c:v libx264 -preset ultrafast -tune zerolatency
-c:a aac -f mpegts -listen 1 http://127.0.0.1:<rand>
Phase 1 ships software x264 per plan §7; VAAPI is Phase 2.
src/host/wayland.rs is the new module. capture.rs becomes a thin
dispatcher with a CaptureHandle enum (Wayland today, X11 next).
host/mod.rs swaps the 150ms sleep for a poll-until-listener-ready
helper, and calls handle.shutdown().await for an orderly SIGTERM /
1s grace / SIGKILL teardown. The Drop impl is the panic backstop.
The pipewire fd handoff clears CLOEXEC before gst-launch spawn and
closes the parent's copy of the raw fd after the child has it.
Also deletes the empty src/host/tunnel.rs and src/viewer/tunnel.rs
placeholder files — the generic bridge in common/tunnel.rs is doing
the work, and there's no host- or viewer-specific tunnel concern
worth a module yet.