Files
codex/codex-rs/tui/src/system_motion.rs
T
Ian MacLeod 78600239a1 Honor system reduced-motion preferences in the TUI (#44666)
## Why

The TUI should respect the host's accessibility preference even when `tui.animations` is enabled.

## What changed

- Read the system motion preference once at launch on macOS, Windows, and Linux. Changes to that preference require restarting the TUI.
- Suppress animations when reduced motion is requested without changing saved configuration. Preserve configured behavior when detection is unavailable, and limit the Linux portal query to 250 ms.
- Use the effective animation setting for voice controls and transcripts, and honor system reduced motion in the startup composer.

## Testing

Add tests for animation preference resolution and preservation of saved settings, plus composer snapshots without sparkles at widths of 40 and 80 columns.

GitOrigin-RevId: 3ea4e721ae23220a040a06123a27286a8c8f1c7c
2026-09-10 22:55:04 +00:00

84 lines
2.8 KiB
Rust

//! Reads the TUI host's accessibility preference once at launch.
//!
//! Detection never changes persisted configuration or consults the app server. An unavailable
//! preference preserves configured behavior; an explicit request for less motion suppresses all
//! effects governed by `tui.animations`. Restart the TUI after changing the OS preference.
use std::sync::OnceLock;
use crate::motion::MotionMode;
static SYSTEM_MOTION: OnceLock<MotionMode> = OnceLock::new();
pub(crate) async fn initialize() {
let mode = detect().await.unwrap_or(MotionMode::Animated);
let _ = SYSTEM_MOTION.set(mode);
}
pub(crate) fn mode() -> MotionMode {
SYSTEM_MOTION.get().copied().unwrap_or(MotionMode::Animated)
}
#[cfg(target_os = "macos")]
async fn detect() -> Option<MotionMode> {
let workspace = objc2_app_kit::NSWorkspace::sharedWorkspace();
Some(if workspace.accessibilityDisplayShouldReduceMotion() {
MotionMode::Reduced
} else {
MotionMode::Animated
})
}
#[cfg(windows)]
async fn detect() -> Option<MotionMode> {
use windows_sys::Win32::UI::WindowsAndMessaging::SPI_GETCLIENTAREAANIMATION;
use windows_sys::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;
let mut enabled: windows_sys::Win32::Foundation::BOOL = 0;
// SAFETY: SPI_GETCLIENTAREAANIMATION writes one BOOL to this valid, aligned pointer.
let success = unsafe {
SystemParametersInfoW(
SPI_GETCLIENTAREAANIMATION,
/*uiparam*/ 0,
(&mut enabled as *mut windows_sys::Win32::Foundation::BOOL).cast(),
/*fwinini*/ 0,
)
};
(success != 0).then(|| MotionMode::from_animations_enabled(enabled != 0))
}
#[cfg(target_os = "linux")]
async fn detect() -> Option<MotionMode> {
// A missing or unresponsive session bus must not hold up terminal startup.
tokio::time::timeout(std::time::Duration::from_millis(/*millis*/ 250), async {
let connection = zbus::Connection::session().await.ok()?;
let proxy = zbus::Proxy::new(
&connection,
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
)
.await
.ok()?;
let value: zbus::zvariant::OwnedValue = proxy
.call("ReadOne", &("org.freedesktop.appearance", "reduced-motion"))
.await
.ok()?;
let value = u32::try_from(value).ok()?;
// The portal specifies that unknown values mean no preference.
Some(if value == 1 {
MotionMode::Reduced
} else {
MotionMode::Animated
})
})
.await
.ok()
.flatten()
}
#[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
async fn detect() -> Option<MotionMode> {
None
}