mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-14 04:43:42 +01:00
refactor(mac): ax_registry, the id -> {element, observer, application} table
Step 11(ii), first part. New module ax_registry.rs instantiates
komorebi_backend::Registry (rules tested natively in 4c9094d7) with AxEntry
{element, observer, application with its pid}; lifetime and thread rules are
written at the top of the file: entries end on Window::drop of a dead window,
reaper reaping and application termination (wired in the next revisions);
nothing in it dispatches to main or takes the WM lock; removed entries are
dropped after the table's lock is released. Window gets entry() and
from_entry() to convert. MacosBackend drops its private HashMap<u32, Window>
and looks windows up here (register and forget delegate), so the compile-only
adapter now resolves an id the same way the daemon will.
BEHAVIOUR: none yet, nothing registers (the adapter is still unused by the
daemon). Verified: macOS fake-CC type-check (tools/fast-check) PASS. Not
verified: cannot run on Linux.
This commit is contained in:
1 parent
4c9094d776
commit
386c5a79c6
4 files changed
+132
-13
No files matched your search
@@ -0,0 +1,104 @@
|
||||
//! The id -> {AX element, pid, observer} table of step 11(ii): what lets anything act on a
|
||||
//! window from nothing but its `WindowId`.
|
||||
//!
|
||||
//! It is `komorebi_backend::Registry` (whose rules are tested natively: thread-safe, nothing of
|
||||
//! the platform runs under its lock, stale ids are `Error::WindowGone`) instantiated with
|
||||
//! [`AxEntry`].
|
||||
//!
|
||||
//! # Lifetime
|
||||
//!
|
||||
//! An entry is created by `Window::new` and by [`sync`] when a window's element or id changes
|
||||
//! (a native tab). It ends at every place the daemon learns of a death:
|
||||
//!
|
||||
//! - `Window::drop`, when the window is no longer alive ([`forget`]);
|
||||
//! - the reaper, when it reaps a window ([`forget`]);
|
||||
//! - an application terminating ([`forget_pid`], from `WindowManagerEvent::Destroy`).
|
||||
//!
|
||||
//! A window that is alive and never managed stays registered until it dies or its application
|
||||
//! quits: the entry only holds retained references that the `Window` clones held anyway.
|
||||
//!
|
||||
//! # Threads
|
||||
//!
|
||||
//! Callable from any thread. Nothing here dispatches to the main queue or takes the window
|
||||
//! manager's lock (`docs/dev/seam.md` section 3.3); a closure that runs on the main thread may
|
||||
//! call it. Handles removed from the table are dropped after the table's lock is released.
|
||||
|
||||
use crate::AccessibilityObserver;
|
||||
use crate::AccessibilityUiElement;
|
||||
use crate::application::Application;
|
||||
use crate::window::Window;
|
||||
use komorebi_backend::Registry;
|
||||
use komorebi_backend::Result;
|
||||
use komorebi_backend::WindowId;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// What it takes to act on one window: its element and observer, and its application (whose
|
||||
/// element and observer are the application's, and whose `process_id` is the pid).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AxEntry {
|
||||
/// The window's Accessibility element
|
||||
pub element: AccessibilityUiElement,
|
||||
/// The observer that reports the window's events
|
||||
pub observer: AccessibilityObserver,
|
||||
/// The application that owns the window
|
||||
pub application: Application,
|
||||
}
|
||||
|
||||
impl AxEntry {
|
||||
/// The pid of the owning application
|
||||
pub fn pid(&self) -> i32 {
|
||||
self.application.process_id
|
||||
}
|
||||
}
|
||||
|
||||
static WINDOWS: LazyLock<Registry<AxEntry>> = LazyLock::new(Registry::new);
|
||||
|
||||
/// Registers the window under its id, replacing what was registered under it.
|
||||
pub fn register(window: &Window) {
|
||||
let entry = window.entry();
|
||||
let pid = entry.pid();
|
||||
// the replaced entry is dropped here, with the table's lock released
|
||||
drop(WINDOWS.insert(WindowId::from_u32(window.id), pid, entry));
|
||||
}
|
||||
|
||||
/// A window changed its id or its element in place (a native tab that is now shown by another
|
||||
/// element): forget the old id and register the window as it is.
|
||||
pub fn sync(old_id: u32, window: &Window) {
|
||||
if old_id != window.id {
|
||||
forget(old_id);
|
||||
}
|
||||
|
||||
register(window);
|
||||
}
|
||||
|
||||
/// Forgets a window that is gone. A second call is not an error.
|
||||
pub fn forget(id: u32) {
|
||||
drop(WINDOWS.remove(WindowId::from_u32(id)));
|
||||
}
|
||||
|
||||
/// Forgets every window of a process that quit.
|
||||
pub fn forget_pid(pid: i32) {
|
||||
drop(WINDOWS.remove_pid(pid));
|
||||
}
|
||||
|
||||
/// The entry of a window, or `Error::WindowGone` if the id is stale.
|
||||
pub fn get(id: WindowId) -> Result<AxEntry> {
|
||||
WINDOWS.get(id)
|
||||
}
|
||||
|
||||
/// A `Window` made from the entry of the id, or `Error::WindowGone` if the id is stale.
|
||||
pub fn window(id: WindowId) -> Result<Window> {
|
||||
let entry = WINDOWS.get(id)?;
|
||||
// an id that does not fit a `CGWindowID` cannot have been registered
|
||||
let raw = id.to_u32().ok_or(komorebi_backend::Error::WindowGone(id))?;
|
||||
Ok(Window::from_entry(raw, entry))
|
||||
}
|
||||
|
||||
/// Every registered window, by id.
|
||||
pub fn windows() -> Vec<Window> {
|
||||
WINDOWS
|
||||
.snapshot()
|
||||
.into_iter()
|
||||
.filter_map(|(id, entry)| Some(Window::from_entry(id.to_u32()?, entry)))
|
||||
.collect()
|
||||
}
|
||||
@@ -54,6 +54,7 @@ pub mod animation;
|
||||
pub mod app_kit_notification_constants;
|
||||
pub mod application;
|
||||
pub mod arrival;
|
||||
pub mod ax_registry;
|
||||
pub mod border_manager;
|
||||
pub mod container;
|
||||
pub mod core;
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
//! `docs/dev/seam.md`, section 9, and commented where they are.
|
||||
//!
|
||||
//! The trait names a window by an id, and nothing on macOS acts from a bare id: a window is its
|
||||
//! Accessibility element. So the adapter owns the **id registry** of consequence (1): the daemon
|
||||
//! Accessibility element. So the backend looks windows up in the **id registry** of consequence
|
||||
//! (1), `ax_registry`: the daemon
|
||||
//! registers each `Window` it learns of ([`MacosBackend::register`]) and forgets it when it is
|
||||
//! gone, and every method looks the element up by id. An id the registry does not hold is a
|
||||
//! window that is gone.
|
||||
@@ -34,7 +35,6 @@ use komorebi_backend::WindowId;
|
||||
use komorebi_backend::WindowInfo;
|
||||
use objc2::MainThreadMarker;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// The shared [`Backend`] for macOS.
|
||||
///
|
||||
@@ -42,7 +42,6 @@ use std::collections::HashMap;
|
||||
/// opacity and no focus follows mouse (`docs/dev/seam.md` section 2.1), so those methods answer
|
||||
/// `Unsupported`.
|
||||
pub struct MacosBackend {
|
||||
windows: Mutex<HashMap<u32, Window>>,
|
||||
monitors: Mutex<Vec<MonitorInfo>>,
|
||||
hiding_position: WindowHidingPosition,
|
||||
batches: Mutex<Vec<BatchedUpdates>>,
|
||||
@@ -64,7 +63,6 @@ impl MacosBackend {
|
||||
/// the monitor's configuration, and the trait has no place for it.
|
||||
pub fn new(hiding_position: WindowHidingPosition) -> Self {
|
||||
Self {
|
||||
windows: Mutex::new(HashMap::new()),
|
||||
monitors: Mutex::new(Vec::new()),
|
||||
hiding_position,
|
||||
batches: Mutex::new(Vec::new()),
|
||||
@@ -73,12 +71,12 @@ impl MacosBackend {
|
||||
|
||||
/// Makes the window known to the backend, by its `CGWindowID`.
|
||||
pub fn register(&self, window: Window) {
|
||||
self.windows.lock().insert(window.id, window);
|
||||
crate::ax_registry::register(&window);
|
||||
}
|
||||
|
||||
/// Forgets a window that is gone.
|
||||
pub fn forget(&self, id: u32) {
|
||||
self.windows.lock().remove(&id);
|
||||
crate::ax_registry::forget(id);
|
||||
}
|
||||
|
||||
/// Reads the monitors from `NSScreen` into the cache that [`Backend::monitors`] answers
|
||||
@@ -102,9 +100,7 @@ impl MacosBackend {
|
||||
}
|
||||
|
||||
fn lookup(&self, id: WindowId) -> Result<Window> {
|
||||
id.to_u32()
|
||||
.and_then(|raw| self.windows.lock().get(&raw).cloned())
|
||||
.ok_or(Error::WindowGone(id))
|
||||
crate::ax_registry::window(id)
|
||||
}
|
||||
|
||||
/// Says why a call about a window failed. A failed write is how macOS finds out that a window
|
||||
@@ -131,9 +127,7 @@ impl MacosBackend {
|
||||
id: WindowId::from_u32(window.id),
|
||||
pid: window.application.process_id,
|
||||
// the Accessibility position and size, in points; a failure leaves the default
|
||||
rect: window.window_rect()
|
||||
.map(Rect::from)
|
||||
.unwrap_or_default(),
|
||||
rect: window.window_rect().map(Rect::from).unwrap_or_default(),
|
||||
minimized: window.minimized(),
|
||||
alive: true,
|
||||
title: window.title(),
|
||||
@@ -153,7 +147,7 @@ impl Backend for MacosBackend {
|
||||
// The registry is the only list there is: the CoreGraphics list joined with the
|
||||
// Accessibility elements by title lives inside `load_workspace_information`, which fills
|
||||
// a `WindowManager`, and the daemon registers what it finds
|
||||
let registered: Vec<_> = self.windows.lock().values().cloned().collect();
|
||||
let registered = crate::ax_registry::windows();
|
||||
let mut windows: Vec<_> = registered.iter().filter_map(Self::info).collect();
|
||||
windows.sort_by_key(|w| w.id);
|
||||
windows
|
||||
|
||||
@@ -569,6 +569,26 @@ impl Window {
|
||||
})
|
||||
}
|
||||
|
||||
/// The handles that act on this window, as the registry keeps them
|
||||
pub(crate) fn entry(&self) -> crate::ax_registry::AxEntry {
|
||||
crate::ax_registry::AxEntry {
|
||||
element: self.element.clone(),
|
||||
observer: self.observer.clone(),
|
||||
application: self.application.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A window made from a registry entry
|
||||
pub(crate) fn from_entry(id: u32, entry: crate::ax_registry::AxEntry) -> Self {
|
||||
Self {
|
||||
id,
|
||||
element: entry.element,
|
||||
application: entry.application,
|
||||
observer: entry.observer,
|
||||
details: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The frame query (`Backend::window_rect`): the Accessibility position and size, in points.
|
||||
pub fn window_rect(&self) -> Result<CGRect, AccessibilityError> {
|
||||
MacosApi::window_rect(&self.element)
|
||||
|
||||
Reference in new issue
Block a user