Skip to content

Commit 868bae4

Browse files
hubooyclaude
andcommitted
fix(screen_recorder): record the screen, not the webcam, on macOS
avfoundation enumerates cameras before screens, so passing screen_index straight through to -i selects device 0 — the built-in camera on any Mac that has one. The tool then silently records the user's face while the caller believes it is capturing the screen, with no error to reveal it. That is a privacy problem as much as a correctness one: screen_capture_selector advertises this tool as "no webcam" (and routes webcam overlay work to cap_recorder), and screen_index is documented as a monitor index, not a device index. The Windows and Linux branches do not take screen_index at all, so the mac branch was the only one leaking a platform device numbering into a cross-platform parameter. Map the monitor index to its real avfoundation device index by parsing -list_devices, falling back to the raw index when detection fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYThSL15CujvBwD1wuUmr9
1 parent 4eab34c commit 868bae4

1 file changed

Lines changed: 26 additions & 2 deletions

File tree

tools/capture/screen_recorder.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,28 @@ def _detect_audio_device_windows() -> str | None:
5454
return None
5555

5656

57+
def _detect_screen_device_mac(screen_index: int) -> str | None:
58+
"""Map a monitor index to its avfoundation device index.
59+
60+
avfoundation numbers cameras before screens, so device 0 is a webcam on
61+
any Mac with a camera — passing the monitor index straight through would
62+
silently record the camera instead of the screen.
63+
"""
64+
try:
65+
result = subprocess.run(
66+
["ffmpeg", "-f", "avfoundation", "-list_devices", "true", "-i", ""],
67+
capture_output=True, text=True, timeout=10,
68+
)
69+
for line in result.stderr.splitlines():
70+
if f"] Capture screen {screen_index}" in line and "[" in line:
71+
idx = line.split("[")[-1].split("]")[0].strip()
72+
if idx.isdigit():
73+
return idx
74+
except (subprocess.TimeoutExpired, FileNotFoundError, IndexError):
75+
pass
76+
return None
77+
78+
5779
def _detect_audio_device_mac() -> str | None:
5880
"""Find the default audio input index on macOS via avfoundation."""
5981
try:
@@ -328,12 +350,14 @@ def _build_mac_cmd(
328350
cmd += ["-framerate", str(fps)]
329351
cmd += ["-t", str(duration)]
330352

353+
screen_device = _detect_screen_device_mac(screen_index) or str(screen_index)
354+
331355
if region:
332356
# avfoundation doesn't support region directly — we crop in post
333-
cmd += ["-i", f"{screen_index}:{audio_idx}"]
357+
cmd += ["-i", f"{screen_device}:{audio_idx}"]
334358
cmd += ["-vf", f"crop={region['width']}:{region['height']}:{region.get('x', 0)}:{region.get('y', 0)}"]
335359
else:
336-
cmd += ["-i", f"{screen_index}:{audio_idx}"]
360+
cmd += ["-i", f"{screen_device}:{audio_idx}"]
337361

338362
cmd += ["-c:v", "libx264", "-preset", "ultrafast", "-crf", "23"]
339363
if capture_audio and audio_idx != "none":

0 commit comments

Comments
 (0)