@@ -27,13 +27,32 @@ const NS_FLOATING_WINDOW_LEVEL: isize = 3; |
| 27 | 27 | |
| 28 | 28 | /// Top-left Y in CG global coordinates becomes bottom-left Y in Cocoa |
| 29 | 29 | /// global coordinates by subtracting from the primary screen height. |
| 30 | | -fn cg_to_cocoa_frame(cg: CGRect, mtm: MainThreadMarker) -> CGRect { |
| 30 | +/// |
| 31 | +/// The "primary" screen is the one whose Cocoa frame origin is (0, 0) — |
| 32 | +/// by definition the bottom-left of the menu-bar screen, which is the |
| 33 | +/// shared anchor between CG (top-left) and Cocoa (bottom-left) global |
| 34 | +/// coordinates. `[NSScreen screens][0]` is documented as the primary |
| 35 | +/// but isn't reliable on every macOS version, so we look it up by |
| 36 | +/// origin instead. |
| 37 | +fn primary_screen_height(mtm: MainThreadMarker) -> f64 { |
| 31 | 38 | let screens = NSScreen::screens(mtm); |
| 32 | | - let primary_height = if screens.count() > 0 { |
| 39 | + let count = screens.count(); |
| 40 | + for i in 0..count { |
| 41 | + let s = screens.objectAtIndex(i); |
| 42 | + let f = s.frame(); |
| 43 | + if f.origin.x.abs() < 0.5 && f.origin.y.abs() < 0.5 { |
| 44 | + return f.size.height; |
| 45 | + } |
| 46 | + } |
| 47 | + if count > 0 { |
| 33 | 48 | screens.objectAtIndex(0).frame().size.height |
| 34 | 49 | } else { |
| 35 | 50 | 0.0 |
| 36 | | - }; |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +fn cg_to_cocoa_frame(cg: CGRect, mtm: MainThreadMarker) -> CGRect { |
| 55 | + let primary_height = primary_screen_height(mtm); |
| 37 | 56 | let cocoa_y = primary_height - cg.origin.y - cg.size.height; |
| 38 | 57 | CGRect::new( |
| 39 | 58 | CGPoint::new(cg.origin.x, cocoa_y), |
@@ -41,6 +60,30 @@ fn cg_to_cocoa_frame(cg: CGRect, mtm: MainThreadMarker) -> CGRect { |
| 41 | 60 | ) |
| 42 | 61 | } |
| 43 | 62 | |
| 63 | +/// Log all NSScreens and which one we'll treat as primary. Helps diagnose |
| 64 | +/// multi-monitor coordinate issues. |
| 65 | +pub fn log_screens(mtm: MainThreadMarker) { |
| 66 | + let screens = NSScreen::screens(mtm); |
| 67 | + let primary_h = primary_screen_height(mtm); |
| 68 | + tracing::debug!( |
| 69 | + primary_height = primary_h, |
| 70 | + count = screens.count(), |
| 71 | + "NSScreen layout" |
| 72 | + ); |
| 73 | + for i in 0..screens.count() { |
| 74 | + let s = screens.objectAtIndex(i); |
| 75 | + let f = s.frame(); |
| 76 | + tracing::debug!( |
| 77 | + index = i, |
| 78 | + cocoa_x = f.origin.x, |
| 79 | + cocoa_y = f.origin.y, |
| 80 | + w = f.size.width, |
| 81 | + h = f.size.height, |
| 82 | + "screen" |
| 83 | + ); |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 44 | 87 | /// Initialize NSApplication. Must be called once from the main thread. |
| 45 | 88 | pub fn init_application() -> MainThreadMarker { |
| 46 | 89 | let mtm = MainThreadMarker::new().expect("init_application must run on the main thread"); |
@@ -172,6 +215,17 @@ impl OverlayWindow { |
| 172 | 215 | CGSize::new(w + 2.0 * self.border_width, h + 2.0 * self.border_width), |
| 173 | 216 | ); |
| 174 | 217 | let cocoa_frame = cg_to_cocoa_frame(outer_cg, self.mtm); |
| 218 | + tracing::debug!( |
| 219 | + cg_x = outer_cg.origin.x, |
| 220 | + cg_y = outer_cg.origin.y, |
| 221 | + cg_w = outer_cg.size.width, |
| 222 | + cg_h = outer_cg.size.height, |
| 223 | + cocoa_x = cocoa_frame.origin.x, |
| 224 | + cocoa_y = cocoa_frame.origin.y, |
| 225 | + cocoa_w = cocoa_frame.size.width, |
| 226 | + cocoa_h = cocoa_frame.size.height, |
| 227 | + "set_bounds" |
| 228 | + ); |
| 175 | 229 | self.window.setFrame_display(cocoa_frame, true); |
| 176 | 230 | // Update the border path to match new size. |
| 177 | 231 | unsafe { |