Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
170 changes: 108 additions & 62 deletions winit-orbital/src/event_loop.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
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;
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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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 }
}
Expand Down Expand Up @@ -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<()>,
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -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<A: ApplicationHandler>(&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);
}

Expand Down Expand Up @@ -572,7 +604,7 @@ impl EventLoop {
orbital_event.to_option(),
event_state,
&self.window_target,
&mut app,
app,
);
}

Expand Down Expand Up @@ -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;

// 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 {
Expand Down Expand Up @@ -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))))
}
1 change: 1 addition & 0 deletions winit-orbital/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 9 additions & 2 deletions winit/examples/pump_events.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.")
}
1 change: 1 addition & 0 deletions winit/src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions winit/src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ impl AsRawFd for EventLoop {
windows_platform,
macos_platform,
android_platform,
orbital_platform,
x11_platform,
wayland_platform,
docsrs,
Expand Down
2 changes: 1 addition & 1 deletion winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down