diff --git a/winit-android/src/event_loop.rs b/winit-android/src/event_loop.rs index 62895a34d2..c4c3181773 100644 --- a/winit-android/src/event_loop.rs +++ b/winit-android/src/event_loop.rs @@ -199,12 +199,12 @@ impl EventLoop { MainEvent::GainedFocus => { HAS_FOCUS.store(true, Ordering::Relaxed); let event = event::WindowEvent::Focused(true); - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); }, MainEvent::LostFocus => { HAS_FOCUS.store(false, Ordering::Relaxed); let event = event::WindowEvent::Focused(false); - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); }, MainEvent::ConfigChanged { .. } => { let old_scale_factor = scale_factor(&self.android_app); @@ -218,7 +218,7 @@ impl EventLoop { scale_factor, }; - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); } }, MainEvent::LowMemory => { @@ -295,14 +295,14 @@ impl EventLoop { PhysicalSize::new(0, 0) }; let event = event::WindowEvent::SurfaceResized(size); - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); } pending_redraw |= self.redraw_flag.get_and_reset(); if pending_redraw { pending_redraw = false; let event = event::WindowEvent::RedrawRequested; - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); } } @@ -369,7 +369,7 @@ impl EventLoop { _ => event::PointerKind::Unknown, }, }; - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); let event = event::WindowEvent::PointerButton { device_id, primary, @@ -384,7 +384,7 @@ impl EventLoop { _ => event::ButtonSource::Unknown(0), }, }; - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); }, MotionAction::Move => { let primary = self.primary_pointer == Some(finger_id); @@ -401,7 +401,7 @@ impl EventLoop { _ => event::PointerSource::Unknown, }, }; - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); }, MotionAction::Up | MotionAction::PointerUp | MotionAction::Cancel => { let primary = action == MotionAction::Up @@ -427,7 +427,7 @@ impl EventLoop { _ => event::ButtonSource::Unknown(0), }, }; - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); } let event = event::WindowEvent::PointerLeft { @@ -443,7 +443,7 @@ impl EventLoop { _ => event::PointerKind::Unknown, }, }; - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); }, _ => unreachable!(), } @@ -495,7 +495,7 @@ impl EventLoop { is_synthetic: false, }; - app.window_event(&self.window_target, GLOBAL_WINDOW, event); + app.window_event(&self.window_target, GLOBAL_WINDOW, Instant::now(), event); }, } }, diff --git a/winit-appkit/src/app.rs b/winit-appkit/src/app.rs index 43627eace9..8c838e6261 100644 --- a/winit-appkit/src/app.rs +++ b/winit-appkit/src/app.rs @@ -101,6 +101,7 @@ pub(crate) fn override_send_event(global_app: &NSApplication) { } fn maybe_dispatch_device_event(app_state: &Rc, event: &NSEvent) { + let time = app_state.event_time(event); let event_type = event.r#type(); #[allow(non_upper_case_globals)] match event_type { @@ -113,7 +114,7 @@ fn maybe_dispatch_device_event(app_state: &Rc, event: &NSEvent) { if delta_x != 0.0 || delta_y != 0.0 { app_state.maybe_queue_with_handler(move |app, event_loop| { - app.device_event(event_loop, None, DeviceEvent::PointerMotion { + app.device_event(event_loop, None, time, DeviceEvent::PointerMotion { delta: (delta_x, delta_y), }); }); @@ -122,7 +123,7 @@ fn maybe_dispatch_device_event(app_state: &Rc, event: &NSEvent) { NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => { let button = event.buttonNumber() as u32; app_state.maybe_queue_with_handler(move |app, event_loop| { - app.device_event(event_loop, None, DeviceEvent::Button { + app.device_event(event_loop, None, time, DeviceEvent::Button { button, state: ElementState::Pressed, }); @@ -131,7 +132,7 @@ fn maybe_dispatch_device_event(app_state: &Rc, event: &NSEvent) { NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => { let button = event.buttonNumber() as u32; app_state.maybe_queue_with_handler(move |app, event_loop| { - app.device_event(event_loop, None, DeviceEvent::Button { + app.device_event(event_loop, None, time, DeviceEvent::Button { button, state: ElementState::Released, }); diff --git a/winit-appkit/src/app_state.rs b/winit-appkit/src/app_state.rs index 5b057b79b3..c5b6a1093c 100644 --- a/winit-appkit/src/app_state.rs +++ b/winit-appkit/src/app_state.rs @@ -2,12 +2,13 @@ use std::cell::{Cell, OnceCell, RefCell}; use std::mem; use std::rc::Rc; use std::sync::Arc; -use std::time::Instant; +use std::time::{Duration, Instant}; use dispatch2::MainThreadBound; use objc2::MainThreadMarker; -use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication}; +use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSEvent, NSRunningApplication}; use objc2_foundation::NSNotification; +use tracing::warn; use winit_common::core_foundation::{EventLoopProxy, MainRunLoop}; use winit_common::event_handler::EventHandler; use winit_core::application::ApplicationHandler; @@ -16,6 +17,7 @@ use winit_core::event_loop::ControlFlow; use winit_core::window::WindowId; use super::event_loop::{ActiveEventLoop, notify_windows_of_exit, stop_app_immediately}; +use super::ffi::CACurrentMediaTime; use super::menu; use super::observer::EventLoopWaker; @@ -43,6 +45,10 @@ pub(super) struct AppState { start_time: Cell>, wait_timeout: Cell>, pending_redraw: RefCell>, + /// [`Instant`] / `CACurrentMediaTime` pair sampled at startup, used to translate + /// `NSEvent.timestamp` into [`Instant`]. + startup_instant: Instant, + startup_timestamp: f64, // NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and // as such should be careful to not add fields that, in turn, strongly reference those. } @@ -63,6 +69,12 @@ impl AppState { Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop)); })); + // Prime the QuartzCore dylib cache so the subsequent read reflects steady-state cost, + // then sample back-to-back to bound the calibration skew between the two clocks. + let _ = unsafe { CACurrentMediaTime() }; + let startup_instant = Instant::now(); + let startup_timestamp = unsafe { CACurrentMediaTime() }; + let this = Rc::new(Self { mtm, activation_policy, @@ -83,6 +95,8 @@ impl AppState { start_time: Cell::new(None), wait_timeout: Cell::new(None), pending_redraw: RefCell::new(vec![]), + startup_instant, + startup_timestamp, }); GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this)) @@ -96,6 +110,37 @@ impl AppState { .clone() } + /// Convert an `NSEvent`'s `mach_absolute_time` timestamp into an [`Instant`] using the + /// calibration captured at startup. + pub(crate) fn event_time(&self, event: &NSEvent) -> Instant { + let ts = event.timestamp(); + if ts == 0.0 { + warn!("NSEvent has zero timestamp; falling back to Instant::now()"); + return Instant::now(); + } + let secs_since_startup = ts - self.startup_timestamp; + if !secs_since_startup.is_finite() { + return Instant::now(); + } + let offset = Duration::from_secs_f64(secs_since_startup.abs()); + if secs_since_startup < 0.0 { + self.startup_instant.checked_sub(offset).unwrap_or(self.startup_instant) + } else { + self.startup_instant + offset + } + } + + /// Look up the time of the currently-dispatching `NSEvent`, if any; otherwise + /// [`Instant::now()`]. + pub(crate) fn current_event_time(&self) -> Instant { + let app = NSApplication::sharedApplication(self.mtm); + if let Some(nsevent) = app.currentEvent() { + self.event_time(&nsevent) + } else { + Instant::now() + } + } + // NOTE: This notification will, globally, only be emitted once, // no matter how many `EventLoop`s the user creates. pub fn did_finish_launching(self: &Rc, _notification: &NSNotification) { @@ -245,7 +290,12 @@ impl AppState { // -> Don't go back into the event handler when our callstack originates from there if !self.event_handler.in_use() { self.with_handler(|app, event_loop| { - app.window_event(event_loop, window_id, WindowEvent::RedrawRequested); + app.window_event( + event_loop, + window_id, + Instant::now(), + WindowEvent::RedrawRequested, + ); }); // `pump_events` will request to stop immediately _after_ dispatching RedrawRequested @@ -345,7 +395,12 @@ impl AppState { let redraw = mem::take(&mut *self.pending_redraw.borrow_mut()); for window_id in redraw { self.with_handler(|app, event_loop| { - app.window_event(event_loop, window_id, WindowEvent::RedrawRequested); + app.window_event( + event_loop, + window_id, + Instant::now(), + WindowEvent::RedrawRequested, + ); }); } self.with_handler(|app, event_loop| { diff --git a/winit-appkit/src/ffi.rs b/winit-appkit/src/ffi.rs index 7f7cec0bfb..0066031464 100644 --- a/winit-appkit/src/ffi.rs +++ b/winit-appkit/src/ffi.rs @@ -39,6 +39,18 @@ unsafe extern "C" { ) -> i32; } +// `NSEvent.timestamp()` is an `NSTimeInterval` on the `mach_absolute_time` base. +// `CACurrentMediaTime` reads from that same base and is the anchor used when +// converting event timestamps into `Instant`. +// +// `NSProcessInfo.processInfo().systemUptime()` reads the same clock but is a +// "required reason API" that mandates a privacy manifest for App Store +// distribution; `CACurrentMediaTime` is not. +#[link(name = "QuartzCore", kind = "framework")] +unsafe extern "C" { + pub fn CACurrentMediaTime() -> f64; +} + #[repr(transparent)] pub struct TISInputSource(std::ffi::c_void); diff --git a/winit-appkit/src/view.rs b/winit-appkit/src/view.rs index 818e460277..833c5d1371 100644 --- a/winit-appkit/src/view.rs +++ b/winit-appkit/src/view.rs @@ -684,8 +684,9 @@ define_class!( self.update_modifiers(event, false); + let time = self.ivars().app_state.event_time(event); self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { - app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta }) + app.device_event(event_loop, None, time, DeviceEvent::MouseWheel { delta }) }); self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase }); } @@ -849,8 +850,9 @@ impl WinitView { fn queue_event(&self, event: WindowEvent) { let window_id = window_id(&self.window()); + let time = self.ivars().app_state.current_event_time(); self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { - app.window_event(event_loop, window_id, event); + app.window_event(event_loop, window_id, time, event); }); } diff --git a/winit-appkit/src/window_delegate.rs b/winit-appkit/src/window_delegate.rs index e316973e7c..7890d50243 100644 --- a/winit-appkit/src/window_delegate.rs +++ b/winit-appkit/src/window_delegate.rs @@ -904,8 +904,9 @@ impl WindowDelegate { pub(crate) fn queue_event(&self, event: WindowEvent) { let window_id = window_id(self.window()); + let time = self.ivars().app_state.current_event_time(); self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { - app.window_event(event_loop, window_id, event); + app.window_event(event_loop, window_id, time, event); }); } diff --git a/winit-core/src/application/mod.rs b/winit-core/src/application/mod.rs index b9fd34bff1..77a092e177 100644 --- a/winit-core/src/application/mod.rs +++ b/winit-core/src/application/mod.rs @@ -1,5 +1,6 @@ //! End user application handling. +use crate::Instant; use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; use crate::event_loop::ActiveEventLoop; use crate::window::WindowId; @@ -138,6 +139,7 @@ pub trait ApplicationHandler { /// # &mut self, /// # _event_loop: &dyn ActiveEventLoop, /// # _window_id: winit::window::WindowId, + /// # _timestamp: winit::Instant, /// # _event: winit::event::WindowEvent, /// # ) { /// # } @@ -192,23 +194,40 @@ pub trait ApplicationHandler { } /// Emitted when the OS sends an event to a winit window. + /// + /// `timestamp` is the OS-stamped event time where available, otherwise [`Instant::now()`] + /// sampled when winit received the event. Prefer it over a fresh [`Instant::now()`] in + /// the handler to avoid polling-delay latency. + /// + /// ## Platform-specific + /// + /// - **macOS:** `NSEvent.timestamp`; [`Instant::now()`] for synthesized events. + /// - **X11:** `xev.time`, lazily calibrated to [`Instant`] on first observation. + /// - **Wayland:** the `time` field on `wl_pointer` / `wl_keyboard` / `wl_touch` and + /// `utime_hi`/`utime_lo` on `zwp_relative_pointer_v1`, calibrated the same way. + /// - **Windows:** [`Instant::now()`] sampled inside `WindowProc`. + /// - **Android / iOS / Web / Orbital:** [`Instant::now()`]. fn window_event( &mut self, event_loop: &dyn ActiveEventLoop, window_id: WindowId, + timestamp: Instant, event: WindowEvent, ); /// Emitted when the OS sends an event to a device. /// /// Whether device events are delivered depends on the backend in use. + /// + /// See [`window_event`](Self::window_event) for details on `timestamp`. fn device_event( &mut self, event_loop: &dyn ActiveEventLoop, device_id: Option, + timestamp: Instant, event: DeviceEvent, ) { - let _ = (event_loop, device_id, event); + let _ = (event_loop, device_id, timestamp, event); } /// Emitted when the event loop is about to block and wait for new events. @@ -372,9 +391,10 @@ impl ApplicationHandler for &mut A { &mut self, event_loop: &dyn ActiveEventLoop, window_id: WindowId, + timestamp: Instant, event: WindowEvent, ) { - (**self).window_event(event_loop, window_id, event); + (**self).window_event(event_loop, window_id, timestamp, event); } #[inline] @@ -382,9 +402,10 @@ impl ApplicationHandler for &mut A { &mut self, event_loop: &dyn ActiveEventLoop, device_id: Option, + timestamp: Instant, event: DeviceEvent, ) { - (**self).device_event(event_loop, device_id, event); + (**self).device_event(event_loop, device_id, timestamp, event); } #[inline] @@ -440,9 +461,10 @@ impl ApplicationHandler for Box { &mut self, event_loop: &dyn ActiveEventLoop, window_id: WindowId, + timestamp: Instant, event: WindowEvent, ) { - (**self).window_event(event_loop, window_id, event); + (**self).window_event(event_loop, window_id, timestamp, event); } #[inline] @@ -450,9 +472,10 @@ impl ApplicationHandler for Box { &mut self, event_loop: &dyn ActiveEventLoop, device_id: Option, + timestamp: Instant, event: DeviceEvent, ) { - (**self).device_event(event_loop, device_id, event); + (**self).device_event(event_loop, device_id, timestamp, event); } #[inline] diff --git a/winit-core/src/lib.rs b/winit-core/src/lib.rs index 89bc682164..233adbd96f 100644 --- a/winit-core/src/lib.rs +++ b/winit-core/src/lib.rs @@ -23,7 +23,7 @@ pub mod window; // `Instant` is not actually available on `wasm32-unknown-unknown`, the `std` implementation there // is a stub. And `wasm32-none` doesn't even have `std`. Instead, we use `web_time::Instant`. #[cfg(not(all(target_family = "wasm", any(target_os = "unknown", target_os = "none"))))] -pub(crate) use std::time::Instant; +pub use std::time::Instant; #[cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))] -pub(crate) use web_time::Instant; +pub use web_time::Instant; diff --git a/winit-orbital/src/event_loop.rs b/winit-orbital/src/event_loop.rs index 9c9b1adc51..5f9c75d1a9 100644 --- a/winit-orbital/src/event_loop.rs +++ b/winit-orbital/src/event_loop.rs @@ -398,13 +398,14 @@ impl EventLoop { is_synthetic: false, }; - app.window_event(window_target, window_id, event); + app.window_event(window_target, window_id, Instant::now(), event); // If the state of the modifiers has changed, send the event. if modifiers_before != event_state.keyboard { app.window_event( window_target, window_id, + Instant::now(), event::WindowEvent::ModifiersChanged(event_state.modifiers()), ); } @@ -413,16 +414,18 @@ impl EventLoop { app.window_event( window_target, window_id, + Instant::now(), event::WindowEvent::Ime(Ime::Preedit("".into(), None)), ); app.window_event( window_target, window_id, + Instant::now(), event::WindowEvent::Ime(Ime::Commit(character.into())), ); }, EventOption::Mouse(MouseEvent { x, y }) => { - app.window_event(window_target, window_id, event::WindowEvent::PointerMoved { + app.window_event(window_target, window_id, Instant::now(), event::WindowEvent::PointerMoved { device_id: None, primary: true, position: (x, y).into(), @@ -430,13 +433,13 @@ impl EventLoop { }); }, EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => { - app.device_event(window_target, None, event::DeviceEvent::PointerMotion { + app.device_event(window_target, None, Instant::now(), event::DeviceEvent::PointerMotion { delta: (dx as f64, dy as f64), }); }, EventOption::Button(ButtonEvent { left, middle, right }) => { while let Some((button, state)) = event_state.mouse(left, middle, right) { - app.window_event(window_target, window_id, event::WindowEvent::PointerButton { + app.window_event(window_target, window_id, Instant::now(), event::WindowEvent::PointerButton { device_id: None, primary: true, state, @@ -446,22 +449,23 @@ impl EventLoop { } }, EventOption::Scroll(ScrollEvent { x, y }) => { - app.window_event(window_target, window_id, event::WindowEvent::MouseWheel { + app.window_event(window_target, window_id, Instant::now(), event::WindowEvent::MouseWheel { device_id: None, delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32), phase: event::TouchPhase::Moved, }); }, EventOption::Quit(QuitEvent {}) => { - app.window_event(window_target, window_id, event::WindowEvent::CloseRequested); + app.window_event(window_target, window_id, Instant::now(), event::WindowEvent::CloseRequested); }, EventOption::Focus(FocusEvent { focused }) => { - app.window_event(window_target, window_id, event::WindowEvent::Focused(focused)); + app.window_event(window_target, window_id, Instant::now(), event::WindowEvent::Focused(focused)); }, EventOption::Move(MoveEvent { x, y }) => { app.window_event( window_target, window_id, + Instant::now(), event::WindowEvent::Moved((x, y).into()), ); }, @@ -469,6 +473,7 @@ impl EventLoop { app.window_event( window_target, window_id, + Instant::now(), event::WindowEvent::SurfaceResized((width, height).into()), ); @@ -493,7 +498,7 @@ impl EventLoop { } }; - app.window_event(window_target, window_id, event); + app.window_event(window_target, window_id, Instant::now(), event); }, other => { tracing::warn!("unhandled event: {:?}", other); @@ -528,11 +533,11 @@ impl EventLoop { // Send resize event on create to indicate first size. let event = event::WindowEvent::SurfaceResized((properties.w, properties.h).into()); - app.window_event(&self.window_target, window_id, event); + app.window_event(&self.window_target, window_id, Instant::now(), event); // Send moved event on create to indicate first position. let event = event::WindowEvent::Moved((properties.x, properties.y).into()); - app.window_event(&self.window_target, window_id, event); + app.window_event(&self.window_target, window_id, Instant::now(), event); } // Handle window destroys. @@ -540,7 +545,7 @@ impl EventLoop { let mut destroys = self.window_target.destroys.lock().unwrap(); destroys.pop_front() } { - app.window_event(&self.window_target, destroy_id, event::WindowEvent::Destroyed); + app.window_event(&self.window_target, destroy_id, Instant::now(), event::WindowEvent::Destroyed); self.windows .retain(|(window, _event_state)| WindowId::from_raw(window.fd()) != destroy_id); } @@ -608,6 +613,7 @@ impl EventLoop { app.window_event( &self.window_target, window_id, + Instant::now(), event::WindowEvent::RedrawRequested, ); } diff --git a/winit-uikit/src/app_state.rs b/winit-uikit/src/app_state.rs index 7876e34fe1..090cef585d 100644 --- a/winit-uikit/src/app_state.rs +++ b/winit-uikit/src/app_state.rs @@ -441,8 +441,9 @@ pub(crate) fn terminated(application: &UIApplication) { fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) { match event { - EventWrapper::Window { window_id, event } => get_handler(mtm) - .handle(|app| app.window_event(&ActiveEventLoop { mtm }, window_id, event)), + EventWrapper::Window { window_id, event } => get_handler(mtm).handle(|app| { + app.window_event(&ActiveEventLoop { mtm }, window_id, Instant::now(), event) + }), EventWrapper::ScaleFactorChanged(event) => handle_hidpi_proxy(mtm, event), } } @@ -451,10 +452,15 @@ fn handle_hidpi_proxy(mtm: MainThreadMarker, event: ScaleFactorChanged) { let ScaleFactorChanged { suggested_size, scale_factor, window } = event; let new_surface_size = Arc::new(Mutex::new(suggested_size)); get_handler(mtm).handle(|app| { - app.window_event(&ActiveEventLoop { mtm }, window.id(), WindowEvent::ScaleFactorChanged { - scale_factor, - surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)), - }); + app.window_event( + &ActiveEventLoop { mtm }, + window.id(), + Instant::now(), + WindowEvent::ScaleFactorChanged { + scale_factor, + surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)), + }, + ); }); let (view, screen_frame) = get_view_and_screen_frame(&window); let physical_size = *new_surface_size.lock().unwrap(); diff --git a/winit-wayland/src/event_loop/mod.rs b/winit-wayland/src/event_loop/mod.rs index 3a6ca6982b..47a1fc6c4f 100644 --- a/winit-wayland/src/event_loop/mod.rs +++ b/winit-wayland/src/event_loop/mod.rs @@ -47,8 +47,8 @@ type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource, #[derive(Debug)] pub(crate) enum Event { - WindowEvent { window_id: WindowId, event: WindowEvent }, - DeviceEvent { event: DeviceEvent }, + WindowEvent { window_id: WindowId, event: WindowEvent, timestamp: Instant }, + DeviceEvent { event: DeviceEvent, timestamp: Instant }, } /// The Wayland event loop. @@ -358,7 +358,7 @@ impl EventLoop { surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&new_surface_size)), }; - app.window_event(&self.active_event_loop, window_id, event); + app.window_event(&self.active_event_loop, window_id, Instant::now(), event); let physical_size = *new_surface_size.lock().unwrap(); drop(new_surface_size); @@ -402,11 +402,16 @@ impl EventLoop { }); let event = WindowEvent::SurfaceResized(physical_size); - app.window_event(&self.active_event_loop, window_id, event); + app.window_event(&self.active_event_loop, window_id, Instant::now(), event); } if compositor_update.close_window { - app.window_event(&self.active_event_loop, window_id, WindowEvent::CloseRequested); + app.window_event( + &self.active_event_loop, + window_id, + Instant::now(), + WindowEvent::CloseRequested, + ); } } @@ -416,11 +421,11 @@ impl EventLoop { }); for event in buffer_sink.drain() { match event { - Event::WindowEvent { window_id, event } => { - app.window_event(&self.active_event_loop, window_id, event) + Event::WindowEvent { window_id, event, timestamp } => { + app.window_event(&self.active_event_loop, window_id, timestamp, event) }, - Event::DeviceEvent { event } => { - app.device_event(&self.active_event_loop, None, event) + Event::DeviceEvent { event, timestamp } => { + app.device_event(&self.active_event_loop, None, timestamp, event) }, } } @@ -431,11 +436,11 @@ impl EventLoop { }); for event in buffer_sink.drain() { match event { - Event::WindowEvent { window_id, event } => { - app.window_event(&self.active_event_loop, window_id, event) + Event::WindowEvent { window_id, event, timestamp } => { + app.window_event(&self.active_event_loop, window_id, timestamp, event) }, - Event::DeviceEvent { event } => { - app.device_event(&self.active_event_loop, None, event) + Event::DeviceEvent { event, timestamp } => { + app.device_event(&self.active_event_loop, None, timestamp, event) }, } } @@ -473,7 +478,7 @@ impl EventLoop { }); if let Some(event) = event { - app.window_event(&self.active_event_loop, *window_id, event); + app.window_event(&self.active_event_loop, *window_id, Instant::now(), event); } } diff --git a/winit-wayland/src/event_loop/sink.rs b/winit-wayland/src/event_loop/sink.rs index c67463d2e4..263ac2585d 100644 --- a/winit-wayland/src/event_loop/sink.rs +++ b/winit-wayland/src/event_loop/sink.rs @@ -1,5 +1,6 @@ //! An event loop's sink to deliver events from the Wayland event callbacks. +use std::time::Instant; use std::vec::Drain; use winit_core::event::{DeviceEvent, WindowEvent}; @@ -25,16 +26,39 @@ impl EventSink { self.window_events.is_empty() } - /// Add new device event to a queue. + /// Add a new device event to the queue, stamped with the moment of the call. + /// + /// Call sites that have a real compositor timestamp should prefer + /// [`push_device_event_at`](Self::push_device_event_at). #[inline] pub fn push_device_event(&mut self, event: DeviceEvent) { - self.window_events.push(Event::DeviceEvent { event }); + self.push_device_event_at(event, Instant::now()); } - /// Add new window event to a queue. + /// Add a new device event to the queue with an explicit timestamp. + #[inline] + pub fn push_device_event_at(&mut self, event: DeviceEvent, timestamp: Instant) { + self.window_events.push(Event::DeviceEvent { event, timestamp }); + } + + /// Add a new window event to the queue, stamped with the moment of the call. + /// + /// Call sites that have a real compositor timestamp should prefer + /// [`push_window_event_at`](Self::push_window_event_at). #[inline] pub fn push_window_event(&mut self, event: WindowEvent, window_id: WindowId) { - self.window_events.push(Event::WindowEvent { event, window_id }); + self.push_window_event_at(event, window_id, Instant::now()); + } + + /// Add a new window event to the queue with an explicit timestamp. + #[inline] + pub fn push_window_event_at( + &mut self, + event: WindowEvent, + window_id: WindowId, + timestamp: Instant, + ) { + self.window_events.push(Event::WindowEvent { event, window_id, timestamp }); } #[inline] diff --git a/winit-wayland/src/seat/keyboard/mod.rs b/winit-wayland/src/seat/keyboard/mod.rs index 9b1d1cd620..9e7fc9ecc1 100644 --- a/winit-wayland/src/seat/keyboard/mod.rs +++ b/winit-wayland/src/seat/keyboard/mod.rs @@ -1,7 +1,7 @@ //! The keyboard input handling. use std::sync::Mutex; -use std::time::Duration; +use std::time::{Duration, Instant}; use calloop::timer::{TimeoutAction, Timer}; use calloop::{LoopHandle, RegistrationToken}; @@ -129,10 +129,14 @@ impl Dispatch for WinitState { state.events_sink.push_window_event(WindowEvent::Focused(false), window_id); } }, - WlKeyboardEvent::Key { key, state: WEnum::Value(key_state), .. } + WlKeyboardEvent::Key { key, state: WEnum::Value(key_state), time, .. } if matches!(key_state, WlKeyState::Repeated | WlKeyState::Pressed) => { let key = key + 8; + let timestamp = WinitState::resolve_compositor_time_ms( + &state.compositor_time_ms_anchor, + time, + ); key_input( keyboard_state, &mut state.events_sink, @@ -140,6 +144,7 @@ impl Dispatch for WinitState { key, ElementState::Pressed, key_state == WlKeyState::Repeated, + timestamp, ); let delay = match keyboard_state.repeat_info { @@ -194,6 +199,7 @@ impl Dispatch for WinitState { repeat_keycode, ElementState::Pressed, true, + Instant::now(), ); // NOTE: the gap could change dynamically while repeat is going. @@ -204,8 +210,12 @@ impl Dispatch for WinitState { }) .ok(); }, - WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Released), .. } => { + WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Released), time, .. } => { let key = key + 8; + let timestamp = WinitState::resolve_compositor_time_ms( + &state.compositor_time_ms_anchor, + time, + ); key_input( keyboard_state, @@ -214,6 +224,7 @@ impl Dispatch for WinitState { key, ElementState::Released, false, + timestamp, ); if keyboard_state.repeat_info != RepeatInfo::Disable @@ -366,6 +377,7 @@ fn key_input( keycode: u32, state: ElementState, repeat: bool, + timestamp: Instant, ) { let window_id = match *data.window_id.lock().unwrap() { Some(window_id) => window_id, @@ -375,6 +387,6 @@ fn key_input( if let Some(mut key_context) = keyboard_state.xkb_context.key_context() { let event = key_context.process_key_event(keycode, state, repeat); let event = WindowEvent::KeyboardInput { device_id: None, event, is_synthetic: false }; - event_sink.push_window_event(event, window_id); + event_sink.push_window_event_at(event, window_id, timestamp); } } diff --git a/winit-wayland/src/seat/pointer/mod.rs b/winit-wayland/src/seat/pointer/mod.rs index 11970d7069..6b0e8eccbc 100644 --- a/winit-wayland/src/seat/pointer/mod.rs +++ b/winit-wayland/src/seat/pointer/mod.rs @@ -158,8 +158,12 @@ impl PointerHandler for WinitState { window_id, ); }, - PointerEventKind::Motion { .. } => { - self.events_sink.push_window_event( + PointerEventKind::Motion { time } => { + let timestamp = WinitState::resolve_compositor_time_ms( + &self.compositor_time_ms_anchor, + time, + ); + self.events_sink.push_window_event_at( WindowEvent::PointerMoved { primary: true, device_id: None, @@ -167,10 +171,11 @@ impl PointerHandler for WinitState { source: PointerSource::Mouse, }, window_id, + timestamp, ); }, - ref kind @ PointerEventKind::Press { button, serial, .. } - | ref kind @ PointerEventKind::Release { button, serial, .. } => { + ref kind @ PointerEventKind::Press { button, serial, time } + | ref kind @ PointerEventKind::Release { button, serial, time } => { // Update the last button serial. pointer.winit_data().inner.lock().unwrap().latest_button_serial = serial; @@ -180,7 +185,11 @@ impl PointerHandler for WinitState { } else { ElementState::Released }; - self.events_sink.push_window_event( + let timestamp = WinitState::resolve_compositor_time_ms( + &self.compositor_time_ms_anchor, + time, + ); + self.events_sink.push_window_event_at( WindowEvent::PointerButton { primary: true, device_id: None, @@ -189,9 +198,10 @@ impl PointerHandler for WinitState { button, }, window_id, + timestamp, ); }, - PointerEventKind::Axis { horizontal, vertical, .. } => { + PointerEventKind::Axis { horizontal, vertical, time, .. } => { // Get the current phase. let mut pointer_data = pointer.winit_data().inner.lock().unwrap(); @@ -238,9 +248,14 @@ impl PointerHandler for WinitState { ) }; - self.events_sink.push_window_event( + let timestamp = WinitState::resolve_compositor_time_ms( + &self.compositor_time_ms_anchor, + time, + ); + self.events_sink.push_window_event_at( WindowEvent::MouseWheel { device_id: None, delta, phase }, window_id, + timestamp, ) }, } diff --git a/winit-wayland/src/seat/pointer/relative_pointer.rs b/winit-wayland/src/seat/pointer/relative_pointer.rs index 8465b720d6..ebbeae402c 100644 --- a/winit-wayland/src/seat/pointer/relative_pointer.rs +++ b/winit-wayland/src/seat/pointer/relative_pointer.rs @@ -61,15 +61,23 @@ impl Dispatch for RelativePointerS _conn: &Connection, _qhandle: &QueueHandle, ) { - let (dx_unaccel, dy_unaccel) = match event { - zwp_relative_pointer_v1::Event::RelativeMotion { dx_unaccel, dy_unaccel, .. } => { - (dx_unaccel, dy_unaccel) - }, + let (dx_unaccel, dy_unaccel, utime_hi, utime_lo) = match event { + zwp_relative_pointer_v1::Event::RelativeMotion { + dx_unaccel, + dy_unaccel, + utime_hi, + utime_lo, + .. + } => (dx_unaccel, dy_unaccel, utime_hi, utime_lo), _ => return, }; - state - .events_sink - .push_device_event(DeviceEvent::PointerMotion { delta: (dx_unaccel, dy_unaccel) }); + let utime = ((utime_hi as u64) << 32) | (utime_lo as u64); + let timestamp = + WinitState::resolve_compositor_time_us(&state.compositor_time_us_anchor, utime); + state.events_sink.push_device_event_at( + DeviceEvent::PointerMotion { delta: (dx_unaccel, dy_unaccel) }, + timestamp, + ); } } diff --git a/winit-wayland/src/seat/touch/mod.rs b/winit-wayland/src/seat/touch/mod.rs index f0c9bab0ff..a48eb342bd 100644 --- a/winit-wayland/src/seat/touch/mod.rs +++ b/winit-wayland/src/seat/touch/mod.rs @@ -19,12 +19,16 @@ impl TouchHandler for WinitState { _: &Connection, _: &QueueHandle, touch: &WlTouch, - _: u32, - _: u32, + _serial: u32, + time: u32, surface: WlSurface, id: i32, position: (f64, f64), ) { + let timestamp = WinitState::resolve_compositor_time_ms( + &self.compositor_time_ms_anchor, + time, + ); let window_id = crate::make_wid(&surface); let scale_factor = match self.windows.get_mut().get(&window_id) { Some(window) => window.lock().unwrap().scale_factor(), @@ -51,7 +55,7 @@ impl TouchHandler for WinitState { let position = location.to_physical(scale_factor); let finger_id = FingerId::from_raw(id as usize); - self.events_sink.push_window_event( + self.events_sink.push_window_event_at( WindowEvent::PointerEntered { device_id: None, primary, @@ -59,8 +63,9 @@ impl TouchHandler for WinitState { kind: PointerKind::Touch(finger_id), }, window_id, + timestamp, ); - self.events_sink.push_window_event( + self.events_sink.push_window_event_at( WindowEvent::PointerButton { device_id: None, primary, @@ -69,6 +74,7 @@ impl TouchHandler for WinitState { button: ButtonSource::Touch { finger_id, force: None }, }, window_id, + timestamp, ); } @@ -77,10 +83,14 @@ impl TouchHandler for WinitState { _: &Connection, _: &QueueHandle, touch: &WlTouch, - _: u32, - _: u32, + _serial: u32, + time: u32, id: i32, ) { + let timestamp = WinitState::resolve_compositor_time_ms( + &self.compositor_time_ms_anchor, + time, + ); let seat_state = match self.seats.get_mut(&touch.seat().id()) { Some(seat_state) => seat_state, None => { @@ -113,7 +123,7 @@ impl TouchHandler for WinitState { let position = touch_point.location.to_physical(scale_factor); let finger_id = FingerId::from_raw(id as usize); - self.events_sink.push_window_event( + self.events_sink.push_window_event_at( WindowEvent::PointerButton { device_id: None, primary, @@ -122,8 +132,9 @@ impl TouchHandler for WinitState { button: ButtonSource::Touch { finger_id, force: None }, }, window_id, + timestamp, ); - self.events_sink.push_window_event( + self.events_sink.push_window_event_at( WindowEvent::PointerLeft { device_id: None, primary, @@ -131,6 +142,7 @@ impl TouchHandler for WinitState { kind: PointerKind::Touch(finger_id), }, window_id, + timestamp, ); } @@ -139,10 +151,14 @@ impl TouchHandler for WinitState { _: &Connection, _: &QueueHandle, touch: &WlTouch, - _: u32, + time: u32, id: i32, position: (f64, f64), ) { + let timestamp = WinitState::resolve_compositor_time_ms( + &self.compositor_time_ms_anchor, + time, + ); let seat_state = match self.seats.get_mut(&touch.seat().id()) { Some(seat_state) => seat_state, None => { @@ -167,7 +183,7 @@ impl TouchHandler for WinitState { touch_point.location = LogicalPosition::::from(position); - self.events_sink.push_window_event( + self.events_sink.push_window_event_at( WindowEvent::PointerMoved { device_id: None, primary, @@ -178,6 +194,7 @@ impl TouchHandler for WinitState { }, }, window_id, + timestamp, ); } diff --git a/winit-wayland/src/state.rs b/winit-wayland/src/state.rs index 577c8c42ec..59672f34b6 100644 --- a/winit-wayland/src/state.rs +++ b/winit-wayland/src/state.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; use foldhash::HashMap; use sctk::compositor::{CompositorHandler, CompositorState}; @@ -128,6 +129,13 @@ pub struct WinitState { /// Whether the user initiated a wake up. pub proxy_wake_up: bool, + + /// Sliding anchor for the u32 ms clock used by wl_pointer, wl_keyboard, and wl_touch. + /// Separate from the us anchor because the ms field wraps every ~49.7 days. + pub compositor_time_ms_anchor: Mutex>, + + /// Sliding anchor for the u64 us clock used by zwp_relative_pointer_v1. + pub compositor_time_us_anchor: Mutex>, } impl WinitState { @@ -211,9 +219,55 @@ impl WinitState { // Make it true by default. dispatched_events: true, proxy_wake_up: false, + compositor_time_ms_anchor: Mutex::new(None), + compositor_time_us_anchor: Mutex::new(None), }) } + /// Translate a u32 ms compositor timestamp into an [`Instant`]. + /// Slides the anchor forward so the distance never approaches u32::MAX / 2, + /// which keeps the wrap-detection valid past the 49.7-day wrap point. + pub fn resolve_compositor_time_ms( + anchor: &Mutex>, + time_ms: u32, + ) -> Instant { + let mut anchor = anchor.lock().unwrap(); + let (anchor_ms, anchor_instant) = + anchor.get_or_insert_with(|| (time_ms, Instant::now())); + + let delta = time_ms.wrapping_sub(*anchor_ms); + if delta < u32::MAX / 2 { + *anchor_instant += Duration::from_millis(delta as u64); + *anchor_ms = time_ms; + *anchor_instant + } else { + let backward = anchor_ms.wrapping_sub(time_ms); + anchor_instant + .checked_sub(Duration::from_millis(backward as u64)) + .unwrap_or(*anchor_instant) + } + } + + /// Translate a u64 us compositor timestamp into an [`Instant`]. + pub fn resolve_compositor_time_us( + anchor: &Mutex>, + time_us: u64, + ) -> Instant { + let mut anchor = anchor.lock().unwrap(); + let (anchor_us, anchor_instant) = + anchor.get_or_insert_with(|| (time_us, Instant::now())); + + if time_us >= *anchor_us { + *anchor_instant += Duration::from_micros(time_us - *anchor_us); + *anchor_us = time_us; + *anchor_instant + } else { + anchor_instant + .checked_sub(Duration::from_micros(*anchor_us - time_us)) + .unwrap_or(*anchor_instant) + } + } + pub fn scale_factor_changed( &mut self, surface: &WlSurface, diff --git a/winit-web/src/event_loop/runner.rs b/winit-web/src/event_loop/runner.rs index 608fe540d2..2a8a3e4056 100644 --- a/winit-web/src/event_loop/runner.rs +++ b/winit-web/src/event_loop/runner.rs @@ -137,14 +137,19 @@ impl Runner { match event { Event::NewEvents(cause) => self.app.new_events(&self.event_loop, cause), Event::WindowEvent { window_id, event } => { - self.app.window_event(&self.event_loop, window_id, event) + self.app.window_event(&self.event_loop, window_id, Instant::now(), event) }, Event::ScaleChange { canvas, size, scale } => { if let Some(canvas) = canvas.upgrade() { canvas.handle_scale_change( runner, |window_id, event| { - self.app.window_event(&self.event_loop, window_id, event); + self.app.window_event( + &self.event_loop, + window_id, + Instant::now(), + event, + ); }, size, scale, @@ -152,7 +157,7 @@ impl Runner { } }, Event::DeviceEvent { device_id, event } => { - self.app.device_event(&self.event_loop, device_id, event) + self.app.device_event(&self.event_loop, device_id, Instant::now(), event) }, Event::UserWakeUp => self.app.proxy_wake_up(&self.event_loop), Event::Suspended => self.app.suspended(&self.event_loop), diff --git a/winit-win32/src/event_loop.rs b/winit-win32/src/event_loop.rs index 8f1008f98d..d3cb577afa 100644 --- a/winit-win32/src/event_loop.rs +++ b/winit-win32/src/event_loop.rs @@ -114,7 +114,11 @@ pub(crate) struct WindowData { impl WindowData { fn send_window_event(&self, window: HWND, event: WindowEvent) { let window_id = WindowId::from_raw(window as usize); - self.event_loop_runner.send_event(Event::Window { window_id, event }); + self.event_loop_runner.send_event(Event::Window { + window_id, + event, + timestamp: Instant::now(), + }); } fn window_state_lock(&self) -> MutexGuard<'_, WindowState> { @@ -132,7 +136,11 @@ impl ThreadMsgTargetData { } fn send_device_event(&self, device_id: DeviceId, event: DeviceEvent) { - self.event_loop_runner.send_event(Event::Device { device_id, event }); + self.event_loop_runner.send_event(Event::Device { + device_id, + event, + timestamp: Instant::now(), + }); } } diff --git a/winit-win32/src/event_loop/runner.rs b/winit-win32/src/event_loop/runner.rs index 65e6082761..8d66f4e145 100644 --- a/winit-win32/src/event_loop/runner.rs +++ b/winit-win32/src/event_loop/runner.rs @@ -66,9 +66,9 @@ pub(crate) enum RunnerState { #[derive(Debug, Clone)] pub(crate) enum Event { - Device { device_id: DeviceId, event: DeviceEvent }, - Window { window_id: WindowId, event: WindowEvent }, - BufferedScaleFactorChanged(HWND, f64, PhysicalSize), + Device { device_id: DeviceId, event: DeviceEvent, timestamp: Instant }, + Window { window_id: WindowId, event: WindowEvent, timestamp: Instant }, + BufferedScaleFactorChanged(HWND, f64, PhysicalSize, Instant), // FIXME(madsmtm): Coalesce these into a flag (or similar) instead of handling them as events. // https://github.com/rust-windowing/winit/pull/3687 WakeUp, @@ -396,10 +396,12 @@ impl Event { Self::Window { event: WindowEvent::ScaleFactorChanged { scale_factor, surface_size_writer }, window_id, + timestamp, } => Event::BufferedScaleFactorChanged( window_id.into_raw() as HWND, scale_factor, surface_size_writer.surface_size().unwrap(), + timestamp, ), event => event, } @@ -411,15 +413,18 @@ impl Event { event_loop: &dyn RootActiveEventLoop, ) { match self { - Self::Window { window_id, event } => app.window_event(event_loop, window_id, event), - Self::Device { device_id, event } => { - app.device_event(event_loop, Some(device_id), event) + Self::Window { window_id, event, timestamp } => { + app.window_event(event_loop, window_id, timestamp, event) }, - Self::BufferedScaleFactorChanged(window, scale_factor, new_surface_size) => { + Self::Device { device_id, event, timestamp } => { + app.device_event(event_loop, Some(device_id), timestamp, event) + }, + Self::BufferedScaleFactorChanged(window, scale_factor, new_surface_size, timestamp) => { let user_new_surface_size = Arc::new(Mutex::new(new_surface_size)); app.window_event( event_loop, WindowId::from_raw(window as usize), + timestamp, WindowEvent::ScaleFactorChanged { scale_factor, surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade( diff --git a/winit-win32/src/window.rs b/winit-win32/src/window.rs index dba95e9bd8..0760b62662 100644 --- a/winit-win32/src/window.rs +++ b/winit-win32/src/window.rs @@ -5,6 +5,7 @@ use std::mem::{self, MaybeUninit}; use std::rc::Rc; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex, MutexGuard}; +use std::time::Instant; use std::{io, panic, ptr}; use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; @@ -1233,7 +1234,11 @@ impl InitData<'_> { let file_drop_handler = FileDropHandler::new( win.window.hwnd(), Box::new(move |event| { - file_drop_runner.send_event(Event::Window { window_id, event }) + file_drop_runner.send_event(Event::Window { + window_id, + event, + timestamp: Instant::now(), + }) }), ); diff --git a/winit-x11/src/event_loop.rs b/winit-x11/src/event_loop.rs index b89a06c1e1..e23c490dbf 100644 --- a/winit-x11/src/event_loop.rs +++ b/winit-x11/src/event_loop.rs @@ -384,6 +384,7 @@ impl EventLoop { active_window: None, modifiers: Default::default(), is_composing: false, + event_time: Cell::new(Instant::now()), }; // Register for device hotplug events @@ -583,7 +584,12 @@ impl EventLoop { serial, token: winit_core::window::ActivationToken::from_raw(token), }; - app.window_event(&self.event_processor.target, window_id, event); + app.window_event( + &self.event_processor.target, + window_id, + Instant::now(), + event, + ); }, Some(Err(e)) => { tracing::error!("Failed to get activation token: {}", e); @@ -609,6 +615,7 @@ impl EventLoop { app.window_event( &self.event_processor.target, window_id, + Instant::now(), WindowEvent::RedrawRequested, ); } diff --git a/winit-x11/src/event_processor.rs b/winit-x11/src/event_processor.rs index 72c1c295f7..5efa8201f3 100644 --- a/winit-x11/src/event_processor.rs +++ b/winit-x11/src/event_processor.rs @@ -3,6 +3,7 @@ use std::collections::{HashMap, VecDeque}; use std::os::raw::{c_char, c_int, c_long, c_ulong}; use std::slice; use std::sync::{Arc, Mutex}; +use std::time::Instant; use dpi::{PhysicalPosition, PhysicalSize}; use winit_common::xkb::{self, Context, XkbState}; @@ -76,10 +77,46 @@ pub struct EventProcessor { pub xfiltered_modifiers: VecDeque, pub xmodmap: util::ModifierKeymap, pub is_composing: bool, + /// Timestamp of the event currently being processed, expressed as an [`Instant`]. Reset + /// to [`Instant::now()`] at the top of every `process_event`, then overwritten by + /// `record_server_time` for handlers that extract the X server's `time` field. Read by + /// `emit_window_event` / `emit_device_event` when dispatching to the application. + pub event_time: Cell, } impl EventProcessor { + /// Update both the connection's X server "last timestamp" bookkeeping and the stashed + /// `Instant` for the event currently being processed. + #[inline] + fn record_server_time(&self, time: xproto::Timestamp) { + self.target.xconn.set_timestamp(time); + self.event_time.set(self.target.xconn.server_time_to_instant(time)); + } + + #[inline] + fn emit_window_event( + &self, + app: &mut dyn ApplicationHandler, + window_id: WindowId, + event: WindowEvent, + ) { + app.window_event(&self.target, window_id, self.event_time.get(), event); + } + + #[inline] + fn emit_device_event( + &self, + app: &mut dyn ApplicationHandler, + device_id: Option, + event: DeviceEvent, + ) { + app.device_event(&self.target, device_id, self.event_time.get(), event); + } + pub(crate) fn process_event(&mut self, xev: &mut XEvent, app: &mut dyn ApplicationHandler) { + // Reset the event-time stash for this event; handlers that extract `xev.time` will + // overwrite it with a server-calibrated value via `record_server_time`. + self.event_time.set(Instant::now()); self.process_xevent(xev, app); // Handle IME requests. @@ -124,7 +161,7 @@ impl EventProcessor { _ => continue, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } } @@ -364,7 +401,7 @@ impl EventProcessor { let window_id = mkwid(window); if xev.data.get_long(0) as xproto::Atom == self.target.wm_delete_window { - app.window_event(&self.target, window_id, WindowEvent::CloseRequested); + self.emit_window_event(app, window_id, WindowEvent::CloseRequested); return; } @@ -497,7 +534,7 @@ impl EventProcessor { }; // Log this timestamp. - self.target.xconn.set_timestamp(time); + self.record_server_time(time); // This results in the `SelectionNotify` event below unsafe { @@ -519,7 +556,7 @@ impl EventProcessor { paths: path_list.iter().map(Into::into).collect(), position: self.dnd.position, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } (source_window, DndState::Accepted) } else { @@ -542,7 +579,7 @@ impl EventProcessor { if xev.message_type == atoms[XdndLeave] as c_ulong { if self.dnd.dragging { let event = WindowEvent::DragLeft { position: Some(self.dnd.position) }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } self.dnd.reset(); } @@ -555,7 +592,7 @@ impl EventProcessor { let window_id = mkwid(window); // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); if xev.property != atoms[XdndSelection] as c_ulong { return; @@ -575,7 +612,7 @@ impl EventProcessor { WindowEvent::DragEntered { paths, position: self.dnd.position } }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } self.dnd.result = Some(parse_result); @@ -650,7 +687,7 @@ impl EventProcessor { drop(shared_state_lock); if moved { - app.window_event(&self.target, window_id, WindowEvent::Moved(outer.into())); + self.emit_window_event(app, window_id, WindowEvent::Moved(outer.into())); } outer }; @@ -696,7 +733,7 @@ impl EventProcessor { drop(shared_state_lock); let surface_size = Arc::new(Mutex::new(new_surface_size)); - app.window_event(&self.target, window_id, WindowEvent::ScaleFactorChanged { + self.emit_window_event(app, window_id, WindowEvent::ScaleFactorChanged { scale_factor: new_scale_factor, surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)), }); @@ -750,7 +787,7 @@ impl EventProcessor { if resized { let event = WindowEvent::SurfaceResized(new_surface_size.into()); - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } } @@ -777,7 +814,7 @@ impl EventProcessor { // window, given that we can't rely on `CreateNotify`, due to it being not // sent. let focus = self.with_window(window, |window| window.has_focus()).unwrap_or_default(); - app.window_event(&self.target, window_id, WindowEvent::Focused(focus)); + self.emit_window_event(app, window_id, WindowEvent::Focused(focus)); } fn destroy_notify(&self, xev: &XDestroyWindowEvent, app: &mut dyn ApplicationHandler) { @@ -796,7 +833,7 @@ impl EventProcessor { .expect("Failed to destroy input context"); } - app.window_event(&self.target, window_id, WindowEvent::Destroyed); + self.emit_window_event(app, window_id, WindowEvent::Destroyed); } fn property_notify(&mut self, xev: &XPropertyEvent, app: &mut dyn ApplicationHandler) { @@ -815,7 +852,7 @@ impl EventProcessor { let window_id = mkwid(xwindow); let event = WindowEvent::Occluded(xev.state == xlib::VisibilityFullyObscured); - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); self.with_window(xwindow, |window| { window.visibility_notify(); @@ -839,7 +876,7 @@ impl EventProcessor { app: &mut dyn ApplicationHandler, ) { // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); let window = match self.active_window { Some(window) => window, @@ -910,7 +947,7 @@ impl EventProcessor { let event = key_processor.process_key_event(keycode, state, repeat); let event = WindowEvent::KeyboardInput { device_id: None, event, is_synthetic: false }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } // Restore the client's modifiers state after replay. @@ -927,11 +964,11 @@ impl EventProcessor { let written = self.target.xconn.lookup_utf8(ic, xev); if !written.is_empty() { let event = WindowEvent::Ime(Ime::Preedit(String::new(), None)); - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); let event = WindowEvent::Ime(Ime::Commit(written)); self.is_composing = false; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } } } @@ -961,7 +998,7 @@ impl EventProcessor { let mods: ModifiersState = xkb_state.modifiers().into(); let event = WindowEvent::ModifiersChanged(mods.into()); - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } fn xinput2_button_input( @@ -974,7 +1011,7 @@ impl EventProcessor { let device_id = Some(mkdid(event.deviceid as xinput::DeviceId)); // Set the timestamp. - self.target.xconn.set_timestamp(event.time as xproto::Timestamp); + self.record_server_time(event.time as xproto::Timestamp); let Some(DeviceType::Mouse) = self .devices @@ -1054,12 +1091,12 @@ impl EventProcessor { _ => return, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } fn xinput2_mouse_motion(&self, event: &XIDeviceEvent, app: &mut dyn ApplicationHandler) { // Set the timestamp. - self.target.xconn.set_timestamp(event.time as xproto::Timestamp); + self.record_server_time(event.time as xproto::Timestamp); let Some(DeviceType::Mouse) = self .devices @@ -1089,7 +1126,7 @@ impl EventProcessor { position, source: PointerSource::Mouse, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } else if cursor_moved.is_none() { return; } @@ -1134,13 +1171,13 @@ impl EventProcessor { } for event in events { - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } } fn xinput2_mouse_enter(&self, event: &XIEnterEvent, app: &mut dyn ApplicationHandler) { // Set the timestamp. - self.target.xconn.set_timestamp(event.time as xproto::Timestamp); + self.record_server_time(event.time as xproto::Timestamp); let window = event.event as xproto::Window; let window_id = mkwid(window); @@ -1173,7 +1210,7 @@ impl EventProcessor { position, kind: PointerKind::Mouse, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } } @@ -1181,7 +1218,7 @@ impl EventProcessor { let window = event.event as xproto::Window; // Set the timestamp. - self.target.xconn.set_timestamp(event.time as xproto::Timestamp); + self.record_server_time(event.time as xproto::Timestamp); // Leave, FocusIn, and FocusOut can be received by a window that's already // been destroyed, which the user presumably doesn't want to deal with. @@ -1193,7 +1230,7 @@ impl EventProcessor { position: Some(PhysicalPosition::new(event.event_x, event.event_y)), kind: PointerKind::Mouse, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } } @@ -1201,7 +1238,7 @@ impl EventProcessor { let window = xev.event as xproto::Window; // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); if let Some(ime) = self.target.ime.as_ref() { ime.borrow_mut().focus(xev.event).expect("Failed to focus input context"); @@ -1222,7 +1259,7 @@ impl EventProcessor { window.shared_state_lock().has_focus = true; } - app.window_event(&self.target, window_id, WindowEvent::Focused(true)); + self.emit_window_event(app, window_id, WindowEvent::Focused(true)); // Issue key press events for all pressed keys Self::handle_pressed_keys( @@ -1249,14 +1286,14 @@ impl EventProcessor { position, source: PointerSource::Mouse, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } fn xinput2_unfocused(&mut self, xev: &XIFocusOutEvent, app: &mut dyn ApplicationHandler) { let window = xev.event as xproto::Window; // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); if !self.window_exists(window) { return; @@ -1295,13 +1332,13 @@ impl EventProcessor { window.shared_state_lock().has_focus = false; } - app.window_event(&self.target, window_id, WindowEvent::Focused(false)); + self.emit_window_event(app, window_id, WindowEvent::Focused(false)); } } fn xinput2_touch(&mut self, xev: &XIDeviceEvent, phase: i32, app: &mut dyn ApplicationHandler) { // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); let window = xev.event as xproto::Window; if self.window_exists(window) { @@ -1320,7 +1357,7 @@ impl EventProcessor { position: position.cast(), source: PointerSource::Mouse, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } let device_id = Some(mkdid(xev.deviceid as xinput::DeviceId)); @@ -1334,7 +1371,7 @@ impl EventProcessor { position, kind: PointerKind::Touch(finger_id), }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); let event = WindowEvent::PointerButton { device_id, primary: is_first_touch, @@ -1342,7 +1379,7 @@ impl EventProcessor { position, button: ButtonSource::Touch { finger_id, force: None }, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); }, xinput2::XI_TouchUpdate => { let event = WindowEvent::PointerMoved { @@ -1351,7 +1388,7 @@ impl EventProcessor { position, source: PointerSource::Touch { finger_id, force: None }, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); }, xinput2::XI_TouchEnd => { let event = WindowEvent::PointerButton { @@ -1361,14 +1398,14 @@ impl EventProcessor { position, button: ButtonSource::Touch { finger_id, force: None }, }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); let event = WindowEvent::PointerLeft { device_id, primary: is_first_touch, position: Some(position), kind: PointerKind::Touch(finger_id), }; - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); }, _ => unreachable!(), } @@ -1382,17 +1419,17 @@ impl EventProcessor { app: &mut dyn ApplicationHandler, ) { // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); if xev.flags & xinput2::XIPointerEmulated == 0 { let event = DeviceEvent::Button { state, button: xev.detail as u32 }; - app.device_event(&self.target, Some(mkdid(xev.deviceid as xinput::DeviceId)), event); + self.emit_device_event(app, Some(mkdid(xev.deviceid as xinput::DeviceId)), event); } } fn xinput2_raw_mouse_motion(&self, xev: &XIRawEvent, app: &mut dyn ApplicationHandler) { // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); let did = Some(mkdid(xev.deviceid as xinput::DeviceId)); let mask = @@ -1429,14 +1466,14 @@ impl EventProcessor { }; if let Some(mouse_delta) = mouse_delta.consume() { - app.device_event(&self.target, did, DeviceEvent::PointerMotion { delta: mouse_delta }); + self.emit_device_event(app, did, DeviceEvent::PointerMotion { delta: mouse_delta }); } if let Some(scroll_delta) = scroll_delta.consume() { let event = DeviceEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(scroll_delta.0, scroll_delta.1), }; - app.device_event(&self.target, did, event); + self.emit_device_event(app, did, event); } } @@ -1447,7 +1484,7 @@ impl EventProcessor { app: &mut dyn ApplicationHandler, ) { // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); let device_id = Some(mkdid(xev.sourceid as xinput::DeviceId)); let keycode = xev.detail as u32; @@ -1457,12 +1494,12 @@ impl EventProcessor { let physical_key = xkb::raw_keycode_to_physicalkey(keycode); let event = DeviceEvent::Key(RawKeyEvent { physical_key, state }); - app.device_event(&self.target, device_id, event); + self.emit_device_event(app, device_id, event); } fn xinput2_hierarchy_changed(&mut self, xev: &XIHierarchyEvent) { // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); let infos = unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) }; for info in infos { if 0 != info.flags & (xinput2::XISlaveAdded | xinput2::XIMasterAdded) { @@ -1480,7 +1517,7 @@ impl EventProcessor { let xev = unsafe { &*(xev as *const _ as *const xlib::XkbNewKeyboardNotifyEvent) }; // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); let keycodes_changed_flag = 0x1; let geometry_changed_flag = 0x1 << 1; @@ -1524,7 +1561,7 @@ impl EventProcessor { let xev = unsafe { &*(xev as *const _ as *const xlib::XkbStateNotifyEvent) }; // Set the timestamp. - self.target.xconn.set_timestamp(xev.time as xproto::Timestamp); + self.record_server_time(xev.time as xproto::Timestamp); if let Some(state) = self.xkb_context.state_mut() { state.update_modifiers( @@ -1699,7 +1736,7 @@ impl EventProcessor { // and forced was `true`. if self.modifiers.replace(modifiers) != modifiers || force { let event = WindowEvent::ModifiersChanged(self.modifiers.get().into()); - app.window_event(&self.target, window_id, event); + self.emit_window_event(app, window_id, event); } } @@ -1731,7 +1768,7 @@ impl EventProcessor { for keycode in target.xconn.query_keymap().into_iter().filter(|k| *k >= KEYCODE_OFFSET) { let event = key_processor.process_key_event(keycode as u32, state, false); let event = WindowEvent::KeyboardInput { device_id: None, event, is_synthetic: true }; - app.window_event(target, window_id, event); + app.window_event(target, window_id, Instant::now(), event); } } diff --git a/winit-x11/src/window.rs b/winit-x11/src/window.rs index 0277732110..e60a39fb98 100644 --- a/winit-x11/src/window.rs +++ b/winit-x11/src/window.rs @@ -6,6 +6,7 @@ use std::ops::Deref; use std::os::raw::*; use std::path::Path; use std::sync::{Arc, Mutex, MutexGuard}; +use std::time::Instant; use std::{cmp, env}; use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; @@ -1237,7 +1238,7 @@ impl UnownedWindow { let old_surface_size = PhysicalSize::new(width, height); let surface_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height))); - app.window_event(event_loop, self.id(), WindowEvent::ScaleFactorChanged { + app.window_event(event_loop, self.id(), Instant::now(), WindowEvent::ScaleFactorChanged { scale_factor: new_monitor.scale_factor, surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(&surface_size)), }); diff --git a/winit-x11/src/xdisplay.rs b/winit-x11/src/xdisplay.rs index 9eb190bc79..4a58822398 100644 --- a/winit-x11/src/xdisplay.rs +++ b/winit-x11/src/xdisplay.rs @@ -3,6 +3,7 @@ use std::error::Error; use std::ffi::c_int; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard}; +use std::time::{Duration, Instant}; use std::{fmt, ptr}; use rwh_06::HasDisplayHandle; @@ -46,6 +47,11 @@ pub struct XConnection { /// The last timestamp received by this connection. timestamp: AtomicU32, + /// Anchor for translating X server millisecond timestamps into [`Instant`]. `None` until + /// the first server timestamp is observed, then paired with a fresh [`Instant::now()`] + /// so that returned timestamps stay close to real time. + server_time_anchor: Mutex>, + /// List of monitor handles. pub monitor_handles: Mutex>>, @@ -152,6 +158,7 @@ impl XConnection { atoms: Box::new(atoms), default_screen, timestamp: AtomicU32::new(0), + server_time_anchor: Mutex::new(None), latest_error: Mutex::new(None), monitor_handles: Mutex::new(None), database: RwLock::new(database), @@ -244,6 +251,27 @@ impl XConnection { self.timestamp.load(Ordering::Relaxed) } + /// Translate an X server millisecond timestamp into an [`Instant`]. + /// Slides the anchor forward so the distance never approaches u32::MAX / 2, + /// which keeps the wrap-detection valid past the 49.7-day wrap point. + pub fn server_time_to_instant(&self, server_time: u32) -> Instant { + let mut anchor = self.server_time_anchor.lock().unwrap_or_else(|e| e.into_inner()); + let (anchor_time, anchor_instant) = + anchor.get_or_insert_with(|| (server_time, Instant::now())); + + let delta = server_time.wrapping_sub(*anchor_time); + if delta < u32::MAX / 2 { + *anchor_instant += Duration::from_millis(delta as u64); + *anchor_time = server_time; + *anchor_instant + } else { + let backward = anchor_time.wrapping_sub(server_time); + anchor_instant + .checked_sub(Duration::from_millis(backward as u64)) + .unwrap_or(*anchor_instant) + } + } + /// Set the last witnessed timestamp. #[inline] pub fn set_timestamp(&self, timestamp: u32) { diff --git a/winit/examples/application.rs b/winit/examples/application.rs index 4098ebff68..b32a8a27d3 100644 --- a/winit/examples/application.rs +++ b/winit/examples/application.rs @@ -10,16 +10,13 @@ use std::fmt::Debug; use std::num::NonZeroU32; use std::sync::Arc; use std::sync::mpsc::{self, Receiver, Sender}; -#[cfg(not(web_platform))] -use std::time::Instant; use std::{fmt, mem}; use cursor_icon::CursorIcon; use rwh_06::{DisplayHandle, HasDisplayHandle}; use softbuffer::{Context, Surface}; use tracing::{error, info}; -#[cfg(web_platform)] -use web_time::Instant; +use winit::Instant; use winit::application::ApplicationHandler; use winit::cursor::{Cursor, CustomCursor, CustomCursorSource}; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; @@ -418,6 +415,7 @@ impl ApplicationHandler for Application { &mut self, event_loop: &dyn ActiveEventLoop, window_id: WindowId, + _timestamp: Instant, event: WindowEvent, ) { let window = match self.windows.get_mut(&window_id) { @@ -558,6 +556,7 @@ impl ApplicationHandler for Application { &mut self, _event_loop: &dyn ActiveEventLoop, device_id: Option, + _timestamp: Instant, event: DeviceEvent, ) { info!("Device {device_id:?} event: {event:?}"); diff --git a/winit/examples/child_window.rs b/winit/examples/child_window.rs index 4077bca36b..c9a79a7dd5 100644 --- a/winit/examples/child_window.rs +++ b/winit/examples/child_window.rs @@ -4,6 +4,7 @@ fn main() -> Result<(), impl std::error::Error> { use std::collections::HashMap; use tracing::info; + use winit::Instant; use winit::application::ApplicationHandler; use winit::dpi::{LogicalPosition, LogicalSize, Position}; use winit::event::{ElementState, KeyEvent, WindowEvent}; @@ -49,6 +50,7 @@ fn main() -> Result<(), impl std::error::Error> { &mut self, event_loop: &dyn ActiveEventLoop, window_id: winit::window::WindowId, + _timestamp: Instant, event: WindowEvent, ) { match event { diff --git a/winit/examples/control_flow.rs b/winit/examples/control_flow.rs index 04e6a04d62..d4f12a5488 100644 --- a/winit/examples/control_flow.rs +++ b/winit/examples/control_flow.rs @@ -76,6 +76,7 @@ impl ApplicationHandler for ControlFlowDemo { &mut self, _event_loop: &dyn ActiveEventLoop, _window_id: WindowId, + _timestamp: winit::Instant, event: WindowEvent, ) { info!("{event:?}"); diff --git a/winit/examples/dnd.rs b/winit/examples/dnd.rs index c8d0332656..7efe17a19b 100644 --- a/winit/examples/dnd.rs +++ b/winit/examples/dnd.rs @@ -1,6 +1,7 @@ use std::error::Error; use tracing::info; +use winit::Instant; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; @@ -43,6 +44,7 @@ impl ApplicationHandler for Application { &mut self, event_loop: &dyn ActiveEventLoop, _window_id: WindowId, + _timestamp: Instant, event: WindowEvent, ) { match event { diff --git a/winit/examples/ime.rs b/winit/examples/ime.rs index 1aee54f111..ab8f248c69 100644 --- a/winit/examples/ime.rs +++ b/winit/examples/ime.rs @@ -10,6 +10,7 @@ use std::error::Error; use dpi::{LogicalPosition, PhysicalSize}; use tracing::{error, info}; +use winit::Instant; use winit::application::ApplicationHandler; use winit::event::{Ime, WindowEvent}; use winit::event_loop::{ActiveEventLoop, EventLoop}; @@ -97,7 +98,13 @@ impl ApplicationHandler for App { self.window().request_ime_update(enable_ime).unwrap(); } - fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) { + fn window_event( + &mut self, + event_loop: &dyn ActiveEventLoop, + _: WindowId, + _timestamp: Instant, + event: WindowEvent, + ) { match event { WindowEvent::CloseRequested => { info!("Close was requested; stopping"); diff --git a/winit/examples/pump_events.rs b/winit/examples/pump_events.rs index 181fb366af..591a5d1a0c 100644 --- a/winit/examples/pump_events.rs +++ b/winit/examples/pump_events.rs @@ -8,6 +8,7 @@ fn main() -> std::process::ExitCode { use std::time::Duration; use tracing::info; + use winit::Instant; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::pump_events::{EventLoopExtPumpEvents, PumpStatus}; @@ -34,6 +35,7 @@ fn main() -> std::process::ExitCode { &mut self, event_loop: &dyn ActiveEventLoop, _window_id: WindowId, + _timestamp: Instant, event: WindowEvent, ) { info!("{event:?}"); diff --git a/winit/examples/run_on_demand.rs b/winit/examples/run_on_demand.rs index a9df3cff9e..d37ad733bd 100644 --- a/winit/examples/run_on_demand.rs +++ b/winit/examples/run_on_demand.rs @@ -6,6 +6,7 @@ fn main() -> Result<(), Box> { use std::time::Duration; use tracing::info; + use winit::Instant; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand; @@ -44,6 +45,7 @@ fn main() -> Result<(), Box> { &mut self, event_loop: &dyn ActiveEventLoop, window_id: WindowId, + _timestamp: Instant, event: WindowEvent, ) { if event == WindowEvent::Destroyed && self.window_id == Some(window_id) { diff --git a/winit/examples/util/fill.rs b/winit/examples/util/fill.rs index ff3ca198d8..079d013084 100644 --- a/winit/examples/util/fill.rs +++ b/winit/examples/util/fill.rs @@ -12,12 +12,9 @@ use std::collections::HashMap; use std::mem; use std::mem::ManuallyDrop; use std::num::NonZeroU32; -#[cfg(not(web_platform))] -use std::time::Instant; use softbuffer::{Context, Surface}; -#[cfg(web_platform)] -use web_time::Instant; +use winit::Instant; use winit::window::{Window, WindowId}; thread_local! { diff --git a/winit/examples/window.rs b/winit/examples/window.rs index 7d9fc59f4c..e3e88c2272 100644 --- a/winit/examples/window.rs +++ b/winit/examples/window.rs @@ -3,6 +3,7 @@ use std::error::Error; use tracing::{error, info}; +use winit::Instant; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; @@ -37,7 +38,13 @@ impl ApplicationHandler for App { } } - fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) { + fn window_event( + &mut self, + event_loop: &dyn ActiveEventLoop, + _: WindowId, + _timestamp: Instant, + event: WindowEvent, + ) { info!("{event:?}"); match event { WindowEvent::CloseRequested => { diff --git a/winit/examples/x11_embed.rs b/winit/examples/x11_embed.rs index b25d6ff39d..d5bd80d17e 100644 --- a/winit/examples/x11_embed.rs +++ b/winit/examples/x11_embed.rs @@ -3,6 +3,7 @@ use std::error::Error; #[cfg(x11_platform)] fn main() -> Result<(), Box> { + use winit::Instant; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; @@ -36,6 +37,7 @@ fn main() -> Result<(), Box> { &mut self, event_loop: &dyn ActiveEventLoop, _window_id: WindowId, + _timestamp: Instant, event: WindowEvent, ) { let window = self.window.as_ref().unwrap(); diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index 729bfd639b..0042780e64 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -54,6 +54,21 @@ changelog entry. - Updated `windows-sys` to `v0.61`. - On older macOS versions (tested up to 12.7.6), applications now receive mouse movement events for unfocused windows, matching the behavior on other platforms. +- `ApplicationHandler::window_event` and `ApplicationHandler::device_event` now take an additional `timestamp: Instant` parameter between the id and the event. The timestamp is derived from the OS-provided event time where available (macOS via `NSEvent.timestamp`, X11 via `xev.time`, Wayland via the `time` field on `wl_keyboard`/`wl_pointer`/`wl_touch` events and the `utime_hi`/`utime_lo` pair on `zwp_relative_pointer_v1`) and otherwise is sampled close to when winit received the event. Using this timestamp instead of `Instant::now()` when the event is received eliminates the polling-delay latency previously seen under load. + + To migrate, add `timestamp: Instant` to your implementations of `window_event` and `device_event`. Use the `winit::Instant` re-export rather than `std::time::Instant` so the signature compiles on `wasm32-unknown-unknown` (where winit uses `web_time::Instant`) without `cfg`-gated imports: + + ```rust + use winit::Instant; + + fn window_event( + &mut self, + event_loop: &dyn ActiveEventLoop, + window_id: WindowId, + timestamp: Instant, // new + event: WindowEvent, + ) { /* ... */ } + ``` ### Fixed diff --git a/winit/src/event_loop.rs b/winit/src/event_loop.rs index 22fc1b9801..e766450c73 100644 --- a/winit/src/event_loop.rs +++ b/winit/src/event_loop.rs @@ -150,14 +150,19 @@ impl EventLoop { /// // window, changing the window theme, etc. /// for event in event_loop.events() { /// match event { - /// window event => app.window_event(event_loop, window_id, event), - /// device event => app.device_event(event_loop, device_id, event), + /// window event => app.window_event(event_loop, window_id, timestamp, event), + /// device event => app.device_event(event_loop, device_id, timestamp, event), /// } /// } /// /// // Handle redraws. /// for window_id in event_loop.pending_redraws() { - /// app.window_event(event_loop, window_id, WindowEvent::RedrawRequested); + /// app.window_event( + /// event_loop, + /// window_id, + /// timestamp, + /// WindowEvent::RedrawRequested, + /// ); /// } /// /// // Done handling events, wait until we're woken up again. diff --git a/winit/src/lib.rs b/winit/src/lib.rs index beb5c809ec..f8bf7e6b34 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -37,6 +37,7 @@ //! //! //! ```no_run +//! use winit::Instant; //! use winit::application::ApplicationHandler; //! use winit::event::WindowEvent; //! use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; @@ -59,6 +60,7 @@ //! &mut self, //! event_loop: &dyn ActiveEventLoop, //! id: WindowId, +//! _timestamp: Instant, //! event: WindowEvent, //! ) { //! // Called by `EventLoop::run_app` when a new event happens on the window. @@ -292,7 +294,7 @@ pub use rwh_06 as raw_window_handle; #[cfg(any(doc, doctest, test))] pub mod changelog; pub mod event_loop; -pub use winit_core::{application, cursor, error, event, icon, keyboard, monitor, window}; +pub use winit_core::{Instant, application, cursor, error, event, icon, keyboard, monitor, window}; #[macro_use] mod os_error; mod platform_impl;