From d6f0b0ff3f7ccdb2f6fcf77c43617f805cdcb75f Mon Sep 17 00:00:00 2001 From: John Nunley Date: Wed, 21 Aug 2024 21:14:07 -0700 Subject: [PATCH 01/25] m: Ignore mutex poisoning in X11_BACKEND A panic doesn't really put any of the fields in XConnection into an invalid state, so there is no real reason to panic when poisoning is detected. So just ignore the poison. Closes #3870 Signed-off-by: John Nunley --- src/platform_impl/linux/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index bee440189d..c429722b37 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -650,7 +650,7 @@ unsafe extern "C" fn x_error_callback( display: *mut x11::ffi::Display, event: *mut x11::ffi::XErrorEvent, ) -> c_int { - let xconn_lock = X11_BACKEND.lock().unwrap(); + let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()); if let Ok(ref xconn) = *xconn_lock { // Call all the hooks. let mut error_handled = false; @@ -777,7 +777,7 @@ impl EventLoop { #[cfg(x11_platform)] fn new_x11_any_thread() -> Result, EventLoopError> { - let xconn = match X11_BACKEND.lock().unwrap().as_ref() { + let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() { Ok(xconn) => xconn.clone(), Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())), }; From 2b586ca149d4b8baca043ddbfe79ebc1e94eff0d Mon Sep 17 00:00:00 2001 From: John Nunley Date: Fri, 23 Aug 2024 04:47:40 -0700 Subject: [PATCH 02/25] x11: use more information in X11 "not supported" errors This makes it so, when X11 fails to initialize due to not loading a library, it provides more verbose information on what exactly happened. Fixes #3883. Signed-off-by: John Nunley Co-authored-by: Kirill Chibisov --- src/platform_impl/linux/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index c429722b37..ef843d89f7 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -117,6 +117,8 @@ pub(crate) static X11_BACKEND: Lazy, XNotSupported pub enum OsError { Misc(&'static str), #[cfg(x11_platform)] + XNotSupported(XNotSupported), + #[cfg(x11_platform)] XError(Arc), #[cfg(wayland_platform)] WaylandError(Arc), @@ -127,6 +129,8 @@ impl fmt::Display for OsError { match *self { OsError::Misc(e) => _f.pad(e), #[cfg(x11_platform)] + OsError::XNotSupported(ref e) => fmt::Display::fmt(e, _f), + #[cfg(x11_platform)] OsError::XError(ref e) => fmt::Display::fmt(e, _f), #[cfg(wayland_platform)] OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f), @@ -779,7 +783,9 @@ impl EventLoop { fn new_x11_any_thread() -> Result, EventLoopError> { let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() { Ok(xconn) => xconn.clone(), - Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())), + Err(err) => { + return Err(EventLoopError::Os(os_error!(OsError::XNotSupported(err.clone())))) + }, }; Ok(EventLoop::X(x11::EventLoop::new(xconn))) From 400fbf96d2e8ecb44a76f28ef97a02b394290e57 Mon Sep 17 00:00:00 2001 From: Tarek Abdel Sater Date: Wed, 4 Sep 2024 16:44:05 +0400 Subject: [PATCH 03/25] macOS: add option to explicitly hide menu/dock in Borderless (#3882) --- src/changelog/unreleased.md | 5 +++++ src/platform/macos.rs | 26 ++++++++++++++++++++++ src/platform_impl/macos/window_delegate.rs | 25 ++++++++++++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..1936e13c3b 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,8 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Added + +- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game` + to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games. diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 66f25c92b2..a6c075717a 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -94,6 +94,12 @@ pub trait WindowExtMacOS { /// Getter for the [`WindowExtMacOS::set_option_as_alt`]. fn option_as_alt(&self) -> OptionAsAlt; + + /// Disable the Menu Bar and Dock in Borderless Fullscreen mode. Useful for games. + fn set_borderless_game(&self, borderless_game: bool); + + /// Getter for the [`WindowExtMacOS::set_borderless_game`]. + fn is_borderless_game(&self) -> bool; } impl WindowExtMacOS for Window { @@ -166,6 +172,18 @@ impl WindowExtMacOS for Window { fn option_as_alt(&self) -> OptionAsAlt { self.window.maybe_wait_on_main(|w| w.option_as_alt()) } + + #[inline] + fn set_borderless_game(&self, borderless_game: bool) { + let window = self.as_any().downcast_ref::().unwrap(); + window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game)) + } + + #[inline] + fn is_borderless_game(&self) -> bool { + let window = self.as_any().downcast_ref::().unwrap(); + window.maybe_wait_on_main(|w| w.is_borderless_game()) + } } /// Corresponds to `NSApplicationActivationPolicy`. @@ -216,6 +234,8 @@ pub trait WindowAttributesExtMacOS { /// /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set. fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self; + /// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set. + fn with_borderless_game(self, borderless_game: bool) -> Self; } impl WindowAttributesExtMacOS for WindowAttributes { @@ -284,6 +304,12 @@ impl WindowAttributesExtMacOS for WindowAttributes { self.platform_specific.option_as_alt = option_as_alt; self } + + #[inline] + fn with_borderless_game(mut self, borderless_game: bool) -> Self { + self.platform_specific.borderless_game = borderless_game; + self + } } pub trait EventLoopBuilderExtMacOS { diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 2a7877b8bc..5424170e71 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -55,6 +55,7 @@ pub struct PlatformSpecificWindowAttributes { pub accepts_first_mouse: bool, pub tabbing_identifier: Option, pub option_as_alt: OptionAsAlt, + pub borderless_game: bool, } impl Default for PlatformSpecificWindowAttributes { @@ -72,6 +73,7 @@ impl Default for PlatformSpecificWindowAttributes { accepts_first_mouse: true, tabbing_identifier: None, option_as_alt: Default::default(), + borderless_game: false, } } } @@ -120,6 +122,7 @@ pub(crate) struct State { standard_frame: Cell>, is_simple_fullscreen: Cell, saved_style: Cell>, + is_borderless_game: Cell, } declare_class!( @@ -725,6 +728,7 @@ impl WindowDelegate { standard_frame: Cell::new(None), is_simple_fullscreen: Cell::new(false), saved_style: Cell::new(None), + is_borderless_game: Cell::new(attrs.platform_specific.borderless_game), }); let delegate: Retained = unsafe { msg_send_id![super(delegate), init] }; @@ -1409,7 +1413,7 @@ impl WindowDelegate { } match (old_fullscreen, fullscreen) { - (None, Some(_)) => { + (None, Some(fullscreen)) => { // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we // set a normal style temporarily. The previous state will be // restored in `WindowDelegate::window_did_exit_fullscreen`. @@ -1419,6 +1423,17 @@ impl WindowDelegate { self.set_style_mask(required); self.ivars().saved_style.set(Some(curr_mask)); } + + // In borderless games, we want to disable the dock and menu bar + // by setting the presentation options. We do this here rather than in + // `window:willUseFullScreenPresentationOptions` because for some reason + // the menu bar remains interactable despite being hidden. + if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) { + let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; + app.setPresentationOptions(presentation_options); + } + toggle_fullscreen(self.window()); }, (Some(Fullscreen::Borderless(_)), None) => { @@ -1829,6 +1844,14 @@ impl WindowExtMacOS for WindowDelegate { fn option_as_alt(&self) -> OptionAsAlt { self.view().option_as_alt() } + + fn set_borderless_game(&self, borderless_game: bool) { + self.ivars().is_borderless_game.set(borderless_game); + } + + fn is_borderless_game(&self) -> bool { + self.ivars().is_borderless_game.get() + } } const DEFAULT_STANDARD_FRAME: NSRect = From e1eb2515f4874d476c8e80b8f0a9d8868bc847ee Mon Sep 17 00:00:00 2001 From: purajit Date: Mon, 16 Sep 2024 06:49:18 -0700 Subject: [PATCH 04/25] Prevent winit from overriding LSUIElement in package manifests (#3920) --- src/changelog/unreleased.md | 5 +++++ src/platform/macos.rs | 15 ++++++++------- src/platform_impl/macos/app_state.rs | 15 ++++++++++++--- src/platform_impl/macos/event_loop.rs | 15 ++++++--------- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 1936e13c3b..4e9589855c 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -44,3 +44,8 @@ changelog entry. - On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game` to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games. + +### Fixed + +- On MacOS, package manifest definitions of `LSUIElement` will no longer be overridden with the + default activation policy, unless explicitly provided during initialization. diff --git a/src/platform/macos.rs b/src/platform/macos.rs index a6c075717a..424c9d6213 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -175,14 +175,12 @@ impl WindowExtMacOS for Window { #[inline] fn set_borderless_game(&self, borderless_game: bool) { - let window = self.as_any().downcast_ref::().unwrap(); - window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game)) + self.window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game)) } #[inline] fn is_borderless_game(&self) -> bool { - let window = self.as_any().downcast_ref::().unwrap(); - window.maybe_wait_on_main(|w| w.is_borderless_game()) + self.window.maybe_wait_on_main(|w| w.is_borderless_game()) } } @@ -313,9 +311,12 @@ impl WindowAttributesExtMacOS for WindowAttributes { } pub trait EventLoopBuilderExtMacOS { - /// Sets the activation policy for the application. + /// Sets the activation policy for the application. If used, this will override + /// any relevant settings provided in the package manifest. + /// For instance, `with_activation_policy(ActivationPolicy::Regular)` will prevent + /// the application from running as an "agent", even if LSUIElement is set to true. /// - /// It is set to [`ActivationPolicy::Regular`] by default. + /// If unused, the Winit will honor the package manifest. /// /// # Example /// @@ -367,7 +368,7 @@ pub trait EventLoopBuilderExtMacOS { impl EventLoopBuilderExtMacOS for EventLoopBuilder { #[inline] fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self { - self.platform_specific.activation_policy = activation_policy; + self.platform_specific.activation_policy = Some(activation_policy); self } diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index ee149384b7..71f7c937fc 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -18,7 +18,12 @@ use crate::window::WindowId as RootWindowId; #[derive(Debug)] pub(super) struct AppState { - activation_policy: NSApplicationActivationPolicy, + // <<<<<<< HEAD:src/platform_impl/macos/app_state.rs + // activation_policy: NSApplicationActivationPolicy, + // ======= + activation_policy: Option, + // >>>>>>> 7e819bb2 (Prevent winit from overriding LSUIElement in package manifests + // (#3920)):src/platform_impl/apple/appkit/app_state.rs default_menu: bool, activate_ignoring_other_apps: bool, run_loop: RunLoop, @@ -74,7 +79,7 @@ declare_class!( impl ApplicationDelegate { pub(super) fn new( mtm: MainThreadMarker, - activation_policy: NSApplicationActivationPolicy, + activation_policy: Option, default_menu: bool, activate_ignoring_other_apps: bool, ) -> Retained { @@ -111,7 +116,11 @@ impl ApplicationDelegate { // We need to delay setting the activation policy and activating the app // until `applicationDidFinishLaunching` has been called. Otherwise the // menu bar is initially unresponsive on macOS 10.15. - app.setActivationPolicy(self.ivars().activation_policy); + // If no activation policy is explicitly provided, do not set it at all + // to allow the package manifest to define behavior via LSUIElement. + if let Some(activation_policy) = self.ivars().activation_policy { + app.setActivationPolicy(activation_policy); + } window_activation_hack(&app); #[allow(deprecated)] diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 9b7be282f6..cb0620ac95 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -202,18 +202,14 @@ pub struct EventLoop { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes { - pub(crate) activation_policy: ActivationPolicy, + pub(crate) activation_policy: Option, pub(crate) default_menu: bool, pub(crate) activate_ignoring_other_apps: bool, } impl Default for PlatformSpecificEventLoopAttributes { fn default() -> Self { - Self { - activation_policy: Default::default(), // Regular - default_menu: true, - activate_ignoring_other_apps: true, - } + Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true } } } @@ -235,9 +231,10 @@ impl EventLoop { } let activation_policy = match attributes.activation_policy { - ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular, - ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory, - ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited, + None => None, + Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular), + Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory), + Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited), }; let delegate = ApplicationDelegate::new( mtm, From d0480b905fdb9625a9a5497dba821aed44aac533 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 23 Sep 2024 22:26:21 +0300 Subject: [PATCH 05/25] chore: fix nightly CI on linux/ios Co-authored-by: Mads Marquart --- src/platform/x11.rs | 4 +--- src/platform_impl/ios/app_state.rs | 2 ++ src/platform_impl/linux/mod.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platform/x11.rs b/src/platform/x11.rs index 749c400ab0..4ab900c94c 100644 --- a/src/platform/x11.rs +++ b/src/platform/x11.rs @@ -81,9 +81,7 @@ pub type XWindow = u32; #[inline] pub fn register_xlib_error_hook(hook: XlibErrorHook) { // Append new hook. - unsafe { - crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook); - } + crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook); } /// Additional methods on [`ActiveEventLoop`] that are specific to X11. diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index f62fe5e70a..30019a0701 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -147,6 +147,8 @@ impl AppState { // must be mut because plain `static` requires `Sync` static mut APP_STATE: RefCell> = RefCell::new(None); + #[allow(unknown_lints)] // New lint below + #[allow(static_mut_refs)] // TODO: Use `MainThreadBound` instead. let mut guard = unsafe { APP_STATE.borrow_mut() }; if guard.is_none() { #[inline(never)] diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index ef843d89f7..c4670ce09d 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -647,7 +647,7 @@ pub(crate) enum PlatformCustomCursor { /// Hooks for X11 errors. #[cfg(x11_platform)] -pub(crate) static mut XLIB_ERROR_HOOKS: Mutex> = Mutex::new(Vec::new()); +pub(crate) static XLIB_ERROR_HOOKS: Mutex> = Mutex::new(Vec::new()); #[cfg(x11_platform)] unsafe extern "C" fn x_error_callback( @@ -658,7 +658,7 @@ unsafe extern "C" fn x_error_callback( if let Ok(ref xconn) = *xconn_lock { // Call all the hooks. let mut error_handled = false; - for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() { + for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() { error_handled |= hook(display as *mut _, event as *mut _); } From c48cd9e315d1df4af7c3aea7584050928bfba48b Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 24 Sep 2024 01:06:10 +0200 Subject: [PATCH 06/25] macOS: Fix move event sometimes being triggered on resize (#3914) --- src/changelog/unreleased.md | 1 + src/platform_impl/macos/window_delegate.rs | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 4e9589855c..c15aa3ebab 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -47,5 +47,6 @@ changelog entry. ### Fixed +- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize. - On MacOS, package manifest definitions of `LSUIElement` will no longer be overridden with the default activation policy, unless explicitly provided during initialization. diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 5424170e71..8aa8a269f6 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -87,8 +87,8 @@ pub(crate) struct State { // During `windowDidResize`, we use this to only send Moved if the position changed. // - // This is expressed in native screen coordinates. - previous_position: Cell>, + // This is expressed in desktop coordinates, and flipped to match Winit's coordinate system. + previous_position: Cell, // Used to prevent redundant events. previous_scale_factor: Cell, @@ -714,7 +714,7 @@ impl WindowDelegate { let delegate = mtm.alloc().set_ivars(State { app_delegate: app_delegate.retain(), window: window.retain(), - previous_position: Cell::new(None), + previous_position: Cell::new(flip_window_screen_coordinates(window.frame())), previous_scale_factor: Cell::new(scale_factor), resize_increments: Cell::new(resize_increments), decorations: Cell::new(attrs.decorations), @@ -839,13 +839,12 @@ impl WindowDelegate { } fn emit_move_event(&self) { - let frame = self.window().frame(); - if self.ivars().previous_position.get() == Some(frame.origin) { + let position = flip_window_screen_coordinates(self.window().frame()); + if self.ivars().previous_position.get() == position { return; } - self.ivars().previous_position.set(Some(frame.origin)); + self.ivars().previous_position.set(position); - let position = flip_window_screen_coordinates(frame); let position = LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor()); self.queue_event(WindowEvent::Moved(position)); From d3d55855fec860a045f5236d4319d8aab6ad38f3 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 13 Oct 2024 21:57:02 +0300 Subject: [PATCH 07/25] x11: don't forward key events to IME when it's disabled Fixes #3815. --- src/changelog/unreleased.md | 1 + src/platform_impl/linux/x11/event_processor.rs | 18 +++++++++++++++--- src/platform_impl/linux/x11/ime/mod.rs | 10 ++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index c15aa3ebab..2c2a254be2 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -50,3 +50,4 @@ changelog entry. - On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize. - On MacOS, package manifest definitions of `LSUIElement` will no longer be overridden with the default activation policy, unless explicitly provided during initialization. +- On X11, key events forward to IME anyway, even when it's disabled. diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 9c6b95a2ac..14f80b67ce 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -145,8 +145,19 @@ impl EventProcessor { { let event_type = xev.get_type(); - if self.filter_event(xev) { - if event_type == xlib::KeyPress || event_type == xlib::KeyRelease { + // If we have IME disabled, don't try to `filter_event`, since only IME can consume them + // and forward back. This is not desired for e.g. games since some IMEs may delay the input + // and game can toggle IME back when e.g. typing into some field where latency won't really + // matter. + if event_type == xlib::KeyPress || event_type == xlib::KeyRelease { + let wt = Self::window_target(&self.target); + let ime = wt.ime.as_ref(); + let window = self.active_window.map(|window| window as XWindow); + let forward_to_ime = ime + .and_then(|ime| window.map(|window| ime.borrow().is_ime_allowed(window))) + .unwrap_or(false); + + if forward_to_ime && self.filter_event(xev) { let xev: &XKeyEvent = xev.as_ref(); if self.xmodmap.is_modifier(xev.keycode as u8) { // Don't grow the buffer past the `MAX_MOD_REPLAY_LEN`. This could happen @@ -159,7 +170,8 @@ impl EventProcessor { self.xfiltered_modifiers.push_front(xev.serial); } } - return; + } else { + self.filter_event(xev); } match event_type { diff --git a/src/platform_impl/linux/x11/ime/mod.rs b/src/platform_impl/linux/x11/ime/mod.rs index 606e00d9f3..063598a3ea 100644 --- a/src/platform_impl/linux/x11/ime/mod.rs +++ b/src/platform_impl/linux/x11/ime/mod.rs @@ -226,6 +226,16 @@ impl Ime { // Create new context supporting IME input. let _ = self.create_context(window, allowed); } + + pub fn is_ime_allowed(&self, window: ffi::Window) -> bool { + if self.is_destroyed() { + false + } else if let Some(Some(context)) = self.inner.contexts.get(&window) { + context.is_allowed() + } else { + false + } + } } impl Drop for Ime { From a8e5c6eed1c6789b20cb84c610067bad37206c46 Mon Sep 17 00:00:00 2001 From: Shane Celis Date: Tue, 22 Oct 2024 07:00:53 -0400 Subject: [PATCH 08/25] macOS: fix panic during drag_window Return error from it instead of unwrapping. --- src/changelog/unreleased.md | 3 +- src/platform_impl/apple/appkit/window.rs | 369 +++++++++++++++++++++ src/platform_impl/macos/window_delegate.rs | 3 +- 3 files changed, 373 insertions(+), 2 deletions(-) create mode 100644 src/platform_impl/apple/appkit/window.rs diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 2c2a254be2..086df8be02 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -48,6 +48,7 @@ changelog entry. ### Fixed - On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize. -- On MacOS, package manifest definitions of `LSUIElement` will no longer be overridden with the +- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the default activation policy, unless explicitly provided during initialization. +- On macOS, fix crash when calling `drag_window()` without a left click present. - On X11, key events forward to IME anyway, even when it's disabled. diff --git a/src/platform_impl/apple/appkit/window.rs b/src/platform_impl/apple/appkit/window.rs new file mode 100644 index 0000000000..be59dbed99 --- /dev/null +++ b/src/platform_impl/apple/appkit/window.rs @@ -0,0 +1,369 @@ +#![allow(clippy::unnecessary_cast)] + +use dpi::{Position, Size}; +use objc2::rc::{autoreleasepool, Retained}; +use objc2::{declare_class, mutability, ClassType, DeclaredClass}; +use objc2_app_kit::{NSResponder, NSWindow}; +use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject}; + +use super::event_loop::ActiveEventLoop; +use super::window_delegate::WindowDelegate; +use crate::error::RequestError; +use crate::monitor::MonitorHandle as CoreMonitorHandle; +use crate::window::{ + Cursor, Fullscreen, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow, + WindowAttributes, WindowButtons, WindowId, WindowLevel, +}; + +pub(crate) struct Window { + window: MainThreadBound>, + /// The window only keeps a weak reference to this, so we must keep it around here. + delegate: MainThreadBound>, +} + +impl Window { + pub(crate) fn new( + window_target: &ActiveEventLoop, + attributes: WindowAttributes, + ) -> Result { + let mtm = window_target.mtm; + let delegate = + autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?; + Ok(Window { + window: MainThreadBound::new(delegate.window().retain(), mtm), + delegate: MainThreadBound::new(delegate, mtm), + }) + } + + pub(crate) fn maybe_wait_on_main( + &self, + f: impl FnOnce(&WindowDelegate) -> R + Send, + ) -> R { + self.delegate.get_on_main(|delegate| f(delegate)) + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub(crate) fn raw_window_handle_rwh_06( + &self, + ) -> Result { + if let Some(mtm) = MainThreadMarker::new() { + Ok(self.delegate.get(mtm).raw_window_handle_rwh_06()) + } else { + Err(rwh_06::HandleError::Unavailable) + } + } + + #[cfg(feature = "rwh_06")] + #[inline] + pub(crate) fn raw_display_handle_rwh_06( + &self, + ) -> Result { + Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new())) + } +} + +impl Drop for Window { + fn drop(&mut self) { + // Restore the video mode. + if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_))) { + self.set_fullscreen(None); + } + + self.window.get_on_main(|window| autoreleasepool(|_| window.close())) + } +} + +#[cfg(feature = "rwh_06")] +impl rwh_06::HasDisplayHandle for Window { + fn display_handle(&self) -> Result, rwh_06::HandleError> { + let raw = self.raw_display_handle_rwh_06()?; + unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) } + } +} + +#[cfg(feature = "rwh_06")] +impl rwh_06::HasWindowHandle for Window { + fn window_handle(&self) -> Result, rwh_06::HandleError> { + let raw = self.raw_window_handle_rwh_06()?; + unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) } + } +} + +impl CoreWindow for Window { + fn id(&self) -> crate::window::WindowId { + self.maybe_wait_on_main(|delegate| delegate.id()) + } + + fn scale_factor(&self) -> f64 { + self.maybe_wait_on_main(|delegate| delegate.scale_factor()) + } + + fn request_redraw(&self) { + self.maybe_wait_on_main(|delegate| delegate.request_redraw()); + } + + fn pre_present_notify(&self) { + self.maybe_wait_on_main(|delegate| delegate.pre_present_notify()); + } + + fn reset_dead_keys(&self) { + self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys()); + } + + fn inner_position(&self) -> Result, RequestError> { + Ok(self.maybe_wait_on_main(|delegate| delegate.inner_position())) + } + + fn outer_position(&self) -> Result, RequestError> { + Ok(self.maybe_wait_on_main(|delegate| delegate.outer_position())) + } + + fn set_outer_position(&self, position: Position) { + self.maybe_wait_on_main(|delegate| delegate.set_outer_position(position)); + } + + fn surface_size(&self) -> dpi::PhysicalSize { + self.maybe_wait_on_main(|delegate| delegate.surface_size()) + } + + fn request_surface_size(&self, size: Size) -> Option> { + self.maybe_wait_on_main(|delegate| delegate.request_surface_size(size)) + } + + fn outer_size(&self) -> dpi::PhysicalSize { + self.maybe_wait_on_main(|delegate| delegate.outer_size()) + } + + fn set_min_surface_size(&self, min_size: Option) { + self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size)) + } + + fn set_max_surface_size(&self, max_size: Option) { + self.maybe_wait_on_main(|delegate| delegate.set_max_surface_size(max_size)); + } + + fn surface_resize_increments(&self) -> Option> { + self.maybe_wait_on_main(|delegate| delegate.surface_resize_increments()) + } + + fn set_surface_resize_increments(&self, increments: Option) { + self.maybe_wait_on_main(|delegate| delegate.set_surface_resize_increments(increments)); + } + + fn set_title(&self, title: &str) { + self.maybe_wait_on_main(|delegate| delegate.set_title(title)); + } + + fn set_transparent(&self, transparent: bool) { + self.maybe_wait_on_main(|delegate| delegate.set_transparent(transparent)); + } + + fn set_blur(&self, blur: bool) { + self.maybe_wait_on_main(|delegate| delegate.set_blur(blur)); + } + + fn set_visible(&self, visible: bool) { + self.maybe_wait_on_main(|delegate| delegate.set_visible(visible)); + } + + fn is_visible(&self) -> Option { + self.maybe_wait_on_main(|delegate| delegate.is_visible()) + } + + fn set_resizable(&self, resizable: bool) { + self.maybe_wait_on_main(|delegate| delegate.set_resizable(resizable)) + } + + fn is_resizable(&self) -> bool { + self.maybe_wait_on_main(|delegate| delegate.is_resizable()) + } + + fn set_enabled_buttons(&self, buttons: WindowButtons) { + self.maybe_wait_on_main(|delegate| delegate.set_enabled_buttons(buttons)) + } + + fn enabled_buttons(&self) -> WindowButtons { + self.maybe_wait_on_main(|delegate| delegate.enabled_buttons()) + } + + fn set_minimized(&self, minimized: bool) { + self.maybe_wait_on_main(|delegate| delegate.set_minimized(minimized)); + } + + fn is_minimized(&self) -> Option { + self.maybe_wait_on_main(|delegate| delegate.is_minimized()) + } + + fn set_maximized(&self, maximized: bool) { + self.maybe_wait_on_main(|delegate| delegate.set_maximized(maximized)); + } + + fn is_maximized(&self) -> bool { + self.maybe_wait_on_main(|delegate| delegate.is_maximized()) + } + + fn set_fullscreen(&self, fullscreen: Option) { + self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen.map(Into::into))) + } + + fn fullscreen(&self) -> Option { + self.maybe_wait_on_main(|delegate| delegate.fullscreen().map(Into::into)) + } + + fn set_decorations(&self, decorations: bool) { + self.maybe_wait_on_main(|delegate| delegate.set_decorations(decorations)); + } + + fn is_decorated(&self) -> bool { + self.maybe_wait_on_main(|delegate| delegate.is_decorated()) + } + + fn set_window_level(&self, level: WindowLevel) { + self.maybe_wait_on_main(|delegate| delegate.set_window_level(level)); + } + + fn set_window_icon(&self, window_icon: Option) { + self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon)); + } + + fn set_ime_cursor_area(&self, position: Position, size: Size) { + self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size)); + } + + fn set_ime_allowed(&self, allowed: bool) { + self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed)); + } + + fn set_ime_purpose(&self, purpose: ImePurpose) { + self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose)); + } + + fn focus_window(&self) { + self.maybe_wait_on_main(|delegate| delegate.focus_window()); + } + + fn has_focus(&self) -> bool { + self.maybe_wait_on_main(|delegate| delegate.has_focus()) + } + + fn request_user_attention(&self, request_type: Option) { + self.maybe_wait_on_main(|delegate| delegate.request_user_attention(request_type)); + } + + fn set_theme(&self, theme: Option) { + self.maybe_wait_on_main(|delegate| delegate.set_theme(theme)); + } + + fn theme(&self) -> Option { + self.maybe_wait_on_main(|delegate| delegate.theme()) + } + + fn set_content_protected(&self, protected: bool) { + self.maybe_wait_on_main(|delegate| delegate.set_content_protected(protected)); + } + + fn title(&self) -> String { + self.maybe_wait_on_main(|delegate| delegate.title()) + } + + fn set_cursor(&self, cursor: Cursor) { + self.maybe_wait_on_main(|delegate| delegate.set_cursor(cursor)); + } + + fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> { + self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position)) + } + + fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> { + self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode)) + } + + fn set_cursor_visible(&self, visible: bool) { + self.maybe_wait_on_main(|delegate| delegate.set_cursor_visible(visible)) + } + + fn drag_window(&self) -> Result<(), RequestError> { + self.maybe_wait_on_main(|delegate| delegate.drag_window()) + } + + fn drag_resize_window( + &self, + direction: crate::window::ResizeDirection, + ) -> Result<(), RequestError> { + Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?) + } + + fn show_window_menu(&self, position: Position) { + self.maybe_wait_on_main(|delegate| delegate.show_window_menu(position)) + } + + fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> { + self.maybe_wait_on_main(|delegate| delegate.set_cursor_hittest(hittest)); + Ok(()) + } + + fn current_monitor(&self) -> Option { + self.maybe_wait_on_main(|delegate| { + delegate.current_monitor().map(|inner| CoreMonitorHandle { inner }) + }) + } + + fn available_monitors(&self) -> Box> { + self.maybe_wait_on_main(|delegate| { + Box::new( + delegate.available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner }), + ) + }) + } + + fn primary_monitor(&self) -> Option { + self.maybe_wait_on_main(|delegate| { + delegate.primary_monitor().map(|inner| CoreMonitorHandle { inner }) + }) + } + + #[cfg(feature = "rwh_06")] + fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle { + self + } + + #[cfg(feature = "rwh_06")] + fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle { + self + } +} + +declare_class!( + #[derive(Debug)] + pub struct WinitWindow; + + unsafe impl ClassType for WinitWindow { + #[inherits(NSResponder, NSObject)] + type Super = NSWindow; + type Mutability = mutability::MainThreadOnly; + const NAME: &'static str = "WinitWindow"; + } + + impl DeclaredClass for WinitWindow {} + + unsafe impl WinitWindow { + #[method(canBecomeMainWindow)] + fn can_become_main_window(&self) -> bool { + trace_scope!("canBecomeMainWindow"); + true + } + + #[method(canBecomeKeyWindow)] + fn can_become_key_window(&self) -> bool { + trace_scope!("canBecomeKeyWindow"); + true + } + } +); + +impl WinitWindow { + pub(super) fn id(&self) -> WindowId { + WindowId::from_raw(self as *const Self as usize) + } +} diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 8aa8a269f6..19dc605e09 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1163,7 +1163,8 @@ impl WindowDelegate { #[inline] pub fn drag_window(&self) -> Result<(), ExternalError> { let mtm = MainThreadMarker::from(self); - let event = NSApplication::sharedApplication(mtm).currentEvent().unwrap(); + let event = + NSApplication::sharedApplication(mtm).currentEvent().ok_or(ExternalError::Ignored)?; self.window().performWindowDragWithEvent(&event); Ok(()) } From 2579d49181891cbe1b403cd0a1b167c24c1f03f7 Mon Sep 17 00:00:00 2001 From: AngelicosPhosphoros Date: Sun, 13 Oct 2024 12:56:24 +0200 Subject: [PATCH 09/25] Improve waiting for messages on Windows Previous version used [`SetTimer`] with `GetMessageW` for waiting. The downside of UI timers like ones created by `SetTimer`, is that they may be late by up to 15-16 ms. To fix this behaviour, I added use of high resolution timers created by [`CreateWaitableTimerExW`] with the flag `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`. In my previous experience, waiting on such timers have precision of roundly 0.5 ms which is the best available on Windows at the moment. I use [`MsgWaitForMultipleObjectsEx`] to wait simultaneously for both timer and newly arriving events. Unfortunately, high resolution timers are available only since Windows 10 1803. However: 1. Win 10 is already getting to the end of support, like all previous versions, so it is OK to rely on APIs introduced in it; 2. I use `dwMilliseconds` parameter of `MsgWaitForMultipleObjectsEx` as a fallback. It should perform not worse compared to waiting for events from `SetTimer`. I also refactored code to remove event dispatching from function responsible for waiting for events. This provides more clear separations of concern and avoids unnecessary duplication of dispatching logic. After [review] from @rib, I also moved the waiting itself from `wait_for_messages` method to separate function, so it is clearly seen that `wait_for_messages` do 3 things: notify app that we about to wait, wait, notify that we have new events. I have tested behaviour using a egui app with Vulkan rendering with `VK_PRESENT_MODE_IMMEDIATE_KHR`, and older version consistently have twice less FPS than requested (e.g. 30 FPS when limit is 60 and 60 FPS when limit is 120) while newer version works more correctly (almost always 60 FPS when limit is 60, and only 5-10 frames missing when FPS is set to 120 or more). Fixes https://github.com/rust-windowing/winit/issues/1610 [`CreateWaitableTimerExW`]: https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createwaitabletimerexw [`MsgWaitForMultipleObjectsEx`]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-msgwaitformultipleobjectsex [`SetTimer`]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-settimer [review]: https://github.com/rust-windowing/winit/pull/3950#discussion_r1800184479 --- Cargo.toml | 1 + src/changelog/unreleased.md | 1 + src/platform_impl/windows/event_loop.rs | 276 +++++++++++++++--------- 3 files changed, 179 insertions(+), 99 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0f1a038068..22659f8425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -220,6 +220,7 @@ features = [ "Win32_System_Com", "Win32_System_LibraryLoader", "Win32_System_Ole", + "Win32_Security", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 086df8be02..172df56663 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -52,3 +52,4 @@ changelog entry. default activation policy, unless explicitly provided during initialization. - On macOS, fix crash when calling `drag_window()` without a left click present. - On X11, key events forward to IME anyway, even when it's disabled. +- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 639a93865d..926ce352ac 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -6,6 +6,7 @@ use std::cell::Cell; use std::collections::VecDeque; use std::ffi::c_void; use std::marker::PhantomData; +use std::os::windows::io::{AsRawHandle as _, FromRawHandle as _, OwnedHandle, RawHandle}; use std::rc::Rc; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::mpsc::{self, Receiver, Sender}; @@ -16,13 +17,18 @@ use std::{mem, panic, ptr}; use crate::utils::Lazy; use windows_sys::Win32::Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE; -use windows_sys::Win32::Foundation::{HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}; +use windows_sys::Win32::Foundation::{ + GetLastError, FALSE, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WAIT_FAILED, WPARAM, +}; use windows_sys::Win32::Graphics::Gdi::{ GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE, }; use windows_sys::Win32::System::Ole::RevokeDragDrop; -use windows_sys::Win32::System::Threading::{GetCurrentThreadId, INFINITE}; +use windows_sys::Win32::System::Threading::{ + CreateWaitableTimerExW, GetCurrentThreadId, SetWaitableTimer, + CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, INFINITE, TIMER_ALL_ACCESS, +}; use windows_sys::Win32::UI::Controls::{HOVER_DEFAULT, WM_MOUSELEAVE}; use windows_sys::Win32::UI::Input::Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW}; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ @@ -38,15 +44,15 @@ use windows_sys::Win32::UI::Input::Touch::{ use windows_sys::Win32::UI::Input::{RAWINPUT, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE}; use windows_sys::Win32::UI::WindowsAndMessaging::{ CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetClientRect, GetCursorPos, - GetMenu, GetMessageW, KillTimer, LoadCursorW, PeekMessageW, PostMessageW, RegisterClassExW, - RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos, TranslateMessage, CREATESTRUCTW, - GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO, - MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN, PT_TOUCH, RI_MOUSE_HWHEEL, - RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, - SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, - WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT, - WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, - WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, + GetMenu, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW, + RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, TranslateMessage, + CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, + MINMAXINFO, MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN, + PT_TOUCH, QS_ALLEVENTS, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, + SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, + WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, + WMSZ_TOPRIGHT, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, + WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, @@ -150,6 +156,10 @@ pub struct EventLoop { user_event_receiver: Receiver, window_target: RootAEL, msg_hook: Option bool + 'static>>, + // It is a timer used on timed waits. + // It is created lazily in case if we have `ControlFlow::WaitUntil`. + // Keep it as a field to avoid recreating it on every `ControlFlow::WaitUntil`. + high_resolution_timer: Option, } pub(crate) struct PlatformSpecificEventLoopAttributes { @@ -208,6 +218,7 @@ impl EventLoop { _marker: PhantomData, }, msg_hook: attributes.msg_hook.take(), + high_resolution_timer: None, }) } @@ -256,8 +267,9 @@ impl EventLoop { } let exit_code = loop { - self.wait_and_dispatch_message(None); - + self.wait_for_messages(None); + // wait_for_messages calls user application before and after waiting + // so it may have decided to exit. if let Some(code) = self.exit_code() { break code; } @@ -316,8 +328,11 @@ impl EventLoop { } } - self.wait_and_dispatch_message(timeout); - + if self.exit_code().is_none() { + self.wait_for_messages(timeout); + } + // wait_for_messages calls user application before and after waiting + // so it may have decided to exit. if self.exit_code().is_none() { self.dispatch_peeked_messages(); } @@ -347,101 +362,27 @@ impl EventLoop { status } - /// Wait for one message and dispatch it, optionally with a timeout - fn wait_and_dispatch_message(&mut self, timeout: Option) { - 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) - let timer_id = timeout.map(|timeout| SetTimer(0, 0, dur2timeout(timeout), None)); - let get_status = GetMessageW(msg, 0, 0, 0); - if let Some(timer_id) = timer_id { - KillTimer(0, timer_id); - } - // A return value of 0 implies `WM_QUIT` - if get_status == 0 { - PumpStatus::Exit(0) - } else { - PumpStatus::Continue - } - } - } - - /// Fetch the next MSG either via PeekMessage or GetMessage depending on whether the - /// requested timeout is `ZERO` (and so we don't want to block) - /// - /// Returns `None` if no MSG was read, else a `Continue` or `Exit` status - fn wait_for_msg(msg: &mut MSG, timeout: Option) -> Option { - if timeout == Some(Duration::ZERO) { - unsafe { - if PeekMessageW(msg, 0, 0, 0, PM_REMOVE) != 0 { - Some(PumpStatus::Continue) - } else { - None - } - } - } else { - Some(get_msg_with_timeout(msg, timeout)) - } - } - + /// Waits until new event messages arrive to be peeked. + /// Doesn't peek messages itself. + /// + /// Parameter timeout is optional. This method would wait for the smaller timeout + /// between the argument and a timeout from control flow. + fn wait_for_messages(&mut self, timeout: Option) { 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. // - // We emulate similar behaviour by treating `GetMessage` as our wait + // We emulate similar behaviour by treating `MsgWaitForMultipleObjectsEx` as our wait // point and wake up point (when it returns) and we drain all other // pending messages via `PeekMessage` until we come back to "wait" via - // `GetMessage` + // `MsgWaitForMultipleObjectsEx`. // 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 - // API) and there's no API to construct or initialize a `MSG`. This - // is the simplest way avoid uninitialized memory in Rust - let mut msg = unsafe { mem::zeroed() }; - let msg_status = wait_for_msg(&mut msg, timeout); - + wait_for_messages_impl(&mut self.high_resolution_timer, runner.control_flow(), timeout); // Before we potentially exit, make sure to consistently emit an event for the wake up runner.wakeup(); - - match msg_status { - None => {}, // No MSG to dispatch - Some(PumpStatus::Exit(code)) => { - runner.set_exit_code(code); - }, - Some(PumpStatus::Continue) => { - unsafe { - let handled = if let Some(callback) = self.msg_hook.as_deref_mut() { - callback(&mut msg as *mut _ as *mut _) - } else { - false - }; - if !handled { - TranslateMessage(&msg); - DispatchMessageW(&msg); - } - } - - if let Err(payload) = runner.take_panic_error() { - runner.reset_runner(); - panic::resume_unwind(payload); - } - }, - } } /// Dispatch all queued messages via `PeekMessageW` @@ -460,7 +401,7 @@ impl EventLoop { // initializing a `MSG` struct (it can be uninitialized memory for the C // API) and there's no API to construct or initialize a `MSG`. This // is the simplest way avoid uninitialized memory in Rust - let mut msg = unsafe { mem::zeroed() }; + let mut msg: MSG = unsafe { mem::zeroed() }; loop { unsafe { @@ -682,6 +623,143 @@ impl Drop for EventLoop { } } +/// Set upper limit for waiting time to avoid overflows. +/// I chose 50 days as a limit because it is used in dur2timeout. +const FIFTY_DAYS: Duration = Duration::from_secs(50_u64 * 24 * 60 * 60); +/// Waitable timers use 100 ns intervals to indicate due time. +/// +/// And there is no point waiting using other ways for such small timings +/// because they are even less precise (can overshoot by few ms). +const MIN_WAIT: Duration = Duration::from_nanos(100); + +fn create_high_resolution_timer() -> Option { + unsafe { + let handle: HANDLE = CreateWaitableTimerExW( + ptr::null(), + ptr::null(), + CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, + TIMER_ALL_ACCESS, + ); + // CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is supported only after + // Win10 1803 but it is already default option for rustc + // (std uses it to implement `std::thread::sleep`). + if handle == 0 { + None + } else { + Some(OwnedHandle::from_raw_handle(handle as *mut c_void)) + } + } +} + +/// This function should not return error if parameters are valid +/// but there is no guarantee about that at MSDN docs +/// so we return result of GetLastError if fail. +/// +/// ## Safety +/// +/// timer must be a valid timer handle created by [create_high_resolution_timer]. +/// timeout divided by 100 nanoseconds must be more than 0 and less than i64::MAX. +unsafe fn set_high_resolution_timer(timer: RawHandle, timeout: Duration) -> Result<(), u32> { + const INTERVAL_NS: u32 = MIN_WAIT.subsec_nanos(); + const INTERVALS_IN_SEC: u64 = (Duration::from_secs(1).as_nanos() / INTERVAL_NS as u128) as u64; + let intervals_to_wait: u64 = + timeout.as_secs() * INTERVALS_IN_SEC + u64::from(timeout.subsec_nanos() / INTERVAL_NS); + debug_assert!(intervals_to_wait < i64::MAX as u64, "Must be called with smaller duration",); + // Use negative time to indicate relative time. + let due_time: i64 = -(intervals_to_wait as i64); + unsafe { + let set_result = SetWaitableTimer(timer as HANDLE, &due_time, 0, None, ptr::null(), FALSE); + if set_result != FALSE { + Ok(()) + } else { + Err(GetLastError()) + } + } +} + +/// Implementation detail of [EventLoop::wait_for_messages]. +/// +/// Does actual system-level waiting and doesn't process any messages itself, +/// including winits internal notifications about waiting and new messages arrival. +fn wait_for_messages_impl( + high_resolution_timer: &mut Option, + control_flow: ControlFlow, + timeout: Option, +) { + let timeout = { + let control_flow_timeout = match 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(timeout, control_flow_timeout); + if timeout == Some(Duration::ZERO) { + // Do not wait if we don't have time. + return; + } + // Now we decided to wait so need to do some clamping + // to avoid problems with overflow and calling WinAPI with invalid parameters. + timeout + .map(|t| t.min(FIFTY_DAYS)) + // If timeout is less than minimally supported by Windows, + // increase it to that minimum. Who want less than microsecond delays anyway? + .map(|t| t.max(MIN_WAIT)) + }; + + if timeout.is_some() && high_resolution_timer.is_none() { + *high_resolution_timer = create_high_resolution_timer(); + } + + let high_resolution_timer: Option = + high_resolution_timer.as_ref().map(OwnedHandle::as_raw_handle); + + let use_timer: bool; + if let (Some(handle), Some(timeout)) = (high_resolution_timer, timeout) { + let res = unsafe { + // Safety: handle can be Some only if we succeeded in creating high resolution + // timer. We properly clamped timeout so it can be used as argument + // to timer. + set_high_resolution_timer(handle, timeout) + }; + if let Err(error_code) = res { + // We successfully got timer but failed to set it? + // Should be some bug in our code. + tracing::trace!("Failed to set high resolution timer: last error {}", error_code); + use_timer = false; + } else { + use_timer = true; + } + } else { + use_timer = false; + } + + unsafe { + // Either: + // 1. User wants to wait indefinely if timeout is not set. + // 2. We failed to get and set high resolution timer and we need something instead of it. + let wait_duration_ms = timeout.map(dur2timeout).unwrap_or(INFINITE); + + let (num_handles, raw_handles) = + if use_timer { (1, [high_resolution_timer.unwrap()]) } else { (0, [ptr::null_mut()]) }; + + let result = MsgWaitForMultipleObjectsEx( + num_handles, + raw_handles.as_ptr() as *const _, + wait_duration_ms, + QS_ALLEVENTS, + MWMO_INPUTAVAILABLE, + ); + if result == WAIT_FAILED { + // Well, nothing smart to do in such case. + // Treat it as spurious wake up. + tracing::warn!("Failed to MsgWaitForMultipleObjectsEx: error code {}", GetLastError(),); + } + } +} + pub(crate) struct EventLoopThreadExecutor { thread_id: u32, target_window: HWND, From a7ee8c99c10844b0ae1dd83e2f5caf87b329a622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Laitl?= Date: Mon, 28 Oct 2024 11:41:56 +0100 Subject: [PATCH 10/25] x11: iterate only visuals from the matching screen --- src/changelog/unreleased.md | 1 + src/platform_impl/linux/x11/window.rs | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 172df56663..276397eeff 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -53,3 +53,4 @@ changelog entry. - On macOS, fix crash when calling `drag_window()` without a left click present. - On X11, key events forward to IME anyway, even when it's disabled. - On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`. +- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again. diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index ea7c02354a..94cb28d66c 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -212,13 +212,18 @@ impl UnownedWindow { None => xconn.default_screen_index() as c_int, }; - // An iterator over all of the visuals combined with their depths. - let mut all_visuals = xconn - .xcb_connection() - .setup() - .roots + let screen = { + let screen_id_usize = usize::try_from(screen_id) + .map_err(|_| os_error!(OsError::Misc("screen id must be non-negative")))?; + xconn.xcb_connection().setup().roots.get(screen_id_usize).ok_or(os_error!( + OsError::Misc("requested screen id not present in server's response") + ))? + }; + + // An iterator over the visuals matching screen id combined with their depths. + let mut all_visuals = screen + .allowed_depths .iter() - .flat_map(|root| &root.allowed_depths) .flat_map(|depth| depth.visuals.iter().map(move |visual| (visual, depth.depth))); // creating From 8a023c022f2f15e3b74bad684f65eae57df8097c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Laitl?= Date: Mon, 28 Oct 2024 15:15:52 +0100 Subject: [PATCH 11/25] x11: fix WindowAttributesExtX11::with_x11_screen() Based on https://github.com/rust-windowing/winit/pull/3973, which should be merged first. There's an API to programmatically specify X11 screen id (override what is determined from the `DISPLAY` env variable), but it doesn't work. Seeting up X Server with 2 screens and calling `DISPLAY=:0 X11_SCREEN_ID=1 cargo run --example window` should be equivalent to calling `DISPLAY=:0.1 cargo run --example window` The latter works (and places the window on the correct screen), but the former yields `failed to create initial window: Os(OsError { line: 620, file: "src/platform_impl/linux/x11/window.rs", error: X11Error(X11Error { error_kind: Match, error_code: 8, sequence: 219, bad_value: 1319, minor_opcode: 0, major_opcode: 1, extension_name: None, request_name: Some("CreateWindow") }) })` _Here `1319` is the root window id for screen 0, which doesn't match the screen 1 that we request._ The problem is that we need to factor in the screen id when determining the parent (root) window when not explicitly set. This patch does that. --- Also: Extend the window example with X11_{SCREEN,VISUAL}_ID env variables --- examples/window.rs | 24 ++++++++++++++++++++++ src/changelog/unreleased.md | 3 +++ src/platform_impl/linux/x11/window.rs | 29 ++++++++++++++------------- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/examples/window.rs b/examples/window.rs index 11f324713c..48afadf9f3 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -31,6 +31,8 @@ use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMac use winit::platform::startup_notify::{ self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify, }; +#[cfg(x11_platform)] +use winit::platform::x11::WindowAttributesExtX11; #[path = "util/tracing.rs"] mod tracing; @@ -140,6 +142,28 @@ impl Application { window_attributes = window_attributes.with_activation_token(token); } + #[cfg(x11_platform)] + match std::env::var("X11_VISUAL_ID") { + Ok(visual_id_str) => { + info!("Using X11 visual id {visual_id_str}"); + let visual_id = visual_id_str.parse()?; + window_attributes = window_attributes.with_x11_visual(visual_id); + }, + Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"), + } + + #[cfg(x11_platform)] + match std::env::var("X11_SCREEN_ID") { + Ok(screen_id_str) => { + info!("Placing the window on X11 screen {screen_id_str}"); + let screen_id = screen_id_str.parse()?; + window_attributes = window_attributes.with_x11_screen(screen_id); + }, + Err(_) => info!( + "Set the X11_SCREEN_ID env variable to place the window on non-default screen" + ), + } + #[cfg(macos_platform)] if let Some(tab_id) = _tab_id { window_attributes = window_attributes.with_tabbing_identifier(&tab_id); diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 276397eeff..6e9802dd13 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -44,6 +44,8 @@ changelog entry. - On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game` to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games. +- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env + variables to test the respective modifiers of window creation. ### Fixed @@ -54,3 +56,4 @@ changelog entry. - On X11, key events forward to IME anyway, even when it's disabled. - On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`. - On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again. +- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again. diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 94cb28d66c..fcadf5805d 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -144,12 +144,26 @@ impl UnownedWindow { ) -> Result { let xconn = &event_loop.xconn; let atoms = xconn.atoms(); + + let screen_id = match window_attrs.platform_specific.x11.screen_id { + Some(id) => id, + None => xconn.default_screen_index() as c_int, + }; + + let screen = { + let screen_id_usize = usize::try_from(screen_id) + .map_err(|_| os_error!(OsError::Misc("screen id must be non-negative")))?; + xconn.xcb_connection().setup().roots.get(screen_id_usize).ok_or(os_error!( + OsError::Misc("requested screen id not present in server's response") + ))? + }; + #[cfg(feature = "rwh_06")] let root = match window_attrs.parent_window.as_ref().map(|handle| handle.0) { Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window, Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(), Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"), - None => event_loop.root, + None => screen.root, }; #[cfg(not(feature = "rwh_06"))] let root = event_loop.root; @@ -207,19 +221,6 @@ impl UnownedWindow { dimensions }; - let screen_id = match window_attrs.platform_specific.x11.screen_id { - Some(id) => id, - None => xconn.default_screen_index() as c_int, - }; - - let screen = { - let screen_id_usize = usize::try_from(screen_id) - .map_err(|_| os_error!(OsError::Misc("screen id must be non-negative")))?; - xconn.xcb_connection().setup().roots.get(screen_id_usize).ok_or(os_error!( - OsError::Misc("requested screen id not present in server's response") - ))? - }; - // An iterator over the visuals matching screen id combined with their depths. let mut all_visuals = screen .allowed_depths From 2031673ca94c6e3b8dae276ffcc4d460bf462310 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 12 Nov 2024 14:52:48 +0100 Subject: [PATCH 12/25] docs: add `fn main` to root examples This is not strictly required, but makes the examples a bit easier to read understand (especially since the `EventLoop` really _should_ be created inside `fn main`, and not in some random function potentially running on a random thread). --- src/lib.rs | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index eb1bd68633..ba3e7cd179 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,12 @@ //! //! ```no_run //! use winit::event_loop::EventLoop; -//! let event_loop = EventLoop::new().unwrap(); +//! +//! # // Intentionally use `fn main` for clarity +//! fn main() { +//! let event_loop = EventLoop::new().unwrap(); +//! // ... +//! } //! ``` //! //! Then you create a [`Window`] with [`create_window`]. @@ -84,19 +89,22 @@ //! } //! } //! -//! let event_loop = EventLoop::new().unwrap(); +//! # // Intentionally use `fn main` for clarity +//! fn main() { +//! let event_loop = EventLoop::new().unwrap(); //! -//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't -//! // dispatched any events. This is ideal for games and similar applications. -//! event_loop.set_control_flow(ControlFlow::Poll); +//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't +//! // dispatched any events. This is ideal for games and similar applications. +//! event_loop.set_control_flow(ControlFlow::Poll); //! -//! // ControlFlow::Wait pauses the event loop if no events are available to process. -//! // This is ideal for non-game applications that only update in response to user -//! // input, and uses significantly less power/CPU time than ControlFlow::Poll. -//! event_loop.set_control_flow(ControlFlow::Wait); +//! // ControlFlow::Wait pauses the event loop if no events are available to process. +//! // This is ideal for non-game applications that only update in response to user +//! // input, and uses significantly less power/CPU time than ControlFlow::Poll. +//! event_loop.set_control_flow(ControlFlow::Wait); //! -//! let mut app = App::default(); -//! event_loop.run_app(&mut app); +//! let mut app = App::default(); +//! event_loop.run_app(&mut app); +//! } //! ``` //! //! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be From c330c7c48f79431a47695678c4301c72aad129ce Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Wed, 13 Nov 2024 12:43:59 +0100 Subject: [PATCH 13/25] android: use show_soft_input to summon the keyboard Route it via the `Window::set_ime_allowed` like on iOS. --- src/changelog/unreleased.md | 1 + src/platform_impl/android/mod.rs | 8 +++++++- src/window.rs | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 6e9802dd13..676bf90c00 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -46,6 +46,7 @@ changelog entry. to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games. - On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env variables to test the respective modifiers of window creation. +- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`. ### Fixed diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 950e310299..bc0ad680e5 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -908,7 +908,13 @@ impl Window { pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {} - pub fn set_ime_allowed(&self, _allowed: bool) {} + pub fn set_ime_allowed(&self, allowed: bool) { + if allowed { + self.app.show_soft_input(true); + } else { + self.app.hide_soft_input(true); + } + } pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} diff --git a/src/window.rs b/src/window.rs index f19265e3a1..9bd002ab4c 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1274,7 +1274,8 @@ impl Window { /// /// - **macOS:** IME must be enabled to receive text-input where dead-key sequences are /// combined. - /// - **iOS / Android / Web / Orbital:** Unsupported. + /// - **iOS / Android:** This will show / hide the soft keyboard. + /// - **Web / Orbital:** Unsupported. /// - **X11**: Enabling IME will disable dead keys reporting during compose. /// /// [`Ime`]: crate::event::WindowEvent::Ime From e6665022474132831c78f1a0ebd4df7a42e17773 Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Mon, 19 Aug 2024 22:04:29 +0200 Subject: [PATCH 14/25] Basic iOS IME support (#3823) This implements basic iOS IME support (typing, backspace, support for emojis etc but no autocomplete or copy / paste menu). Co-authored-by: Mads Marquart --- Cargo.toml | 2 + src/changelog/unreleased.md | 1 + src/platform_impl/ios/view.rs | 100 ++++++++++++++++++++++++++++++-- src/platform_impl/ios/window.rs | 18 +++++- 4 files changed, 114 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22659f8425..06d5ba972f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -189,6 +189,8 @@ features = [ "UIEvent", "UIGeometry", "UIGestureRecognizer", + "UITextInput", + "UITextInputTraits", "UIOrientation", "UIPanGestureRecognizer", "UIPinchGestureRecognizer", diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 676bf90c00..a6ac8e617a 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -47,6 +47,7 @@ changelog entry. - On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env variables to test the respective modifiers of window creation. - On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`. +- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`. ### Fixed diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 75386d368e..9be43e4130 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -4,19 +4,21 @@ use std::cell::{Cell, RefCell}; use objc2::rc::Retained; use objc2::runtime::{NSObjectProtocol, ProtocolObject}; use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass}; -use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet}; +use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString}; use objc2_ui_kit::{ UICoordinateSpace, UIEvent, UIForceTouchCapability, UIGestureRecognizer, - UIGestureRecognizerDelegate, UIGestureRecognizerState, UIPanGestureRecognizer, + UIGestureRecognizerDelegate, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer, UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, - UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView, + UITextInputTraits, UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView, }; use super::app_state::{self, EventWrapper}; use super::window::WinitUIWindow; use crate::dpi::PhysicalPosition; -use crate::event::{Event, Force, Touch, TouchPhase, WindowEvent}; +use crate::event::{ElementState, Event, Force, KeyEvent, Touch, TouchPhase, WindowEvent}; +use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey}; use crate::platform_impl::platform::DEVICE_ID; +use crate::platform_impl::KeyEventExtra; use crate::window::{WindowAttributes, WindowId as RootWindowId}; pub struct WinitViewState { @@ -314,6 +316,11 @@ declare_class!( let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_event(mtm, gesture_event); } + + #[method(canBecomeFirstResponder)] + fn can_become_first_responder(&self) -> bool { + true + } } unsafe impl NSObjectProtocol for WinitView {} @@ -324,6 +331,26 @@ declare_class!( true } } + + unsafe impl UITextInputTraits for WinitView { + } + + unsafe impl UIKeyInput for WinitView { + #[method(hasText)] + fn has_text(&self) -> bool { + true + } + + #[method(insertText:)] + fn insert_text(&self, text: &NSString) { + self.handle_insert_text(text) + } + + #[method(deleteBackward)] + fn delete_backward(&self) { + self.handle_delete_backward() + } + } ); impl WinitView { @@ -512,4 +539,69 @@ impl WinitView { let mtm = MainThreadMarker::new().unwrap(); app_state::handle_nonuser_events(mtm, touch_events); } + + fn handle_insert_text(&self, text: &NSString) { + let window = self.window().unwrap(); + let window_id = RootWindowId(window.id()); + let mtm = MainThreadMarker::new().unwrap(); + // send individual events for each character + app_state::handle_nonuser_events( + mtm, + text.to_string().chars().flat_map(|c| { + let text = smol_str::SmolStr::from_iter([c]); + // Emit both press and release events + [ElementState::Pressed, ElementState::Released].map(|state| { + EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + event: KeyEvent { + text: if state == ElementState::Pressed { + Some(text.clone()) + } else { + None + }, + state, + location: KeyLocation::Standard, + repeat: false, + logical_key: Key::Character(text.clone()), + physical_key: PhysicalKey::Unidentified( + NativeKeyCode::Unidentified, + ), + platform_specific: KeyEventExtra {}, + }, + is_synthetic: false, + device_id: DEVICE_ID, + }, + }) + }) + }), + ); + } + + fn handle_delete_backward(&self) { + let window = self.window().unwrap(); + let window_id = RootWindowId(window.id()); + let mtm = MainThreadMarker::new().unwrap(); + app_state::handle_nonuser_events( + mtm, + [ElementState::Pressed, ElementState::Released].map(|state| { + EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event: KeyEvent { + state, + logical_key: Key::Named(NamedKey::Backspace), + physical_key: PhysicalKey::Code(KeyCode::Backspace), + platform_specific: KeyEventExtra {}, + repeat: false, + location: KeyLocation::Standard, + text: None, + }, + is_synthetic: false, + }, + }) + }), + ); + } } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 675fdfeef8..be0275952d 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -367,12 +367,24 @@ impl Inner { warn!("`Window::set_ime_cursor_area` is ignored on iOS") } - pub fn set_ime_allowed(&self, _allowed: bool) { - warn!("`Window::set_ime_allowed` is ignored on iOS") + /// Show / hide the keyboard. To show the keyboard, we call `becomeFirstResponder`, + /// requesting focus for the [WinitView]. Since [WinitView] implements + /// [objc2_ui_kit::UIKeyInput], the keyboard will be shown. + /// + pub fn set_ime_allowed(&self, allowed: bool) { + if allowed { + unsafe { + self.view.becomeFirstResponder(); + } + } else { + unsafe { + self.view.resignFirstResponder(); + } + } } pub fn set_ime_purpose(&self, _purpose: ImePurpose) { - warn!("`Window::set_ime_allowed` is ignored on iOS") + warn!("`Window::set_ime_purpose` is ignored on iOS") } pub fn focus_window(&self) { From dc576e26721ac5da66881b588140cd8036b0add1 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Tue, 19 Nov 2024 09:19:45 +1300 Subject: [PATCH 15/25] macOS: set activation policy by default if app is not bundled (#3961) --- src/platform_impl/macos/app_state.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 71f7c937fc..35076b6d19 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -5,7 +5,9 @@ use std::time::Instant; use objc2::rc::Retained; use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; -use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate}; +use objc2_app_kit::{ + NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate, NSRunningApplication, +}; use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol}; use super::event_handler::EventHandler; @@ -18,12 +20,7 @@ use crate::window::WindowId as RootWindowId; #[derive(Debug)] pub(super) struct AppState { - // <<<<<<< HEAD:src/platform_impl/macos/app_state.rs - // activation_policy: NSApplicationActivationPolicy, - // ======= activation_policy: Option, - // >>>>>>> 7e819bb2 (Prevent winit from overriding LSUIElement in package manifests - // (#3920)):src/platform_impl/apple/appkit/app_state.rs default_menu: bool, activate_ignoring_other_apps: bool, run_loop: RunLoop, @@ -120,6 +117,19 @@ impl ApplicationDelegate { // to allow the package manifest to define behavior via LSUIElement. if let Some(activation_policy) = self.ivars().activation_policy { app.setActivationPolicy(activation_policy); + } else { + // If no activation policy is explicitly provided, and the application + // is bundled, do not set the activation policy at all, to allow the + // package manifest to define the behavior via LSUIElement. + // + // See: + // - https://github.com/rust-windowing/winit/issues/261 + // - https://github.com/rust-windowing/winit/issues/3958 + let is_bundled = + unsafe { NSRunningApplication::currentApplication().bundleIdentifier().is_some() }; + if !is_bundled { + app.setActivationPolicy(NSApplicationActivationPolicy::Regular); + } } window_activation_hack(&app); From e01bd37f28dc382be6bca52d318214fc9a7b7f8d Mon Sep 17 00:00:00 2001 From: Rodrigo Rivas Costa Date: Thu, 21 Nov 2024 12:04:55 +0100 Subject: [PATCH 16/25] x11: move up XInput2 event registration It should be done before mapping the window, or we could lose the firsst XInput2 events, such as the first FocusIn. Fixes #2841. --- src/changelog/unreleased.md | 1 + src/platform_impl/linux/x11/window.rs | 28 +++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index a6ac8e617a..d3380d34bc 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -59,3 +59,4 @@ changelog entry. - On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`. - On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again. - On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again. +- On X11, fix XInput handling that prevented a new window from getting the focus in some cases. diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index fcadf5805d..9eff6dd028 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -490,6 +490,20 @@ impl UnownedWindow { ); leap!(result).ignore_error(); + // Select XInput2 events + let mask = xinput::XIEventMask::MOTION + | xinput::XIEventMask::BUTTON_PRESS + | xinput::XIEventMask::BUTTON_RELEASE + | xinput::XIEventMask::ENTER + | xinput::XIEventMask::LEAVE + | xinput::XIEventMask::FOCUS_IN + | xinput::XIEventMask::FOCUS_OUT + | xinput::XIEventMask::TOUCH_BEGIN + | xinput::XIEventMask::TOUCH_UPDATE + | xinput::XIEventMask::TOUCH_END; + leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask)) + .ignore_error(); + // Set visibility (map window) if window_attrs.visible { leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error(); @@ -513,20 +527,6 @@ impl UnownedWindow { } } - // Select XInput2 events - let mask = xinput::XIEventMask::MOTION - | xinput::XIEventMask::BUTTON_PRESS - | xinput::XIEventMask::BUTTON_RELEASE - | xinput::XIEventMask::ENTER - | xinput::XIEventMask::LEAVE - | xinput::XIEventMask::FOCUS_IN - | xinput::XIEventMask::FOCUS_OUT - | xinput::XIEventMask::TOUCH_BEGIN - | xinput::XIEventMask::TOUCH_UPDATE - | xinput::XIEventMask::TOUCH_END; - leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask)) - .ignore_error(); - // Try to create input context for the window. if let Some(ime) = event_loop.ime.as_ref() { let result = ime.borrow_mut().create_context(window.xwindow as ffi::Window, false); From 8937151b34ec05eb5fb0145f2037e28a05c9ebe9 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 2 Dec 2024 12:51:26 +0100 Subject: [PATCH 17/25] chore: fix clippy lints --- src/platform_impl/linux/common/xkb/mod.rs | 2 +- src/platform_impl/linux/x11/activation.rs | 2 +- src/platform_impl/linux/x11/mod.rs | 6 +++--- src/platform_impl/linux/x11/monitor.rs | 2 +- src/platform_impl/linux/x11/util/memory.rs | 6 +++--- src/platform_impl/macos/view.rs | 2 +- src/platform_impl/orbital/mod.rs | 2 +- src/platform_impl/windows/event_loop.rs | 2 +- src/platform_impl/windows/window.rs | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/platform_impl/linux/common/xkb/mod.rs b/src/platform_impl/linux/common/xkb/mod.rs index 0b951b666c..e2e7680065 100644 --- a/src/platform_impl/linux/common/xkb/mod.rs +++ b/src/platform_impl/linux/common/xkb/mod.rs @@ -184,7 +184,7 @@ pub struct KeyContext<'a> { scratch_buffer: &'a mut Vec, } -impl<'a> KeyContext<'a> { +impl KeyContext<'_> { pub fn process_key_event( &mut self, keycode: u32, diff --git a/src/platform_impl/linux/x11/activation.rs b/src/platform_impl/linux/x11/activation.rs index a5e961bb9d..5f83e1c0a7 100644 --- a/src/platform_impl/linux/x11/activation.rs +++ b/src/platform_impl/linux/x11/activation.rs @@ -165,7 +165,7 @@ fn push_display(buffer: &mut Vec, display: &impl std::fmt::Display) { buffer: &'a mut Vec, } - impl<'a> std::fmt::Write for Writer<'a> { + impl std::fmt::Write for Writer<'_> { fn write_str(&mut self, s: &str) -> std::fmt::Result { self.buffer.extend_from_slice(s.as_bytes()); Ok(()) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 3aafd7b316..8b9b690fb2 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -751,14 +751,14 @@ impl<'a> DeviceInfo<'a> { } } -impl<'a> Drop for DeviceInfo<'a> { +impl Drop for DeviceInfo<'_> { fn drop(&mut self) { assert!(!self.info.is_null()); unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) }; } } -impl<'a> Deref for DeviceInfo<'a> { +impl Deref for DeviceInfo<'_> { type Target = [ffi::XIDeviceInfo]; fn deref(&self) -> &Self::Target { @@ -957,7 +957,7 @@ trait CookieResultExt { fn expect_then_ignore_error(self, msg: &str); } -impl<'a, E: fmt::Debug> CookieResultExt for Result, E> { +impl CookieResultExt for Result, E> { fn expect_then_ignore_error(self, msg: &str) { self.expect(msg).ignore_error() } diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 6580ac30de..1964bc9383 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -301,7 +301,7 @@ impl XConnection { let info = self .xcb_connection() .extension_information(randr::X11_EXTENSION_NAME)? - .ok_or_else(|| X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?; + .ok_or(X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?; // Select input data. let event_mask = diff --git a/src/platform_impl/linux/x11/util/memory.rs b/src/platform_impl/linux/x11/util/memory.rs index d32eb8cebe..4e052a758c 100644 --- a/src/platform_impl/linux/x11/util/memory.rs +++ b/src/platform_impl/linux/x11/util/memory.rs @@ -19,7 +19,7 @@ impl<'a, T> XSmartPointer<'a, T> { } } -impl<'a, T> Deref for XSmartPointer<'a, T> { +impl Deref for XSmartPointer<'_, T> { type Target = T; fn deref(&self) -> &T { @@ -27,13 +27,13 @@ impl<'a, T> Deref for XSmartPointer<'a, T> { } } -impl<'a, T> DerefMut for XSmartPointer<'a, T> { +impl DerefMut for XSmartPointer<'_, T> { fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.ptr } } } -impl<'a, T> Drop for XSmartPointer<'a, T> { +impl Drop for XSmartPointer<'_, T> { fn drop(&mut self) { unsafe { (self.xconn.xlib.XFree)(self.ptr as *mut _); diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 50cffcee67..44965ae3d4 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -399,7 +399,7 @@ declare_class!( unsafe { &*string }.to_string() }; - let is_control = string.chars().next().map_or(false, |c| c.is_control()); + let is_control = string.chars().next().is_some_and(|c| c.is_control()); // Commit only if we have marked text. if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control { diff --git a/src/platform_impl/orbital/mod.rs b/src/platform_impl/orbital/mod.rs index f210a8ce04..2d1525137a 100644 --- a/src/platform_impl/orbital/mod.rs +++ b/src/platform_impl/orbital/mod.rs @@ -154,7 +154,7 @@ impl<'a> WindowProperties<'a> { } } -impl<'a> fmt::Display for WindowProperties<'a> { +impl fmt::Display for WindowProperties<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 926ce352ac..3fdf98d550 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -564,7 +564,7 @@ impl OwnedDisplayHandle { fn main_thread_id() -> u32 { static mut MAIN_THREAD_ID: u32 = 0; - /// Function pointer used in CRT initialization section to set the above static field's value. + // Function pointer used in CRT initialization section to set the above static field's value. // Mark as used so this is not removable. #[used] diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 23fec32b34..a8b375c9b5 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -1129,7 +1129,7 @@ pub(super) struct InitData<'a> { pub window: Option, } -impl<'a> InitData<'a> { +impl InitData<'_> { unsafe fn create_window(&self, window: HWND) -> Window { // Register for touch events if applicable { From 7e71641b4978803cd20ac4f032085327f24a6a4e Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 18:48:23 +0100 Subject: [PATCH 18/25] macOS: Fix crash when pressing Caps Lock (#4024) Events emitted by `flagsChanged:` cannot access `charactersIgnoringModifiers`. We were previously doing this because we were trying to re-use the `create_key_event` function, but that is unsuited for this purpose, so I have separated the `flagsChanged:` logic out from it. --- src/changelog/unreleased.md | 1 + src/platform_impl/macos/event.rs | 26 ++++++-------------- src/platform_impl/macos/view.rs | 42 +++++++++++++++++++++----------- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index d3380d34bc..9c904725c0 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -60,3 +60,4 @@ changelog entry. - On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again. - On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again. - On X11, fix XInput handling that prevented a new window from getting the focus in some cases. +- On macOS, fix crash when pressing Caps Lock in certain configurations. diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 602ab6278f..4eacb3dc44 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -92,17 +92,12 @@ fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key { /// Create `KeyEvent` for the given `NSEvent`. /// /// This function shouldn't be called when the IME input is in process. -pub(crate) fn create_key_event( - ns_event: &NSEvent, - is_press: bool, - is_repeat: bool, - key_override: Option, -) -> KeyEvent { +pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent { use ElementState::{Pressed, Released}; let state = if is_press { Pressed } else { Released }; let scancode = unsafe { ns_event.keyCode() }; - let mut physical_key = key_override.unwrap_or_else(|| scancode_to_physicalkey(scancode as u32)); + let mut physical_key = scancode_to_physicalkey(scancode as u32); // NOTE: The logical key should heed both SHIFT and ALT if possible. // For instance: @@ -111,20 +106,15 @@ pub(crate) fn create_key_event( // * Pressing CTRL SHIFT A: logical key should also be "A" // This is not easy to tease out of `NSEvent`, but we do our best. - let text_with_all_modifiers: Option = if key_override.is_some() { + let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default(); + let text_with_all_modifiers = if characters.is_empty() { None } else { - let characters = - unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default(); - if characters.is_empty() { - None - } else { - if matches!(physical_key, PhysicalKey::Unidentified(_)) { - // The key may be one of the funky function keys - physical_key = extra_function_key_to_code(scancode, &characters); - } - Some(SmolStr::new(characters)) + if matches!(physical_key, PhysicalKey::Unidentified(_)) { + // The key may be one of the funky function keys + physical_key = extra_function_key_to_code(scancode, &characters); } + Some(SmolStr::new(characters)) }; let key_from_code = code_to_key(physical_key, scancode); diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 44965ae3d4..d2a9948fc1 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -20,13 +20,13 @@ use super::app_state::ApplicationDelegate; use super::cursor::{default_cursor, invisible_cursor}; use super::event::{ code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed, - scancode_to_physicalkey, + scancode_to_physicalkey, KeyEventExtra, }; use super::window::WinitWindow; use super::DEVICE_ID; use crate::dpi::{LogicalPosition, LogicalSize}; use crate::event::{ - DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase, + DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent, }; use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey}; @@ -482,7 +482,7 @@ declare_class!( }; if !had_ime_input || self.ivars().forward_key_to_app.get() { - let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None); + let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }); self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, event: key_event, @@ -505,7 +505,7 @@ declare_class!( ) { self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, - event: create_key_event(&event, false, false, None), + event: create_key_event(&event, false, false), is_synthetic: false, }); } @@ -552,7 +552,7 @@ declare_class!( .expect("could not find current event"); self.update_modifiers(&event, false); - let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None); + let event = create_key_event(&event, true, unsafe { event.isARepeat() }); self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, @@ -933,22 +933,36 @@ impl WinitView { let scancode = unsafe { ns_event.keyCode() }; let physical_key = scancode_to_physicalkey(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 logical_key = code_to_key(physical_key, scancode); // Ignore processing of unknown modifiers because we can't determine whether // it was pressed or release reliably. - let Some(event_modifier) = key_to_modifier(&key) else { + // + // Furthermore, sometimes normal keys are reported inside flagsChanged:, such as + // when holding Caps Lock while pressing another key, see: + // https://github.com/alacritty/alacritty/issues/8268 + let Some(event_modifier) = key_to_modifier(&logical_key) else { break 'send_event; }; - event.physical_key = physical_key; - event.logical_key = key.clone(); - event.location = code_to_location(physical_key); + + let mut event = KeyEvent { + location: code_to_location(physical_key), + logical_key: logical_key.clone(), + physical_key, + repeat: false, + // We'll correct this later. + state: Pressed, + text: None, + platform_specific: KeyEventExtra { + text_with_all_modifiers: None, + key_without_modifiers: logical_key.clone(), + }, + }; + let location_mask = ModLocationMask::from_location(event.location); let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut(); - let phys_mod = phys_mod_state.entry(key).or_insert(ModLocationMask::empty()); + let phys_mod = + phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty()); let is_active = current_modifiers.state().contains(event_modifier); let mut events = VecDeque::with_capacity(2); From f757a9e9a59fc8ed3abc387e3a6bdc0e923a6a13 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 19:02:53 +0100 Subject: [PATCH 19/25] Fix MonitorHandle PartialEq and Hash on iOS (#4013) --- src/changelog/unreleased.md | 1 + src/platform_impl/ios/monitor.rs | 39 +++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 9c904725c0..4c9b9aa808 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -61,3 +61,4 @@ changelog entry. - On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again. - On X11, fix XInput handling that prevented a new window from getting the focus in some cases. - On macOS, fix crash when pressing Caps Lock in certain configurations. +- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations. diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index 1c707a3ac4..9f017a246c 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -102,13 +102,20 @@ impl Clone for MonitorHandle { impl hash::Hash for MonitorHandle { fn hash(&self, state: &mut H) { - (self as *const Self).hash(state); + // SAFETY: Only getting the pointer. + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + Retained::as_ptr(self.ui_screen.get(mtm)).hash(state); } } impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { - ptr::eq(self, other) + // SAFETY: Only getting the pointer. + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + ptr::eq( + Retained::as_ptr(self.ui_screen.get(mtm)), + Retained::as_ptr(other.ui_screen.get(mtm)), + ) } } @@ -122,8 +129,10 @@ impl PartialOrd for MonitorHandle { impl Ord for MonitorHandle { fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // SAFETY: Only getting the pointer. // TODO: Make a better ordering - (self as *const Self).cmp(&(other as *const Self)) + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm))) } } @@ -242,3 +251,27 @@ pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque { #[allow(deprecated)] UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect() } + +#[cfg(test)] +mod tests { + use objc2_foundation::NSSet; + + use super::*; + + // Test that UIScreen pointer comparisons are correct. + #[test] + #[allow(deprecated)] + fn screen_comparisons() { + // Test code, doesn't matter that it's not thread safe + let mtm = unsafe { MainThreadMarker::new_unchecked() }; + + assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm))); + + let main = UIScreen::mainScreen(mtm); + assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main))); + + assert!(unsafe { + NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm)) + }); + } +} From 97a5fc9c946e4b0153be67cc4614642e53831d6e Mon Sep 17 00:00:00 2001 From: "Skip R." Date: Sun, 8 Dec 2024 13:01:57 -0800 Subject: [PATCH 20/25] macOS: Fix checking for undocumented cursors (#4033) `AnyClass::responds_to` delegates to `class_respondsToSelector`, a function provided by the Objective-C runtime. However, at some point, this began to return `false` for selectors referring to undocumented cursors, despite the cursors remaining accessible via said selectors. That this check fails prevents the cursors from being used. We can instead send `respondsToSelector:` to the `NSCursor` class itself. As an instance method, this is nominally impossible; however, Apple grants an exemption[1] that permits class objects to perform instance methods defined in the root class. Checking for the undocumented cursors in this way gets them working again, at least on macOS Sequoia 15.1.1. [1]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocObjectsClasses.html#//apple_ref/doc/uid/TP30001163-CH11-TPXREF120 --- src/changelog/unreleased.md | 1 + src/platform_impl/macos/cursor.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 4c9b9aa808..f3fc968497 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -62,3 +62,4 @@ changelog entry. - On X11, fix XInput handling that prevented a new window from getting the focus in some cases. - On macOS, fix crash when pressing Caps Lock in certain configurations. - On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations. +- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor. diff --git a/src/platform_impl/macos/cursor.rs b/src/platform_impl/macos/cursor.rs index cc8f5f3088..9e14e8be61 100644 --- a/src/platform_impl/macos/cursor.rs +++ b/src/platform_impl/macos/cursor.rs @@ -4,7 +4,7 @@ use std::sync::OnceLock; use objc2::rc::Retained; use objc2::runtime::Sel; -use objc2::{msg_send_id, sel, ClassType}; +use objc2::{msg_send, msg_send_id, sel, ClassType}; use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage}; use objc2_foundation::{ ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize, @@ -66,7 +66,7 @@ pub(crate) fn default_cursor() -> Retained { unsafe fn try_cursor_from_selector(sel: Sel) -> Option> { let cls = NSCursor::class(); - if cls.responds_to(sel) { + if msg_send![cls, respondsToSelector: sel] { let cursor: Retained = unsafe { msg_send_id![cls, performSelector: sel] }; Some(cursor) } else { From c9c20b74e42e05d4a4964e2b938c6eb69f3254b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Aug 2024 15:14:12 +0300 Subject: [PATCH 21/25] build(deps): bump EmbarkStudios/cargo-deny-action Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: daxpedda --- .github/workflows/ci.yml | 2 +- Cargo.toml | 12 ++++- deny.toml | 97 +++++++++++++++++++++++++--------------- 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82094815db..955344727f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -221,7 +221,7 @@ jobs: steps: - uses: taiki-e/checkout-action@v1 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check log-level: error diff --git a/Cargo.toml b/Cargo.toml index 06d5ba972f..4169b28710 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,17 @@ rust-version.workspace = true repository.workspace = true license.workspace = true edition.workspace = true -exclude = ["/.cargo"] +include = [ + "/build.rs", + "/docs", + "/examples", + "/FEATURES.md", + "/LICENSE", + "/src", + "!/src/platform_impl/web/script", + "/src/platform_impl/web/script/**/*.min.js", + "/tests", +] [package.metadata.docs.rs] features = [ diff --git a/deny.toml b/deny.toml index 27fe32cd01..4fcc441625 100644 --- a/deny.toml +++ b/deny.toml @@ -1,15 +1,20 @@ -# https://embarkstudios.github.io/cargo-deny/ +# https://embarkstudios.github.io/cargo-deny # cargo install cargo-deny -# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check +# cargo update && cargo deny --target aarch64-apple-ios check # Note: running just `cargo deny check` without a `--target` will result in # false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324 +[graph] +all-features = true +exclude-dev = true targets = [ { triple = "aarch64-apple-ios" }, { triple = "aarch64-linux-android" }, { triple = "i686-pc-windows-gnu" }, { triple = "i686-pc-windows-msvc" }, { triple = "i686-unknown-linux-gnu" }, - { triple = "wasm32-unknown-unknown" }, + { triple = "wasm32-unknown-unknown", features = [ + "atomics", + ] }, { triple = "x86_64-apple-darwin" }, { triple = "x86_64-apple-ios" }, { triple = "x86_64-pc-windows-gnu" }, @@ -18,45 +23,65 @@ targets = [ { triple = "x86_64-unknown-redox" }, ] - -[advisories] -vulnerability = "deny" -unmaintained = "warn" -yanked = "deny" -ignore = [] - +[licenses] +allow = [ + "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) + "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) + "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) + "ISC", # https://tldrlegal.com/license/-isc-license + "MIT", # https://tldrlegal.com/license/mit-license + "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html +] +confidence-threshold = 1.0 +private = { ignore = true } [bans] multiple-versions = "deny" -wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed -deny = [] skip = [ - { name = "raw-window-handle" }, # we intentionally have multiple versions of this - { name = "bitflags" }, # the ecosystem is in the process of migrating. + { crate = "raw-window-handle", reason = "we depend on multiple behind features" } + { crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" } ] -skip-tree = [] +wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed +[bans.build] +include-archives = true +interpreted = "deny" -[licenses] -private = { ignore = true } -unlicensed = "deny" -allow-osi-fsf-free = "neither" -confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text -copyleft = "deny" +[[bans.build.bypass]] allow = [ - "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html - "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) - "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) - "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) - "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained - "CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/ - "ISC", # https://tldrlegal.com/license/-isc-license - "LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321 - "MIT-0", # https://choosealicense.com/licenses/mit-0/ - "MIT", # https://tldrlegal.com/license/mit-license - "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux. - "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html - "OpenSSL", # https://www.openssl.org/source/license.html - used on Linux - "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html - "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) + { path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" }, ] +crate = "android-activity" + +[[bans.build.bypass]] +allow-globs = ["freetype2/*"] +crate = "freetype-sys" + +[[bans.build.bypass]] +allow = [ + { path = "releases/friends.sh", checksum = "f896ccdcb8445d29ed6dd0d9a360f94d4f33af2f1cc9965e7bb38b156c45949d" }, +] +crate = "wasm-bindgen" + +[[bans.build.bypass]] +allow = [ + { path = "ui-tests/update-all-references.sh", checksum = "8b8dbf31e7ada1314956db7a20ab14b13af3ae246a6295afdc7dc96af8ec3773" }, + { path = "ui-tests/update-references.sh", checksum = "65375c25981646e08e8589449a06be4505b1a2c9e10d35f650be4b1b495dff22" }, +] +crate = "wasm-bindgen-macro" + +[[bans.build.bypass]] +allow-globs = ["lib/*.a"] +crate = "windows_i686_gnu" + +[[bans.build.bypass]] +allow-globs = ["lib/*.lib"] +crate = "windows_i686_msvc" + +[[bans.build.bypass]] +allow-globs = ["lib/*.a"] +crate = "windows_x86_64_gnu" + +[[bans.build.bypass]] +allow-globs = ["lib/*.lib"] +crate = "windows_x86_64_msvc" From 093fb4ba48201ed41ba18bb7d56f79155bd215e7 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 21 Nov 2024 08:28:32 +0100 Subject: [PATCH 22/25] ci: fix cargo deny --- deny.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/deny.toml b/deny.toml index 4fcc441625..9ca214a98b 100644 --- a/deny.toml +++ b/deny.toml @@ -25,12 +25,12 @@ targets = [ [licenses] allow = [ - "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) - "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) - "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) - "ISC", # https://tldrlegal.com/license/-isc-license - "MIT", # https://tldrlegal.com/license/mit-license - "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html + "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) + "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) + "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) + "ISC", # https://tldrlegal.com/license/isc-license + "MIT", # https://tldrlegal.com/license/mit-license + "Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html ] confidence-threshold = 1.0 private = { ignore = true } @@ -38,8 +38,8 @@ private = { ignore = true } [bans] multiple-versions = "deny" skip = [ - { crate = "raw-window-handle", reason = "we depend on multiple behind features" } - { crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" } + { crate = "raw-window-handle", reason = "we depend on multiple behind features" }, + { crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" }, ] wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed From 44c415c228580a234863bcfe478124cd67e1ff38 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Tue, 13 Aug 2024 21:54:03 +0200 Subject: [PATCH 23/25] Update minimum version of `wasm-bindgen` (#3860) --- Cargo.toml | 105 +++++++++--------- deny.toml | 13 --- src/platform_impl/web/cursor.rs | 4 +- src/platform_impl/web/event_loop/runner.rs | 61 +++++----- .../web/web_sys/resize_scaling.rs | 7 +- src/platform_impl/web/web_sys/schedule.rs | 4 +- 6 files changed, 87 insertions(+), 107 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4169b28710..e880f6dd07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -291,60 +291,63 @@ xkbcommon-dl = "0.4.2" orbclient = { version = "0.3.47", default-features = false } redox_syscall = "0.4.1" -[target.'cfg(target_family = "wasm")'.dependencies.web_sys] -package = "web-sys" -version = "0.3.64" -features = [ - 'AbortController', - 'AbortSignal', - 'Blob', - 'BlobPropertyBag', - 'console', - 'CssStyleDeclaration', - 'Document', - 'DomException', - 'DomRect', - 'DomRectReadOnly', - 'Element', - 'Event', - 'EventTarget', - 'FocusEvent', - 'HtmlCanvasElement', - 'HtmlElement', - 'HtmlImageElement', - 'ImageBitmap', - 'ImageBitmapOptions', - 'ImageBitmapRenderingContext', - 'ImageData', - 'IntersectionObserver', - 'IntersectionObserverEntry', - 'KeyboardEvent', - 'MediaQueryList', - 'MessageChannel', - 'MessagePort', - 'Navigator', - 'Node', - 'PageTransitionEvent', - 'PointerEvent', - 'PremultiplyAlpha', - 'ResizeObserver', - 'ResizeObserverBoxOptions', - 'ResizeObserverEntry', - 'ResizeObserverOptions', - 'ResizeObserverSize', - 'VisibilityState', - 'Window', - 'WheelEvent', - 'Worker', - 'Url', -] - [target.'cfg(target_family = "wasm")'.dependencies] -js-sys = "0.3.64" +js-sys = "0.3.70" pin-project = "1" -wasm-bindgen = "0.2" -wasm-bindgen-futures = "0.4" +wasm-bindgen = "0.2.93" +wasm-bindgen-futures = "0.4.43" web-time = "1" +web_sys = { package = "web-sys", version = "0.3.70", features = [ + "AbortController", + "AbortSignal", + "Blob", + "BlobPropertyBag", + "console", + "CssStyleDeclaration", + "Document", + "DomException", + "DomRect", + "DomRectReadOnly", + "Element", + "Event", + "EventTarget", + "FocusEvent", + "HtmlCanvasElement", + "HtmlElement", + "HtmlImageElement", + "ImageBitmap", + "ImageBitmapOptions", + "ImageBitmapRenderingContext", + "ImageData", + "IntersectionObserver", + "IntersectionObserverEntry", + "KeyboardEvent", + "MediaQueryList", + "MessageChannel", + "MessagePort", + "Navigator", + "Node", + "OrientationLockType", + "OrientationType", + "PageTransitionEvent", + "Permissions", + "PermissionState", + "PermissionStatus", + "PointerEvent", + "PremultiplyAlpha", + "ResizeObserver", + "ResizeObserverBoxOptions", + "ResizeObserverEntry", + "ResizeObserverOptions", + "ResizeObserverSize", + "Screen", + "ScreenOrientation", + "Url", + "VisibilityState", + "WheelEvent", + "Window", + "Worker", +] } [target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies] atomic-waker = "1" diff --git a/deny.toml b/deny.toml index 9ca214a98b..a6aef58f06 100644 --- a/deny.toml +++ b/deny.toml @@ -57,19 +57,6 @@ crate = "android-activity" allow-globs = ["freetype2/*"] crate = "freetype-sys" -[[bans.build.bypass]] -allow = [ - { path = "releases/friends.sh", checksum = "f896ccdcb8445d29ed6dd0d9a360f94d4f33af2f1cc9965e7bb38b156c45949d" }, -] -crate = "wasm-bindgen" - -[[bans.build.bypass]] -allow = [ - { path = "ui-tests/update-all-references.sh", checksum = "8b8dbf31e7ada1314956db7a20ab14b13af3ae246a6295afdc7dc96af8ec3773" }, - { path = "ui-tests/update-references.sh", checksum = "65375c25981646e08e8589449a06be4505b1a2c9e10d35f650be4b1b495dff22" }, -] -crate = "wasm-bindgen-macro" - [[bans.build.bypass]] allow-globs = ["lib/*.a"] crate = "windows_i686_gnu" diff --git a/src/platform_impl/web/cursor.rs b/src/platform_impl/web/cursor.rs index 0da977af95..214ff45a5e 100644 --- a/src/platform_impl/web/cursor.rs +++ b/src/platform_impl/web/cursor.rs @@ -542,8 +542,8 @@ fn from_rgba( // // We call `createImageBitmap()` before spawning the future, // to not have to clone the image buffer. - let mut options = ImageBitmapOptions::new(); - options.premultiply_alpha(PremultiplyAlpha::None); + let options = ImageBitmapOptions::new(); + options.set_premultiply_alpha(PremultiplyAlpha::None); let bitmap = JsFuture::from( window .create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options) diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index ba8abbd477..d9ba3d3734 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -1,3 +1,15 @@ +use std::cell::{Cell, RefCell}; +use std::collections::{HashSet, VecDeque}; +use std::iter; +use std::num::NonZeroUsize; +use std::ops::Deref; +use std::rc::{Rc, Weak}; + +use wasm_bindgen::prelude::Closure; +use wasm_bindgen::JsCast; +use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent}; +use web_time::{Duration, Instant}; + use super::super::main_thread::MainThreadMarker; use super::super::DeviceId; use super::backend; @@ -14,18 +26,6 @@ use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawne use crate::platform_impl::platform::window::Inner; use crate::window::WindowId; -use js_sys::Function; -use std::cell::{Cell, RefCell}; -use std::collections::{HashSet, VecDeque}; -use std::iter; -use std::num::NonZeroUsize; -use std::ops::Deref; -use std::rc::{Rc, Weak}; -use wasm_bindgen::prelude::{wasm_bindgen, Closure}; -use wasm_bindgen::JsCast; -use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent}; -use web_time::{Duration, Instant}; - pub struct Shared(Rc); pub(super) type EventHandler = dyn FnMut(Event<()>); @@ -459,33 +459,24 @@ impl Shared { if local { // If the loop is not running and triggered locally, queue on next microtick. - if let Ok(RunnerEnum::Running(ref runner)) = + if let Ok(RunnerEnum::Running(_)) = self.0.runner.try_borrow().as_ref().map(Deref::deref) { - // If we're currently polling let `send_events` do its job. - if !matches!(runner.state, State::Poll { .. }) { - #[wasm_bindgen] - extern "C" { - #[wasm_bindgen(js_name = queueMicrotask)] - fn queue_microtask(task: Function); - } - - queue_microtask( - Closure::once_into_js({ - let this = Rc::downgrade(&self.0); - move || { - if let Some(shared) = this.upgrade() { - Shared(shared).send_events( - iter::repeat(Event::UserEvent(())).take(count.get()), - ) - } + self.window().queue_microtask( + &Closure::once_into_js({ + let this = Rc::downgrade(&self.0); + move || { + if let Some(shared) = this.upgrade() { + Shared(shared).send_events( + iter::repeat(Event::UserEvent(())).take(count.get()), + ) } - }) - .unchecked_into(), - ); + } + }) + .unchecked_into(), + ); - return; - } + return; } } diff --git a/src/platform_impl/web/web_sys/resize_scaling.rs b/src/platform_impl/web/web_sys/resize_scaling.rs index fdfda75acd..4d10b3ac01 100644 --- a/src/platform_impl/web/web_sys/resize_scaling.rs +++ b/src/platform_impl/web/web_sys/resize_scaling.rs @@ -139,10 +139,9 @@ impl ResizeScaleInternal { // Safari doesn't support `devicePixelContentBoxSize` if has_device_pixel_support() { - observer.observe_with_options( - canvas, - ResizeObserverOptions::new().box_(ResizeObserverBoxOptions::DevicePixelContentBox), - ); + let options = ResizeObserverOptions::new(); + options.set_box(ResizeObserverBoxOptions::DevicePixelContentBox); + observer.observe_with_options(canvas, &options); } else { observer.observe(canvas); } diff --git a/src/platform_impl/web/web_sys/schedule.rs b/src/platform_impl/web/web_sys/schedule.rs index dfb500b4b8..eb9706843d 100644 --- a/src/platform_impl/web/web_sys/schedule.rs +++ b/src/platform_impl/web/web_sys/schedule.rs @@ -286,8 +286,8 @@ struct ScriptUrl(String); impl ScriptUrl { fn new(script: &str) -> Self { let sequence = Array::of1(&script.into()); - let mut property = BlobPropertyBag::new(); - property.type_("text/javascript"); + let property = BlobPropertyBag::new(); + property.set_type("text/javascript"); let blob = Blob::new_with_str_sequence_and_options(&sequence, &property) .expect("`new Blob()` should never throw"); From 32cf5694e9c105dcf21d7956a1eb2fa451c5b6de Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 16 Dec 2024 19:48:03 +0300 Subject: [PATCH 24/25] chore: fix typos --- src/event_loop.rs | 2 +- src/platform/web.rs | 2 +- src/platform_impl/web/async/channel.rs | 2 +- src/platform_impl/windows/event_loop/runner.rs | 8 ++++---- src/platform_impl/windows/keyboard.rs | 10 +++++----- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/event_loop.rs b/src/event_loop.rs index b5d5d26402..233374beec 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -501,7 +501,7 @@ unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop { /// A proxy for the underlying display handle. /// -/// The purpose of this type is to provide a cheaply clonable handle to the underlying +/// The purpose of this type is to provide a cheaply cloneable handle to the underlying /// display handle. This is often used by graphics APIs to connect to the underlying APIs. /// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`] /// type. In contrast, this type involves no lifetimes and can be persisted for as long as diff --git a/src/platform/web.rs b/src/platform/web.rs index a48c6b09b4..f257ca416b 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -422,7 +422,7 @@ impl fmt::Display for BadAnimation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Empty => write!(f, "No cursors supplied"), - Self::Animation => write!(f, "A supplied cursor is an animtion"), + Self::Animation => write!(f, "A supplied cursor is an animation"), } } } diff --git a/src/platform_impl/web/async/channel.rs b/src/platform_impl/web/async/channel.rs index 42401a2824..11a7a47957 100644 --- a/src/platform_impl/web/async/channel.rs +++ b/src/platform_impl/web/async/channel.rs @@ -23,7 +23,7 @@ pub struct Sender(Arc>); struct SenderInner { // We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't // be accessed on the main thread, as it could block. Additionally we need - // to wrap `Sender` in an `Arc` to make it clonable on the main thread without + // to wrap `Sender` in an `Arc` to make it cloneable on the main thread without // having to block. sender: Mutex>, shared: Arc, diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index ad6c801965..3243ec47d4 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -377,19 +377,19 @@ impl BufferedEvent { match self { Self::Event(event) => dispatch(event), Self::ScaleFactorChanged(window_id, scale_factor, new_inner_size) => { - let user_new_innner_size = Arc::new(Mutex::new(new_inner_size)); + let user_new_inner_size = Arc::new(Mutex::new(new_inner_size)); dispatch(Event::WindowEvent { window_id, event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer: InnerSizeWriter::new(Arc::downgrade( - &user_new_innner_size, + &user_new_inner_size, )), }, }); - let inner_size = *user_new_innner_size.lock().unwrap(); + let inner_size = *user_new_inner_size.lock().unwrap(); - drop(user_new_innner_size); + drop(user_new_inner_size); if inner_size != new_inner_size { let window_flags = unsafe { diff --git a/src/platform_impl/windows/keyboard.rs b/src/platform_impl/windows/keyboard.rs index 46716f4c67..ab4a0f4070 100644 --- a/src/platform_impl/windows/keyboard.rs +++ b/src/platform_impl/windows/keyboard.rs @@ -98,7 +98,7 @@ impl KeyEventBuilder { MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) }, WM_KILLFOCUS => { - // sythesize keyup events + // synthesize keyup events let kbd_state = get_kbd_state(); let key_events = Self::synthesize_kbd_state(ElementState::Released, &kbd_state); MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) @@ -334,11 +334,11 @@ impl KeyEventBuilder { // We are synthesizing the press event for caps-lock first for the following reasons: // 1. If caps-lock is *not* held down but *is* active, then we have to synthesize all // printable keys, respecting the caps-lock state. - // 2. If caps-lock is held down, we could choose to sythesize its keypress after every other - // key, in which case all other keys *must* be sythesized as if the caps-lock state was - // be the opposite of what it currently is. + // 2. If caps-lock is held down, we could choose to synthesize its keypress after every + // other key, in which case all other keys *must* be sythesized as if the caps-lock state + // was be the opposite of what it currently is. // -- - // For the sake of simplicity we are choosing to always sythesize + // For the sake of simplicity we are choosing to always synthesize // caps-lock first, and always use the current caps-lock state // to determine the produced text if is_key_pressed!(VK_CAPITAL) { From 74bc57a21beee8220caad057032804a9f22216ff Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 16 Dec 2024 19:29:24 +0300 Subject: [PATCH 25/25] Winit version 0.30.6 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 24 ------------------------ src/changelog/v0.30.md | 26 ++++++++++++++++++++++++++ src/platform/android.rs | 2 +- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e880f6dd07..34e2b6d7f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.5" +version = "0.30.6" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index 981dcebb24..236fa7d7a9 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.5" +winit = "0.30.6" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3fc968497..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,27 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Added - -- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game` - to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games. -- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env - variables to test the respective modifiers of window creation. -- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`. -- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`. - -### Fixed - -- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize. -- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the - default activation policy, unless explicitly provided during initialization. -- On macOS, fix crash when calling `drag_window()` without a left click present. -- On X11, key events forward to IME anyway, even when it's disabled. -- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`. -- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again. -- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again. -- On X11, fix XInput handling that prevented a new window from getting the focus in some cases. -- On macOS, fix crash when pressing Caps Lock in certain configurations. -- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations. -- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index 11af4a99af..b51aaa95f6 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,29 @@ +## 0.30.6 + +### Added + +- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game` + to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games. +- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env + variables to test the respective modifiers of window creation. +- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`. +- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`. + +### Fixed + +- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize. +- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the + default activation policy, unless explicitly provided during initialization. +- On macOS, fix crash when calling `drag_window()` without a left click present. +- On X11, key events forward to IME anyway, even when it's disabled. +- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`. +- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again. +- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again. +- On X11, fix XInput handling that prevented a new window from getting the focus in some cases. +- On macOS, fix crash when pressing Caps Lock in certain configurations. +- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations. +- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor. + ## 0.30.5 ### Added diff --git a/src/platform/android.rs b/src/platform/android.rs index 823ef5a37b..5313215137 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -62,7 +62,7 @@ //! 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.30.5", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.6", //! 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