Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog.d/11095-macos-window-appearance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### Fixed

- macOS windows now inherit the application's effective appearance, allowing
`NSRequiresAquaSystemAppearance` to keep an app in light mode while preserving
the normal system appearance behavior by default.
5 changes: 5 additions & 0 deletions crates/perry-ui-macos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,8 @@ harness = false
name = "native_button_image_size"
path = "tests/native_button_image_size.rs"
harness = false

[[test]]
name = "native_window_appearance"
path = "tests/native_window_appearance.rs"
harness = false
19 changes: 3 additions & 16 deletions crates/perry-ui-macos/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,9 @@ pub fn app_create(title_ptr: *const u8, width: f64, height: f64) -> i64 {
let ns_title = NSString::from_str(&title);
window.setTitle(&ns_title);

// Match window appearance to the system setting so native controls
// (NSTextField, NSPopUpButton, etc.) use the correct light/dark theme.
// isDarkMode() is called here (after NSApp exists) to get the correct value.
let is_dark = super::perry_system_is_dark_mode() != 0;
let appearance_name = if is_dark {
NSString::from_str("NSAppearanceNameDarkAqua")
} else {
NSString::from_str("NSAppearanceNameAqua")
};
let appearance_cls = objc2::runtime::AnyClass::get(c"NSAppearance").unwrap();
let appearance: *mut objc2::runtime::AnyObject = objc2::msg_send![
appearance_cls, appearanceNamed: &*appearance_name
];
if !appearance.is_null() {
let _: () = objc2::msg_send![&*window, setAppearance: appearance];
}
// Keep the window's appearance unset so it inherits NSApp's effective
// appearance. AppKit follows the system by default and honors an
// app-bundle `NSRequiresAquaSystemAppearance` override (#11092).

APPS.with(|a| {
let mut apps = a.borrow_mut();
Expand Down
43 changes: 43 additions & 0 deletions crates/perry-ui-macos/tests/native_window_appearance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// #11092 — a main window must leave its explicit appearance unset so it
// inherits NSApp.effectiveAppearance. This lets AppKit follow the system by
// default and honor NSRequiresAquaSystemAppearance from the app bundle.

#[cfg(target_os = "macos")]
fn main() {
use objc2::{msg_send, runtime::AnyObject};
use objc2_app_kit::NSApplication;
use objc2_foundation::MainThreadMarker;
use perry_runtime as _;

if std::env::args().any(|arg| arg == "--list") {
println!("native_window_appearance: test");
return;
}

let mtm = MainThreadMarker::new().expect("native window test runs on the main thread");
let app = NSApplication::sharedApplication(mtm);
let windows_before = app.windows().len();

perry_ui_macos::app::app_create(std::ptr::null(), 320.0, 200.0);

let windows = app.windows();
assert_eq!(
windows.len(),
windows_before + 1,
"app_create must register one NSWindow"
);
let window = windows
.iter()
.last()
.expect("app_create registered its NSWindow");
let appearance: *mut AnyObject = unsafe { msg_send![&**window, appearance] };
assert!(
appearance.is_null(),
"the main window must inherit NSApp.effectiveAppearance"
);

println!("PASS native window appearance inherits NSApp");
}

#[cfg(not(target_os = "macos"))]
fn main() {}
Loading