"""The Linux-native capture path: a real X11 screen, no fake media devices. The rest of the suite drives Chromium's fake capture device, which is deterministic but synthetic. This module is the counterweight: it starts its own Xvfb server, runs a *headed* Chromium on it, and asks that browser to mirror the whole monitor through Chromium's real X11 capture backend. A second Chromium process — a different machine as far as WebRTC cares — watches it. Skipped unless ``MIRROR_REALDISPLAY=1``, because it needs an X server binary and a headed browser:: MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay Measured on the reference Linux host (Xvfb 1280x800x24, headed Chromium, phase 02, 2026-11-01) — requested vs delivered, per sharer window size: =================== ============ ============ ===================== sharer window requested delivered viewer decodes =================== ============ ============ ===================== 1200x760 1200x760 1200x750 1200x750 (93.75% of desktop) 1024x700 1024x700 1024x640 1024x640 (80% -> fails the floor) 800x600 800x600 800x500 800x500 (62.5% -> fails the floor) =================== ============ ============ ===================== * ``requested`` is ``screen x devicePixelRatio``; under Playwright ``screen`` tracks the browser window, so the request can never outgrow the window here. * Chromium *still* clamps a monitor capture to the capturing window even with an explicit ``ideal`` size named — which agrees with PLAN §5 ("Chromium scales a monitor capture to the capturing window"). Removing the phase 02 size constraints (frameRate only) delivered the identical 1200x750 for a 1200x760 window, so on this host the floor discriminates by *window* size, not by the constraints. On real hardware ``screen`` is the true display resolution, so a small window on a large display is the case the 90% floor and the share page's 75% toast are for. Audio on the real capture path is environment-gated, and the gap is written down here rather than imagined away. ``--auto-select-desktop-capture-source= Entire screen`` selects the screen without ticking the picker's "share audio" box, and a host without an audio device has no track to offer at all — on such hosts ``test_a_real_desktop_shares_audio_when_the_host_offers_any`` finds no audio track in the captured stream and *skips with that reason* (never a stubbed peer, never an xfailed-into-green failure). Where a host does expose an audio source, the test asserts the viewer's inbound-rtp audio bytes increase across two samples and the picture still decodes distinct frames, the same way the fake-device suite does. """ from __future__ import annotations import os import re import shutil import socket import subprocess import sys import time import httpx import pytest from playwright.sync_api import expect, sync_playwright pytestmark = [ pytest.mark.e2e, pytest.mark.realdisplay, pytest.mark.timeout(240), pytest.mark.skipif(shutil.which("Xvfb") is None, reason="Xvfb is not installed"), pytest.mark.skipif( os.environ.get("MIRROR_REALDISPLAY", "") not in {"1", "true", "yes"}, reason="set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend", ), ] # Deliberately not 16:9: an aspect ratio of exactly 1280/800 is a fingerprint # of the real screen. (The fake device, where it is used, defaults to 1280x720 # and, once a size is requested, clamps to the capturing window — PLAN §5.) XVFB_GEOMETRY = "1280x800x24" XVFB_ASPECT = 1280 / 800 FAKE_DEVICE_SIZE = (1280, 720) # The sharpness floor: 90% of the desktop width (1152 of 1280 px). An exact # 100% match is environment-specific (compositor, fractional DPI, window # chrome), but a window-clamped capture of a small share page lands at 62-93% # of the desktop — that is where the fuzziness a person sees lives. XVFB_WIDTH = 1280 SHARP_FLOOR_PX = int(XVFB_WIDTH * 0.9) # Flags learned the hard way, and worth keeping written down: # # * No `--use-fake-device-for-media-stream`: that is exactly what we are trying # not to use here. # * No `--use-fake-ui-for-media-stream` either. It short-circuits the picker # *before* the X11 backend selects a source, and every capture then dies with # `NotReadableError: Could not start video source` in ~55ms. Choosing the # source with `--auto-select-desktop-capture-source` grants and selects in one # step, which is what makes unattended desktop capture work. # * `--ozone-platform=x11` keeps Chromium on its native X11 capturer instead of # xdg-desktop-portal/PipeWire. Combined with `browser_env()` this makes a # permission dialog on the developer's own session impossible. REAL_CAPTURE_ARGS = [ "--auto-select-desktop-capture-source=Entire screen", "--ozone-platform=x11", "--autoplay-policy=no-user-gesture-required", "--no-sandbox", "--disable-dev-shm-usage", "--no-proxy-server", ] # Never reachable: without a session bus and without a Wayland socket there is no # portal to ask, so these tests cannot interrupt a human. UNREACHABLE_SESSION_VARS = ( "WAYLAND_DISPLAY", "DBUS_SESSION_BUS_ADDRESS", "XDG_CURRENT_DESKTOP", "XDG_BACKEND", ) def browser_env(display: str | None = None) -> dict[str, str]: """An environment that can only reach the display this test owns.""" env = {key: value for key, value in os.environ.items() if key not in UNREACHABLE_SESSION_VARS} env["XDG_SESSION_TYPE"] = "x11" env["MIRROR_ICE_SERVERS"] = "" if display is None: env.pop("DISPLAY", None) else: env["DISPLAY"] = display return env # Starts a pixel-sampling loop without blocking, so the test can drive motion on # the captured screen while the watcher is measuring it. PROBE = """() => { const video = document.querySelector('.tile video'); const canvas = document.createElement('canvas'); canvas.width = 96; canvas.height = 60; const ctx = canvas.getContext('2d', {willReadFrequently: true}); window.__probe = (async () => { const seen = new Set(); for (let i = 0; i < 14; i++) { await new Promise((r) => setTimeout(r, 120)); try { ctx.drawImage(video, 0, 0, 96, 60); } catch {} const d = ctx.getImageData(0, 0, 96, 60).data; let h = 0; for (let j = 0; j < d.length; j += 4) { h = ((h * 31) + d[j] + d[j + 1] * 3 + d[j + 2] * 7) >>> 0; } seen.add(h); } return {distinct: seen.size, width: video.videoWidth, height: video.videoHeight}; })(); return 'started'; }""" # The one global a test is allowed to use (AGENTS.md §6): collect the # RTCPeerConnection instances watch.js creates so a test can read the # viewer's inbound-rtp stats. The e2e suite (test_audio_share.py) installs # the same wrapper; it is repeated here because the e2e convention is one # copy per suite file. On the module's shared watch page `window.__probe` # is the frame-sampling promise instead — the two uses never share a page. PC_PROBE_INIT = """ window.__probe = {pcs: []}; const RealPC = window.RTCPeerConnection; window.RTCPeerConnection = function (config) { const pc = new RealPC(config); window.__probe.pcs.push(pc); return pc; }; window.RTCPeerConnection.prototype = RealPC.prototype; window.RTCPeerConnection.static = RealPC.static; """ def _free_display() -> str: for number in range(90, 120): path = f"/tmp/.X11-unix/X{number}" if os.path.exists(path): continue with socket.socket(socket.AF_UNIX) as probe: try: probe.connect(path) except OSError: return f":{number}" else: continue raise RuntimeError("no free X display in :90..:119") def _socket_path(display: str) -> str: """`:90` -> `/tmp/.X11-unix/X90`, the file whose existence means "ready".""" return f"/tmp/.X11-unix/X{display.lstrip(':')}" @pytest.fixture(scope="module") def x_display(): display = _free_display() ready = _socket_path(display) server = subprocess.Popen( ["Xvfb", display, "-screen", "0", XVFB_GEOMETRY, "-nolisten", "tcp"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) deadline = time.time() + 20 while time.time() < deadline: if os.path.exists(ready): break if server.poll() is not None: # pragma: no cover raise RuntimeError(f"Xvfb exited immediately with code {server.returncode}") time.sleep(0.15) else: # pragma: no cover server.terminate() raise RuntimeError(f"Xvfb never created {ready}") yield display server.terminate() try: server.wait(timeout=5) except subprocess.TimeoutExpired: # pragma: no cover server.kill() @pytest.fixture(scope="module") def browsers(x_display): """Two Chromium *processes* from one Playwright driver. The sharer is headed and lives on the Xvfb display, so what it captures is a genuine desktop. The watcher is headless: it never touches the display, which is exactly why it can be a separate process. """ previous = os.environ.get("DISPLAY") os.environ["DISPLAY"] = x_display try: with sync_playwright() as playwright: sharer = playwright.chromium.launch( channel="chromium", headless=False, args=REAL_CAPTURE_ARGS, env=browser_env(x_display), ) watcher = playwright.chromium.launch( channel="chromium", headless=True, args=[ "--no-sandbox", "--disable-dev-shm-usage", "--no-proxy-server", "--ozone-platform=x11", ], env=browser_env(), ) try: yield {"sharer": sharer, "watcher": watcher} finally: sharer.close() watcher.close() finally: if previous is not None: os.environ["DISPLAY"] = previous else: # pragma: no cover os.environ.pop("DISPLAY", None) @pytest.fixture def share_page(browsers, base_url): page = browsers["sharer"].new_context(viewport={"width": 1200, "height": 760}).new_page() page.goto(f"{base_url}/mirror", wait_until="domcontentloaded") expect(page.locator("#net")).to_have_attribute("data-state", "online", timeout=20_000) yield page page.close() @pytest.fixture def watch_page(browsers, base_url): page = browsers["watcher"].new_context().new_page() page.goto(f"{base_url}/watch", wait_until="domcontentloaded") expect(page.locator("#net")).to_have_attribute("data-state", "online", timeout=20_000) yield page page.close() class TestRealX11Capture: def test_the_real_desktop_is_captured_not_a_synthetic_one(self, base_url, share_page): errors: list[str] = [] share_page.on("pageerror", lambda error: errors.append(str(error))) share_page.locator("#start").click() expect(share_page.locator("body")).to_have_attribute("data-live", "on", timeout=40_000) settings = share_page.evaluate( """() => { const track = document.querySelector('#preview').srcObject.getVideoTracks()[0]; return {label: track.label, ...track.getSettings()}; }""" ) assert settings["displaySurface"] == "monitor", f"not a monitor capture: {settings}" assert settings["label"], "a real capture always names its source" assert not settings["label"].startswith("screen:-"), ( f"{settings['label']} is Chromium's synthetic capture device" ) size = (settings["width"], settings["height"]) assert size != FAKE_DEVICE_SIZE, ( f"got exactly {size}: that is Chromium's fake capture device, not the X11 screen" ) assert settings["width"] >= 640, f"implausibly small capture: {settings}" # Measured after phase 02: the 1200x760 window requests 1200x760 # (Playwright emulates screen == window) and Chromium still clamps the # monitor capture to the capturing window: 1280x800 desktop in, 1200x750 # out, aspect 1.6000 preserved. So compare the aspect ratio, not # absolute pixels (PLAN §5). assert abs(settings["width"] / settings["height"] - XVFB_ASPECT) < 0.06, ( f"{size} does not match the {XVFB_GEOMETRY} desktop aspect " f"({XVFB_ASPECT:.3f}); this is not our Xvfb screen" ) # The roster must carry the real geometry, not a made-up one. time.sleep(0.6) mirror = httpx.get(f"{base_url}/api/streams", timeout=5).json()["mirrors"][0] assert (mirror["width"], mirror["height"]) == size assert mirror["displaySurface"] == "monitor" assert errors == [], f"uncaught browser errors: {errors}" def test_a_real_desktop_reaches_a_second_machine_and_moves(self, base_url, browsers): share_page = ( browsers["sharer"].new_context(viewport={"width": 1200, "height": 760}).new_page() ) share_page.goto(f"{base_url}/mirror", wait_until="domcontentloaded") expect(share_page.locator("#net")).to_have_attribute("data-state", "online", timeout=20_000) share_page.locator("#start").click() expect(share_page.locator("body")).to_have_attribute("data-live", "on", timeout=40_000) watch_page = browsers["watcher"].new_context().new_page() watch_page.goto(f"{base_url}/watch", wait_until="domcontentloaded") expect(watch_page.locator("#net")).to_have_attribute("data-state", "online", timeout=20_000) expect(watch_page.locator(".tile[data-state='live']")).to_have_count(1, timeout=60_000) # Start measuring on the watcher, then paint: cycling the captured # machine's background is what a real presenter's screen does constantly. assert watch_page.evaluate(PROBE) == "started" for colour in ("#c81e1e", "#0b0d10", "#1e6ec8", "#0b0d10", "#1ec85a"): share_page.evaluate("(colour) => { document.body.style.background = colour; }", colour) time.sleep(0.28) result = watch_page.evaluate("() => window.__probe") assert result["width"] >= SHARP_FLOOR_PX, ( f"the watcher decoded a {result['width']}px-wide picture: the floor is " f"{SHARP_FLOOR_PX}px (90% of the {XVFB_GEOMETRY} desktop); below it the " f"wall shows a window-clamped capture, not the screen: {result}" ) assert abs(result["width"] / result["height"] - XVFB_ASPECT) < 0.06, result assert result["distinct"] >= 2, ( f"the watcher received a frozen frame: {result} — real desktop motion " f"never made it across the peer connection" ) # And the sharer agrees that someone is watching its desktop. expect(share_page.locator("#viewer-count")).to_have_text("1", timeout=20_000) # Stopping the share tears the mirror away for the watcher as well. share_page.locator("#stop").click() expect(watch_page.locator(".tile")).to_have_count(0, timeout=20_000) watch_page.close() share_page.close() def test_sharp_picture_reaches_the_wall(self, base_url, share_page, watch_page): """The size floor that makes \"sharper\" an assertion. The viewer's decoded width must be at least 90% of the Xvfb desktop (1152 of 1280 px), the picture must keep moving in the same probe (a large-but-frozen picture must not pass), and the roster must not lie about what is on the wire: the published ``/api/streams`` width/height sit within one rounding step of both the sharer's ``track.getSettings()`` and the viewer's decoded size. Measured on this host (phase 02, 2026-11-01): the 1200x760 share window requests 1200x760 (Playwright emulates ``screen`` == viewport) and Chromium clamps the monitor capture to the capturing window, so the viewer decodes 1200x750 — 93.75% of the desktop, over the floor. Removing the phase 02 size constraints delivered the identical 1200x750 here (the request cannot outgrow the window under Playwright), while a 1024x700 window delivered 1024x640 (80%) — under the floor. On real hardware ``screen`` is the true display resolution, so a small window on a large display is the case this floor and the share page's 75% toast are for. """ errors: list[str] = [] share_page.on("pageerror", lambda error: errors.append(str(error))) share_page.locator("#start").click() expect(share_page.locator("body")).to_have_attribute("data-live", "on", timeout=40_000) settings = share_page.evaluate( """() => { const track = document.querySelector('#preview').srcObject.getVideoTracks()[0]; const s = track.getSettings(); return {width: s.width, height: s.height, surface: s.displaySurface}; }""" ) assert settings["surface"] == "monitor", f"not a monitor capture: {settings}" expect(watch_page.locator(".tile[data-state='live']")).to_have_count(1, timeout=60_000) # Start measuring on the watcher, then paint: cycling the captured # machine's background is what a real presenter's screen does constantly. assert watch_page.evaluate(PROBE) == "started" for colour in ("#c81e1e", "#0b0d10", "#1e6ec8", "#0b0d10", "#1ec85a"): share_page.evaluate("(colour) => { document.body.style.background = colour; }", colour) time.sleep(0.28) result = watch_page.evaluate("() => window.__probe") assert result["width"] >= SHARP_FLOOR_PX, ( f"the watcher decoded a {result['width']}px-wide picture: the floor is " f"{SHARP_FLOOR_PX}px (90% of the {XVFB_GEOMETRY} desktop); below it the " f"wall is a window-clamped capture, not the screen: {result}" ) assert abs(result["width"] / result["height"] - XVFB_ASPECT) < 0.06, ( f"{result} does not match the {XVFB_GEOMETRY} desktop aspect " f"({XVFB_ASPECT:.3f}); this is not our Xvfb screen" ) assert result["distinct"] >= 2, ( f"the watcher received a frozen frame: {result} — a large-but-still " f"picture must not pass a sharpness test" ) # The roster must not lie about what is on the wire: the published # size sits within one rounding step of the sharer's delivered # settings *and* the viewer's decoded size. time.sleep(0.6) mirror = httpx.get(f"{base_url}/api/streams", timeout=5).json()["mirrors"][0] for source, (w, h) in { "the sharer's track.getSettings()": (settings["width"], settings["height"]), "the viewer's decoded size": (result["width"], result["height"]), }.items(): assert abs(mirror["width"] - w) <= 1, ( f"roster width {mirror['width']} vs {w} from {source}: the roster must " f"not lie about what is on the wire" ) assert abs(mirror["height"] - h) <= 1, ( f"roster height {mirror['height']} vs {h} from {source}: the roster must " f"not lie about what is on the wire" ) # And the sharer agrees that someone is watching its desktop. expect(share_page.locator("#viewer-count")).to_have_text("1", timeout=20_000) assert errors == [], f"uncaught browser errors: {errors}" def test_a_real_desktop_shares_audio_when_the_host_offers_any(self, base_url, browsers): """The realdisplay counterpart of the audio-arrival assertion. Where the host exposes an audio source to the real capture path, the viewer's ``inbound-rtp`` audio bytes must strictly increase across two samples (Opus frames over SRTP) and the picture must still decode distinct frames. ``--auto-select-desktop-capture-source=Entire screen`` selects the screen without ticking the picker's "share audio" box, and a host without an audio device has no track to offer at all — in that case the test *skips with the reason written down* (module docstring), never asserts on an absent track, and never stubs the peer connection. """ errors: list[str] = [] share_page = ( browsers["sharer"].new_context(viewport={"width": 1200, "height": 760}).new_page() ) share_page.on("pageerror", lambda error: errors.append(str(error))) share_page.goto(f"{base_url}/mirror", wait_until="domcontentloaded") expect(share_page.locator("#net")).to_have_attribute("data-state", "online", timeout=20_000) share_page.locator("#start").click() expect(share_page.locator("body")).to_have_attribute("data-live", "on", timeout=40_000) # The honest gate: does the captured stream actually carry an audio # track? This is what the browser's real capture path delivered. has_audio = share_page.evaluate( """() => { const so = document.querySelector('#preview').srcObject; return !!(so && so.getAudioTracks().length); }""" ) if not has_audio: share_page.locator("#stop").click() share_page.close() pytest.skip( "the host offers no audio source to the real capture path " f"(platform={sys.platform}; --auto-select-desktop-capture-source=" "'Entire screen' does not tick 'share audio', and this host " "delivers no audio track); see the module docstring" ) watcher = browsers["watcher"].new_context() watcher.add_init_script(PC_PROBE_INIT) watch_page = watcher.new_page() watch_page.on("pageerror", lambda error: errors.append(str(error))) watch_page.goto(f"{base_url}/watch", wait_until="domcontentloaded") expect(watch_page.locator("#net")).to_have_attribute("data-state", "online", timeout=20_000) expect(watch_page.locator(".tile[data-state='live']")).to_have_count(1, timeout=60_000) # The deliberate gesture: stage, then unmute. watch_page.keyboard.press("Enter") expect(watch_page.locator("body")).to_have_class(re.compile("staged"), timeout=5_000) watch_page.keyboard.press("m") assert ( watch_page.evaluate("() => !document.querySelector('#watch-stage video').muted") is True ) def audio_inbound() -> dict: """One ``inbound-rtp`` audio sample from the viewer's leg.""" return watch_page.evaluate( """async () => { const pcs = (window.__probe && window.__probe.pcs) || []; const pc = pcs.find((p) => p.connectionState === 'connected'); if (!pc) return {pc: false}; const stats = await pc.getStats(); let audio = null; stats.forEach((r) => { if (r.type === 'inbound-rtp' && r.kind === 'audio') audio = r; }; if (!audio) return {pc: true, entry: null}; const energy = audio.totalAudioEnergy; const level = audio.audioLevel; return { pc: true, entry: { bytes: audio.bytesReceived || 0, energy: Number.isFinite(energy) ? energy : null, level: Number.isFinite(level) ? level : null, }, }; }""" ) first: dict = {} deadline = time.time() + 30 while time.time() < deadline: first = audio_inbound() if first.get("entry"): break time.sleep(0.5) assert first.get("entry"), ( f"the viewer's connected leg has no inbound-rtp audio entry: {first}" ) time.sleep(4.0) second = audio_inbound() assert second.get("entry"), f"the inbound-rtp audio entry vanished: {second}" assert second["entry"]["bytes"] > first["entry"]["bytes"], ( f"inbound audio bytes did not strictly increase: {first} -> {second}" ) # The picture is asserted in the same test: audio arriving while the # picture froze would be a different bug. One self-contained sample # loop — no global (on this page `window.__probe` belongs to the # peer-connection collector). frames = watch_page.evaluate( """async () => { const video = document.querySelector('#watch-stage video'); const canvas = document.createElement('canvas'); canvas.width = 96; canvas.height = 60; const ctx = canvas.getContext('2d', {willReadFrequently: true}); const seen = new Set(); for (let i = 0; i < 10; i++) { await new Promise((r) => setTimeout(r, 120)); try { ctx.drawImage(video, 0, 0, 96, 60); } catch {} const d = ctx.getImageData(0, 0, 96, 60).data; let h = 0; for (let j = 0; j < d.length; j += 4) h = ((h * 31) + d[j] + d[j + 1] * 3 + d[j + 2] * 7) >>> 0; seen.add(h); } return {width: video.videoWidth, distinct: seen.size}; }""" ) assert frames["width"] >= SHARP_FLOOR_PX, ( f"the watcher decoded a {frames['width']}px-wide picture: {frames}" ) assert frames["distinct"] >= 2, f"the watcher received a frozen frame: {frames}" # Clean up before the module-scoped fixtures serve the next test. watch_page.close() watcher.close() share_page.locator("#stop").click() share_page.close() assert errors == [], f"uncaught browser errors: {errors}"