Files
mirror/pyproject.toml
T
ducoterra 555fc1c3b4 feat: browser-only screen mirroring prototype
A FastAPI server with two pages and one WebSocket. /mirror captures a
window, screen or tab through the OS-native getDisplayMedia picker; /watch
shows every live mirror as a wall and takes one fullscreen on click. Any
number of sharers and watchers, at the same time.

Media never touches the server: each watcher gets its own RTCPeerConnection
to the sharer, and the Python process only relays JSON. The roster lives in
registry.py, which is transport-free by design -- every operation returns
the envelopes to deliver, so admission, fan-out and teardown policy are all
unit-testable without a socket.

Registry rules worth stating: a signal frame is relayed only along an
existing subscription in either direction; viewer-to-peer traffic is
refused; and teardown is broadcast to every viewer rather than only the
subscribers, because a tile is drawn on stream_added and a viewer that has
not yet subscribed still has a stale tile to clear.

Frontend is hand-written ES modules with no build step and no external
asset. Chromium scales a monitor capture to the capturing window and
requires a secure context, so /mirror detects an insecure origin and prints
the three ways out rather than failing on a button click.

LLM-Generated: true
2026-11-01 16:54:03 -05:00

69 lines
1.4 KiB
TOML

[project]
name = "mirror"
version = "0.1.0"
description = "Device-agnostic, browser-only screen mirroring for the local network."
readme = "README.md"
requires-python = ">=3.13"
license = { text = "MIT" }
authors = [{ name = "Reese Apps" }]
dependencies = [
"fastapi>=0.115",
"uvicorn[standard]>=0.30",
"python-dotenv>=1.0",
]
[project.optional-dependencies]
dev = [
"debugpy>=1.8",
"pytest>=8.3",
"pytest-asyncio>=0.24",
"pytest-cov>=5.0",
"httpx>=0.27",
"ruff>=0.6",
"pyright>=1.1.380",
"playwright>=1.47",
"pytest-timeout>=2.3",
]
[project.scripts]
mirror = "mirror.__main__:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/mirror"]
[tool.ruff]
line-length = 100
target-version = "py313"
src = ["src", "tests"]
[tool.ruff.lint]
select = ["E", "F", "W", "I", "UP", "B", "C4", "SIM", "ARG", "RUF"]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra --strict-markers --cov=mirror --cov-report=term-missing"
timeout = 60
asyncio_default_fixture_loop_scope = "function"
asyncio_mode = "auto"
markers = [
"e2e: browser end-to-end tests (require Playwright browsers)",
"realdisplay: end-to-end tests that capture a real X11 display",
]
[tool.coverage.run]
source = ["src/mirror"]
[tool.pyright]
include = ["src"]
pythonVersion = "3.13"
typeCheckingMode = "standard"
[dependency-groups]
dev = [
"pytest-timeout>=2.4.0",
]