refactor(mac): register every Window in ax_registry and forget the dead

Step 11(ii), second part. BEHAVIOUR (memory and bookkeeping only; no AX call
is added anywhere, and no existing call reads the table yet except the
still-unused MacosBackend).

Window::new registers the window under its id. Entries end where the daemon
learns of a death: Window::drop when the window is no longer alive (next to
the InvalidWindow notification it already sends), reap_window in the reaper,
and WindowManagerEvent::Destroy for NSWorkspaceDidTerminateApplication
(forget_pid, next to enhanced_ui::forget: a new process may reuse the pid).
A live window that is never managed stays registered until it dies or its
application quits; the table holds retained references that the Window clones
held anyway. The native tab case (id and element changed in place) follows in
its own revision.

Verified: macOS fake-CC type-check (tools/fast-check) PASS. Not verified:
cannot run on Linux. Gate needed: the Mac hand check, see
docs/dev/macos-handcheck.md (open and close many windows and quit apps; the
daemon's memory must not grow; nothing else should look different).
This commit is contained in:
LGUG2Z committed 2026-10-31 10:10:38 -07:00
1 parent 386c5a79c6
commit f0b1a448d4
3 files changed
+14 -2

No files matched your search

@@ -745,6 +745,7 @@ impl WindowManager {
)
) {
enhanced_ui::forget(process_id);
crate::ax_registry::forget_pid(process_id);
}
// some apps like Discord in all of their relentless Electron slop stupidity
+3
View File
@@ -111,6 +111,9 @@ fn invalid_window_ids(
}
fn reap_window(wm: &mut WindowManager, window_id: u32) -> color_eyre::Result<()> {
// whatever else happens to the window, the id no longer names anything
crate::ax_registry::forget(window_id);
let mut should_update = false;
for monitor in wm.monitors_mut() {
for workspace in monitor.workspaces_mut() {
+10 -2
View File
@@ -476,6 +476,9 @@ impl Drop for Window {
// for now - hopefully it doesn't break anything else
reaper::send_notification(ReaperNotification::InvalidWindow(self.id));
// and the id no longer names anything
crate::ax_registry::forget(self.id);
// make sure the observer gets removed from any run loops
AccessibilityApi::invalidate_observer(&self.observer);
}
@@ -560,13 +563,18 @@ impl Window {
Some(window_observer_callback),
)?;
Ok(Self {
let window = Self {
id: AccessibilityApi::window_id(&element)?,
element: AccessibilityUiElement(element),
application,
observer: AccessibilityObserver(Some(observer)),
details: None,
})
};
// so that the window can be found again from its id alone
crate::ax_registry::register(&window);
Ok(window)
}
/// The handles that act on this window, as the registry keeps them