Repository navigation
feat(stereo-camera): R3 — consent, foreground rule, indicator, kill switches, keyed persistentId (ADR-043) - #1819
Merged
Merged
Conversation
dfattal
pushed a commit
that referenced
this pull request
Oct 6, 2026
…ompt cursor type Two findings from the R3 hardware run on the SR laptop (#1819): - 'camera sharing off' then 'camera probe' exited 3 ('the service exposes no stereo camera') instead of the documented 6. While sharing is off the service enumerates no camera, so the probe never reached the stream-create refusal that maps to 6. The probe now asks the service's status (a DIAG-only control, so over a second short-lived connection) when the enumeration is empty, and reports DISABLED / exit 6 when sharing is off. A machine with no stereo camera still exits 3. - service_tray_win.c: LoadCursorW(NULL, IDC_ARROW) is MSVC C4133 in this non-UNICODE build (IDC_ARROW is the ANSI-typed MAKEINTRESOURCE); use MAKEINTRESOURCEW(32512). Compiled on macOS (displayxr-cli, displayxr-service). NOT run: the exit code needs a service with the sim or SR camera and a verified DIAG CLI; the tray file is Windows-only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
Author
Hardware gates on the SR laptop — PASS (run by the Windows session, 2026-10-05)Runtime R3
Two findings from that run are fixed in Not run: |
…04999300-310, catalog note (ADR-043) Type values sit at 1004999300-310 (not 290-300 as first drafted): on the rebase onto post-lift main, 290 had been taken by XR_TYPE_WEAVE_SUBMIT_OVERLAY_UNCHANGED_DXR (weave spec v14). Safe to move: spec v1, nothing shipped chains these. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ator, NV12/BGRA/GRAY8 conversion, persistent id, disparity probe Pure, OS-free building blocks shared by the service camera manager, the sim_display fake and displayxr-cli camera (ADR-043). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_STEREO_CAMERA) + sim_display FAKE stereo camera Six optional slots appended to xrt_plugin_iface per ADR-020 (struct_size-gated, no ABI bump): enumerate, get_calibration (OpenCV R,T of the ACTIVE device), open (= tracker keep-alive), wait_frame, release_frame, close. They sit after a one-pointer placeholder for ADR-042's create_dp_d3d11_lift, which claims the offset right after the vk fingerprint on feat/lift-ext but is not on main yet: the camera slots are therefore at their final offsets whichever branch merges first, and tests_stereo_camera asserts the adjacency (against the real lift slot once it exists). sim_display fills the slots; SIM_DISPLAY_FAKE_STEREO_CAMERA=1 advertises one 640x480-per-eye 30 Hz GRAY8 camera rendering a random-dot pair with a 12 px background and a 40 px bar (68 deg HFOV, 50 mm, 2.0/0.6 m) plus a frame counter; _SIZE / _FPS / _FORMAT / _SUSPEND_PERIOD_MS knobs. xrt_instance gains the stereo_camera client aspect (IPC instance only) and get_active_plugin (native instance) for the service's camera manager. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mera_* calls) ipc_server_stereo_camera: the service opens each plug-in camera once, on the first stream start, and closes it 2 s after the last stop. One runtime-owned thread per open camera pulls wait_frame and fans each frame out to every started stream: per stream a 3-slot latest-wins pinned ring in shared memory (u_stereo_camera_ring), format conversion, decimation, and a per-stream wake handle (auto-reset event on Windows, non-blocking pipe on POSIX). States WAITING / AVAILABLE / SUSPENDED / UNAVAILABLE follow the plug-in; a suspension clears every pin. Streams are owned by the connection and die with it. Privacy hooks, deny by default: start and calibration reads are refused (XRT_ERROR_NOT_AUTHORIZED) unless DXR_STEREO_CAMERA_DEV_ALLOW=1 is set in the SERVICE environment (R3 replaces it with OS + DisplayXR consent); DXR_STEREO_CAMERA=0 is the kill switch (zero cameras); RAW is refused to PRESENT_OWNER clients (the browser); the foreground rule and in-use indicator are marked hook points. Windows hands out a FILE_MAP_READ section duplicate and a SYNCHRONIZE-only event duplicate. The D3D11 / AHardwareBuffer transports are designed in the file comment (same ring, texture slots + fence / sync_file). Client side: ipc_client_stereo_camera (connection-level calls) and the xrt_stereo_camera_client aspect the IPC instance now carries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n its open time Re-anchoring the pacing clock after a pause also moved the suspend phase, so every SUSPENDED half-period stretched (36 frames in 5 s instead of ~75 at a 1 s period). Separate origins; now 92 frames in 6 s, 3 suspensions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A real DIAG consumer over IPC: probe creates + starts a stream (through the service's authorisation), maps the ring READ-ONLY, waits on the per-stream wake handle, acquires, and reports the measured delivery rate, frame-index gaps, the SBS layout, block-matched disparities of the last frame and the service's stream stats; --out writes the last frame as a PNG. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ce-level, service-only) Nine entry points over the instance's xrt_stereo_camera_client aspect. An in-process instance enumerates zero (maintainer decision: service clients only in R1). Consent refusal maps to XR_ERROR_PERMISSION_INSUFFICIENT (retryable), a dead pipe to XR_ERROR_INSTANCE_LOST; capture times are converted to XrTime; handles returned through XrStereoCameraStreamTransportDXR are the caller's (closed here when nothing is chained to receive them). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Enumerates the active plug-in's cameras through the iface and validates each description (even non-zero eye extent, known format, positive rate, CALIBRATED backed by a real calibration). Absence never fails. sim_display: 'PASS: stereo_camera_caps — 0 cameras (OK)'; with the fake: '1 camera(s); [0] "Sim 3D camera (fake)" 640x480/eye @ 30.0 Hz, flags 0x1f'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stant expressions for MSVC/GCC) Caught by the MinGW syntax check: the XR flag bits are static const XrFlags64, which only clang accepts in _Static_assert. Pin the xrt mirrors to the header's literal values at compile time and cross-check the real constants once at runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oid facts - spec: status R1, the DXR_STEREO_CAMERA_DEV_ALLOW dev gate, privacy hook status, slot placement behind the lift placeholder, the OpenCV R,T plug-in convention and the wait_frame result enum, the fake as built, CLI output. - roadmap: §C Android rewritten from the NP02J/K68 measurements (logical cameras 4/5, two raw 1280x720 NV12 streams, LeiaCameraBuilder is not CNSDK, opening the front pair evicts the tracker for good), design consequence: the runtime owns the pair and runs in-app tracking from the same capture; §E R1 as built + merge order; §F Android risk + CNSDK asks; §G the four R1 maintainer decisions. - ADR-043: R1 status, Android context/consequence updated. - xrt_plugin_iface reference: the stereo_camera_* slot contract. - env census: DXR_STEREO_CAMERA, DXR_STEREO_CAMERA_DEV_ALLOW. - ADR index regenerated (it was stale on the docs branch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…SVC C5287)
MSVC /W4 raised C5287 ("operands are different enum types") on the
XrStereoCamera*DXR vs xrt_stereo_camera_* _Static_asserts, even through the
(int) casts. Pin each side against the header's literal instead: two
enum-vs-literal checks carry the same proof with no cross-enum operator.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ver a guess Field test (Leia SR tracking camera): the descriptor said "up to 30.0 Hz" while the source ran 58-64 Hz — the plug-in's enumerate-time rate was a hard-coded placeholder. The service now measures every source over the first 60 frames after each open (u_stereo_camera_rate_meter; a >0.5 s gap restarts the window) and the descriptor, the decimator and the stream info use that; until a window completes they use the plug-in's value, where 0 now means "unknown" (xrt_plugin doc, XR header doc, spec). One WARN on the first measurement, INFO after. - selftest: max_frame_rate 0 is legal; negative / NaN / > 1000 is malformed. - camera list prints "rate unknown (measured once a stream runs)"; probe prints "cap = source rate" instead of "cap 0.0 Hz". - sim fake: SIM_DISPLAY_FAKE_STEREO_CAMERA_ADVERTISED_FPS (0 = unknown) to exercise the measured path; tests_stereo_camera covers the meter (jitter, suspension gap, restart keeps the last value). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, not a fixed 64 px
On the Leia SR tracking camera (baseline 120 mm, f ~513 px) a face at 0.6 m is
~95 px of disparity, so every block of the fixed 0..64 px search clamped at 64
and the probe reported "d = 64". The bound is now f*B/0.4 m from the camera's
own baseline + HFOV (quarter eye width when uncalibrated; clamped to
[64, 0.6*eye]), and a block whose best match sits ON the bound is counted and
excluded ("AT THE SEARCH BOUND") instead of silently reported as the bound.
sim fake: SIM_DISPLAY_FAKE_STEREO_CAMERA_BASELINE_MM (default 50). At 120 mm
the probe reads 28 px (background) / 95 px (bar at 0.6 m), search 0..143.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…slots after platform-state, values 300-310 Mechanical follow-through of the rebase onto main (lift v2.22.0 + ADR-045): - docs no longer describe the reserved_adr042_create_dp_d3d11_lift placeholder; the camera slots append after create_dp_d3d11_lift AND get_platform_state. - type values quoted as 1004999300-310 everywhere (290 is weave v14's). - cli CMake: one stb include for lift + camera, not two. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…emap, no OpenCV (R2) Vendor-neutral rectifier for XR_DXR_stereo_camera (ADR-043 R2), in three layers so a GPU path can replace only the last one: - geometry: OpenCV's stereoRectify(CALIB_ZERO_DISPARITY, alpha = 0) in C (half rotations + baseline onto x, min-fy focal, corner-centroid principal point, 9x9 inner-rectangle zoom), plus a border check of the real maps that zooms further until no output pixel samples outside its raw image; - float maps (what a GPU backend uploads as an RG32F texture); - CPU backend: fixed-point bilinear taps over the whole SBS image, 1/2/4 channels (GRAY8, NV12 Y + half-res UV, BGRA8). RADTAN5 / RADTAN8 / KB4 lens models, calibration rescaled when it was taken at a different size than the frames. Also u_stereo_camera_estimate_offset: SSD block matching over dx AND dy, for row-alignment checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd truth (R2) SIM_DISPLAY_FAKE_STEREO_CAMERA_DISTORT=1 renders the fake's scene through two RAW cameras: different per-eye intrinsics, RADTAN5 barrel lenses, and each camera turned by half of (pitch 0.35, yaw 0.25, roll 0.45 deg) in opposite senses (~6 px vertical misalignment, 0.9 deg relative roll). The symmetric split makes Bouguet recover exactly the virtual parallel pair the scene is defined in, so the ground truth after rectification is exact: aligned rows and disparity f_rect * B / Z. The camera drops NATIVELY_RECTIFIED and its calibration slot returns the truth, so RECTIFIED output exercises the service's rectifier. The scene is sampled continuously (bilinear value noise), so resampling is honest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the distorted fake tests_stereo_rectify, with fixtures generated offline by tests/fixtures/gen_stereo_rectify_fixtures.py (OpenCV 4.10) for three calibrations (the sim fake, an SR-like pair with a non-axial baseline, a RADTAN8 pair calibrated at 2x the frame size): - R1/R2 equal OpenCV's to 1e-12; principal point equals the converged reference to 1e-4 px (OpenCV's own P1 within 0.75 px: it stops undistortPoints after 5 iterations); focal within 1 % (measured +0.05..0.13 %); - maps equal initUndistortRectifyMap to 3e-5 px; - alpha = 0 really leaves no black pixel (full and half resolution); - end to end: raw |dy| median 2.9 px -> rectified worst block 0.061 px; disparity 11.33 / 37.77 px ground truth, worst block error 0.063 px; - NV12 / BGRA8 planes agree with GRAY8; hidden [.perf] case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fied calibration (R2) A per-camera rectifier is built at manager create from the plug-in's RAW calibration when it is CALIBRATED and not NATIVELY_RECTIFIED. It runs once per source frame on the camera thread, outside the manager lock, and only while a started stream wants RECTIFIED; RAW streams read the original frame. A RECTIFIED stream never receives unrectified pixels (a frame produced before it started is skipped). get_calibration(RECTIFIED) returns the same geometry: one pinhole for both eyes (zero disparity at infinity), no distortion, rightFromLeft = identity + (+baseline, 0, 0). horizontalFovDeg is the rectified one. The browser (PRESENT_OWNER) is refused a camera the rectifier rejected instead of getting the RAW-flagged fallback native clients get. The seam is struct scam_rectifier (backend-neutral geometry + an apply() backend: CPU LUT today). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lignment, --rectified gates it Every probe now 2-D block-matches the last frame (textured 32x32 blocks) and prints |dy| median / p90 / blocks above 1 px, plus the dominant disparities as depths Z = f*B/d from the RECTIFIED calibration. probe --rectified asks for RECTIFIED output and exits 5 when frames come back RAW or the median |dy| exceeds 0.5 px. Against the distorted fake: median 0.016 px, depths 2.004 m and 0.599 m (truth 2.0 / 0.6); --raw shows the 2.6 px it fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…measured numbers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… camera at -x A swapped SBS pair still rectifies (Bouguet does not care), but every disparity comes out negative and rightFromLeft points the wrong way. The L1 Leia provider decides eye order from the sign of T; this makes a wrong decision visible in the service log instead of only in a probe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MSVC only defines M_PI when _USE_MATH_DEFINES precedes the first <cmath>, which Catch may already have pulled in. Use a local constant. First MSVC build of R2; the test passes (716 assertions, 7 cases). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…, signed dy, bound hits flagged First SR-hardware run: a face at 0.6 m on the 120 mm tracker is ~95 px of disparity, past the probe's 0..64 search, and the SSD matcher's +-10 px dy window clamped too — it reported "d = 64, |dy| = 10.000" as if measured. - u_stereo_camera_match_block: zero-mean NCC (blind to the two sensors' gain/offset), coarse pass on every 2nd pixel + full-res hill-climb, parabolic sub-pixel on both axes, the searched window returned, and dx_at_edge / dy_at_edge when the integer peak sits ON a bound (a clamp, not a measurement). - camera probe: disparity window f*B/0.4 m from the camera (R1 helper), dy +-24 px, accept NCC >= 0.90; prints accepted / low-NCC / at-bound counts, warns when > 20 % of blocks hit a bound, reports the SIGNED dy median, and --rectified now also fails on a constant offset (|signed median| > 0.5 px). --max-disparity / --max-dy / --min-ncc override. - sim fake: the DISTORTED variant honours _BASELINE_MM too (truth + slot). - tests: large-baseline matcher (bar 94.9 px, signed dy +1.68 recovered to 0.07 px, gain/offset, flat refused, the old 64 px window is REJECTED not reported); END TO END at 120 mm (bar 90.64 px GT, measured 90.66); the [vshift] hypotheses for the field's +1.7 px residual (per-eye cy error = constant dy ~ -dcy, transposed R = large roll-shaped dy, calib-size rescale and the plug-in's half swap exact, identity = pure scale about cy). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…torted fake (R2) SIM_DISPLAY_FAKE_STEREO_CAMERA_DY (px) and _DY_SLOPE (px per 100 px) shift the right eye's rows on top of the DISTORTED pair without telling the reported calibration, like a device whose stored calibration is slightly off (the Leia SR laptop: +2.6 px, -0.47 px / 100 px). Drives the online refinement's tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reo_vrefine) On a Leia SR laptop the rectified rows stayed +1.71 px apart, and OpenCV's stereoRectify reproduces that on the same frame: the stored calibration is off, not the rectifier. u_stereo_vrefine measures the residual dy = a + b (y - cy) on rectified frames (Shi-Tomasi patches, half-res NCC search, full-res NCC refinement, gated on NCC, d > 0, unclamped peaks and vertical curvature), fits it robustly (median + Tukey IRLS) and runs the controller: duty cycle, >= 50 matches over >= 3 frames, 0.2 px deadband, |a| <= 6 px, |b| <= 2 px/100 px. u_stereo_rectify folds the correction in as v_offset = a/f, v_slope = b: a symmetric per-eye vertical affine in normalized rectified coordinates applied BEFORE the principal point and the alpha = 0 crop, so P1/P2 describe the corrected frames. Adds u_stereo_rectify_rect_from_raw (the inverse map). tests_stereo_vrefine: the fake with the SR model converges to +0.000 px in one rebuild; degenerate frames never apply; clamps hold; and a REAL SR raw frame (62 KB JPEG + calibration + 63 OpenCV SIFT matches as an independent judge): signed dy median +1.715 px -> +0.075 px. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reports it A per-camera worker measures rectified frames the camera thread parks for it (every 250 ms for 5 s after an open or an update, then every 30 s; 2.7 ms per measurement at -O2, off the frame path), rebuilds geometry + LUTs when the correction moves, and the camera thread swaps them in between frames and bumps calibration_generation. WARN on first application, INFO after, re-verified on re-open, DXR_STEREO_CAMERA_REFINE=0 kill switch. Stream stats carry the state, applied a/b, match count and dy before/after; camera probe prints them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… |dy| median On the SR laptop with a face at 0.6 m the refined rows were aligned (signed median -0.10/-0.18 px) but --rectified FAILED: block-match noise alone put the unsigned |dy| median at 1.03/1.10 px. Gate on |signed median| <= 0.5 px, MAD about it <= 1.5 px and >= 20 blocks; print the gate numbers; keep |dy| stats informational. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tions
On the SR laptop the B1 browser's "3D Camera (DisplayXR)" never appeared:
[CAP] refusing client pid=... ('ChromiumStereoCamera'): class PRESENT_OWNER
quota 2/2 exhausted (#960). One browser already takes both slots (GPU
process weaves, browser process binds the window), so its video-capture
utility - the XR_DXR_stereo_camera client, which declares PRESENT_OWNER
by enabling XR_DXR_weave - was the third connection and was refused
(xrCreateInstance -10 in the capture service).
A connection from the same executable as an admitted PRESENT_OWNER is now
a sibling and takes no new slot; the quota is compared against DISTINCT
owner executables, so two different browsers still fill it. A peer whose
image path is unreadable is never a sibling (fails closed).
Verified on the SR laptop: all three Chromium processes admitted, the
page lists and opens "3D Camera (DisplayXR)" (sbs 1280x480), stream
RECTIFIED.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…stentId (ADR-043 R3) u_camera_consent: the spec §7.1 decision tree (sharing off > dev override > no identity > delegating client > OS camera switch > stored decision > Allow-once > prompt) over two injected vtables so it is testable with fakes. u_camera_consent_store: the real store — HKCU\Software\DisplayXR\CameraConsent on Windows (plus HKLM / HKCU Delegating lists and the CapabilityAccessManager webcam switch), camera_consent.json (0600) in the user config dir on POSIX plus the installer's system delegating list. persistentId becomes HMAC-SHA-256 under a 32-byte per-user secret the store generates once (u_sha256: compact FIPS 180-4 / RFC 2104, pinned by test vectors). u_client_class: the PRESENT_OWNER owner-counting rule as a pure function. tests_camera_consent covers every rule, Allow-once bound to the pid, prompt timeout / unavailable retried, the keyed id's stability and unlinkability, and the owner count with a CAMERA_CONSUMER sibling. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…de the panel quota A browser's video-capture utility (B1) has no window and no weave, yet was classed PRESENT_OWNER by enabling XR_DXR_weave and spent a panel-owner slot. XRT_CLIENT_CLASS_CAMERA_CONSUMER is declared explicitly with the new XrStereoCameraClientInfoDXR (CONSUMER_ONLY bit) on XrInstanceCreateInfo, verified by use (camera handlers only; session_create refuses it), quota 4, and never counted toward the PRESENT_OWNER quota — the handler now uses the pure u_client_class_present_owner_count(). A hybrid runtime always routes a declared consumer to the service. The tester's owner-quota (sibling) rule for the browser's other processes is kept. XR_DXR_stereo_camera spec v2: appends 1004999311–316 (client-info struct, CONSENT_REFUSED / DISABLED / BUSY / STREAM_ENDED results, the stream-ended event); nothing renumbered. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…k; distinct results; events delivered Replaces the R1 DXR_STEREO_CAMERA_DEV_ALLOW gate with the u_camera_consent policy at every stream start and calibration read, evaluated with the manager lock released and one evaluation at a time (the tray prompt blocks the client thread for up to 60 s). Each verdict has its own result — XRT_ERROR_STEREO_CAMERA_CONSENT_REFUSED / _DISABLED / _BUSY / _STREAM_ENDED, mapped 1:1 to the new XrResults — so a browser can map consent to NotAllowedError. RAW is refused to PRESENT_OWNER and delegating clients. Foreground rule per published frame (250 ms cache): window-bearing classes need a visible top-level window of the peer pid (EnumWindows / NSRunning- Application); delegating, CAMERA_CONSUMER and DIAG are exempt. Every stream is suspended while the OS session is locked (set_session_locked, fed by the platform hooks); the reported state is SUSPENDED meanwhile. The state-changed and stream-ended events are now delivered: per-connection queues drained by stereo_camera_poll_event from xrPollEvent, mapping a service stream id back to the app's handle. stop_all / set_sharing / get_status serve the UI; the macOS menu-bar item gains the in-use badge, "Stop camera sharing", the sharing toggle, a floating consent panel and the screen-lock / session-switch observers. persistentId is keyed with the store's secret. Consumers receive a READ-ONLY section on every platform (ipc_shmem_create_with_readonly). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…itch, WTS lock suspension Icon badge (red dot) + tooltip + menu line "3D camera in use by <app>", one balloon per transition, "Stop camera sharing", the "Share the 3D camera with apps" checkmark, a top-most consent dialog (Allow / Allow once / Deny) hosted on the tray thread for the manager's prompt provider, and WTSRegisterSessionNotification (lock / console or remote disconnect) plus SM_REMOTESESSION at start feeding the lock suspension. Written to the API; needs the Windows box to run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sharing, fake-lock; probe reports events list/calib/probe connect as CAMERA_CONSUMER (the browser utility's class); the control ops as DIAG. probe prints every event with its time since start and exits 4 / 6 / 7 / 8 for consent refused / sharing off / busy / ended by the service. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Spec §2a + §7 rewritten as built, §8 results, v2 history; roadmap R3 row and "R3 as built"; env census (DEV_ALLOW as the documented override, DXR_STEREO_CAMERA_PROMPT); docs/guides/stereo-camera-consent.md for app and browser developers (incl. the B1 migration); ADR-043 Amendment 1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ompt cursor type Two findings from the R3 hardware run on the SR laptop (#1819): - 'camera sharing off' then 'camera probe' exited 3 ('the service exposes no stereo camera') instead of the documented 6. While sharing is off the service enumerates no camera, so the probe never reached the stream-create refusal that maps to 6. The probe now asks the service's status (a DIAG-only control, so over a second short-lived connection) when the enumeration is empty, and reports DISABLED / exit 6 when sharing is off. A machine with no stereo camera still exits 3. - service_tray_win.c: LoadCursorW(NULL, IDC_ARROW) is MSVC C4133 in this non-UNICODE build (IDC_ARROW is the ANSI-typed MAKEINTRESOURCE); use MAKEINTRESOURCEW(32512). Compiled on macOS (displayxr-cli, displayxr-service). NOT run: the exit code needs a service with the sim or SR camera and a verified DIAG CLI; the tray file is Windows-only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
dfattal
force-pushed
the
feat/stereo-camera-r3
branch
from
October 6, 2026 07:44
b567f32 to
fa524ce
Compare
dfattal
marked this pull request as ready for review
October 6, 2026 07:44
This was referenced Oct 6, 2026
dfattal
added a commit
that referenced
this pull request
Oct 6, 2026
… a stored Deny Follow-up to PR #1819 (R3 consent). As built, a registered consent- delegating client was allowed at step 4 of the consent decision, before the OS camera privacy switch (step 5) and before a stored per-app Deny (step 6), and tests_camera_consent pinned that order. Once a browser's installer registered it, it would get the 3D camera with the OS switch off and a Deny the user had stored silently ignored. Roadmap §B.3 and the pre-R3 spec say the opposite, and no rationale for the order was recorded; "the browser enforces the OS switch itself" cannot hold, because the service, not the browser, opens the camera, so the OS never sees the browser as a camera user. New order (spec §7.1 is the one statement of it): sharing off / DXR_STEREO_CAMERA=0 -> DISABLED; dev override; no identity; OS switch -> PERMISSION_INSUFFICIENT; stored Deny -> CONSENT_REFUSED; delegating -> allowed; stored Allow; Allow once; prompt. Delegation keeps meaning exactly "no runtime prompt and no stored decision needed". The decision's `delegating` flag is now reported from step 3 on whatever the verdict. `displayxr-cli camera trust <exe>` over a stored Deny clears the Deny and says so (the user's newer explicit word, like `allow` over `deny`); an installer's machine-wide registration never touches the user's store. `camera deny` on a delegating exe notes that the Deny wins. Explicit <exe> arguments are stored the way the service sees the peer: resolved with realpath on POSIX; on Windows read from the UTF-16 command line (argv is the ANSI code page) and made absolute with GetFullPathNameW. Windows store reader (u_camera_consent_store.c), both bugs verified: - it used the ANSI registry API while the runtime compares UTF-8 paths (QueryFullProcessImageNameW -> CP_UTF8), so a non-ASCII install path could never match an installer's UTF-16 entry. Every read, write, delete and enumeration (HKLM and HKCU, Apps, Delegating, Sharing, Secret, the ConsentStore webcam keys) now uses the wide API with explicit UTF-8 conversion. - fixed 256-char name / 1024-byte data buffers turned one oversized value into ERROR_MORE_DATA and `break`, hiding every later entry. Buffers are now sized from RegQueryInfoKeyW; an entry that still does not fit, has the wrong type or does not convert is skipped, never ends the scan. The matching is the pure u_camera_consent_list_find() over an abstract enumerator plus u_camera_consent_path_equal(), both unit-tested on every OS. Tests: the delegation-first assertions are rewritten; new cases for delegating + OS switch off, + stored Deny, + sharing off / kill switch, + nothing stored (allowed, no prompt, nothing written), unregistered neighbour unchanged, path rules, and skip-not-stop enumeration. Mutation: restoring the old order fails 2 cases / 7 assertions; making the list scan stop on a skipped entry fails the enumeration case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
added a commit
that referenced
this pull request
Oct 6, 2026
… a stored Deny Follow-up to PR #1819 (R3 consent). As built, a registered consent- delegating client was allowed at step 4 of the consent decision, before the OS camera privacy switch (step 5) and before a stored per-app Deny (step 6), and tests_camera_consent pinned that order. Once a browser's installer registered it, it would get the 3D camera with the OS switch off and a Deny the user had stored silently ignored. Roadmap §B.3 and the pre-R3 spec say the opposite, and no rationale for the order was recorded; "the browser enforces the OS switch itself" cannot hold, because the service, not the browser, opens the camera, so the OS never sees the browser as a camera user. New order (spec §7.1 is the one statement of it): sharing off / DXR_STEREO_CAMERA=0 -> DISABLED; dev override; no identity; OS switch -> PERMISSION_INSUFFICIENT; stored Deny -> CONSENT_REFUSED; delegating -> allowed; stored Allow; Allow once; prompt. Delegation keeps meaning exactly "no runtime prompt and no stored decision needed". The decision's `delegating` flag is now reported from step 3 on whatever the verdict. `displayxr-cli camera trust <exe>` over a stored Deny clears the Deny and says so (the user's newer explicit word, like `allow` over `deny`); an installer's machine-wide registration never touches the user's store. `camera deny` on a delegating exe notes that the Deny wins. Explicit <exe> arguments are stored the way the service sees the peer: resolved with realpath on POSIX; on Windows read from the UTF-16 command line (argv is the ANSI code page) and made absolute with GetFullPathNameW. Windows store reader (u_camera_consent_store.c), both bugs verified: - it used the ANSI registry API while the runtime compares UTF-8 paths (QueryFullProcessImageNameW -> CP_UTF8), so a non-ASCII install path could never match an installer's UTF-16 entry. Every read, write, delete and enumeration (HKLM and HKCU, Apps, Delegating, Sharing, Secret, the ConsentStore webcam keys) now uses the wide API with explicit UTF-8 conversion. - fixed 256-char name / 1024-byte data buffers turned one oversized value into ERROR_MORE_DATA and `break`, hiding every later entry. Buffers are now sized from RegQueryInfoKeyW; an entry that still does not fit, has the wrong type or does not convert is skipped, never ends the scan. The matching is the pure u_camera_consent_list_find() over an abstract enumerator plus u_camera_consent_path_equal(), both unit-tested on every OS. Tests: the delegation-first assertions are rewritten; new cases for delegating + OS switch off, + stored Deny, + sharing off / kill switch, + nothing stored (allowed, no prompt, nothing written), unregistered neighbour unchanged, path rules, and skip-not-stop enumeration. Mutation: restoring the old order fails 2 cases / 7 assertions; making the list scan stop on a skipped entry fails the enumeration case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
added a commit
that referenced
this pull request
Oct 6, 2026
… a stored Deny (#1836) Follow-up to PR #1819 (R3 consent). As built, a registered consent- delegating client was allowed at step 4 of the consent decision, before the OS camera privacy switch (step 5) and before a stored per-app Deny (step 6), and tests_camera_consent pinned that order. Once a browser's installer registered it, it would get the 3D camera with the OS switch off and a Deny the user had stored silently ignored. Roadmap §B.3 and the pre-R3 spec say the opposite, and no rationale for the order was recorded; "the browser enforces the OS switch itself" cannot hold, because the service, not the browser, opens the camera, so the OS never sees the browser as a camera user. New order (spec §7.1 is the one statement of it): sharing off / DXR_STEREO_CAMERA=0 -> DISABLED; dev override; no identity; OS switch -> PERMISSION_INSUFFICIENT; stored Deny -> CONSENT_REFUSED; delegating -> allowed; stored Allow; Allow once; prompt. Delegation keeps meaning exactly "no runtime prompt and no stored decision needed". The decision's `delegating` flag is now reported from step 3 on whatever the verdict. `displayxr-cli camera trust <exe>` over a stored Deny clears the Deny and says so (the user's newer explicit word, like `allow` over `deny`); an installer's machine-wide registration never touches the user's store. `camera deny` on a delegating exe notes that the Deny wins. Explicit <exe> arguments are stored the way the service sees the peer: resolved with realpath on POSIX; on Windows read from the UTF-16 command line (argv is the ANSI code page) and made absolute with GetFullPathNameW. Windows store reader (u_camera_consent_store.c), both bugs verified: - it used the ANSI registry API while the runtime compares UTF-8 paths (QueryFullProcessImageNameW -> CP_UTF8), so a non-ASCII install path could never match an installer's UTF-16 entry. Every read, write, delete and enumeration (HKLM and HKCU, Apps, Delegating, Sharing, Secret, the ConsentStore webcam keys) now uses the wide API with explicit UTF-8 conversion. - fixed 256-char name / 1024-byte data buffers turned one oversized value into ERROR_MORE_DATA and `break`, hiding every later entry. Buffers are now sized from RegQueryInfoKeyW; an entry that still does not fit, has the wrong type or does not convert is skipped, never ends the scan. The matching is the pure u_camera_consent_list_find() over an abstract enumerator plus u_camera_consent_path_equal(), both unit-tested on every OS. Tests: the delegation-first assertions are rewritten; new cases for delegating + OS switch off, + stored Deny, + sharing off / kill switch, + nothing stored (allowed, no prompt, nothing written), unregistered neighbour unchanged, path rules, and skip-not-stop enumeration. Mutation: restoring the old order fails 2 cases / 7 assertions; making the list scan stop on a skipped entry fails the enumeration case. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase R3 of the stereo camera source (ADR-043): consent and privacy, vendor-neutral — the service enforces it, the plug-in never sees it. Stacked on #1747 (R2), which stacks on #1746 (R1); this PR's own commits start at
2146e1231.What is implemented
CAMERA_CONSUMER(value 6), declared withXrStereoCameraClientInfoDXR+XR_STEREO_CAMERA_CLIENT_CONSUMER_ONLY_BIT_DXRonXrInstanceCreateInfo. Verified by use (camera handlers only,xrCreateSessionrefused), quota 4, never counted toward the PRESENT_OWNER quota — the owner-counting rule is now the pureu_client_class_present_owner_count()(tested). The tester's sibling rule for the browser's other processes is kept. A hybrid runtime always routes a declared consumer to the service.u_camera_consent.{h,c},u_camera_consent_store.c): sharing off → dev override → no identity → registered delegating client (HKLM / HKCUCameraConsent\Delegating,/etc/displayxr/camera-delegating.json,/Library/Application Support/DisplayXR/…, ordisplayxr-cli camera trust) → WindowsConsentStore\webcam→ stored Allow/Deny (HKCU\Software\DisplayXR\CameraConsent\Apps,camera_consent.json0600) → Allow-once (per pid) → tray prompt " wants to use the 3D camera — Allow / Allow once / Deny" (blocks the start ≤ 60 s; unanswered = refused, retryable).DXR_STEREO_CAMERA_DEV_ALLOW=1stays as the documented, logged dev override;DXR_STEREO_CAMERA=0the kill switch; newDXR_STEREO_CAMERA_PROMPT=0for headless services.1004999311–316):XR_ERROR_STEREO_CAMERA_CONSENT_REFUSED_DXR(−1004999312, →NotAllowedError),_DISABLED_DXR(−…313),_BUSY_DXR(−…314),_STREAM_ENDED_DXR(−…316);XR_TYPE_STEREO_CAMERA_CLIENT_INFO_DXR(…311),XR_TYPE_EVENT_DATA_STEREO_CAMERA_STREAM_ENDED_DXR(…315). xrt side:XRT_ERROR_STEREO_CAMERA_*−45…−48.PERMISSION_INSUFFICIENTnow means the OS switch / wrong class.EnumWindows/NSRunningApplication; Linux always visible); delegating / CAMERA_CONSUMER / DIAG exempt. Every stream suspends on session lock (WindowsWM_WTSSESSION_CHANGE+SM_REMOTESESSION; macOSscreenIsLocked+sessionDidResignActive);camera fake-lockfor tests.stereo_camera_poll_event) drained fromxrPollEvent—StateChanged(effective state: SUSPENDED while locked / sharing off) and the newStreamEnded(handle + reason).STREAM_ENDED); "Share the 3D camera with apps" persistent toggle (zero cameras,DISABLED).persistentId: HMAC-SHA-256 under a per-user 32-byte secret kept in the store (compactu_sha256, FIPS / RFC 4231 vectors).ipc_shmem_create_with_readonly: FILE_MAP_READ dup / O_RDONLY re-open before unlink /ASharedMemory_setProt).camera consent | allow | deny | revoke | trust | untrust (<exe>|--self)(local),status | stop-all | sharing on|off | fake-lock on|off(IPC, DIAG);probeprints events and exits 4/6/7/8.docs/guides/stereo-camera-consent.md, ADR-043 Amendment 1.Evidence (macOS, sim fake, headless prompt)
tests_camera_consent: 105 assertions / 11 cases;tests_stereo_camera,tests_ipc_protogreen; MinGWaux_util(registry + bcrypt store) compiles. End to end withSIM_DISPLAY_FAKE_STEREO_CAMERA=1 DXR_STEREO_CAMERA_PROMPT=0:Needs the Windows box
service_tray_win.c: badge icon, consent dialog, WTS lock/unlock,SM_REMOTESESSION; theConsentStore\webcamread against the real Settings switch; manual matrix allow / deny / revoke / OS switch off / lock screen / background app.ipc_server_stereo_camera.cWindows branches (EnumWindowsforeground rule, FILE_MAP_READ section).Follow-ups
XrStereoCameraClientInfoDXR(CONSUMER_ONLY) on the capture utility's instance instead of enablingXR_DXR_weaveto be classed as the browser; mapCONSENT_REFUSED→NotAllowedError,BUSY→NotReadableError,DISABLED→ hide the device; handleXrEventDataStereoCameraStreamEndedDXRas track end.chrome.exeunderHKLM\Software\DisplayXR\CameraConsent\Delegating(REG_SZ data = full path).🤖 Generated with Claude Code