diff --git a/crates/komorebi-testwin/Cargo.toml b/crates/komorebi-testwin/Cargo.toml index fb7443f5..c85f3fcb 100644 --- a/crates/komorebi-testwin/Cargo.toml +++ b/crates/komorebi-testwin/Cargo.toml @@ -23,6 +23,9 @@ objc2-app-kit = { version = "0.3", default-features = false, features = [ "NSScreen", "NSGraphics", "NSEvent", + "NSAccessibility", + "NSAccessibilityConstants", + "NSAccessibilityProtocols", "NSRunningApplication", "objc2-core-foundation", "libc", diff --git a/crates/komorebi-testwin/src/appkit.rs b/crates/komorebi-testwin/src/appkit.rs index 2a906b3d..94d57d39 100644 --- a/crates/komorebi-testwin/src/appkit.rs +++ b/crates/komorebi-testwin/src/appkit.rs @@ -15,6 +15,7 @@ use std::time::Instant; use objc2::MainThreadMarker; use objc2::MainThreadOnly; use objc2::rc::Retained; +use objc2_app_kit::NSAccessibility; use objc2_app_kit::NSApplication; use objc2_app_kit::NSApplicationActivationPolicy; use objc2_app_kit::NSBackingStoreType; @@ -154,6 +155,11 @@ pub fn run(options: cli::Options) { // SAFETY: this program owns the window, so a close does not release it a second time. unsafe { window.setReleasedWhenClosed(false) }; window.setTitle(&NSString::from_str(&options.title)); + if let Some(class) = &options.class { + // a macOS window has no class: the daemon takes the accessibility subrole of a window for + // it, which a program is free to name as it likes + window.setAccessibilitySubrole(Some(&NSString::from_str(class))); + } if let Some(rgb) = options.color { window.setBackgroundColor(Some(&color(rgb))); } diff --git a/crates/komorebi-testwin/src/cli.rs b/crates/komorebi-testwin/src/cli.rs index 02273005..02882501 100644 --- a/crates/komorebi-testwin/src/cli.rs +++ b/crates/komorebi-testwin/src/cli.rs @@ -9,7 +9,8 @@ pub struct Options { pub title: String, /// The size of the window, in pixels (points on macOS). pub size: (i32, i32), - /// The window class on Windows; no other platform has one, and ignores it. + /// The window class on Windows; on macOS, which has none, the accessibility subrole of the + /// window, which is what the daemon takes for one; no other platform has one, and ignores it. pub class: Option, /// `0xRRGGBB`. pub color: Option, diff --git a/e2e/platforms/tart_macos/macos_platform.py b/e2e/platforms/tart_macos/macos_platform.py index 62791941..ff014fa0 100644 --- a/e2e/platforms/tart_macos/macos_platform.py +++ b/e2e/platforms/tart_macos/macos_platform.py @@ -254,8 +254,11 @@ class MacosPlatform: rename_to: str | None = None, after: float = 0, ) -> Client: - # `app_id` is a window class, which a macOS window does not have: it is not passed args = ["--title", title] + if app_id is not None: + # a macOS window has no class: the daemon takes its accessibility subrole for one, which + # the test window is given as this + args += ["--class", app_id] if rename_to is not None: # the window gives itself this title `after` seconds from now args += ["--rename-to", rename_to, "--after", str(after)]