Show overlay on all Spaces and alongside fullscreen apps

Set canJoinAllSpaces + fullScreenAuxiliary on overlay windows so the
border renders regardless of which Space or fullscreen app is active.
In window mode, fall back to screen-edge border when Cursor isn't
frontmost to avoid a floating rectangle on other Spaces.

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-11 03:07:12 -04:00
parent 23fe6ac101
commit 6610919a58
3 changed files with 63 additions and 5 deletions

View File

@@ -357,12 +357,19 @@ class FlasherDaemon:
self._last_flash[workspace] = now
def _resolve_frames(self, window_frame: tuple) -> list[tuple]:
"""Return frame(s) based on flash_mode config."""
"""Return frame(s) based on flash_mode config.
In "window" mode, falls back to screen frame when Cursor is not
frontmost (e.g. different Space or behind fullscreen app) to avoid
drawing a floating rectangle at stale coordinates.
"""
mode = self.config.flash_mode
if mode == "allscreens":
return all_screen_frames()
if mode == "screen":
return [screen_frame_for_window(window_frame)]
if not is_cursor_frontmost():
return [screen_frame_for_window(window_frame)]
return [window_frame]
def _cleanup(self) -> None:

View File

@@ -11,6 +11,8 @@ from Cocoa import (
NSView,
NSBezierPath,
NSTimer,
NSWindowCollectionBehaviorCanJoinAllSpaces,
NSWindowCollectionBehaviorFullScreenAuxiliary,
)
from Foundation import NSInsetRect
@@ -161,6 +163,10 @@ class OverlayManager:
window.setOpaque_(False)
window.setBackgroundColor_(NSColor.clearColor())
window.setLevel_(2147483631)
window.setCollectionBehavior_(
NSWindowCollectionBehaviorCanJoinAllSpaces
| NSWindowCollectionBehaviorFullScreenAuxiliary
)
window.setIgnoresMouseEvents_(True)
window.setHasShadow_(False)

View File

@@ -122,6 +122,7 @@ class TestFlasherDaemon:
window = {"title": "my-project", "frame": ((0, 0), (800, 600))}
with patch("cursor_flasher.daemon.find_window_by_workspace", return_value=window), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=True), \
patch("cursor_flasher.daemon.play_alert"), \
patch(PATCH_APPEARANCE, return_value="light"):
daemon._handle_message(
@@ -132,6 +133,46 @@ class TestFlasherDaemon:
"/path", [((0, 0), (800, 600))], daemon.config.light.completed
)
def test_window_mode_falls_back_to_screen_when_cursor_not_frontmost(self):
"""Window mode falls back to screen frame when Cursor isn't frontmost."""
daemon = self._make_daemon(approval_delay=0.0, flash_mode="window")
window = {"title": "proj", "frame": ((0, 0), (800, 600))}
screen = ((0, 0), (1920, 1080))
daemon._handle_message(
json.dumps({"workspace": "/path", "event": "preToolUse", "tool": "Shell"}).encode()
)
with patch("cursor_flasher.daemon.find_window_by_workspace", return_value=window), \
patch("cursor_flasher.daemon.screen_frame_for_window", return_value=screen), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=False), \
patch("cursor_flasher.daemon.play_alert"), \
patch(PATCH_APPEARANCE, return_value="dark"):
daemon._check_pending()
daemon.overlay.add_pulse.assert_called_once_with(
"/path", [screen], daemon.config.dark.running
)
def test_stop_falls_back_to_screen_when_cursor_not_frontmost(self):
"""Stop flash in window mode falls back to screen when Cursor isn't frontmost."""
daemon = self._make_daemon(flash_mode="window")
window = {"title": "proj", "frame": ((0, 0), (800, 600))}
screen = ((0, 0), (1920, 1080))
with patch("cursor_flasher.daemon.find_window_by_workspace", return_value=window), \
patch("cursor_flasher.daemon.screen_frame_for_window", return_value=screen), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=False), \
patch("cursor_flasher.daemon.play_alert"), \
patch(PATCH_APPEARANCE, return_value="dark"):
daemon._handle_message(
json.dumps({"workspace": "/path", "event": "stop"}).encode()
)
daemon.overlay.add_flash.assert_called_once_with(
"/path", [screen], daemon.config.dark.completed
)
def test_allscreens_mode_uses_all_screens(self):
daemon = self._make_daemon(flash_mode="allscreens", approval_delay=0.0)
window = {"title": "my-project", "frame": ((0, 0), (800, 600))}
@@ -380,7 +421,7 @@ class TestFlasherDaemon:
)
with patch("cursor_flasher.daemon.find_window_by_workspace", return_value=window), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=False), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=True), \
patch("cursor_flasher.daemon.play_alert") as mock_alert, \
patch(PATCH_APPEARANCE, return_value="dark"):
daemon._check_pending()
@@ -395,6 +436,7 @@ class TestFlasherDaemon:
window = {"title": "proj", "frame": ((0, 0), (800, 600))}
with patch("cursor_flasher.daemon.find_window_by_workspace", return_value=window), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=True), \
patch("cursor_flasher.daemon.play_alert") as mock_alert, \
patch(PATCH_APPEARANCE, return_value="dark"):
daemon._handle_message(
@@ -411,6 +453,7 @@ class TestFlasherDaemon:
window = {"title": "proj", "frame": ((0, 0), (800, 600))}
with patch("cursor_flasher.daemon.find_window_by_workspace", return_value=window), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=True), \
patch("cursor_flasher.daemon.play_alert") as mock_alert, \
patch(PATCH_APPEARANCE, return_value="dark"):
daemon._handle_message(
@@ -434,7 +477,7 @@ class TestFlasherDaemon:
)
with patch("cursor_flasher.daemon.find_window_by_workspace", return_value=window), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=False), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=True), \
patch("cursor_flasher.daemon.play_alert"), \
patch(PATCH_APPEARANCE, return_value="dark"):
daemon._check_pending()
@@ -448,6 +491,7 @@ class TestFlasherDaemon:
daemon._active_pulses.clear()
with patch("cursor_flasher.daemon.find_window_by_workspace", return_value=window), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=True), \
patch("cursor_flasher.daemon.play_alert"), \
patch(PATCH_APPEARANCE, return_value="dark"):
daemon._handle_message(
@@ -475,7 +519,7 @@ class TestFlasherDaemon:
)
with patch("cursor_flasher.daemon.find_window_by_workspace", return_value=window), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=False), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=True), \
patch("cursor_flasher.daemon.play_alert"), \
patch(PATCH_APPEARANCE, return_value="light"):
daemon._check_pending()
@@ -500,7 +544,7 @@ class TestFlasherDaemon:
)
with patch("cursor_flasher.daemon.find_window_by_workspace", side_effect=[win_a, win_b]), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=False), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=True), \
patch("cursor_flasher.daemon.play_alert"), \
patch(PATCH_APPEARANCE, return_value="dark"):
daemon._check_pending()
@@ -533,6 +577,7 @@ class TestFlasherDaemon:
window = {"title": "project-a", "frame": ((0, 0), (800, 600))}
with patch("cursor_flasher.daemon.find_window_by_workspace", return_value=window), \
patch("cursor_flasher.daemon.is_cursor_frontmost", return_value=True), \
patch("cursor_flasher.daemon.play_alert"), \
patch(PATCH_APPEARANCE, return_value="dark"):
daemon._handle_message(