pub(super) mod ax;
use std::collections::{HashMap, HashSet};
use serde::Serialize;
const MATCH_EPSILON: f64 = 1.0;
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub(super) struct Frame {
pub(super) x: f64,
pub(super) y: f64,
pub(super) width: f64,
pub(super) height: f64,
}
impl Frame {
fn matches(self, other: Self) -> bool {
(self.x - other.x).abs() <= MATCH_EPSILON
&& (self.y - other.y).abs() <= MATCH_EPSILON
&& (self.width - other.width).abs() <= MATCH_EPSILON
&& (self.height - other.height).abs() <= MATCH_EPSILON
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct OsWindow {
pub(super) title: String,
pub(super) frame: Frame,
pub(super) minimized: bool,
pub(super) fullscreen: bool,
pub(super) standard: bool,
pub(super) focused: bool,
}
#[derive(Debug, Clone)]
pub(super) struct RegisteredWindow {
pub(super) key: String,
pub(super) live: bool,
pub(super) title: Option<String>,
pub(super) pid: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) struct WindowId {
pub(super) app_pid: u32,
pub(super) index: usize,
}
#[derive(Debug, Clone)]
pub(super) struct PlannedMove {
pub(super) index: usize,
pub(super) key: String,
pub(super) title: String,
pub(super) window: WindowId,
pub(super) from: Frame,
}
#[derive(Debug, Clone, Serialize)]
pub(super) struct TargetResult {
#[serde(skip)]
pub(super) index: usize,
pub(super) key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub(super) title: Option<String>,
pub(super) outcome: &'static str,
pub(super) detail: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub(super) frame: Option<Frame>,
}
pub(super) const OUTCOME_MOVED: &str = "moved";
pub(super) const OUTCOME_WOULD_MOVE: &str = "would-move";
pub(super) const OUTCOME_UNCHANGED: &str = "unchanged";
pub(super) const OUTCOME_PARTIAL: &str = "partial";
pub(super) const OUTCOME_NO_WINDOW: &str = "no-window";
pub(super) const OUTCOME_NO_PID: &str = "no-pid";
pub(super) const OUTCOME_NO_TITLE: &str = "no-title";
pub(super) const OUTCOME_NOT_FOUND: &str = "not-found";
pub(super) const OUTCOME_AMBIGUOUS: &str = "ambiguous";
pub(super) const OUTCOME_MINIMIZED: &str = "minimized";
pub(super) const OUTCOME_FULLSCREEN: &str = "fullscreen";
pub(super) const OUTCOME_REFERENCE: &str = "reference";
pub(super) const OUTCOME_FAILED: &str = "failed";
impl TargetResult {
fn resolved(
planned: &PlannedMove,
outcome: &'static str,
detail: impl Into<String>,
frame: Option<Frame>,
) -> Self {
Self {
index: planned.index,
key: planned.key.clone(),
title: Some(planned.title.clone()),
outcome,
detail: detail.into(),
frame,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub(super) struct ResolvedReference {
pub(super) key: String,
pub(super) title: String,
pub(super) frame: Frame,
}
#[derive(Debug, Clone, Serialize)]
pub(super) struct Blocked {
pub(super) reason: &'static str,
pub(super) detail: String,
}
pub(super) const BLOCKED_UNIDENTIFIABLE: &str = "reference-unidentifiable";
pub(super) const BLOCKED_NOT_FOUND: &str = "reference-not-found";
pub(super) const BLOCKED_AMBIGUOUS: &str = "reference-ambiguous";
pub(super) const BLOCKED_FULLSCREEN: &str = "reference-fullscreen";
pub(super) const BLOCKED_MINIMIZED: &str = "reference-minimized";
pub(super) trait WindowBackend {
fn trusted(&self) -> bool;
fn app_pids(&self, pids: &[u32]) -> HashMap<u32, u32>;
fn windows(&self, app_pid: u32) -> Result<Vec<OsWindow>, String>;
fn set_frame(&self, id: WindowId, frame: Frame) -> Result<Frame, String>;
}
#[derive(Debug)]
pub(super) struct RepositionReport {
pub(super) trusted: bool,
pub(super) blocked: Option<Blocked>,
pub(super) reference: Option<ResolvedReference>,
pub(super) results: Vec<TargetResult>,
pub(super) undo: Vec<(String, Frame)>,
}
impl RepositionReport {
fn untrusted() -> Self {
Self {
trusted: false,
blocked: None,
reference: None,
results: Vec::new(),
undo: Vec::new(),
}
}
pub(super) fn moved(&self) -> usize {
self.results
.iter()
.filter(|r| r.outcome == OUTCOME_MOVED || r.outcome == OUTCOME_PARTIAL)
.count()
}
pub(super) fn skipped(&self) -> usize {
self.results.len() - self.moved()
}
}
pub(super) fn reposition(
backend: &dyn WindowBackend,
reference: &RegisteredWindow,
targets: &[RegisteredWindow],
check: bool,
) -> RepositionReport {
if !backend.trusted() {
return RepositionReport::untrusted();
}
let (windows, app_pids) = enumerate(backend, std::iter::once(reference).chain(targets));
let (reference_window, reference_info) = match resolve_reference(reference, &windows, &app_pids)
{
Ok(resolved) => resolved,
Err(blocked) => {
return RepositionReport {
trusted: true,
blocked: Some(blocked),
reference: None,
results: Vec::new(),
undo: Vec::new(),
}
}
};
let (moves, mut results) = plan_moves(targets, &windows, &app_pids, Some(reference_window));
let wanted = reference_info.frame;
let mut undo = Vec::new();
for planned in &moves {
results.push(apply(backend, planned, wanted, check, &mut undo));
}
RepositionReport {
trusted: true,
blocked: None,
reference: Some(reference_info),
results: in_request_order(results),
undo,
}
}
pub(super) fn restore(
backend: &dyn WindowBackend,
entries: &[(RegisteredWindow, Frame)],
) -> RepositionReport {
if !backend.trusted() {
return RepositionReport::untrusted();
}
let targets: Vec<RegisteredWindow> = entries.iter().map(|(w, _)| w.clone()).collect();
let (windows, app_pids) = enumerate(backend, targets.iter());
let (moves, mut results) = plan_moves(&targets, &windows, &app_pids, None);
let mut undo = Vec::new();
for planned in &moves {
let wanted = entries[planned.index].1;
results.push(apply(backend, planned, wanted, false, &mut undo));
}
RepositionReport {
trusted: true,
blocked: None,
reference: None,
results: in_request_order(results),
undo,
}
}
fn enumerate<'a>(
backend: &dyn WindowBackend,
registered: impl Iterator<Item = &'a RegisteredWindow>,
) -> (HashMap<u32, Vec<OsWindow>>, HashMap<u32, u32>) {
let pids: HashSet<u32> = registered.filter_map(|w| w.pid).collect();
let pids: Vec<u32> = pids.into_iter().collect();
let app_pids = backend.app_pids(&pids);
let mut windows = HashMap::new();
let apps: HashSet<u32> = app_pids.values().copied().collect();
for app_pid in apps {
match backend.windows(app_pid) {
Ok(list) => {
windows.insert(app_pid, list);
}
Err(err) => tracing::warn!(app_pid, "cannot enumerate windows: {err}"),
}
}
(windows, app_pids)
}
fn apply(
backend: &dyn WindowBackend,
planned: &PlannedMove,
wanted: Frame,
check: bool,
undo: &mut Vec<(String, Frame)>,
) -> TargetResult {
if planned.from.matches(wanted) {
return TargetResult::resolved(
planned,
OUTCOME_UNCHANGED,
"already in the wanted position",
Some(planned.from),
);
}
if check {
return TargetResult::resolved(
planned,
OUTCOME_WOULD_MOVE,
"resolved to one window; would be moved",
Some(planned.from),
);
}
let result = move_result(planned, wanted, backend.set_frame(planned.window, wanted));
if result.outcome == OUTCOME_MOVED || result.outcome == OUTCOME_PARTIAL {
undo.push((planned.key.clone(), planned.from));
}
result
}
fn resolve_reference(
reference: &RegisteredWindow,
windows: &HashMap<u32, Vec<OsWindow>>,
app_pids: &HashMap<u32, u32>,
) -> Result<(WindowId, ResolvedReference), Blocked> {
let title = reference.title.as_deref().filter(|t| !t.trim().is_empty());
let (true, Some(title), Some(pid)) = (reference.live, title, reference.pid) else {
return Err(Blocked {
reason: BLOCKED_UNIDENTIFIABLE,
detail: "the invoking window is not registered, or reported no name or process id"
.to_string(),
});
};
let Some(app_pid) = app_pids.get(&pid).copied() else {
return Err(Blocked {
reason: BLOCKED_UNIDENTIFIABLE,
detail: format!("cannot resolve the application owning process {pid}"),
});
};
let candidates = windows.get(&app_pid).map_or(&[][..], Vec::as_slice);
let index = match match_window(candidates, title, true) {
Match::One(index) => index,
Match::None => {
return Err(Blocked {
reason: BLOCKED_NOT_FOUND,
detail: format!("no window of the invoking application is named “{title}”"),
})
}
Match::Many(count) => {
return Err(Blocked {
reason: BLOCKED_AMBIGUOUS,
detail: format!("{count} windows match the invoking window's name “{title}”"),
})
}
};
let window = &candidates[index];
if window.fullscreen {
return Err(Blocked {
reason: BLOCKED_FULLSCREEN,
detail: "the invoking window is in fullscreen, so its frame is a whole display"
.to_string(),
});
}
if window.minimized {
return Err(Blocked {
reason: BLOCKED_MINIMIZED,
detail: "the invoking window is minimized, so it has no on-screen frame".to_string(),
});
}
Ok((
WindowId { app_pid, index },
ResolvedReference {
key: reference.key.clone(),
title: title.to_string(),
frame: window.frame,
},
))
}
fn plan_moves(
targets: &[RegisteredWindow],
windows: &HashMap<u32, Vec<OsWindow>>,
app_pids: &HashMap<u32, u32>,
reference_window: Option<WindowId>,
) -> (Vec<PlannedMove>, Vec<TargetResult>) {
let mut claimed: HashSet<WindowId> = reference_window.into_iter().collect();
let mut moves = Vec::new();
let mut skipped = Vec::new();
for (index, target) in targets.iter().enumerate() {
match plan_target(
index,
target,
windows,
app_pids,
reference_window,
&mut claimed,
) {
Resolution::Movable(planned) => moves.push(planned),
Resolution::Skipped(result) => skipped.push(result),
}
}
(moves, skipped)
}
enum Resolution {
Movable(PlannedMove),
Skipped(TargetResult),
}
fn plan_target(
index: usize,
target: &RegisteredWindow,
windows: &HashMap<u32, Vec<OsWindow>>,
app_pids: &HashMap<u32, u32>,
reference_window: Option<WindowId>,
claimed: &mut HashSet<WindowId>,
) -> Resolution {
let key = target.key.as_str();
let skip = |outcome, detail: String| {
Resolution::Skipped(TargetResult {
index,
key: key.to_string(),
title: target.title.clone(),
outcome,
detail,
frame: None,
})
};
if !target.live {
return skip(
OUTCOME_NO_WINDOW,
"no window is open on this worktree any more".to_string(),
);
}
let Some(title) = target.title.as_deref().filter(|t| !t.trim().is_empty()) else {
return skip(
OUTCOME_NO_TITLE,
"the window reported no name to match on".to_string(),
);
};
let Some(pid) = target.pid else {
return skip(
OUTCOME_NO_PID,
"the window reported no process id".to_string(),
);
};
let Some(app_pid) = app_pids.get(&pid).copied() else {
return skip(
OUTCOME_NOT_FOUND,
format!("cannot resolve the application owning process {pid}"),
);
};
let candidates = windows.get(&app_pid).map_or(&[][..], Vec::as_slice);
let window_index = match match_window(candidates, title, false) {
Match::One(index) => index,
Match::None => {
return skip(
OUTCOME_NOT_FOUND,
format!("no window named “{title}” is open in its application"),
)
}
Match::Many(count) => {
return skip(
OUTCOME_AMBIGUOUS,
format!("{count} open windows match “{title}”; refusing to guess"),
)
}
};
let id = WindowId {
app_pid,
index: window_index,
};
if !claimed.insert(id) {
return if reference_window == Some(id) {
skip(
OUTCOME_REFERENCE,
"this is the window the command was invoked from".to_string(),
)
} else {
skip(
OUTCOME_AMBIGUOUS,
format!("“{title}” resolves to a window an earlier entry already claimed"),
)
};
}
let window = &candidates[window_index];
if window.minimized {
return skip(OUTCOME_MINIMIZED, "the window is minimized".to_string());
}
if window.fullscreen {
return skip(
OUTCOME_FULLSCREEN,
"the window is in fullscreen, where a resize has no effect".to_string(),
);
}
Resolution::Movable(PlannedMove {
index,
key: key.to_string(),
title: title.to_string(),
window: id,
from: window.frame,
})
}
#[derive(Debug, PartialEq, Eq)]
enum Match {
One(usize),
None,
Many(usize),
}
fn match_window(windows: &[OsWindow], root_name: &str, allow_focus_tiebreak: bool) -> Match {
let standard: Vec<usize> = (0..windows.len())
.filter(|i| windows[*i].standard)
.collect();
let rules: [fn(&str, &str) -> bool; 3] = [
|title, name| title == name,
|title, name| title.ends_with(name),
|title, name| title.contains(name),
];
let mut tightest_ambiguous: Option<Vec<usize>> = None;
for rule in rules {
let hits: Vec<usize> = standard
.iter()
.copied()
.filter(|i| rule(&windows[*i].title, root_name))
.collect();
match hits.len() {
0 => {}
1 => return Match::One(hits[0]),
_ => {
if tightest_ambiguous.is_none() {
tightest_ambiguous = Some(hits);
}
}
}
}
match tightest_ambiguous {
None => Match::None,
Some(hits) => {
if allow_focus_tiebreak {
let focused: Vec<usize> = hits
.iter()
.copied()
.filter(|i| windows[*i].focused)
.collect();
if let [only] = focused[..] {
return Match::One(only);
}
}
Match::Many(hits.len())
}
}
}
fn move_result(
planned: &PlannedMove,
wanted: Frame,
actual: Result<Frame, String>,
) -> TargetResult {
match actual {
Ok(actual) if actual.matches(wanted) => {
TargetResult::resolved(planned, OUTCOME_MOVED, "moved into position", Some(actual))
}
Ok(actual) => TargetResult::resolved(
planned,
OUTCOME_PARTIAL,
format!(
"settled at {}×{} at ({}, {}) instead of {}×{} at ({}, {})",
actual.width.round(),
actual.height.round(),
actual.x.round(),
actual.y.round(),
wanted.width.round(),
wanted.height.round(),
wanted.x.round(),
wanted.y.round(),
),
Some(actual),
),
Err(err) => TargetResult::resolved(planned, OUTCOME_FAILED, err, None),
}
}
fn in_request_order(mut results: Vec<TargetResult>) -> Vec<TargetResult> {
results.sort_by_key(|r| r.index);
results
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use std::cell::RefCell;
fn frame(x: f64, y: f64, width: f64, height: f64) -> Frame {
Frame {
x,
y,
width,
height,
}
}
fn win(title: &str, frame: Frame) -> OsWindow {
OsWindow {
title: title.to_string(),
frame,
minimized: false,
fullscreen: false,
standard: true,
focused: false,
}
}
fn registered(key: &str, title: &str, pid: u32) -> RegisteredWindow {
RegisteredWindow {
key: key.to_string(),
live: true,
title: Some(title.to_string()),
pid: Some(pid),
}
}
struct FakeBackend {
trusted: bool,
app_pids: HashMap<u32, u32>,
windows: HashMap<u32, Vec<OsWindow>>,
broken: HashMap<u32, String>,
writes: RefCell<Vec<(WindowId, Frame)>>,
settles_at: Option<Frame>,
}
impl FakeBackend {
fn new() -> Self {
Self {
trusted: true,
app_pids: HashMap::from([(11, 900), (12, 900), (13, 900)]),
windows: HashMap::from([(
900,
vec![
win("plan.md — reference-tree", frame(0.0, 0.0, 800.0, 600.0)),
win("main.rs — target-tree", frame(100.0, 200.0, 500.0, 400.0)),
],
)]),
broken: HashMap::new(),
writes: RefCell::new(Vec::new()),
settles_at: None,
}
}
fn writes(&self) -> Vec<(WindowId, Frame)> {
self.writes.borrow().clone()
}
}
impl WindowBackend for FakeBackend {
fn trusted(&self) -> bool {
self.trusted
}
fn app_pids(&self, pids: &[u32]) -> HashMap<u32, u32> {
pids.iter()
.filter_map(|p| self.app_pids.get(p).map(|app| (*p, *app)))
.collect()
}
fn windows(&self, app_pid: u32) -> Result<Vec<OsWindow>, String> {
if let Some(err) = self.broken.get(&app_pid) {
return Err(err.clone());
}
Ok(self.windows.get(&app_pid).cloned().unwrap_or_default())
}
fn set_frame(&self, id: WindowId, frame: Frame) -> Result<Frame, String> {
self.writes.borrow_mut().push((id, frame));
Ok(self.settles_at.unwrap_or(frame))
}
}
#[test]
fn moves_a_target_onto_the_reference_frame() {
let backend = FakeBackend::new();
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[registered("t1", "target-tree", 12)],
false,
);
assert!(report.trusted);
assert!(report.blocked.is_none());
assert_eq!(
report.reference.as_ref().unwrap().frame,
frame(0.0, 0.0, 800.0, 600.0)
);
assert_eq!(report.results.len(), 1);
assert_eq!(report.results[0].outcome, OUTCOME_MOVED);
assert_eq!(report.moved(), 1);
assert_eq!(report.skipped(), 0);
assert_eq!(
backend.writes(),
vec![(
WindowId {
app_pid: 900,
index: 1
},
frame(0.0, 0.0, 800.0, 600.0)
)]
);
assert_eq!(
report.undo,
vec![("t1".to_string(), frame(100.0, 200.0, 500.0, 400.0))],
"the undo entry records where the window was before the move"
);
}
#[test]
fn a_dry_run_resolves_everything_and_writes_nothing() {
let backend = FakeBackend::new();
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[registered("t1", "target-tree", 12)],
true,
);
assert_eq!(report.results[0].outcome, OUTCOME_WOULD_MOVE);
assert!(
backend.writes().is_empty(),
"a dry run must not touch a window"
);
assert!(report.undo.is_empty(), "a dry run leaves nothing to undo");
}
#[test]
fn an_untrusted_daemon_attempts_nothing() {
let backend = FakeBackend {
trusted: false,
..FakeBackend::new()
};
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[registered("t1", "target-tree", 12)],
false,
);
assert!(!report.trusted);
assert!(report.results.is_empty());
assert!(report.reference.is_none());
assert!(backend.writes().is_empty());
}
#[test]
fn a_target_already_in_position_is_unchanged_not_written() {
let mut backend = FakeBackend::new();
backend.windows.insert(
900,
vec![
win("a.rs — reference-tree", frame(10.0, 20.0, 800.0, 600.0)),
win("b.rs — target-tree", frame(10.4, 20.4, 800.4, 600.4)),
],
);
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[registered("t1", "target-tree", 12)],
false,
);
assert_eq!(report.results[0].outcome, OUTCOME_UNCHANGED);
assert!(backend.writes().is_empty());
assert!(report.undo.is_empty());
}
#[test]
fn a_clamped_window_is_partial_and_still_undoable() {
let backend = FakeBackend {
settles_at: Some(frame(0.0, 0.0, 640.0, 600.0)),
..FakeBackend::new()
};
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[registered("t1", "target-tree", 12)],
false,
);
assert_eq!(report.results[0].outcome, OUTCOME_PARTIAL);
assert!(report.results[0].detail.contains("640"));
assert_eq!(report.moved(), 1, "a partial move still moved the window");
assert_eq!(report.undo.len(), 1, "and still needs undoing");
}
#[test]
fn a_failed_write_is_reported_and_not_undoable() {
struct Failing(FakeBackend);
impl WindowBackend for Failing {
fn trusted(&self) -> bool {
true
}
fn app_pids(&self, pids: &[u32]) -> HashMap<u32, u32> {
self.0.app_pids(pids)
}
fn windows(&self, app_pid: u32) -> Result<Vec<OsWindow>, String> {
self.0.windows(app_pid)
}
fn set_frame(&self, _id: WindowId, _frame: Frame) -> Result<Frame, String> {
Err("AXError -25204 (cannot complete)".to_string())
}
}
let report = reposition(
&Failing(FakeBackend::new()),
®istered("ref", "reference-tree", 11),
&[registered("t1", "target-tree", 12)],
false,
);
assert_eq!(report.results[0].outcome, OUTCOME_FAILED);
assert!(report.results[0].detail.contains("-25204"));
assert_eq!(report.moved(), 0);
assert!(report.undo.is_empty());
}
#[test]
fn the_reference_window_is_never_moved_even_when_selected() {
let backend = FakeBackend::new();
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[
registered("ref", "reference-tree", 11),
registered("t1", "target-tree", 12),
],
false,
);
let outcomes: Vec<&str> = report.results.iter().map(|r| r.outcome).collect();
assert_eq!(
outcomes,
vec![OUTCOME_REFERENCE, OUTCOME_MOVED],
"results stay in request order"
);
assert_eq!(backend.writes().len(), 1);
}
#[test]
fn two_targets_cannot_claim_the_same_window() {
let backend = FakeBackend::new();
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[
registered("t1", "target-tree", 12),
registered("t2", "target-tree", 13),
],
false,
);
let outcomes: Vec<&str> = report.results.iter().map(|r| r.outcome).collect();
assert_eq!(
outcomes,
vec![OUTCOME_MOVED, OUTCOME_AMBIGUOUS],
"a duplicate claim is an ambiguity, not the reference"
);
}
#[test]
fn minimized_and_fullscreen_targets_are_skipped() {
let mut backend = FakeBackend::new();
backend.windows.insert(
900,
vec![
win("a — reference-tree", frame(0.0, 0.0, 800.0, 600.0)),
OsWindow {
minimized: true,
..win("b — dozing-tree", frame(0.0, 0.0, 10.0, 10.0))
},
OsWindow {
fullscreen: true,
..win("c — huge-tree", frame(0.0, 0.0, 2560.0, 1440.0))
},
],
);
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[
registered("t1", "dozing-tree", 12),
registered("t2", "huge-tree", 13),
],
false,
);
let outcomes: Vec<&str> = report.results.iter().map(|r| r.outcome).collect();
assert_eq!(outcomes, vec![OUTCOME_MINIMIZED, OUTCOME_FULLSCREEN]);
assert!(backend.writes().is_empty());
}
#[test]
fn missing_title_pid_and_window_each_get_their_own_outcome() {
let backend = FakeBackend::new();
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[
RegisteredWindow {
key: "blank".to_string(),
live: true,
title: Some(" ".to_string()),
pid: Some(12),
},
RegisteredWindow {
key: "pidless".to_string(),
live: true,
title: Some("target-tree".to_string()),
pid: None,
},
registered("stranger", "target-tree", 99),
registered("gone", "never-opened", 12),
],
false,
);
let outcomes: Vec<&str> = report.results.iter().map(|r| r.outcome).collect();
assert_eq!(
outcomes,
vec![
OUTCOME_NO_TITLE,
OUTCOME_NO_PID,
OUTCOME_NOT_FOUND,
OUTCOME_NOT_FOUND
],
"an unresolvable pid and an unmatched name are both not-found"
);
}
#[test]
fn an_unenumerable_application_leaves_its_targets_not_found() {
let mut backend = FakeBackend::new();
backend
.broken
.insert(900, "AXError -25211 (API disabled)".to_string());
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[registered("t1", "target-tree", 12)],
false,
);
assert_eq!(report.blocked.as_ref().unwrap().reason, BLOCKED_NOT_FOUND);
assert!(report.results.is_empty());
}
#[test]
fn an_unresolvable_reference_blocks_the_whole_batch() {
let backend = FakeBackend::new();
for (reference, expected) in [
(
RegisteredWindow {
key: "r".to_string(),
live: true,
title: None,
pid: Some(11),
},
BLOCKED_UNIDENTIFIABLE,
),
(
RegisteredWindow {
key: "r".to_string(),
live: true,
title: Some("reference-tree".to_string()),
pid: None,
},
BLOCKED_UNIDENTIFIABLE,
),
(
registered("r", "reference-tree", 99),
BLOCKED_UNIDENTIFIABLE,
),
(registered("r", "no-such-window", 11), BLOCKED_NOT_FOUND),
] {
let report = reposition(
&backend,
&reference,
&[registered("t1", "target-tree", 12)],
false,
);
assert_eq!(report.blocked.as_ref().unwrap().reason, expected);
assert!(report.results.is_empty(), "no target is attempted");
assert!(backend.writes().is_empty());
}
}
#[test]
fn an_ambiguous_reference_blocks_the_batch() {
let mut backend = FakeBackend::new();
backend.windows.insert(
900,
vec![
win("a.rs — main", frame(0.0, 0.0, 10.0, 10.0)),
win("b.rs — main", frame(0.0, 0.0, 20.0, 20.0)),
],
);
let report = reposition(&backend, ®istered("r", "main", 11), &[], false);
assert_eq!(report.blocked.as_ref().unwrap().reason, BLOCKED_AMBIGUOUS);
}
#[test]
fn a_fullscreen_or_minimized_reference_blocks_the_batch() {
for (fullscreen, expected) in [(true, BLOCKED_FULLSCREEN), (false, BLOCKED_MINIMIZED)] {
let mut backend = FakeBackend::new();
backend.windows.insert(
900,
vec![OsWindow {
fullscreen,
minimized: !fullscreen,
..win("a — reference-tree", frame(0.0, 0.0, 800.0, 600.0))
}],
);
let report = reposition(&backend, ®istered("r", "reference-tree", 11), &[], false);
assert_eq!(report.blocked.as_ref().unwrap().reason, expected);
}
}
#[test]
fn restore_puts_each_window_back_at_its_own_frame() {
let backend = FakeBackend::new();
let entries = vec![
(
registered("t1", "target-tree", 12),
frame(300.0, 400.0, 700.0, 500.0),
),
(
registered("ref", "reference-tree", 11),
frame(50.0, 60.0, 900.0, 700.0),
),
];
let report = restore(&backend, &entries);
assert!(report.reference.is_none());
let outcomes: Vec<&str> = report.results.iter().map(|r| r.outcome).collect();
assert_eq!(outcomes, vec![OUTCOME_MOVED, OUTCOME_MOVED]);
assert_eq!(
backend.writes(),
vec![
(
WindowId {
app_pid: 900,
index: 1
},
frame(300.0, 400.0, 700.0, 500.0)
),
(
WindowId {
app_pid: 900,
index: 0
},
frame(50.0, 60.0, 900.0, 700.0)
),
]
);
}
#[test]
fn restore_needs_the_permission_too() {
let backend = FakeBackend {
trusted: false,
..FakeBackend::new()
};
let report = restore(
&backend,
&[(
registered("t1", "target-tree", 12),
frame(0.0, 0.0, 10.0, 10.0),
)],
);
assert!(!report.trusted);
assert!(report.results.is_empty());
assert!(backend.writes().is_empty());
}
#[test]
fn a_target_whose_name_matches_several_windows_is_ambiguous() {
let mut backend = FakeBackend::new();
backend.windows.insert(
900,
vec![
win("a.rs — reference-tree", frame(0.0, 0.0, 800.0, 600.0)),
win("b.rs — main", frame(10.0, 10.0, 100.0, 100.0)),
win("c.rs — main", frame(20.0, 20.0, 200.0, 200.0)),
],
);
let report = reposition(
&backend,
®istered("ref", "reference-tree", 11),
&[registered("t1", "main", 12)],
false,
);
assert_eq!(report.results.len(), 1);
assert_eq!(report.results[0].outcome, OUTCOME_AMBIGUOUS);
assert!(
report.results[0].detail.contains("2 open windows match"),
"the detail should say how many: {}",
report.results[0].detail
);
assert!(backend.writes().is_empty());
}
#[test]
fn restore_reports_a_window_that_has_since_closed() {
let backend = FakeBackend::new();
let entries = vec![(
registered("gone", "closed-since", 12),
frame(0.0, 0.0, 10.0, 10.0),
)];
let report = restore(&backend, &entries);
assert_eq!(report.results[0].outcome, OUTCOME_NOT_FOUND);
assert!(backend.writes().is_empty());
}
#[test]
fn ends_with_beats_a_substring_collision() {
let windows = [
win("lib.rs — omni-dev", frame(0.0, 0.0, 10.0, 10.0)),
win(
"x.rs — issue-198-omni-dev-coverage-check",
frame(0.0, 0.0, 10.0, 10.0),
),
];
assert_eq!(match_window(&windows, "omni-dev", false), Match::One(0));
assert_eq!(
match_window(&windows, "issue-198-omni-dev-coverage-check", false),
Match::One(1)
);
}
#[test]
fn a_bare_root_name_title_wins_over_a_decorated_one() {
let windows = [
win("some-tree", frame(0.0, 0.0, 10.0, 10.0)),
win("a.rs — other-some-tree", frame(0.0, 0.0, 10.0, 10.0)),
];
assert_eq!(match_window(&windows, "some-tree", false), Match::One(0));
}
#[test]
fn a_profile_suffix_still_matches_by_substring() {
let windows = [win("a.rs — some-tree — Work", frame(0.0, 0.0, 10.0, 10.0))];
assert_eq!(match_window(&windows, "some-tree", false), Match::One(0));
}
#[test]
fn a_dirty_marker_prefix_does_not_break_the_match() {
let windows = [win("● a.rs — some-tree", frame(0.0, 0.0, 10.0, 10.0))];
assert_eq!(match_window(&windows, "some-tree", false), Match::One(0));
}
#[test]
fn duplicate_root_names_are_ambiguous_not_guessed() {
let windows = [
win("a.rs — main", frame(0.0, 0.0, 10.0, 10.0)),
win("b.rs — main", frame(0.0, 0.0, 10.0, 10.0)),
];
assert_eq!(match_window(&windows, "main", false), Match::Many(2));
}
#[test]
fn focus_breaks_a_tie_for_the_reference_only() {
let windows = [
win("a.rs — main", frame(0.0, 0.0, 10.0, 10.0)),
OsWindow {
focused: true,
..win("b.rs — main", frame(0.0, 0.0, 10.0, 10.0))
},
];
assert_eq!(match_window(&windows, "main", true), Match::One(1));
assert_eq!(
match_window(&windows, "main", false),
Match::Many(2),
"a target must never win on focus — that is the reference's window"
);
}
#[test]
fn non_standard_windows_are_never_candidates() {
let windows = [OsWindow {
standard: false,
..win("Open — some-tree", frame(0.0, 0.0, 10.0, 10.0))
}];
assert_eq!(match_window(&windows, "some-tree", true), Match::None);
}
}