diff --git a/Cargo.toml b/Cargo.toml index d64c8f96f7..ef05ae1f83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,8 +81,8 @@ xkbcommon-dl = "0.4.2" # Orbital dependencies. libredox = "0.1.12" -orbclient = { version = "0.3.47", default-features = false } -redox_event = { package = "redox_event", version = "0.4.5" } +orbclient = { version = "0.4.3", default-features = false } +redox_event = { package = "redox_event", version = "0.4.8" } # Web dependencies. atomic-waker = "1" diff --git a/winit-orbital/src/event_loop.rs b/winit-orbital/src/event_loop.rs index f42b9188b6..2175df7061 100644 --- a/winit-orbital/src/event_loop.rs +++ b/winit-orbital/src/event_loop.rs @@ -1,9 +1,8 @@ use std::cell::Cell; use std::collections::VecDeque; -use std::os::raw::c_long; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, mpsc}; -use std::time::Instant; +use std::time::{Duration, Instant}; use std::{iter, mem, slice}; use bitflags::bitflags; @@ -11,12 +10,13 @@ use orbclient::{ ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent, MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent, }; -use redox_event::{EventFlags, EventQueue}; +use redox_event::{EventFlags, EventQueue, UserData}; use smol_str::SmolStr; use winit_core::application::ApplicationHandler; use winit_core::cursor::{CustomCursor, CustomCursorSource}; use winit_core::error::{EventLoopError, NotSupportedError, RequestError}; use winit_core::event::{self, Ime, Modifiers, StartCause}; +use winit_core::event_loop::pump_events::PumpStatus; use winit_core::event_loop::{ ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, @@ -31,6 +31,9 @@ use winit_core::window::{Theme, Window as CoreWindow, WindowId}; use crate::window::Window; use crate::{RedoxSocket, TimeSocket, WindowProperties}; +/// timeout from redox_syscall +const EVENT_TIMEOUT_ID: usize = usize::MAX - 2; + fn convert_scancode(scancode: u8) -> (PhysicalKey, Option) { // Key constants from https://docs.rs/orbclient/latest/orbclient/event/index.html let (key_code, named_key_opt) = match scancode { @@ -157,6 +160,15 @@ fn convert_scancode(scancode: u8) -> (PhysicalKey, Option) { (PhysicalKey::Code(key_code), named_key_opt) } +pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { + convert_scancode(scancode.try_into().unwrap_or_default()).0 +} + +pub fn physicalkey_to_scancode(_physical_key: PhysicalKey) -> Option { + // TODO + None +} + fn element_state(pressed: bool) -> event::ElementState { if pressed { event::ElementState::Pressed } else { event::ElementState::Released } } @@ -296,6 +308,8 @@ impl EventState { #[derive(Debug)] pub struct EventLoop { + /// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop + loop_running: bool, windows: Vec<(Arc, EventState)>, window_target: ActiveEventLoop, user_events_receiver: mpsc::Receiver<()>, @@ -323,6 +337,7 @@ impl EventLoop { .map_err(|error| os_error!(format!("{error}")))?; Ok(Self { + loop_running: false, windows: Vec::new(), window_target: ActiveEventLoop { control_flow: Cell::new(ControlFlow::default()), @@ -508,11 +523,28 @@ impl EventLoop { &mut self, mut app: A, ) -> Result<(), EventLoopError> { - let mut start_cause = StartCause::Init; loop { - app.new_events(&self.window_target, start_cause); + match self.pump_app_events(None, &mut app) { + PumpStatus::Exit(0) => { + break Ok(()); + }, + PumpStatus::Exit(code) => { + break Err(EventLoopError::ExitFailure(code)); + }, + _ => { + continue; + }, + } + } + } + + fn single_iteration(&mut self, app: &mut A, cause: StartCause) { + // TODO: Unindent + { + app.new_events(&self.window_target, cause); - if start_cause == StartCause::Init { + if cause == StartCause::Init { + // NB: For consistency all platforms must call `can_create_surfaces` app.can_create_surfaces(&self.window_target); } @@ -572,7 +604,7 @@ impl EventLoop { orbital_event.to_option(), event_state, &self.window_target, - &mut app, + app, ); } @@ -616,75 +648,82 @@ impl EventLoop { } app.about_to_wait(&self.window_target); + } + } - if self.window_target.exiting() { - break; - } + pub fn pump_app_events( + &mut self, + timeout: Option, + mut app: A, + ) -> PumpStatus { + if !self.loop_running { + self.loop_running = true; + + // Run the initial loop iteration. + self.single_iteration(&mut app, StartCause::Init); + } + + if self.window_target.exit.get() { + self.loop_running = false; + // TODO: other exit codes + return PumpStatus::Exit(0); + } + let start = Instant::now(); + let timeout = { let requested_resume = match self.window_target.control_flow() { - ControlFlow::Poll => { - start_cause = StartCause::Poll; - continue; - }, + ControlFlow::Poll => Some(Duration::ZERO), ControlFlow::Wait => None, - ControlFlow::WaitUntil(instant) => Some(instant), + ControlFlow::WaitUntil(instant) => Some(instant.saturating_duration_since(start)), }; + min_timeout(timeout, requested_resume) + }; - // Re-using wake socket caused extra wake events before because there were leftover - // timeouts, and then new timeouts were added each time a spurious timeout expired. - let timeout_socket = TimeSocket::open().unwrap(); - + if let Some(timeout) = timeout { self.window_target .event_socket - .subscribe(timeout_socket.0.fd(), EventSource::Time, EventFlags::READ) - .unwrap(); - - let start = Instant::now(); - if let Some(instant) = requested_resume { - let mut time = timeout_socket.current_time().unwrap(); - - if let Some(duration) = instant.checked_duration_since(start) { - time.tv_sec += duration.as_secs() as i64; - time.tv_nsec += duration.subsec_nanos() as c_long; - // Normalize timespec so tv_nsec is not greater than one second. - while time.tv_nsec >= 1_000_000_000 { - time.tv_sec += 1; - time.tv_nsec -= 1_000_000_000; - } - } + .subscribe( + EVENT_TIMEOUT_ID, + UserData::from_user_data(timeout.as_millis() as usize), + EventFlags::READ, + ) + .expect("failed to register EVENT_TIMEOUT_ID") + } - timeout_socket.timeout(&time).unwrap(); + // Wait for event if needed. + let event = loop { + match self.window_target.event_socket.next_event() { + Ok(event) => break event, + Err(err) if err.is_interrupt() => continue, + Err(err) => { + panic!("failed to read event: {err}"); + }, } + }; - // Wait for event if needed. - let event = loop { - match self.window_target.event_socket.next_event() { - Ok(event) => break event, - Err(err) if err.is_interrupt() => continue, - Err(err) => { - return Err(os_error!(format!("failed to read event: {err}")).into()); - }, + if timeout.is_some() && event.fd == EVENT_TIMEOUT_ID { + // NB: EVENT_TIMEOUT_ID is not a regular event ID. + // Here nothing is happened yet. + return PumpStatus::Continue; + } + + // Normal window event or spurious timeout. + let cause = match self.window_target.control_flow() { + ControlFlow::Poll => StartCause::Poll, + ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None }, + ControlFlow::WaitUntil(deadline) => { + if Instant::now() < deadline { + StartCause::WaitCancelled { start, requested_resume: Some(deadline) } + } else { + StartCause::ResumeTimeReached { start, requested_resume: deadline } } - }; + }, + }; - // TODO: handle spurious wakeups (redraw caused wakeup but redraw already handled) - match requested_resume { - Some(requested_resume) - if event.fd == timeout_socket.0.fd() - && matches!(event.user_data, EventSource::Time) => - { - // If the event is from the special timeout socket, report that resume - // time was reached. - start_cause = StartCause::ResumeTimeReached { start, requested_resume }; - }, - _ => { - // Normal window event or spurious timeout. - start_cause = StartCause::WaitCancelled { start, requested_resume }; - }, - } - } + // Do actual event processing + self.single_iteration(&mut app, cause); - Ok(()) + PumpStatus::Continue } pub fn window_target(&self) -> &dyn RootActiveEventLoop { @@ -802,3 +841,10 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle { #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct PlatformSpecificEventLoopAttributes {} + +/// Returns the minimum `Option`, taking into account that `None` +/// equates to an infinite timeout, not a zero timeout (so can't just use +/// `Option::min`) +fn min_timeout(a: Option, b: Option) -> Option { + a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout)))) +} diff --git a/winit-orbital/src/lib.rs b/winit-orbital/src/lib.rs index 857ba4d056..23fd58e29f 100644 --- a/winit-orbital/src/lib.rs +++ b/winit-orbital/src/lib.rs @@ -66,6 +66,7 @@ impl TimeSocket { } // Read current time. + #[allow(unused)] fn current_time(&self) -> Result { let mut timespec: libredox::data::TimeSpec = unsafe { mem::zeroed() }; let timespec_bytes = unsafe { diff --git a/winit/examples/pump_events.rs b/winit/examples/pump_events.rs index 782c7b6788..74644559e6 100644 --- a/winit/examples/pump_events.rs +++ b/winit/examples/pump_events.rs @@ -1,7 +1,14 @@ #![allow(clippy::single_match)] // Limit this example to only compatible platforms. -#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, android_platform,))] +#[cfg(any( + windows_platform, + macos_platform, + x11_platform, + wayland_platform, + android_platform, + orbital_platform, +))] fn main() -> std::process::ExitCode { use std::process::ExitCode; use std::thread::sleep; @@ -81,7 +88,7 @@ fn main() -> std::process::ExitCode { } } -#[cfg(any(ios_platform, web_platform, orbital_platform))] +#[cfg(any(ios_platform, web_platform))] fn main() { panic!("This platform doesn't support pump_events.") } diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index a14ab1fa72..4f5ea80a2d 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -45,6 +45,7 @@ changelog entry. - Add `keyboard` support for OpenHarmony. - On iOS, add Apple Pencil support with force, altitude, and azimuth data. - On Redox, add support for missing keyboard scancodes. +- On Redox, add support for `EventLoopExtPumpEvents::pump_app_events`. - Implement `Send` and `Sync` for `OwnedDisplayHandle`. - Use new macOS 15 cursors for resize icons. - On Android, added scancode conversions for more obscure key codes. diff --git a/winit/src/event_loop.rs b/winit/src/event_loop.rs index 22fc1b9801..57adf6fb69 100644 --- a/winit/src/event_loop.rs +++ b/winit/src/event_loop.rs @@ -326,6 +326,7 @@ impl AsRawFd for EventLoop { windows_platform, macos_platform, android_platform, + orbital_platform, x11_platform, wayland_platform, docsrs, diff --git a/winit/src/lib.rs b/winit/src/lib.rs index bab3fd9900..70343bbb5b 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -269,7 +269,7 @@ //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle //! [`EventLoopExtPumpEvents::pump_app_events()`]: crate::event_loop::pump_events::EventLoopExtPumpEvents::pump_app_events() -//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland. +//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, Redox, X11 and Wayland. #![deny(rust_2018_idioms)] #![deny(rustdoc::broken_intra_doc_links)]