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.
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"

View File

@@ -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<NamedKey>) {
// 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<NamedKey>) {
(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 {
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<RedoxSocket>, 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;
},
}
}
}
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);
}
@@ -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<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
mut app: A,
) -> PumpStatus {
if !self.loop_running {
self.loop_running = true;
let requested_resume = match self.window_target.control_flow() {
ControlFlow::Poll => {
start_cause = StartCause::Poll;
continue;
},
ControlFlow::Wait => None,
ControlFlow::WaitUntil(instant) => Some(instant),
};
// 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();
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;
}
}
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) => {
return Err(os_error!(format!("failed to read event: {err}")).into());
},
}
};
// 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 };
},
}
// Run the initial loop iteration.
self.single_iteration(&mut app, StartCause::Init);
}
Ok(())
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 => Some(Duration::ZERO),
ControlFlow::Wait => None,
ControlFlow::WaitUntil(instant) => Some(instant.saturating_duration_since(start)),
};
min_timeout(timeout, requested_resume)
};
if let Some(timeout) = timeout {
self.window_target
.event_socket
.subscribe(
EVENT_TIMEOUT_ID,
UserData::from_user_data(timeout.as_millis() as usize),
EventFlags::READ,
)
.expect("failed to register EVENT_TIMEOUT_ID")
}
// 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}");
},
}
};
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 }
}
},
};
// Do actual event processing
self.single_iteration(&mut app, cause);
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<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.
#[allow(unused)]
fn current_time(&self) -> Result<TimeSpec> {
let mut timespec: libredox::data::TimeSpec = unsafe { mem::zeroed() };
let timespec_bytes = unsafe {

View File

@@ -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.")
}

View File

@@ -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.

View File

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

View File

@@ -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)]