@@ -476,6 +476,37 @@ impl BorderMap { |
| 476 | 476 | changed |
| 477 | 477 | } |
| 478 | 478 | |
| 479 | + /// Re-apply set_bounds for every tracked overlay even when the |
| 480 | + /// stored CG bounds match the current SLS bounds. After a display |
| 481 | + /// reconfiguration the cocoa frame depends on the (possibly new) |
| 482 | + /// primary screen height, so unchanged CG bounds still need their |
| 483 | + /// cocoa frame recomputed. |
| 484 | + fn reconcile_all_force(&mut self) { |
| 485 | + let tracked: Vec<u32> = self.overlays.keys().copied().collect(); |
| 486 | + let active_only = self.active_only; |
| 487 | + let focused = self.focused_wid; |
| 488 | + for wid in tracked { |
| 489 | + let mut bounds = CGRect::default(); |
| 490 | + unsafe { |
| 491 | + if SLSGetWindowBounds(self.main_cid, wid, &mut bounds) != kCGErrorSuccess { |
| 492 | + self.remove(wid); |
| 493 | + continue; |
| 494 | + } |
| 495 | + } |
| 496 | + if let Some(overlay) = self.overlays.get_mut(&wid) { |
| 497 | + overlay.window.set_bounds( |
| 498 | + bounds.origin.x, |
| 499 | + bounds.origin.y, |
| 500 | + bounds.size.width, |
| 501 | + bounds.size.height, |
| 502 | + ); |
| 503 | + if !active_only || wid == focused { |
| 504 | + overlay.window.order_above(wid); |
| 505 | + } |
| 506 | + } |
| 507 | + } |
| 508 | + } |
| 509 | + |
| 479 | 510 | /// With NSWindow.setFrame_display we no longer need a destroy-and- |
| 480 | 511 | /// recreate path on resize. Kept as a thin alias so existing call |
| 481 | 512 | /// sites keep working. |
@@ -838,6 +869,7 @@ fn main() { |
| 838 | 869 | // require a main-thread context. |
| 839 | 870 | let mtm = nswindow_overlay::init_application(); |
| 840 | 871 | nswindow_overlay::log_screens(mtm); |
| 872 | + register_display_hotplug_callback(); |
| 841 | 873 | |
| 842 | 874 | let cid = unsafe { SLSMainConnectionID() }; |
| 843 | 875 | let own_pid = unsafe { |
@@ -1183,6 +1215,36 @@ fn process_event_batch( |
| 1183 | 1215 | borders.enforce_active_only(); |
| 1184 | 1216 | } |
| 1185 | 1217 | |
| 1218 | +/// Re-log the screen layout when the display configuration changes |
| 1219 | +/// (monitor plug/unplug, resolution change). The callback also nudges |
| 1220 | +/// every tracked overlay to re-fetch its bounds so any cached cocoa Y |
| 1221 | +/// computed against the old primary height gets refreshed. |
| 1222 | +unsafe extern "C" fn display_reconfig_callback( |
| 1223 | + display_id: u32, |
| 1224 | + flags: u32, |
| 1225 | + _user_info: *mut std::ffi::c_void, |
| 1226 | +) { |
| 1227 | + debug!(display_id, flags, "[hotplug] CGDisplay reconfiguration"); |
| 1228 | + if let Some(mtm) = objc2::MainThreadMarker::new() { |
| 1229 | + nswindow_overlay::log_screens(mtm); |
| 1230 | + } |
| 1231 | + MAIN_STATE.with(|cell| { |
| 1232 | + if let Some(s) = cell.borrow_mut().as_mut() { |
| 1233 | + s.borders.reconcile_all_force(); |
| 1234 | + } |
| 1235 | + }); |
| 1236 | +} |
| 1237 | + |
| 1238 | +fn register_display_hotplug_callback() { |
| 1239 | + unsafe { |
| 1240 | + let rc = CGDisplayRegisterReconfigurationCallback( |
| 1241 | + Some(display_reconfig_callback), |
| 1242 | + std::ptr::null_mut(), |
| 1243 | + ); |
| 1244 | + debug!("[hotplug] register CGDisplayReconfiguration rc={}", rc); |
| 1245 | + } |
| 1246 | +} |
| 1247 | + |
| 1186 | 1248 | fn setup_event_port(cid: CGSConnectionID) { |
| 1187 | 1249 | unsafe { |
| 1188 | 1250 | let mut port: u32 = 0; |