diff --git a/Cargo.toml b/Cargo.toml index 71f7c58113..5b2d09a29f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -220,6 +220,7 @@ features = [ [target.'cfg(target_os = "windows")'.dependencies] unicode-segmentation = "1.7.1" +windows-version = "0.1.1" [target.'cfg(target_os = "windows")'.dependencies.windows-sys] version = "0.52.0" diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..cb8e6a6727 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,21 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Changed + +- On Web, let events wake up event loop immediately when using + `ControlFlow::Poll`. + +### Fixed + +- On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately + when not called from inside the event loop. Now queues a microtask instead. +- On Windows, prevent incorrect shifting when dragging window onto a monitor + with different DPI. + +### Removed + +- Remove `EventLoop::run`. +- Remove `EventLoopExtRunOnDemand::run_on_demand`. +- Remove `EventLoopExtPumpEvents::pump_events`. diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index f29b314a7c..fce1f4a26c 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -15,7 +15,7 @@ use calloop::generic::Generic; use calloop::ping::Ping; use calloop::{EventLoop as Loop, Readiness}; use libc::{setlocale, LC_CTYPE}; -use tracing::warn; +use tracing::{error, warn}; use x11rb::connection::RequestConnection; use x11rb::errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError}; @@ -669,6 +669,18 @@ impl ActiveEventLoop { self.xconn .select_xinput_events(self.root, ALL_MASTER_DEVICES, mask) + .map_err(|err| { + // Handle loss of connection to the X server by exiting instead of panicking. + if let X11Error::Connection(_) = err { + error!( + "Detected loss of connection to X server while reading window geometry; \ + exiting application..." + ); + // Use exit code 1 to match the default Xlib I/O error handler. + std::process::exit(1); + } + err + }) .expect_then_ignore_error("Failed to update device event filter"); } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 7e15d5f242..1acca0b1d0 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -5,7 +5,7 @@ use std::path::Path; use std::sync::{Arc, Mutex, MutexGuard}; use std::{cmp, env}; -use tracing::{debug, info, warn}; +use tracing::{debug, error, info, warn}; use x11rb::connection::Connection; use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification}; use x11rb::protocol::shape::{ConnectionExt as ShapeExt, SK, SO}; @@ -1244,6 +1244,18 @@ impl UnownedWindow { // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn .get_geometry(self.xwindow) + .map_err(|err| { + // Handle loss of connection to the X server by exiting instead of panicking. + if let X11Error::Connection(_) = err { + error!( + "Detected loss of connection to X server while reading window geometry; \ + exiting application..." + ); + // Use exit code 1 to match the default Xlib I/O error handler. + std::process::exit(1); + } + err + }) .map(|geo| (geo.width.into(), geo.height.into())) .unwrap() } diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index d741f09872..79703aff88 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -553,10 +553,19 @@ impl ActiveEventLoop { let canvas = canvas_clone.clone(); move |new_size| { - let canvas = canvas.borrow(); - canvas.set_current_size(new_size); - if canvas.old_size() != new_size { - canvas.set_old_size(new_size); + // Release the canvas borrow before sending events, because event + // processing can synchronously re-enter and attempt to borrow_mut + // the canvas (e.g. to update the cursor), which would panic. + let size_changed = { + let canvas = canvas.borrow(); + canvas.set_current_size(new_size); + let changed = canvas.old_size() != new_size; + if changed { + canvas.set_old_size(new_size); + } + changed + }; + if size_changed { runner.send_event(Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::Resized(new_size), diff --git a/src/platform_impl/web/web_sys/media_query_handle.rs b/src/platform_impl/web/web_sys/media_query_handle.rs index 766157a681..79c329b258 100644 --- a/src/platform_impl/web/web_sys/media_query_handle.rs +++ b/src/platform_impl/web/web_sys/media_query_handle.rs @@ -30,6 +30,7 @@ impl MediaQueryListHandle { Self { mql, closure } } + #[allow(dead_code)] pub fn mql(&self) -> &MediaQueryList { &self.mql } diff --git a/src/platform_impl/web/web_sys/resize_scaling.rs b/src/platform_impl/web/web_sys/resize_scaling.rs index 4d10b3ac01..1a323d06da 100644 --- a/src/platform_impl/web/web_sys/resize_scaling.rs +++ b/src/platform_impl/web/web_sys/resize_scaling.rs @@ -125,11 +125,14 @@ impl ResizeScaleInternal { (-webkit-device-pixel-ratio: {current_scale})", ); let mql = MediaQueryListHandle::new(window, &media_query, closure); - debug_assert!( - mql.mql().matches(), - "created media query doesn't match, {current_scale} != {}", - super::scale_factor(window) - ); + // TODO(PLAT-806): There's a winit/browser bug that causes this debug_assert + // to trigger when the print dialog is open. To prevent debug builds from + // panicking, we disable the debug_assert for now. + // debug_assert!( + // mql.mql().matches(), + // "created media query doesn't match, {current_scale} != {}", + // super::scale_factor(window) + // ); mql } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 6d60c1c524..a5597e8f3d 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -2306,103 +2306,110 @@ unsafe fn public_window_callback_inner( } let new_outer_rect: RECT; - { - let suggested_ul = - (suggested_rect.left + margin_left, suggested_rect.top + margin_top); - - let mut conservative_rect = RECT { - left: suggested_ul.0, - top: suggested_ul.1, - right: suggested_ul.0 + new_physical_inner_size.width as i32, - bottom: suggested_ul.1 + new_physical_inner_size.height as i32, - }; - - conservative_rect = window_flags - .adjust_rect(window, conservative_rect) - .unwrap_or(conservative_rect); - - // If we're dragging the window, offset the window so that the cursor's - // relative horizontal position in the title bar is preserved. - if dragging_window { - let bias = { - let cursor_pos = { - let mut pos = unsafe { mem::zeroed() }; - unsafe { GetCursorPos(&mut pos) }; - pos - }; - let suggested_cursor_horizontal_ratio = (cursor_pos.x - suggested_rect.left) - as f64 - / (suggested_rect.right - suggested_rect.left) as f64; - - (cursor_pos.x - - (suggested_cursor_horizontal_ratio - * (conservative_rect.right - conservative_rect.left) as f64) - as i32) - - conservative_rect.left + if util::WIN_VERSION.build < 22000 { + // The window position needs adjustment on Windows 10. + { + let suggested_ul = + (suggested_rect.left + margin_left, suggested_rect.top + margin_top); + + let mut conservative_rect = RECT { + left: suggested_ul.0, + top: suggested_ul.1, + right: suggested_ul.0 + new_physical_inner_size.width as i32, + bottom: suggested_ul.1 + new_physical_inner_size.height as i32, }; - conservative_rect.left += bias; - conservative_rect.right += bias; - } - // Check to see if the new window rect is on the monitor with the new DPI factor. - // If it isn't, offset the window so that it is. - let new_dpi_monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) }; - let conservative_rect_monitor = - unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) }; - new_outer_rect = if conservative_rect_monitor == new_dpi_monitor { - conservative_rect - } else { - let get_monitor_rect = |monitor| { - let mut monitor_info = MONITORINFO { - cbSize: mem::size_of::() as _, - ..unsafe { mem::zeroed() } + conservative_rect = window_flags + .adjust_rect(window, conservative_rect) + .unwrap_or(conservative_rect); + + // If we're dragging the window, offset the window so that the cursor's + // relative horizontal position in the title bar is preserved. + if dragging_window { + let bias = { + let cursor_pos = { + let mut pos = unsafe { mem::zeroed() }; + unsafe { GetCursorPos(&mut pos) }; + pos + }; + let suggested_cursor_horizontal_ratio = + (cursor_pos.x - suggested_rect.left) as f64 + / (suggested_rect.right - suggested_rect.left) as f64; + + (cursor_pos.x + - (suggested_cursor_horizontal_ratio + * (conservative_rect.right - conservative_rect.left) as f64) + as i32) + - conservative_rect.left }; - unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }; - monitor_info.rcMonitor - }; - let wrong_monitor = conservative_rect_monitor; - let wrong_monitor_rect = get_monitor_rect(wrong_monitor); - let new_monitor_rect = get_monitor_rect(new_dpi_monitor); - - // The direction to nudge the window in to get the window onto the monitor with - // the new DPI factor. We calculate this by seeing which monitor edges are - // shared and nudging away from the wrong monitor based on those. - #[allow(clippy::bool_to_int_with_if)] - let delta_nudge_to_dpi_monitor = ( - if wrong_monitor_rect.left == new_monitor_rect.right { - -1 - } else if wrong_monitor_rect.right == new_monitor_rect.left { - 1 - } else { - 0 - }, - if wrong_monitor_rect.bottom == new_monitor_rect.top { - 1 - } else if wrong_monitor_rect.top == new_monitor_rect.bottom { - -1 - } else { - 0 - }, - ); + conservative_rect.left += bias; + conservative_rect.right += bias; + } - let abort_after_iterations = new_monitor_rect.right - new_monitor_rect.left - + new_monitor_rect.bottom - - new_monitor_rect.top; - for _ in 0..abort_after_iterations { - conservative_rect.left += delta_nudge_to_dpi_monitor.0; - conservative_rect.right += delta_nudge_to_dpi_monitor.0; - conservative_rect.top += delta_nudge_to_dpi_monitor.1; - conservative_rect.bottom += delta_nudge_to_dpi_monitor.1; - - if unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) } - == new_dpi_monitor - { - break; + // Check to see if the new window rect is on the monitor with the new DPI factor. + // If it isn't, offset the window so that it is. + let new_dpi_monitor = + unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) }; + let conservative_rect_monitor = + unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) }; + new_outer_rect = if conservative_rect_monitor == new_dpi_monitor { + conservative_rect + } else { + let get_monitor_rect = |monitor| { + let mut monitor_info = MONITORINFO { + cbSize: mem::size_of::() as _, + ..unsafe { mem::zeroed() } + }; + unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }; + monitor_info.rcMonitor + }; + let wrong_monitor = conservative_rect_monitor; + let wrong_monitor_rect = get_monitor_rect(wrong_monitor); + let new_monitor_rect = get_monitor_rect(new_dpi_monitor); + + // The direction to nudge the window in to get the window onto the monitor with + // the new DPI factor. We calculate this by seeing which monitor edges are + // shared and nudging away from the wrong monitor based on those. + #[allow(clippy::bool_to_int_with_if)] + let delta_nudge_to_dpi_monitor = ( + if wrong_monitor_rect.left == new_monitor_rect.right { + -1 + } else if wrong_monitor_rect.right == new_monitor_rect.left { + 1 + } else { + 0 + }, + if wrong_monitor_rect.bottom == new_monitor_rect.top { + 1 + } else if wrong_monitor_rect.top == new_monitor_rect.bottom { + -1 + } else { + 0 + }, + ); + + let abort_after_iterations = new_monitor_rect.right - new_monitor_rect.left + + new_monitor_rect.bottom + - new_monitor_rect.top; + for _ in 0..abort_after_iterations { + conservative_rect.left += delta_nudge_to_dpi_monitor.0; + conservative_rect.right += delta_nudge_to_dpi_monitor.0; + conservative_rect.top += delta_nudge_to_dpi_monitor.1; + conservative_rect.bottom += delta_nudge_to_dpi_monitor.1; + + if unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) } + == new_dpi_monitor + { + break; + } } - } - conservative_rect - }; + conservative_rect + }; + } + } else { + // The suggested position is fine w/o adjustment on Windows 11. + new_outer_rect = suggested_rect } unsafe { diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index a3a7705d2b..3bbd79b8e9 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -3,6 +3,7 @@ use std::iter::once; use std::ops::BitAnd; use std::os::windows::prelude::{OsStrExt, OsStringExt}; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::LazyLock; use std::{io, mem, ptr}; use crate::utils::Lazy; @@ -26,6 +27,9 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ use crate::window::CursorIcon; +pub static WIN_VERSION: LazyLock = + LazyLock::new(windows_version::OsVersion::current); + pub fn encode_wide(string: impl AsRef) -> Vec { string.as_ref().encode_wide().chain(once(0)).collect() }