Skip to content

Commit ba00f3a

Browse files
committed
Failed attempt to fix window tracking on macOS
1 parent 4bd0493 commit ba00f3a

File tree

9 files changed

+187
-58
lines changed

9 files changed

+187
-58
lines changed

Cargo.lock

Lines changed: 16 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ objc2-foundation = { version = "0.3.1", features = [
8787
"NSGeometry",
8888
"NSEnumerator",
8989
] }
90-
objc2-core-graphics = { version = "0.3.1", features = ["CGWindow"] }
90+
core-graphics = "0.25.0"
9191
accessibility = "0.2.0"
9292
accessibility-sys = "0.2.0"
9393
core-foundation = "0.10.1"

rust/client/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ smithay-client-toolkit.workspace = true
3939
objc2.workspace = true
4040
objc2-app-kit.workspace = true
4141
objc2-foundation.workspace = true
42-
objc2-core-graphics.workspace = true
42+
core-graphics.workspace = true
4343
accessibility.workspace = true
4444
accessibility-sys.workspace = true
4545
core-foundation.workspace = true

rust/client/src/ui/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,7 @@ fn update(state: &mut AppModel, message: AppMsg) -> Task<AppMsg> {
13071307
}
13081308
}
13091309
AppMsg::WindowTrackingMacosFocusWindow { window_uuid } => {
1310+
#[cfg(target_os = "macos")]
13101311
state.macos_window_tracker.focus_window(window_uuid);
13111312
Task::none()
13121313
}

rust/client/src/ui/window_tracking/macos/apps.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ impl ApplicationNotificationDelegate {
151151
return Err(anyhow::anyhow!("No window with uuid: {}", window_uuid));
152152
};
153153

154+
println!("Focusing window: {}, {:?}, {}", window_uuid, window, pid);
155+
154156
make_key_window(*pid, window).context("Failed to make window key")?;
155157

156158
// some apps seem to also require additional raise action

rust/client/src/ui/window_tracking/macos/mod.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use core_foundation::boolean::CFBoolean;
1111
use core_foundation::dictionary::CFDictionary;
1212
use core_foundation::string::CFString;
1313

14-
// see https://github.com/jkelleyrtp/kauresel/tree/master for a massive collection of related links
1514

1615
pub fn request_macos_accessibility_permissions() -> bool {
1716
let options = CFDictionary::from_CFType_pairs(&[(
@@ -22,8 +21,47 @@ pub fn request_macos_accessibility_permissions() -> bool {
2221
unsafe { AXIsProcessTrustedWithOptions(options.as_concrete_TypeRef()) }
2322
}
2423

24+
// todo
25+
// on each ax notification + non-ax destroy event
26+
// get app pid using AXUIElementGetPid
27+
// run bruteforce search (+ regular) for axuielement using _AXUIElementCreateWithRemoteToken
28+
// from each found window axuielement
29+
// filter based on window role/subrole
30+
// get window using _AXUIElementGetWindow
31+
// get title
32+
// to focus use window id and private api
33+
34+
35+
// do not support hidden windows
36+
// do not support multiple "desktop" spaces unless they are all visible
37+
// what about multiple fullscreen windows of the same app?
38+
// what about tabs in visible apps in non-visible spaces?
39+
// i.e., only non-hidden windows in visible spaces and maybe(?) fullscreen apps
40+
// support minimized windows but only on visible spaces
41+
42+
// support fullscreen applications?
43+
// support tabs on the visible windows in visible spaces only
44+
45+
// I think ignoring existence of spaces is fine???
46+
47+
// is the private function for focusing a window needed?
48+
2549
// todo support for windows on separate spaces
50+
// todo multiple desktop spaces ("Desktop" vs. "Desktop 1" and "Desktop 2")
2651
// todo sometimes the window state seems to be lost, clearing the list of windows
2752
// todo when starting the gauntlet, tabbed windows only show single one
2853
// todo implement this https://github.com/glide-wm/glide/issues/10
2954
// todo how to handle system apps and settings wrt window tracking?
55+
// todo add all github issue links and appreciations to the commit message
56+
// https://github.com/Hammerspoon/hammerspoon/issues/370#issuecomment-545545468
57+
// https://github.com/lwouis/alt-tab-macos/issues/1540#issuecomment-1138579049
58+
// https://github.com/lwouis/alt-tab-macos/issues/3589#issuecomment-2422002210
59+
// https://github.com/lwouis/alt-tab-macos/issues/258#issuecomment-624753940
60+
// https://github.com/jkelleyrtp/kauresel/tree/master
61+
// https://github.com/koekeishiya/yabai/issues/68
62+
// https://github.com/koekeishiya/yabai/commit/6f9006dd957100ec13096d187a8865e85a164a9b#r148091577
63+
// https://github.com/koekeishiya/yabai/issues/68
64+
// https://github.com/koekeishiya/yabai/issues/199#issuecomment-519152388
65+
// https://github.com/lwouis/alt-tab-macos/issues/1324#issuecomment-2631035482
66+
67+

rust/client/src/ui/window_tracking/macos/sys.rs

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
use accessibility::AXUIElement;
2+
use accessibility::AXUIElementAttributes;
23
use accessibility_sys::AXError;
34
use accessibility_sys::AXObserverGetTypeID;
45
use accessibility_sys::AXObserverRef;
56
use accessibility_sys::AXUIElementRef;
67
use accessibility_sys::kAXErrorSuccess;
8+
use accessibility_sys::kAXWindowRole;
79
use accessibility_sys::pid_t;
810
use anyhow::Context;
911
use anyhow::anyhow;
12+
use core_foundation::base::CFIndexConvertible;
1013
use core_foundation::base::OSStatus;
1114
use core_foundation::base::TCFType;
15+
use core_foundation::data::CFData;
16+
use core_foundation::data::CFDataCreate;
17+
use core_foundation::data::CFDataRef;
1218
use core_foundation::declare_TCFType;
1319
use core_foundation::impl_CFTypeDescription;
1420
use core_foundation::impl_TCFType;
15-
use objc2_core_graphics::CGError;
16-
use objc2_core_graphics::CGWindowID;
21+
use core_graphics::base::kCGErrorSuccess;
22+
use core_graphics::display::CGError;
23+
use core_graphics::window::CGWindowID;
1724

1825
declare_TCFType!(AXObserver, AXObserverRef);
1926
impl_TCFType!(AXObserver, AXObserverRef, AXObserverGetTypeID);
@@ -25,6 +32,13 @@ unsafe extern "C" {
2532
fn GetProcessForPID(pid: pid_t, psn: *mut ProcessSerialNumber) -> OSStatus;
2633
}
2734

35+
#[link(name = "SkyLight", kind = "framework")] // PrivateFrameworks included in build.rs
36+
unsafe extern "C" {
37+
fn _SLPSSetFrontProcessWithOptions(psn: *const ProcessSerialNumber, wid: CGWindowID, mode: u32) -> CGError;
38+
fn SLPSPostEventRecordTo(psn: *const ProcessSerialNumber, bytes: *const u8) -> CGError;
39+
pub fn _AXUIElementCreateWithRemoteToken(data: CFDataRef) -> AXUIElementRef;
40+
}
41+
2842
#[repr(C)]
2943
#[derive(Default)]
3044
struct ProcessSerialNumber {
@@ -47,11 +61,9 @@ pub fn make_key_window(pid: pid_t, window: &AXUIElement) -> anyhow::Result<()> {
4761
// See https://github.com/Hammerspoon/hammerspoon/issues/370#issuecomment-545545468.
4862
// god bless all the wizards in that thread that worked on it, thank you
4963

50-
let mut window_id = 0;
51-
let res = unsafe { _AXUIElementGetWindow(window.as_concrete_TypeRef(), &mut window_id) };
52-
if res != kAXErrorSuccess {
53-
return Err(accessibility::Error::Ax(res)).context(format!("Failed to get window id for element {:?}", window));
54-
}
64+
let window_id = ax_window_id(window)?;
65+
66+
println!("window id: {}", window_id);
5567

5668
#[allow(non_upper_case_globals)]
5769
const kCPSUserGenerated: u32 = 0x200;
@@ -69,7 +81,7 @@ pub fn make_key_window(pid: pid_t, window: &AXUIElement) -> anyhow::Result<()> {
6981
let psn = ProcessSerialNumber::for_pid(pid)?;
7082

7183
let check = |err| {
72-
if err == CGError::Success {
84+
if err == kCGErrorSuccess {
7385
Ok(())
7486
} else {
7587
Err(anyhow::anyhow!("Cursed api failed: {:?}", err))
@@ -83,8 +95,46 @@ pub fn make_key_window(pid: pid_t, window: &AXUIElement) -> anyhow::Result<()> {
8395
Ok(())
8496
}
8597

86-
#[link(name = "SkyLight", kind = "framework")] // PrivateFrameworks included in build.rs
87-
unsafe extern "C" {
88-
fn _SLPSSetFrontProcessWithOptions(psn: *const ProcessSerialNumber, wid: u32, mode: u32) -> CGError;
89-
fn SLPSPostEventRecordTo(psn: *const ProcessSerialNumber, bytes: *const u8) -> CGError;
98+
pub fn bruteforce_windows_for_app(app_pid: pid_t) -> Vec<AXUIElement> {
99+
unsafe {
100+
let mut result = vec![];
101+
let mut data = [0; 0x14];
102+
103+
let app_pid = u32::to_ne_bytes(app_pid as u32);
104+
data[0..0x4].copy_from_slice(&app_pid);
105+
106+
let magic = u32::to_ne_bytes(0x636f636f);
107+
data[0x8..0xC].copy_from_slice(&magic);
108+
109+
for element_id in 0..0x7fffu64 {
110+
let mut data = data.clone();
111+
112+
let element_id = element_id.to_ne_bytes();
113+
data[0xC..0x14].copy_from_slice(&element_id);
114+
115+
let data_ref = CFDataCreate(std::ptr::null(), data.as_ptr(), data.len().to_CFIndex());
116+
let data_ref = CFData::wrap_under_create_rule(data_ref);
117+
let data_ref = data_ref.as_concrete_TypeRef();
118+
119+
let window = AXUIElement::wrap_under_create_rule(_AXUIElementCreateWithRemoteToken(data_ref));
120+
121+
let role = window.role().ok().map(|role| role.to_string());
122+
if role.as_deref() != Some(kAXWindowRole) {
123+
continue;
124+
}
125+
126+
result.push(window);
127+
}
128+
129+
result
130+
}
131+
}
132+
133+
pub fn ax_window_id(window: &AXUIElement) -> anyhow::Result<CGWindowID> {
134+
let mut window_id = 0;
135+
let res = unsafe { _AXUIElementGetWindow(window.as_concrete_TypeRef(), &mut window_id) };
136+
if res != kAXErrorSuccess {
137+
return Err(accessibility::Error::Ax(res)).context(format!("Failed to get window id for element {:?}", window));
138+
}
139+
Ok(window_id)
90140
}

0 commit comments

Comments
 (0)