fix: WM ext does not work when operating focused win from another display (#919)

This commit fixes a bug that most Window Management extension commands
won't work if you:

1. operate the focused window from another display
2. and they are adjacent

To reproduce this:

say you have 2 displays

1. Put the focused window on a non-main display, maximize the window
2. Move the mourse to the main display, making it the active display
3. Launch Coco, then execute the `TopHalf` command

The focused window will be moved to the main display, while it should
stay in the non-main display.

The root cause of the issue is that the previous implementation of
`intersects()` didn't handle an edge case correctly, adjavent rectangles
should not be considered overlapping. This commit replaces the buggy
implementation with the `CGRectIntersectsRect()` function from macOS
core graphics library.
This commit is contained in:
SteveLauC
2025-10-13 11:13:42 +08:00
committed by GitHub
parent 00eb6bed2b
commit ca71f07f3a
2 changed files with 60 additions and 7 deletions

View File

@@ -22,7 +22,8 @@ feat: support pageup/pagedown to navigate search results #920
fix: automatic update of service list #913
fix: duplicate chat content #916
fix: resolve pinned window shortcut not working (#917)
fix: resolve pinned window shortcut not working #917
fix: WM ext does not work when operating focused win from another display #919
### ✈️ Improvements

View File

@@ -34,6 +34,7 @@ use objc2_core_graphics::CGEventType;
use objc2_core_graphics::CGMouseButton;
use objc2_core_graphics::CGRectGetMidX;
use objc2_core_graphics::CGRectGetMinY;
use objc2_core_graphics::CGRectIntersectsRect;
use objc2_core_graphics::CGWindowID;
use super::error::Error;
@@ -46,12 +47,7 @@ use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
fn intersects(r1: CGRect, r2: CGRect) -> bool {
let overlapping = !(r1.origin.x + r1.size.width < r2.origin.x
|| r1.origin.y + r1.size.height < r2.origin.y
|| r1.origin.x > r2.origin.x + r2.size.width
|| r1.origin.y > r2.origin.y + r2.size.height);
overlapping
unsafe { CGRectIntersectsRect(r1, r2) }
}
/// Core graphics APIs use flipped coordinate system, while AppKit uses the
@@ -461,6 +457,9 @@ fn get_frontmost_window_close_button_frame() -> Result<CGRect, Error> {
/// 2. For non-main displays, it assumes that they don't have a menu bar, but macOS
/// puts a menu bar on every display.
///
/// Update: This could be wrong, but looks like Apple fixed these 2 bugs in macOS
/// 26. At least the buggy behaviors disappear in my test.
///
///
/// [^1]: Visible frame: a rectangle defines the portion of the screen in which it
/// is currently safe to draw your apps content.
@@ -636,3 +635,56 @@ pub(crate) fn get_frontmost_window_last_frame(window_id: CGWindowID) -> Option<C
let map = LAST_FRAME.lock().unwrap();
map.get(&window_id).cloned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intersects_adjacent_rects_x() {
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
let r2 = CGRect::new(CGPoint::new(100.0, 0.0), CGSize::new(100.0, 100.0));
assert!(
!intersects(r1, r2),
"Adjacent rects on X should not intersect"
);
}
#[test]
fn test_intersects_adjacent_rects_y() {
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
let r2 = CGRect::new(CGPoint::new(0.0, 100.0), CGSize::new(100.0, 100.0));
assert!(
!intersects(r1, r2),
"Adjacent rects on Y should not intersect"
);
}
#[test]
fn test_intersects_overlapping_rects() {
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
let r2 = CGRect::new(CGPoint::new(50.0, 50.0), CGSize::new(100.0, 100.0));
assert!(intersects(r1, r2), "Overlapping rects should intersect");
}
#[test]
fn test_intersects_separate_rects() {
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
let r2 = CGRect::new(CGPoint::new(101.0, 101.0), CGSize::new(100.0, 100.0));
assert!(!intersects(r1, r2), "Separate rects should not intersect");
}
#[test]
fn test_intersects_contained_rect() {
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
let r2 = CGRect::new(CGPoint::new(10.0, 10.0), CGSize::new(50.0, 50.0));
assert!(intersects(r1, r2), "Contained rect should intersect");
}
#[test]
fn test_intersects_identical_rects() {
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
let r2 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
assert!(intersects(r1, r2), "Identical rects should intersect");
}
}