diff --git a/apps/desktop-gpui/Cargo.lock b/apps/desktop-gpui/Cargo.lock index e189df4ea3..b7e12867d5 100644 --- a/apps/desktop-gpui/Cargo.lock +++ b/apps/desktop-gpui/Cargo.lock @@ -1532,6 +1532,7 @@ dependencies = [ "anyhow", "ashpd 0.11.1", "base64 0.22.1", + "block2 0.5.1", "bytes", "cap-audio", "cap-camera", diff --git a/apps/desktop-gpui/Cargo.toml b/apps/desktop-gpui/Cargo.toml index dc01411ceb..8f9008b984 100644 --- a/apps/desktop-gpui/Cargo.toml +++ b/apps/desktop-gpui/Cargo.toml @@ -210,10 +210,11 @@ x11rb = "0.13" objc2 = "0.5.2" # `NSVisualEffectView` is the pre-macOS-26 window-material fallback. Features # only -- the versions stay pinned, or a second objc2 universe gets built. -objc2-app-kit = { version = "0.2.2", features = ["NSAppearance", "NSWindow", "NSView", "NSResponder", "NSGraphics", "NSPasteboard", "NSVisualEffectView"] } +objc2-app-kit = { version = "0.2.2", features = ["NSAppearance", "NSWindow", "NSView", "NSResponder", "NSGraphics", "NSPasteboard", "NSVisualEffectView", "NSWorkspace", "NSAccessibility"] } # NSRect/NSPoint/NSSize for `setFrame:display:`. Already in the tree as # objc2-app-kit's own dependency, so this adds nothing to the build. -objc2-foundation = "0.2.2" +objc2-foundation = { version = "0.2.2", features = ["NSNotification", "NSOperation", "NSString", "block2"] } +block2 = "0.5.1" raw-window-handle = "0.6" core-foundation = "0.10" core-video = { version = "0.5.2", features = ["metal"] } diff --git a/apps/desktop-gpui/src/main.rs b/apps/desktop-gpui/src/main.rs index 066acdd740..0d2ba82f25 100644 --- a/apps/desktop-gpui/src/main.rs +++ b/apps/desktop-gpui/src/main.rs @@ -316,6 +316,7 @@ fn main() { // the path runs (`gpui/src/window.rs:5280-5296`). ui::bind_text_input_keys(cx); crate::theme::init(cx); + platform::follow_system_reduce_motion(cx); let session = RecordingSession::init(cx); crate::sign_in::SignIn::init(cx); diff --git a/apps/desktop-gpui/src/platform.rs b/apps/desktop-gpui/src/platform.rs index 8bf5a7ee58..6189355ea7 100644 --- a/apps/desktop-gpui/src/platform.rs +++ b/apps/desktop-gpui/src/platform.rs @@ -587,6 +587,15 @@ pub struct PanelBehavior { #[cfg(target_os = "macos")] pub use mac::*; +/// GPUI keeps its own Reduce Motion flag and never reads the OS setting, so +/// every animation ignored it until this fed it in. +pub fn follow_system_reduce_motion(cx: &mut gpui::App) { + #[cfg(target_os = "macos")] + mac::follow_system_reduce_motion(cx); + #[cfg(not(target_os = "macos"))] + let _ = cx; +} + #[cfg(target_os = "macos")] mod macos_exception_guard; @@ -595,6 +604,42 @@ mod macos_occlusion; #[cfg(target_os = "macos")] mod mac { + pub fn follow_system_reduce_motion(cx: &mut gpui::App) { + use objc2_app_kit::{ + NSWorkspace, NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification, + }; + use objc2_foundation::{NSNotification, NSOperationQueue}; + + let reduce_motion = || { + let reduce = + unsafe { NSWorkspace::sharedWorkspace().accessibilityDisplayShouldReduceMotion() }; + tracing::debug!(reduce, "system reduce motion"); + reduce + }; + cx.set_reduce_motion(reduce_motion()); + + let (changed, changes) = flume::unbounded::<()>(); + let block = block2::RcBlock::new(move |_: std::ptr::NonNull| { + let _ = changed.send(()); + }); + unsafe { + let center = NSWorkspace::sharedWorkspace().notificationCenter(); + let observer = center.addObserverForName_object_queue_usingBlock( + Some(NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification), + None, + Some(&NSOperationQueue::mainQueue()), + &block, + ); + std::mem::forget(observer); + } + cx.spawn(async move |cx| { + while changes.recv_async().await.is_ok() { + cx.update(|cx| cx.set_reduce_motion(reduce_motion())); + } + }) + .detach(); + } + use gpui::Window; use objc2::rc::Id; use objc2::runtime::{AnyObject, Sel};