Skip to content

Commit ca71f07

Browse files
authored
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.
1 parent 00eb6be commit ca71f07

2 files changed

Lines changed: 60 additions & 7 deletions

File tree

  • docs/content.en/docs/release-notes
  • src-tauri/src/extension/built_in/window_management/backend

docs/content.en/docs/release-notes/_index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ feat: support pageup/pagedown to navigate search results #920
2222

2323
fix: automatic update of service list #913
2424
fix: duplicate chat content #916
25-
fix: resolve pinned window shortcut not working (#917)
25+
fix: resolve pinned window shortcut not working #917
26+
fix: WM ext does not work when operating focused win from another display #919
2627

2728
### ✈️ Improvements
2829

src-tauri/src/extension/built_in/window_management/backend/mod.rs

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use objc2_core_graphics::CGEventType;
3434
use objc2_core_graphics::CGMouseButton;
3535
use objc2_core_graphics::CGRectGetMidX;
3636
use objc2_core_graphics::CGRectGetMinY;
37+
use objc2_core_graphics::CGRectIntersectsRect;
3738
use objc2_core_graphics::CGWindowID;
3839

3940
use super::error::Error;
@@ -46,12 +47,7 @@ use std::collections::HashMap;
4647
use std::sync::{LazyLock, Mutex};
4748

4849
fn intersects(r1: CGRect, r2: CGRect) -> bool {
49-
let overlapping = !(r1.origin.x + r1.size.width < r2.origin.x
50-
|| r1.origin.y + r1.size.height < r2.origin.y
51-
|| r1.origin.x > r2.origin.x + r2.size.width
52-
|| r1.origin.y > r2.origin.y + r2.size.height);
53-
54-
overlapping
50+
unsafe { CGRectIntersectsRect(r1, r2) }
5551
}
5652

5753
/// 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> {
461457
/// 2. For non-main displays, it assumes that they don't have a menu bar, but macOS
462458
/// puts a menu bar on every display.
463459
///
460+
/// Update: This could be wrong, but looks like Apple fixed these 2 bugs in macOS
461+
/// 26. At least the buggy behaviors disappear in my test.
462+
///
464463
///
465464
/// [^1]: Visible frame: a rectangle defines the portion of the screen in which it
466465
/// is currently safe to draw your app’s content.
@@ -636,3 +635,56 @@ pub(crate) fn get_frontmost_window_last_frame(window_id: CGWindowID) -> Option<C
636635
let map = LAST_FRAME.lock().unwrap();
637636
map.get(&window_id).cloned()
638637
}
638+
639+
#[cfg(test)]
640+
mod tests {
641+
use super::*;
642+
643+
#[test]
644+
fn test_intersects_adjacent_rects_x() {
645+
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
646+
let r2 = CGRect::new(CGPoint::new(100.0, 0.0), CGSize::new(100.0, 100.0));
647+
assert!(
648+
!intersects(r1, r2),
649+
"Adjacent rects on X should not intersect"
650+
);
651+
}
652+
653+
#[test]
654+
fn test_intersects_adjacent_rects_y() {
655+
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
656+
let r2 = CGRect::new(CGPoint::new(0.0, 100.0), CGSize::new(100.0, 100.0));
657+
assert!(
658+
!intersects(r1, r2),
659+
"Adjacent rects on Y should not intersect"
660+
);
661+
}
662+
663+
#[test]
664+
fn test_intersects_overlapping_rects() {
665+
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
666+
let r2 = CGRect::new(CGPoint::new(50.0, 50.0), CGSize::new(100.0, 100.0));
667+
assert!(intersects(r1, r2), "Overlapping rects should intersect");
668+
}
669+
670+
#[test]
671+
fn test_intersects_separate_rects() {
672+
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
673+
let r2 = CGRect::new(CGPoint::new(101.0, 101.0), CGSize::new(100.0, 100.0));
674+
assert!(!intersects(r1, r2), "Separate rects should not intersect");
675+
}
676+
677+
#[test]
678+
fn test_intersects_contained_rect() {
679+
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
680+
let r2 = CGRect::new(CGPoint::new(10.0, 10.0), CGSize::new(50.0, 50.0));
681+
assert!(intersects(r1, r2), "Contained rect should intersect");
682+
}
683+
684+
#[test]
685+
fn test_intersects_identical_rects() {
686+
let r1 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
687+
let r2 = CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(100.0, 100.0));
688+
assert!(intersects(r1, r2), "Identical rects should intersect");
689+
}
690+
}

0 commit comments

Comments
 (0)