From b14d5c0c99812931006aea0e927dbed94edd0a14 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 12 Jun 2024 00:02:26 +0200 Subject: [PATCH 001/116] Web: queue `EventLoopProxy::send_event()` to microtask --- src/changelog/unreleased.md | 16 +++++ src/platform_impl/web/async/waker.rs | 47 ++++++++------- src/platform_impl/web/event_loop/runner.rs | 69 +++++++++++++++++----- 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..7c663de60e 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,19 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Changed + +- On Web, let events wake up event loop immediately when using + `ControlFlow::Poll`. + +### Fixed + +- On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately + when not called from inside the event loop. Now queues a microtask instead. + +### Removed + +- Remove `EventLoop::run`. +- Remove `EventLoopExtRunOnDemand::run_on_demand`. +- Remove `EventLoopExtPumpEvents::pump_events`. diff --git a/src/platform_impl/web/async/waker.rs b/src/platform_impl/web/async/waker.rs index 86771714db..40b316dc8d 100644 --- a/src/platform_impl/web/async/waker.rs +++ b/src/platform_impl/web/async/waker.rs @@ -1,4 +1,5 @@ use std::future; +use std::num::NonZeroUsize; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; use std::task::Poll; @@ -6,13 +7,13 @@ use std::task::Poll; use super::super::main_thread::MainThreadMarker; use super::{AtomicWaker, Wrapper}; -pub struct WakerSpawner(Wrapper, Sender, usize>); +pub struct WakerSpawner(Wrapper, Sender, NonZeroUsize>); -pub struct Waker(Wrapper, Sender, usize>); +pub struct Waker(Wrapper, Sender, NonZeroUsize>); struct Handler { value: T, - handler: fn(&T, usize), + handler: fn(&T, NonZeroUsize, bool), } #[derive(Clone)] @@ -20,7 +21,11 @@ struct Sender(Arc); impl WakerSpawner { #[track_caller] - pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, usize)) -> Option { + pub fn new( + main_thread: MainThreadMarker, + value: T, + handler: fn(&T, NonZeroUsize, bool), + ) -> Option { let inner = Arc::new(Inner { counter: AtomicUsize::new(0), waker: AtomicWaker::new(), @@ -37,7 +42,7 @@ impl WakerSpawner { |handler, count| { let handler = handler.borrow(); let handler = handler.as_ref().unwrap(); - (handler.handler)(&handler.value, count); + (handler.handler)(&handler.value, count, true); }, { let inner = Arc::clone(&inner); @@ -46,29 +51,31 @@ impl WakerSpawner { while let Some(count) = future::poll_fn(|cx| { let count = inner.counter.swap(0, Ordering::Relaxed); - if count > 0 { - Poll::Ready(Some(count)) - } else { - inner.waker.register(cx.waker()); + match NonZeroUsize::new(count) { + Some(count) => Poll::Ready(Some(count)), + None => { + inner.waker.register(cx.waker()); - let count = inner.counter.swap(0, Ordering::Relaxed); + let count = inner.counter.swap(0, Ordering::Relaxed); - if count > 0 { - Poll::Ready(Some(count)) - } else { - if inner.closed.load(Ordering::Relaxed) { - return Poll::Ready(None); - } + match NonZeroUsize::new(count) { + Some(count) => Poll::Ready(Some(count)), + None => { + if inner.closed.load(Ordering::Relaxed) { + return Poll::Ready(None); + } - Poll::Pending - } + Poll::Pending + }, + } + }, } }) .await { let handler = handler.borrow(); let handler = handler.as_ref().unwrap(); - (handler.handler)(&handler.value, count); + (handler.handler)(&handler.value, count, false); } } }, @@ -107,7 +114,7 @@ impl Drop for WakerSpawner { impl Waker { pub fn wake(&self) { - self.0.send(1) + self.0.send(NonZeroUsize::MIN) } } diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 14311d6890..bd2c3bbe1f 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -14,12 +14,15 @@ 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::Closure; +use wasm_bindgen::prelude::{wasm_bindgen, Closure}; +use wasm_bindgen::JsCast; use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent}; use web_time::{Duration, Instant}; @@ -133,12 +136,13 @@ impl Shared { let document = window.document().expect("Failed to obtain document"); Shared(Rc::::new_cyclic(|weak| { - let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, count| { - if let Some(runner) = runner.upgrade() { - Shared(runner).send_events(iter::repeat(Event::UserEvent(())).take(count)) - } - }) - .expect("`EventLoop` has to be created in the main thread"); + let proxy_spawner = + WakerSpawner::new(main_thread, weak.clone(), |runner, count, local| { + if let Some(runner) = runner.upgrade() { + Shared(runner).send_user_events(count, local) + } + }) + .expect("`EventLoop` has to be created in the main thread"); Execution { main_thread, @@ -460,6 +464,48 @@ impl Shared { self.send_events(iter::once(event)); } + // Add a series of user events to the event loop runner + // + // This will schedule the event loop to wake up instead of waking it up immediately if its not + // running. + pub(crate) fn send_user_events(&self, count: NonZeroUsize, local: bool) { + // If the event loop is closed, it should discard any new events + if self.is_closed() { + return; + } + + if local { + // If the loop is not running and triggered locally, queue on next microtick. + if let Ok(RunnerEnum::Running(_)) = + self.0.runner.try_borrow().as_ref().map(Deref::deref) + { + #[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()), + ) + } + } + }) + .unchecked_into(), + ); + + return; + } + } + + self.send_events(iter::repeat(Event::UserEvent(())).take(count.get())) + } + // Add a series of events to the event loop runner // // It will determine if the event should be immediately sent to the user or buffered for later @@ -471,13 +517,8 @@ impl Shared { // If we can run the event processing right now, or need to queue this and wait for later let mut process_immediately = true; match self.0.runner.try_borrow().as_ref().map(Deref::deref) { - Ok(RunnerEnum::Running(ref runner)) => { - // If we're currently polling, queue this and wait for the poll() method to be - // called - if let State::Poll { .. } = runner.state { - process_immediately = false; - } - }, + // If the runner is attached but not running, we always wake it up. + Ok(RunnerEnum::Running(_)) => (), Ok(RunnerEnum::Pending) => { // The runner still hasn't been attached: queue this event and wait for it to be process_immediately = false; From 54e974c090fba07999b594315e2d93d2b9f9fa01 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 12 Jun 2024 00:12:14 +0200 Subject: [PATCH 002/116] Web: don't overwrite cursor with `CursorIcon::Default` (#3729) --- src/changelog/unreleased.md | 1 + src/platform_impl/web/cursor.rs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 7c663de60e..50f46de40f 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -49,6 +49,7 @@ changelog entry. - On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately when not called from inside the event loop. Now queues a microtask instead. +- On Web, stop overwriting default cursor with `CursorIcon::Default`. ### Removed diff --git a/src/platform_impl/web/cursor.rs b/src/platform_impl/web/cursor.rs index 2844b2da50..0da977af95 100644 --- a/src/platform_impl/web/cursor.rs +++ b/src/platform_impl/web/cursor.rs @@ -324,7 +324,11 @@ impl Inner { match &self.cursor { SelectedCursor::Icon(icon) | SelectedCursor::Loading { previous: Previous::Icon(icon), .. } => { - self.style.set("cursor", icon.name()) + if let CursorIcon::Default = icon { + self.style.remove("cursor") + } else { + self.style.set("cursor", icon.name()) + } }, SelectedCursor::Loading { previous: Previous::Image(cursor), .. } | SelectedCursor::Image(cursor) => { From 1745b015021f5b8fe4647eaf428b0740affd1ecd Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 12 Jun 2024 00:22:03 +0200 Subject: [PATCH 003/116] Web: fix crash `InnerSizeWriter::request_inner_size()` (#3727) --- src/changelog/unreleased.md | 1 + src/platform_impl/web/web_sys/canvas.rs | 4 +- .../web/web_sys/resize_scaling.rs | 49 +++++++++---------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 50f46de40f..73486cdf4d 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -50,6 +50,7 @@ changelog entry. - On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately when not called from inside the event loop. Now queues a microtask instead. - On Web, stop overwriting default cursor with `CursorIcon::Default`. +- On Web, prevent crash when using `InnerSizeWriter::request_inner_size()`. ### Removed diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 0450ba8e0d..644ef76c8b 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -427,8 +427,8 @@ impl Canvas { pub(crate) fn on_resize_scale(&mut self, scale_handler: S, size_handler: R) where - S: 'static + FnMut(PhysicalSize, f64), - R: 'static + FnMut(PhysicalSize), + S: 'static + Fn(PhysicalSize, f64), + R: 'static + Fn(PhysicalSize), { self.on_resize_scale = Some(ResizeScaleHandle::new( self.window().clone(), diff --git a/src/platform_impl/web/web_sys/resize_scaling.rs b/src/platform_impl/web/web_sys/resize_scaling.rs index e22fba54b8..fdfda75acd 100644 --- a/src/platform_impl/web/web_sys/resize_scaling.rs +++ b/src/platform_impl/web/web_sys/resize_scaling.rs @@ -16,7 +16,7 @@ use super::media_query_handle::MediaQueryListHandle; use std::cell::{Cell, RefCell}; use std::rc::Rc; -pub struct ResizeScaleHandle(Rc>); +pub struct ResizeScaleHandle(Rc); impl ResizeScaleHandle { pub(crate) fn new( @@ -28,8 +28,8 @@ impl ResizeScaleHandle { resize_handler: R, ) -> Self where - S: 'static + FnMut(PhysicalSize, f64), - R: 'static + FnMut(PhysicalSize), + S: 'static + Fn(PhysicalSize, f64), + R: 'static + Fn(PhysicalSize), { Self(ResizeScaleInternal::new( window, @@ -42,7 +42,7 @@ impl ResizeScaleHandle { } pub(crate) fn notify_resize(&self) { - self.0.borrow_mut().notify() + self.0.notify() } } @@ -53,11 +53,11 @@ struct ResizeScaleInternal { document: Document, canvas: HtmlCanvasElement, style: Style, - mql: MediaQueryListHandle, + mql: RefCell, observer: ResizeObserver, _observer_closure: Closure, - scale_handler: Box, f64)>, - resize_handler: Box)>, + scale_handler: Box, f64)>, + resize_handler: Box)>, notify_scale: Cell, } @@ -69,12 +69,12 @@ impl ResizeScaleInternal { style: Style, scale_handler: S, resize_handler: R, - ) -> Rc> + ) -> Rc where - S: 'static + FnMut(PhysicalSize, f64), - R: 'static + FnMut(PhysicalSize), + S: 'static + Fn(PhysicalSize, f64), + R: 'static + Fn(PhysicalSize), { - Rc::>::new_cyclic(|weak_self| { + Rc::::new_cyclic(|weak_self| { let mql = Self::create_mql(&window, { let weak_self = weak_self.clone(); move |mql| { @@ -86,9 +86,7 @@ impl ResizeScaleInternal { let weak_self = weak_self.clone(); let observer_closure = Closure::new(move |entries: Array, _| { - if let Some(rc_self) = weak_self.upgrade() { - let mut this = rc_self.borrow_mut(); - + if let Some(this) = weak_self.upgrade() { let size = this.process_entry(entries); if this.notify_scale.replace(false) { @@ -101,18 +99,18 @@ impl ResizeScaleInternal { }); let observer = Self::create_observer(&canvas, observer_closure.as_ref()); - RefCell::new(Self { + Self { window, document, canvas, style, - mql, + mql: RefCell::new(mql), observer, _observer_closure: observer_closure, scale_handler: Box::new(scale_handler), resize_handler: Box::new(resize_handler), notify_scale: Cell::new(false), - }) + } }) } @@ -152,7 +150,7 @@ impl ResizeScaleInternal { observer } - fn notify(&mut self) { + fn notify(&self) { if !self.document.contains(Some(&self.canvas)) || self.style.get("display") == "none" { let size = PhysicalSize::new(0, 0); @@ -200,10 +198,9 @@ impl ResizeScaleInternal { } } - fn handle_scale(this: Rc>, mql: &MediaQueryList) { - let weak_self = Rc::downgrade(&this); - let mut this = this.borrow_mut(); - let scale = super::scale_factor(&this.window); + fn handle_scale(self: Rc, mql: &MediaQueryList) { + let weak_self = Rc::downgrade(&self); + let scale = super::scale_factor(&self.window); // TODO: confirm/reproduce this problem, see: // . @@ -217,15 +214,15 @@ impl ResizeScaleInternal { return; } - let new_mql = Self::create_mql(&this.window, move |mql| { + let new_mql = Self::create_mql(&self.window, move |mql| { if let Some(rc_self) = weak_self.upgrade() { Self::handle_scale(rc_self, mql); } }); - this.mql = new_mql; + self.mql.replace(new_mql); - this.notify_scale.set(true); - this.notify(); + self.notify_scale.set(true); + self.notify(); } fn process_entry(&self, entries: Array) -> PhysicalSize { From 96388f4f6b2b168ae4763f93f06b36fea5e0bd61 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 15 Jun 2024 15:26:26 +0300 Subject: [PATCH 004/116] chore: address 1.79 clippy lints --- src/platform_impl/android/mod.rs | 2 -- src/platform_impl/ios/app_state.rs | 6 +++--- src/platform_impl/ios/event_loop.rs | 7 +++---- src/platform_impl/ios/mod.rs | 1 - src/platform_impl/linux/wayland/mod.rs | 2 -- src/platform_impl/linux/x11/event_processor.rs | 2 +- src/platform_impl/linux/x11/ime/context.rs | 4 +++- src/platform_impl/linux/x11/mod.rs | 2 -- src/platform_impl/macos/event_loop.rs | 3 +-- src/platform_impl/macos/observer.rs | 10 +++++----- src/platform_impl/macos/window_delegate.rs | 4 ++-- src/platform_impl/orbital/mod.rs | 2 +- src/platform_impl/web/async/dispatcher.rs | 7 ++++++- src/platform_impl/web/event_loop/mod.rs | 6 +++++- src/platform_impl/windows/mod.rs | 2 -- 15 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 31285b63f9..e279b5bdb0 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -1,5 +1,3 @@ -#![cfg(android_platform)] - use std::cell::Cell; use std::collections::VecDeque; use std::hash::Hash; diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 172ffe8e42..f62fe5e70a 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -798,7 +798,7 @@ impl EventLoopWaker { // future, but that gets changed to fire immediately in did_finish_launching let timer = CFRunLoopTimerCreate( ptr::null_mut(), - std::f64::MAX, + f64::MAX, 0.000_000_1, 0, 0, @@ -812,11 +812,11 @@ impl EventLoopWaker { } fn stop(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) } } fn start(&mut self) { - unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) } } fn start_at(&mut self, instant: Instant) { diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index f05429598e..ac485b9e8f 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -274,8 +274,7 @@ impl EventLoopProxy { cancel: None, perform: event_loop_proxy_handler, }; - let source = - CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context); + let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); CFRunLoopWakeUp(rl); @@ -358,7 +357,7 @@ fn setup_control_flow_observers() { ptr::null_mut(), kCFRunLoopAfterWaiting, 1, // repeat = true - CFIndex::min_value(), + CFIndex::MIN, control_flow_begin_handler, ptr::null_mut(), ); @@ -378,7 +377,7 @@ fn setup_control_flow_observers() { ptr::null_mut(), kCFRunLoopExit | kCFRunLoopBeforeWaiting, 1, // repeat = true - CFIndex::max_value(), + CFIndex::MAX, control_flow_end_handler, ptr::null_mut(), ); diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index bf2c011f25..f69ac21b79 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -1,4 +1,3 @@ -#![cfg(ios_platform)] #![allow(clippy::let_unit_value)] mod app_delegate; diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs index 3e5939f6db..674671ff8c 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -1,5 +1,3 @@ -#![cfg(wayland_platform)] - //! Winit's Wayland backend. use std::fmt::Display; diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 4b233ec692..9c6b95a2ac 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -434,7 +434,7 @@ impl EventProcessor { let flags = xev.data.get_long(1); let version = flags >> 24; self.dnd.version = Some(version); - let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1; + let has_more_types = flags - (flags & (c_long::MAX - 1)) == 1; if !has_more_types { let type_list = vec![ xev.data.get_long(2) as xproto::Atom, diff --git a/src/platform_impl/linux/x11/ime/context.rs b/src/platform_impl/linux/x11/ime/context.rs index 1cc8c355b9..aa046af67b 100644 --- a/src/platform_impl/linux/x11/ime/context.rs +++ b/src/platform_impl/linux/x11/ime/context.rs @@ -158,7 +158,9 @@ struct PreeditCallbacks { impl PreeditCallbacks { pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks { let start_callback = create_xim_callback(client_data, unsafe { - mem::transmute(preedit_start_callback as usize) + mem::transmute::( + preedit_start_callback as usize, + ) }); let done_callback = create_xim_callback(client_data, preedit_done_callback); let caret_callback = create_xim_callback(client_data, preedit_caret_callback); diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 6a4708e7b5..88071b72ba 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -1,5 +1,3 @@ -#![cfg(x11_platform)] - use std::cell::{Cell, RefCell}; use std::collections::{HashMap, HashSet, VecDeque}; use std::ffi::CStr; diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 0fcd21363c..1680953c34 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -482,8 +482,7 @@ impl EventLoopProxy { cancel: None, perform: event_loop_proxy_handler, }; - let source = - CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context); + let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context); CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); CFRunLoopWakeUp(rl); diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index 1f960d0a6b..8339803086 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -214,13 +214,13 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak) { let dimensions = dimensions.unwrap_or(Size::Logical(LogicalSize { - width: std::f32::MAX as f64, - height: std::f32::MAX as f64, + width: f32::MAX as f64, + height: f32::MAX as f64, })); let scale_factor = self.scale_factor(); let max_size = dimensions.to_logical::(scale_factor); diff --git a/src/platform_impl/orbital/mod.rs b/src/platform_impl/orbital/mod.rs index 91d85c3d16..f210a8ce04 100644 --- a/src/platform_impl/orbital/mod.rs +++ b/src/platform_impl/orbital/mod.rs @@ -103,7 +103,7 @@ pub struct WindowId { impl WindowId { pub const fn dummy() -> Self { - WindowId { fd: u64::max_value() } + WindowId { fd: u64::MAX } } } diff --git a/src/platform_impl/web/async/dispatcher.rs b/src/platform_impl/web/async/dispatcher.rs index 6623572154..10ab345e44 100644 --- a/src/platform_impl/web/async/dispatcher.rs +++ b/src/platform_impl/web/async/dispatcher.rs @@ -68,7 +68,12 @@ impl Dispatcher { // SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is // safe because this function won't return until `f` has finished executing. See // `Self::new()`. - let closure = Closure(unsafe { std::mem::transmute(closure) }); + let closure = Closure(unsafe { + std::mem::transmute::< + Box, + Box, + >(closure) + }); self.0.send(closure); diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index 263a5fd964..738bd2749b 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -53,7 +53,11 @@ impl EventLoop { // SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe // because this function will never return and all resources not cleaned up by the point we // `throw` will leak, making this actually `'static`. - let handler = unsafe { std::mem::transmute(handler) }; + let handler = unsafe { + std::mem::transmute::)>, Box) + 'static>>( + handler, + ) + }; self.elw.p.run(handler, false); // Throw an exception to break out of Rust execution and use unreachable to tell the diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index bfb18d81cc..15051f0ca3 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -1,5 +1,3 @@ -#![cfg(windows_platform)] - use smol_str::SmolStr; use windows_sys::Win32::Foundation::{HANDLE, HWND}; use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX}; From b512ed1e6340255ea49cd1aed4fda45570537867 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 15 Jun 2024 15:41:34 +0300 Subject: [PATCH 005/116] macOS: fix opacity handling Not using `NSColor::clearColor()` results in Quartz thinking that the window is not transparent at all, which results in artifacts. However, not setting the `windowBackgroundColor` in `Window::set_transparent` results in border not properly rendered. Fixes: 94664ff6876cd (Don't set the background color) --- Cargo.toml | 1 + src/platform_impl/macos/window_delegate.rs | 22 ++++++++++++++++++++-- src/window.rs | 3 +-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e38d46e29a..dcbe781f6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,6 +142,7 @@ features = [ "NSApplication", "NSBitmapImageRep", "NSButton", + "NSColor", "NSControl", "NSCursor", "NSDragging", diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 882a11ef47..1e3675f1ca 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -10,7 +10,7 @@ use objc2::runtime::{AnyObject, ProtocolObject}; use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass}; use objc2_app_kit::{ NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication, - NSApplicationPresentationOptions, NSBackingStoreType, NSDraggingDestination, + NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, @@ -613,6 +613,8 @@ fn new_window( if attrs.transparent { window.setOpaque(false); + // See `set_transparent` for details on why we do this. + window.setBackgroundColor(unsafe { Some(&NSColor::clearColor()) }); } // register for drag and drop operations. @@ -821,7 +823,23 @@ impl WindowDelegate { } pub fn set_transparent(&self, transparent: bool) { - self.window().setOpaque(!transparent) + // This is just a hint for Quartz, it doesn't actually speculate with window alpha. + // Providing a wrong value here could result in visual artifacts, when the window is + // transparent. + self.window().setOpaque(!transparent); + + // AppKit draws the window with a background color by default, which is usually really + // nice, but gets in the way when we want to allow the contents of the window to be + // transparent, as in that case, the transparent contents will just be drawn on top of + // the background color. As such, to allow the window to be transparent, we must also set + // the background color to one with an empty alpha channel. + let color = if transparent { + unsafe { NSColor::clearColor() } + } else { + unsafe { NSColor::windowBackgroundColor() } + }; + + self.window().setBackgroundColor(Some(&color)); } pub fn set_blur(&self, blur: bool) { diff --git a/src/window.rs b/src/window.rs index f443a71db4..76fa34e405 100644 --- a/src/window.rs +++ b/src/window.rs @@ -942,8 +942,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **macOS:** If you're not drawing to the window yourself, you might have to set the - /// background color of the window to enable transparency. + /// - **macOS:** This will reset the window's background color. /// - **Web / iOS / Android:** Unsupported. /// - **X11:** Can only be set while building the window, with /// [`WindowAttributes::with_transparent`]. From ecd14688dcd902a1646c85098b1742c11dd4d36e Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 15 Jun 2024 16:37:29 +0300 Subject: [PATCH 006/116] Revert: Web: don't wait for polling when sending events This is a breaking change, thus revert it for patch series. --- src/changelog/unreleased.md | 5 --- src/platform_impl/web/event_loop/runner.rs | 50 +++++++++++++--------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 73486cdf4d..03bef7103e 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -40,11 +40,6 @@ changelog entry. ## Unreleased -### Changed - -- On Web, let events wake up event loop immediately when using - `ControlFlow::Poll`. - ### Fixed - On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index bd2c3bbe1f..7a553a50d2 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -476,30 +476,33 @@ impl Shared { if local { // If the loop is not running and triggered locally, queue on next microtick. - if let Ok(RunnerEnum::Running(_)) = + if let Ok(RunnerEnum::Running(ref runner)) = self.0.runner.try_borrow().as_ref().map(Deref::deref) { - #[wasm_bindgen] - extern "C" { - #[wasm_bindgen(js_name = queueMicrotask)] - fn queue_microtask(task: Function); - } + // 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()), - ) + 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; + } } } @@ -517,8 +520,13 @@ impl Shared { // If we can run the event processing right now, or need to queue this and wait for later let mut process_immediately = true; match self.0.runner.try_borrow().as_ref().map(Deref::deref) { - // If the runner is attached but not running, we always wake it up. - Ok(RunnerEnum::Running(_)) => (), + Ok(RunnerEnum::Running(ref runner)) => { + // If we're currently polling, queue this and wait for the poll() method to be + // called. + if let State::Poll { .. } = runner.state { + process_immediately = false; + } + }, Ok(RunnerEnum::Pending) => { // The runner still hasn't been attached: queue this event and wait for it to be process_immediately = false; From 79aa95b212e930b5bff29b329cef2814f9f0209b Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 15 Jun 2024 16:40:36 +0300 Subject: [PATCH 007/116] Winit version 0.30.2 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 13 ------------- src/changelog/v0.30.md | 10 ++++++++++ src/platform/android.rs | 2 +- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dcbe781f6d..97b8924a3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.1" +version = "0.30.2" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index b66142122b..0b4af6e431 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.1" +winit = "0.30.2" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 03bef7103e..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,16 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Fixed - -- On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately - when not called from inside the event loop. Now queues a microtask instead. -- On Web, stop overwriting default cursor with `CursorIcon::Default`. -- On Web, prevent crash when using `InnerSizeWriter::request_inner_size()`. - -### Removed - -- Remove `EventLoop::run`. -- Remove `EventLoopExtRunOnDemand::run_on_demand`. -- Remove `EventLoopExtPumpEvents::pump_events`. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index 92efbb66ba..dc1182a3eb 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,13 @@ +## 0.30.2 + +### Fixed + +- On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately + when not called from inside the event loop. Now queues a microtask instead. +- On Web, stop overwriting default cursor with `CursorIcon::Default`. +- On Web, prevent crash when using `InnerSizeWriter::request_inner_size()`. +- On macOS, fix not working opacity for entire window. + ## 0.30.1 ### Added diff --git a/src/platform/android.rs b/src/platform/android.rs index de8ff40ad7..2227f3a7a9 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.1", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.2", //! 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 From c73d8cff202cfaa1f0de671bab6d3ce24d1e21a8 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 17 Jun 2024 13:51:08 +0300 Subject: [PATCH 008/116] x11: fix build on arm The c_char type, which was used under the hood is different depending on arch, thus use it directly instead of i8. Fixes #3735. --- src/changelog/unreleased.md | 4 ++++ src/platform_impl/linux/x11/ime/context.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..a399e03468 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Fixed + +- On X11, build on arm platforms. diff --git a/src/platform_impl/linux/x11/ime/context.rs b/src/platform_impl/linux/x11/ime/context.rs index aa046af67b..89a241cce3 100644 --- a/src/platform_impl/linux/x11/ime/context.rs +++ b/src/platform_impl/linux/x11/ime/context.rs @@ -158,7 +158,7 @@ struct PreeditCallbacks { impl PreeditCallbacks { pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks { let start_callback = create_xim_callback(client_data, unsafe { - mem::transmute::( + mem::transmute::( preedit_start_callback as usize, ) }); From 3d7d7661826b4af6727f13265b01bb5914b0e587 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 20 Jun 2024 16:05:34 +0200 Subject: [PATCH 009/116] macOS: set the theme on the NSWindow, instead of application-wide This new implementation uses: - The NSAppearanceCustomization protocol for retrieving the appearance of the window, instead of using the application-wide `-[NSApplication effectiveAppearance]`. - Key-Value observing for observing the `effectiveAppearance` to compute the `ThemeChanged` event, instead of using the undocumented `AppleInterfaceThemeChangedNotification` notification. This also fixes `WindowBuilder::with_theme` not having any effect, and the conversion between `Theme` and `NSAppearance` is made a bit more robust. --- Cargo.toml | 1 + examples/window.rs | 20 ++- src/changelog/unreleased.md | 5 + src/event.rs | 2 + src/platform_impl/macos/window_delegate.rs | 195 +++++++++++++-------- src/window.rs | 14 +- 6 files changed, 153 insertions(+), 84 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 97b8924a3c..c1ae8cf0af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,7 @@ features = [ "NSDictionary", "NSDistributedNotificationCenter", "NSEnumerator", + "NSKeyValueObserving", "NSNotification", "NSObjCRuntime", "NSPathUtilities", diff --git a/examples/window.rs b/examples/window.rs index 3a4bd61519..11f324713c 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -212,6 +212,12 @@ impl Application { Action::PrintHelp => self.print_help(), #[cfg(macos_platform)] Action::CycleOptionAsAlt => window.cycle_option_as_alt(), + Action::SetTheme(theme) => { + window.window.set_theme(theme); + // Get the resulting current theme to draw with + let actual_theme = theme.or_else(|| window.window.theme()).unwrap_or(Theme::Dark); + window.set_draw_theme(actual_theme); + }, #[cfg(macos_platform)] Action::CreateNewTab => { let tab_id = window.window.tabbing_identifier(); @@ -334,7 +340,7 @@ impl ApplicationHandler for Application { }, WindowEvent::ThemeChanged(theme) => { info!("Theme changed to {theme:?}"); - window.set_theme(theme); + window.set_draw_theme(theme); }, WindowEvent::RedrawRequested => { if let Err(err) = window.draw() { @@ -733,8 +739,8 @@ impl WindowState { self.window.request_redraw(); } - /// Change the theme. - fn set_theme(&mut self, theme: Theme) { + /// Change the theme that things are drawn in. + fn set_draw_theme(&mut self, theme: Theme) { self.theme = theme; self.window.request_redraw(); } @@ -884,6 +890,7 @@ enum Action { ShowWindowMenu, #[cfg(macos_platform)] CycleOptionAsAlt, + SetTheme(Option), #[cfg(macos_platform)] CreateNewTab, RequestResize, @@ -915,6 +922,9 @@ impl Action { Action::ShowWindowMenu => "Show window menu", #[cfg(macos_platform)] Action::CycleOptionAsAlt => "Cycle option as alt mode", + Action::SetTheme(None) => "Change to the system theme", + Action::SetTheme(Some(Theme::Light)) => "Change to a light theme", + Action::SetTheme(Some(Theme::Dark)) => "Change to a dark theme", #[cfg(macos_platform)] Action::CreateNewTab => "Create new tab", Action::RequestResize => "Request a resize", @@ -1059,6 +1069,10 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[ Action::AnimationCustomCursor, ), Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility), + // K. + Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)), + Binding::new("K", ModifiersState::SUPER, Action::SetTheme(Some(Theme::Light))), + Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))), #[cfg(macos_platform)] Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab), #[cfg(macos_platform)] diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index a399e03468..6adb37b188 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -40,6 +40,11 @@ changelog entry. ## Unreleased +### Changed + +- On macOS, set the window theme on the `NSWindow` instead of application-wide. + ### Fixed - On X11, build on arm platforms. +- On macOS, fixed `WindowBuilder::with_theme` not having any effect on the window. diff --git a/src/event.rs b/src/event.rs index 5cd3877a26..30a71d6514 100644 --- a/src/event.rs +++ b/src/event.rs @@ -389,6 +389,8 @@ pub enum WindowEvent { /// Applications might wish to react to this to change the theme of the content of the window /// when the system changes the window theme. /// + /// This only reports a change if the window theme was not overridden by [`Window::set_theme`]. + /// /// ## Platform-specific /// /// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported. diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 1e3675f1ca..674088cf0a 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1,6 +1,8 @@ #![allow(clippy::unnecessary_cast)] use std::cell::{Cell, RefCell}; use std::collections::VecDeque; +use std::ffi::c_void; +use std::ptr; use std::sync::{Arc, Mutex}; use core_graphics::display::{CGDisplay, CGPoint}; @@ -9,18 +11,20 @@ use objc2::rc::{autoreleasepool, Retained}; use objc2::runtime::{AnyObject, ProtocolObject}; use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass}; use objc2_app_kit::{ - NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication, - NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSDraggingDestination, - NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSView, - NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, - NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, - NSWindowTabbingMode, NSWindowTitleVisibility, + NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, + NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, + NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, + NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate, + NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, + NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, }; use objc2_foundation::{ - ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDistributedNotificationCenter, - NSObject, NSObjectNSDelayedPerforming, NSObjectNSThreadPerformAdditions, NSObjectProtocol, - NSPoint, NSRect, NSSize, NSString, + ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey, + NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSObject, + NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, + NSRect, NSSize, NSString, }; +use tracing::{trace, warn}; use super::app_state::ApplicationDelegate; use super::cursor::cursor_from_icon; @@ -79,8 +83,6 @@ pub(crate) struct State { window: Retained, - current_theme: Cell>, - // During `windowDidResize`, we use this to only send Moved if the position changed. // // This is expressed in native screen coordinates. @@ -419,32 +421,66 @@ declare_class!( } } + // Key-Value Observing unsafe impl WindowDelegate { - // Observe theme change - #[method(effectiveAppearanceDidChange:)] - fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) { - trace_scope!("effectiveAppearanceDidChange:"); - unsafe { - self.performSelectorOnMainThread_withObject_waitUntilDone( - sel!(effectiveAppearanceDidChangedOnMainThread:), - sender, - false, - ) - }; - } + #[method(observeValueForKeyPath:ofObject:change:context:)] + fn observe_value( + &self, + key_path: Option<&NSString>, + _object: Option<&AnyObject>, + change: Option<&NSDictionary>, + _context: *mut c_void, + ) { + trace_scope!("observeValueForKeyPath:ofObject:change:context:"); + // NOTE: We don't _really_ need to check the key path, as there should only be one, but + // in the future we might want to observe other key paths. + if key_path == Some(ns_string!("effectiveAppearance")) { + let change = change.expect("requested a change dictionary in `addObserver`, but none was provided"); + let old = change.get(unsafe { NSKeyValueChangeOldKey }).expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`"); + let new = change.get(unsafe { NSKeyValueChangeNewKey }).expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`"); + + // SAFETY: The value of `effectiveAppearance` is `NSAppearance` + let old: *const AnyObject = old; + let old: *const NSAppearance = old.cast(); + let old: &NSAppearance = unsafe { &*old }; + let new: *const AnyObject = new; + let new: *const NSAppearance = new.cast(); + let new: &NSAppearance = unsafe { &*new }; + + trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed"); + + // Ignore the change if the window's theme is customized by the user (since in that + // case the `effectiveAppearance` is only emitted upon said customization, and then + // it's triggered directly by a user action, and we don't want to emit the event). + if unsafe { self.window().appearance() }.is_some() { + return; + } - #[method(effectiveAppearanceDidChangedOnMainThread:)] - fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) { - let mtm = MainThreadMarker::from(self); - let theme = get_ns_theme(mtm); - let old_theme = self.ivars().current_theme.replace(Some(theme)); - if old_theme != Some(theme) { - self.queue_event(WindowEvent::ThemeChanged(theme)); + let old = appearance_to_theme(old); + let new = appearance_to_theme(new); + // Check that the theme changed in Winit's terms (the theme might have changed on + // other parameters, such as level of contrast, but the event should not be emitted + // in those cases). + if old == new { + return; + } + + self.queue_event(WindowEvent::ThemeChanged(new)); + } else { + panic!("unknown observed keypath {key_path:?}"); } } } ); +impl Drop for WindowDelegate { + fn drop(&mut self) { + unsafe { + self.window().removeObserver_forKeyPath(self, ns_string!("effectiveAppearance")); + } + } +} + fn new_window( app_delegate: &ApplicationDelegate, attrs: &WindowAttributes, @@ -668,15 +704,13 @@ impl WindowDelegate { let scale_factor = window.backingScaleFactor() as _; - let current_theme = match attrs.preferred_theme { - Some(theme) => Some(theme), - None => Some(get_ns_theme(mtm)), - }; + if let Some(appearance) = theme_to_appearance(attrs.preferred_theme) { + unsafe { window.setAppearance(Some(&appearance)) }; + } let delegate = mtm.alloc().set_ivars(State { app_delegate: app_delegate.retain(), window: window.retain(), - current_theme: Cell::new(current_theme), previous_position: Cell::new(None), previous_scale_factor: Cell::new(scale_factor), resize_increments: Cell::new(resize_increments), @@ -702,14 +736,16 @@ impl WindowDelegate { } window.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); - // Enable theme change event - let notification_center = unsafe { NSDistributedNotificationCenter::defaultCenter() }; + // Listen for theme change event. + // + // SAFETY: The observer is un-registered in the `Drop` of the delegate. unsafe { - notification_center.addObserver_selector_name_object( + window.addObserver_forKeyPath_options_context( &delegate, - sel!(effectiveAppearanceDidChange:), - Some(ns_string!("AppleInterfaceThemeChangedNotification")), - None, + ns_string!("effectiveAppearance"), + NSKeyValueObservingOptions::NSKeyValueObservingOptionNew + | NSKeyValueObservingOptions::NSKeyValueObservingOptionOld, + ptr::null_mut(), ) }; @@ -1615,20 +1651,24 @@ impl WindowDelegate { } } - #[inline] - pub fn theme(&self) -> Option { - self.ivars().current_theme.get() - } - #[inline] pub fn has_focus(&self) -> bool { self.window().isKeyWindow() } + pub fn theme(&self) -> Option { + // Note: We could choose between returning the value of `effectiveAppearance` or + // `appearance`, depending on what the user is asking about: + // - "how should I render on this particular frame". + // - "what is the configuration for this window". + // + // We choose the latter for consistency with the `set_theme` call, though it might also be + // useful to expose the former. + Some(appearance_to_theme(unsafe { &*self.window().appearance()? })) + } + pub fn set_theme(&self, theme: Option) { - let mtm = MainThreadMarker::from(self); - set_ns_theme(theme, mtm); - self.ivars().current_theme.set(theme.or_else(|| Some(get_ns_theme(mtm)))); + unsafe { self.window().setAppearance(theme_to_appearance(theme).as_deref()) }; } #[inline] @@ -1787,34 +1827,39 @@ impl WindowExtMacOS for WindowDelegate { const DEFAULT_STANDARD_FRAME: NSRect = NSRect::new(NSPoint::new(50.0, 50.0), NSSize::new(800.0, 600.0)); -pub(super) fn get_ns_theme(mtm: MainThreadMarker) -> Theme { - let app = NSApplication::sharedApplication(mtm); - if !app.respondsToSelector(sel!(effectiveAppearance)) { - return Theme::Light; - } - let appearance = app.effectiveAppearance(); - let name = appearance - .bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[ - NSString::from_str("NSAppearanceNameAqua"), - NSString::from_str("NSAppearanceNameDarkAqua"), - ])) - .unwrap(); - match &*name.to_string() { - "NSAppearanceNameDarkAqua" => Theme::Dark, - _ => Theme::Light, +fn dark_appearance_name() -> &'static NSString { + // Don't use the static `NSAppearanceNameDarkAqua` to allow linking on macOS < 10.14 + ns_string!("NSAppearanceNameDarkAqua") +} + +fn appearance_to_theme(appearance: &NSAppearance) -> Theme { + let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[ + unsafe { NSAppearanceNameAqua.copy() }, + dark_appearance_name().copy(), + ])); + if let Some(best_match) = best_match { + if *best_match == *dark_appearance_name() { + Theme::Dark + } else { + Theme::Light + } + } else { + warn!(?appearance, "failed to determine the theme of the appearance"); + // Default to light in this case + Theme::Light } } -fn set_ns_theme(theme: Option, mtm: MainThreadMarker) { - let app = NSApplication::sharedApplication(mtm); - if app.respondsToSelector(sel!(effectiveAppearance)) { - let appearance = theme.map(|t| { - let name = match t { - Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"), - Theme::Light => NSString::from_str("NSAppearanceNameAqua"), - }; - NSAppearance::appearanceNamed(&name).unwrap() - }); - app.setAppearance(appearance.as_ref().map(|a| a.as_ref())); +fn theme_to_appearance(theme: Option) -> Option> { + let appearance = match theme? { + Theme::Light => unsafe { NSAppearance::appearanceNamed(NSAppearanceNameAqua) }, + Theme::Dark => NSAppearance::appearanceNamed(dark_appearance_name()), + }; + if let Some(appearance) = appearance { + Some(appearance) + } else { + warn!(?theme, "could not find appearance for theme"); + // Assume system appearance in this case + None } } diff --git a/src/window.rs b/src/window.rs index 76fa34e405..775b3f5352 100644 --- a/src/window.rs +++ b/src/window.rs @@ -393,7 +393,6 @@ impl WindowAttributes { /// /// ## Platform-specific /// - /// - **macOS:** This is an app-wide setting. /// - **Wayland:** This controls only CSD. When using `None` it'll try to use dbus to get the /// system preference. When explicit theme is used, this will avoid dbus all together. /// - **x11:** Build window with `_GTK_THEME_VARIANT` hint set to `dark` or `light`. @@ -1354,11 +1353,12 @@ impl Window { self.window.maybe_queue_on_main(move |w| w.request_user_attention(request_type)) } - /// Sets the current window theme. Use `None` to fallback to system default. + /// Set or override the window theme. + /// + /// Specify `None` to reset the theme to the system default. /// /// ## Platform-specific /// - /// - **macOS:** This is an app-wide setting. /// - **Wayland:** Sets the theme for the client side decorations. Using `None` will use dbus to /// get the system preference. /// - **X11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used, it @@ -1374,12 +1374,14 @@ impl Window { self.window.maybe_queue_on_main(move |w| w.set_theme(theme)) } - /// Returns the current window theme. + /// Returns the current window theme override. + /// + /// Returns `None` if the current theme is set as the system default, or if it cannot be + /// determined on the current platform. /// /// ## Platform-specific /// - /// - **macOS:** This is an app-wide setting. - /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported. + /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported, returns `None`. #[inline] pub fn theme(&self) -> Option { let _span = tracing::debug_span!("winit::Window::theme",).entered(); From a974640a66cc86cc89c8c99080022124f7232eda Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 20 Jun 2024 22:56:08 +0200 Subject: [PATCH 010/116] Web: set control flow strategies on `EventLoop` (#3740) --- src/platform/web.rs | 22 ++++++++++++++++++++++ src/platform_impl/web/event_loop/mod.rs | 9 +++++++++ 2 files changed, 31 insertions(+) diff --git a/src/platform/web.rs b/src/platform/web.rs index 25765ae8e3..f43a51cba1 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -190,6 +190,20 @@ pub trait EventLoopExtWebSys { fn spawn(self, event_handler: F) where F: 'static + FnMut(Event, &ActiveEventLoop); + + /// Sets the strategy for [`ControlFlow::Poll`]. + /// + /// See [`PollStrategy`]. + /// + /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll + fn set_poll_strategy(&self, strategy: PollStrategy); + + /// Gets the strategy for [`ControlFlow::Poll`]. + /// + /// See [`PollStrategy`]. + /// + /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll + fn poll_strategy(&self) -> PollStrategy; } impl EventLoopExtWebSys for EventLoop { @@ -207,6 +221,14 @@ impl EventLoopExtWebSys for EventLoop { { self.event_loop.spawn(event_handler) } + + fn set_poll_strategy(&self, strategy: PollStrategy) { + self.event_loop.set_poll_strategy(strategy); + } + + fn poll_strategy(&self) -> PollStrategy { + self.event_loop.poll_strategy() + } } pub trait ActiveEventLoopExtWebSys { diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index 738bd2749b..aa0a0d1102 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -4,6 +4,7 @@ use std::sync::mpsc::{self, Receiver, Sender}; use crate::error::EventLoopError; use crate::event::Event; use crate::event_loop::ActiveEventLoop as RootActiveEventLoop; +use crate::platform::web::{ActiveEventLoopExtWebSys, PollStrategy}; use super::{backend, device, window}; @@ -99,4 +100,12 @@ impl EventLoop { pub fn window_target(&self) -> &RootActiveEventLoop { &self.elw } + + pub fn set_poll_strategy(&self, strategy: PollStrategy) { + self.elw.set_poll_strategy(strategy); + } + + pub fn poll_strategy(&self) -> PollStrategy { + self.elw.poll_strategy() + } } From d8f4d8f1b7e9c6bb4ecaec7f0dc210c0ee000b9f Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 20 Jun 2024 23:07:42 +0200 Subject: [PATCH 011/116] Web: implement `WaitUntilStrategy` (#3739) --- .github/workflows/ci.yml | 16 ++++ .gitignore | 3 - .swcrc | 12 +++ Cargo.toml | 2 + src/platform/web.rs | 69 ++++++++++++++ src/platform_impl/web/event_loop/mod.rs | 10 +- src/platform_impl/web/event_loop/runner.rs | 13 ++- .../web/event_loop/window_target.rs | 10 +- src/platform_impl/web/web_sys/schedule.rs | 94 +++++++++++++++++-- src/platform_impl/web/web_sys/worker.js | 10 ++ src/platform_impl/web/web_sys/worker.min.js | 1 + 11 files changed, 226 insertions(+), 14 deletions(-) create mode 100644 .swcrc create mode 100644 src/platform_impl/web/web_sys/worker.js create mode 100644 src/platform_impl/web/web_sys/worker.min.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecf78fcf25..d940ec0ffb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -226,3 +226,19 @@ jobs: command: check log-level: error arguments: --all-features --target ${{ matrix.platform.target }} + + swc: + name: Minimize JavaScript + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install SWC + run: sudo npm i -g @swc/cli + - name: Run SWC + run: | + swc src/platform_impl/web/web_sys/worker.js -o src/platform_impl/web/web_sys/worker.min.js + - name: Check for diff + run: | + [[ -z $(git status -s) ]] diff --git a/.gitignore b/.gitignore index ebed8e3651..2caffd8242 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,5 @@ target/ rls/ .vscode/ *~ -*.wasm -*.ts -*.js #*# .DS_Store diff --git a/.swcrc b/.swcrc new file mode 100644 index 0000000000..1d7eccb99d --- /dev/null +++ b/.swcrc @@ -0,0 +1,12 @@ +{ + "minify": true, + "jsc": { + "target": "es2022", + "minify": { + "compress": { + "unused": true + }, + "mangle": true + } + } +} diff --git a/Cargo.toml b/Cargo.toml index c1ae8cf0af..337a036978 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -285,6 +285,7 @@ features = [ 'AbortController', 'AbortSignal', 'Blob', + 'BlobPropertyBag', 'console', 'CssStyleDeclaration', 'Document', @@ -320,6 +321,7 @@ features = [ 'VisibilityState', 'Window', 'WheelEvent', + 'Worker', 'Url', ] diff --git a/src/platform/web.rs b/src/platform/web.rs index f43a51cba1..23831ceaa3 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -204,6 +204,20 @@ pub trait EventLoopExtWebSys { /// /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll fn poll_strategy(&self) -> PollStrategy; + + /// Sets the strategy for [`ControlFlow::WaitUntil`]. + /// + /// See [`WaitUntilStrategy`]. + /// + /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil + fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy); + + /// Gets the strategy for [`ControlFlow::WaitUntil`]. + /// + /// See [`WaitUntilStrategy`]. + /// + /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil + fn wait_until_strategy(&self) -> WaitUntilStrategy; } impl EventLoopExtWebSys for EventLoop { @@ -229,6 +243,14 @@ impl EventLoopExtWebSys for EventLoop { fn poll_strategy(&self) -> PollStrategy { self.event_loop.poll_strategy() } + + fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { + self.event_loop.set_wait_until_strategy(strategy); + } + + fn wait_until_strategy(&self) -> WaitUntilStrategy { + self.event_loop.wait_until_strategy() + } } pub trait ActiveEventLoopExtWebSys { @@ -246,6 +268,20 @@ pub trait ActiveEventLoopExtWebSys { /// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll fn poll_strategy(&self) -> PollStrategy; + /// Sets the strategy for [`ControlFlow::WaitUntil`]. + /// + /// See [`WaitUntilStrategy`]. + /// + /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil + fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy); + + /// Gets the strategy for [`ControlFlow::WaitUntil`]. + /// + /// See [`WaitUntilStrategy`]. + /// + /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil + fn wait_until_strategy(&self) -> WaitUntilStrategy; + /// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the /// cursor has completely finished loading. fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture; @@ -266,6 +302,16 @@ impl ActiveEventLoopExtWebSys for ActiveEventLoop { fn poll_strategy(&self) -> PollStrategy { self.p.poll_strategy() } + + #[inline] + fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { + self.p.set_wait_until_strategy(strategy); + } + + #[inline] + fn wait_until_strategy(&self) -> WaitUntilStrategy { + self.p.wait_until_strategy() + } } /// Strategy used for [`ControlFlow::Poll`][crate::event_loop::ControlFlow::Poll]. @@ -294,6 +340,29 @@ pub enum PollStrategy { Scheduler, } +/// Strategy used for [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil]. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum WaitUntilStrategy { + /// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available + /// this will fallback to [`setTimeout()`]. + /// + /// This strategy is commonly not affected by browser throttling unless the window is not + /// focused. + /// + /// This is the default strategy. + /// + /// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API + /// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout + #[default] + Scheduler, + /// Equal to [`Scheduler`][Self::Scheduler] but wakes up the event loop from a [worker]. + /// + /// This strategy is commonly not affected by browser throttling regardless of window focus. + /// + /// [worker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API + Worker, +} + pub trait CustomCursorExtWebSys { /// Returns if this cursor is an animation. fn is_animation(&self) -> bool; diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index aa0a0d1102..23fe2e00ad 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -4,7 +4,7 @@ use std::sync::mpsc::{self, Receiver, Sender}; use crate::error::EventLoopError; use crate::event::Event; use crate::event_loop::ActiveEventLoop as RootActiveEventLoop; -use crate::platform::web::{ActiveEventLoopExtWebSys, PollStrategy}; +use crate::platform::web::{ActiveEventLoopExtWebSys, PollStrategy, WaitUntilStrategy}; use super::{backend, device, window}; @@ -108,4 +108,12 @@ impl EventLoop { pub fn poll_strategy(&self) -> PollStrategy { self.elw.poll_strategy() } + + pub fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { + self.elw.set_wait_until_strategy(strategy); + } + + pub fn wait_until_strategy(&self) -> WaitUntilStrategy { + self.elw.wait_until_strategy() + } } diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 7a553a50d2..51457a8fbc 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -8,7 +8,7 @@ use crate::event::{ WindowEvent, }; use crate::event_loop::{ControlFlow, DeviceEvents}; -use crate::platform::web::PollStrategy; +use crate::platform::web::{PollStrategy, WaitUntilStrategy}; use crate::platform_impl::platform::backend::EventListenerHandle; use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner}; use crate::platform_impl::platform::window::Inner; @@ -43,6 +43,7 @@ pub struct Execution { proxy_spawner: WakerSpawner>, control_flow: Cell, poll_strategy: Cell, + wait_until_strategy: Cell, exit: Cell, runner: RefCell, suspended: Cell, @@ -149,6 +150,7 @@ impl Shared { proxy_spawner, control_flow: Cell::new(ControlFlow::default()), poll_strategy: Cell::new(PollStrategy::default()), + wait_until_strategy: Cell::new(WaitUntilStrategy::default()), exit: Cell::new(false), runner: RefCell::new(RunnerEnum::Pending), suspended: Cell::new(false), @@ -696,6 +698,7 @@ impl Shared { start, end, _timeout: backend::Schedule::new_with_duration( + self.wait_until_strategy(), self.window(), move || cloned.resume_time_reached(start, end), delay, @@ -808,6 +811,14 @@ impl Shared { self.0.poll_strategy.get() } + pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { + self.0.wait_until_strategy.set(strategy) + } + + pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy { + self.0.wait_until_strategy.get() + } + pub(crate) fn waker(&self) -> Waker> { self.0.proxy_spawner.waker() } diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 142466ab0b..e27d024051 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -18,7 +18,7 @@ use crate::event::{ }; use crate::event_loop::{ControlFlow, DeviceEvents}; use crate::keyboard::ModifiersState; -use crate::platform::web::{CustomCursorFuture, PollStrategy}; +use crate::platform::web::{CustomCursorFuture, PollStrategy, WaitUntilStrategy}; use crate::platform_impl::platform::cursor::CustomCursor; use crate::platform_impl::platform::r#async::Waker; use crate::window::{ @@ -682,6 +682,14 @@ impl ActiveEventLoop { self.runner.poll_strategy() } + pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { + self.runner.set_wait_until_strategy(strategy) + } + + pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy { + self.runner.wait_until_strategy() + } + pub(crate) fn waker(&self) -> Waker> { self.runner.waker() } diff --git a/src/platform_impl/web/web_sys/schedule.rs b/src/platform_impl/web/web_sys/schedule.rs index c8b82b2e9f..dfb500b4b8 100644 --- a/src/platform_impl/web/web_sys/schedule.rs +++ b/src/platform_impl/web/web_sys/schedule.rs @@ -1,12 +1,14 @@ -use js_sys::{Function, Object, Promise, Reflect}; +use js_sys::{Array, Function, Object, Promise, Reflect}; use std::cell::OnceCell; use std::time::Duration; use wasm_bindgen::closure::Closure; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; -use web_sys::{AbortController, AbortSignal, MessageChannel, MessagePort}; +use web_sys::{ + AbortController, AbortSignal, Blob, BlobPropertyBag, MessageChannel, MessagePort, Url, Worker, +}; -use crate::platform::web::PollStrategy; +use crate::platform::web::{PollStrategy, WaitUntilStrategy}; #[derive(Debug)] pub struct Schedule { @@ -29,6 +31,7 @@ enum Inner { port: MessagePort, _timeout_closure: Closure, }, + Worker(MessagePort), } impl Schedule { @@ -45,14 +48,24 @@ impl Schedule { } } - pub fn new_with_duration(window: &web_sys::Window, f: F, duration: Duration) -> Schedule + pub fn new_with_duration( + strategy: WaitUntilStrategy, + window: &web_sys::Window, + f: F, + duration: Duration, + ) -> Schedule where F: 'static + FnMut(), { - if has_scheduler_support(window) { - Self::new_scheduler(window, f, Some(duration)) - } else { - Self::new_timeout(window.clone(), f, Some(duration)) + match strategy { + WaitUntilStrategy::Scheduler => { + if has_scheduler_support(window) { + Self::new_scheduler(window, f, Some(duration)) + } else { + Self::new_timeout(window.clone(), f, Some(duration)) + } + }, + WaitUntilStrategy::Worker => Self::new_worker(f, duration), } } @@ -153,6 +166,44 @@ impl Schedule { }, } } + + fn new_worker(f: F, duration: Duration) -> Schedule + where + F: 'static + FnMut(), + { + thread_local! { + static URL: ScriptUrl = ScriptUrl::new(include_str!("worker.min.js")); + static WORKER: Worker = URL.with(|url| Worker::new(&url.0)).expect("`new Worker()` is not expected to fail with a local script"); + } + + let channel = MessageChannel::new().unwrap(); + let closure = Closure::new(f); + let port_1 = channel.port1(); + port_1.set_onmessage(Some(closure.as_ref().unchecked_ref())); + port_1.start(); + + // `Duration::as_millis()` always rounds down (because of truncation), we want to round + // up instead. This makes sure that the we never wake up **before** the given time. + let duration = duration + .as_secs() + .try_into() + .ok() + .and_then(|secs: u32| secs.checked_mul(1000)) + .and_then(|secs| secs.checked_add(duration_millis_ceil(duration))) + .unwrap_or(u32::MAX); + + WORKER + .with(|worker| { + let port_2 = channel.port2(); + worker.post_message_with_transfer( + &Array::of2(&port_2, &duration.into()), + &Array::of1(&port_2).into(), + ) + }) + .expect("`Worker.postMessage()` is not expected to fail"); + + Schedule { _closure: closure, inner: Inner::Worker(port_1) } + } } impl Drop for Schedule { @@ -165,6 +216,10 @@ impl Drop for Schedule { port.close(); port.set_onmessage(None); }, + Inner::Worker(port) => { + port.close(); + port.set_onmessage(None); + }, } } } @@ -226,6 +281,29 @@ fn has_idle_callback_support(window: &web_sys::Window) -> bool { }) } +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 blob = Blob::new_with_str_sequence_and_options(&sequence, &property) + .expect("`new Blob()` should never throw"); + + let url = Url::create_object_url_with_blob(&blob) + .expect("`URL.createObjectURL()` should never throw"); + + Self(url) + } +} + +impl Drop for ScriptUrl { + fn drop(&mut self) { + Url::revoke_object_url(&self.0).expect("`URL.revokeObjectURL()` should never throw"); + } +} + #[wasm_bindgen] extern "C" { type WindowSupportExt; diff --git a/src/platform_impl/web/web_sys/worker.js b/src/platform_impl/web/web_sys/worker.js new file mode 100644 index 0000000000..5a8411ef83 --- /dev/null +++ b/src/platform_impl/web/web_sys/worker.js @@ -0,0 +1,10 @@ +onmessage = event => { + const [port, timeout] = event.data + const f = () => port.postMessage(undefined) + + if ('scheduler' in this) { + scheduler.postTask(f, { delay: timeout }) + } else { + setTimeout(f, timeout) + } +} diff --git a/src/platform_impl/web/web_sys/worker.min.js b/src/platform_impl/web/web_sys/worker.min.js new file mode 100644 index 0000000000..fd394a732f --- /dev/null +++ b/src/platform_impl/web/web_sys/worker.min.js @@ -0,0 +1 @@ +onmessage=e=>{let[s,t]=e.data,a=()=>s.postMessage(void 0);"scheduler"in this?scheduler.postTask(a,{delay:t}):setTimeout(a,t)}; From d2d4d201086ad799d69128a30df06ce4734644a8 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Thu, 20 Jun 2024 17:25:47 +0300 Subject: [PATCH 012/116] Winit version 0.30.3 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 9 --------- src/changelog/v0.30.md | 21 +++++++++++++++++++++ src/platform/android.rs | 2 +- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 337a036978..0e084afa55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.2" +version = "0.30.3" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index 0b4af6e431..e3aab0e550 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.2" +winit = "0.30.3" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 6adb37b188..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,12 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Changed - -- On macOS, set the window theme on the `NSWindow` instead of application-wide. - -### Fixed - -- On X11, build on arm platforms. -- On macOS, fixed `WindowBuilder::with_theme` not having any effect on the window. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index dc1182a3eb..e190d11462 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,24 @@ +## 0.30.3 + +### Added + +- On Web, add `EventLoopExtWebSys::(set_)poll_strategy()` to allow setting + control flow strategies before starting the event loop. +- On Web, add `WaitUntilStrategy`, which allows to set different strategies for + `ControlFlow::WaitUntil`. By default the Prioritized Task Scheduling API is + used, with a fallback to `setTimeout()` with a trick to circumvent throttling + to 4ms. But an option to use a Web worker to schedule the timer is available + as well, which commonly prevents any throttling when the window is not focused. + +### Changed + +- On macOS, set the window theme on the `NSWindow` instead of application-wide. + +### Fixed + +- On X11, build on arm platforms. +- On macOS, fixed `WindowBuilder::with_theme` not having any effect on the window. + ## 0.30.2 ### Fixed diff --git a/src/platform/android.rs b/src/platform/android.rs index 2227f3a7a9..4d4c591e06 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.2", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.3", //! 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 From 6abfef1220587454489530ee38e7ca807c35848c Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sat, 22 Jun 2024 16:38:42 +0700 Subject: [PATCH 013/116] Use `default-features`, not `default_features` (#3746) The latter syntax is deprecated and will be removed in Rust 2024 edition. This also generates a warning with current versions of Rust. --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0e084afa55..621a458efe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,11 +86,11 @@ rwh_06 = { package = "raw-window-handle", version = "0.6", features = [ ], optional = true } serde = { workspace = true, optional = true } smol_str = "0.2.0" -tracing = { version = "0.1.40", default_features = false } +tracing = { version = "0.1.40", default-features = false } [dev-dependencies] image = { version = "0.25.0", default-features = false, features = ["png"] } -tracing = { version = "0.1.40", default_features = false, features = ["log"] } +tracing = { version = "0.1.40", default-features = false, features = ["log"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } winit = { path = ".", features = ["rwh_05"] } @@ -252,8 +252,8 @@ rustix = { version = "0.38.4", default-features = false, features = [ sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = [ "calloop", ], optional = true } -sctk-adwaita = { version = "0.9.0", default_features = false, optional = true } -wayland-backend = { version = "0.3.0", default_features = false, features = [ +sctk-adwaita = { version = "0.9.0", default-features = false, optional = true } +wayland-backend = { version = "0.3.0", default-features = false, features = [ "client_system", ], optional = true } wayland-client = { version = "0.31.1", optional = true } From 7c81364e1cc86a4e4287fbbd5aa99584e3ec4cf7 Mon Sep 17 00:00:00 2001 From: msiglreith Date: Sat, 22 Jun 2024 12:58:23 +0200 Subject: [PATCH 014/116] Update codeowner list --- .github/CODEOWNERS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a7339145ef..0a080af3cc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,6 @@ # Android -/src/platform/android.rs @msiglreith @MarijnS95 -/src/platform_impl/android @msiglreith @MarijnS95 +/src/platform/android.rs @MarijnS95 +/src/platform_impl/android @MarijnS95 # iOS /src/platform/ios.rs @madsmtm @@ -26,8 +26,8 @@ /src/platform_impl/web @daxpedda # Windows -/src/platform/windows.rs @msiglreith -/src/platform_impl/windows @msiglreith +# /src/platform/windows.rs +# /src/platform_impl/windows # Orbital (Redox OS) /src/platform/orbital.rs @jackpot51 From faa641e57fc89bd795dc0646414a35f537e485d5 Mon Sep 17 00:00:00 2001 From: msiglreith Date: Sat, 22 Jun 2024 16:43:51 +0200 Subject: [PATCH 015/116] Add notgull as Windows maintainer --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0a080af3cc..89b5752ec9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -26,8 +26,8 @@ /src/platform_impl/web @daxpedda # Windows -# /src/platform/windows.rs -# /src/platform_impl/windows +/src/platform/windows.rs @notgull +/src/platform_impl/windows @notgull # Orbital (Redox OS) /src/platform/orbital.rs @jackpot51 From 655fdc896f6a5df737949a602cad96699e9779cb Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sun, 23 Jun 2024 10:33:39 -0700 Subject: [PATCH 016/116] ci: Use taiki-e/checkout-action taiki-e/checkout-action has a few advantages over actions/checkout, such as: - It is written in Bash rather than Node.js - It does not have frequent breaking changes, reducing churn Signed-off-by: John Nunley --- .github/workflows/ci.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d940ec0ffb..82094815db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: name: Check formatting runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: taiki-e/checkout-action@v1 - uses: dtolnay/rust-toolchain@nightly with: components: rustfmt @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: taiki-e/checkout-action@v1 - uses: taiki-e/install-action@v2 with: tool: typos-cli @@ -88,7 +88,7 @@ jobs: CMD: ${{ matrix.platform.cmd }} steps: - - uses: actions/checkout@v4 + - uses: taiki-e/checkout-action@v1 - name: Restore cache of cargo folder # We use `restore` and later `save`, so that we can create the key after @@ -220,7 +220,7 @@ jobs: - { name: 'Windows', target: x86_64-pc-windows-gnu } steps: - - uses: actions/checkout@v4 + - uses: taiki-e/checkout-action@v1 - uses: EmbarkStudios/cargo-deny-action@v1 with: command: check @@ -232,8 +232,7 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: taiki-e/checkout-action@v1 - name: Install SWC run: sudo npm i -g @swc/cli - name: Run SWC From feca480b4c832ae2c0af09bb83f16bc09bfd3992 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 24 Jun 2024 03:57:48 +0200 Subject: [PATCH 017/116] Avoid `path` when importing modules (#3755) Rust tooling generally works better this way. This includes rust-analyzer, but more noticeably the output from `tracing` typically prints the module path, which did not correspond to the actual file system before. Concretely, tracing output from the macOS backend changes from printing: `winit::platform_impl::platform::util` To printing: `winit::platform_impl::macos::util` --- src/platform_impl/mod.rs | 40 +++++++++++++++++++++--------------- src/platform_impl/web/mod.rs | 5 ++--- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/platform_impl/mod.rs b/src/platform_impl/mod.rs index a25fb3a209..3bfce6887e 100644 --- a/src/platform_impl/mod.rs +++ b/src/platform_impl/mod.rs @@ -1,27 +1,35 @@ use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoModeHandle as RootVideoModeHandle}; use crate::window::Fullscreen as RootFullscreen; -#[cfg(windows_platform)] -#[path = "windows/mod.rs"] -mod platform; +#[cfg(android_platform)] +mod android; +#[cfg(ios_platform)] +mod ios; #[cfg(any(x11_platform, wayland_platform))] -#[path = "linux/mod.rs"] -mod platform; +mod linux; #[cfg(macos_platform)] -#[path = "macos/mod.rs"] -mod platform; +mod macos; +#[cfg(orbital_platform)] +mod orbital; +#[cfg(web_platform)] +mod web; +#[cfg(windows_platform)] +mod windows; + #[cfg(android_platform)] -#[path = "android/mod.rs"] -mod platform; +use android as platform; #[cfg(ios_platform)] -#[path = "ios/mod.rs"] -mod platform; -#[cfg(web_platform)] -#[path = "web/mod.rs"] -mod platform; +use ios as platform; +#[cfg(any(x11_platform, wayland_platform))] +use linux as platform; +#[cfg(macos_platform)] +use macos as platform; #[cfg(orbital_platform)] -#[path = "orbital/mod.rs"] -mod platform; +use orbital as platform; +#[cfg(web_platform)] +use web as platform; +#[cfg(windows_platform)] +use windows as platform; pub use self::platform::*; diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 7b896b4c39..969d8bb188 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -28,11 +28,9 @@ mod event_loop; mod keyboard; mod main_thread; mod monitor; +mod web_sys; mod window; -#[path = "web_sys/mod.rs"] -mod backend; - pub use self::device::DeviceId; pub use self::error::OsError; pub(crate) use self::event_loop::{ @@ -43,6 +41,7 @@ pub use self::monitor::{MonitorHandle, VideoModeHandle}; pub use self::window::{PlatformSpecificWindowAttributes, Window, WindowId}; pub(crate) use self::keyboard::KeyEventExtra; +use self::web_sys as backend; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; pub(crate) use cursor::{ From ec24c3efd3aaf2e42d137cced48b9bba1a512937 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 5 Jul 2024 13:09:23 +0300 Subject: [PATCH 018/116] wayland: ignore events for dead objects Nothing wrong will happen if we ignore events when compositor is at wrong, at least crashing because compositor is just _wrong_ probably is not a great option. Links: https://github.com/alacritty/alacritty/issues/8065 --- src/changelog/unreleased.md | 4 ++ .../linux/wayland/seat/keyboard/mod.rs | 59 +++++++++++-------- src/platform_impl/linux/wayland/seat/mod.rs | 17 +++++- .../linux/wayland/seat/pointer/mod.rs | 34 +++++++---- .../linux/wayland/seat/touch/mod.rs | 37 ++++++++++-- 5 files changed, 106 insertions(+), 45 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..b7bb6e6143 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Fixed + +- On Wayland, avoid crashing when compositor is misbehaving. diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index 4e731d47e7..ef4f99520b 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -18,7 +18,6 @@ use crate::keyboard::ModifiersState; use crate::platform_impl::common::xkb::Context; use crate::platform_impl::wayland::event_loop::sink::EventSink; -use crate::platform_impl::wayland::seat::WinitSeatState; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::{self, DeviceId, WindowId}; @@ -33,7 +32,17 @@ impl Dispatch for WinitState { ) { let seat_state = match state.seats.get_mut(&data.seat.id()) { Some(seat_state) => seat_state, - None => return, + None => { + warn!("Received keyboard event {event:?} without seat"); + return; + }, + }; + let keyboard_state = match seat_state.keyboard_state.as_mut() { + Some(keyboard_state) => keyboard_state, + None => { + warn!("Received keyboard event {event:?} without keyboard"); + return; + }, }; match event { @@ -43,7 +52,7 @@ impl Dispatch for WinitState { warn!("non-xkb compatible keymap") }, WlKeymapFormat::XkbV1 => { - let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context; + let context = &mut keyboard_state.xkb_context; context.set_keymap_from_fd(fd, size as usize); }, _ => unreachable!(), @@ -67,7 +76,6 @@ impl Dispatch for WinitState { }; // Drop the repeat, if there were any. - let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); keyboard_state.current_repeat = None; if let Some(token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(token); @@ -93,7 +101,6 @@ impl Dispatch for WinitState { // NOTE: we should drop the repeat regardless whethere it was for the present // window of for the window which just went gone. - let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); keyboard_state.current_repeat = None; if let Some(token) = keyboard_state.repeat_token.take() { keyboard_state.loop_handle.remove(token); @@ -128,7 +135,7 @@ impl Dispatch for WinitState { let key = key + 8; key_input( - seat_state, + keyboard_state, &mut state.events_sink, data, key, @@ -136,7 +143,6 @@ impl Dispatch for WinitState { false, ); - let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); let delay = match keyboard_state.repeat_info { RepeatInfo::Repeat { delay, .. } => delay, RepeatInfo::Disable => return, @@ -163,18 +169,25 @@ impl Dispatch for WinitState { state.dispatched_events = true; let data = wl_keyboard.data::().unwrap(); - let seat_state = state.seats.get_mut(&data.seat.id()).unwrap(); - - // NOTE: The removed on event source is batched, but key change to - // `None` is instant. - let repeat_keycode = - match seat_state.keyboard_state.as_ref().unwrap().current_repeat { - Some(repeat_keycode) => repeat_keycode, - None => return TimeoutAction::Drop, - }; + let seat_state = match state.seats.get_mut(&data.seat.id()) { + Some(seat_state) => seat_state, + None => return TimeoutAction::Drop, + }; + + let keyboard_state = match seat_state.keyboard_state.as_mut() { + Some(keyboard_state) => keyboard_state, + None => return TimeoutAction::Drop, + }; + + // NOTE: The removed on event source is batched, but key change to `None` + // is instant. + let repeat_keycode = match keyboard_state.current_repeat { + Some(repeat_keycode) => repeat_keycode, + None => return TimeoutAction::Drop, + }; key_input( - seat_state, + keyboard_state, &mut state.events_sink, data, repeat_keycode, @@ -183,7 +196,7 @@ impl Dispatch for WinitState { ); // NOTE: the gap could change dynamically while repeat is going. - match seat_state.keyboard_state.as_ref().unwrap().repeat_info { + match keyboard_state.repeat_info { RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap), RepeatInfo::Disable => TimeoutAction::Drop, } @@ -194,7 +207,7 @@ impl Dispatch for WinitState { let key = key + 8; key_input( - seat_state, + keyboard_state, &mut state.events_sink, data, key, @@ -202,7 +215,6 @@ impl Dispatch for WinitState { false, ); - let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); if keyboard_state.repeat_info != RepeatInfo::Disable && keyboard_state.xkb_context.keymap_mut().unwrap().key_repeats(key) && Some(key) == keyboard_state.current_repeat @@ -216,7 +228,7 @@ impl Dispatch for WinitState { WlKeyboardEvent::Modifiers { mods_depressed, mods_latched, mods_locked, group, .. } => { - let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context; + let xkb_context = &mut keyboard_state.xkb_context; let xkb_state = match xkb_context.state_mut() { Some(state) => state, None => return, @@ -240,7 +252,6 @@ impl Dispatch for WinitState { ); }, WlKeyboardEvent::RepeatInfo { rate, delay } => { - let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); keyboard_state.repeat_info = if rate == 0 { // Stop the repeat once we get a disable event. keyboard_state.current_repeat = None; @@ -348,7 +359,7 @@ impl KeyboardData { } fn key_input( - seat_state: &mut WinitSeatState, + keyboard_state: &mut KeyboardState, event_sink: &mut EventSink, data: &KeyboardData, keycode: u32, @@ -360,8 +371,6 @@ fn key_input( None => return, }; - let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); - let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); if let Some(mut key_context) = keyboard_state.xkb_context.key_context() { let event = key_context.process_key_event(keycode, state, repeat); diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 103cf6fce0..82e38e5414 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use ahash::AHashMap; +use tracing::warn; use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::protocol::wl_seat::WlSeat; @@ -76,7 +77,13 @@ impl SeatHandler for WinitState { seat: WlSeat, capability: SeatCapability, ) { - let seat_state = self.seats.get_mut(&seat.id()).unwrap(); + let seat_state = match self.seats.get_mut(&seat.id()) { + Some(seat_state) => seat_state, + None => { + warn!("Received wl_seat::new_capability for unknown seat"); + return; + }, + }; match capability { SeatCapability::Touch if seat_state.touch.is_none() => { @@ -139,7 +146,13 @@ impl SeatHandler for WinitState { seat: WlSeat, capability: SeatCapability, ) { - let seat_state = self.seats.get_mut(&seat.id()).unwrap(); + let seat_state = match self.seats.get_mut(&seat.id()) { + Some(seat_state) => seat_state, + None => { + warn!("Received wl_seat::remove_capability for unknown seat"); + return; + }, + }; if let Some(text_input) = seat_state.text_input.take() { text_input.destroy(); diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index f26c326e9c..fcca59343b 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -4,6 +4,8 @@ use std::ops::Deref; use std::sync::{Arc, Mutex}; use std::time::Duration; +use tracing::warn; + use sctk::reexports::client::delegate_dispatch; use sctk::reexports::client::protocol::wl_pointer::WlPointer; use sctk::reexports::client::protocol::wl_seat::WlSeat; @@ -41,7 +43,21 @@ impl PointerHandler for WinitState { events: &[PointerEvent], ) { let seat = pointer.winit_data().seat(); - let seat_state = self.seats.get(&seat.id()).unwrap(); + let seat_state = match self.seats.get(&seat.id()) { + Some(seat_state) => seat_state, + None => { + warn!("Received pointer event without seat"); + return; + }, + }; + + let themed_pointer = match seat_state.pointer.as_ref() { + Some(pointer) => pointer, + None => { + warn!("Received pointer event without pointer"); + return; + }, + }; let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); @@ -78,9 +94,7 @@ impl PointerHandler for WinitState { event.position.0, event.position.1, ) { - if let Some(pointer) = seat_state.pointer.as_ref() { - let _ = pointer.set_cursor(connection, icon); - } + let _ = themed_pointer.set_cursor(connection, icon); } }, PointerEventKind::Leave { .. } if parent_surface != surface => { @@ -113,9 +127,7 @@ impl PointerHandler for WinitState { self.events_sink .push_window_event(WindowEvent::CursorEntered { device_id }, window_id); - if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) { - window.pointer_entered(pointer); - } + window.pointer_entered(Arc::downgrade(themed_pointer)); // Set the currently focused surface. pointer.winit_data().inner.lock().unwrap().surface = Some(window_id); @@ -126,9 +138,7 @@ impl PointerHandler for WinitState { ); }, PointerEventKind::Leave { .. } => { - if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) { - window.pointer_left(pointer); - } + window.pointer_left(Arc::downgrade(themed_pointer)); // Remove the active surface. pointer.winit_data().inner.lock().unwrap().surface = None; @@ -185,13 +195,13 @@ impl PointerHandler for WinitState { // Mice events have both pixel and discrete delta's at the same time. So prefer // the descrite values if they are present. let delta = if has_discrete_scroll { - // XXX Wayland sign convention is the inverse of winit. + // NOTE: Wayland sign convention is the inverse of winit. MouseScrollDelta::LineDelta( (-horizontal.discrete) as f32, (-vertical.discrete) as f32, ) } else { - // XXX Wayland sign convention is the inverse of winit. + // NOTE: Wayland sign convention is the inverse of winit. MouseScrollDelta::PixelDelta( LogicalPosition::new(-horizontal.absolute, -vertical.absolute) .to_physical(scale_factor), diff --git a/src/platform_impl/linux/wayland/seat/touch/mod.rs b/src/platform_impl/linux/wayland/seat/touch/mod.rs index a037ae9f37..124504feea 100644 --- a/src/platform_impl/linux/wayland/seat/touch/mod.rs +++ b/src/platform_impl/linux/wayland/seat/touch/mod.rs @@ -1,5 +1,7 @@ //! Touch handling. +use tracing::warn; + use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_surface::WlSurface; use sctk::reexports::client::protocol::wl_touch::WlTouch; @@ -31,11 +33,16 @@ impl TouchHandler for WinitState { None => return, }; - let location = LogicalPosition::::from(position); - - let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); + let seat_state = match self.seats.get_mut(&touch.seat().id()) { + Some(seat_state) => seat_state, + None => { + warn!("Received wl_touch::down without seat"); + return; + }, + }; // Update the state of the point. + let location = LogicalPosition::::from(position); seat_state.touch_map.insert(id, TouchPoint { surface, location }); self.events_sink.push_window_event( @@ -61,7 +68,13 @@ impl TouchHandler for WinitState { _: u32, id: i32, ) { - let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); + let seat_state = match self.seats.get_mut(&touch.seat().id()) { + Some(seat_state) => seat_state, + None => { + warn!("Received wl_touch::up without seat"); + return; + }, + }; // Remove the touch point. let touch_point = match seat_state.touch_map.remove(&id) { @@ -98,7 +111,13 @@ impl TouchHandler for WinitState { id: i32, position: (f64, f64), ) { - let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); + let seat_state = match self.seats.get_mut(&touch.seat().id()) { + Some(seat_state) => seat_state, + None => { + warn!("Received wl_touch::motion without seat"); + return; + }, + }; // Remove the touch point. let touch_point = match seat_state.touch_map.get_mut(&id) { @@ -129,7 +148,13 @@ impl TouchHandler for WinitState { } fn cancel(&mut self, _: &Connection, _: &QueueHandle, touch: &WlTouch) { - let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap(); + let seat_state = match self.seats.get_mut(&touch.seat().id()) { + Some(seat_state) => seat_state, + None => { + warn!("Received wl_touch::cancel without seat"); + return; + }, + }; for (id, touch_point) in seat_state.touch_map.drain() { let window_id = wayland::make_wid(&touch_point.surface); From 71dea4637d107ec8a8789abd37df7208756b75a2 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Fri, 5 Jul 2024 15:54:46 +0200 Subject: [PATCH 019/116] Fix CI (#3775) --- src/platform/run_on_demand.rs | 4 +++- src/platform_impl/windows/keyboard.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/run_on_demand.rs b/src/platform/run_on_demand.rs index ecb22395a7..821e87914b 100644 --- a/src/platform/run_on_demand.rs +++ b/src/platform/run_on_demand.rs @@ -42,7 +42,9 @@ pub trait EventLoopExtRunOnDemand { /// # Caveats /// - This extension isn't available on all platforms, since it's not always possible to return /// to the caller (specifically this is impossible on iOS and Web - though with the Web - /// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead). + /// backend it is possible to use `EventLoopExtWebSys::spawn()` + #[cfg_attr(not(web_platform), doc = "[^1]")] + /// more than once instead). /// - No [`Window`] state can be carried between separate runs of the event loop. /// /// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you diff --git a/src/platform_impl/windows/keyboard.rs b/src/platform_impl/windows/keyboard.rs index 43dea40db6..46716f4c67 100644 --- a/src/platform_impl/windows/keyboard.rs +++ b/src/platform_impl/windows/keyboard.rs @@ -556,7 +556,7 @@ impl PartialKeyEventInfo { // We convert dead keys into their character. // The reason for this is that `key_without_modifiers` is designed for key-bindings, // but the US International layout treats `'` (apostrophe) as a dead key and the - // reguar US layout treats it a character. In order for a single binding + // regular US layout treats it a character. In order for a single binding // configuration to work with both layouts, we forward each dead key as a character. Key::Dead(k) => { if let Some(ch) = k { From 6509f8a18b043fcf346014704a918d932d545420 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 14 Jul 2024 13:14:32 +0200 Subject: [PATCH 020/116] Make `DeviceId/WindowId::dummy()` safe (#3784) --- src/changelog/unreleased.md | 4 ++++ src/event.rs | 15 ++++++--------- src/platform_impl/ios/mod.rs | 2 +- src/platform_impl/ios/window.rs | 2 +- src/platform_impl/linux/mod.rs | 8 ++++---- src/platform_impl/linux/wayland/mod.rs | 2 +- src/platform_impl/linux/x11/mod.rs | 2 +- src/platform_impl/macos/mod.rs | 2 +- src/platform_impl/macos/window.rs | 2 +- src/platform_impl/web/device.rs | 2 +- src/platform_impl/web/event_loop/runner.rs | 4 ++-- src/platform_impl/web/event_loop/window_target.rs | 4 ++-- src/platform_impl/web/window.rs | 2 +- src/platform_impl/windows/mod.rs | 4 ++-- src/window.rs | 9 +++------ 15 files changed, 31 insertions(+), 33 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index b7bb6e6143..b3df644625 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -40,6 +40,10 @@ changelog entry. ## Unreleased +### Changed + +- `DeviceId::dummy()` and `WindowId::dummy()` are no longer marked `unsafe`. + ### Fixed - On Wayland, avoid crashing when compositor is misbehaving. diff --git a/src/event.rs b/src/event.rs index 30a71d6514..d05d35c853 100644 --- a/src/event.rs +++ b/src/event.rs @@ -449,16 +449,13 @@ pub struct DeviceId(pub(crate) platform_impl::DeviceId); impl DeviceId { /// Returns a dummy id, useful for unit testing. /// - /// # Safety + /// # Notes /// /// The only guarantee made about the return value of this function is that /// it will always be equal to itself and to future values returned by this function. /// No other guarantees are made. This may be equal to a real `DeviceId`. - /// - /// **Passing this into a winit function will result in undefined behavior.** - pub const unsafe fn dummy() -> Self { - #[allow(unused_unsafe)] - DeviceId(unsafe { platform_impl::DeviceId::dummy() }) + pub const fn dummy() -> Self { + DeviceId(platform_impl::DeviceId::dummy()) } } @@ -1021,7 +1018,7 @@ mod tests { ($closure:expr) => {{ #[allow(unused_mut)] let mut x = $closure; - let did = unsafe { event::DeviceId::dummy() }; + let did = event::DeviceId::dummy(); #[allow(deprecated)] { @@ -1031,7 +1028,7 @@ mod tests { use crate::window::WindowId; // Mainline events. - let wid = unsafe { WindowId::dummy() }; + let wid = WindowId::dummy(); x(UserEvent(())); x(NewEvents(event::StartCause::Init)); x(AboutToWait); @@ -1160,7 +1157,7 @@ mod tests { }); let _ = event::StartCause::Init.clone(); - let did = unsafe { crate::event::DeviceId::dummy() }.clone(); + let did = crate::event::DeviceId::dummy().clone(); HashSet::new().insert(did); let mut set = [did, did, did]; set.sort_unstable(); diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index f69ac21b79..9c2362e11d 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -32,7 +32,7 @@ pub(crate) use crate::platform_impl::Fullscreen; pub struct DeviceId; impl DeviceId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { DeviceId } } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 8ea7aeae91..675fdfeef8 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -701,7 +701,7 @@ pub struct WindowId { } impl WindowId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { WindowId { window: std::ptr::null_mut() } } } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 776689fe20..6cb99fee31 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -157,7 +157,7 @@ impl From for WindowId { } impl WindowId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { Self(0) } } @@ -171,11 +171,11 @@ pub enum DeviceId { } impl DeviceId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { #[cfg(wayland_platform)] - return DeviceId::Wayland(unsafe { wayland::DeviceId::dummy() }); + return DeviceId::Wayland(wayland::DeviceId::dummy()); #[cfg(all(not(wayland_platform), x11_platform))] - return DeviceId::X(unsafe { x11::DeviceId::dummy() }); + return DeviceId::X(x11::DeviceId::dummy()); } } diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs index 674671ff8c..63052b78af 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -66,7 +66,7 @@ impl From for OsError { pub struct DeviceId; impl DeviceId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { DeviceId } } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 88071b72ba..3aafd7b316 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -771,7 +771,7 @@ pub struct DeviceId(xinput::DeviceId); impl DeviceId { #[allow(unused)] - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { DeviceId(0) } } diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 2c3d3e3032..1b427a87d3 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -37,7 +37,7 @@ pub(crate) use crate::platform_impl::Fullscreen; pub struct DeviceId; impl DeviceId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { DeviceId } } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index eb1f75deea..04da8b29ee 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -74,7 +74,7 @@ impl Window { pub struct WindowId(pub usize); impl WindowId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { Self(0) } } diff --git a/src/platform_impl/web/device.rs b/src/platform_impl/web/device.rs index 383bf3a1b8..91d08586c8 100644 --- a/src/platform_impl/web/device.rs +++ b/src/platform_impl/web/device.rs @@ -2,7 +2,7 @@ pub struct DeviceId(pub i32); impl DeviceId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { Self(0) } } diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 51457a8fbc..3e7507106d 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -370,7 +370,7 @@ impl Shared { } runner.send_event(Event::DeviceEvent { - device_id: RootDeviceId(unsafe { DeviceId::dummy() }), + device_id: RootDeviceId(DeviceId::dummy()), event: DeviceEvent::Key(RawKeyEvent { physical_key: backend::event::key_code(&event), state: ElementState::Pressed, @@ -388,7 +388,7 @@ impl Shared { } runner.send_event(Event::DeviceEvent { - device_id: RootDeviceId(unsafe { DeviceId::dummy() }), + device_id: RootDeviceId(DeviceId::dummy()), event: DeviceEvent::Key(RawKeyEvent { physical_key: backend::event::key_code(&event), state: ElementState::Released, diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index e27d024051..eb0051d36d 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -142,7 +142,7 @@ impl ActiveEventLoop { } }); - let device_id = RootDeviceId(unsafe { DeviceId::dummy() }); + let device_id = RootDeviceId(DeviceId::dummy()); runner.send_events( iter::once(Event::WindowEvent { @@ -178,7 +178,7 @@ impl ActiveEventLoop { } }); - let device_id = RootDeviceId(unsafe { DeviceId::dummy() }); + let device_id = RootDeviceId(DeviceId::dummy()); runner.send_events( iter::once(Event::WindowEvent { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index acfefe3063..29ca730299 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -431,7 +431,7 @@ impl Drop for Inner { pub struct WindowId(pub(crate) u32); impl WindowId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { Self(0) } } diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 15051f0ca3..9bb02fd4ed 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -67,7 +67,7 @@ unsafe impl Sync for PlatformSpecificWindowAttributes {} pub struct DeviceId(u32); impl DeviceId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { DeviceId(0) } } @@ -103,7 +103,7 @@ unsafe impl Send for WindowId {} unsafe impl Sync for WindowId {} impl WindowId { - pub const unsafe fn dummy() -> Self { + pub const fn dummy() -> Self { WindowId(0) } } diff --git a/src/window.rs b/src/window.rs index 775b3f5352..7c4812c560 100644 --- a/src/window.rs +++ b/src/window.rs @@ -71,16 +71,13 @@ pub struct WindowId(pub(crate) platform_impl::WindowId); impl WindowId { /// Returns a dummy id, useful for unit testing. /// - /// # Safety + /// # Notes /// /// The only guarantee made about the return value of this function is that /// it will always be equal to itself and to future values returned by this function. /// No other guarantees are made. This may be equal to a real [`WindowId`]. - /// - /// **Passing this into a winit function will result in undefined behavior.** - pub const unsafe fn dummy() -> Self { - #[allow(unused_unsafe)] - WindowId(unsafe { platform_impl::WindowId::dummy() }) + pub const fn dummy() -> Self { + WindowId(platform_impl::WindowId::dummy()) } } From ba188376d16adf0fe38a26d7f9fb86f3587c6bf9 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 16 Jul 2024 12:58:03 +0300 Subject: [PATCH 021/116] wayland: bump dependencies Update SCTK as a follow-up to 1170554dbdb (ignore events to dead objects) fixing it for wl_output. --- Cargo.toml | 14 +++++++------- deny.toml | 1 - src/platform_impl/linux/wayland/state.rs | 20 +++++++++++++++++++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 621a458efe..8ed5f8bc59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -239,7 +239,7 @@ features = [ [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] ahash = { version = "0.8.7", features = ["no-rng"], optional = true } bytemuck = { version = "1.13.1", default-features = false, optional = true } -calloop = "0.12.3" +calloop = "0.13.0" libc = "0.2.64" memmap2 = { version = "0.9.0", optional = true } percent-encoding = { version = "2.0", optional = true } @@ -249,18 +249,18 @@ rustix = { version = "0.38.4", default-features = false, features = [ "thread", "process", ] } -sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = [ +sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [ "calloop", ], optional = true } -sctk-adwaita = { version = "0.9.0", default-features = false, optional = true } -wayland-backend = { version = "0.3.0", default-features = false, features = [ +sctk-adwaita = { version = "0.10.1", default-features = false, optional = true } +wayland-backend = { version = "0.3.5", default-features = false, features = [ "client_system", ], optional = true } -wayland-client = { version = "0.31.1", optional = true } -wayland-protocols = { version = "0.31.0", features = [ +wayland-client = { version = "0.31.4", optional = true } +wayland-protocols = { version = "0.32.2", features = [ "staging", ], optional = true } -wayland-protocols-plasma = { version = "0.2.0", features = [ +wayland-protocols-plasma = { version = "0.3.2", features = [ "client", ], optional = true } x11-dl = { version = "2.19.1", optional = true } diff --git a/deny.toml b/deny.toml index 317542b864..27fe32cd01 100644 --- a/deny.toml +++ b/deny.toml @@ -33,7 +33,6 @@ deny = [] skip = [ { name = "raw-window-handle" }, # we intentionally have multiple versions of this { name = "bitflags" }, # the ecosystem is in the process of migrating. - { name = "libloading" }, # x11rb uses a different version until the next update ] skip-tree = [] diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index 8c021bb9fd..13ef99c26a 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -339,12 +339,30 @@ impl CompositorHandler for WinitState { &mut self, _: &Connection, _: &QueueHandle, - _: &wayland_client::protocol::wl_surface::WlSurface, + _: &WlSurface, _: wayland_client::protocol::wl_output::Transform, ) { // TODO(kchibisov) we need to expose it somehow in winit. } + fn surface_enter( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlSurface, + _: &WlOutput, + ) { + } + + fn surface_leave( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlSurface, + _: &WlOutput, + ) { + } + fn scale_factor_changed( &mut self, _: &Connection, From bf68ac0b1430699b174cfb1c0841d851ff8e3559 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Tue, 16 Jul 2024 12:29:27 +0200 Subject: [PATCH 022/116] Web: fix `WindowEvent::Resized` not using rAF (#3790) --- src/changelog/unreleased.md | 3 +++ src/platform_impl/web/event_loop/window_target.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index b3df644625..11f65fb388 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -47,3 +47,6 @@ changelog entry. ### Fixed - On Wayland, avoid crashing when compositor is misbehaving. +- On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending + `WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested` + to not be de-duplicated. diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index eb0051d36d..85148281e7 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -605,7 +605,7 @@ impl ActiveEventLoop { window_id: RootWindowId(id), event: WindowEvent::Resized(new_size), }); - runner.request_redraw(RootWindowId(id)); + canvas.request_animation_frame(); } } }, From 949cb0f203b6fc4f9cd59ddd11bb4019dbeedc34 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sat, 29 Jun 2024 00:52:05 +0200 Subject: [PATCH 023/116] Web: fix `MouseMotion` coordinate space (#3770) --- Cargo.toml | 1 + src/changelog/unreleased.md | 1 + src/platform_impl/web/web_sys/event.rs | 89 +++++++++++--------------- src/platform_impl/web/web_sys/mod.rs | 75 +++++++++++++++++++++- 4 files changed, 113 insertions(+), 53 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ed5f8bc59..e36fc26014 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -309,6 +309,7 @@ features = [ 'MediaQueryList', 'MessageChannel', 'MessagePort', + 'Navigator', 'Node', 'PageTransitionEvent', 'PointerEvent', diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 11f65fb388..d867b31cc1 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -50,3 +50,4 @@ changelog entry. - On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending `WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested` to not be de-duplicated. +- Account for different browser engine implementations of pointer movement coordinate space. diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 51df63714b..ddd125765c 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -1,13 +1,15 @@ -use crate::dpi::LogicalPosition; use crate::event::{MouseButton, MouseScrollDelta}; use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey}; +use dpi::{LogicalPosition, PhysicalPosition, Position}; use smol_str::SmolStr; use std::cell::OnceCell; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent}; +use super::Engine; + bitflags::bitflags! { // https://www.w3.org/TR/pointerevents3/#the-buttons-property #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -95,42 +97,48 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { LogicalPosition { x: event.offset_x(), y: event.offset_y() } } -// TODO: Remove this when Firefox supports correct movement values in coalesced events. +// TODO: Remove this when Firefox supports correct movement values in coalesced events and browsers +// have agreed on what coordinate space `movementX/Y` is using. // See . -pub struct MouseDelta(Option); - -pub struct MouseDeltaInner { - old_position: LogicalPosition, - old_delta: LogicalPosition, +// See . +pub enum MouseDelta { + Chromium, + Gecko { old_position: LogicalPosition, old_delta: LogicalPosition }, + Other, } impl MouseDelta { pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self { - // Firefox has wrong movement values in coalesced events, we will detect that by checking - // for `pointerrawupdate` support. Presumably an implementation of `pointerrawupdate` - // should require correct movement values, otherwise uncoalesced events might be broken as - // well. - Self((!has_pointer_raw_support(window) && has_coalesced_events_support(event)).then(|| { - MouseDeltaInner { + match super::engine(window) { + Some(Engine::Chromium) => Self::Chromium, + // Firefox has wrong movement values in coalesced events. + Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko { old_position: mouse_position(event), - old_delta: LogicalPosition { - x: event.movement_x() as f64, - y: event.movement_y() as f64, - }, - } - })) + old_delta: LogicalPosition::new( + event.movement_x() as f64, + event.movement_y() as f64, + ), + }, + _ => Self::Other, + } } - pub fn delta(&mut self, event: &MouseEvent) -> LogicalPosition { - if let Some(inner) = &mut self.0 { - let new_position = mouse_position(event); - let x = new_position.x - inner.old_position.x + inner.old_delta.x; - let y = new_position.y - inner.old_position.y + inner.old_delta.y; - inner.old_position = new_position; - inner.old_delta = LogicalPosition::new(0., 0.); - LogicalPosition::new(x, y) - } else { - LogicalPosition { x: event.movement_x() as f64, y: event.movement_y() as f64 } + pub fn delta(&mut self, event: &MouseEvent) -> Position { + match self { + MouseDelta::Chromium => { + PhysicalPosition::new(event.movement_x(), event.movement_y()).into() + }, + MouseDelta::Gecko { old_position, old_delta } => { + let new_position = mouse_position(event); + let x = new_position.x - old_position.x + old_delta.x; + let y = new_position.y - old_position.y + old_delta.y; + *old_position = new_position; + *old_delta = LogicalPosition::new(0., 0.); + LogicalPosition::new(x, y).into() + }, + MouseDelta::Other => { + LogicalPosition::new(event.movement_x(), event.movement_y()).into() + }, } } } @@ -238,29 +246,6 @@ pub fn pointer_move_event(event: PointerEvent) -> impl Iterator. -pub fn has_pointer_raw_support(window: &web_sys::Window) -> bool { - thread_local! { - static POINTER_RAW_SUPPORT: OnceCell = const { OnceCell::new() }; - } - - POINTER_RAW_SUPPORT.with(|support| { - *support.get_or_init(|| { - #[wasm_bindgen] - extern "C" { - type PointerRawSupport; - - #[wasm_bindgen(method, getter, js_name = onpointerrawupdate)] - fn has_on_pointerrawupdate(this: &PointerRawSupport) -> JsValue; - } - - let support: &PointerRawSupport = window.unchecked_ref(); - !support.has_on_pointerrawupdate().is_undefined() - }) - }) -} - // TODO: Remove when Safari supports `getCoalescedEvents`. // See . pub fn has_coalesced_events_support(event: &PointerEvent) -> bool { diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index b0a8fbae42..08962b49bc 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -9,6 +9,8 @@ mod pointer; mod resize_scaling; mod schedule; +use std::sync::OnceLock; + pub use self::canvas::{Canvas, Style}; pub use self::event::ButtonsState; pub use self::event_handle::EventListenerHandle; @@ -16,8 +18,13 @@ pub use self::resize_scaling::ResizeScaleHandle; pub use self::schedule::Schedule; use crate::dpi::{LogicalPosition, LogicalSize}; +use js_sys::Array; use wasm_bindgen::closure::Closure; -use web_sys::{Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState}; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsCast; +use web_sys::{ + Document, HtmlCanvasElement, Navigator, PageTransitionEvent, VisibilityState, Window, +}; pub fn throw(msg: &str) { wasm_bindgen::throw_str(msg); @@ -158,3 +165,69 @@ pub fn is_visible(document: &Document) -> bool { } pub type RawCanvasType = HtmlCanvasElement; + +#[derive(Clone, Copy)] +pub enum Engine { + Chromium, + Gecko, + WebKit, +} + +pub fn engine(window: &Window) -> Option { + static ENGINE: OnceLock> = OnceLock::new(); + + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(extends = Navigator)] + type NavigatorExt; + + #[wasm_bindgen(method, getter, js_name = userAgentData)] + fn user_agent_data(this: &NavigatorExt) -> Option; + + type NavigatorUaData; + + #[wasm_bindgen(method, getter)] + fn brands(this: &NavigatorUaData) -> Array; + + type NavigatorUaBrandVersion; + + #[wasm_bindgen(method, getter)] + fn brand(this: &NavigatorUaBrandVersion) -> String; + } + + *ENGINE.get_or_init(|| { + let navigator: NavigatorExt = window.navigator().unchecked_into(); + + if let Some(data) = navigator.user_agent_data() { + for brand in data + .brands() + .iter() + .map(NavigatorUaBrandVersion::unchecked_from_js) + .map(|brand| brand.brand()) + { + match brand.as_str() { + "Chromium" => return Some(Engine::Chromium), + // TODO: verify when Firefox actually implements it. + "Gecko" => return Some(Engine::Gecko), + // TODO: verify when Safari actually implements it. + "WebKit" => return Some(Engine::WebKit), + _ => (), + } + } + + None + } else { + let data = navigator.user_agent().ok()?; + + if data.contains("Chrome/") { + Some(Engine::Chromium) + } else if data.contains("Gecko/") { + Some(Engine::Gecko) + } else if data.contains("AppleWebKit/") { + Some(Engine::WebKit) + } else { + None + } + } + }) +} From 04d8a284a060113003ffe63d42ec42979b221b47 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 16 Jul 2024 13:39:57 +0300 Subject: [PATCH 024/116] Winit version 0.30.4 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 12 ------------ src/changelog/v0.30.md | 14 ++++++++++++++ src/platform/android.rs | 2 +- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e36fc26014..4bdbc736fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.3" +version = "0.30.4" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index e3aab0e550..7722972403 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.3" +winit = "0.30.4" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index d867b31cc1..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,15 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Changed - -- `DeviceId::dummy()` and `WindowId::dummy()` are no longer marked `unsafe`. - -### Fixed - -- On Wayland, avoid crashing when compositor is misbehaving. -- On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending - `WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested` - to not be de-duplicated. -- Account for different browser engine implementations of pointer movement coordinate space. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index e190d11462..ee38a6dfc5 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,17 @@ +## 0.30.4 + +### Changed + +- `DeviceId::dummy()` and `WindowId::dummy()` are no longer marked `unsafe`. + +### Fixed + +- On Wayland, avoid crashing when compositor is misbehaving. +- On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending + `WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested` + to not be de-duplicated. +- Account for different browser engine implementations of pointer movement coordinate space. + ## 0.30.3 ### Added diff --git a/src/platform/android.rs b/src/platform/android.rs index 4d4c591e06..748126a010 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.3", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.4", //! features = [ "android-native-activity" ] }` //! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc //! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize From 1dec9b4b33d10515b58c84bfbcb04c1fea84d33b Mon Sep 17 00:00:00 2001 From: daxpedda Date: Wed, 17 Jul 2024 02:33:06 +0200 Subject: [PATCH 025/116] Web: implement `Error` for `CustomCursorError` (#3793) --- src/changelog/unreleased.md | 4 ++++ src/platform/web.rs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..3ed337ce42 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Added + +- On Web, implement `Error` for `platform::web::CustomCursorError`. diff --git a/src/platform/web.rs b/src/platform/web.rs index 23831ceaa3..a48c6b09b4 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -461,3 +461,5 @@ impl Display for CustomCursorError { } } } + +impl Error for CustomCursorError {} From 4b1aa51094edf3ee02b7f7dd111c739fdc0d4016 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 22 Jul 2024 12:11:27 +0200 Subject: [PATCH 026/116] macOS: fix building with `feature = "rwh_04"` --- src/changelog/unreleased.md | 4 ++++ src/platform_impl/macos/window_delegate.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 3ed337ce42..051f12aff7 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -43,3 +43,7 @@ changelog entry. ### Added - On Web, implement `Error` for `platform::web::CustomCursorError`. + +### Fixed + +- On MacOS, fix building with `feature = "rwh_04"`. diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 674088cf0a..6f3d4abb50 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1613,7 +1613,7 @@ impl WindowDelegate { pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::AppKitHandle::empty(); window_handle.ns_window = self.window() as *const WinitWindow as *mut _; - window_handle.ns_view = Retained::as_ptr(&self.contentView().unwrap()) as *mut _; + window_handle.ns_view = Retained::as_ptr(&self.view()) as *mut _; rwh_04::RawWindowHandle::AppKit(window_handle) } From 9ca8c09208fde6552a244e7ea0e896c6e6918a09 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Tue, 23 Jul 2024 15:32:20 +0200 Subject: [PATCH 027/116] Web: don't block pen input (#3813) --- src/changelog/unreleased.md | 1 + src/platform_impl/web/event_loop/runner.rs | 19 ------ .../web/event_loop/window_target.rs | 44 ------------- src/platform_impl/web/web_sys/canvas.rs | 37 ++--------- src/platform_impl/web/web_sys/pointer.rs | 66 +++++++------------ 5 files changed, 32 insertions(+), 135 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 051f12aff7..49f2dc9c1c 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -47,3 +47,4 @@ changelog entry. ### Fixed - On MacOS, fix building with `feature = "rwh_04"`. +- On Web, pen events are now routed through to `WindowEvent::Cursor*`. diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 3e7507106d..ba8abbd477 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -245,21 +245,10 @@ impl Shared { return; } - let pointer_type = event.pointer_type(); - - if pointer_type != "mouse" { - return; - } - // chorded button event let device_id = RootDeviceId(DeviceId(event.pointer_id())); if let Some(button) = backend::event::mouse_button(&event) { - debug_assert_eq!( - pointer_type, "mouse", - "expect pointer type of a chorded button event to be a mouse" - ); - let state = if backend::event::mouse_buttons(&event).contains(button.into()) { ElementState::Pressed } else { @@ -323,10 +312,6 @@ impl Shared { return; } - if event.pointer_type() != "mouse" { - return; - } - let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(event.pointer_id())), @@ -346,10 +331,6 @@ impl Shared { return; } - if event.pointer_type() != "mouse" { - return; - } - let button = backend::event::mouse_button(&event).expect("no mouse button pressed"); runner.send_event(Event::DeviceEvent { device_id: RootDeviceId(DeviceId(event.pointer_id())), diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 85148281e7..05f87dcc35 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -258,21 +258,6 @@ impl ActiveEventLoop { }); canvas.on_cursor_move( - { - let runner = self.runner.clone(); - let has_focus = has_focus.clone(); - let modifiers = self.modifiers.clone(); - - move |active_modifiers| { - if has_focus.get() && modifiers.get() != active_modifiers { - modifiers.set(active_modifiers); - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ModifiersChanged(active_modifiers.into()), - }) - } - } - }, { let runner = self.runner.clone(); let has_focus = has_focus.clone(); @@ -372,20 +357,6 @@ impl ActiveEventLoop { ); canvas.on_mouse_press( - { - let runner = self.runner.clone(); - let modifiers = self.modifiers.clone(); - - move |active_modifiers| { - if modifiers.get() != active_modifiers { - modifiers.set(active_modifiers); - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ModifiersChanged(active_modifiers.into()), - }) - } - } - }, { let runner = self.runner.clone(); let modifiers = self.modifiers.clone(); @@ -450,21 +421,6 @@ impl ActiveEventLoop { ); canvas.on_mouse_release( - { - let runner = self.runner.clone(); - let has_focus = has_focus.clone(); - let modifiers = self.modifiers.clone(); - - move |active_modifiers| { - if has_focus.get() && modifiers.get() != active_modifiers { - modifiers.set(active_modifiers); - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ModifiersChanged(active_modifiers.into()), - }); - } - } - }, { let runner = self.runner.clone(); let has_focus = has_focus.clone(); diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 644ef76c8b..84dde9f7de 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -329,51 +329,29 @@ impl Canvas { self.pointer_handler.on_cursor_enter(&self.common, handler) } - pub fn on_mouse_release( - &mut self, - modifier_handler: MOD, - mouse_handler: M, - touch_handler: T, - ) where - MOD: 'static + FnMut(ModifiersState), + pub fn on_mouse_release(&mut self, mouse_handler: M, touch_handler: T) + where M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { - self.pointer_handler.on_mouse_release( - &self.common, - modifier_handler, - mouse_handler, - touch_handler, - ) + self.pointer_handler.on_mouse_release(&self.common, mouse_handler, touch_handler) } - pub fn on_mouse_press( - &mut self, - modifier_handler: MOD, - mouse_handler: M, - touch_handler: T, - ) where - MOD: 'static + FnMut(ModifiersState), + pub fn on_mouse_press(&mut self, mouse_handler: M, touch_handler: T) + where M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { self.pointer_handler.on_mouse_press( &self.common, - modifier_handler, mouse_handler, touch_handler, Rc::clone(&self.prevent_default), ) } - pub fn on_cursor_move( - &mut self, - modifier_handler: MOD, - mouse_handler: M, - touch_handler: T, - button_handler: B, - ) where - MOD: 'static + FnMut(ModifiersState), + pub fn on_cursor_move(&mut self, mouse_handler: M, touch_handler: T, button_handler: B) + where M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), @@ -381,7 +359,6 @@ impl Canvas { { self.pointer_handler.on_cursor_move( &self.common, - modifier_handler, mouse_handler, touch_handler, button_handler, diff --git a/src/platform_impl/web/web_sys/pointer.rs b/src/platform_impl/web/web_sys/pointer.rs index 90e8b27b7f..3ee168bb86 100644 --- a/src/platform_impl/web/web_sys/pointer.rs +++ b/src/platform_impl/web/web_sys/pointer.rs @@ -44,7 +44,7 @@ impl PointerHandler { // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. - let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id()); + let pointer_id = (event.pointer_type() != "touch").then(|| event.pointer_id()); handler(modifiers, pointer_id); })); @@ -61,20 +61,18 @@ impl PointerHandler { // touch events are handled separately // handling them here would produce duplicate mouse events, inconsistent with // other platforms. - let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id()); + let pointer_id = (event.pointer_type() != "touch").then(|| event.pointer_id()); handler(modifiers, pointer_id); })); } - pub fn on_mouse_release( + pub fn on_mouse_release( &mut self, canvas_common: &Common, - mut modifier_handler: MOD, mut mouse_handler: M, mut touch_handler: T, ) where - MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { @@ -90,26 +88,23 @@ impl PointerHandler { event::mouse_position(&event).to_physical(super::scale_factor(&window)), Force::Normalized(event.pressure() as f64), ), - "mouse" => mouse_handler( + _ => mouse_handler( modifiers, event.pointer_id(), event::mouse_position(&event).to_physical(super::scale_factor(&window)), event::mouse_button(&event).expect("no mouse button released"), ), - _ => modifier_handler(modifiers), } })); } - pub fn on_mouse_press( + pub fn on_mouse_press( &mut self, canvas_common: &Common, - mut modifier_handler: MOD, mut mouse_handler: M, mut touch_handler: T, prevent_default: Rc>, ) where - MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, PhysicalPosition, MouseButton), T: 'static + FnMut(ModifiersState, i32, PhysicalPosition, Force), { @@ -125,8 +120,9 @@ impl PointerHandler { } let modifiers = event::mouse_modifiers(&event); + let pointer_type = &event.pointer_type(); - match event.pointer_type().as_str() { + match pointer_type.as_str() { "touch" => { touch_handler( modifiers, @@ -135,7 +131,7 @@ impl PointerHandler { Force::Normalized(event.pressure() as f64), ); }, - "mouse" => { + _ => { mouse_handler( modifiers, event.pointer_id(), @@ -143,27 +139,27 @@ impl PointerHandler { event::mouse_button(&event).expect("no mouse button pressed"), ); - // Error is swallowed here since the error would occur every time the mouse - // is clicked when the cursor is grabbed, and there - // is probably not a situation where this could - // fail, that we care if it fails. - let _e = canvas.set_pointer_capture(event.pointer_id()); + if pointer_type == "mouse" { + // Error is swallowed here since the error would occur every time the + // mouse is clicked when the cursor is + // grabbed, and there is probably not a + // situation where this could fail, that we + // care if it fails. + let _e = canvas.set_pointer_capture(event.pointer_id()); + } }, - _ => modifier_handler(modifiers), } })); } - pub fn on_cursor_move( + pub fn on_cursor_move( &mut self, canvas_common: &Common, - mut modifier_handler: MOD, mut mouse_handler: M, mut touch_handler: T, mut button_handler: B, prevent_default: Rc>, ) where - MOD: 'static + FnMut(ModifiersState), M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator>), T: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator, Force)>), @@ -175,23 +171,10 @@ impl PointerHandler { Some(canvas_common.add_event("pointermove", move |event: PointerEvent| { let modifiers = event::mouse_modifiers(&event); - let pointer_type = event.pointer_type(); - - if let "touch" | "mouse" = pointer_type.as_str() { - } else { - modifier_handler(modifiers); - return; - } - let id = event.pointer_id(); // chorded button event if let Some(button) = event::mouse_button(&event) { - debug_assert_eq!( - pointer_type, "mouse", - "expect pointer type of a chorded button event to be a mouse" - ); - if prevent_default.get() { // prevent text selection event.prevent_default(); @@ -212,13 +195,7 @@ impl PointerHandler { // pointer move event let scale = super::scale_factor(&window); - match pointer_type.as_str() { - "mouse" => mouse_handler( - modifiers, - id, - &mut event::pointer_move_event(event) - .map(|event| event::mouse_position(&event).to_physical(scale)), - ), + match event.pointer_type().as_str() { "touch" => touch_handler( modifiers, id, @@ -229,7 +206,12 @@ impl PointerHandler { ) }), ), - _ => unreachable!("didn't return early before"), + _ => mouse_handler( + modifiers, + id, + &mut event::pointer_move_event(event) + .map(|event| event::mouse_position(&event).to_physical(scale)), + ), }; })); } From 5735786f42ba45da789d35a74e28f16c2c057b4d Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 25 Jul 2024 15:15:21 +0200 Subject: [PATCH 028/116] Fix CI for Rust v1.80 (#3822) `clippy::doc_lazy_continuation` was added, which needed some fixing from our side. --- src/changelog/v0.20.md | 2 +- src/changelog/v0.9.md | 6 +-- src/event.rs | 17 ++++--- src/platform/pump_events.rs | 26 +++++------ src/platform_impl/windows/raw_input.rs | 64 +++++++++++++------------- src/window.rs | 18 ++++---- 6 files changed, 65 insertions(+), 68 deletions(-) diff --git a/src/changelog/v0.20.md b/src/changelog/v0.20.md index 55ad78e81b..eb4ba6a1de 100644 --- a/src/changelog/v0.20.md +++ b/src/changelog/v0.20.md @@ -137,7 +137,7 @@ - On X11, non-resizable windows now have maximize explicitly disabled. - On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile` -and `WindowEvent::HoveredFile`. + and `WindowEvent::HoveredFile`. - On Mac, implement `DeviceEvent::Button`. - Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`. - On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor. diff --git a/src/changelog/v0.9.md b/src/changelog/v0.9.md index ff36cf3f6d..2a9e8cc320 100644 --- a/src/changelog/v0.9.md +++ b/src/changelog/v0.9.md @@ -3,20 +3,20 @@ - Added event `WindowEvent::HiDPIFactorChanged`. - Added method `MonitorId::get_hidpi_factor`. - Deprecated `get_inner_size_pixels` and `get_inner_size_points` methods of `Window` in favor of -`get_inner_size`. + `get_inner_size`. - **Breaking:** `EventsLoop` is `!Send` and `!Sync` because of platform-dependant constraints, but `Window`, `WindowId`, `DeviceId` and `MonitorId` guaranteed to be `Send`. - `MonitorId::get_position` now returns `(i32, i32)` instead of `(u32, u32)`. - Rewrite of the wayland backend to use wayland-client-0.11 - Support for dead keys on wayland for keyboard utf8 input - Monitor enumeration on Windows is now implemented using `EnumDisplayMonitors` instead of -`EnumDisplayDevices`. This changes the value returned by `MonitorId::get_name()`. + `EnumDisplayDevices`. This changes the value returned by `MonitorId::get_name()`. - On Windows added `MonitorIdExt::hmonitor` method - Impl `Clone` for `EventsLoopProxy` - `EventsLoop::get_primary_monitor()` on X11 will fallback to any available monitor if no primary is found - Support for touch event on wayland - `WindowEvent`s `MouseMoved`, `MouseEntered`, and `MouseLeft` have been renamed to -`CursorMoved`, `CursorEntered`, and `CursorLeft`. + `CursorMoved`, `CursorEntered`, and `CursorLeft`. - New `DeviceEvent`s added, `MouseMotion` and `MouseWheel`. - Send `CursorMoved` event after `CursorEntered` and `Focused` events. - Add support for `ModifiersState`, `MouseMove`, `MouseInput`, `MouseMotion` for emscripten backend. diff --git a/src/event.rs b/src/event.rs index d05d35c853..1d14259de4 100644 --- a/src/event.rs +++ b/src/event.rs @@ -532,11 +532,11 @@ pub struct KeyEvent { /// ## Caveats /// /// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that - /// implements DVORAK in hardware (or firmware) + /// implements DVORAK in hardware (or firmware) /// - Your application will likely have to handle keyboards which are missing keys that your - /// own keyboard has. + /// own keyboard has. /// - Certain `KeyCode`s will move between a couple of different positions depending on what - /// layout the keyboard was manufactured to support. + /// layout the keyboard was manufactured to support. /// /// **Because of these caveats, it is important that you provide users with a way to configure /// most (if not all) keybinds in your application.** @@ -558,8 +558,7 @@ pub struct KeyEvent { /// /// This has two use cases: /// - Allows querying whether the current input is a Dead key. - /// - Allows handling key-bindings on platforms which don't - /// support [`key_without_modifiers`]. + /// - Allows handling key-bindings on platforms which don't support [`key_without_modifiers`]. /// /// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard /// shortcuts, **it is important that you provide users with a way to configure your @@ -567,8 +566,8 @@ pub struct KeyEvent { /// incompatible keyboard layout.** /// /// ## Platform-specific - /// - **Web:** Dead keys might be reported as the real key instead - /// of `Dead` depending on the browser/OS. + /// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the + /// browser/OS. /// /// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers pub logical_key: keyboard::Key, @@ -850,8 +849,8 @@ pub struct Touch { /// /// - Only available on **iOS** 9.0+, **Windows** 8+, **Web**, and **Android**. /// - **Android**: This will never be [None]. If the device doesn't support pressure - /// sensitivity, force will either be 0.0 or 1.0. Also see the - /// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE). + /// sensitivity, force will either be 0.0 or 1.0. Also see the + /// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE). pub force: Option, /// Unique identifier of a finger. pub id: u64, diff --git a/src/platform/pump_events.rs b/src/platform/pump_events.rs index 7eedda2816..dbe14f6fac 100644 --- a/src/platform/pump_events.rs +++ b/src/platform/pump_events.rs @@ -52,18 +52,18 @@ pub trait EventLoopExtPumpEvents { /// - `RedrawRequested` events, used to schedule rendering. /// /// macOS for example uses a `drawRect` callback to drive rendering - /// within applications and expects rendering to be finished before - /// the `drawRect` callback returns. + /// within applications and expects rendering to be finished before + /// the `drawRect` callback returns. /// /// For portability it's strongly recommended that applications should - /// keep their rendering inside the closure provided to Winit. + /// keep their rendering inside the closure provided to Winit. /// - Any lifecycle events, such as `Suspended` / `Resumed`. /// /// The handling of these events needs to be synchronized with the - /// operating system and it would never be appropriate to buffer a - /// notification that your application has been suspended or resumed and - /// then handled that later since there would always be a chance that - /// other lifecycle events occur while the event is buffered. + /// operating system and it would never be appropriate to buffer a + /// notification that your application has been suspended or resumed and + /// then handled that later since there would always be a chance that + /// other lifecycle events occur while the event is buffered. /// /// ## Supported Platforms /// @@ -74,13 +74,13 @@ pub trait EventLoopExtPumpEvents { /// /// ## Unsupported Platforms /// - /// - **Web:** This API is fundamentally incompatible with the event-based way in which - /// Web browsers work because it's not possible to have a long-running external - /// loop that would block the browser and there is nothing that can be - /// polled to ask for new new events. Events are delivered via callbacks based - /// on an event loop that is internal to the browser itself. + /// - **Web:** This API is fundamentally incompatible with the event-based way in which Web + /// browsers work because it's not possible to have a long-running external loop that would + /// block the browser and there is nothing that can be polled to ask for new new events. + /// Events are delivered via callbacks based on an event loop that is internal to the browser + /// itself. /// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so - /// there's no way to support the same approach to polling as on MacOS. + /// there's no way to support the same approach to polling as on MacOS. /// /// ## Platform-specific /// diff --git a/src/platform_impl/windows/raw_input.rs b/src/platform_impl/windows/raw_input.rs index 44c7c95a73..87d16750ea 100644 --- a/src/platform_impl/windows/raw_input.rs +++ b/src/platform_impl/windows/raw_input.rs @@ -263,39 +263,37 @@ pub fn get_keyboard_physical_key(keyboard: RAWKEYBOARD) -> Option { scancode_to_physicalkey(scancode as u32) }; if keyboard.VKey == VK_SHIFT { - if let PhysicalKey::Code(code) = physical_key { - match code { - KeyCode::NumpadDecimal - | KeyCode::Numpad0 - | KeyCode::Numpad1 - | KeyCode::Numpad2 - | KeyCode::Numpad3 - | KeyCode::Numpad4 - | KeyCode::Numpad5 - | KeyCode::Numpad6 - | KeyCode::Numpad7 - | KeyCode::Numpad8 - | KeyCode::Numpad9 => { - // On Windows, holding the Shift key makes numpad keys behave as if NumLock - // wasn't active. The way this is exposed to applications by the system is that - // the application receives a fake key release event for the shift key at the - // moment when the numpad key is pressed, just before receiving the numpad key - // as well. - // - // The issue is that in the raw device event (here), the fake shift release - // event reports the numpad key as the scancode. Unfortunately, the event - // doesn't have any information to tell whether it's the - // left shift or the right shift that needs to get the fake - // release (or press) event so we don't forward this - // event to the application at all. - // - // For more on this, read the article by Raymond Chen, titled: - // "The shift key overrides NumLock" - // https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953 - return None; - }, - _ => (), - } + if let PhysicalKey::Code( + KeyCode::NumpadDecimal + | KeyCode::Numpad0 + | KeyCode::Numpad1 + | KeyCode::Numpad2 + | KeyCode::Numpad3 + | KeyCode::Numpad4 + | KeyCode::Numpad5 + | KeyCode::Numpad6 + | KeyCode::Numpad7 + | KeyCode::Numpad8 + | KeyCode::Numpad9, + ) = physical_key + { + // On Windows, holding the Shift key makes numpad keys behave as if NumLock + // wasn't active. The way this is exposed to applications by the system is that + // the application receives a fake key release event for the shift key at the + // moment when the numpad key is pressed, just before receiving the numpad key + // as well. + // + // The issue is that in the raw device event (here), the fake shift release + // event reports the numpad key as the scancode. Unfortunately, the event + // doesn't have any information to tell whether it's the + // left shift or the right shift that needs to get the fake + // release (or press) event so we don't forward this + // event to the application at all. + // + // For more on this, read the article by Raymond Chen, titled: + // "The shift key overrides NumLock" + // https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953 + return None; } } diff --git a/src/window.rs b/src/window.rs index 7c4812c560..31ef19a330 100644 --- a/src/window.rs +++ b/src/window.rs @@ -417,8 +417,8 @@ impl WindowAttributes { /// /// ## Platform-specific /// - /// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely - /// prevent all apps from reading the window content, for instance, QuickTime. + /// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely prevent all + /// apps from reading the window content, for instance, QuickTime. /// - **iOS / Android / Web / x11 / Orbital:** Ignored. /// /// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone @@ -466,8 +466,8 @@ impl WindowAttributes { /// ## Platform-specific /// /// - **Windows** : A child window has the WS_CHILD style and is confined - /// to the client area of its parent window. For more information, see - /// + /// to the client area of its parent window. For more information, see + /// /// - **X11**: A child window is confined to the client area of its parent window. /// - **Android / iOS / Wayland / Web:** Unsupported. #[cfg(feature = "rwh_06")] @@ -530,9 +530,9 @@ impl Window { /// provided by XRandR. /// /// If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use - /// the XRandR scaling method. Generally speaking, you should try to configure the - /// standard system variables to do what you want before resorting to - /// `WINIT_X11_SCALE_FACTOR`. + /// the XRandR scaling method. Generally speaking, you should try to configure the + /// standard system variables to do what you want before resorting to + /// `WINIT_X11_SCALE_FACTOR`. /// - **Wayland:** The scale factor is suggested by the compositor for each window individually /// by using the wp-fractional-scale protocol if available. Falls back to integer-scale /// factors otherwise. @@ -1389,8 +1389,8 @@ impl Window { /// /// ## Platform-specific /// - /// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely - /// prevent all apps from reading the window content, for instance, QuickTime. + /// - **macOS**: if `false`, [`NSWindowSharingNone`] is used but doesn't completely prevent all + /// apps from reading the window content, for instance, QuickTime. /// - **iOS / Android / x11 / Wayland / Web / Orbital:** Unsupported. /// /// [`NSWindowSharingNone`]: https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone From 5fce4a09dea0e386a12499012894f263d4545b89 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 26 Jul 2024 21:49:32 +0300 Subject: [PATCH 029/116] android: add `{Active,}EventLoopExtAndroid::android_app` This type comes from the user and stored for the entire lifetime, so no need to hide it from them after they've passed it to winit. Fixes #3818. --- src/changelog/unreleased.md | 1 + src/platform/android.rs | 28 +++++++++++++++++++++------- src/platform_impl/android/mod.rs | 4 ++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 49f2dc9c1c..5a8a60626c 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -43,6 +43,7 @@ changelog entry. ### Added - On Web, implement `Error` for `platform::web::CustomCursorError`. +- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop. ### Fixed diff --git a/src/platform/android.rs b/src/platform/android.rs index 748126a010..b75667b5ae 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -76,12 +76,22 @@ use crate::window::{Window, WindowAttributes}; use self::activity::{AndroidApp, ConfigurationRef, Rect}; /// Additional methods on [`EventLoop`] that are specific to Android. -pub trait EventLoopExtAndroid {} +pub trait EventLoopExtAndroid { + /// Get the [`AndroidApp`] which was used to create this event loop. + fn android_app(&self) -> &AndroidApp; +} -impl EventLoopExtAndroid for EventLoop {} +impl EventLoopExtAndroid for EventLoop { + fn android_app(&self) -> &AndroidApp { + &self.event_loop.android_app + } +} /// Additional methods on [`ActiveEventLoop`] that are specific to Android. -pub trait ActiveEventLoopExtAndroid {} +pub trait ActiveEventLoopExtAndroid { + /// Get the [`AndroidApp`] which was used to create this event loop. + fn android_app(&self) -> &AndroidApp; +} /// Additional methods on [`Window`] that are specific to Android. pub trait WindowExtAndroid { @@ -100,7 +110,11 @@ impl WindowExtAndroid for Window { } } -impl ActiveEventLoopExtAndroid for ActiveEventLoop {} +impl ActiveEventLoopExtAndroid for ActiveEventLoop { + fn android_app(&self) -> &AndroidApp { + &self.p.app + } +} /// Additional methods on [`WindowAttributes`] that are specific to Android. pub trait WindowAttributesExtAndroid {} @@ -108,9 +122,9 @@ pub trait WindowAttributesExtAndroid {} impl WindowAttributesExtAndroid for WindowAttributes {} pub trait EventLoopBuilderExtAndroid { - /// Associates the `AndroidApp` that was passed to `android_main()` with the event loop + /// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop /// - /// This must be called on Android since the `AndroidApp` is not global state. + /// This must be called on Android since the [`AndroidApp`] is not global state. fn with_android_app(&mut self, app: AndroidApp) -> &mut Self; /// Calling this will mark the volume keys to be manually handled by the application @@ -147,7 +161,7 @@ impl EventLoopBuilderExtAndroid for EventLoopBuilder { /// depending on the `android_activity` crate, and instead consume the API that /// is re-exported by Winit. /// -/// For compatibility applications should then import the `AndroidApp` type for +/// For compatibility applications should then import the [`AndroidApp`] type for /// their `android_main(app: AndroidApp)` function like: /// ```rust /// #[cfg(target_os = "android")] diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index e279b5bdb0..9b12eaaa1d 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -132,7 +132,7 @@ impl RedrawRequester { pub struct KeyEventExtra {} pub struct EventLoop { - android_app: AndroidApp, + pub(crate) android_app: AndroidApp, window_target: event_loop::ActiveEventLoop, redraw_flag: SharedFlag, user_events_sender: mpsc::Sender, @@ -646,7 +646,7 @@ impl EventLoopProxy { } pub struct ActiveEventLoop { - app: AndroidApp, + pub(crate) app: AndroidApp, control_flow: Cell, exit: Cell, redraw_requester: RedrawRequester, From 77e8fe6094374220d0149e76b148c36995f6795b Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 4 Aug 2024 12:03:26 +0200 Subject: [PATCH 030/116] Windows: use `Box::leak()` instead of `Box::into_raw()` This was detected by a new change in Nightly Rust that applied `#[must_use]` to the return value of `Box::into_raw()`. --- src/platform_impl/windows/event_loop.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 4fc31553ff..35e3410d15 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -2441,7 +2441,7 @@ unsafe extern "system" fn thread_event_target_callback( if userdata_removed { drop(userdata); } else { - Box::into_raw(userdata); + Box::leak(userdata); } result } From f3d1f922f999925e469492020b6f7970729781cf Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Mon, 5 Aug 2024 12:44:18 +0200 Subject: [PATCH 031/116] macOS: skip releasing unavailable monitors Prevent assertion error when trying to close a fullscreen window that was on a display that got disconnected. --- src/changelog/unreleased.md | 1 + src/platform_impl/macos/window_delegate.rs | 31 ++++++++++++---------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 5a8a60626c..9f7b74bd05 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -49,3 +49,4 @@ changelog entry. - On MacOS, fix building with `feature = "rwh_04"`. - On Web, pen events are now routed through to `WindowEvent::Cursor*`. +- On macOS, fix panic when releasing not available monitor. diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 6f3d4abb50..a946dcf00f 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1426,13 +1426,7 @@ impl WindowDelegate { toggle_fullscreen(self.window()); }, (Some(Fullscreen::Exclusive(ref video_mode)), None) => { - unsafe { - ffi::CGRestorePermanentDisplayConfiguration(); - assert_eq!( - ffi::CGDisplayRelease(video_mode.monitor().native_identifier()), - ffi::kCGErrorSuccess - ); - }; + restore_and_release_display(&video_mode.monitor()); toggle_fullscreen(self.window()); }, (Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => { @@ -1463,13 +1457,7 @@ impl WindowDelegate { ); app.setPresentationOptions(presentation_options); - unsafe { - ffi::CGRestorePermanentDisplayConfiguration(); - assert_eq!( - ffi::CGDisplayRelease(video_mode.monitor().native_identifier()), - ffi::kCGErrorSuccess - ); - }; + restore_and_release_display(&video_mode.monitor()); // Restore the normal window level following the Borderless fullscreen // `CGShieldingWindowLevel() + 1` hack. @@ -1689,6 +1677,21 @@ impl WindowDelegate { } } +fn restore_and_release_display(monitor: &MonitorHandle) { + let available_monitors = monitor::available_monitors(); + if available_monitors.contains(monitor) { + unsafe { + ffi::CGRestorePermanentDisplayConfiguration(); + assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess); + }; + } else { + warn!( + monitor = monitor.name(), + "Tried to restore exclusive fullscreen on a monitor that is no longer available" + ); + } +} + impl WindowExtMacOS for WindowDelegate { #[inline] fn simple_fullscreen(&self) -> bool { From fa3ec006e2225beae468c8fa7990ad1979563f02 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 5 Aug 2024 20:51:38 +0200 Subject: [PATCH 032/116] Add `ActiveEventLoop::system_theme()` This also fixes macOS returning `None` in `Window::theme()` if no theme override is set, instead it now returns the system theme. MacOS and Wayland were the only ones working correctly according to the documentation, which was an oversight. The documentation was "fixed" now. Fixes #3837. --- src/changelog/unreleased.md | 2 ++ src/event_loop.rs | 13 ++++++++++- src/platform_impl/android/mod.rs | 5 +++++ src/platform_impl/ios/event_loop.rs | 9 ++++++-- src/platform_impl/linux/mod.rs | 5 +++++ src/platform_impl/macos/event_loop.rs | 15 +++++++++++-- src/platform_impl/macos/window_delegate.rs | 22 +++++++++++-------- src/platform_impl/orbital/event_loop.rs | 7 +++++- .../web/event_loop/window_target.rs | 10 +++++++++ src/platform_impl/windows/dark_mode.rs | 2 +- src/platform_impl/windows/event_loop.rs | 6 ++++- src/window.rs | 8 +++---- 12 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 9f7b74bd05..6d4d3e6a5d 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -42,6 +42,7 @@ changelog entry. ### Added +- Add `ActiveEventLoop::system_theme()`, returning the current system theme. - On Web, implement `Error` for `platform::web::CustomCursorError`. - On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop. @@ -50,3 +51,4 @@ changelog entry. - On MacOS, fix building with `feature = "rwh_04"`. - On Web, pen events are now routed through to `WindowEvent::Cursor*`. - On macOS, fix panic when releasing not available monitor. +- On MacOS, return the system theme in `Window::theme()` if no theme override is set. diff --git a/src/event_loop.rs b/src/event_loop.rs index 99e72040f8..b5d5d26402 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -23,7 +23,7 @@ use crate::error::{EventLoopError, OsError}; use crate::event::Event; use crate::monitor::MonitorHandle; use crate::platform_impl; -use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes}; +use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes}; /// Provides a way to retrieve events from the system and from the windows that were registered to /// the events loop. @@ -437,6 +437,17 @@ impl ActiveEventLoop { self.p.listen_device_events(allowed); } + /// Returns the current system theme. + /// + /// Returns `None` if it cannot be determined on the current platform. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported. + pub fn system_theme(&self) -> Option { + self.p.system_theme() + } + /// Sets the [`ControlFlow`]. pub fn set_control_flow(&self, control_flow: ControlFlow) { self.p.set_control_flow(control_flow) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 9b12eaaa1d..950e310299 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -677,6 +677,11 @@ impl ActiveEventLoop { rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty()) } + #[inline] + pub fn system_theme(&self) -> Option { + None + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index ac485b9e8f..52d8062bea 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -22,8 +22,8 @@ use crate::event_loop::{ ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed, }; use crate::platform::ios::Idiom; -use crate::platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents}; -use crate::window::{CustomCursor, CustomCursorSource}; +use crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents}; +use crate::window::{CustomCursor, CustomCursorSource, Theme}; use super::app_delegate::AppDelegate; use super::app_state::AppState; @@ -58,6 +58,11 @@ impl ActiveEventLoop { rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty()) } + #[inline] + pub fn system_theme(&self) -> Option { + None + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 6cb99fee31..bee440189d 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -897,6 +897,11 @@ impl ActiveEventLoop { x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05()) } + #[inline] + pub fn system_theme(&self) -> Option { + None + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 1680953c34..9b7be282f6 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -16,7 +16,7 @@ use core_foundation::runloop::{ }; use objc2::rc::{autoreleasepool, Retained}; use objc2::runtime::ProtocolObject; -use objc2::{msg_send_id, ClassType}; +use objc2::{msg_send_id, sel, ClassType}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow}; use objc2_foundation::{MainThreadMarker, NSObjectProtocol}; @@ -33,7 +33,7 @@ use crate::event_loop::{ use crate::platform::macos::ActivationPolicy; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::platform::cursor::CustomCursor; -use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource}; +use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme}; #[derive(Default)] pub struct PanicInfo { @@ -106,6 +106,17 @@ impl ActiveEventLoop { rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty()) } + #[inline] + pub fn system_theme(&self) -> Option { + let app = NSApplication::sharedApplication(self.mtm); + + if app.respondsToSelector(sel!(effectiveAppearance)) { + Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) + } else { + Some(Theme::Light) + } + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index a946dcf00f..2a7877b8bc 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1645,14 +1645,18 @@ impl WindowDelegate { } pub fn theme(&self) -> Option { - // Note: We could choose between returning the value of `effectiveAppearance` or - // `appearance`, depending on what the user is asking about: - // - "how should I render on this particular frame". - // - "what is the configuration for this window". - // - // We choose the latter for consistency with the `set_theme` call, though it might also be - // useful to expose the former. - Some(appearance_to_theme(unsafe { &*self.window().appearance()? })) + unsafe { self.window().appearance() } + .map(|appearance| appearance_to_theme(&appearance)) + .or_else(|| { + let mtm = MainThreadMarker::from(self); + let app = NSApplication::sharedApplication(mtm); + + if app.respondsToSelector(sel!(effectiveAppearance)) { + Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) + } else { + Some(Theme::Light) + } + }) } pub fn set_theme(&self, theme: Option) { @@ -1835,7 +1839,7 @@ fn dark_appearance_name() -> &'static NSString { ns_string!("NSAppearanceNameDarkAqua") } -fn appearance_to_theme(appearance: &NSAppearance) -> Theme { +pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme { let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[ unsafe { NSAppearanceNameAqua.copy() }, dark_appearance_name().copy(), diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index 59ba9e522d..91f76e7591 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -20,7 +20,7 @@ use crate::keyboard::{ PhysicalKey, }; use crate::window::{ - CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId, + CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId, }; use super::{ @@ -775,6 +775,11 @@ impl ActiveEventLoop { rwh_05::RawDisplayHandle::Orbital(rwh_05::OrbitalDisplayHandle::empty()) } + #[inline] + pub fn system_theme(&self) -> Option { + None + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 05f87dcc35..d741f09872 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -614,6 +614,16 @@ impl ActiveEventLoop { self.runner.listen_device_events(allowed) } + pub fn system_theme(&self) -> Option { + backend::is_dark_mode(self.runner.window()).map(|is_dark_mode| { + if is_dark_mode { + Theme::Dark + } else { + Theme::Light + } + }) + } + pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.runner.set_control_flow(control_flow) } diff --git a/src/platform_impl/windows/dark_mode.rs b/src/platform_impl/windows/dark_mode.rs index 4f910ad5c9..9d4bad9279 100644 --- a/src/platform_impl/windows/dark_mode.rs +++ b/src/platform_impl/windows/dark_mode.rs @@ -123,7 +123,7 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool { } } -fn should_use_dark_mode() -> bool { +pub fn should_use_dark_mode() -> bool { should_apps_use_dark_mode() && !is_high_contrast() } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 35e3410d15..639a93865d 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -81,7 +81,7 @@ use crate::platform_impl::platform::{ raw_input, util, wrap_device_id, Fullscreen, WindowId, DEVICE_ID, }; use crate::window::{ - CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId, + CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId, }; use runner::{EventLoopRunner, EventLoopRunnerShared}; @@ -550,6 +550,10 @@ impl ActiveEventLoop { raw_input::register_all_mice_and_keyboards_for_raw_input(self.thread_msg_target, allowed); } + pub fn system_theme(&self) -> Option { + Some(if super::dark_mode::should_use_dark_mode() { Theme::Dark } else { Theme::Light }) + } + pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.runner_shared.set_control_flow(control_flow) } diff --git a/src/window.rs b/src/window.rs index 31ef19a330..f19265e3a1 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1371,14 +1371,14 @@ impl Window { self.window.maybe_queue_on_main(move |w| w.set_theme(theme)) } - /// Returns the current window theme override. + /// Returns the current window theme. /// - /// Returns `None` if the current theme is set as the system default, or if it cannot be - /// determined on the current platform. + /// Returns `None` if it cannot be determined on the current platform. /// /// ## Platform-specific /// - /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported, returns `None`. + /// - **iOS / Android / x11 / Orbital:** Unsupported. + /// - **Wayland:** Only returns theme overrides. #[inline] pub fn theme(&self) -> Option { let _span = tracing::debug_span!("winit::Window::theme",).entered(); From 8c07319ced6f7a32c92b1d01479c7917cf4c7fec Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 6 Aug 2024 15:04:58 +0300 Subject: [PATCH 033/116] chore: drop libera link It has low amount of users and not active, so better direct people straight to matrix at this point. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7722972403..c77c6f7650 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu ## Contact Us -Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit). +Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings). From 114512bb329ac2da78e33dcd54c3f466f34da394 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 7 Aug 2024 12:40:40 +0300 Subject: [PATCH 034/116] Winit version 0.30.5 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 13 ------------- src/changelog/v0.30.md | 15 +++++++++++++++ src/platform/android.rs | 2 +- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4bdbc736fe..0f1a038068 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.4" +version = "0.30.5" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index c77c6f7650..981dcebb24 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.4" +winit = "0.30.5" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 6d4d3e6a5d..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,16 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Added - -- Add `ActiveEventLoop::system_theme()`, returning the current system theme. -- On Web, implement `Error` for `platform::web::CustomCursorError`. -- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop. - -### Fixed - -- On MacOS, fix building with `feature = "rwh_04"`. -- On Web, pen events are now routed through to `WindowEvent::Cursor*`. -- On macOS, fix panic when releasing not available monitor. -- On MacOS, return the system theme in `Window::theme()` if no theme override is set. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index ee38a6dfc5..11af4a99af 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,18 @@ +## 0.30.5 + +### Added + +- Add `ActiveEventLoop::system_theme()`, returning the current system theme. +- On Web, implement `Error` for `platform::web::CustomCursorError`. +- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop. + +### Fixed + +- On MacOS, fix building with `feature = "rwh_04"`. +- On Web, pen events are now routed through to `WindowEvent::Cursor*`. +- On macOS, fix panic when releasing not available monitor. +- On MacOS, return the system theme in `Window::theme()` if no theme override is set. + ## 0.30.4 ### Changed diff --git a/src/platform/android.rs b/src/platform/android.rs index b75667b5ae..823ef5a37b 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.4", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.5", //! 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 From 00ecd1533ea6fa35ec078c3e5c4c9a3719efc6f6 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Wed, 21 Aug 2024 21:14:07 -0700 Subject: [PATCH 035/116] 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 f4a5ad9c228ea266de7b2117dcafb236417292e2 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Fri, 23 Aug 2024 04:47:40 -0700 Subject: [PATCH 036/116] 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 838b2459a7cb9a036c3ed91ab1eaf8450f20b356 Mon Sep 17 00:00:00 2001 From: Tarek Abdel Sater Date: Wed, 4 Sep 2024 16:44:05 +0400 Subject: [PATCH 037/116] 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 cfe7eb89541d51e56fe0278373b6f7e3bcc37b81 Mon Sep 17 00:00:00 2001 From: purajit Date: Mon, 16 Sep 2024 06:49:18 -0700 Subject: [PATCH 038/116] 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 685090021b1395d6830d606007f89609071d95f4 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 23 Sep 2024 22:26:21 +0300 Subject: [PATCH 039/116] 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 1bc405e526a3479281f86f857eb6ccd69db6459a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 24 Sep 2024 01:06:10 +0200 Subject: [PATCH 040/116] 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 107bf91a2d9ce628a1f5d9fee2f2c6bc1e82d4ce Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 13 Oct 2024 21:57:02 +0300 Subject: [PATCH 041/116] 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 485ae90aaea5cd1d316168bea714bee05bd3e140 Mon Sep 17 00:00:00 2001 From: Shane Celis Date: Tue, 22 Oct 2024 07:00:53 -0400 Subject: [PATCH 042/116] 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 9ceb8d4144f6f64587c74598337e83cc9c530c32 Mon Sep 17 00:00:00 2001 From: AngelicosPhosphoros Date: Sun, 13 Oct 2024 12:56:24 +0200 Subject: [PATCH 043/116] 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 b02a70e6bb81129db309eca61b011dcffd335a6a 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 044/116] 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 bbab3d8e35f1f69a98dc32fe7eec6024a6625cea 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 045/116] 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 bab2d82fd360bdcc5bc080ec95ed5efdbf26df7a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 12 Nov 2024 14:52:48 +0100 Subject: [PATCH 046/116] 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 1d18a6ca57c0c778f867863e6ea48e97bc391e83 Mon Sep 17 00:00:00 2001 From: Piotr Podusowski Date: Wed, 13 Nov 2024 12:43:59 +0100 Subject: [PATCH 047/116] 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 5c1cc652c4572adb2ea9cad4c691938b0ad6eedc Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Mon, 19 Aug 2024 22:04:29 +0200 Subject: [PATCH 048/116] 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 fccfd359db3ab96b6bedbb100e3cba740243ce4c Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Tue, 19 Nov 2024 09:19:45 +1300 Subject: [PATCH 049/116] 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 d828523b394069ba91a369c856160fce41c0acb4 Mon Sep 17 00:00:00 2001 From: Rodrigo Rivas Costa Date: Thu, 21 Nov 2024 12:04:55 +0100 Subject: [PATCH 050/116] 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 d6ab3dcb86c6a091e58f202b6d8e8b3dc987affb Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 2 Dec 2024 12:51:26 +0100 Subject: [PATCH 051/116] 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 1fb118031ce3b707fdd4e0efce77ea568df4dc7f Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 18:48:23 +0100 Subject: [PATCH 052/116] 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 1e44c354feed01a234657202a4a40a660ce84fdb Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Dec 2024 19:02:53 +0100 Subject: [PATCH 053/116] 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 836e7170219b69e818e03b503098291915c2438a Mon Sep 17 00:00:00 2001 From: "Skip R." Date: Sun, 8 Dec 2024 13:01:57 -0800 Subject: [PATCH 054/116] 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 f2fb7dee3d0d15b8b8887bd420ea0394de553d68 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 055/116] 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 cf829a44e728df7033f78071c19f8e7e4de65d21 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 21 Nov 2024 08:28:32 +0100 Subject: [PATCH 056/116] 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 6f22822025475618f9ea054a7d148a6d42c5c1ec Mon Sep 17 00:00:00 2001 From: daxpedda Date: Tue, 13 Aug 2024 21:54:03 +0200 Subject: [PATCH 057/116] 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 5d2151e8936021a9599af8fceeb1c14ee7849174 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 16 Dec 2024 19:48:03 +0300 Subject: [PATCH 058/116] 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 82f0949aea6ff93e5a898d97f652f60ede587d5c Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 16 Dec 2024 19:29:24 +0300 Subject: [PATCH 059/116] 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 From 727583ffbfe73e2ceab6e8a4749db7ab159c7dcd Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 22 Dec 2024 17:51:44 +0300 Subject: [PATCH 060/116] x11: fix KeyboardInput delivered twice with IME The filtered events were still processed even though they shouldn't once we know that they're filtered. Fixes #4048. --- src/changelog/unreleased.md | 4 ++++ src/platform_impl/linux/x11/event_processor.rs | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..b9c461aed8 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Fixed + +- On X11, fixed KeyboardInput delivered twice when IME enabled. diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 14f80b67ce..438c808b8d 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -129,6 +129,7 @@ impl EventProcessor { /// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences, /// along with an extra copy of the KeyRelease events. This also prevents backspace and /// arrow keys from being detected twice. + #[must_use] fn filter_event(&mut self, xev: &mut XEvent) -> bool { let wt = Self::window_target(&self.target); unsafe { @@ -149,7 +150,7 @@ impl EventProcessor { // 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 filtered = 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); @@ -157,7 +158,8 @@ impl EventProcessor { .and_then(|ime| window.map(|window| ime.borrow().is_ime_allowed(window))) .unwrap_or(false); - if forward_to_ime && self.filter_event(xev) { + let filtered = forward_to_ime && self.filter_event(xev); + if filtered { 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 @@ -170,8 +172,15 @@ impl EventProcessor { self.xfiltered_modifiers.push_front(xev.serial); } } + + filtered } else { - self.filter_event(xev); + self.filter_event(xev) + }; + + // Don't process event if it was filtered. + if filtered { + return; } match event_type { From 9cbce055d35d1270e7e4cc43ba4c81fffa6d1bb9 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 22 Dec 2024 22:29:58 +0300 Subject: [PATCH 061/116] Winit version 0.30.7 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 4 ---- src/changelog/v0.30.md | 6 ++++++ src/platform/android.rs | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 34e2b6d7f5..33d429f671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.6" +version = "0.30.7" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index 236fa7d7a9..88d7403200 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.6" +winit = "0.30.7" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index b9c461aed8..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,7 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Fixed - -- On X11, fixed KeyboardInput delivered twice when IME enabled. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index b51aaa95f6..3ff54b508d 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,9 @@ +## 0.30.7 + +### Fixed + +- On X11, fixed KeyboardInput delivered twice when IME enabled. + ## 0.30.6 ### Added diff --git a/src/platform/android.rs b/src/platform/android.rs index 5313215137..dc3a111201 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.6", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.7", //! 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 From 43c323ccc03dd2bc729d17aa3826b777d58fec13 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Mon, 23 Dec 2024 15:23:44 -0600 Subject: [PATCH 062/116] windows: fix the event loop not waking on accessibility requests Fixes #4055. --- src/changelog/unreleased.md | 4 ++++ src/platform_impl/windows/event_loop.rs | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..aef8dc2e45 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Fixed + +- On Windows, fixed the event loop not waking on accessibility requests. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3fdf98d550..fa912f6ce0 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -48,7 +48,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ 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, + PT_TOUCH, QS_ALLINPUT, 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, @@ -745,11 +745,12 @@ fn wait_for_messages_impl( let (num_handles, raw_handles) = if use_timer { (1, [high_resolution_timer.unwrap()]) } else { (0, [ptr::null_mut()]) }; + // We must use `QS_ALLINPUT` to wake on accessibility messages. let result = MsgWaitForMultipleObjectsEx( num_handles, raw_handles.as_ptr() as *const _, wait_duration_ms, - QS_ALLEVENTS, + QS_ALLINPUT, MWMO_INPUTAVAILABLE, ); if result == WAIT_FAILED { From 6b5cc165dd29b5513afcb9d79af185fd1a1bbcdd Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 23 Dec 2024 23:38:46 +0300 Subject: [PATCH 063/116] x11: add workaround for disabling IME on gnome GNOME doesn't list that there's a _NONE_ style at all, but it still works if you use it. --- src/changelog/unreleased.md | 4 ++++ src/platform_impl/linux/x11/ime/input_method.rs | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index aef8dc2e45..5e2c6bf9b7 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -40,6 +40,10 @@ changelog entry. ## Unreleased +### Added + +- On X11, add a workaround for disabling IME on GNOME. + ### Fixed - On Windows, fixed the event loop not waking on accessibility requests. diff --git a/src/platform_impl/linux/x11/ime/input_method.rs b/src/platform_impl/linux/x11/ime/input_method.rs index b9d3ca7101..7f147bf1c4 100644 --- a/src/platform_impl/linux/x11/ime/input_method.rs +++ b/src/platform_impl/linux/x11/ime/input_method.rs @@ -81,7 +81,9 @@ impl InputMethod { } let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap()); - let none_style = none_style.unwrap_or(preedit_style); + // Always initialize none style even when it's not advertised, since it seems to work + // regardless... + let none_style = none_style.unwrap_or(Style::None(XIM_NONE_STYLE)); Some(InputMethod { im, _name: name, preedit_style, none_style }) } From 61314cd50a6b42c865b381f2f301f72717491566 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 31 Dec 2024 05:52:12 +0300 Subject: [PATCH 064/116] x11: fix cursor grab mode tracking on error Fixes #4064. --- src/changelog/unreleased.md | 1 + src/platform_impl/linux/x11/window.rs | 65 ++++++++++++++------------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 5e2c6bf9b7..ca9e513531 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -47,3 +47,4 @@ changelog entry. ### Fixed - On Windows, fixed the event loop not waking on accessibility requests. +- On X11, fixed cursor grab mode state tracking on error. diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 9eff6dd028..1777f904df 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1492,6 +1492,11 @@ impl UnownedWindow { #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { + // We don't support the locked cursor yet, so ignore it early on. + if mode == CursorGrabMode::Locked { + return Err(ExternalError::NotSupported(NotSupportedError::new())); + } + let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap(); if mode == *grabbed_lock { return Ok(()); @@ -1503,40 +1508,40 @@ impl UnownedWindow { .xcb_connection() .ungrab_pointer(x11rb::CURRENT_TIME) .expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`"); + *grabbed_lock = CursorGrabMode::None; let result = match mode { CursorGrabMode::None => self.xconn.flush_requests().map_err(|err| { ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into()))) }), CursorGrabMode::Confined => { - let result = { - self.xconn - .xcb_connection() - .grab_pointer( - true as _, - self.xwindow, - xproto::EventMask::BUTTON_PRESS - | xproto::EventMask::BUTTON_RELEASE - | xproto::EventMask::ENTER_WINDOW - | xproto::EventMask::LEAVE_WINDOW - | xproto::EventMask::POINTER_MOTION - | xproto::EventMask::POINTER_MOTION_HINT - | xproto::EventMask::BUTTON1_MOTION - | xproto::EventMask::BUTTON2_MOTION - | xproto::EventMask::BUTTON3_MOTION - | xproto::EventMask::BUTTON4_MOTION - | xproto::EventMask::BUTTON5_MOTION - | xproto::EventMask::KEYMAP_STATE, - xproto::GrabMode::ASYNC, - xproto::GrabMode::ASYNC, - self.xwindow, - 0u32, - x11rb::CURRENT_TIME, - ) - .expect("Failed to call `grab_pointer`") - .reply() - .expect("Failed to receive reply from `grab_pointer`") - }; + let result = self + .xconn + .xcb_connection() + .grab_pointer( + true as _, + self.xwindow, + xproto::EventMask::BUTTON_PRESS + | xproto::EventMask::BUTTON_RELEASE + | xproto::EventMask::ENTER_WINDOW + | xproto::EventMask::LEAVE_WINDOW + | xproto::EventMask::POINTER_MOTION + | xproto::EventMask::POINTER_MOTION_HINT + | xproto::EventMask::BUTTON1_MOTION + | xproto::EventMask::BUTTON2_MOTION + | xproto::EventMask::BUTTON3_MOTION + | xproto::EventMask::BUTTON4_MOTION + | xproto::EventMask::BUTTON5_MOTION + | xproto::EventMask::KEYMAP_STATE, + xproto::GrabMode::ASYNC, + xproto::GrabMode::ASYNC, + self.xwindow, + 0u32, + x11rb::CURRENT_TIME, + ) + .expect("Failed to call `grab_pointer`") + .reply() + .expect("Failed to receive reply from `grab_pointer`"); match result.status { xproto::GrabStatus::SUCCESS => Ok(()), @@ -1556,9 +1561,7 @@ impl UnownedWindow { } .map_err(|err| ExternalError::Os(os_error!(OsError::Misc(err)))) }, - CursorGrabMode::Locked => { - return Err(ExternalError::NotSupported(NotSupportedError::new())); - }, + CursorGrabMode::Locked => return Ok(()), }; if result.is_ok() { From d7710f7264a45147467c7a1043bcfcef0368a725 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 31 Dec 2024 06:08:18 +0300 Subject: [PATCH 065/116] api: add `ActivationToken::{from,into}_raw` This is needed when passing and getting token from the IPC to activate the window. --- src/changelog/unreleased.md | 1 + src/platform/startup_notify.rs | 6 ++-- .../linux/wayland/types/xdg_activation.rs | 2 +- src/platform_impl/linux/wayland/window/mod.rs | 2 +- src/platform_impl/linux/x11/mod.rs | 2 +- src/platform_impl/linux/x11/window.rs | 2 +- src/window.rs | 29 +++++++++++++++++-- 7 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index ca9e513531..84e9e766fc 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -42,6 +42,7 @@ changelog entry. ### Added +- `ActivationToken::from_raw` and `ActivationToken::into_raw`. - On X11, add a workaround for disabling IME on GNOME. ### Fixed diff --git a/src/platform/startup_notify.rs b/src/platform/startup_notify.rs index c9047a03b2..6fab284e1a 100644 --- a/src/platform/startup_notify.rs +++ b/src/platform/startup_notify.rs @@ -64,7 +64,7 @@ impl EventLoopExtStartupNotify for ActiveEventLoop { crate::platform_impl::ActiveEventLoop::X(_) => env::var(X11_VAR), } .ok() - .map(ActivationToken::_new) + .map(ActivationToken::from_raw) } } @@ -94,6 +94,6 @@ pub fn reset_activation_token_env() { /// /// This could be used before running daemon processes. pub fn set_activation_token_env(token: ActivationToken) { - env::set_var(X11_VAR, &token._token); - env::set_var(WAYLAND_VAR, token._token); + env::set_var(X11_VAR, &token.token); + env::set_var(WAYLAND_VAR, token.token); } diff --git a/src/platform_impl/linux/wayland/types/xdg_activation.rs b/src/platform_impl/linux/wayland/types/xdg_activation.rs index 8bd21d0abb..9efc75da34 100644 --- a/src/platform_impl/linux/wayland/types/xdg_activation.rs +++ b/src/platform_impl/linux/wayland/types/xdg_activation.rs @@ -80,7 +80,7 @@ impl Dispatch for XdgA state.events_sink.push_window_event( crate::event::WindowEvent::ActivationTokenDone { serial: *serial, - token: ActivationToken::_new(token), + token: ActivationToken::from_raw(token), }, *window_id, ); diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 08e4504a83..83d6f80328 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -168,7 +168,7 @@ impl Window { if let (Some(xdg_activation), Some(token)) = (xdg_activation.as_ref(), attributes.platform_specific.activation_token) { - xdg_activation.activate(token._token, &surface); + xdg_activation.activate(token.token, &surface); } // XXX Do initial commit. diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 8b9b690fb2..4042da0d09 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -531,7 +531,7 @@ impl EventLoop { window_id: crate::window::WindowId(window_id), event: WindowEvent::ActivationTokenDone { serial, - token: crate::window::ActivationToken::_new(token), + token: crate::window::ActivationToken::from_raw(token), }, }; callback(event, &self.event_processor.target) diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 1777f904df..ec1b0420b4 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -559,7 +559,7 @@ impl UnownedWindow { // Remove the startup notification if we have one. if let Some(startup) = window_attrs.platform_specific.activation_token.as_ref() { - leap!(xconn.remove_activation_token(xwindow, &startup._token)); + leap!(xconn.remove_activation_token(xwindow, &startup.token)); } // We never want to give the user a broken window, since by then, it's too late to handle. diff --git a/src/window.rs b/src/window.rs index 9bd002ab4c..f59273ed49 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1850,11 +1850,34 @@ impl Default for ImePurpose { /// [`Window`]: crate::window::Window #[derive(Debug, PartialEq, Eq, Clone)] pub struct ActivationToken { - pub(crate) _token: String, + pub(crate) token: String, } impl ActivationToken { - pub(crate) fn _new(_token: String) -> Self { - Self { _token } + /// Make an [`ActivationToken`] from a string. + /// + /// This method should be used to wrap tokens passed by side channels to your application, like + /// dbus. + /// + /// The validity of the token is ensured by the windowing system. Using the invalid token will + /// only result in the side effect of the operation involving it being ignored (e.g. window + /// won't get focused automatically), but won't yield any errors. + /// + /// To obtain a valid token, use + #[cfg_attr(any(x11_platform, wayland_platform, docsrs), doc = " [`request_activation_token`].")] + #[cfg_attr( + not(any(x11_platform, wayland_platform, docsrs)), + doc = " `request_activation_token`." + )] + /// + #[rustfmt::skip] + /// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token + pub fn from_raw(token: String) -> Self { + Self { token } + } + + /// Convert the token to its string representation to later pass via IPC. + pub fn into_raw(self) -> String { + self.token } } From 58402b58cf660e236e825b36287a8fc2abfb1f5d Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 4 Jan 2025 01:31:08 +0300 Subject: [PATCH 066/116] Winit version 0.30.8 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 10 ---------- src/changelog/v0.30.md | 12 ++++++++++++ src/platform/android.rs | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 33d429f671..d6487655a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.7" +version = "0.30.8" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index 88d7403200..136e75de93 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.7" +winit = "0.30.8" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 84e9e766fc..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,13 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Added - -- `ActivationToken::from_raw` and `ActivationToken::into_raw`. -- On X11, add a workaround for disabling IME on GNOME. - -### Fixed - -- On Windows, fixed the event loop not waking on accessibility requests. -- On X11, fixed cursor grab mode state tracking on error. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index 3ff54b508d..2a791ec0d0 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,15 @@ +## 0.30.8 + +### Added + +- `ActivationToken::from_raw` and `ActivationToken::into_raw`. +- On X11, add a workaround for disabling IME on GNOME. + +### Fixed + +- On Windows, fixed the event loop not waking on accessibility requests. +- On X11, fixed cursor grab mode state tracking on error. + ## 0.30.7 ### Fixed diff --git a/src/platform/android.rs b/src/platform/android.rs index dc3a111201..71c3364559 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.7", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.8", //! 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 From 090498a4a6bf7069344372664fd1451aa64a23a3 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Fri, 17 Jan 2025 12:38:42 +0100 Subject: [PATCH 067/116] Use wrapper type for CFUUID (#4032) This no longer exposes `CGDisplayCreateUUIDFromDisplayID` and instead uses `CFUUID` to avoid a leak. Monitor comparisons should also be more stable now. --- src/platform_impl/macos/monitor.rs | 53 +++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index e78d84f0be..bf7ee708ea 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -6,6 +6,7 @@ use std::fmt; use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex}; use core_foundation::base::{CFRelease, TCFType}; use core_foundation::string::CFString; +use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUID}; use core_graphics::display::{ CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode, }; @@ -100,15 +101,42 @@ impl VideoModeHandle { #[derive(Clone)] pub struct MonitorHandle(CGDirectDisplayID); +type MonitorUuid = [u8; 16]; + +impl MonitorHandle { + /// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle. + fn uuid(&self) -> MonitorUuid { + let cf_uuid = unsafe { + CFUUID::wrap_under_create_rule(ffi::CGDisplayCreateUUIDFromDisplayID(self.0)) + }; + let uuid = unsafe { CFUUIDGetUUIDBytes(cf_uuid.as_concrete_TypeRef()) }; + MonitorUuid::from([ + uuid.byte0, + uuid.byte1, + uuid.byte2, + uuid.byte3, + uuid.byte4, + uuid.byte5, + uuid.byte6, + uuid.byte7, + uuid.byte8, + uuid.byte9, + uuid.byte10, + uuid.byte11, + uuid.byte12, + uuid.byte13, + uuid.byte14, + uuid.byte15, + ]) + } +} + // `CGDirectDisplayID` changes on video mode change, so we cannot rely on that // for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an // unique identifier that persists even across system reboots impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { - unsafe { - ffi::CGDisplayCreateUUIDFromDisplayID(self.0) - == ffi::CGDisplayCreateUUIDFromDisplayID(other.0) - } + self.uuid() == other.uuid() } } @@ -122,18 +150,13 @@ impl PartialOrd for MonitorHandle { impl Ord for MonitorHandle { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - unsafe { - ffi::CGDisplayCreateUUIDFromDisplayID(self.0) - .cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0)) - } + self.uuid().cmp(&other.uuid()) } } impl std::hash::Hash for MonitorHandle { fn hash(&self, state: &mut H) { - unsafe { - ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state); - } + self.uuid().hash(state); } } @@ -296,13 +319,11 @@ impl MonitorHandle { } pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option> { - let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) }; + let uuid = self.uuid(); NSScreen::screens(mtm).into_iter().find(|screen| { let other_native_id = get_display_id(screen); - let other_uuid = unsafe { - ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID) - }; - uuid == other_uuid + let other = MonitorHandle::new(other_native_id); + uuid == other.uuid() }) } } From abfe90bddbc9a831309b3d03da3670e36ed6861b Mon Sep 17 00:00:00 2001 From: Tom Churchman Date: Fri, 17 Jan 2025 17:29:10 +0100 Subject: [PATCH 068/116] wayland: clear IME preedit only when necessary When all we'll be doing is setting a new preedit, the preedit doesn't have to be explicitly cleared first. This change is perhaps debatable. The direct reason for this is to make it easier to work around quirks/bugs: in Masonry we've found IBus appears to resend the IME preedit in response to `Window::set_ime_cursor_area` (`zwp_text_input_v3::set_cursor_rectangle`). Because currently the preedit is first cleared, a new IME cursor area is sent, which again causes IBus to resend the preedit. This can loop for a while. The Wayland protocol is mechanically quite prescriptive, it says for zwp_text_input_v3:event:done. > 1. Replace existing preedit string with the cursor. > 2. Delete requested surrounding text. > 3. Insert commit string with the cursor at its end. > 4. Calculate surrounding text to send. > 5. Insert new preedit text in cursor position. > 6. Place cursor inside preedit text. Winit currently doesn't do surrounding text, so 2. and 4. can be ignored. In Winit's IME model, without a commit, sending just the `Ime::Preedit` event without explicitly clearing is arguably still equivalent to doing 1., 5., and 6. --- src/changelog/unreleased.md | 4 ++++ .../linux/wayland/seat/text_input/mod.rs | 14 +++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..fbcee62140 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Changed + +- On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`. diff --git a/src/platform_impl/linux/wayland/seat/text_input/mod.rs b/src/platform_impl/linux/wayland/seat/text_input/mod.rs index 49d9363597..db724893c5 100644 --- a/src/platform_impl/linux/wayland/seat/text_input/mod.rs +++ b/src/platform_impl/linux/wayland/seat/text_input/mod.rs @@ -121,11 +121,15 @@ impl Dispatch for TextInputState { None => return, }; - // Clear preedit at the start of `Done`. - state.events_sink.push_window_event( - WindowEvent::Ime(Ime::Preedit(String::new(), None)), - window_id, - ); + // Clear preedit, unless all we'll be doing next is sending a new preedit. + if text_input_data.pending_commit.is_some() + || text_input_data.pending_preedit.is_none() + { + state.events_sink.push_window_event( + WindowEvent::Ime(Ime::Preedit(String::new(), None)), + window_id, + ); + } // Send `Commit`. if let Some(text) = text_input_data.pending_commit.take() { From 3154c60ef442d54bc910f96852538b0e2c55dac9 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 31 Jan 2025 18:24:33 +0100 Subject: [PATCH 069/116] Document that we require cargo +nightly fmt (#4105) --- CONTRIBUTING.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74351b2785..dfcb93a323 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,6 +19,11 @@ All patches have to be sent on Github as [pull requests][prs]. To simplify your life during review it's recommended to check the "give contributors write access to the branch" checkbox. +We use unstable Rustfmt options across the project, so please run +`cargo +nightly fmt` before submitting your work. If you are unable to do so, +the maintainers can do it for you before merging, just state so in your pull +request description. + #### Handling review During the review process certain events could require an action from your side, From b77ea7d218602c026c174fbb27d182bd90d621c0 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 3 Feb 2025 20:43:43 +0300 Subject: [PATCH 070/116] x11: fix crash with uim Let's just not forward events to the IME once the user requested that it should be disabled, though, still try to change its state explicitly. Fixes #4082. --- src/changelog/unreleased.md | 4 +++ src/platform_impl/linux/x11/ime/callbacks.rs | 8 ++---- src/platform_impl/linux/x11/ime/context.rs | 25 +++++++++-------- .../linux/x11/ime/input_method.rs | 4 +-- src/platform_impl/linux/x11/ime/mod.rs | 27 +++++-------------- 5 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index fbcee62140..38c723927f 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -43,3 +43,7 @@ changelog entry. ### Changed - On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`. + +### Fixed + +- On X11, fixed crash with uim diff --git a/src/platform_impl/linux/x11/ime/callbacks.rs b/src/platform_impl/linux/x11/ime/callbacks.rs index c75724ec83..fa0fa5c6fc 100644 --- a/src/platform_impl/linux/x11/ime/callbacks.rs +++ b/src/platform_impl/linux/x11/ime/callbacks.rs @@ -123,19 +123,15 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> { let is_allowed = old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default(); - // We can't use the style from the old context here, since it may change on reload, so - // pick style from the new XIM based on the old state. - let style = if is_allowed { new_im.preedit_style } else { new_im.none_style }; - let new_context = { let result = unsafe { ImeContext::new( xconn, - new_im.im, - style, + &new_im, *window, spot, (*inner).event_sender.clone(), + is_allowed, ) }; if result.is_err() { diff --git a/src/platform_impl/linux/x11/ime/context.rs b/src/platform_impl/linux/x11/ime/context.rs index 89a241cce3..e90836a5f2 100644 --- a/src/platform_impl/linux/x11/ime/context.rs +++ b/src/platform_impl/linux/x11/ime/context.rs @@ -5,10 +5,9 @@ use std::{mem, ptr}; use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct}; -use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle}; -use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender}; - use super::{ffi, util, XConnection, XError}; +use crate::platform_impl::platform::x11::ime::input_method::{InputMethod, Style, XIMStyle}; +use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender}; /// IME creation error. #[derive(Debug)] @@ -184,7 +183,7 @@ struct ImeContextClientData { pub struct ImeContext { pub(crate) ic: ffi::XIC, pub(crate) ic_spot: ffi::XPoint, - pub(crate) style: Style, + pub(crate) allowed: bool, // Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free // from there we keep the pointer to automatically deallocate it. _client_data: Box, @@ -193,11 +192,11 @@ pub struct ImeContext { impl ImeContext { pub(crate) unsafe fn new( xconn: &Arc, - im: ffi::XIM, - style: Style, + im: &InputMethod, window: ffi::Window, ic_spot: Option, event_sender: ImeEventSender, + allowed: bool, ) -> Result { let client_data = Box::into_raw(Box::new(ImeContextClientData { window, @@ -206,20 +205,24 @@ impl ImeContext { cursor_pos: 0, })); + let style = if allowed { im.preedit_style } else { im.none_style }; + let ic = match style as _ { Style::Preedit(style) => unsafe { ImeContext::create_preedit_ic( xconn, - im, + im.im, style, window, client_data as ffi::XPointer, ) }, Style::Nothing(style) => unsafe { - ImeContext::create_nothing_ic(xconn, im, style, window) + ImeContext::create_nothing_ic(xconn, im.im, style, window) + }, + Style::None(style) => unsafe { + ImeContext::create_none_ic(xconn, im.im, style, window) }, - Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) }, } .ok_or(ImeContextCreationError::Null)?; @@ -228,7 +231,7 @@ impl ImeContext { let mut context = ImeContext { ic, ic_spot: ffi::XPoint { x: 0, y: 0 }, - style, + allowed, _client_data: unsafe { Box::from_raw(client_data) }, }; @@ -335,7 +338,7 @@ impl ImeContext { } pub fn is_allowed(&self) -> bool { - !matches!(self.style, Style::None(_)) + self.allowed } // Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks diff --git a/src/platform_impl/linux/x11/ime/input_method.rs b/src/platform_impl/linux/x11/ime/input_method.rs index 7f147bf1c4..b9d3ca7101 100644 --- a/src/platform_impl/linux/x11/ime/input_method.rs +++ b/src/platform_impl/linux/x11/ime/input_method.rs @@ -81,9 +81,7 @@ impl InputMethod { } let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap()); - // Always initialize none style even when it's not advertised, since it seems to work - // regardless... - let none_style = none_style.unwrap_or(Style::None(XIM_NONE_STYLE)); + let none_style = none_style.unwrap_or(preedit_style); Some(InputMethod { im, _name: name, preedit_style, none_style }) } diff --git a/src/platform_impl/linux/x11/ime/mod.rs b/src/platform_impl/linux/x11/ime/mod.rs index 063598a3ea..0a419c8d50 100644 --- a/src/platform_impl/linux/x11/ime/mod.rs +++ b/src/platform_impl/linux/x11/ime/mod.rs @@ -10,15 +10,13 @@ use std::sync::Arc; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use tracing::debug; - -use super::{ffi, util, XConnection, XError}; use self::callbacks::*; use self::context::ImeContext; pub use self::context::ImeContextCreationError; use self::inner::{close_im, ImeInner}; -use self::input_method::{PotentialInputMethods, Style}; +use self::input_method::PotentialInputMethods; +use super::{ffi, util, XConnection, XError}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -114,39 +112,26 @@ impl Ime { pub fn create_context( &mut self, window: ffi::Window, - with_preedit: bool, + with_ime: bool, ) -> Result { let context = if self.is_destroyed() { // Create empty entry in map, so that when IME is rebuilt, this window has a context. None } else { let im = self.inner.im.as_ref().unwrap(); - let style = if with_preedit { im.preedit_style } else { im.none_style }; let context = unsafe { ImeContext::new( &self.inner.xconn, - im.im, - style, + im, window, None, self.inner.event_sender.clone(), + with_ime, )? }; - // Check the state on the context, since it could fail to enable or disable preedit. - let event = if matches!(style, Style::None(_)) { - if with_preedit { - debug!("failed to create IME context with preedit support.") - } - ImeEvent::Disabled - } else { - if !with_preedit { - debug!("failed to create IME context without preedit support.") - } - ImeEvent::Enabled - }; - + let event = if context.is_allowed() { ImeEvent::Enabled } else { ImeEvent::Disabled }; self.inner.event_sender.send((window, event)).expect("Failed to send enabled event"); Some(context) From 487137b867e37d0d4b76a37c02fc57281eac522c Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 5 Feb 2025 09:04:09 +0300 Subject: [PATCH 071/116] x11: fix modifiers replay The serial was not unique, thus leading to issues and replay being triggered for normal input. Track modifiers based on they keycodes instead, since it's more unique. Links: https://github.com/alacritty/alacritty/issues/8461 --- src/changelog/unreleased.md | 3 ++- src/platform_impl/linux/x11/event_processor.rs | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 38c723927f..afe1edb60e 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -46,4 +46,5 @@ changelog entry. ### Fixed -- On X11, fixed crash with uim +- On X11, fix crash with uim. +- On X11, fix modifiers for keys that were sent by the same X11 request. diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 438c808b8d..79f5c114f0 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -66,7 +66,10 @@ pub struct EventProcessor { pub active_window: Option, /// Latest modifiers we've sent for the user to trigger change in event. pub modifiers: Cell, - pub xfiltered_modifiers: VecDeque, + // Track modifiers based on keycodes. NOTE: that serials generally don't work for tracking + // since they are not unique and could be duplicated in case of sequence of key events is + // delivered at near the same time. + pub xfiltered_modifiers: VecDeque, pub xmodmap: util::ModifierKeymap, pub is_composing: bool, } @@ -163,13 +166,11 @@ impl EventProcessor { 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 - // when the modifiers are consumed entirely or serials are altered. - // - // Both cases shouldn't happen in well behaving clients. + // when the modifiers are consumed entirely. if self.xfiltered_modifiers.len() == MAX_MOD_REPLAY_LEN { self.xfiltered_modifiers.pop_back(); } - self.xfiltered_modifiers.push_front(xev.serial); + self.xfiltered_modifiers.push_front(xev.keycode as u8); } } @@ -950,7 +951,7 @@ impl EventProcessor { // itself are out of sync due to XkbState being delivered before XKeyEvent, since it's // being replayed by the XIM, thus we should replay ourselves. let replay = if let Some(position) = - self.xfiltered_modifiers.iter().rev().position(|&s| s == xev.serial) + self.xfiltered_modifiers.iter().rev().position(|&s| s == xev.keycode as u8) { // We don't have to replay modifiers pressed before the current event if some events // were not forwarded to us, since their state is irrelevant. From 501d9b4a4433015974ddb35a448ab2898bb9b733 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 6 Feb 2025 10:56:10 +0100 Subject: [PATCH 072/116] ios: fix timers Fixes #4074. Fixes #3816. --- src/changelog/unreleased.md | 1 + src/platform_impl/ios/app_state.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index afe1edb60e..460d62f69e 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -48,3 +48,4 @@ changelog entry. - On X11, fix crash with uim. - On X11, fix modifiers for keys that were sent by the same X11 request. +- On iOS, fix high CPU usage even when using `ControlFlow::Wait`. diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 30019a0701..e34bf43c97 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -375,6 +375,7 @@ impl AppState { (ControlFlow::Wait, ControlFlow::Wait) => { let start = Instant::now(); self.set_state(AppStateImpl::Waiting { waiting_handler, start }); + self.waker.stop() }, (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) if old_instant == new_instant => From 1ae4f5cdeab1231d14b24bc515dc14ef2ee69ece Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 5 Feb 2025 14:58:57 +0300 Subject: [PATCH 073/116] Winit version 0.30.9 --- .github/workflows/ci.yml | 7 ++++++- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 10 ---------- src/changelog/v0.30.md | 12 ++++++++++++ src/platform/android.rs | 2 +- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 955344727f..f2e85a932c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,7 +107,12 @@ jobs: - name: Generate lockfile # Also updates the crates.io index - run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0 + run: | + cargo generate-lockfile + cargo update -p ahash --precise 0.8.7 + cargo update -p bumpalo --precise 3.14.0 + cargo update -p objc2-encode --precise 4.0.3 + cargo update -p orbclient --precise 0.3.47 - name: Install GCC Multilib if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') diff --git a/Cargo.toml b/Cargo.toml index d6487655a0..19e53ed603 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.8" +version = "0.30.9" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index 136e75de93..966e36e5e1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.8" +winit = "0.30.9" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 460d62f69e..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,13 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Changed - -- On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`. - -### Fixed - -- On X11, fix crash with uim. -- On X11, fix modifiers for keys that were sent by the same X11 request. -- On iOS, fix high CPU usage even when using `ControlFlow::Wait`. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index 2a791ec0d0..1abc1e8af2 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,15 @@ +## 0.30.9 + +### Changed + +- On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`. + +### Fixed + +- On X11, fix crash with uim. +- On X11, fix modifiers for keys that were sent by the same X11 request. +- On iOS, fix high CPU usage even when using `ControlFlow::Wait`. + ## 0.30.8 ### Added diff --git a/src/platform/android.rs b/src/platform/android.rs index 71c3364559..55aa05561c 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.8", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.9", //! 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 From 37a4394a3e2c119a5de501e79daf700ef2232d30 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Mar 2025 23:50:15 +0100 Subject: [PATCH 074/116] Remove apple/appkit/window.rs Accidentally added in 485ae90aaea5cd1d316168bea714bee05bd3e140. --- src/platform_impl/apple/appkit/window.rs | 369 ----------------------- 1 file changed, 369 deletions(-) delete mode 100644 src/platform_impl/apple/appkit/window.rs diff --git a/src/platform_impl/apple/appkit/window.rs b/src/platform_impl/apple/appkit/window.rs deleted file mode 100644 index be59dbed99..0000000000 --- a/src/platform_impl/apple/appkit/window.rs +++ /dev/null @@ -1,369 +0,0 @@ -#![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) - } -} From 1db15b68759bc1209a471b969707115d7eed90a5 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Thu, 20 Feb 2025 20:21:07 +0300 Subject: [PATCH 075/116] chore: fix clippy lints --- src/platform_impl/linux/mod.rs | 4 ++-- src/platform_impl/linux/x11/ime/input_method.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index c4670ce09d..362573f7e9 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -768,9 +768,9 @@ impl EventLoop { // Create the display based on the backend. match backend { #[cfg(wayland_platform)] - Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into), + Backend::Wayland => EventLoop::new_wayland_any_thread(), #[cfg(x11_platform)] - Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into), + Backend::X => EventLoop::new_x11_any_thread(), } } diff --git a/src/platform_impl/linux/x11/ime/input_method.rs b/src/platform_impl/linux/x11/ime/input_method.rs index b9d3ca7101..e1f2fbd2f6 100644 --- a/src/platform_impl/linux/x11/ime/input_method.rs +++ b/src/platform_impl/linux/x11/ime/input_method.rs @@ -176,7 +176,7 @@ unsafe fn get_xim_servers(xconn: &Arc) -> Result, GetXi ) .map_err(GetXimServersError::GetPropertyError)? .into_iter() - .map(ffi::Atom::from) + .map(|atom| atom as _) .collect::>(); let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len()); From b9b2f1643e90e0abfd339d6b5fd437c5deb9edb5 Mon Sep 17 00:00:00 2001 From: rctlmk Date: Thu, 27 Feb 2025 00:02:32 +0300 Subject: [PATCH 076/116] Windows: add `IconExtWindows::from_resource_name` (#4137) --- src/changelog/unreleased.md | 4 ++ src/platform/windows.rs | 72 ++++++++++++++++++++++++++++++- src/platform_impl/windows/icon.rs | 17 +++++++- 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..52d4636bfb 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Added + +- On Windows, add `IconExtWindows::from_resource_name`. diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 5b79771a91..e4593be10d 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -660,6 +660,17 @@ impl DeviceIdExtWindows for DeviceId { } /// Additional methods on `Icon` that are specific to Windows. +/// +/// Windows icons can be created from files, or from the [`embedded resources`](https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files). +/// +/// The `ICON` resource definition statement use the following syntax: +/// ```rc +/// nameID ICON filename +/// ``` +/// `nameID` is a unique name or a 16-bit unsigned integer value identifying the resource, +/// `filename` is the name of the file that contains the resource. +/// +/// More information about the `ICON` resource can be found at [`Microsoft Learn`](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) portal. pub trait IconExtWindows: Sized { /// Create an icon from a file path. /// @@ -671,7 +682,12 @@ pub trait IconExtWindows: Sized { fn from_path>(path: P, size: Option>) -> Result; - /// Create an icon from a resource embedded in this executable or library. + /// Create an icon from a resource embedded in this executable or library by its ordinal id. + /// + /// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an + /// invalid ordinal id, but it can be used with [`from_resource_name`] as `"0"`. + /// + /// [`from_resource_name`]: IconExtWindows::from_resource_name /// /// Specify `size` to load a specific icon size from the file, or `None` to load the default /// icon size from the file. @@ -679,6 +695,55 @@ pub trait IconExtWindows: Sized { /// In cases where the specified size does not exist in the file, Windows may perform scaling /// to get an icon of the desired size. fn from_resource(ordinal: u16, size: Option>) -> Result; + + /// Create an icon from a resource embedded in this executable or library by its name. + /// + /// Specify `size` to load a specific icon size from the file, or `None` to load the default + /// icon size from the file. + /// + /// In cases where the specified size does not exist in the file, Windows may perform scaling + /// to get an icon of the desired size. + /// + /// # Notes + /// + /// Consider the following resource definition statements: + /// ```rc + /// app ICON "app.ico" + /// 1 ICON "a.ico" + /// 0027 ICON "custom.ico" + /// 0 ICON "alt.ico" + /// ``` + /// + /// Due to some internal implementation details of the resource embedding/loading process on + /// Windows platform, strings that can be interpreted as 16-bit unsigned integers (`"1"`, + /// `"002"`, etc.) cannot be used as valid resource names, and instead should be passed into + /// [`from_resource`]: + /// + /// [`from_resource`]: IconExtWindows::from_resource + /// + /// ```rust,no_run + /// use winit::platform::windows::IconExtWindows; + /// use winit::window::Icon; + /// + /// assert!(Icon::from_resource_name("app", None).is_ok()); + /// assert!(Icon::from_resource(1, None).is_ok()); + /// assert!(Icon::from_resource(27, None).is_ok()); + /// assert!(Icon::from_resource_name("27", None).is_err()); + /// assert!(Icon::from_resource_name("0027", None).is_err()); + /// ``` + /// + /// While `0` cannot be used as an ordinal id (see [`from_resource`]), it can be used as a + /// name: + /// + /// [`from_resource`]: IconExtWindows::from_resource + /// + /// ```rust,no_run + /// # use winit::platform::windows::IconExtWindows; + /// # use winit::window::Icon; + /// assert!(Icon::from_resource_name("0", None).is_ok()); + /// assert!(Icon::from_resource(0, None).is_err()); + /// ``` + fn from_resource_name(name: &str, size: Option>) -> Result; } impl IconExtWindows for Icon { @@ -694,4 +759,9 @@ impl IconExtWindows for Icon { let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?; Ok(Icon { inner: win_icon }) } + + fn from_resource_name(name: &str, size: Option>) -> Result { + let win_icon = crate::platform_impl::WinIcon::from_resource_name(name, size)?; + Ok(Icon { inner: win_icon }) + } } diff --git a/src/platform_impl/windows/icon.rs b/src/platform_impl/windows/icon.rs index 624c8c8279..4e5fe69e1a 100644 --- a/src/platform_impl/windows/icon.rs +++ b/src/platform_impl/windows/icon.rs @@ -109,13 +109,28 @@ impl WinIcon { pub fn from_resource( resource_id: u16, size: Option>, + ) -> Result { + Self::from_resource_ptr(resource_id as PCWSTR, size) + } + + pub fn from_resource_name( + resource_name: &str, + size: Option>, + ) -> Result { + let wide_name = util::encode_wide(resource_name); + Self::from_resource_ptr(wide_name.as_ptr(), size) + } + + fn from_resource_ptr( + resource: PCWSTR, + size: Option>, ) -> Result { // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size let (width, height) = size.map(Into::into).unwrap_or((0, 0)); let handle = unsafe { LoadImageW( util::get_instance_handle(), - resource_id as PCWSTR, + resource, IMAGE_ICON, width, height, From ec7677d6929ba4c0a5e053efcaf97b79f95f865f Mon Sep 17 00:00:00 2001 From: aloucks Date: Sun, 16 Mar 2025 21:27:27 -0400 Subject: [PATCH 077/116] Fix a pause in the event loop when clicking the title bar on windows (#4136) * Fix a pause in the event loop when clicking the title bar on windows When clicking the title bar on Windows, to drag the window, there is a noticible pause in continuous redraw requests. This was fixed in #839 and then regressed in #1852. The cursor blinks in both cases and is unrelated. The regression made the blink happen after the pause instead of immediately. * Update the event loop pause note on the WM_NCLBUTTONDOWN handler The application example was also updated to optionally animate the fill color in order to demonstrate continuous redraw without pauses in the event loop. --- src/changelog/unreleased.md | 4 ++++ src/platform_impl/windows/event_loop.rs | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 52d4636bfb..38650e80db 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -43,3 +43,7 @@ changelog entry. ### Added - On Windows, add `IconExtWindows::from_resource_name`. + +### Fixed + +- On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index fa912f6ce0..1fe748c67e 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1216,6 +1216,31 @@ unsafe fn public_window_callback_inner( WM_NCLBUTTONDOWN => { if wparam == HTCAPTION as _ { + // Prevent the user event loop from pausing when left clicking the title bar. + // + // When the user interacts with the title bar, Windows enters the modal event + // loop. Currently, a left click causes a pause for about 500ms. Sending a dummy + // mouse-move event seems to cancel the modal loop early, preventing the pause. + // The application will never see this dummy event. + // + // The mouse coordinates are encoded into the lparam value, however the WM_MOUSEMOVE + // event is not using the same coordinate system of the WM_NCLBUTTONDOWN event. + // One uses client-area coordinates and the other is screen-coordinates. In any + // case, passing the lparam as-is with the dummy event does not seem the cancel + // the modal loop. + // + // However, passing in a value of 0 has been observed to always cancel the pause. + // + // Other notes: + // + // For some unknown reason, the cursor will blink when clicking the title bar. + // Cancelling the modal loop early causes the blink to happen *immediately*. + // Otherwise, the blank happens *after* the pause. + // + // When right-click the title bar, the system window menu is presented to the user, + // and the modal event loop begins. This dummy event does *not* prevent the freeze + // in the main event loop caused by that popup menu. + let lparam = 0; unsafe { PostMessageW(window, WM_MOUSEMOVE, 0, lparam) }; } result = ProcResult::DefWindowProc(wparam); From c591089ece1a952f5020723ccf1a34f9b1ecfae0 Mon Sep 17 00:00:00 2001 From: aloucks Date: Sun, 16 Mar 2025 21:58:47 -0400 Subject: [PATCH 078/116] macOS: Make set_simple_fullscreen honor set_borderless_game (#4164) * Prevent panic when calling set_simple_fullscreen(false) on macos Calling `set_simple_fullscreen(false)` to restore the window after a previous call to `set_simple_fullscreen(true)` panics with `view must be installed in a window` in the call to `set_style_mask` with the old style. Moving the `set_style_mask` call after the frame has been resized fixes the issue. * Hide the doc and menubar on macos when using set_borderless_game with set_simple_fullscreen --- src/changelog/unreleased.md | 1 + src/platform/macos.rs | 4 +++- src/platform_impl/macos/window_delegate.rs | 16 +++++++++------- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 38650e80db..5878630451 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -47,3 +47,4 @@ changelog entry. ### Fixed - On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw. +- On macos, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game` diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 424c9d6213..7d4b8ca348 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -95,7 +95,9 @@ 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. + /// Disable the Menu Bar and Dock in Simple or Borderless Fullscreen mode. Useful for games. + /// The effect is applied when [`WindowExtMacOS::set_simple_fullscreen`] or + /// [`Window::set_fullscreen`] is called. fn set_borderless_game(&self, borderless_game: bool); /// Getter for the [`WindowExtMacOS::set_borderless_game`]. diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 19dc605e09..8982d40880 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1746,9 +1746,13 @@ impl WindowExtMacOS for WindowDelegate { self.ivars().is_simple_fullscreen.set(true); // Simulate pre-Lion fullscreen by hiding the dock and menu bar - let presentation_options = + let presentation_options = if self.is_borderless_game() { + NSApplicationPresentationOptions::NSApplicationPresentationHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar + } else { NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock - | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar; + | NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar + }; app.setPresentationOptions(presentation_options); // Hide the titlebar @@ -1762,11 +1766,8 @@ impl WindowExtMacOS for WindowDelegate { self.toggle_style_mask(NSWindowStyleMask::Miniaturizable, false); self.toggle_style_mask(NSWindowStyleMask::Resizable, false); self.window().setMovable(false); - - true } else { let new_mask = self.saved_style(); - self.set_style_mask(new_mask); self.ivars().is_simple_fullscreen.set(false); let save_presentation_opts = self.ivars().save_presentation_opts.get(); @@ -1778,9 +1779,10 @@ impl WindowExtMacOS for WindowDelegate { self.window().setFrame_display(frame, true); self.window().setMovable(true); - - true + self.set_style_mask(new_mask); } + + true } #[inline] From c6cfa048b0157c1f0b8c7ba976ff969f8f7a86e7 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 17 Mar 2025 13:20:17 +0300 Subject: [PATCH 079/116] x11:wayland: fix pump_events blocking with `Wait` Using `Duration::Zero` with `Wait` polling mode was still blocking until the event was actually delivered. Thus when `pump_events` API is used, ensure that it's not happening. Fixes #4130. --- src/changelog/unreleased.md | 1 + src/platform_impl/linux/wayland/event_loop/mod.rs | 5 ++++- src/platform_impl/linux/x11/mod.rs | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 5878630451..a75ff6941d 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -48,3 +48,4 @@ changelog entry. - On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw. - On macos, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game` +- On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index f379841a6a..b4c69b0f5c 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -285,7 +285,10 @@ impl EventLoop { // Reduce spurious wake-ups. let dispatched_events = self.with_state(|state| state.dispatched_events); - if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events { + if matches!(cause, StartCause::WaitCancelled { .. }) + && !dispatched_events + && timeout.is_none() + { continue; } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 4042da0d09..6d3f931151 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -497,6 +497,7 @@ impl EventLoop { // If we don't have any pending `_receiver` if !self.has_pending() && !matches!(&cause, StartCause::ResumeTimeReached { .. } | StartCause::Poll) + && timeout.is_none() { return; } From aaecc92b628b1a36707563972a1f4f2781a3cfd4 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 6 Apr 2025 15:41:46 +0900 Subject: [PATCH 080/116] chore: fix clippy issues --- src/event.rs | 2 +- src/lib.rs | 4 +++- src/platform/pump_events.rs | 20 ++++++++++---------- src/platform/run_on_demand.rs | 2 +- src/platform_impl/ios/view.rs | 6 +++--- src/platform_impl/linux/x11/activation.rs | 2 +- src/platform_impl/linux/x11/mod.rs | 20 ++++++++++---------- src/platform_impl/linux/x11/xdisplay.rs | 2 +- src/platform_impl/orbital/window.rs | 2 +- src/window.rs | 8 ++++---- 10 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/event.rs b/src/event.rs index 1d14259de4..1890aea97d 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1152,7 +1152,7 @@ mod tests { #[test] fn ensure_attrs_do_not_panic() { foreach_event!(|event: event::Event<()>| { - let _ = format!("{:?}", event); + let _ = format!("{event:?}"); }); let _ = event::StartCause::Init.clone(); diff --git a/src/lib.rs b/src/lib.rs index ba3e7cd179..9743e49990 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -165,7 +165,6 @@ //! [`Window`]: window::Window //! [`WindowId`]: window::WindowId //! [`WindowAttributes`]: window::WindowAttributes -//! [window_new]: window::Window::new //! [`create_window`]: event_loop::ActiveEventLoop::create_window //! [`Window::id()`]: window::Window::id //! [`WindowEvent`]: event::WindowEvent @@ -185,6 +184,9 @@ // doc #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))] #![allow(clippy::missing_safety_doc)] +#![warn(clippy::uninlined_format_args)] +// TODO: wasm-binding needs to be updated for that to be resolved, for now just silence it. +#![cfg_attr(web_platform, allow(unknown_lints, wasm_c_abi))] #[cfg(feature = "rwh_04")] pub use rwh_04 as raw_window_handle_04; diff --git a/src/platform/pump_events.rs b/src/platform/pump_events.rs index dbe14f6fac..4476635356 100644 --- a/src/platform/pump_events.rs +++ b/src/platform/pump_events.rs @@ -51,19 +51,19 @@ pub trait EventLoopExtPumpEvents { /// buffered and handled outside of Winit include: /// - `RedrawRequested` events, used to schedule rendering. /// - /// macOS for example uses a `drawRect` callback to drive rendering - /// within applications and expects rendering to be finished before - /// the `drawRect` callback returns. + /// macOS for example uses a `drawRect` callback to drive rendering + /// within applications and expects rendering to be finished before + /// the `drawRect` callback returns. /// - /// For portability it's strongly recommended that applications should - /// keep their rendering inside the closure provided to Winit. + /// For portability it's strongly recommended that applications should + /// keep their rendering inside the closure provided to Winit. /// - Any lifecycle events, such as `Suspended` / `Resumed`. /// - /// The handling of these events needs to be synchronized with the - /// operating system and it would never be appropriate to buffer a - /// notification that your application has been suspended or resumed and - /// then handled that later since there would always be a chance that - /// other lifecycle events occur while the event is buffered. + /// The handling of these events needs to be synchronized with the + /// operating system and it would never be appropriate to buffer a + /// notification that your application has been suspended or resumed and + /// then handled that later since there would always be a chance that + /// other lifecycle events occur while the event is buffered. /// /// ## Supported Platforms /// diff --git a/src/platform/run_on_demand.rs b/src/platform/run_on_demand.rs index 821e87914b..0bb96e92ed 100644 --- a/src/platform/run_on_demand.rs +++ b/src/platform/run_on_demand.rs @@ -63,8 +63,8 @@ pub trait EventLoopExtRunOnDemand { /// are delivered via callbacks based on an event loop that is internal to the browser itself. /// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS. #[cfg_attr(not(web_platform), doc = "[^1]: `spawn()` is only available on `wasm` platforms.")] - #[rustfmt::skip] /// + #[rustfmt::skip] /// [`exit()`]: ActiveEventLoop::exit() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() fn run_app_on_demand>( diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 9be43e4130..418968c70c 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -190,7 +190,7 @@ declare_class!( // Pass -delta so that action is reversed (TouchPhase::Cancelled, -recognizer.scale()) } - state => panic!("unexpected recognizer state: {:?}", state), + state => panic!("unexpected recognizer state: {state:?}"), }; let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent { @@ -249,7 +249,7 @@ declare_class!( // Pass -delta so that action is reversed (TouchPhase::Cancelled, -recognizer.rotation()) } - state => panic!("unexpected recognizer state: {:?}", state), + state => panic!("unexpected recognizer state: {state:?}"), }; // Make delta negative to match macos, convert to degrees @@ -300,7 +300,7 @@ declare_class!( // Pass -delta so that action is reversed (TouchPhase::Cancelled, -last_pan.x, -last_pan.y) } - state => panic!("unexpected recognizer state: {:?}", state), + state => panic!("unexpected recognizer state: {state:?}"), }; diff --git a/src/platform_impl/linux/x11/activation.rs b/src/platform_impl/linux/x11/activation.rs index 5f83e1c0a7..8f4aa7962d 100644 --- a/src/platform_impl/linux/x11/activation.rs +++ b/src/platform_impl/linux/x11/activation.rs @@ -172,7 +172,7 @@ fn push_display(buffer: &mut Vec, display: &impl std::fmt::Display) { } } - write!(Writer { buffer }, "{}", display).unwrap(); + write!(Writer { buffer }, "{display}").unwrap(); } #[cfg(test)] diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 6d3f931151..f29b314a7c 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -850,24 +850,24 @@ pub enum X11Error { impl fmt::Display for X11Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - X11Error::Xlib(e) => write!(f, "Xlib error: {}", e), - X11Error::Connect(e) => write!(f, "X11 connection error: {}", e), - X11Error::Connection(e) => write!(f, "X11 connection error: {}", e), - X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e), - X11Error::GetProperty(e) => write!(f, "Failed to get X property {}", e), - X11Error::X11(e) => write!(f, "X11 error: {:?}", e), - X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s), + X11Error::Xlib(e) => write!(f, "Xlib error: {e}"), + X11Error::Connect(e) => write!(f, "X11 connection error: {e}"), + X11Error::Connection(e) => write!(f, "X11 connection error: {e}"), + X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {e}"), + X11Error::GetProperty(e) => write!(f, "Failed to get X property {e}"), + X11Error::X11(e) => write!(f, "X11 error: {e:?}"), + X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {s}"), X11Error::InvalidActivationToken(s) => write!( f, "Invalid activation token: {}", std::str::from_utf8(s).unwrap_or("") ), - X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {}", s), + X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {s}"), X11Error::NoSuchVisual(visualid) => { - write!(f, "Could not find a matching X11 visual for ID `{:x}`", visualid) + write!(f, "Could not find a matching X11 visual for ID `{visualid:x}`") }, X11Error::XsettingsParse(err) => { - write!(f, "Failed to parse xsettings: {:?}", err) + write!(f, "Failed to parse xsettings: {err:?}") }, } } diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index 0a56f290a4..902d0ce4cb 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -145,7 +145,7 @@ impl XConnection { fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option { // Fetch the _XSETTINGS_S[screen number] atom. let xsettings_screen = xcb - .intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes()) + .intern_atom(false, format!("_XSETTINGS_S{default_screen}").as_bytes()) .ok()? .reply() .ok()? diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index d90d6a4584..3e676c3656 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -420,7 +420,7 @@ impl Window { window::ResizeDirection::West => "L", }; self.window_socket - .write(format!("D,{}", arg).as_bytes()) + .write(format!("D,{arg}").as_bytes()) .map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?; Ok(()) } diff --git a/src/window.rs b/src/window.rs index f59273ed49..fa861dc272 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1864,14 +1864,14 @@ impl ActivationToken { /// won't get focused automatically), but won't yield any errors. /// /// To obtain a valid token, use - #[cfg_attr(any(x11_platform, wayland_platform, docsrs), doc = " [`request_activation_token`].")] + #[cfg_attr( + any(x11_platform, wayland_platform, docsrs), + doc = " [`request_activation_token`](crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token)." + )] #[cfg_attr( not(any(x11_platform, wayland_platform, docsrs)), doc = " `request_activation_token`." )] - /// - #[rustfmt::skip] - /// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token pub fn from_raw(token: String) -> Self { Self { token } } From 114599c2dac2b21ad94d7404591c713c148a3c1f Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 20 Apr 2025 19:47:11 +0900 Subject: [PATCH 081/116] wayland/fix: crash due consequent calls to set_cursor_grab Only mark that the grab was applied when it actually got applied. Previously there was an issue with grab being marked as applied without a pointer over the window, when in reality it wasn't. Fixes #4073. --- src/changelog/unreleased.md | 1 + .../linux/wayland/window/state.rs | 37 ++++++++++++++----- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index a75ff6941d..27d8a14471 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -49,3 +49,4 @@ changelog entry. - On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw. - On macos, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game` - On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode +- On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus. diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index 7685b85952..130bc69418 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -222,9 +222,9 @@ impl WindowState { } /// Apply closure on the given pointer. - fn apply_on_pointer, &WinitPointerData)>( + fn apply_on_pointer, &WinitPointerData)>( &self, - callback: F, + mut callback: F, ) { self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| { let data = pointer.pointer().winit_data(); @@ -827,34 +827,51 @@ impl WindowState { None => return Err(ExternalError::NotSupported(NotSupportedError::new())), }; - // Replace the current mode. - let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode); - - match old_mode { - CursorGrabMode::None => (), + let mut unset_old = false; + match self.cursor_grab_mode.current_grab_mode { + CursorGrabMode::None => unset_old = true, CursorGrabMode::Confined => self.apply_on_pointer(|_, data| { data.unconfine_pointer(); + unset_old = true; }), CursorGrabMode::Locked => { - self.apply_on_pointer(|_, data| data.unlock_pointer()); + self.apply_on_pointer(|_, data| { + data.unlock_pointer(); + unset_old = true; + }); }, } + // In case we haven't unset the old mode, it means that we don't have a cursor above + // the window, thus just wait for it to re-appear. + if !unset_old { + return Ok(()); + } + + let mut set_mode = false; let surface = self.window.wl_surface(); match mode { CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| { let pointer = pointer.pointer(); - data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle) + data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle); + set_mode = true; }), CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| { let pointer = pointer.pointer(); - data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle) + data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle); + set_mode = true; }), CursorGrabMode::None => { // Current lock/confine was already removed. + set_mode = true; }, } + // Replace the current grab mode after we've ensure that it got updated. + if set_mode { + self.cursor_grab_mode.current_grab_mode = mode; + } + Ok(()) } From a224b3de0685434b4838b24b846e577d5357a1ac Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:41:56 +0200 Subject: [PATCH 082/116] windows: add locked cursor --- src/changelog/unreleased.md | 1 + src/platform_impl/windows/util.rs | 15 +++++++++++---- src/platform_impl/windows/window.rs | 13 ++++--------- src/platform_impl/windows/window_state.rs | 18 +++++++++++++++++- src/window.rs | 3 +-- 5 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 27d8a14471..5896142aab 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -43,6 +43,7 @@ changelog entry. ### Added - On Windows, add `IconExtWindows::from_resource_name`. +- On Windows, add `CursorGrabMode::Locked`. ### Fixed diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 87bcbd4256..52912c3c6f 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -7,7 +7,7 @@ use std::{io, mem, ptr}; use crate::utils::Lazy; use windows_sys::core::{HRESULT, PCWSTR}; -use windows_sys::Win32::Foundation::{BOOL, HANDLE, HMODULE, HWND, RECT}; +use windows_sys::Win32::Foundation::{BOOL, HANDLE, HMODULE, HWND, POINT, RECT}; use windows_sys::Win32::Graphics::Gdi::{ClientToScreen, HMONITOR}; use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; use windows_sys::Win32::System::SystemServices::IMAGE_DOS_HEADER; @@ -17,9 +17,9 @@ use windows_sys::Win32::UI::HiDpi::{ use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow; use windows_sys::Win32::UI::Input::Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO}; use windows_sys::Win32::UI::WindowsAndMessaging::{ - ClipCursor, GetClientRect, GetClipCursor, GetSystemMetrics, GetWindowPlacement, GetWindowRect, - IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, - IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT, + ClipCursor, GetClientRect, GetClipCursor, GetCursorPos, GetSystemMetrics, GetWindowPlacement, + GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, + IDC_IBEAM, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_MAXIMIZE, WINDOWPLACEMENT, }; @@ -99,6 +99,13 @@ pub fn set_cursor_hidden(hidden: bool) { } } +pub fn get_cursor_position() -> Result { + unsafe { + let mut point: POINT = mem::zeroed(); + win_to_err(GetCursorPos(&mut point)).map(|_| point) + } +} + pub fn get_cursor_clip() -> Result { unsafe { let mut rect: RECT = mem::zeroed(); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index a8b375c9b5..228c6f03e5 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -428,14 +428,6 @@ impl Window { #[inline] pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { - let confine = match mode { - CursorGrabMode::None => false, - CursorGrabMode::Confined => true, - CursorGrabMode::Locked => { - return Err(ExternalError::NotSupported(NotSupportedError::new())) - }, - }; - let window = self.window; let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel(); @@ -446,7 +438,10 @@ impl Window { .lock() .unwrap() .mouse - .set_cursor_flags(window, |f| f.set(CursorFlags::GRABBED, confine)) + .set_cursor_flags(window, |f| { + f.set(CursorFlags::GRABBED, mode != CursorGrabMode::None); + f.set(CursorFlags::LOCKED, mode == CursorGrabMode::Locked); + }) .map_err(|e| ExternalError::Os(os_error!(e))); let _ = tx.send(result); }); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 911467b4c7..8e24a4f252 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -77,6 +77,7 @@ bitflags! { const GRABBED = 1 << 0; const HIDDEN = 1 << 1; const IN_WINDOW = 1 << 2; + const LOCKED = 1 << 3; } } bitflags! { @@ -485,7 +486,22 @@ impl CursorFlags { if util::is_focused(window) { let cursor_clip = match self.contains(CursorFlags::GRABBED) { true => { - if self.contains(CursorFlags::HIDDEN) { + if self.contains(CursorFlags::LOCKED) { + if let Ok(pos) = util::get_cursor_position() { + Some(RECT { + left: pos.x, + right: pos.x + 1, + top: pos.y, + bottom: pos.y + 1, + }) + } else { + // If lock is applied while the cursor is not available, lock it to the + // middle of the window. + let cx = (client_rect.left + client_rect.right) / 2; + let cy = (client_rect.top + client_rect.bottom) / 2; + Some(RECT { left: cx, right: cx + 1, top: cy, bottom: cy + 1 }) + } + } else if self.contains(CursorFlags::HIDDEN) { // Confine the cursor to the center of the window if the cursor is hidden. // This avoids problems with the cursor activating // the taskbar if the window borders or overlaps that. diff --git a/src/window.rs b/src/window.rs index fa861dc272..0db22bb327 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1707,8 +1707,7 @@ pub enum CursorGrabMode { /// /// ## Platform-specific /// - /// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for - /// now. + /// - **X11:** Not implemented. Always returns [`ExternalError::NotSupported`] for now. /// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`]. Locked, } From 53bbe6c2737469a6a85e5d8d90f4ec39f804c94f Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 29 Apr 2025 11:43:36 +0900 Subject: [PATCH 083/116] wayland: ensure external loop is notified with pump_events Spawn a thread when pump_events is used, so the external thread will get woken-up correctly. This only happens when timeout was given. Fixes #4183. --- Cargo.toml | 8 +- src/changelog/unreleased.md | 1 + .../linux/wayland/event_loop/mod.rs | 115 +++++++++++++++++- 3 files changed, 118 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 19e53ed603..348d3423b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -266,14 +266,14 @@ sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-feature "calloop", ], optional = true } sctk-adwaita = { version = "0.10.1", default-features = false, optional = true } -wayland-backend = { version = "0.3.5", default-features = false, features = [ +wayland-backend = { version = "0.3.9", default-features = false, features = [ "client_system", ], optional = true } -wayland-client = { version = "0.31.4", optional = true } -wayland-protocols = { version = "0.32.2", features = [ +wayland-client = { version = "0.31.8", optional = true } +wayland-protocols = { version = "0.32.6", features = [ "staging", ], optional = true } -wayland-protocols-plasma = { version = "0.3.2", features = [ +wayland-protocols-plasma = { version = "0.3.4", features = [ "client", ], optional = true } x11-dl = { version = "2.19.1", optional = true } diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 5896142aab..94ece67416 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -51,3 +51,4 @@ changelog entry. - On macos, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game` - On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode - On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus. +- On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`. diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index b4c69b0f5c..99b282cb4c 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -4,15 +4,21 @@ use std::cell::{Cell, RefCell}; use std::io::Result as IOResult; use std::marker::PhantomData; use std::mem; +use std::os::fd::OwnedFd; use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd}; use std::rc::Rc; use std::sync::atomic::Ordering; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::JoinHandle; use std::time::{Duration, Instant}; +use calloop::ping::Ping; +use rustix::event::{PollFd, PollFlags}; +use rustix::pipe::{self, PipeFlags}; use sctk::reexports::calloop::Error as CalloopError; use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::reexports::client::{globals, Connection, QueueHandle}; +use tracing::warn; use crate::cursor::OnlyCursorImage; use crate::dpi::LogicalSize; @@ -68,6 +74,8 @@ pub struct EventLoop { // XXX drop after everything else, just to be safe. /// Calloop's event loop. event_loop: calloop::EventLoop<'static, WinitState>, + + pump_event_notifier: Option, } impl EventLoop { @@ -168,6 +176,7 @@ impl EventLoop { p: PlatformActiveEventLoop::Wayland(window_target), _marker: PhantomData, }, + pump_event_notifier: None, }; Ok(event_loop) @@ -223,6 +232,27 @@ impl EventLoop { PumpStatus::Exit(code) } else { + // NOTE: spawn a wake-up thread, thus if we have code reading the wayland connection + // in parallel to winit, we ensure that the loop itself is marked as having events. + if timeout.is_some() && self.pump_event_notifier.is_none() { + let awakener = match &self.window_target.p { + PlatformActiveEventLoop::Wayland(window_target) => { + window_target.event_loop_awakener.clone() + }, + #[cfg(x11_platform)] + PlatformActiveEventLoop::X(_) => unreachable!(), + }; + + self.pump_event_notifier = + Some(PumpEventNotifier::spawn(self.connection.clone(), awakener)); + } + + if let Some(pump_event_notifier) = self.pump_event_notifier.as_ref() { + // Notify that we don't have to wait, since we're out of winit. + *pump_event_notifier.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor; + pump_event_notifier.control.1.notify_one(); + } + PumpStatus::Continue } } @@ -603,7 +633,7 @@ impl AsRawFd for EventLoop { pub struct ActiveEventLoop { /// The event loop wakeup source. - pub event_loop_awakener: calloop::ping::Ping, + pub event_loop_awakener: Ping, /// The main queue used by the event loop. pub queue_handle: QueueHandle, @@ -687,3 +717,84 @@ impl ActiveEventLoop { .into()) } } + +#[derive(Debug)] +struct PumpEventNotifier { + /// Whether we're in winit or not. + control: Arc<(Mutex, Condvar)>, + /// Waker handle for the working thread. + worker_waker: Option, + /// Thread handle. + handle: Option>, +} + +impl Drop for PumpEventNotifier { + fn drop(&mut self) { + // Wake-up the thread. + if let Some(worker_waker) = self.worker_waker.as_ref() { + let _ = rustix::io::write(worker_waker.as_fd(), &[0u8]); + } + *self.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor; + self.control.1.notify_one(); + + if let Some(handle) = self.handle.take() { + let _ = handle.join(); + } + } +} + +impl PumpEventNotifier { + fn spawn(connection: Connection, awakener: Ping) -> Self { + // Start from the waiting state. + let control = Arc::new((Mutex::new(PumpEventNotifierAction::Pause), Condvar::new())); + let control_thread = Arc::clone(&control); + + let (read, write) = match pipe::pipe_with(PipeFlags::CLOEXEC | PipeFlags::NONBLOCK) { + Ok((read, write)) => (read, write), + Err(_) => return Self { control, handle: None, worker_waker: None }, + }; + + let handle = + std::thread::Builder::new().name(String::from("pump_events mon")).spawn(move || { + let (lock, cvar) = &*control_thread; + 'outer: loop { + let mut wait = lock.lock().unwrap(); + while *wait == PumpEventNotifierAction::Pause { + wait = cvar.wait(wait).unwrap(); + } + // Wake-up the main loop and put this one back to sleep. + *wait = PumpEventNotifierAction::Pause; + drop(wait); + + while let Some(read_guard) = connection.prepare_read() { + let _ = connection.flush(); + let poll_fd = PollFd::from_borrowed_fd(connection.as_fd(), PollFlags::IN); + let pipe_poll_fd = PollFd::from_borrowed_fd(read.as_fd(), PollFlags::IN); + // Read from the `fd` before going back to poll. + if Ok(1) == rustix::io::read(read.as_fd(), &mut [0u8; 1]) { + break 'outer; + } + let _ = rustix::event::poll(&mut [poll_fd, pipe_poll_fd], -1); + // Non-blocking read the connection. + let _ = read_guard.read_without_dispatch(); + } + + awakener.ping(); + } + }); + + if let Some(err) = handle.as_ref().err() { + warn!("failed to spawn pump_events wake-up thread: {err}"); + } + + PumpEventNotifier { control, handle: handle.ok(), worker_waker: Some(write) } + } +} + +#[derive(Debug, PartialEq, Eq)] +enum PumpEventNotifierAction { + /// Monitor the wayland queue. + Monitor, + /// Pause monitoring. + Pause, +} From 847511672ae35c78a5d35b34f11047ec9ce271bf Mon Sep 17 00:00:00 2001 From: Putta Khunchalee Date: Tue, 29 Apr 2025 13:31:49 +0900 Subject: [PATCH 084/116] wayland: add WindowExtWayland::xdg_toplevel Fixes #4068. --- src/changelog/unreleased.md | 1 + src/platform/wayland.rs | 24 +++++++++++++++++-- src/platform_impl/linux/wayland/window/mod.rs | 6 +++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 94ece67416..2bbcfdccc5 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -44,6 +44,7 @@ changelog entry. - On Windows, add `IconExtWindows::from_resource_name`. - On Windows, add `CursorGrabMode::Locked`. +- On Wayland, add `WindowExtWayland::xdg_toplevel`. ### Fixed diff --git a/src/platform/wayland.rs b/src/platform/wayland.rs index db6a217dcb..9c1a8e4379 100644 --- a/src/platform/wayland.rs +++ b/src/platform/wayland.rs @@ -13,6 +13,10 @@ //! * `wayland-csd-adwaita` (default). //! * `wayland-csd-adwaita-crossfont`. //! * `wayland-csd-adwaita-notitle`. + +use std::ffi::c_void; +use std::ptr::NonNull; + use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder}; use crate::monitor::MonitorHandle; use crate::window::{Window, WindowAttributes}; @@ -72,9 +76,25 @@ impl EventLoopBuilderExtWayland for EventLoopBuilder { } /// Additional methods on [`Window`] that are specific to Wayland. -pub trait WindowExtWayland {} +/// +/// [`Window`]: crate::window::Window +pub trait WindowExtWayland { + /// Returns `xdg_toplevel` of the window or [`None`] if the window is X11 window. + fn xdg_toplevel(&self) -> Option>; +} -impl WindowExtWayland for Window {} +impl WindowExtWayland for Window { + #[inline] + fn xdg_toplevel(&self) -> Option> { + #[allow(clippy::single_match)] + match &self.window { + #[cfg(x11_platform)] + crate::platform_impl::Window::X(_) => None, + #[cfg(wayland_platform)] + crate::platform_impl::Window::Wayland(window) => window.xdg_toplevel(), + } + } +} /// Additional methods on [`WindowAttributes`] that are specific to Wayland. pub trait WindowAttributesExtWayland { diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 83d6f80328..047affeb0d 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -1,5 +1,7 @@ //! The Wayland window. +use std::ffi::c_void; +use std::ptr::NonNull; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; @@ -223,6 +225,10 @@ impl Window { window_events_sink, }) } + + pub(crate) fn xdg_toplevel(&self) -> Option> { + NonNull::new(self.window.xdg_toplevel().id().as_ptr().cast()) + } } impl Window { From 7672fd56576cd4b6746068e7c025c49220e8d501 Mon Sep 17 00:00:00 2001 From: jpy794 <42579422+jpy794@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:12:43 +0800 Subject: [PATCH 085/116] wayland: support fractional scale for custom cursor --- src/changelog/unreleased.md | 1 + src/platform_impl/linux/wayland/seat/mod.rs | 6 +++- .../linux/wayland/seat/pointer/mod.rs | 19 ++++++++++++- .../linux/wayland/window/state.rs | 28 +++++++++++-------- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 2bbcfdccc5..01fc4c0121 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -53,3 +53,4 @@ changelog entry. - On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode - On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus. - On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`. +- On Wayland, apply fractional scaling to custom cursors. diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 82e38e5414..eaecd93b33 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -96,8 +96,12 @@ impl SeatHandler for WinitState { }, SeatCapability::Pointer if seat_state.pointer.is_none() => { let surface = self.compositor_state.create_surface(queue_handle); + let viewport = self + .viewporter_state + .as_ref() + .map(|state| state.get_viewport(&surface, queue_handle)); let surface_id = surface.id(); - let pointer_data = WinitPointerData::new(seat.clone()); + let pointer_data = WinitPointerData::new(seat.clone(), viewport); let themed_pointer = self .seat_state .get_pointer_with_theme_and_data( diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index fcca59343b..ab83997cbe 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -18,6 +18,7 @@ use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_ma use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1}; use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::csd_frame::FrameClick; +use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; use sctk::compositor::SurfaceData; use sctk::globals::GlobalData; @@ -225,13 +226,17 @@ pub struct WinitPointerData { /// The data required by the sctk. sctk_data: PointerData, + + /// Viewport for fractional cursor. + viewport: Option, } impl WinitPointerData { - pub fn new(seat: WlSeat) -> Self { + pub fn new(seat: WlSeat, viewport: Option) -> Self { Self { inner: Mutex::new(WinitPointerDataInner::default()), sctk_data: PointerData::new(seat), + viewport, } } @@ -312,6 +317,18 @@ impl WinitPointerData { locked_pointer.set_cursor_position_hint(surface_x, surface_y); } } + + pub fn viewport(&self) -> Option<&WpViewport> { + self.viewport.as_ref() + } +} + +impl Drop for WinitPointerData { + fn drop(&mut self) { + if let Some(viewport) = self.viewport.take() { + viewport.destroy(); + } + } } impl PointerDataExt for WinitPointerData { diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index 130bc69418..846dc44c2e 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -31,7 +31,7 @@ use sctk::subcompositor::SubcompositorState; use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur; use crate::cursor::CustomCursor as RootCustomCursor; -use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size}; +use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Size}; use crate::error::{ExternalError, NotSupportedError}; use crate::platform_impl::wayland::logical_to_physical_rounded; use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor}; @@ -726,17 +726,26 @@ impl WindowState { } fn apply_custom_cursor(&self, cursor: &CustomCursor) { - self.apply_on_pointer(|pointer, _| { + self.apply_on_pointer(|pointer, data| { let surface = pointer.surface(); - let scale = surface.data::().unwrap().surface_data().scale_factor(); + let scale = if let Some(viewport) = data.viewport() { + let scale = self.scale_factor(); + let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale); + viewport.set_destination(size.width, size.height); + scale + } else { + let scale = surface.data::().unwrap().surface_data().scale_factor(); + surface.set_buffer_scale(scale); + scale as f64 + }; - surface.set_buffer_scale(scale); surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0); if surface.version() >= 4 { surface.damage_buffer(0, 0, cursor.w, cursor.h); } else { - surface.damage(0, 0, cursor.w / scale, cursor.h / scale); + let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale); + surface.damage(0, 0, size.width, size.height); } surface.commit(); @@ -746,12 +755,9 @@ impl WindowState { .and_then(|data| data.pointer_data().latest_enter_serial()) .unwrap(); - pointer.pointer().set_cursor( - serial, - Some(surface), - cursor.hotspot_x / scale, - cursor.hotspot_y / scale, - ); + let hotspot = + PhysicalPosition::new(cursor.hotspot_x, cursor.hotspot_y).to_logical(scale); + pointer.pointer().set_cursor(serial, Some(surface), hotspot.x, hotspot.y); }); } From 6556cde24676a7b78e509a5d1c4490929cb12baf Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Mar 2025 11:29:53 +0100 Subject: [PATCH 086/116] macOS: Close windows automatically when exiting This disallows carrying over open windows between calls of `run_app_on_demand` (which wasn't intended to be supported anyhow). --- src/changelog/unreleased.md | 1 + src/platform_impl/macos/app_state.rs | 7 +++++-- src/platform_impl/macos/event_loop.rs | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 01fc4c0121..d0c6189e54 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -54,3 +54,4 @@ changelog entry. - On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus. - On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`. - On Wayland, apply fractional scaling to custom cursors. +- On macOS, fixed `run_app_on_demand` returning without closing open windows. diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 35076b6d19..dd2ccd1168 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -11,7 +11,7 @@ use objc2_app_kit::{ use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol}; use super::event_handler::EventHandler; -use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo}; +use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop, PanicInfo}; use super::observer::{EventLoopWaker, RunLoop}; use super::{menu, WindowId, DEVICE_ID}; use crate::event::{DeviceEvent, Event, StartCause, WindowEvent}; @@ -165,7 +165,9 @@ impl ApplicationDelegate { fn will_terminate(&self, _notification: &NSNotification) { trace_scope!("applicationWillTerminate:"); - // TODO: Notify every window that it will be destroyed, like done in iOS? + let mtm = MainThreadMarker::from(self); + let app = NSApplication::sharedApplication(mtm); + notify_windows_of_exit(&app); self.internal_exit(); } @@ -392,6 +394,7 @@ impl ApplicationDelegate { if self.exiting() { let app = NSApplication::sharedApplication(mtm); stop_app_immediately(&app); + notify_windows_of_exit(&app); } if self.ivars().stop_before_wait.get() { diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index cb0620ac95..8bc69f28ce 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -421,6 +421,22 @@ pub(super) fn stop_app_immediately(app: &NSApplication) { }); } +/// Tell all windows to close. +/// +/// This will synchronously trigger `WindowEvent::Destroyed` within +/// `windowWillClose:`, giving the application one last chance to handle +/// those events. It doesn't matter if the user also ends up closing the +/// windows in `Window`'s `Drop` impl, once a window has been closed once, it +/// stays closed. +/// +/// This ensures that no windows linger on after the event loop has exited, +/// see . +pub(super) fn notify_windows_of_exit(app: &NSApplication) { + for window in app.windows() { + window.close(); + } +} + /// Catches panics that happen inside `f` and when a panic /// happens, stops the `sharedApplication` #[inline] From 53321dc6f52749180ac923753c7d7996430518ab Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 29 Apr 2025 13:49:39 +0200 Subject: [PATCH 087/116] Swizzle sendEvent: instead of subclassing NSApplication This is done to avoid order-dependent behavior that you'd otherwise encounter where `EventLoop::new` had to be called at the beginning of `fn main` to ensure that Winit's application was the one being registered as the main application by calling `sharedApplication`. Fixes https://github.com/rust-windowing/winit/issues/3772. This should also make it (more) possible to use multiple versions of Winit in the same application (though that's still untested). Finally, it should allow the user to override `NSApplication` themselves if they need to do that for some reason. --- src/changelog/unreleased.md | 4 + src/platform_impl/macos/app.rs | 180 ++++++++++++++++++++------ src/platform_impl/macos/event_loop.rs | 18 +-- 3 files changed, 155 insertions(+), 47 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index d0c6189e54..2725610000 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -46,6 +46,10 @@ changelog entry. - On Windows, add `CursorGrabMode::Locked`. - On Wayland, add `WindowExtWayland::xdg_toplevel`. +### Changed + +- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself). + ### Fixed - On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw. diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index 38372f4297..4fb95dbd22 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -1,49 +1,104 @@ #![allow(clippy::unnecessary_cast)] +#![allow(unknown_lints)] // New lint below +#![allow(static_mut_refs)] // Uses `MainThreadBound` in new version. -use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass}; -use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; -use objc2_foundation::{MainThreadMarker, NSObject}; +use std::cell::Cell; +use std::mem; + +use objc2::runtime::{Imp, Sel}; +use objc2::sel; +use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType}; +use objc2_foundation::MainThreadMarker; use super::app_state::ApplicationDelegate; use crate::event::{DeviceEvent, ElementState}; -declare_class!( - pub(super) struct WinitApplication; +type SendEvent = extern "C" fn(&NSApplication, Sel, &NSEvent); - unsafe impl ClassType for WinitApplication { - #[inherits(NSResponder, NSObject)] - type Super = NSApplication; - type Mutability = mutability::MainThreadOnly; - const NAME: &'static str = "WinitApplication"; - } +// NOTE: Only used on the main thread. Ideally, we'd use `MainThreadBound`, but that isn't +// constructible from `const` with this `objc2` version. +static mut ORIGINAL: Cell> = Cell::new(None); - impl DeclaredClass for WinitApplication {} - - unsafe impl WinitApplication { - // Normally, holding Cmd + any key never sends us a `keyUp` event for that key. - // Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196) - // Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553) - #[method(sendEvent:)] - fn send_event(&self, event: &NSEvent) { - // For posterity, there are some undocumented event types - // (https://github.com/servo/cocoa-rs/issues/155) - // but that doesn't really matter here. - let event_type = unsafe { event.r#type() }; - let modifier_flags = unsafe { event.modifierFlags() }; - if event_type == NSEventType::KeyUp - && modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand) - { - if let Some(key_window) = self.keyWindow() { - key_window.sendEvent(event); - } - } else { - let delegate = ApplicationDelegate::get(MainThreadMarker::from(self)); - maybe_dispatch_device_event(&delegate, event); - unsafe { msg_send![super(self), sendEvent: event] } - } +extern "C" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) { + let mtm = MainThreadMarker::from(app); + + // Normally, holding Cmd + any key never sends us a `keyUp` event for that key. + // Overriding `sendEvent:` fixes that. (https://stackoverflow.com/a/15294196) + // Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553) + // + // For posterity, there are some undocumented event types + // (https://github.com/servo/cocoa-rs/issues/155) + // but that doesn't really matter here. + let event_type = unsafe { event.r#type() }; + let modifier_flags = unsafe { event.modifierFlags() }; + if event_type == NSEventType::KeyUp + && modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand) + { + if let Some(key_window) = app.keyWindow() { + key_window.sendEvent(event); } + return; + } + + // Events are generally scoped to the window level, so the best way + // to get device events is to listen for them on NSApplication. + let delegate = ApplicationDelegate::get(mtm); + maybe_dispatch_device_event(&delegate, event); + + let _ = mtm; + let original = unsafe { ORIGINAL.get().expect("no existing sendEvent: handler set") }; + original(app, sel, event) +} + +/// Override the [`sendEvent:`][NSApplication::sendEvent] method on the given application class. +/// +/// The previous implementation created a subclass of [`NSApplication`], however we would like to +/// give the user full control over their `NSApplication`, so we override the method here using +/// method swizzling instead. +/// +/// This _should_ also allow two versions of Winit to exist in the same application. +/// +/// See the following links for more info on method swizzling: +/// - +/// - +/// - +/// +/// NOTE: This function assumes that the passed in application object is the one returned from +/// [`NSApplication::sharedApplication`], i.e. the one and only global shared application object. +/// For testing though, we allow it to be a different object. +pub(crate) fn override_send_event(global_app: &NSApplication) { + let mtm = MainThreadMarker::from(global_app); + let class = global_app.class(); + + let method = + class.instance_method(sel!(sendEvent:)).expect("NSApplication must have sendEvent: method"); + + // SAFETY: Converting our `sendEvent:` implementation to an IMP. + let overridden = unsafe { mem::transmute::(send_event) }; + + // If we've already overridden the method, don't do anything. + // FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV. + #[allow(unknown_lints, unpredictable_function_pointer_comparisons)] + if overridden == method.implementation() { + return; } -); + + // SAFETY: Our implementation has: + // 1. The same signature as `sendEvent:`. + // 2. Does not impose extra safety requirements on callers. + let original = unsafe { method.set_implementation(overridden) }; + + // SAFETY: This is the actual signature of `sendEvent:`. + let original = unsafe { mem::transmute::(original) }; + + // NOTE: If NSApplication was safe to use from multiple threads, then this would potentially be + // a (checked) race-condition, since one could call `sendEvent:` before the original had been + // stored here. + // + // It is only usable from the main thread, however, so we're good! + let _ = mtm; + unsafe { ORIGINAL.set(Some(original)) }; +} fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) { let event_type = unsafe { event.r#type() }; @@ -85,3 +140,56 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) _ => (), } } + +#[cfg(test)] +mod tests { + use objc2::rc::Retained; + use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; + + use super::*; + + #[test] + fn test_override() { + // FIXME(madsmtm): Ensure this always runs (maybe use cargo-nextest or `--test-threads=1`?) + let Some(mtm) = MainThreadMarker::new() else { return }; + + // Create a new application, without making it the shared application. + let app = unsafe { NSApplication::new(mtm) }; + override_send_event(&app); + // Test calling twice works. + override_send_event(&app); + + // FIXME(madsmtm): Can't test this yet, need some way to mock AppState. + // unsafe { + // let event = super::super::event::dummy_event().unwrap(); + // app.sendEvent(&event) + // } + } + + #[test] + fn test_custom_class() { + let Some(_mtm) = MainThreadMarker::new() else { return }; + + declare_class!( + struct TestApplication; + + unsafe impl ClassType for TestApplication { + type Super = NSApplication; + type Mutability = mutability::MainThreadOnly; + const NAME: &'static str = "TestApplication"; + } + + impl DeclaredClass for TestApplication {} + + unsafe impl TestApplication { + #[method(sendEvent:)] + fn send_event(&self, _event: &NSEvent) { + todo!() + } + } + ); + + let app: Retained = unsafe { msg_send_id![TestApplication::class(), new] }; + override_send_event(&app); + } +} diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 8bc69f28ce..5c093d5a1d 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -16,11 +16,11 @@ use core_foundation::runloop::{ }; use objc2::rc::{autoreleasepool, Retained}; use objc2::runtime::ProtocolObject; -use objc2::{msg_send_id, sel, ClassType}; +use objc2::sel; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow}; use objc2_foundation::{MainThreadMarker, NSObjectProtocol}; -use super::app::WinitApplication; +use super::app::override_send_event; use super::app_state::{ApplicationDelegate, HandlePendingUserEvents}; use super::event::dummy_event; use super::monitor::{self, MonitorHandle}; @@ -220,15 +220,8 @@ impl EventLoop { let mtm = MainThreadMarker::new() .expect("on macOS, `EventLoop` must be created on the main thread!"); - let app: Retained = - unsafe { msg_send_id![WinitApplication::class(), sharedApplication] }; - - if !app.is_kind_of::() { - panic!( - "`winit` requires control over the principal class. You must create the event \ - loop before other parts of your application initialize NSApplication" - ); - } + // Initialize the application (if it has not already been). + let app = NSApplication::sharedApplication(mtm); let activation_policy = match attributes.activation_policy { None => None, @@ -247,6 +240,9 @@ impl EventLoop { app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); }); + // Override `sendEvent:` on the application to forward to our application state. + override_send_event(&app); + let panic_info: Rc = Default::default(); setup_control_flow_observers(mtm, Rc::downgrade(&panic_info)); From da7a09658a6f220bd576d6d2f94a20eb2a04f0c0 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 29 Apr 2025 12:27:02 +0200 Subject: [PATCH 088/116] fix: Support fractional refresh rates in video modes on macOS (#4191) We were rounding the refresh rate before converting it to millihertz. --- src/changelog/unreleased.md | 1 + src/platform_impl/macos/monitor.rs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 2725610000..cb53b417fd 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -59,3 +59,4 @@ changelog entry. - On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`. - On Wayland, apply fractional scaling to custom cursors. - On macOS, fixed `run_app_on_demand` returning without closing open windows. +- On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates. diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index bf7ee708ea..dab09039dd 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -281,12 +281,12 @@ impl MonitorHandle { }; modes.into_iter().map(move |mode| { - let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; + let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode); // CGDisplayModeGetRefreshRate returns 0.0 for any display that // isn't a CRT - let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 { - (cg_refresh_rate_hertz * 1000) as u32 + let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0.0 { + (cg_refresh_rate_hertz * 1000.0).round() as u32 } else { refresh_rate_millihertz }; From 57baf727411f607c8015a17af625b19ee93cedd7 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 11 Aug 2024 23:14:18 +0200 Subject: [PATCH 089/116] Allow the user to register the application delegate on iOS iOS parts of #3758. This allows the user to override the application delegate themselves, which opens several doors for customization that were previously closed. To do this, we use notifications instead of a top-level application delegate. One effect of not providing an application delegate on iOS is that we no longer act as-if the application successfully open all URLs there. This is a breaking change, although unlikely to matter in practice, since the return value of `application:didFinishLaunchingWithOptions:` is seldom used by the system (and this is likely the preferred behaviour anyhow). --- Cargo.toml | 4 +- src/changelog/unreleased.md | 4 + src/platform/ios.rs | 15 ++- src/platform/macos.rs | 70 ++++++++++- src/platform_impl/ios/app_delegate.rs | 60 ---------- src/platform_impl/ios/event_loop.rs | 119 +++++++++++++++++-- src/platform_impl/ios/mod.rs | 2 +- src/platform_impl/ios/notification_center.rs | 27 +++++ 8 files changed, 229 insertions(+), 72 deletions(-) delete mode 100644 src/platform_impl/ios/app_delegate.rs create mode 100644 src/platform_impl/ios/notification_center.rs diff --git a/Cargo.toml b/Cargo.toml index 348d3423b2..f71e03d298 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,12 +117,12 @@ android-activity = "0.6.0" ndk = { version = "0.9.0", default-features = false } [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] +block2 = "0.5.1" core-foundation = "0.9.3" objc2 = "0.5.2" [target.'cfg(target_os = "macos")'.dependencies] core-graphics = "0.23.1" -block2 = "0.5.1" [target.'cfg(target_os = "macos")'.dependencies.objc2-foundation] version = "0.2.2" @@ -180,11 +180,13 @@ features = [ [target.'cfg(target_os = "ios")'.dependencies.objc2-foundation] version = "0.2.2" features = [ + "block2", "dispatch", "NSArray", "NSEnumerator", "NSGeometry", "NSObjCRuntime", + "NSOperation", "NSString", "NSProcessInfo", "NSThread", diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index cb53b417fd..812e5f0f78 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -49,6 +49,10 @@ changelog entry. ### Changed - On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself). +- On iOS, remove custom application delegates. You are now allowed to override the + application delegate yourself. +- On iOS, no longer act as-if the application successfully open all URLs. Override + `application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself. ### Fixed diff --git a/src/platform/ios.rs b/src/platform/ios.rs index 59f5396163..6e62876834 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -3,11 +3,14 @@ //! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on //! iOS 9.3. //! +//! ## Window initialization +//! //! iOS's main `UIApplicationMain` does some init work that's required by all //! UI-related code (see issue [#1705]). It is best to create your windows -//! inside `Event::Resumed`. +//! inside [`ApplicationHandler::resumed`]. //! //! [#1705]: https://github.com/rust-windowing/winit/issues/1705 +//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed //! //! ## Building app //! @@ -63,6 +66,16 @@ //! opengl will result in segfault. //! //! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed. +//! +//! ## Custom `UIApplicationDelegate` +//! +//! Winit usually handles everything related to the lifecycle events of the application. Sometimes, +//! though, you might want to access some of the more niche stuff that [the application +//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it +//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not +//! register an application delegate, so you can set up a custom one in a nib file instead. +//! +//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc use std::os::raw::c_void; diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 7d4b8ca348..30623b01e0 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -3,16 +3,84 @@ //! Winit has an OS requirement of macOS 10.11 or higher (same as Rust //! itself), and is regularly tested on macOS 10.14. //! +//! ## Window initialization +//! //! A lot of functionality expects the application to be ready before you //! start doing anything; this includes creating windows, fetching monitors, //! drawing, and so on, see issues [#2238], [#2051] and [#2087]. //! //! If you encounter problems, you should try doing your initialization inside -//! `Event::Resumed`. +//! [`ApplicationHandler::resumed`]. //! //! [#2238]: https://github.com/rust-windowing/winit/issues/2238 //! [#2051]: https://github.com/rust-windowing/winit/issues/2051 //! [#2087]: https://github.com/rust-windowing/winit/issues/2087 +//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed +//! +//! ## Custom `NSApplicationDelegate` +//! +//! Winit usually handles everything related to the lifecycle events of the application. Sometimes, +//! though, you might want to do more niche stuff, such as [handle when the user re-activates the +//! application][reopen]. Such functionality is not exposed directly in Winit, since it would +//! increase the API surface by quite a lot. +//! +//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc +//! +//! Instead, Winit guarantees that it will not register an application delegate, so the solution is +//! to register your own application delegate, as outlined in the following example (see +//! `objc2-app-kit` for more detailed information). +#![cfg_attr(target_os = "macos", doc = "```")] +#![cfg_attr(not(target_os = "macos"), doc = "```ignore")] +//! use objc2::rc::Retained; +//! use objc2::runtime::ProtocolObject; +//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; +//! use objc2_app_kit::{NSApplication, NSApplicationDelegate}; +//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol}; +//! use winit::event_loop::EventLoop; +//! +//! declare_class!( +//! struct AppDelegate; +//! +//! unsafe impl ClassType for AppDelegate { +//! type Super = NSObject; +//! type Mutability = mutability::MainThreadOnly; +//! const NAME: &'static str = "MyAppDelegate"; +//! } +//! +//! impl DeclaredClass for AppDelegate {} +//! +//! unsafe impl NSObjectProtocol for AppDelegate {} +//! +//! unsafe impl NSApplicationDelegate for AppDelegate { +//! #[method(application:openURLs:)] +//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray) { +//! // Note: To specifically get `application:openURLs:` to work, you _might_ +//! // have to bundle your application. This is not done in this example. +//! println!("open urls: {application:?}, {urls:?}"); +//! } +//! } +//! ); +//! +//! impl AppDelegate { +//! fn new(mtm: MainThreadMarker) -> Retained { +//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] } +//! } +//! } +//! +//! fn main() -> Result<(), Box> { +//! let event_loop = EventLoop::new()?; +//! +//! let mtm = MainThreadMarker::new().unwrap(); +//! let delegate = AppDelegate::new(mtm); +//! // Important: Call `sharedApplication` after `EventLoop::new`, +//! // doing it before is not yet supported. +//! let app = NSApplication::sharedApplication(mtm); +//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); +//! +//! // event_loop.run_app(&mut my_app); +//! Ok(()) +//! } +//! ``` use std::os::raw::c_void; diff --git a/src/platform_impl/ios/app_delegate.rs b/src/platform_impl/ios/app_delegate.rs deleted file mode 100644 index 2328c58c50..0000000000 --- a/src/platform_impl/ios/app_delegate.rs +++ /dev/null @@ -1,60 +0,0 @@ -use objc2::{declare_class, mutability, ClassType, DeclaredClass}; -use objc2_foundation::{MainThreadMarker, NSObject}; -use objc2_ui_kit::UIApplication; - -use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper}; -use crate::event::Event; - -declare_class!( - pub struct AppDelegate; - - unsafe impl ClassType for AppDelegate { - type Super = NSObject; - type Mutability = mutability::InteriorMutable; - const NAME: &'static str = "WinitApplicationDelegate"; - } - - impl DeclaredClass for AppDelegate {} - - // UIApplicationDelegate protocol - unsafe impl AppDelegate { - #[method(application:didFinishLaunchingWithOptions:)] - fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool { - app_state::did_finish_launching(MainThreadMarker::new().unwrap()); - true - } - - #[method(applicationDidBecomeActive:)] - fn did_become_active(&self, _application: &UIApplication) { - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed)) - } - - #[method(applicationWillResignActive:)] - fn will_resign_active(&self, _application: &UIApplication) { - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended)) - } - - #[method(applicationWillEnterForeground:)] - fn will_enter_foreground(&self, application: &UIApplication) { - send_occluded_event_for_all_windows(application, false); - } - - #[method(applicationDidEnterBackground:)] - fn did_enter_background(&self, application: &UIApplication) { - send_occluded_event_for_all_windows(application, true); - } - - #[method(applicationWillTerminate:)] - fn will_terminate(&self, application: &UIApplication) { - app_state::terminated(application); - } - - #[method(applicationDidReceiveMemoryWarning:)] - fn did_receive_memory_warning(&self, _application: &UIApplication) { - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning)) - } - } -); diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 52d8062bea..a093b7b590 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -13,8 +13,14 @@ use core_foundation::runloop::{ }; use objc2::rc::Retained; use objc2::{msg_send_id, ClassType}; -use objc2_foundation::{MainThreadMarker, NSString}; -use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom}; +use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject}; +use objc2_ui_kit::{ + UIApplication, UIApplicationDidBecomeActiveNotification, + UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification, + UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain, + UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification, + UIApplicationWillTerminateNotification, UIDevice, UIScreen, UIUserInterfaceIdiom, +}; use crate::error::EventLoopError; use crate::event::Event; @@ -25,8 +31,8 @@ use crate::platform::ios::Idiom; use crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents}; use crate::window::{CustomCursor, CustomCursorSource, Theme}; -use super::app_delegate::AppDelegate; -use super::app_state::AppState; +use super::app_state::{send_occluded_event_for_all_windows, AppState, EventWrapper}; +use super::notification_center::create_observer; use super::{app_state, monitor, MonitorHandle}; #[derive(Debug)] @@ -132,6 +138,18 @@ pub struct EventLoop { sender: Sender, receiver: Receiver, window_target: RootActiveEventLoop, + + // Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the + // system instead cleans it up next time it would have posted a notification to it. + // + // Though we do still need to keep the observers around to prevent them from being deallocated. + _did_finish_launching_observer: Retained, + _did_become_active_observer: Retained, + _will_resign_active_observer: Retained, + _will_enter_foreground_observer: Retained, + _did_enter_background_observer: Retained, + _will_terminate_observer: Retained, + _did_receive_memory_warning_observer: Retained, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -158,11 +176,97 @@ impl EventLoop { // this line sets up the main run loop before `UIApplicationMain` setup_control_flow_observers(); + let center = unsafe { NSNotificationCenter::defaultCenter() }; + + let _did_finish_launching_observer = create_observer( + ¢er, + // `application:didFinishLaunchingWithOptions:` + unsafe { UIApplicationDidFinishLaunchingNotification }, + move |_| { + app_state::did_finish_launching(mtm); + }, + ); + let _did_become_active_observer = create_observer( + ¢er, + // `applicationDidBecomeActive:` + unsafe { UIApplicationDidBecomeActiveNotification }, + move |_| { + app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed)); + }, + ); + let _will_resign_active_observer = create_observer( + ¢er, + // `applicationWillResignActive:` + unsafe { UIApplicationWillResignActiveNotification }, + move |_| { + app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended)); + }, + ); + let _will_enter_foreground_observer = create_observer( + ¢er, + // `applicationWillEnterForeground:` + unsafe { UIApplicationWillEnterForegroundNotification }, + move |notification| { + let app = unsafe { notification.object() }.expect( + "UIApplicationWillEnterForegroundNotification to have application object", + ); + // SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is + // documented to be `UIApplication`. + let app: Retained = unsafe { Retained::cast(app) }; + send_occluded_event_for_all_windows(&app, false); + }, + ); + let _did_enter_background_observer = create_observer( + ¢er, + // `applicationDidEnterBackground:` + unsafe { UIApplicationDidEnterBackgroundNotification }, + move |notification| { + let app = unsafe { notification.object() }.expect( + "UIApplicationDidEnterBackgroundNotification to have application object", + ); + // SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is + // documented to be `UIApplication`. + let app: Retained = unsafe { Retained::cast(app) }; + send_occluded_event_for_all_windows(&app, true); + }, + ); + let _will_terminate_observer = create_observer( + ¢er, + // `applicationWillTerminate:` + unsafe { UIApplicationWillTerminateNotification }, + move |notification| { + let app = unsafe { notification.object() } + .expect("UIApplicationWillTerminateNotification to have application object"); + // SAFETY: The `object` in `UIApplicationWillTerminateNotification` is + // (somewhat) documented to be `UIApplication`. + let app: Retained = unsafe { Retained::cast(app) }; + app_state::terminated(&app); + }, + ); + let _did_receive_memory_warning_observer = create_observer( + ¢er, + // `applicationDidReceiveMemoryWarning:` + unsafe { UIApplicationDidReceiveMemoryWarningNotification }, + move |_| { + app_state::handle_nonuser_event( + mtm, + EventWrapper::StaticEvent(Event::MemoryWarning), + ); + }, + ); + Ok(EventLoop { mtm, sender, receiver, window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData }, + _did_finish_launching_observer, + _did_become_active_observer, + _will_resign_active_observer, + _will_enter_foreground_observer, + _did_enter_background_observer, + _will_terminate_observer, + _did_receive_memory_warning_observer, }) } @@ -192,9 +296,6 @@ impl EventLoop { app_state::will_launch(self.mtm, handler); - // Ensure application delegate is initialized - let _ = AppDelegate::class(); - extern "C" { // These functions are in crt_externs.h. fn _NSGetArgc() -> *mut c_int; @@ -205,8 +306,10 @@ impl EventLoop { UIApplicationMain( *_NSGetArgc(), NonNull::new(*_NSGetArgv()).unwrap(), + // We intentionally override neither the application nor the delegate, to allow the + // user to do so themselves! + None, None, - Some(&NSString::from_str(AppDelegate::NAME)), ) }; unreachable!() diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index 9c2362e11d..69e79c921e 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -1,9 +1,9 @@ #![allow(clippy::let_unit_value)] -mod app_delegate; mod app_state; mod event_loop; mod monitor; +mod notification_center; mod view; mod view_controller; mod window; diff --git a/src/platform_impl/ios/notification_center.rs b/src/platform_impl/ios/notification_center.rs new file mode 100644 index 0000000000..652bf1d079 --- /dev/null +++ b/src/platform_impl/ios/notification_center.rs @@ -0,0 +1,27 @@ +use std::ptr::NonNull; + +use block2::RcBlock; +use objc2::rc::Retained; +use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject}; + +/// Observe the given notification. +/// +/// This is used in Winit as an alternative to declaring an application delegate, as we want to +/// give the user full control over those. +pub fn create_observer( + center: &NSNotificationCenter, + name: &NSNotificationName, + handler: impl Fn(&NSNotification) + 'static, +) -> Retained { + let block = RcBlock::new(move |notification: NonNull| { + handler(unsafe { notification.as_ref() }); + }); + unsafe { + center.addObserverForName_object_queue_usingBlock( + Some(name), + None, // No sender filter + None, // No queue, run on posting thread (i.e. main thread) + &block, + ) + } +} From 5f1e9f6cc1f0ebd4c49ebe93eec0b7c7ddbb2b9a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Mar 2025 09:18:14 +0100 Subject: [PATCH 090/116] macOS: Store UUID in MonitorHandle instead of CGDirectDisplayID The monitor UUID is what actually represents the monitor, CGDirectDisplayID is closer in correspondence to a specific framebuffer. --- src/changelog/unreleased.md | 3 +- src/platform_impl/macos/ffi.rs | 2 + src/platform_impl/macos/monitor.rs | 71 +++++++++++++++------- src/platform_impl/macos/window_delegate.rs | 3 +- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 812e5f0f78..c839df5f3a 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -57,10 +57,11 @@ changelog entry. ### Fixed - On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw. -- On macos, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game` +- On macOS, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game` - On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode - On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus. - On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`. - On Wayland, apply fractional scaling to custom cursors. - On macOS, fixed `run_app_on_demand` returning without closing open windows. - On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates. +- On macOS, store monitor handle to avoid panics after going in/out of sleep. diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 92d4874e4b..1daee071e5 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -68,6 +68,8 @@ pub type CGDisplayModeRef = *mut c_void; #[link(name = "ApplicationServices", kind = "framework")] extern "C" { pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; + + pub fn CGDisplayGetDisplayIDFromUUID(uuid: CFUUIDRef) -> CGDirectDisplayID; } #[link(name = "CoreGraphics", kind = "framework")] diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index dab09039dd..7d17ca2b7a 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -14,6 +14,7 @@ use objc2::rc::Retained; use objc2::runtime::AnyObject; use objc2_app_kit::NSScreen; use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect}; +use tracing::warn; use super::ffi; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; @@ -98,18 +99,29 @@ impl VideoModeHandle { } } +/// `CGDirectDisplayID` is documented as: +/// > a framebuffer, a color correction (gamma) table, and possibly an attached monitor. +/// +/// That is, it doesn't actually represent the monitor itself. Instead, we use the UUID of the +/// monitor, as retrieved from `CGDisplayCreateUUIDFromDisplayID` (this makes the monitor ID stable, +/// even across reboots and video mode changes). +/// +/// NOTE: I'd be perfectly valid to store `[u8; 16]` in here instead, we only store `CFUUID` to +/// avoid having to re-create it when we want to fetch the display ID. #[derive(Clone)] -pub struct MonitorHandle(CGDirectDisplayID); +pub struct MonitorHandle(CFUUID); + +// SAFETY: CFUUID is immutable. +// FIXME(madsmtm): Upstream this into `objc2-core-foundation`. +unsafe impl Send for MonitorHandle {} +unsafe impl Sync for MonitorHandle {} type MonitorUuid = [u8; 16]; impl MonitorHandle { /// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle. fn uuid(&self) -> MonitorUuid { - let cf_uuid = unsafe { - CFUUID::wrap_under_create_rule(ffi::CGDisplayCreateUUIDFromDisplayID(self.0)) - }; - let uuid = unsafe { CFUUIDGetUUIDBytes(cf_uuid.as_concrete_TypeRef()) }; + let uuid = unsafe { CFUUIDGetUUIDBytes(self.0.as_concrete_TypeRef()) }; MonitorUuid::from([ uuid.byte0, uuid.byte1, @@ -129,11 +141,26 @@ impl MonitorHandle { uuid.byte15, ]) } + + fn display_id(&self) -> CGDirectDisplayID { + unsafe { ffi::CGDisplayGetDisplayIDFromUUID(self.0.as_concrete_TypeRef()) } + } + + #[track_caller] + pub(crate) fn new(display_id: CGDirectDisplayID) -> Option { + // kCGNullDirectDisplay + if display_id == 0 { + // `CGDisplayCreateUUIDFromDisplayID` checks kCGNullDirectDisplay internally. + warn!("constructing monitor from invalid display ID 0; falling back to main monitor"); + } + let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(display_id) }; + if ptr.is_null() { + return None; + } + Some(Self(unsafe { CFUUID::wrap_under_create_rule(ptr) })) + } } -// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that -// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an -// unique identifier that persists even across system reboots impl PartialEq for MonitorHandle { fn eq(&self, other: &Self) -> bool { self.uuid() == other.uuid() @@ -164,7 +191,8 @@ pub fn available_monitors() -> VecDeque { if let Ok(displays) = CGDisplay::active_displays() { let mut monitors = VecDeque::with_capacity(displays.len()); for display in displays { - monitors.push_back(MonitorHandle(display)); + // Display ID just fetched from `CGGetActiveDisplayList`, should be fine to unwrap. + monitors.push_back(MonitorHandle::new(display).expect("invalid display ID")); } monitors } else { @@ -173,7 +201,8 @@ pub fn available_monitors() -> VecDeque { } pub fn primary_monitor() -> MonitorHandle { - MonitorHandle(CGDisplay::main().id) + // Display ID just fetched from `CGMainDisplayID`, should be fine to unwrap. + MonitorHandle::new(CGDisplay::main().id).expect("invalid display ID") } impl fmt::Debug for MonitorHandle { @@ -190,26 +219,20 @@ impl fmt::Debug for MonitorHandle { } impl MonitorHandle { - pub fn new(id: CGDirectDisplayID) -> Self { - MonitorHandle(id) - } - // TODO: Be smarter about this: // pub fn name(&self) -> Option { - let MonitorHandle(display_id) = *self; - let screen_num = CGDisplay::new(display_id).model_number(); + let screen_num = CGDisplay::new(self.display_id()).model_number(); Some(format!("Monitor #{screen_num}")) } #[inline] pub fn native_identifier(&self) -> u32 { - self.0 + self.display_id() } pub fn size(&self) -> PhysicalSize { - let MonitorHandle(display_id) = *self; - let display = CGDisplay::new(display_id); + let display = CGDisplay::new(self.display_id()); let height = display.pixels_high(); let width = display.pixels_wide(); PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.scale_factor()) @@ -236,14 +259,15 @@ impl MonitorHandle { pub fn refresh_rate_millihertz(&self) -> Option { unsafe { - let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _); + let current_display_mode = + NativeDisplayMode(CGDisplayCopyDisplayMode(self.display_id()) as _); let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0); if refresh_rate > 0.0 { return Some((refresh_rate * 1000.0).round() as u32); } let mut display_link = std::ptr::null_mut(); - if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link) + if ffi::CVDisplayLinkCreateWithCGDisplay(self.display_id(), &mut display_link) != ffi::kCVReturnSuccess { return None; @@ -266,7 +290,7 @@ impl MonitorHandle { unsafe { let modes = { - let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null()); + let array = ffi::CGDisplayCopyAllDisplayModes(self.display_id(), std::ptr::null()); assert!(!array.is_null(), "failed to get list of display modes"); let array_count = CFArrayGetCount(array); let modes: Vec<_> = (0..array_count) @@ -322,7 +346,8 @@ impl MonitorHandle { let uuid = self.uuid(); NSScreen::screens(mtm).into_iter().find(|screen| { let other_native_id = get_display_id(screen); - let other = MonitorHandle::new(other_native_id); + // Display ID just fetched from live NSScreen, should be fine to unwrap. + let other = MonitorHandle::new(other_native_id).expect("invalid display ID"); uuid == other.uuid() }) } diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 8982d40880..abc1402309 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1592,7 +1592,8 @@ impl WindowDelegate { // Allow directly accessing the current monitor internally without unwrapping. pub(crate) fn current_monitor_inner(&self) -> Option { let display_id = get_display_id(&*self.window().screen()?); - Some(MonitorHandle::new(display_id)) + // Display ID just fetched from live NSScreen, should be fine to unwrap. + Some(MonitorHandle::new(display_id).expect("invalid display ID")) } #[inline] From 80bddda641d07778670ca3fc245cfe8ce083a9d4 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 29 Apr 2025 12:41:42 +0200 Subject: [PATCH 091/116] macOS: Fix monitors connected via certain Thunderbolt hubs Instead of panicking, raise a warning and return `None` or similar. Co-Authored-By: RJ --- src/changelog/unreleased.md | 1 + src/platform_impl/macos/monitor.rs | 40 ++++++++++++++-------- src/platform_impl/macos/window_delegate.rs | 10 ++++-- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index c839df5f3a..5f15e1b6a6 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -65,3 +65,4 @@ changelog entry. - On macOS, fixed `run_app_on_demand` returning without closing open windows. - On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates. - On macOS, store monitor handle to avoid panics after going in/out of sleep. +- On macOS, allow certain invalid monitor handles and return `None` instead of panicking. diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 7d17ca2b7a..44e6316b66 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -291,17 +291,24 @@ impl MonitorHandle { unsafe { let modes = { let array = ffi::CGDisplayCopyAllDisplayModes(self.display_id(), std::ptr::null()); - assert!(!array.is_null(), "failed to get list of display modes"); - let array_count = CFArrayGetCount(array); - let modes: Vec<_> = (0..array_count) - .map(move |i| { - let mode = CFArrayGetValueAtIndex(array, i) as *mut _; - ffi::CGDisplayModeRetain(mode); - mode - }) - .collect(); - CFRelease(array as *const _); - modes + if array.is_null() { + // Occasionally, certain CalDigit Thunderbolt Hubs report a spurious monitor + // during sleep/wake/cycling monitors. It tends to have null + // or 1 video mode only. See . + warn!(monitor = ?self, "failed to get a list of display modes"); + Vec::new() + } else { + let array_count = CFArrayGetCount(array); + let modes: Vec<_> = (0..array_count) + .map(move |i| { + let mode = CFArrayGetValueAtIndex(array, i) as *mut _; + ffi::CGDisplayModeRetain(mode); + mode + }) + .collect(); + CFRelease(array as *const _); + modes + } }; modes.into_iter().map(move |mode| { @@ -346,9 +353,14 @@ impl MonitorHandle { let uuid = self.uuid(); NSScreen::screens(mtm).into_iter().find(|screen| { let other_native_id = get_display_id(screen); - // Display ID just fetched from live NSScreen, should be fine to unwrap. - let other = MonitorHandle::new(other_native_id).expect("invalid display ID"); - uuid == other.uuid() + if let Some(other) = MonitorHandle::new(other_native_id) { + uuid == other.uuid() + } else { + // Display ID was just fetched from live NSScreen, but can still result in `None` + // with certain Thunderbolt docked monitors. + warn!(other_native_id, "comparing against screen with invalid display ID"); + false + } }) } } diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index abc1402309..e0dc693c5b 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1592,8 +1592,14 @@ impl WindowDelegate { // Allow directly accessing the current monitor internally without unwrapping. pub(crate) fn current_monitor_inner(&self) -> Option { let display_id = get_display_id(&*self.window().screen()?); - // Display ID just fetched from live NSScreen, should be fine to unwrap. - Some(MonitorHandle::new(display_id).expect("invalid display ID")) + if let Some(monitor) = MonitorHandle::new(display_id) { + Some(monitor) + } else { + // NOTE: Display ID was just fetched from live NSScreen, but can still result in `None` + // with certain Thunderbolt docked monitors. + warn!(display_id, "got screen with invalid display ID"); + None + } } #[inline] From a26899a75dacccc7209216f524e0351f85cdd16c Mon Sep 17 00:00:00 2001 From: Mitoma Ryo Date: Tue, 29 Apr 2025 21:11:54 +0900 Subject: [PATCH 092/116] windows: fix incorrect cursor_range calculation in Ime::Preedit The `text` is retrieved as UTF-8 while `attributes` are based on UTF-16, thus the offset was getting out of sync on some unicode payloads like surrogate pairs. Fixes #3967. --- src/changelog/unreleased.md | 1 + src/platform_impl/windows/ime.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 5f15e1b6a6..ddeb2138ac 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -66,3 +66,4 @@ changelog entry. - On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates. - On macOS, store monitor handle to avoid panics after going in/out of sleep. - On macOS, allow certain invalid monitor handles and return `None` instead of panicking. +- On Windows, fixed `Ime::Preedit` cursor offset calculation. diff --git a/src/platform_impl/windows/ime.rs b/src/platform_impl/windows/ime.rs index 6761487351..eb65abf3aa 100644 --- a/src/platform_impl/windows/ime.rs +++ b/src/platform_impl/windows/ime.rs @@ -35,8 +35,13 @@ impl ImeContext { let mut first = None; let mut last = None; let mut boundary_before_char = 0; + let mut attr_idx = 0; + + for chr in text.chars() { + let Some(attr) = attrs.get(attr_idx).copied() else { + break; + }; - for (attr, chr) in attrs.into_iter().zip(text.chars()) { let char_is_targeted = attr as u32 == ATTR_TARGET_CONVERTED || attr as u32 == ATTR_TARGET_NOTCONVERTED; @@ -47,6 +52,7 @@ impl ImeContext { } boundary_before_char += chr.len_utf8(); + attr_idx += chr.len_utf16(); } if first.is_some() && last.is_none() { From 6db1343c0b65a4f1bccff9aa793455e25809c0c1 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 30 Apr 2025 02:09:44 +0900 Subject: [PATCH 093/116] wayland: bump wayland-rs to avoid yanked release --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f71e03d298..6e460c61d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -268,14 +268,14 @@ sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-feature "calloop", ], optional = true } sctk-adwaita = { version = "0.10.1", default-features = false, optional = true } -wayland-backend = { version = "0.3.9", default-features = false, features = [ +wayland-backend = { version = "0.3.10", default-features = false, features = [ "client_system", ], optional = true } -wayland-client = { version = "0.31.8", optional = true } -wayland-protocols = { version = "0.32.6", features = [ +wayland-client = { version = "0.31.10", optional = true } +wayland-protocols = { version = "0.32.8", features = [ "staging", ], optional = true } -wayland-protocols-plasma = { version = "0.3.4", features = [ +wayland-protocols-plasma = { version = "0.3.8", features = [ "client", ], optional = true } x11-dl = { version = "2.19.1", optional = true } From 23854103661922c7bba91725b6896ac70720822c Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 29 Apr 2025 15:05:33 +0900 Subject: [PATCH 094/116] Winit version 0.30.10 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 28 ---------------------------- src/changelog/v0.30.md | 30 ++++++++++++++++++++++++++++++ src/platform/android.rs | 2 +- 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e460c61d2..baee02c4a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.9" +version = "0.30.10" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index 966e36e5e1..039339028b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.9" +winit = "0.30.10" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index ddeb2138ac..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,31 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Added - -- On Windows, add `IconExtWindows::from_resource_name`. -- On Windows, add `CursorGrabMode::Locked`. -- On Wayland, add `WindowExtWayland::xdg_toplevel`. - -### Changed - -- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself). -- On iOS, remove custom application delegates. You are now allowed to override the - application delegate yourself. -- On iOS, no longer act as-if the application successfully open all URLs. Override - `application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself. - -### Fixed - -- On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw. -- On macOS, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game` -- On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode -- On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus. -- On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`. -- On Wayland, apply fractional scaling to custom cursors. -- On macOS, fixed `run_app_on_demand` returning without closing open windows. -- On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates. -- On macOS, store monitor handle to avoid panics after going in/out of sleep. -- On macOS, allow certain invalid monitor handles and return `None` instead of panicking. -- On Windows, fixed `Ime::Preedit` cursor offset calculation. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index 1abc1e8af2..41bd5b571e 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,33 @@ +## 0.30.10 + +### Added + +- On Windows, add `IconExtWindows::from_resource_name`. +- On Windows, add `CursorGrabMode::Locked`. +- On Wayland, add `WindowExtWayland::xdg_toplevel`. + +### Changed + +- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself). +- On iOS, remove custom application delegates. You are now allowed to override the + application delegate yourself. +- On iOS, no longer act as-if the application successfully open all URLs. Override + `application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself. + +### Fixed + +- On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw. +- On macOS, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game` +- On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode +- On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus. +- On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`. +- On Wayland, apply fractional scaling to custom cursors. +- On macOS, fixed `run_app_on_demand` returning without closing open windows. +- On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates. +- On macOS, store monitor handle to avoid panics after going in/out of sleep. +- On macOS, allow certain invalid monitor handles and return `None` instead of panicking. +- On Windows, fixed `Ime::Preedit` cursor offset calculation. + ## 0.30.9 ### Changed diff --git a/src/platform/android.rs b/src/platform/android.rs index 55aa05561c..1a8c2cd5cb 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.9", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.10", //! 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 From f49a2a182750901bb56112d9ad6eaefb8e97cdf0 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 2 May 2025 16:02:30 +0900 Subject: [PATCH 095/116] clippy: fix casing in windows backend --- src/platform_impl/windows/util.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 52912c3c6f..a3a7705d2b 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -228,31 +228,31 @@ pub type GetDpiForMonitor = unsafe extern "system" fn( pub type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL; pub type AdjustWindowRectExForDpi = unsafe extern "system" fn( rect: *mut RECT, - dwStyle: u32, - bMenu: BOOL, - dwExStyle: u32, + dw_style: u32, + b_menu: BOOL, + dw_ex_style: u32, dpi: u32, ) -> BOOL; pub type GetPointerFrameInfoHistory = unsafe extern "system" fn( - pointerId: u32, - entriesCount: *mut u32, - pointerCount: *mut u32, - pointerInfo: *mut POINTER_INFO, + pointer_id: u32, + entries_count: *mut u32, + pointer_count: *mut u32, + pointer_info: *mut POINTER_INFO, ) -> BOOL; -pub type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL; +pub type SkipPointerFrameMessages = unsafe extern "system" fn(pointer_id: u32) -> BOOL; pub type GetPointerDeviceRects = unsafe extern "system" fn( device: HANDLE, - pointerDeviceRect: *mut RECT, - displayRect: *mut RECT, + pointer_device_rect: *mut RECT, + display_rect: *mut RECT, ) -> BOOL; pub type GetPointerTouchInfo = - unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL; + unsafe extern "system" fn(pointer_id: u32, touch_info: *mut POINTER_TOUCH_INFO) -> BOOL; pub type GetPointerPenInfo = - unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL; + unsafe extern "system" fn(point_id: u32, pen_info: *mut POINTER_PEN_INFO) -> BOOL; pub(crate) static GET_DPI_FOR_WINDOW: Lazy> = Lazy::new(|| get_function!("user32.dll", GetDpiForWindow)); From 17b573797244f39344c05be95d5bb4fd78fc95cc Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sat, 3 May 2025 18:38:15 +0700 Subject: [PATCH 096/116] Fix typos from updated `typos` tool (#4213) --- README.md | 4 ++++ src/keyboard.rs | 2 +- src/platform_impl/linux/common/xkb/mod.rs | 2 +- src/platform_impl/linux/wayland/seat/pointer/mod.rs | 2 +- src/platform_impl/linux/x11/util/mod.rs | 2 +- src/platform_impl/windows/keyboard.rs | 7 +++---- src/platform_impl/windows/raw_input.rs | 6 +++--- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 039339028b..bae58e404c 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen show something on the window you need to use the platform-specific getters provided by winit, or another library. +## CONTRIBUTING + +For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md). + ## MSRV Policy This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to diff --git a/src/keyboard.rs b/src/keyboard.rs index 49d55c1e1f..7b406f0890 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1232,7 +1232,7 @@ pub enum NamedKey { Dimmer, /// Swap video sources. (`VK_DISPLAY_SWAP`) DisplaySwap, - /// Select Digital Video Rrecorder. (`KEYCODE_DVR`) + /// Select Digital Video Recorder. (`KEYCODE_DVR`) DVR, /// Exit the current application. (`VK_EXIT`) Exit, diff --git a/src/platform_impl/linux/common/xkb/mod.rs b/src/platform_impl/linux/common/xkb/mod.rs index e2e7680065..706397f6f2 100644 --- a/src/platform_impl/linux/common/xkb/mod.rs +++ b/src/platform_impl/linux/common/xkb/mod.rs @@ -320,7 +320,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> { // The current behaviour makes it so composing a character overrides attempts to input a // control character with the `Ctrl` key. We can potentially add a configuration option - // if someone specifically wants the oppsite behaviour. + // if someone specifically wants the opposite behaviour. pub fn text_with_all_modifiers(&mut self) -> Option { match self.composed_text() { Ok(text) => text, diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index ab83997cbe..3dcb00b269 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -194,7 +194,7 @@ impl PointerHandler for WinitState { pointer_data.phase = phase; // Mice events have both pixel and discrete delta's at the same time. So prefer - // the descrite values if they are present. + // the discrete values if they are present. let delta = if has_discrete_scroll { // NOTE: Wayland sign convention is the inverse of winit. MouseScrollDelta::LineDelta( diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index 5c30293c31..55e6c2ddd8 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -51,7 +51,7 @@ where } impl XConnection { - // This is impoartant, so pay attention! + // This is important, so pay attention! // Xlib has an output buffer, and tries to hide the async nature of X from you. // This buffer contains the requests you make, and is flushed under various circumstances: // 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed" diff --git a/src/platform_impl/windows/keyboard.rs b/src/platform_impl/windows/keyboard.rs index ab4a0f4070..59c4931a65 100644 --- a/src/platform_impl/windows/keyboard.rs +++ b/src/platform_impl/windows/keyboard.rs @@ -213,8 +213,7 @@ impl KeyEventBuilder { .unwrap_or(false); if more_char_coming { // No need to produce an event just yet, because there are still more - // characters that need to appended to this keyobard - // event + // characters that need to be appended to this keyboard event MatchResult::TokenToRemove(pending_token) } else { let mut event_info = self.event_info.lock().unwrap(); @@ -335,8 +334,8 @@ impl KeyEventBuilder { // 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 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. + // other key, in which case all other keys *must* be synthesized 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 synthesize // caps-lock first, and always use the current caps-lock state diff --git a/src/platform_impl/windows/raw_input.rs b/src/platform_impl/windows/raw_input.rs index 87d16750ea..f78a7bb03e 100644 --- a/src/platform_impl/windows/raw_input.rs +++ b/src/platform_impl/windows/raw_input.rs @@ -225,16 +225,16 @@ pub fn get_keyboard_physical_key(keyboard: RAWKEYBOARD) -> Option { if scancode == 0xe11d || scancode == 0xe02a { // At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing // Ctrl+NumLock. - // This equvalence means that if the user presses Pause, the keyboard will emit two + // This equivalence means that if the user presses Pause, the keyboard will emit two // subsequent keypresses: // 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100) // 2, 0x0045 - Which on its own can be interpreted as Pause // // There's another combination which isn't quite an equivalence: - // PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing + // PrtSc used to be Shift+Asterisk. This means that on some keyboards, pressing // PrtSc (print screen) produces the following sequence: // 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000) - // 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on + // 2, 0xE037 - Which is a numpad multiply (0x37) with an extension flag (0xE000). This on // its own it can be interpreted as PrtSc // // For this reason, if we encounter the first keypress, we simply ignore it, trusting From 3930a6334ffc18eca06f47651eda7de3c239cb3d Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 11 May 2025 20:13:59 +0900 Subject: [PATCH 097/116] ci/deny: allow scripts in zerocopy --- deny.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deny.toml b/deny.toml index a6aef58f06..fda5952e37 100644 --- a/deny.toml +++ b/deny.toml @@ -53,6 +53,10 @@ allow = [ ] crate = "android-activity" +[[bans.build.bypass]] +allow-globs = ["ci/*", "githooks/*"] +crate = "zerocopy" + [[bans.build.bypass]] allow-globs = ["freetype2/*"] crate = "freetype-sys" From bd2b5cda8d5d843a1771341e1aa7b33c91495c0a Mon Sep 17 00:00:00 2001 From: Varphone Wong Date: Wed, 14 May 2025 20:31:48 +0800 Subject: [PATCH 098/116] windows: Fix crash in for Windows versions < 17763 In Windows versions < 17763, `GetProcAddress("#132")` from `uxtheme.dll` also returns a non-null pointer. However, the function does not match the expected `extern "system" fn() -> bool` prototype, which causes a crash when it is called. This fix ensures compatibility by adding proper checks to prevent such crashes on older Windows versions. --- src/changelog/unreleased.md | 4 ++++ src/platform_impl/windows/dark_mode.rs | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..5a50389ae1 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Fixed + +- On Windows, fixed crash in should_apps_use_dark_mode() for Windows versions < 17763. diff --git a/src/platform_impl/windows/dark_mode.rs b/src/platform_impl/windows/dark_mode.rs index 9d4bad9279..366e44c685 100644 --- a/src/platform_impl/windows/dark_mode.rs +++ b/src/platform_impl/windows/dark_mode.rs @@ -132,7 +132,13 @@ fn should_apps_use_dark_mode() -> bool { static SHOULD_APPS_USE_DARK_MODE: Lazy> = Lazy::new(|| unsafe { const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: PCSTR = 132 as PCSTR; - let module = LoadLibraryA("uxtheme.dll\0".as_ptr()); + // We won't try to do anything for windows versions < 17763 + // (Windows 10 October 2018 update) + if !*DARK_MODE_SUPPORTED { + return None; + } + + let module = LoadLibraryA("uxtheme.dll\0".as_ptr().cast()); if module == 0 { return None; From f7ac8127e38b54f71033035acbf90be8a962d1c5 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 17 May 2025 13:23:01 +0900 Subject: [PATCH 099/116] wayland: fix pump events's loop drop deadlock --- src/changelog/unreleased.md | 1 + src/platform_impl/linux/wayland/event_loop/mod.rs | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 5a50389ae1..4094853293 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -43,3 +43,4 @@ changelog entry. ### Fixed - On Windows, fixed crash in should_apps_use_dark_mode() for Windows versions < 17763. +- On Wayland, fixed `pump_events` driven loop deadlocking when loop was not drained before exit. diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 99b282cb4c..fef13f4558 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -734,7 +734,7 @@ impl Drop for PumpEventNotifier { if let Some(worker_waker) = self.worker_waker.as_ref() { let _ = rustix::io::write(worker_waker.as_fd(), &[0u8]); } - *self.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor; + *self.control.0.lock().unwrap() = PumpEventNotifierAction::Shutdown; self.control.1.notify_one(); if let Some(handle) = self.handle.take() { @@ -762,6 +762,14 @@ impl PumpEventNotifier { while *wait == PumpEventNotifierAction::Pause { wait = cvar.wait(wait).unwrap(); } + + // Exit the loop when we're asked to. Given that we poll + // only once we can take the `prepare_read`, but in some cases + // it could be not possible, we may block on `join`. + if *wait == PumpEventNotifierAction::Shutdown { + break 'outer; + } + // Wake-up the main loop and put this one back to sleep. *wait = PumpEventNotifierAction::Pause; drop(wait); @@ -797,4 +805,6 @@ enum PumpEventNotifierAction { Monitor, /// Pause monitoring. Pause, + /// Shutdown the thread. + Shutdown, } From 2191eacfc8b824657d8aa8aa27d44e0ca56d142d Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 17 May 2025 14:46:18 +0900 Subject: [PATCH 100/116] chore: appease clippy --- src/platform_impl/linux/mod.rs | 2 ++ src/platform_impl/linux/x11/util/randr.rs | 2 +- src/platform_impl/linux/x11/window.rs | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 362573f7e9..bc0e71c80c 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -696,6 +696,7 @@ unsafe extern "C" fn x_error_callback( 0 } +#[allow(clippy::large_enum_variant)] pub enum EventLoop { #[cfg(wayland_platform)] Wayland(Box>), @@ -849,6 +850,7 @@ impl EventLoopProxy { } } +#[allow(clippy::large_enum_variant)] pub enum ActiveEventLoop { #[cfg(wayland_platform)] Wayland(wayland::ActiveEventLoop), diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 6097bc517c..d10c97e5d7 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -79,7 +79,7 @@ impl XConnection { .iter() // XRROutputInfo contains an array of mode ids that correspond to // modes in the array in XRRScreenResources - .filter(|x| output_modes.iter().any(|id| x.id == *id)) + .filter(|x| output_modes.contains(&x.id)) .map(|mode| { VideoModeHandle { size: (mode.width.into(), mode.height.into()), diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index ec1b0420b4..2fd069fc55 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -976,8 +976,8 @@ impl UnownedWindow { let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT]; match state { Ok(atoms) => { - let horz_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == horz_atom); - let vert_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == vert_atom); + let horz_maximized = atoms.contains(&horz_atom); + let vert_maximized = atoms.contains(&vert_atom); horz_maximized && vert_maximized }, _ => false, From 911fad0af0a3c88c52f60c72d43159a8ccfb4937 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 17 May 2025 14:07:00 +0900 Subject: [PATCH 101/116] Winit version 0.30.11 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 5 ----- src/changelog/v0.30.md | 7 +++++++ src/platform/android.rs | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index baee02c4a4..b914b5e411 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.10" +version = "0.30.11" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index bae58e404c..4fc5b7f8ed 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.10" +winit = "0.30.11" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 4094853293..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,8 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Fixed - -- On Windows, fixed crash in should_apps_use_dark_mode() for Windows versions < 17763. -- On Wayland, fixed `pump_events` driven loop deadlocking when loop was not drained before exit. diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index 41bd5b571e..76fef76263 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,10 @@ +## 0.30.11 + +### Fixed + +- On Windows, fixed crash in should_apps_use_dark_mode() for Windows versions < 17763. +- On Wayland, fixed `pump_events` driven loop deadlocking when loop was not drained before exit. + ## 0.30.10 ### Added diff --git a/src/platform/android.rs b/src/platform/android.rs index 1a8c2cd5cb..65d6717027 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.10", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.11", //! 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 From cc43ea13d97d736fc12427d03c426dedaf736c1c Mon Sep 17 00:00:00 2001 From: Robert Wallis Date: Sun, 20 Jul 2025 01:04:04 -0700 Subject: [PATCH 102/116] macOS: fix runtime crash on macos26 "type code 'q', but found 'Q'" Fixes #4299. --- Cargo.toml | 2 +- src/changelog/unreleased.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b914b5e411..582237a538 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,7 +119,7 @@ ndk = { version = "0.9.0", default-features = false } [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] block2 = "0.5.1" core-foundation = "0.9.3" -objc2 = "0.5.2" +objc2 = { version = "0.5.2", features = ["relax-sign-encoding"] } [target.'cfg(target_os = "macos")'.dependencies] core-graphics = "0.23.1" diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..a7ec5f0acc 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Fixed + +- On macOS, fix crash on macOS 26 by using objc2's `relax-sign-encoding` feature. From b49d34ebf001d3ce7b0f407e3a957d880cf50b78 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 26 May 2025 14:08:30 +0900 Subject: [PATCH 103/116] ci/deny: add rustix Will take a while to move to 1.0 for everyone. --- deny.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deny.toml b/deny.toml index fda5952e37..fdaa4b9083 100644 --- a/deny.toml +++ b/deny.toml @@ -40,8 +40,10 @@ 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 = "rustix@0.38", reason = "the ecosystem is in the process of migrating" }, + { crate = "linux-raw-sys@0.4", reason = "the ecosystem is in the process of migrating" }, ] -wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed +wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed [bans.build] include-archives = true From b248ecba31f1c1b03c73f5448f87108aa1130526 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 22 Jun 2025 19:29:27 +0900 Subject: [PATCH 104/116] winit: silence wasm on nightly The lint is needed for stable, but is no longer present on nightly, so silence it for the time being. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9743e49990..b4a2bffc9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -186,7 +186,7 @@ #![allow(clippy::missing_safety_doc)] #![warn(clippy::uninlined_format_args)] // TODO: wasm-binding needs to be updated for that to be resolved, for now just silence it. -#![cfg_attr(web_platform, allow(unknown_lints, wasm_c_abi))] +#![cfg_attr(web_platform, allow(unknown_lints, renamed_and_removed_lints, wasm_c_abi))] #[cfg(feature = "rwh_04")] pub use rwh_04 as raw_window_handle_04; From c0a8bedee25083ff2d3ddfe0a9514af6afac47e8 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 23 Jul 2025 10:34:44 +0900 Subject: [PATCH 105/116] chore: fix typos from updated `typos` tool --- src/changelog/v0.29.md | 2 +- src/platform_impl/linux/wayland/seat/keyboard/mod.rs | 2 +- src/platform_impl/windows/event_loop.rs | 2 +- src/platform_impl/windows/keyboard.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/changelog/v0.29.md b/src/changelog/v0.29.md index 4558dd6090..fc17f1701a 100644 --- a/src/changelog/v0.29.md +++ b/src/changelog/v0.29.md @@ -250,7 +250,7 @@ - On Web, fix some `WindowBuilder` methods doing nothing. - On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties. - On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events. -- On Web, fix touch input not gaining or loosing focus. +- On Web, fix touch input not gaining or losing focus. - On Web, fix touch location to be as accurate as mouse position. - On Web, handle coalesced pointer events, which increases the resolution of pointer inputs. - On Web, implement `Window::focus_window()`. diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index ef4f99520b..f84c386735 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -99,7 +99,7 @@ impl Dispatch for WinitState { WlKeyboardEvent::Leave { surface, .. } => { let window_id = wayland::make_wid(&surface); - // NOTE: we should drop the repeat regardless whethere it was for the present + // NOTE: we should drop the repeat regardless whether it was for the present // window of for the window which just went gone. keyboard_state.current_repeat = None; if let Some(token) = keyboard_state.repeat_token.take() { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 1fe748c67e..2a615e7b40 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -738,7 +738,7 @@ fn wait_for_messages_impl( unsafe { // Either: - // 1. User wants to wait indefinely if timeout is not set. + // 1. User wants to wait indefinitely 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); diff --git a/src/platform_impl/windows/keyboard.rs b/src/platform_impl/windows/keyboard.rs index 59c4931a65..9c109490eb 100644 --- a/src/platform_impl/windows/keyboard.rs +++ b/src/platform_impl/windows/keyboard.rs @@ -465,7 +465,7 @@ enum PartialText { enum PartialLogicalKey { /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If - /// the text consists of multiple grapheme clusters (user-precieved characters) that means that + /// the text consists of multiple grapheme clusters (user-perceived characters) that means that /// dead key could not be combined with the second input, and in that case we should fall back /// to using what would have without a dead-key input. TextOr(Key), From f6893a4390dfe6118ce4b33458d458fd3efd3025 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 23 Jul 2025 10:19:50 +0900 Subject: [PATCH 106/116] Winit version 0.30.12 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/v0.30.md | 6 ++++++ src/platform/android.rs | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 582237a538..bb4d72ad2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.11" +version = "0.30.12" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index 4fc5b7f8ed..ab230d7533 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.11" +winit = "0.30.12" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index 76fef76263..d6c6ef6b07 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,9 @@ +## 0.30.12 + +### Fixed + +- On macOS, fix crash on macOS 26 by using objc2's `relax-sign-encoding` feature. + ## 0.30.11 ### Fixed diff --git a/src/platform/android.rs b/src/platform/android.rs index 65d6717027..732a9075df 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.11", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.12", //! 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 From ab4c6bfc82686c4cb7bfec639b5b28cdc5b2c81f Mon Sep 17 00:00:00 2001 From: Dan Harris Date: Tue, 4 Nov 2025 20:20:01 -0800 Subject: [PATCH 107/116] macOS: fix a crash when dragging non-file content onto window Winit only supports text, thus we should ignore the rest instead of crashing. --- src/changelog/unreleased.md | 2 +- src/platform_impl/macos/window_delegate.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index a7ec5f0acc..eee5740d89 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -42,4 +42,4 @@ changelog entry. ### Fixed -- On macOS, fix crash on macOS 26 by using objc2's `relax-sign-encoding` feature. +- On macOS, fixed crash when dragging non-file content onto window. diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index e0dc693c5b..e293505ebe 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -373,7 +373,10 @@ declare_class!( use std::path::PathBuf; let pb: Retained = unsafe { msg_send_id![sender, draggingPasteboard] }; - let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap(); + let filenames = match pb.propertyListForType(unsafe { NSFilenamesPboardType }) { + Some(filenames) => filenames, + None => return false.into(), + }; let filenames: Retained> = unsafe { Retained::cast(filenames) }; filenames.into_iter().for_each(|file| { @@ -399,7 +402,10 @@ declare_class!( use std::path::PathBuf; let pb: Retained = unsafe { msg_send_id![sender, draggingPasteboard] }; - let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap(); + let filenames = match pb.propertyListForType(unsafe { NSFilenamesPboardType }) { + Some(filenames) => filenames, + None => return false.into(), + }; let filenames: Retained> = unsafe { Retained::cast(filenames) }; filenames.into_iter().for_each(|file| { From 7035dd554f2475932291bb9cb7084f05f1f75974 Mon Sep 17 00:00:00 2001 From: itsamine27 Date: Tue, 16 Dec 2025 14:50:14 +0100 Subject: [PATCH 108/116] winit-win32: Fix ABI mismatch in INIT_MAIN_THREAD_ID Fixes #4435. --- src/platform_impl/windows/event_loop.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 2a615e7b40..3771cb6c37 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -574,9 +574,11 @@ fn main_thread_id() -> u32 { // // See: https://doc.rust-lang.org/stable/reference/abi.html#the-link_section-attribute #[link_section = ".CRT$XCU"] - static INIT_MAIN_THREAD_ID: unsafe fn() = { - unsafe fn initer() { - unsafe { MAIN_THREAD_ID = GetCurrentThreadId() }; + static INIT_MAIN_THREAD_ID: unsafe extern "C" fn() = { + unsafe extern "C" fn initer() { + unsafe { + MAIN_THREAD_ID = GetCurrentThreadId(); + } } initer }; From 3eb731f8b516df1fb7917ee76275f18cf7997101 Mon Sep 17 00:00:00 2001 From: Silico_Biomancer Date: Tue, 25 Nov 2025 15:06:39 +1300 Subject: [PATCH 109/116] winit-x11: replace xfixes with x11rb in set_hittest The xfixes implementation is not that reliable and rather simple to replace, so use x11rb to implement the same functionality. Fixes #4120. Co-authored-by: avitran0 --- src/changelog/unreleased.md | 1 + src/platform_impl/linux/x11/window.rs | 27 +++++++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index eee5740d89..58f0de13db 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -43,3 +43,4 @@ changelog entry. ### Fixed - On macOS, fixed crash when dragging non-file content onto window. +- On X11, fix `set_hittest` not working on some window managers. diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 2fd069fc55..7e15d5f242 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -8,9 +8,8 @@ use std::{cmp, env}; use tracing::{debug, info, warn}; use x11rb::connection::Connection; use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification}; -use x11rb::protocol::shape::SK; -use x11rb::protocol::xfixes::{ConnectionExt, RegionWrapper}; -use x11rb::protocol::xproto::{self, ConnectionExt as _, Rectangle}; +use x11rb::protocol::shape::{ConnectionExt as ShapeExt, SK, SO}; +use x11rb::protocol::xproto::{self, ClipOrdering, ConnectionExt as _, Rectangle}; use x11rb::protocol::{randr, xinput}; use crate::cursor::{Cursor, CustomCursor as RootCustomCursor}; @@ -1622,6 +1621,15 @@ impl UnownedWindow { #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { + // Implement cursor hittest for X11 by either setting an empty or full window input shape. + + // In X11, every window has two "shapes": + // * Bounding shape: defines the visible outline of the window. + // * Input shape: defines the region of the window that receives pointer/keyboard events. + // If the input shape is the full window rectangle, the window behaves normally. + // If the input shape is empty, the window is completely click‑through. + // Here, we implement hit test by mapping `hittest = true` to "restore a full input shape" + // and `hittest = false` to "clear the input shape" (empty list of rectangles). let mut rectangles: Vec = Vec::new(); if hittest { let size = self.inner_size(); @@ -1632,11 +1640,18 @@ impl UnownedWindow { height: size.height as u16, }) } - let region = RegionWrapper::create_region(self.xconn.xcb_connection(), &rectangles) - .map_err(|_e| ExternalError::Ignored)?; + self.xconn .xcb_connection() - .xfixes_set_window_shape_region(self.xwindow, SK::INPUT, 0, 0, region.region()) + .shape_rectangles( + SO::SET, + SK::INPUT, + ClipOrdering::UNSORTED, + self.xwindow, + 0, + 0, + &rectangles, + ) .map_err(|_e| ExternalError::Ignored)?; self.shared_state_lock().cursor_hittest = Some(hittest); Ok(()) From 69b8a07ae01d8debeea710addf8c4a57b1833e2d Mon Sep 17 00:00:00 2001 From: SuchAFuriousDeath <48620541+SuchAFuriousDeath@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:15:49 +0100 Subject: [PATCH 110/116] winit-x11: fix debug mode overflow panic in `set_timestamp` Fixes #4484 --- src/changelog/unreleased.md | 1 + src/platform_impl/linux/x11/xdisplay.rs | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 58f0de13db..7cfbe9145b 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -44,3 +44,4 @@ changelog entry. - On macOS, fixed crash when dragging non-file content onto window. - On X11, fix `set_hittest` not working on some window managers. +- On X11, fix debug mode overflow panic in `set_timestamp`. diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index 902d0ce4cb..79da0d1faa 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -234,9 +234,7 @@ impl XConnection { // Store the timestamp in the slot if it's greater than the last one. let mut last_timestamp = self.timestamp.load(Ordering::Relaxed); loop { - let wrapping_sub = |a: xproto::Timestamp, b: xproto::Timestamp| (a as i32) - (b as i32); - - if wrapping_sub(timestamp, last_timestamp) <= 0 { + if (timestamp as i32).wrapping_sub(last_timestamp as i32) <= 0 { break; } From bccc568345f4029bc5d33d3cb1c6923c3c0cf8bb Mon Sep 17 00:00:00 2001 From: Takaranoao Date: Sun, 1 Mar 2026 04:55:06 -0800 Subject: [PATCH 111/116] fix(macOS): clamp IME selected_range to prevent substringToIndex crash macOS native Pinyin IME can send a selected_range that exceeds the marked text string length (e.g. index 8 for a 6-character string). This caused an NSRangeException in substringToIndex:, crashing the application with SIGABRT. Clamp both location and end to the string's UTF-16 length before calling substringToIndex. --- src/changelog/unreleased.md | 1 + src/platform_impl/macos/view.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 7cfbe9145b..6dc9fe4895 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -45,3 +45,4 @@ changelog entry. - On macOS, fixed crash when dragging non-file content onto window. - On X11, fix `set_hittest` not working on some window managers. - On X11, fix debug mode overflow panic in `set_timestamp`. +- On macOS, fix crash in `set_marked_text` when native Pinyin IME sends out-of-bounds `selected_range`. diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index d2a9948fc1..c7ca5bc6ec 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -316,9 +316,15 @@ declare_class!( // sending a `None` cursor range. None } else { + // Clamp to string length to avoid NSRangeException from out-of-bounds + // indices sent by macOS IME (e.g. native Pinyin, see + // https://github.com/alacritty/alacritty/issues/8791). + let len = string.length(); + let location = selected_range.location.min(len); + let end = selected_range.end().min(len); // Convert the selected range from UTF-16 indices to UTF-8 indices. - let sub_string_a = unsafe { string.substringToIndex(selected_range.location) }; - let sub_string_b = unsafe { string.substringToIndex(selected_range.end()) }; + let sub_string_a = unsafe { string.substringToIndex(location) }; + let sub_string_b = unsafe { string.substringToIndex(end) }; let lowerbound_utf8 = sub_string_a.len(); let upperbound_utf8 = sub_string_b.len(); Some((lowerbound_utf8, upperbound_utf8)) From 17a73f4dd4fa2d9fcc16ecb5dbbba5f26646d7d1 Mon Sep 17 00:00:00 2001 From: Kotomine Shiki <126475460+Touma-Kazusa2@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:08:45 +0800 Subject: [PATCH 112/116] win32: fix ime setcontext lparam Fixes #3893. --- src/changelog/unreleased.md | 1 + src/platform_impl/windows/event_loop.rs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 6dc9fe4895..5ed7d6bb5c 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -46,3 +46,4 @@ changelog entry. - On X11, fix `set_hittest` not working on some window managers. - On X11, fix debug mode overflow panic in `set_timestamp`. - On macOS, fix crash in `set_marked_text` when native Pinyin IME sends out-of-bounds `selected_range`. +- On Windows, fix `WM_IME_SETCONTEXT` IME UI flag masking on `lParam`. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3771cb6c37..58b00ded95 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1612,9 +1612,9 @@ unsafe fn public_window_callback_inner( }, WM_IME_SETCONTEXT => { - // Hide composing text drawn by IME. - let wparam = wparam & (!ISC_SHOWUICOMPOSITIONWINDOW as usize); - result = ProcResult::DefWindowProc(wparam); + // IME UI visibility flags are in lparam. + let lparam = lparam & !(ISC_SHOWUICOMPOSITIONWINDOW as isize); + result = ProcResult::Value(unsafe { DefWindowProcW(window, msg, wparam, lparam) }); }, // this is necessary for us to maintain minimize/restore state From 6bb43fd130a4fa37a2335ac5ba5858bcc99756fa Mon Sep 17 00:00:00 2001 From: Pedro Macedo Date: Sun, 1 Mar 2026 10:28:35 -0300 Subject: [PATCH 113/116] wayland: implement resize increments --- src/changelog/unreleased.md | 4 ++ src/platform_impl/linux/wayland/window/mod.rs | 19 +++++-- .../linux/wayland/window/state.rs | 50 +++++++++++++++++++ src/window.rs | 3 +- 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 5ed7d6bb5c..e70657fa72 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -40,6 +40,10 @@ changelog entry. ## Unreleased +### Added + +- On Wayland, add `Window::set_resize_increments`. + ### Fixed - On macOS, fixed crash when dragging non-file content onto window. diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 047affeb0d..6d29a5a5f0 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -166,6 +166,12 @@ impl Window { Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor), } + // Apply resize increments. + if let Some(increments) = attributes.resize_increments { + let increments = increments.to_logical(window_state.scale_factor()); + window_state.set_resize_increments(Some(increments)); + } + // Activate the window when the token is passed. if let (Some(xdg_activation), Some(token)) = (xdg_activation.as_ref(), attributes.platform_specific.activation_token) @@ -333,12 +339,19 @@ impl Window { #[inline] pub fn resize_increments(&self) -> Option> { - None + let window_state = self.window_state.lock().unwrap(); + let scale_factor = window_state.scale_factor(); + window_state + .resize_increments() + .map(|size| super::logical_to_physical_rounded(size, scale_factor)) } #[inline] - pub fn set_resize_increments(&self, _increments: Option) { - warn!("`set_resize_increments` is not implemented for Wayland"); + pub fn set_resize_increments(&self, increments: Option) { + let mut window_state = self.window_state.lock().unwrap(); + let scale_factor = window_state.scale_factor(); + let increments = increments.map(|size| size.to_logical(scale_factor)); + window_state.set_resize_increments(increments); } #[inline] diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index 846dc44c2e..246ba3a706 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -127,6 +127,7 @@ pub struct WindowState { /// Min size. min_inner_size: LogicalSize, max_inner_size: Option>, + resize_increments: Option>, /// The size of the window when no states were applied to it. The primary use for it /// is to fallback to original window size, before it was maximized, if the compositor @@ -202,6 +203,7 @@ impl WindowState { last_configure: None, max_inner_size: None, min_inner_size: MIN_WINDOW_SIZE, + resize_increments: None, pointer_constraints, pointers: Default::default(), queue_handle: queue_handle.clone(), @@ -340,6 +342,42 @@ impl WindowState { .unwrap_or(new_size.height); } + // Apply size increments. + // + // We conditionally apply increments to avoid conflicts with the compositor's layout rules: + // 1. If the window is floating (constrain == true), we snap to increments to ensure the + // app's grid alignment. + // 2. If the user is interactively resizing (is_resizing), we snap the size to provide + // feedback. + // + // However, we MUST NOT snap if the compositor enforces a specific size (constrain == false, + // or states like Maximized/Tiled). Snapping in these cases (e.g. corner tiling) would + // shrink the window below the allocated area, creating visible gaps between valid + // windows or screen edges. + if (constrain || configure.is_resizing()) + && !configure.is_maximized() + && !configure.is_fullscreen() + && !configure.is_tiled() + { + if let Some(increments) = self.resize_increments { + // We use min size as a base size for the increments, similar to how X11 does it. + // + // This ensures that we can always reach the min size and the increments are + // calculated from it. + let (delta_width, delta_height) = ( + new_size.width.saturating_sub(self.min_inner_size.width), + new_size.height.saturating_sub(self.min_inner_size.height), + ); + + let width = self.min_inner_size.width + + (delta_width / increments.width) * increments.width; + let height = self.min_inner_size.height + + (delta_height / increments.height) * increments.height; + + new_size = (width, height).into(); + } + } + let new_state = configure.state; let old_state = self.last_configure.as_ref().map(|configure| configure.state); @@ -725,6 +763,18 @@ impl WindowState { self.selected_cursor = SelectedCursor::Custom(cursor); } + /// Set the resize increments of the window. + pub fn set_resize_increments(&mut self, increments: Option>) { + self.resize_increments = increments; + // NOTE: We don't update the window size here, because it will be done on the next resize + // or configure event. + } + + /// Get the resize increments of the window. + pub fn resize_increments(&self) -> Option> { + self.resize_increments + } + fn apply_custom_cursor(&self, cursor: &CustomCursor) { self.apply_on_pointer(|pointer, data| { let surface = pointer.surface(); diff --git a/src/window.rs b/src/window.rs index 0db22bb327..90848d334e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -884,7 +884,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland / Orbital:** Always returns [`None`]. + /// - **iOS / Android / Web / Orbital:** Always returns [`None`]. #[inline] pub fn resize_increments(&self) -> Option> { let _span = tracing::debug_span!("winit::Window::resize_increments",).entered(); @@ -900,7 +900,6 @@ impl Window { /// /// - **macOS:** Increments are converted to logical size and then macOS rounds them to whole /// numbers. - /// - **Wayland:** Not implemented. /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_resize_increments>(&self, increments: Option) { From a9baf5ecdaf822afd474c525da241ba0f41692e3 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 2 Mar 2026 22:42:50 +0900 Subject: [PATCH 114/116] fix(android): Populate `KeyEvent.text` via `Key::to_text()` The `text` field on `KeyEvent` was hardcoded to `None` on Android, making it impossible for custom `NativeActivity` subclasses that show the IME to receive functional text input using *for example* the existing `winit-egui` crate which relies on this field being set. Use `Key::to_text()` on press events to derive `text` from `logical_key`, matching the convention used by the Windows and macOS backends. Supposedly that doesn't include all kinds of special virtual unicode keys, but at least the basics work this way. --- src/changelog/unreleased.md | 1 + src/platform_impl/android/mod.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index e70657fa72..f936e520a0 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -51,3 +51,4 @@ changelog entry. - On X11, fix debug mode overflow panic in `set_timestamp`. - On macOS, fix crash in `set_marked_text` when native Pinyin IME sends out-of-bounds `selected_range`. - On Windows, fix `WM_IME_SETCONTEXT` IME UI flag masking on `lParam`. +- On Android, populate `KeyEvent::text` and `KeyEvent::text_with_all_modifiers` via `Key::to_text()` diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index bc0ad680e5..c6a7416f46 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -446,6 +446,13 @@ impl EventLoop { &mut self.combining_accent, ); + let logical_key = keycodes::to_logical(key_char, keycode); + let text = if state == event::ElementState::Pressed { + logical_key.to_text().map(smol_str::SmolStr::new) + } else { + None + }; + let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::KeyboardInput { @@ -453,10 +460,10 @@ impl EventLoop { event: event::KeyEvent { state, physical_key: keycodes::to_physical_key(keycode), - logical_key: keycodes::to_logical(key_char, keycode), + logical_key, location: keycodes::to_location(keycode), repeat: key.repeat_count() > 0, - text: None, + text, platform_specific: KeyEventExtra {}, }, is_synthetic: false, From efb5b37fffbad99046e9c9c649bee7d39cdedc91 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 1 Mar 2026 23:08:17 +0900 Subject: [PATCH 115/116] chore: fix ci --- .github/workflows/ci.yml | 5 +++++ deny.toml | 2 +- src/event.rs | 2 +- src/lib.rs | 2 +- src/platform_impl/linux/wayland/window/state.rs | 4 ++-- src/platform_impl/linux/x11/ime/context.rs | 4 ++-- src/platform_impl/linux/x11/ime/inner.rs | 7 +++---- src/platform_impl/macos/event.rs | 2 +- src/platform_impl/windows/event_loop.rs | 2 +- src/window.rs | 9 ++------- 10 files changed, 19 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2e85a932c..aa80f40957 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,8 +111,13 @@ jobs: cargo generate-lockfile cargo update -p ahash --precise 0.8.7 cargo update -p bumpalo --precise 3.14.0 + cargo update -p softbuffer --precise 0.4.0 cargo update -p objc2-encode --precise 4.0.3 cargo update -p orbclient --precise 0.3.47 + cargo update -p image --precise 0.25.0 + cargo update -p gethostname@1.1.0 --precise 1.0.2 + cargo update -p unicode-ident --precise 1.0.10 + cargo update -p syn --precise 2.0.114 - name: Install GCC Multilib if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') diff --git a/deny.toml b/deny.toml index fdaa4b9083..735c336fd9 100644 --- a/deny.toml +++ b/deny.toml @@ -56,7 +56,7 @@ allow = [ crate = "android-activity" [[bans.build.bypass]] -allow-globs = ["ci/*", "githooks/*"] +allow-globs = ["ci/*", "githooks/*", "cargo.sh"] crate = "zerocopy" [[bans.build.bypass]] diff --git a/src/event.rs b/src/event.rs index 1890aea97d..4e01420af3 100644 --- a/src/event.rs +++ b/src/event.rs @@ -621,7 +621,7 @@ pub struct KeyEvent { /// /// # Example /// - /// In games, you often want to ignore repated key events - this can be + /// In games, you often want to ignore repeated key events - this can be /// done by ignoring events where this property is set. /// /// ``` diff --git a/src/lib.rs b/src/lib.rs index b4a2bffc9f..41a55b234e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,7 +182,7 @@ #![cfg_attr(clippy, deny(warnings))] // Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly // doc -#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))] +#![cfg_attr(docsrs, feature(doc_cfg), doc(auto_cfg(hide(doc, docsrs))))] #![allow(clippy::missing_safety_doc)] #![warn(clippy::uninlined_format_args)] // TODO: wasm-binding needs to be updated for that to be resolved, for now just silence it. diff --git a/src/platform_impl/linux/wayland/window/state.rs b/src/platform_impl/linux/wayland/window/state.rs index 246ba3a706..1ef7a0656c 100644 --- a/src/platform_impl/linux/wayland/window/state.rs +++ b/src/platform_impl/linux/wayland/window/state.rs @@ -369,8 +369,8 @@ impl WindowState { new_size.height.saturating_sub(self.min_inner_size.height), ); - let width = self.min_inner_size.width - + (delta_width / increments.width) * increments.width; + let width = + self.min_inner_size.width + (delta_width / increments.width) * increments.width; let height = self.min_inner_size.height + (delta_height / increments.height) * increments.height; diff --git a/src/platform_impl/linux/x11/ime/context.rs b/src/platform_impl/linux/x11/ime/context.rs index e90836a5f2..2c6c075e56 100644 --- a/src/platform_impl/linux/x11/ime/context.rs +++ b/src/platform_impl/linux/x11/ime/context.rs @@ -81,7 +81,7 @@ extern "C" fn preedit_draw_callback( call_data.chg_first as usize..(call_data.chg_first + call_data.chg_length) as usize; if chg_range.start > client_data.text.len() || chg_range.end > client_data.text.len() { tracing::warn!( - "invalid chg range: buffer length={}, but chg_first={} chg_lengthg={}", + "invalid chg range: buffer length={}, but chg_first={} chg_length={}", client_data.text.len(), call_data.chg_first, call_data.chg_length @@ -158,7 +158,7 @@ impl PreeditCallbacks { pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks { let start_callback = create_xim_callback(client_data, unsafe { mem::transmute::( - preedit_start_callback as usize, + preedit_start_callback as *const () as usize, ) }); let done_callback = create_xim_callback(client_data, preedit_done_callback); diff --git a/src/platform_impl/linux/x11/ime/inner.rs b/src/platform_impl/linux/x11/ime/inner.rs index 4d4f7cb473..da1ccf4016 100644 --- a/src/platform_impl/linux/x11/ime/inner.rs +++ b/src/platform_impl/linux/x11/ime/inner.rs @@ -51,10 +51,9 @@ impl ImeInner { } pub unsafe fn close_im_if_necessary(&self) -> Result { - if !self.is_destroyed && self.im.is_some() { - unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true) - } else { - Ok(false) + match self.im.as_ref() { + Some(im) if !self.is_destroyed => unsafe { close_im(&self.xconn, im.im).map(|_| true) }, + _ => Ok(false), } } diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 4eacb3dc44..9b7f35b02f 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -128,7 +128,7 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo let logical_key = match text_with_all_modifiers.as_ref() { // Only checking for ctrl and cmd here, not checking for alt because we DO want to - // include its effect in the key. For example if -on the Germay layout- one + // include its effect in the key. For example if -on the German layout- one // presses alt+8, the logical key should be "{" // Also not checking if this is a release event because then this issue would // still affect the key release. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 58b00ded95..6d60c1c524 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -2641,7 +2641,7 @@ unsafe fn handle_raw_input(userdata: &ThreadMsgTargetData, data: RAWINPUT) { } enum PointerMoveKind { - /// Pointer enterd to the window. + /// Pointer entered to the window. Enter, /// Pointer leaved the window client area. Leave, diff --git a/src/window.rs b/src/window.rs index 90848d334e..e0158eff52 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1824,10 +1824,11 @@ pub enum WindowLevel { /// ## Platform-specific /// /// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] #[non_exhaustive] pub enum ImePurpose { /// No special hints for the IME (default). + #[default] Normal, /// The IME is used for password input. Password, @@ -1837,12 +1838,6 @@ pub enum ImePurpose { Terminal, } -impl Default for ImePurpose { - fn default() -> Self { - Self::Normal - } -} - /// An opaque token used to activate the [`Window`]. /// /// [`Window`]: crate::window::Window From e9809ef54b18499bb4f2cac945719ecc2a61061b Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 1 Mar 2026 23:02:21 +0900 Subject: [PATCH 116/116] Winit version 0.30.13 --- Cargo.toml | 2 +- README.md | 2 +- src/changelog/unreleased.md | 13 ------------- src/changelog/v0.30.md | 15 +++++++++++++++ src/platform/android.rs | 2 +- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bb4d72ad2a..71f7c58113 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.30.12" +version = "0.30.13" authors = [ "The winit contributors", "Pierre Krieger ", diff --git a/README.md b/README.md index ab230d7533..ab958874a7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ```toml [dependencies] -winit = "0.30.12" +winit = "0.30.13" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f936e520a0..f3a0f6d2c3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,16 +39,3 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased - -### Added - -- On Wayland, add `Window::set_resize_increments`. - -### Fixed - -- On macOS, fixed crash when dragging non-file content onto window. -- On X11, fix `set_hittest` not working on some window managers. -- On X11, fix debug mode overflow panic in `set_timestamp`. -- On macOS, fix crash in `set_marked_text` when native Pinyin IME sends out-of-bounds `selected_range`. -- On Windows, fix `WM_IME_SETCONTEXT` IME UI flag masking on `lParam`. -- On Android, populate `KeyEvent::text` and `KeyEvent::text_with_all_modifiers` via `Key::to_text()` diff --git a/src/changelog/v0.30.md b/src/changelog/v0.30.md index d6c6ef6b07..6b0b47dd3b 100644 --- a/src/changelog/v0.30.md +++ b/src/changelog/v0.30.md @@ -1,3 +1,18 @@ +## 0.30.13 + +### Added + +- On Wayland, add `Window::set_resize_increments`. + +### Fixed + +- On macOS, fixed crash when dragging non-file content onto window. +- On X11, fix `set_hittest` not working on some window managers. +- On X11, fix debug mode overflow panic in `set_timestamp`. +- On macOS, fix crash in `set_marked_text` when native Pinyin IME sends out-of-bounds `selected_range`. +- On Windows, fix `WM_IME_SETCONTEXT` IME UI flag masking on `lParam`. +- On Android, populate `KeyEvent::text` and `KeyEvent::text_with_all_modifiers` via `Key::to_text()`. + ## 0.30.12 ### Fixed diff --git a/src/platform/android.rs b/src/platform/android.rs index 732a9075df..b76159c095 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.12", +//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.13", //! 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