winit-orbital: Implement pump_app_events

This commit is contained in:
Wildan M
2026-07-18 15:52:09 +07:00
committed by Jeremy Soller
parent 47dada25e4
commit d84ec647d8
7 changed files with 133 additions and 77 deletions

View File

@@ -81,8 +81,8 @@ xkbcommon-dl = "0.4.2"
# Orbital dependencies. # Orbital dependencies.
libredox = "0.1.12" libredox = "0.1.12"
orbclient = { version = "0.3.47", default-features = false } orbclient = { version = "0.4.3", default-features = false }
redox_event = { package = "redox_event", version = "0.4.5" } redox_event = { package = "redox_event", version = "0.4.8" }
# Web dependencies. # Web dependencies.
atomic-waker = "1" atomic-waker = "1"

View File

@@ -1,9 +1,8 @@
use std::cell::Cell; use std::cell::Cell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::os::raw::c_long;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, mpsc}; use std::sync::{Arc, Mutex, mpsc};
use std::time::Instant; use std::time::{Duration, Instant};
use std::{iter, mem, slice}; use std::{iter, mem, slice};
use bitflags::bitflags; use bitflags::bitflags;
@@ -11,12 +10,13 @@ use orbclient::{
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent,
MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent, MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent,
}; };
use redox_event::{EventFlags, EventQueue}; use redox_event::{EventFlags, EventQueue, UserData};
use smol_str::SmolStr; use smol_str::SmolStr;
use winit_core::application::ApplicationHandler; use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource}; use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, NotSupportedError, RequestError}; use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
use winit_core::event::{self, Ime, Modifiers, StartCause}; use winit_core::event::{self, Ime, Modifiers, StartCause};
use winit_core::event_loop::pump_events::PumpStatus;
use winit_core::event_loop::{ use winit_core::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
@@ -31,6 +31,9 @@ use winit_core::window::{Theme, Window as CoreWindow, WindowId};
use crate::window::Window; use crate::window::Window;
use crate::{RedoxSocket, TimeSocket, WindowProperties}; use crate::{RedoxSocket, TimeSocket, WindowProperties};
/// timeout from redox_syscall
const EVENT_TIMEOUT_ID: usize = usize::MAX - 2;
fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) { fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
// Key constants from https://docs.rs/orbclient/latest/orbclient/event/index.html // Key constants from https://docs.rs/orbclient/latest/orbclient/event/index.html
let (key_code, named_key_opt) = match scancode { let (key_code, named_key_opt) = match scancode {
@@ -157,6 +160,15 @@ fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
(PhysicalKey::Code(key_code), named_key_opt) (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<u32> {
// TODO
None
}
fn element_state(pressed: bool) -> event::ElementState { fn element_state(pressed: bool) -> event::ElementState {
if pressed { event::ElementState::Pressed } else { event::ElementState::Released } if pressed { event::ElementState::Pressed } else { event::ElementState::Released }
} }
@@ -296,6 +308,8 @@ impl EventState {
#[derive(Debug)] #[derive(Debug)]
pub struct EventLoop { 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<RedoxSocket>, EventState)>, windows: Vec<(Arc<RedoxSocket>, EventState)>,
window_target: ActiveEventLoop, window_target: ActiveEventLoop,
user_events_receiver: mpsc::Receiver<()>, user_events_receiver: mpsc::Receiver<()>,
@@ -323,6 +337,7 @@ impl EventLoop {
.map_err(|error| os_error!(format!("{error}")))?; .map_err(|error| os_error!(format!("{error}")))?;
Ok(Self { Ok(Self {
loop_running: false,
windows: Vec::new(), windows: Vec::new(),
window_target: ActiveEventLoop { window_target: ActiveEventLoop {
control_flow: Cell::new(ControlFlow::default()), control_flow: Cell::new(ControlFlow::default()),
@@ -508,11 +523,28 @@ impl EventLoop {
&mut self, &mut self,
mut app: A, mut app: A,
) -> Result<(), EventLoopError> { ) -> Result<(), EventLoopError> {
let mut start_cause = StartCause::Init;
loop { 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;
},
}
}
}
if start_cause == StartCause::Init { fn single_iteration<A: ApplicationHandler>(&mut self, app: &mut A, cause: StartCause) {
// TODO: Unindent
{
app.new_events(&self.window_target, cause);
if cause == StartCause::Init {
// NB: For consistency all platforms must call `can_create_surfaces`
app.can_create_surfaces(&self.window_target); app.can_create_surfaces(&self.window_target);
} }
@@ -572,7 +604,7 @@ impl EventLoop {
orbital_event.to_option(), orbital_event.to_option(),
event_state, event_state,
&self.window_target, &self.window_target,
&mut app, app,
); );
} }
@@ -616,44 +648,46 @@ impl EventLoop {
} }
app.about_to_wait(&self.window_target); app.about_to_wait(&self.window_target);
}
if self.window_target.exiting() {
break;
} }
let requested_resume = match self.window_target.control_flow() { pub fn pump_app_events<A: ApplicationHandler>(
ControlFlow::Poll => { &mut self,
start_cause = StartCause::Poll; timeout: Option<Duration>,
continue; mut app: A,
}, ) -> PumpStatus {
ControlFlow::Wait => None, if !self.loop_running {
ControlFlow::WaitUntil(instant) => Some(instant), self.loop_running = true;
};
// Re-using wake socket caused extra wake events before because there were leftover // Run the initial loop iteration.
// timeouts, and then new timeouts were added each time a spurious timeout expired. self.single_iteration(&mut app, StartCause::Init);
let timeout_socket = TimeSocket::open().unwrap(); }
self.window_target if self.window_target.exit.get() {
.event_socket self.loop_running = false;
.subscribe(timeout_socket.0.fd(), EventSource::Time, EventFlags::READ) // TODO: other exit codes
.unwrap(); return PumpStatus::Exit(0);
}
let start = Instant::now(); let start = Instant::now();
if let Some(instant) = requested_resume { let timeout = {
let mut time = timeout_socket.current_time().unwrap(); let requested_resume = match self.window_target.control_flow() {
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::Wait => None,
ControlFlow::WaitUntil(instant) => Some(instant.saturating_duration_since(start)),
};
min_timeout(timeout, requested_resume)
};
if let Some(duration) = instant.checked_duration_since(start) { if let Some(timeout) = timeout {
time.tv_sec += duration.as_secs() as i64; self.window_target
time.tv_nsec += duration.subsec_nanos() as c_long; .event_socket
// Normalize timespec so tv_nsec is not greater than one second. .subscribe(
while time.tv_nsec >= 1_000_000_000 { EVENT_TIMEOUT_ID,
time.tv_sec += 1; UserData::from_user_data(timeout.as_millis() as usize),
time.tv_nsec -= 1_000_000_000; EventFlags::READ,
} )
} .expect("failed to register EVENT_TIMEOUT_ID")
timeout_socket.timeout(&time).unwrap();
} }
// Wait for event if needed. // Wait for event if needed.
@@ -662,29 +696,34 @@ impl EventLoop {
Ok(event) => break event, Ok(event) => break event,
Err(err) if err.is_interrupt() => continue, Err(err) if err.is_interrupt() => continue,
Err(err) => { Err(err) => {
return Err(os_error!(format!("failed to read event: {err}")).into()); panic!("failed to read event: {err}");
}, },
} }
}; };
// TODO: handle spurious wakeups (redraw caused wakeup but redraw already handled) if timeout.is_some() && event.fd == EVENT_TIMEOUT_ID {
match requested_resume { // NB: EVENT_TIMEOUT_ID is not a regular event ID.
Some(requested_resume) // Here nothing is happened yet.
if event.fd == timeout_socket.0.fd() return PumpStatus::Continue;
&& 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 };
},
}
} }
Ok(()) // 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 }
}
},
};
// Do actual event processing
self.single_iteration(&mut app, cause);
PumpStatus::Continue
} }
pub fn window_target(&self) -> &dyn RootActiveEventLoop { 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)] #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct PlatformSpecificEventLoopAttributes {} pub struct PlatformSpecificEventLoopAttributes {}
/// Returns the minimum `Option<Duration>`, 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<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}

View File

@@ -66,6 +66,7 @@ impl TimeSocket {
} }
// Read current time. // Read current time.
#[allow(unused)]
fn current_time(&self) -> Result<TimeSpec> { fn current_time(&self) -> Result<TimeSpec> {
let mut timespec: libredox::data::TimeSpec = unsafe { mem::zeroed() }; let mut timespec: libredox::data::TimeSpec = unsafe { mem::zeroed() };
let timespec_bytes = unsafe { let timespec_bytes = unsafe {

View File

@@ -1,7 +1,14 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
// Limit this example to only compatible platforms. // 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 { fn main() -> std::process::ExitCode {
use std::process::ExitCode; use std::process::ExitCode;
use std::thread::sleep; 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() { fn main() {
panic!("This platform doesn't support pump_events.") panic!("This platform doesn't support pump_events.")
} }

View File

@@ -45,6 +45,7 @@ changelog entry.
- Add `keyboard` support for OpenHarmony. - Add `keyboard` support for OpenHarmony.
- On iOS, add Apple Pencil support with force, altitude, and azimuth data. - 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 missing keyboard scancodes.
- On Redox, add support for `EventLoopExtPumpEvents::pump_app_events`.
- Implement `Send` and `Sync` for `OwnedDisplayHandle`. - Implement `Send` and `Sync` for `OwnedDisplayHandle`.
- Use new macOS 15 cursors for resize icons. - Use new macOS 15 cursors for resize icons.
- On Android, added scancode conversions for more obscure key codes. - On Android, added scancode conversions for more obscure key codes.

View File

@@ -326,6 +326,7 @@ impl AsRawFd for EventLoop {
windows_platform, windows_platform,
macos_platform, macos_platform,
android_platform, android_platform,
orbital_platform,
x11_platform, x11_platform,
wayland_platform, wayland_platform,
docsrs, docsrs,

View File

@@ -269,7 +269,7 @@
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_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() //! [`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(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::broken_intra_doc_links)]