diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec3c32da0..d51fcaeb41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,17 @@ Unreleased` header. # Unreleased +# 0.29.4 + +- Fix crash when running iOS app on macOS. +- On X11, check common alternative cursor names when loading cursor. +- On X11, reload the DPI after a property change event. +- On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread. +- On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account. +- On macOS, send a `Resized` event after each `ScaleFactorChanged` event. +- On Wayland, fix `wl_surface` being destroyed before associated objects. +- On macOS, fix assertion when pressing `Fn` key. + # 0.29.3 - On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible. diff --git a/Cargo.toml b/Cargo.toml index b54d37c06a..a08492af68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.29.3" +version = "0.29.4" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2021" @@ -13,7 +13,14 @@ categories = ["gui"] rust-version = "1.65.0" [package.metadata.docs.rs] -features = ["rwh_04", "rwh_05", "rwh_06", "serde"] +features = [ + "rwh_04", + "rwh_05", + "rwh_06", + "serde", + # Enabled to get docs to compile + "android-native-activity", +] default-target = "x86_64-unknown-linux-gnu" # These are all tested in CI targets = [ @@ -54,7 +61,7 @@ cfg_aliases = "0.1.1" [dependencies] bitflags = "2" -cursor-icon = "1.0.0" +cursor-icon = "1.1.0" log = "0.4" mint = { version = "0.5.6", optional = true } once_cell = "1.12" @@ -74,7 +81,7 @@ softbuffer = "0.3.0" [target.'cfg(target_os = "android")'.dependencies] android-activity = "0.5.0" -ndk = "0.8.0" +ndk = { version = "0.8.0", default-features = false } ndk-sys = "0.5.0" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] diff --git a/README.md b/README.md index 165ce8636f..75abb89d08 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```toml [dependencies] -winit = "0.29.3" +winit = "0.29.4" ``` ## [Documentation](https://docs.rs/winit) @@ -157,7 +157,7 @@ For more details, refer to these `android-activity` [example applications](https If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk` then the minimal changes would be: 1. Remove `ndk-glue` from your `Cargo.toml` -2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.3", features = [ "android-native-activity" ] }` +2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.4", features = [ "android-native-activity" ] }` 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above). 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above). diff --git a/examples/util/fill.rs b/examples/util/fill.rs index a47ce95a04..6ff4ed69eb 100644 --- a/examples/util/fill.rs +++ b/examples/util/fill.rs @@ -53,6 +53,13 @@ pub(super) fn fill_window(window: &Window) { } GC.with(|gc| { + let size = window.inner_size(); + let (Some(width), Some(height)) = + (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) + else { + return; + }; + // Either get the last context used or create a new one. let mut gc = gc.borrow_mut(); let surface = gc @@ -61,13 +68,9 @@ pub(super) fn fill_window(window: &Window) { // Fill a buffer with a solid color. const DARK_GRAY: u32 = 0xFF181818; - let size = window.inner_size(); surface - .resize( - NonZeroU32::new(size.width).expect("Width must be greater than zero"), - NonZeroU32::new(size.height).expect("Height must be greater than zero"), - ) + .resize(width, height) .expect("Failed to resize the softbuffer surface"); let mut buffer = surface diff --git a/src/icon.rs b/src/icon.rs index 2927665935..b61cdd3b7f 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -49,11 +49,7 @@ impl fmt::Display for BadIcon { } } -impl Error for BadIcon { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(self) - } -} +impl Error for BadIcon {} #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct RgbaIcon { diff --git a/src/platform/android.rs b/src/platform/android.rs index cb0528ef35..b5fde4ad49 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -84,5 +84,10 @@ impl EventLoopBuilderExtAndroid for EventLoopBuilder { /// use winit::platform::android::activity::AndroidApp; /// ``` pub mod activity { + // We enable the `"native-activity"` feature just so that we can build the + // docs, but it'll be very confusing for users to see the docs with that + // feature enabled, so we avoid inlining it so that they're forced to view + // it on the crate's own docs.rs page. + #[doc(no_inline)] pub use android_activity::*; } diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 5a4a9cb25b..b486c2f624 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -200,6 +200,10 @@ impl AppState { ) } + fn has_terminated(&self) -> bool { + matches!(self.state(), AppStateImpl::Terminated) + } + fn will_launch_transition(&mut self, queued_event_handler: Box) { let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { AppStateImpl::NotLaunched { @@ -243,7 +247,7 @@ impl AppState { fn wakeup_transition(&mut self) -> Option { // before `AppState::did_finish_launching` is called, pretend there is no running // event loop. - if !self.has_launched() { + if !self.has_launched() || self.has_terminated() { return None; } @@ -390,7 +394,7 @@ impl AppState { } fn events_cleared_transition(&mut self) { - if !self.has_launched() { + if !self.has_launched() || self.has_terminated() { return; } let (waiting_event_handler, old) = match self.take_state() { @@ -586,6 +590,10 @@ pub(crate) fn handle_nonuser_events>( events: I, ) { let mut this = AppState::get_mut(mtm); + if this.has_terminated() { + return; + } + let (mut event_handler, active_control_flow, processing_redraws) = match this.try_user_callback_transition() { UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { @@ -737,7 +745,7 @@ fn handle_user_events(mtm: MainThreadMarker) { pub fn handle_main_events_cleared(mtm: MainThreadMarker) { let mut this = AppState::get_mut(mtm); - if !this.has_launched() { + if !this.has_launched() || this.has_terminated() { return; } match this.state_mut() { diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index dab69606ee..a260f78aec 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -289,7 +289,7 @@ fn setup_control_flow_observers() { #[allow(non_upper_case_globals)] match activity { kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm), - kCFRunLoopExit => unimplemented!(), // not expected to ever happen + kCFRunLoopExit => {} // may happen when running on macOS _ => unreachable!(), } } @@ -304,7 +304,7 @@ fn setup_control_flow_observers() { #[allow(non_upper_case_globals)] match activity { kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm), - kCFRunLoopExit => unimplemented!(), // not expected to ever happen + kCFRunLoopExit => {} // may happen when running on macOS _ => unreachable!(), } } diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index f9f3187efe..0b04ce932e 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -1,6 +1,5 @@ //! The state of the window, which is shared with the event-loop. -use std::mem::ManuallyDrop; use std::num::NonZeroU32; use std::sync::{Arc, Weak}; use std::time::Duration; @@ -55,9 +54,6 @@ pub struct WindowState { /// The connection to Wayland server. pub connection: Connection, - /// The underlying SCTK window. - pub window: ManuallyDrop, - /// The window frame, which is created from the configure request. frame: Option, @@ -149,6 +145,9 @@ pub struct WindowState { /// /// The value is the serial of the event triggered moved. has_pending_move: Option, + + /// The underlying SCTK window. + pub window: Window, } impl WindowState { @@ -206,7 +205,7 @@ impl WindowState { title: String::default(), transparent: false, viewport, - window: ManuallyDrop::new(window), + window, } } @@ -271,7 +270,7 @@ impl WindowState { && !self.csd_fails { match WinitFrame::new( - &*self.window, + &self.window, shm, subcompositor.clone(), self.queue_handle.clone(), @@ -1026,13 +1025,6 @@ impl WindowState { impl Drop for WindowState { fn drop(&mut self) { - let surface = self.window.wl_surface().clone(); - unsafe { - ManuallyDrop::drop(&mut self.window); - } - - // Cleanup objects. - if let Some(blur) = self.blur.take() { blur.release(); } @@ -1045,7 +1037,8 @@ impl Drop for WindowState { viewport.destroy(); } - surface.destroy(); + // NOTE: the wl_surface used by the window is being cleaned up when + // dropping SCTK `Window`. } } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 3e3c0f5e91..6395d35828 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -570,6 +570,14 @@ impl EventProcessor { event: WindowEvent::Destroyed, }); } + ffi::PropertyNotify => { + let xev: &ffi::XPropertyEvent = xev.as_ref(); + let atom = xev.atom as xproto::Atom; + + if atom == xproto::Atom::from(xproto::AtomEnum::RESOURCE_MANAGER) { + self.process_dpi_change(&mut callback); + } + } ffi::VisibilityNotify => { let xev: &ffi::XVisibilityEvent = xev.as_ref(); @@ -1306,72 +1314,7 @@ impl EventProcessor { } } if event_type == self.randr_event_offset as c_int { - // In the future, it would be quite easy to emit monitor hotplug events. - let prev_list = wt.xconn.invalidate_cached_monitor_list(); - if let Some(prev_list) = prev_list { - let new_list = wt - .xconn - .available_monitors() - .expect("Failed to get monitor list"); - for new_monitor in new_list { - // Previous list may be empty, in case of disconnecting and - // reconnecting the only one monitor. We still need to emit events in - // this case. - let maybe_prev_scale_factor = prev_list - .iter() - .find(|prev_monitor| prev_monitor.name == new_monitor.name) - .map(|prev_monitor| prev_monitor.scale_factor); - if Some(new_monitor.scale_factor) != maybe_prev_scale_factor { - for (window_id, window) in wt.windows.borrow().iter() { - if let Some(window) = window.upgrade() { - // Check if the window is on this monitor - let monitor = - window.shared_state_lock().last_monitor.clone(); - if monitor.name == new_monitor.name { - let (width, height) = window.inner_size_physical(); - let (new_width, new_height) = window.adjust_for_dpi( - // If we couldn't determine the previous scale - // factor (e.g., because all monitors were closed - // before), just pick whatever the current monitor - // has set as a baseline. - maybe_prev_scale_factor - .unwrap_or(monitor.scale_factor), - new_monitor.scale_factor, - width, - height, - &window.shared_state_lock(), - ); - - let window_id = crate::window::WindowId(*window_id); - let old_inner_size = PhysicalSize::new(width, height); - let inner_size = Arc::new(Mutex::new( - PhysicalSize::new(new_width, new_height), - )); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor: new_monitor.scale_factor, - inner_size_writer: InnerSizeWriter::new( - Arc::downgrade(&inner_size), - ), - }, - }); - - let new_inner_size = *inner_size.lock().unwrap(); - drop(inner_size); - - if new_inner_size != old_inner_size { - let (new_width, new_height) = new_inner_size.into(); - window.request_inner_size_physical( - new_width, new_height, - ); - } - } - } - } - } - } - } + self.process_dpi_change(&mut callback); } } } @@ -1464,6 +1407,45 @@ impl EventProcessor { }); } } + + fn process_dpi_change(&self, callback: &mut F) + where + F: FnMut(Event), + { + let wt = get_xtarget(&self.target); + + // In the future, it would be quite easy to emit monitor hotplug events. + let prev_list = { + let prev_list = wt.xconn.invalidate_cached_monitor_list(); + match prev_list { + Some(prev_list) => prev_list, + None => return, + } + }; + + let new_list = wt + .xconn + .available_monitors() + .expect("Failed to get monitor list"); + for new_monitor in new_list { + // Previous list may be empty, in case of disconnecting and + // reconnecting the only one monitor. We still need to emit events in + // this case. + let maybe_prev_scale_factor = prev_list + .iter() + .find(|prev_monitor| prev_monitor.name == new_monitor.name) + .map(|prev_monitor| prev_monitor.scale_factor); + if Some(new_monitor.scale_factor) != maybe_prev_scale_factor { + for window in wt.windows.borrow().iter().filter_map(|(_, w)| w.upgrade()) { + window.refresh_dpi_for_monitor( + &new_monitor, + maybe_prev_scale_factor, + &mut *callback, + ) + } + } + } + } } fn is_first_touch(first: &mut Option, num: &mut u32, id: u64, phase: TouchPhase) -> bool { diff --git a/src/platform_impl/linux/x11/util/cursor.rs b/src/platform_impl/linux/x11/util/cursor.rs index 5845511d07..8d62cfa7f0 100644 --- a/src/platform_impl/linux/x11/util/cursor.rs +++ b/src/platform_impl/linux/x11/util/cursor.rs @@ -1,4 +1,5 @@ use std::ffi::CString; +use std::iter; use x11rb::connection::Connection; @@ -56,10 +57,22 @@ impl XConnection { None => return self.create_empty_cursor(), }; - let name = CString::new(cursor.name()).unwrap(); - unsafe { - (self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char) + let mut xcursor = 0; + for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) { + let name = CString::new(name).unwrap(); + xcursor = unsafe { + (self.xcursor.XcursorLibraryLoadCursor)( + self.display, + name.as_ptr() as *const c_char, + ) + }; + + if xcursor != 0 { + break; + } } + + xcursor } fn update_cursor(&self, window: xproto::Window, cursor: ffi::Cursor) -> Result<(), X11Error> { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 1b08869f5f..b2e45bd91c 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -22,6 +22,7 @@ use x11rb::{ use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, + event::{Event, InnerSizeWriter, WindowEvent}, event_loop::AsyncRequestSerial, platform_impl::{ x11::{atoms::*, MonitorHandle as X11MonitorHandle, WakeSender, X11Error}, @@ -276,7 +277,8 @@ impl UnownedWindow { | EventMask::KEYMAP_STATE | EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE - | EventMask::POINTER_MOTION; + | EventMask::POINTER_MOTION + | EventMask::PROPERTY_CHANGE; aux = aux.event_mask(event_mask).border_pixel(0); @@ -923,6 +925,51 @@ impl UnownedWindow { }) } + /// Refresh the API for the given monitor. + #[inline] + pub(super) fn refresh_dpi_for_monitor( + &self, + new_monitor: &X11MonitorHandle, + maybe_prev_scale_factor: Option, + mut callback: impl FnMut(Event), + ) { + // Check if the self is on this monitor + let monitor = self.shared_state_lock().last_monitor.clone(); + if monitor.name == new_monitor.name { + let (width, height) = self.inner_size_physical(); + let (new_width, new_height) = self.adjust_for_dpi( + // If we couldn't determine the previous scale + // factor (e.g., because all monitors were closed + // before), just pick whatever the current monitor + // has set as a baseline. + maybe_prev_scale_factor.unwrap_or(monitor.scale_factor), + new_monitor.scale_factor, + width, + height, + &self.shared_state_lock(), + ); + + let window_id = crate::window::WindowId(self.id()); + let old_inner_size = PhysicalSize::new(width, height); + let inner_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height))); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor: new_monitor.scale_factor, + inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)), + }, + }); + + let new_inner_size = *inner_size.lock().unwrap(); + drop(inner_size); + + if new_inner_size != old_inner_size { + let (new_width, new_height) = new_inner_size.into(); + self.request_inner_size_physical(new_width, new_height); + } + } + } + fn set_minimized_inner(&self, minimized: bool) -> Result, X11Error> { let atoms = self.xconn.atoms(); diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 114c124f5e..33cc1d88e6 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -329,7 +329,7 @@ impl Handler { ) { if let Some(ref mut callback) = *self.callback.lock().unwrap() { let new_inner_size = Arc::new(Mutex::new(suggested_size)); - let event = Event::WindowEvent { + let scale_factor_changed_event = Event::WindowEvent { window_id: WindowId(window.id()), event: WindowEvent::ScaleFactorChanged { scale_factor, @@ -337,13 +337,19 @@ impl Handler { }, }; - callback.handle_nonuser_event(event); + callback.handle_nonuser_event(scale_factor_changed_event); let physical_size = *new_inner_size.lock().unwrap(); drop(new_inner_size); let logical_size = physical_size.to_logical(scale_factor); let size = NSSize::new(logical_size.width, logical_size.height); window.setContentSize(size); + + let resized_event = Event::WindowEvent { + window_id: WindowId(window.id()), + event: WindowEvent::Resized(physical_size), + }; + callback.handle_nonuser_event(resized_event); } } } diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index af5af175a7..9c4a9a95c6 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -156,13 +156,11 @@ pub(crate) fn create_key_event( // Also not checking if this is a release event because then this issue would // still affect the key release. Some(text) if !has_ctrl => Key::Character(text.clone()), - _ => { - let modifierless_chars = match key_without_modifiers.as_ref() { - Key::Character(ch) => ch, - _ => "", - }; - get_logical_key_char(ns_event, modifierless_chars) - } + _ => match key_without_modifiers.as_ref() { + Key::Character(ch) => get_logical_key_char(ns_event, ch), + // Don't try to get text for events which likely don't have it. + _ => key_without_modifiers.clone(), + }, }; (logical_key, key_without_modifiers) diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index c5b5655761..bac77f9e2b 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -74,8 +74,8 @@ enum ImeState { bitflags! { #[derive(Debug, Clone, Copy, PartialEq)] struct ModLocationMask: u8 { - const LEFT = 1; - const RIGHT = 2; + const LEFT = 0b0001; + const RIGHT = 0b0010; } } impl ModLocationMask { @@ -88,13 +88,13 @@ impl ModLocationMask { } } -fn key_to_modifier(key: &Key) -> ModifiersState { +fn key_to_modifier(key: &Key) -> Option { match key { - Key::Named(NamedKey::Alt) => ModifiersState::ALT, - Key::Named(NamedKey::Control) => ModifiersState::CONTROL, - Key::Named(NamedKey::Super) => ModifiersState::SUPER, - Key::Named(NamedKey::Shift) => ModifiersState::SHIFT, - _ => unreachable!(), + Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT), + Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL), + Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER), + Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT), + _ => None, } } @@ -924,91 +924,96 @@ impl WinitView { // event, thus we can't generate regular presses based on that. The `ModifiersChanged` // later will work though, since the flags are attached to the event and contain valid // information. - if is_flags_changed_event && ns_event.key_code() != 0 { - let scancode = ns_event.key_code(); - let physical_key = PhysicalKey::from_scancode(scancode as u32); - - // We'll correct the `is_press` later. - let mut event = create_key_event(ns_event, false, false, Some(physical_key)); - - let key = code_to_key(physical_key, scancode); - let event_modifier = key_to_modifier(&key); - event.physical_key = physical_key; - event.logical_key = key.clone(); - event.location = code_to_location(physical_key); - let location_mask = ModLocationMask::from_location(event.location); - - let mut phys_mod_state = self.state.phys_modifiers.borrow_mut(); - let phys_mod = phys_mod_state - .entry(key) - .or_insert(ModLocationMask::empty()); - - let is_active = current_modifiers.state().contains(event_modifier); - let mut events = VecDeque::with_capacity(2); - - // There is no API for getting whether the button was pressed or released - // during this event. For this reason we have to do a bit of magic below - // to come up with a good guess whether this key was pressed or released. - // (This is not trivial because there are multiple buttons that may affect - // the same modifier) - if !is_active { - event.state = Released; - if phys_mod.contains(ModLocationMask::LEFT) { - let mut event = event.clone(); - event.location = KeyLocation::Left; - event.physical_key = get_left_modifier_code(&event.logical_key).into(); - events.push_back(WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - event, - is_synthetic: false, - }); - } - if phys_mod.contains(ModLocationMask::RIGHT) { - event.location = KeyLocation::Right; - event.physical_key = get_right_modifier_code(&event.logical_key).into(); + 'send_event: { + if is_flags_changed_event && ns_event.key_code() != 0 { + let scancode = ns_event.key_code(); + let physical_key = PhysicalKey::from_scancode(scancode as u32); + + // We'll correct the `is_press` later. + let mut event = create_key_event(ns_event, false, false, Some(physical_key)); + + let key = code_to_key(physical_key, scancode); + // Ignore processing of unkown modifiers because we can't determine whether + // it was pressed or release reliably. + let Some(event_modifier) = key_to_modifier(&key) else { + break 'send_event; + }; + event.physical_key = physical_key; + event.logical_key = key.clone(); + event.location = code_to_location(physical_key); + let location_mask = ModLocationMask::from_location(event.location); + + let mut phys_mod_state = self.state.phys_modifiers.borrow_mut(); + let phys_mod = phys_mod_state + .entry(key) + .or_insert(ModLocationMask::empty()); + + let is_active = current_modifiers.state().contains(event_modifier); + let mut events = VecDeque::with_capacity(2); + + // There is no API for getting whether the button was pressed or released + // during this event. For this reason we have to do a bit of magic below + // to come up with a good guess whether this key was pressed or released. + // (This is not trivial because there are multiple buttons that may affect + // the same modifier) + if !is_active { + event.state = Released; + if phys_mod.contains(ModLocationMask::LEFT) { + let mut event = event.clone(); + event.location = KeyLocation::Left; + event.physical_key = get_left_modifier_code(&event.logical_key).into(); + events.push_back(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }); + } + if phys_mod.contains(ModLocationMask::RIGHT) { + event.location = KeyLocation::Right; + event.physical_key = get_right_modifier_code(&event.logical_key).into(); + events.push_back(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }); + } + *phys_mod = ModLocationMask::empty(); + } else { + if *phys_mod == location_mask { + // Here we hit a contradiction: + // The modifier state was "changed" to active, + // yet the only pressed modifier key was the one that we + // just got a change event for. + // This seemingly means that the only pressed modifier is now released, + // but at the same time the modifier became active. + // + // But this scenario is possible if we released modifiers + // while the application was not in focus. (Because we don't + // get informed of modifier key events while the application + // is not focused) + + // In this case we prioritize the information + // about the current modifier state which means + // that the button was pressed. + event.state = Pressed; + } else { + phys_mod.toggle(location_mask); + let is_pressed = phys_mod.contains(location_mask); + event.state = if is_pressed { Pressed } else { Released }; + } + events.push_back(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event, is_synthetic: false, }); } - *phys_mod = ModLocationMask::empty(); - } else { - // is_active - if *phys_mod == location_mask { - // Here we hit a contradiction: - // The modifier state was "changed" to active, - // yet the only pressed modifier key was the one that we - // just got a change event for. - // This seemingly means that the only pressed modifier is now released, - // but at the same time the modifier became active. - // - // But this scenario is possible if we released modifiers - // while the application was not in focus. (Because we don't - // get informed of modifier key events while the application - // is not focused) - - // In this case we prioritize the information - // about the current modifier state which means - // that the button was pressed. - event.state = Pressed; - } else { - phys_mod.toggle(location_mask); - let is_pressed = phys_mod.contains(location_mask); - event.state = if is_pressed { Pressed } else { Released }; - } - events.push_back(WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - event, - is_synthetic: false, - }); - } + drop(phys_mod_state); - drop(phys_mod_state); - - for event in events { - self.queue_event(event); + for event in events { + self.queue_event(event); + } } } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 2bedd9a4be..d4263920af 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -356,19 +356,6 @@ impl EventLoop { /// Wait for one message and dispatch it, optionally with a timeout fn wait_and_dispatch_message(&mut self, timeout: Option) { - let start = Instant::now(); - - let runner = &self.window_target.p.runner_shared; - - let control_flow_timeout = match runner.control_flow() { - ControlFlow::Wait => None, - ControlFlow::Poll => Some(Duration::ZERO), - ControlFlow::WaitUntil(wait_deadline) => { - Some(wait_deadline.saturating_duration_since(start)) - } - }; - let timeout = min_timeout(control_flow_timeout, timeout); - fn get_msg_with_timeout(msg: &mut MSG, timeout: Option) -> PumpStatus { unsafe { // A timeout of None means wait indefinitely (so we don't need to call SetTimer) @@ -404,6 +391,8 @@ impl EventLoop { } } + let runner = &self.window_target.p.runner_shared; + // We aim to be consistent with the MacOS backend which has a RunLoop // observer that will dispatch AboutToWait when about to wait for // events, and NewEvents after the RunLoop wakes up. @@ -415,6 +404,16 @@ impl EventLoop { // runner.prepare_wait(); + let control_flow_timeout = match runner.control_flow() { + ControlFlow::Wait => None, + ControlFlow::Poll => Some(Duration::ZERO), + ControlFlow::WaitUntil(wait_deadline) => { + let start = Instant::now(); + Some(wait_deadline.saturating_duration_since(start)) + } + }; + let timeout = min_timeout(control_flow_timeout, timeout); + // # Safety // The Windows API has no documented requirement for bitwise // initializing a `MSG` struct (it can be uninitialized memory for the C diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 3f891d5be8..7d5bc6d746 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -472,27 +472,41 @@ impl Window { } unsafe fn handle_os_dragging(&self, wparam: WPARAM) { - let points = { - let mut pos = unsafe { mem::zeroed() }; - unsafe { GetCursorPos(&mut pos) }; - pos - }; - let points = POINTS { - x: points.x as i16, - y: points.y as i16, - }; - unsafe { ReleaseCapture() }; + let window = self.window.clone(); + let window_state = self.window_state.clone(); - self.window_state_lock().dragging = true; + self.thread_executor.execute_in_thread(move || { + { + let mut guard = window_state.lock().unwrap(); + if !guard.dragging { + guard.dragging = true; + } else { + return; + } + } - unsafe { - PostMessageW( - self.hwnd(), - WM_NCLBUTTONDOWN, - wparam, - &points as *const _ as LPARAM, - ) - }; + let points = { + let mut pos = unsafe { mem::zeroed() }; + unsafe { GetCursorPos(&mut pos) }; + pos + }; + let points = POINTS { + x: points.x as i16, + y: points.y as i16, + }; + + // ReleaseCapture needs to execute on the main thread + unsafe { ReleaseCapture() }; + + unsafe { + PostMessageW( + window.0, + WM_NCLBUTTONDOWN, + wparam, + &points as *const _ as LPARAM, + ) + }; + }); } #[inline] diff --git a/src/window.rs b/src/window.rs index 8251e92749..87531e8ad8 100644 --- a/src/window.rs +++ b/src/window.rs @@ -600,11 +600,11 @@ impl Window { self.window.maybe_queue_on_main(|w| w.request_redraw()) } - /// Notify the windowing system that you're before presenting to the window. + /// Notify the windowing system before presenting to the window. /// - /// You should call this event after you've done drawing operations, but before you submit + /// You should call this event after your drawing operations, but before you submit /// the buffer to the display or commit your drawings. Doing so will help winit to properly - /// schedule and do assumptions about its internal state. For example, it could properly + /// schedule and make assumptions about its internal state. For example, it could properly /// throttle [`WindowEvent::RedrawRequested`]. /// /// ## Example