fegger 69a1cdac1d feat(app): wire the app icon into the GUI, waybar, and SDL window
screencast_icon/ holds the single source icon set; meson install ships
the 256 px PNG into the hicolor theme for the GTK panel (themed icon
name) and the waybar CSS, and a custom target embeds it as
icon_png_data.h so the SDL receiver window sets it via SDL_SetWindowIcon
with an optional SDL3_image dependency (built-in find_library, since no
pkg-config file ships with it).
2026-09-10 11:30:39 +02:00

screen_cast

A native Linux peer-to-peer screencast application.

  • Send your desktop or a window to another Linux machine.
  • Receive a stream and render it in a window — or fullscreen headless.
  • Discover receivers on the LAN via mDNS/Avahi; negotiate sessions with JSON signaling; stream H.264 over RTP/UDP.
  • Control it from the CLI, a GTK4 panel, or a waybar widget.

Built with C++20, Meson, PipeWire, FFmpeg, and SDL3.

Quick start

Requirements:

  • C++20 compiler with <format> (GCC 13+, Clang 18+)
  • Meson >= 0.63, Ninja
  • FFmpeg development packages (libavcodec, libavutil, libswscale)
  • SDL3 development package (sdl3)
  • nlohmann JSON (nlohmann_json) and Avahi client (avahi-client)
  • For the sender only: PipeWire dev (libpipewire-0.3) and libportal
  • Optional, for the GUI: gtkmm-4.0

Build and run tests:

meson setup build
meson compile -C build
meson test -C build --print-errorlogs

Stream between two machines:

screencast --receive        # machine A: announces itself, opens a window
screencast --send           # machine B: discovers A, negotiates, streams

See docs/RUNBOOK.md for all modes, flags, and validation procedures.

Modes and flags

screencast --send [--target monitor|window] [--peer HOST[:PORT]] [--bitrate MAX_KBPS] [--crf 0-51] [--fps 1-60]
screencast --receive [--port PORT] [--signaling-port PORT] [--fullscreen] [--swdecode]
screencast --discover [--timeout SECONDS]
screencast waybar [--toggle]        # for waybar widgets
Mode Flag Default Meaning
--send --target monitor monitor or window (portal source picker)
--peer auto-discover target a receiver directly, HOST[:PORT]
--bitrate 4000 VBV max bitrate (kbps)
--crf 22 encoder quality (lower = better)
--fps capture rate frame-rate cap, 1-60
--receive --port 5004 local RTP UDP port
--signaling-port 5005 TCP signaling port announced via mDNS
--fullscreen off force fullscreen (automatic under KMSDRM)
--swdecode off skip the hardware decode probe
--discover --timeout 3 seconds to wait for mDNS responses
waybar --toggle print status toggle streaming (stop / restart last session)

--send without --peer discovers receivers on the LAN and requires that exactly one is found; use --discover to list them.

GUI and waybar widget

Build the GUI alongside the CLI (meson configure build -Dgui=true, then recompile; installs as screencast-gui):

screencast-gui          # receiver list → pick one → bitrate → Start
screencast waybar       # one JSON line for a waybar custom module
screencast waybar --toggle

The waybar module shows the app icon (dimmed while idle, highlighted while streaming; tooltip: receiver, bitrate, elapsed); left-click toggles streaming to the last receiver, right-click opens the panel. All front-ends agree on state because the sender publishes it to $XDG_RUNTIME_DIR/screencast/sender.json. The icon (from screencast_icon/) is installed into the hicolor theme by meson install, used by the GTK panel and the waybar CSS, and embedded in the SDL receiver window.

Under the hood: resilience

The receiver absorbs loss in two stages and recovers actively:

  1. Jitter window: RTP packets are re-ordered by sequence number in a small buffer (16 packets / 60 ms), so Wi-Fi reordering is not misread as loss. In-order streams release immediately (zero added latency).
  2. PLI feedback: when a frame arrives genuinely damaged, the receiver drops it and asks the sender for a keyframe over the signaling channel (rate-limited to one request per 500 ms); the sender re-encodes a keyframe immediately, so recovery takes one frame time.

Hardware decode: the receiver probes h264_v4l2m2m (the VideoCore path on Raspberry Pi) and falls back to software automatically; --swdecode forces software.

Receiver on a small ARM board (e.g. Raspberry Pi Zero 2 W)

The receiver does not need the sender's PipeWire/portal capture stack. On the board, run:

sudo ./scripts/install-receiver.sh
screencast --receive

The script installs the dependencies, builds a receiver-only binary (-Dsender=false), runs the test suite, and installs to /usr/local/bin (override with SC_RECEIVER_INSTALL_DIR). It needs Raspberry Pi OS Trixie or newer (GCC 13+ for C++20 <format>), builds SDL3 from source when the distribution does not package it, and enables avahi-daemon. It also installs and enables a systemd service so the receiver starts at boot — systemctl status screencast-receiver to check on it (stop with sudo systemctl stop screencast-receiver; always SIGTERM, never kill -9, or stale mDNS records linger).

On a headless console the receiver runs fullscreen automatically with aspect-preserving letterboxing — no window manager needed (SDL3 KMSDRM; it renders on its own VT, tty7, so Ctrl+Alt+F1 returns to the console). See docs/RUNBOOK.md for the headless setup details.

The receiver can act as a Wi-Fi access point, so a sender connects to the board directly with no router in between:

sudo scripts/pi-hotspot.sh on        # prints SSID + generated password
sudo scripts/pi-hotspot.sh status   # shows the saved credentials
sudo scripts/pi-hotspot.sh off      # back to normal router Wi-Fi

While active the Pi is reachable at 10.42.0.1; on the sender, join the hotspot's Wi-Fi and run screencast --send.

Architecture

See docs/ARCHITECTURE.md for module boundaries and design rules.

Roadmap

Development is split into phases in docs/PHASES.md.

Current phase: Phase 7 — Resilience and polish. PLI recovery, the jitter buffer, hardware decode, and the GUI/waybar front-ends are complete and validated on a real two-machine setup (desktop → Raspberry Pi Zero 2 W over Wi-Fi); deferred items are the VAAPI hardware encode probe and packaging.

License

MIT — see LICENSE (to be added).

S
Description
No description provided
Readme 1.2 MiB
Languages
C++ 60.6%
Swift 18%
Kotlin 15.2%
Shell 3.9%
Meson 2%
Other 0.2%