Skip to content

UI Freeze: Area selection overlay triggers unexpectedly and locks the screen when exiting Settings in Instant Mode #1945

Closed as not planned

Description

@wubie052

Description

When I select the area recording option in the Cap UI, switch to Instant mode, and then open the Settings menu, a logical bug occurs when I try to exit Settings. The system immediately triggers the area selection overlay, even though I haven't clicked 'Start Recording' or the 'Area' button.
At this point, the selection interface freezes, making the computer appear completely unresponsive. My entire desktop is covered by an invisible, semi-transparent overlay. All mouse interactions and most keyboard inputs are completely intercepted. I can't see anything, and right-clicking anywhere brings up the Cap context menu. The only way to recover is to force-kill the application or click 'Refresh' in the Cap context menu.

Reproduction

  1. Select the area recording option in the Cap UI.
  2. Switch to Instant mode.
  3. Open the Settings menu, and then exit it.
  4. Observe the screen after exiting the Settings menu.

Additional Context

  • Cap version:0.5.1
  • Operating system, version:Windows 11 - 10.0.26200
  • Device (optional): Dual monitors with identical resolution and size(HKC Display)

Activity

  1. linear commented on Jun 25, 2026

    @linear
  2. tembo commented on Jun 25, 2026

    @tembo

    The root cause is in the Settings window close handler at apps/desktop/src-tauri/src/lib.rs:5138-5162. When the Settings window is destroyed, it unconditionally calls show_overlay() for any existing TargetSelectOverlay windows:

    CapWindowId::Settings => {
        for (label, window) in app.webview_windows() {
            if let Ok(id) = CapWindowId::from_str(&label) {
                match id {
                    CapWindowId::TargetSelectOverlay { .. } => {
                        show_overlay(&window);  // Shows overlay regardless of current targetMode
                    }
                    // ...
                }
            }
        }
    }

    The show_overlay() function (windows.rs:108-111) enables cursor events and shows the window:

    pub fn show_overlay(window: &WebviewWindow) {
        let _ = window.set_ignore_cursor_events(false);
        let _ = window.show();
    }

    The issue is that the overlay window may still exist from when the user selected area mode, but the frontend targetMode state has changed. When Settings closes, the backend restores the overlay without checking whether the frontend actually wants it displayed.

    A fix would need to either:

    1. Check the current targetMode from RecordingSettingsStore before calling show_overlay() and only show if targetMode is still "area"
    2. Or emit a RequestSetTargetMode event with the current stored target mode so the frontend can decide whether to show the overlay
    3. Or close the overlay windows when switching away from area mode, rather than just hiding them

    This is similar to the pattern used for the Main window restoration in the same handler, where it properly coordinates with restore_main_window_inputs(). The overlay restoration needs similar coordination with the frontend state.

    Related: #1915 discusses area picker issues on Windows with multi-monitor setups, and PR #1412 covers the Settings window close behavior.

  3. wubie052 commented on Jun 25, 2026

    @wubie052
    Author

    Yes,That's definitely the exact same issue. I'm also running Windows on a dual-monitor setup and selected 'Area Mode'
    Hardware: Dual monitors with identical resolution and size(HKC Display)

  4. wubie052 commented on Jun 25, 2026

    @wubie052
    Author

    Also, just to add to this: the bug is also triggered when returning from the Settings window while in Studio mode, or when Display mode is selected.

  5. DPS0340 commented on Jul 25, 2026

    @DPS0340

    I traced this one through the Rust side and I think the mechanism is narrower than "the Settings close handler unconditionally restores overlays" — there's a specific state-lifetime bug. Posting the trace since I can't build the desktop crate in my environment to verify a patch (the build script needs the cap-muxer/cap-exporter sidecars and Spacedrive.framework, which aren't checked in).

    The path

    1. Opening Settings runs hide_recording_windows(app, true) (windows.rs:1403).
    2. With restore_target_select_overlays = true, that records every currently visible overlay into a restore set (windows.rs:158-166 → remember_overlay_for_restore), then hides them.
    3. Closing Settings hits CapWindowId::Settings => restore_main_and_target_select_windows(app) (lib.rs:5722).
    4. That drains the set via take_overlay_restore_labels() and calls show_overlay(&window) for every label in it — lib.rs:6163-6169:
    CapWindowId::TargetSelectOverlay { .. } => {
        if window.is_visible().unwrap_or(false)
            || target_select_overlays_hidden_for_restore.contains(label.as_str())
        {
            show_overlay(&window);
        }
    }

    Note the ||: a label in the restore set is shown regardless of whether the window is currently visible.

    Where it goes wrong

    The restore set is written when Settings opens and only read when Settings closes. Nothing invalidates it in between. set_recording_mode (recording_settings.rs:92-96) is the whole command:

    pub fn set_recording_mode(app: AppHandle, mode: RecordingMode) -> Result<(), String> {
        RecordingSettingsStore::set_mode(&app, mode)?;
        tray::update_tray_icon_for_mode(&app, mode);
        Ok(())
    }

    It persists the mode and repaints the tray. It does not touch WindowFocusManager, so a stale "restore these overlays" instruction from the previous mode survives the switch and fires on Settings close. That matches the reported repro exactly — pick Area, switch to Instant, open Settings, close it, and an overlay you never asked for appears.

    It also explains @wubie052's follow-up that Studio and Display mode trigger it too: the trigger isn't a specific mode, it's any mode change between the remember and the restore.

    Worth noting close_target_select_overlay_windows already calls clear_overlay_restore_labels() (target_select_overlay.rs:322) — so the invalidation primitive exists and is used on the explicit-close path. The mode-change path just doesn't call it.

    Why it locks the screen rather than showing a visible overlay

    show_overlay brings back a full-screen transparent window that intercepts input. In Instant mode the overlay UI has no reason to render its selection chrome, so the user gets an invisible click-eating surface — which is the "screen is frozen, right-click shows the Cap menu" symptom.

    Suggested fix

    Clearing the pending restore in set_recording_mode looks like the minimal correct change, reusing the existing primitive:

    if let Some(focus_manager) = app.try_state::<target_select_overlay::WindowFocusManager>() {
        focus_manager.clear_overlay_restore_labels();
    }

    I wrote that and it's a two-line change, but I don't want to open a PR I can't compile — the desktop crate needs prebuilt sidecars I don't have. If a maintainer wants it as a PR anyway I'm happy to push it, or it's small enough to just apply directly.

    One thing I'd want checked by someone who can run it: whether the restore should be cleared on mode change specifically, or whether the || in restore_main_and_target_select_windows should additionally require the overlay to still be relevant to the current mode. The former is minimal; the latter is more defensive if there are other paths that can stale the set.

  6. richiemcilroy commented on Oct 20, 2026

    @richiemcilroy
    Member

    Thanks for reporting this! This issue is in our old Tauri-based desktop app, which we've now fully replaced with a new native app (built on GPUI) from Cap 0.6.1 on macOS, Windows and Linux. We hope it's a much better experience for you. If you still see this problem in 0.6.1 or later, reply here and we'll reopen it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions