# mirror **Device-agnostic screen mirroring that lives entirely in the browser.** `mirror` runs a small HTTP server on your machine. Open it from any device on your network: | Page | URL | What it does | | ------------ | ------------------ | ----------------------------------------------------------------------- | | **Share** | `/mirror` | Picks a window, screen, or tab via the OS-native picker and mirrors it. | | **Watch** | `/watch` | Shows every screen currently being mirrored; click a tile to take the stage. | | **Index** | `/` | Signpost + live count of mirrors. | Any number of people can mirror at the same time and any number of people can watch. There is **no authentication** — this is built for a trusted local network, and that assumption is deliberate and documented in [SECURITY.md](SECURITY.md). ## Quickstart ```bash uv sync --extra dev uv run mirror serve # http://0.0.0.0:8055 ``` Then, from another machine on the LAN: ``` http://:8055/mirror # to share http://:8055/watch # to watch ``` Linux is the primary target. On Linux the native picker is X11/Wayland's own (Chromium uses `xdg-desktop-portal` on Wayland/GNOME/KDE, and its built-in X11 picker on Xorg), so nothing has to be installed on the client side. ## How it works Media never touches the server. Each mirror is a WebRTC peer connection established directly between the sharer's browser and each watcher's browser (a "mesh" fan-out). The server is a **signaling relay and roster only**: it tracks who is publishing what, forwards SDP/ICE between peers, and pushes roster deltas to viewers. ``` sharer browser ──ws──► ┌──────────────────────┐ ◄──ws── watcher browser │ │ FastAPI │ ▲ │ offer/ICE ───►│ /ws (signalling) │◄─── offer/ICE │ │ │ registry (in-mem) │ │ └────── WebRTC (SRTP, LAN, p2p) ─────────┴─────────────────┘ ``` ## Configuration Environment variables (all optional; a `.env` file is loaded if present): | Variable | Default | Meaning | | ---------------------- | ------- | ---------------------------------------------------- | | `MIRROR_HOST` | `0.0.0.0` | Bind address for `mirror serve`. | | `MIRROR_PORT` | `8055` | Bind port. | | `MIRROR_ICE_SERVERS` | `[]` | JSON array of RTCIceServer objects, if peers cannot reach each other directly. | | `MIRROR_MAX_MIRRORS` | `0` | Cap on concurrent mirrors (`0` = unlimited). | | `DEBUGPY` | `0` | `1` opens a debugpy listener on `DEBUGPY_PORT`. | | `DEBUGPY_PORT` | `5678` | debugpy port. | ## Platform notes **Screen capture needs a secure context.** `http://localhost` is one; a plain `http://192.168.x.x` opened from another device is not, and the browser hides `getDisplayMedia()` completely. The `/mirror` page detects this and prints the three ways forward (share from localhost, serve HTTPS, or mark that one origin as secure on the sharing device). Watching never needs permission. **Linux.** On X11/Xorg Chromium uses its native capturer — the picker appears and that is the end of it. On Wayland (GNOME/KDE) Chromium goes through `xdg-desktop-portal`, so the compositor shows its **own permission dialog for each share**, which mirror neither controls nor tries to bypass. Chrome's picker offers whole screens and individual windows; on Wayland what the portal will agree to depends on the compositor. ## Development ```bash uv run pytest # unit + API + browser E2E (~20s) uv run pytest -m "not e2e" # everything except the browser (~0.5s) uv run ruff check . && uv run ruff format --check . && uv run pyright ``` The browser suite drives **real capture**: Chromium's fake media device yields a moving 1280×720 monitor stream, so the assertions are on decoded, changing pixels at the watcher — not on log lines. There is a second, opt-in rung that exercises Chromium's **real X11 capture backend** on a private Xvfb display, isolated from your own session so it can never prompt you or mirror your desktop: ```bash MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay ``` See [AGENTS.md](AGENTS.md) for the operating rules, [.agents/PLAN.md](.agents/PLAN.md) for the locked architectural decisions, and [SECURITY.md](SECURITY.md) for why there is no login.