Fix cursor style changes across windows (#55323)

We were storing a cursor hidden boolean in the window state, but that
state is actually global to the application.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable


Release Notes:

- N/A
This commit is contained in:
Agus Zubiaga 2026-04-30 16:19:27 +02:00 committed by GitHub
parent 9beeed084b
commit 1eba1ca72e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 67 additions and 20 deletions

View file

@ -52,7 +52,7 @@ use std::{
ptr,
rc::Rc,
slice, str,
sync::{Arc, OnceLock},
sync::{Arc, OnceLock, atomic::AtomicBool},
};
use util::{
ResultExt,
@ -179,6 +179,7 @@ pub(crate) struct MacPlatformState {
dock_menu: Option<id>,
menus: Option<Vec<OwnedMenu>>,
keyboard_mapper: Rc<MacKeyboardMapper>,
cursor_hidden: Arc<AtomicBool>,
}
impl MacPlatform {
@ -215,6 +216,7 @@ impl MacPlatform {
on_thermal_state_change: None,
menus: None,
keyboard_mapper,
cursor_hidden: Arc::new(AtomicBool::new(false)),
}))
}
@ -619,12 +621,22 @@ impl Platform for MacPlatform {
handle: AnyWindowHandle,
options: WindowParams,
) -> Result<Box<dyn PlatformWindow>> {
let renderer_context = self.0.lock().renderer_context.clone();
let (cursor_hidden, foreground_executor, background_executor, renderer_context) = {
let guard = self.0.lock();
(
guard.cursor_hidden.clone(),
guard.foreground_executor.clone(),
guard.background_executor.clone(),
guard.renderer_context.clone(),
)
};
Ok(Box::new(MacWindow::open(
handle,
options,
self.foreground_executor(),
self.background_executor(),
cursor_hidden,
foreground_executor,
background_executor,
renderer_context,
)))
}
@ -979,8 +991,9 @@ impl Platform for MacPlatform {
/// Match cursor style to one of the styles available
/// in macOS's [NSCursor](https://developer.apple.com/documentation/appkit/nscursor).
fn set_cursor_style(&self, style: CursorStyle) {
let cursor_hidden = self.0.lock().cursor_hidden.clone();
unsafe {
set_active_window_cursor_style(style);
set_active_window_cursor_style(style, &cursor_hidden);
}
}

View file

@ -324,17 +324,34 @@ pub(crate) fn convert_mouse_position(position: NSPoint, window_height: Pixels) -
/// This function is not thread safe. Callers must ensure this is called on the AppKit main
/// thread because it reads the active AppKit window and updates GPUI window state associated
/// with Objective-C objects.
pub(crate) unsafe fn set_active_window_cursor_style(style: CursorStyle) {
pub(crate) unsafe fn set_active_window_cursor_style(
style: CursorStyle,
cursor_hidden: &AtomicBool,
) {
// SAFETY: The caller guarantees AppKit main-thread access. The class check ensures the
// window has our WINDOW_STATE_IVAR before reading it.
unsafe {
let app = NSApplication::sharedApplication(nil);
let key_window: id = msg_send![app, keyWindow];
let main_window: id = msg_send![app, mainWindow];
if main_window.is_null() || !msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
return;
}
let active_window = if !key_window.is_null()
&& msg_send![key_window, isKindOfClass: WINDOW_CLASS]
{
Some(key_window)
} else if !main_window.is_null() && msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
Some(main_window)
} else {
None
};
let window_state = get_window_state(&*main_window);
let Some(active_window) = active_window else {
if !matches!(style, CursorStyle::None) {
unhide_cursor(cursor_hidden);
}
return;
};
let window_state = get_window_state(&*active_window);
let mut window_state = window_state.lock();
if window_state.cursor_style != style {
window_state.cursor_style = style;
@ -346,6 +363,22 @@ pub(crate) unsafe fn set_active_window_cursor_style(style: CursorStyle) {
}
}
/// Unhides the cursor if this GPUI platform instance has hidden it.
///
/// # Safety
///
/// Must be called on the AppKit main thread.
unsafe fn unhide_cursor(cursor_hidden: &AtomicBool) {
unsafe {
if cursor_hidden
.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
let _: () = msg_send![class!(NSCursor), unhide];
}
}
}
unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
unsafe {
let mut decl = ClassDecl::new(name, superclass).unwrap();
@ -463,7 +496,7 @@ struct MacWindowState {
blurred_view: Option<id>,
background_appearance: WindowBackgroundAppearance,
cursor_style: CursorStyle,
cursor_hidden: bool,
cursor_hidden: Arc<AtomicBool>,
display_link: Option<DisplayLink>,
renderer: renderer::Renderer,
request_frame_callback: Option<Box<dyn FnMut(RequestFrameOptions)>>,
@ -665,6 +698,7 @@ impl MacWindow {
tabbing_identifier,
..
}: WindowParams,
cursor_hidden: Arc<AtomicBool>,
foreground_executor: ForegroundExecutor,
background_executor: BackgroundExecutor,
renderer_context: renderer::Context,
@ -782,7 +816,7 @@ impl MacWindow {
blurred_view: None,
background_appearance: WindowBackgroundAppearance::Opaque,
cursor_style: CursorStyle::Arrow,
cursor_hidden: false,
cursor_hidden,
display_link: None,
renderer: renderer::new_renderer(
renderer_context,
@ -1820,18 +1854,21 @@ extern "C" fn reset_cursor_rects(this: &Object, _: Sel) {
let cursor_hidden;
{
let mut window_state = window_state.lock();
let window_state = window_state.lock();
if matches!(window_state.cursor_style, CursorStyle::None) {
if !window_state.cursor_hidden {
if window_state
.cursor_hidden
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
let _: () = msg_send![class!(NSCursor), hide];
window_state.cursor_hidden = true;
}
return;
}
cursor_style = window_state.cursor_style;
cursor_hidden = window_state.cursor_hidden;
cursor_hidden = window_state.cursor_hidden.clone();
};
let cursor: id = match cursor_style {
@ -1871,10 +1908,7 @@ extern "C" fn reset_cursor_rects(this: &Object, _: Sel) {
CursorStyle::None => unreachable!(),
};
if cursor_hidden {
let _: () = msg_send![class!(NSCursor), unhide];
window_state.lock().cursor_hidden = false;
}
unhide_cursor(&cursor_hidden);
let bounds = NSView::bounds(this as *const Object as id);
let _: () = msg_send![this, addCursorRect: bounds cursor: cursor];