diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 06e388f88c..0d311f1e32 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -25,10 +25,11 @@ mod test; mod windows; use crate::{ - Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, + point, Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, SharedString, Size, Task, TaskLabel, WindowContext, + DEFAULT_WINDOW_SIZE, }; use anyhow::Result; use async_task::Runnable; @@ -167,6 +168,14 @@ pub trait PlatformDisplay: Send + Sync + Debug { /// Get the bounds for this display fn bounds(&self) -> Bounds; + + /// Get the default bounds for this display to place a window + fn default_bounds(&self) -> Bounds { + let center = self.bounds().center(); + let offset = DEFAULT_WINDOW_SIZE / 2; + let origin = point(center.x - offset.width, center.y - offset.height); + Bounds::new(origin, DEFAULT_WINDOW_SIZE) + } } /// An opaque identifier for a hardware display diff --git a/crates/gpui/src/platform/windows/display.rs b/crates/gpui/src/platform/windows/display.rs index eaba1370cc..e1e7b1e225 100644 --- a/crates/gpui/src/platform/windows/display.rs +++ b/crates/gpui/src/platform/windows/display.rs @@ -108,6 +108,22 @@ impl WindowsDisplay { Some(WindowsDisplay::new_with_handle(monitor)) } + /// Check if the center point of given bounds is inside this monitor + pub fn check_given_bounds(&self, bounds: Bounds) -> bool { + let center = bounds.center(); + let center = POINT { + x: center.x.0, + y: center.y.0, + }; + let monitor = unsafe { MonitorFromPoint(center, MONITOR_DEFAULTTONULL) }; + if monitor.is_invalid() { + false + } else { + let display = WindowsDisplay::new_with_handle(monitor); + display.uuid == self.uuid + } + } + pub fn displays() -> Vec> { available_monitors() .into_iter() @@ -135,6 +151,11 @@ impl WindowsDisplay { .then(|| devmode.dmDisplayFrequency) }) } + + /// Check if this monitor is still online + pub fn is_connected(hmonitor: HMONITOR) -> bool { + available_monitors().iter().contains(&hmonitor) + } } impl PlatformDisplay for WindowsDisplay { diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs index 9182adf8be..8cc5c65a91 100644 --- a/crates/gpui/src/platform/windows/events.rs +++ b/crates/gpui/src/platform/windows/events.rs @@ -39,6 +39,7 @@ pub(crate) fn handle_msg( WM_TIMER => handle_timer_msg(handle, wparam, state_ptr), WM_NCCALCSIZE => handle_calc_client_size(handle, wparam, lparam, state_ptr), WM_DPICHANGED => handle_dpi_changed_msg(handle, wparam, lparam, state_ptr), + WM_DISPLAYCHANGE => handle_display_change_msg(handle, state_ptr), WM_NCHITTEST => handle_hit_test_msg(handle, msg, wparam, lparam, state_ptr), WM_PAINT => handle_paint_msg(handle, state_ptr), WM_CLOSE => handle_close_msg(state_ptr), @@ -112,6 +113,8 @@ fn handle_move_msg( { // center of the window may have moved to another monitor let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) }; + // minimize the window can trigger this event too, in this case, + // monitor is invalid, we do nothing. if !monitor.is_invalid() && lock.display.handle != monitor { // we will get the same monitor if we only have one lock.display = WindowsDisplay::new_with_handle(monitor); @@ -775,6 +778,41 @@ fn handle_dpi_changed_msg( Some(0) } +/// The following conditions will trigger this event: +/// 1. The monitor on which the window is located goes offline or changes resolution. +/// 2. Another monitor goes offline, is plugged in, or changes resolution. +/// +/// In either case, the window will only receive information from the monitor on which +/// it is located. +/// +/// For example, in the case of condition 2, where the monitor on which the window is +/// located has actually changed nothing, it will still receive this event. +fn handle_display_change_msg(handle: HWND, state_ptr: Rc) -> Option { + // NOTE: + // Even the `lParam` holds the resolution of the screen, we just ignore it. + // Because WM_DPICHANGED, WM_MOVE, WM_SIEZ will come first, window reposition and resize + // are handled there. + // So we only care about if monitor is disconnected. + let previous_monitor = state_ptr.as_ref().state.borrow().display; + if WindowsDisplay::is_connected(previous_monitor.handle) { + // we are fine, other display changed + return None; + } + // display disconnected + // in this case, the OS will move our window to another monitor, and minimize it. + // we deminimize the window and query the monitor after moving + unsafe { ShowWindow(handle, SW_SHOWNORMAL) }; + let new_monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) }; + // all monitors disconnected + if new_monitor.is_invalid() { + log::error!("No monitor detected!"); + return None; + } + let new_display = WindowsDisplay::new_with_handle(new_monitor); + state_ptr.as_ref().state.borrow_mut().display = new_display; + Some(0) +} + fn handle_hit_test_msg( handle: HWND, msg: u32, diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index cfa720a4b1..ee714c9c29 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -258,13 +258,17 @@ impl WindowsWindow { ); let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; let hinstance = get_module_handle(); + let display = if let Some(display_id) = params.display_id { + // if we obtain a display_id, then this ID must be valid. + WindowsDisplay::new(display_id).unwrap() + } else { + WindowsDisplay::primary_monitor().unwrap() + }; let mut context = WindowCreateContext { inner: None, handle, hide_title_bar, - // todo(windows) move window to target monitor - // options.display_id - display: WindowsDisplay::primary_monitor().unwrap(), + display, transparent: params.window_background != WindowBackgroundAppearance::Opaque, executor, mouse_wheel_settings, @@ -297,10 +301,16 @@ impl WindowsWindow { ..Default::default() }; GetWindowPlacement(raw_hwnd, &mut placement).log_err(); - placement.rcNormalPosition.left = params.bounds.left().0; - placement.rcNormalPosition.right = params.bounds.right().0; - placement.rcNormalPosition.top = params.bounds.top().0; - placement.rcNormalPosition.bottom = params.bounds.bottom().0; + // the bounds may be not inside the display + let bounds = if display.check_given_bounds(params.bounds) { + params.bounds + } else { + display.default_bounds() + }; + placement.rcNormalPosition.left = bounds.left().0; + placement.rcNormalPosition.right = bounds.right().0; + placement.rcNormalPosition.top = bounds.top().0; + placement.rcNormalPosition.bottom = bounds.bottom().0; SetWindowPlacement(raw_hwnd, &placement).log_err(); } unsafe { ShowWindow(raw_hwnd, SW_SHOW).ok().log_err() }; diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index ef9c0a610f..f0fbe28e49 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -51,6 +51,9 @@ mod prompts; pub use prompts::*; +pub(crate) const DEFAULT_WINDOW_SIZE: Size = + size(DevicePixels(1024), DevicePixels(700)); + /// Represents the two different phases when dispatching events. #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] pub enum DispatchPhase { @@ -573,7 +576,6 @@ pub(crate) struct ElementStateBox { } fn default_bounds(display_id: Option, cx: &mut AppContext) -> Bounds { - const DEFAULT_WINDOW_SIZE: Size = size(DevicePixels(1024), DevicePixels(700)); const DEFAULT_WINDOW_OFFSET: Point = point(DevicePixels(0), DevicePixels(35)); cx.active_window() @@ -585,12 +587,7 @@ fn default_bounds(display_id: Option, cx: &mut AppContext) -> Bounds< .unwrap_or_else(|| cx.primary_display()); display - .map(|display| { - let center = display.bounds().center(); - let offset = DEFAULT_WINDOW_SIZE / 2; - let origin = point(center.x - offset.width, center.y - offset.height); - Bounds::new(origin, DEFAULT_WINDOW_SIZE) - }) + .map(|display| display.default_bounds()) .unwrap_or_else(|| { Bounds::new(point(DevicePixels(0), DevicePixels(0)), DEFAULT_WINDOW_SIZE) })