diff --git a/docs/spec/capture-session.md b/docs/spec/capture-session.md index a43accd6..386f87df 100644 --- a/docs/spec/capture-session.md +++ b/docs/spec/capture-session.md @@ -36,8 +36,8 @@ cross-platform architecture. - On macOS, Frozen mode may recognize text from the current frozen capture and copy the recognized text to the clipboard. - Cmd+S (macOS) / Ctrl+S saves the frozen PNG to disk. - `Esc` cancels capture. -- In Frozen mode, a loupe and toolbar are part of the floating HUD set and can still - interact with pointer movement and redraw. +- In Frozen mode, the toolbar remains part of the floating HUD set. The live loupe is + hidden after freeze and may be recreated only when a later live-mode transition needs it. - In Frozen mode, a dragged-region capture may be repositioned by dragging inside the bright selected area; width and height remain fixed and the moved rect stays on the current monitor. @@ -51,6 +51,10 @@ cross-platform architecture. 3. When the capture session overlay is visible, underlying desktop content MUST NOT be interactive. 4. The overlay background should be transparent and non-dimming by default. +4a. On macOS, rsnap overlay, HUD, loupe, frozen toolbar, and scroll preview windows MUST remain + externally capturable by system screenshot and screen-recording tools. Internal self-capture + correctness comes from rsnap's own exclusion filters and freeze handoff logic, not window + content protection. 5. In live mode, the overlay MUST show a HUD near the cursor with: - global cursor coordinates `x,y` - pixel color `rgb(r,g,b)` under the cursor @@ -62,6 +66,9 @@ cross-platform architecture. - `Space` -> copy the frozen cropped PNG (region/window/fullscreen) to the system clipboard, then exit - On macOS, the frozen toolbar may expose `Recognize Text`, which runs Apple Vision OCR on the current frozen capture, copies the recognized text to the clipboard, and exits - Cmd+S (macOS) / Ctrl+S -> save the frozen cropped PNG to disk, then exit + - On macOS, freeze may complete directly from a fresh live-stream snapshot only when the + live stream has complete self-capture exclusions. Otherwise live snapshots are preview-only + until authoritative capture completes - In Frozen mode, toolbar-driven annotations are part of the frozen capture state. Current annotation/edit tools are pointer, pen, arrow, text, mosaic, and spotlight; the pen-tool contract lives in `docs/spec/annotation-pen.md` @@ -198,9 +205,10 @@ Research and cross-platform notes live in: ## HUD/toolbar lifecycle -- All floating HUD windows are created at overlay start. -- In Frozen mode, loupe/toolbar visibility follows Tab + current mode state and - `show_frozen_capture` state. +- Overlay windows and the main HUD are created at overlay start. +- The loupe, frozen toolbar, and scroll preview windows may be created lazily when the + active mode first needs them. +- In Frozen mode, toolbar visibility follows current mode state and `show_frozen_capture`. ## Current non-goals diff --git a/packages/rsnap-overlay/src/overlay.rs b/packages/rsnap-overlay/src/overlay.rs index 22cca83f..63895994 100644 --- a/packages/rsnap-overlay/src/overlay.rs +++ b/packages/rsnap-overlay/src/overlay.rs @@ -241,6 +241,8 @@ type ScrollCaptureStartedHook = Arc; type Result = std::result::Result; +pub(crate) const CAPTURE_WINDOW_CONTENT_PROTECTION_ENABLED: bool = false; + #[cfg(target_os = "macos")] const KCG_HID_EVENT_TAP: u32 = 0; #[cfg(target_os = "macos")] diff --git a/packages/rsnap-overlay/src/overlay/rendering.rs b/packages/rsnap-overlay/src/overlay/rendering.rs index 5035f457..b30f7b25 100644 --- a/packages/rsnap-overlay/src/overlay/rendering.rs +++ b/packages/rsnap-overlay/src/overlay/rendering.rs @@ -12,6 +12,8 @@ use self::hud_rendering::LiveLoupeTexture; use self::hud_surface::{HudBg, HudBlurUniformRaw}; #[cfg(target_os = "macos")] use crate::overlay::MacOSOverlayCursorRectSupport; +#[cfg(target_os = "macos")] +use crate::overlay::macos_configure_hud_window; use crate::overlay::{ self, AcquiredSurfaceFrame, Adapter, AddressMode, Arc, BindGroupLayout, BindingResource, BindingType, BlendState, Buffer, BufferBindingType, BufferSize, BufferUsages, ClippedPrimitive, diff --git a/packages/rsnap-overlay/src/overlay/rendering/scroll_preview_window.rs b/packages/rsnap-overlay/src/overlay/rendering/scroll_preview_window.rs index 551f5bbd..b780ba68 100644 --- a/packages/rsnap-overlay/src/overlay/rendering/scroll_preview_window.rs +++ b/packages/rsnap-overlay/src/overlay/rendering/scroll_preview_window.rs @@ -1,7 +1,5 @@ use wgpu::SurfaceConfiguration; -#[cfg(target_os = "macos")] -use crate::overlay; use crate::overlay::rendering::{GpuContext, ScrollPreviewView, WindowRenderer}; use crate::overlay::{ AcquiredSurfaceFrame, ActiveEventLoop, Align, Arc, CentralPanel, Color32, ColorImage, @@ -33,7 +31,7 @@ impl ScrollPreviewWindow { .with_visible(false) .with_resizable(false) .with_decorations(false) - .with_content_protected(cfg!(target_os = "macos")) + .with_content_protected(super::overlay::CAPTURE_WINDOW_CONTENT_PROTECTION_ENABLED) .with_transparent(true) .with_inner_size(LogicalSize::new( SCROLL_PREVIEW_WINDOW_WIDTH_POINTS, @@ -84,7 +82,7 @@ impl ScrollPreviewWindow { let _ = window.set_cursor_hittest(false); #[cfg(target_os = "macos")] - overlay::macos_configure_hud_window(window.as_ref(), false, 0.0, Some(18.0)); + super::macos_configure_hud_window(window.as_ref(), false, 0.0, Some(18.0)); Ok(Self { window, diff --git a/packages/rsnap-overlay/src/overlay/tests/self_capture_runtime.rs b/packages/rsnap-overlay/src/overlay/tests/self_capture_runtime.rs index 011142a5..b9864096 100644 --- a/packages/rsnap-overlay/src/overlay/tests/self_capture_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/tests/self_capture_runtime.rs @@ -231,6 +231,13 @@ fn authoritative_freeze_capture_hides_overlay_windows_on_macos() { assert!(session.should_hide_overlay_windows_during_capture()); } +#[cfg(target_os = "macos")] +#[test] +#[allow(clippy::assertions_on_constants)] +fn capture_windows_are_not_content_protected_on_macos() { + assert!(!super::super::CAPTURE_WINDOW_CONTENT_PROTECTION_ENABLED); +} + #[cfg(target_os = "macos")] #[test] fn repeated_freeze_capture_send_full_aborts_and_restores_hidden_windows() { diff --git a/packages/rsnap-overlay/src/overlay/window_runtime.rs b/packages/rsnap-overlay/src/overlay/window_runtime.rs index fe0db6e9..2c96c1a4 100644 --- a/packages/rsnap-overlay/src/overlay/window_runtime.rs +++ b/packages/rsnap-overlay/src/overlay/window_runtime.rs @@ -628,7 +628,7 @@ impl OverlaySession { .with_title("rsnap-overlay") .with_decorations(false) .with_resizable(false) - .with_content_protected(cfg!(target_os = "macos")) + .with_content_protected(overlay::CAPTURE_WINDOW_CONTENT_PROTECTION_ENABLED) .with_transparent(true) .with_window_level(WindowLevel::AlwaysOnTop) .with_inner_size(LogicalSize::new( @@ -769,7 +769,7 @@ impl OverlaySession { .with_title("rsnap-hud") .with_decorations(false) .with_resizable(false) - .with_content_protected(cfg!(target_os = "macos")) + .with_content_protected(overlay::CAPTURE_WINDOW_CONTENT_PROTECTION_ENABLED) .with_transparent(true) .with_visible(false) .with_window_level(WindowLevel::AlwaysOnTop) @@ -803,7 +803,7 @@ impl OverlaySession { .with_title("rsnap-loupe") .with_decorations(false) .with_resizable(false) - .with_content_protected(cfg!(target_os = "macos")) + .with_content_protected(overlay::CAPTURE_WINDOW_CONTENT_PROTECTION_ENABLED) .with_transparent(true) .with_visible(false) .with_window_level(WindowLevel::AlwaysOnTop) @@ -840,7 +840,7 @@ impl OverlaySession { .with_title("rsnap-toolbar") .with_decorations(false) .with_resizable(false) - .with_content_protected(cfg!(target_os = "macos")) + .with_content_protected(overlay::CAPTURE_WINDOW_CONTENT_PROTECTION_ENABLED) .with_inner_size(LogicalSize::new( startup_size.x as f64, f64::from(startup_size.y.max(TOOLBAR_EXPANDED_HEIGHT_PX)),