Compare commits

...

4 Commits

Author SHA1 Message Date
Kirill Chibisov
f98ccf8efc Winit version 0.31.0-beta.2 2025-11-16 17:23:28 +09:00
Kirill Chibisov
b961e8e941 winit-core: fix set_ime_allowed always panicing 2025-11-16 17:23:28 +09:00
Mads Marquart
4c5bf0ee08 winit/event_loop: Add register_app and run_app_never_return
To allow users to explicitly choose the run semantics that they want.
2025-11-16 17:23:28 +09:00
Mads Marquart
f69b601abb winit-web: return immediately from run_app on web
This avoids using JavaScript exceptions to support `EventLoop::run_app`
on the web, which is a huge hack, and doesn't work with the Exception
Handling Proposal for WebAssembly:
https://github.com/WebAssembly/exception-handling

This needs the application handler passed to `run_app` to be `'static`,
but that works better on iOS too anyhow (since you can't accidentally
forget to pass in state that then wouldn't be dropped when terminating).
2025-11-16 17:23:28 +09:00
24 changed files with 160 additions and 144 deletions

View File

@@ -8,22 +8,22 @@ edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/rust-windowing/winit"
rust-version = "1.85"
version = "0.31.0-beta.1"
version = "0.31.0-beta.2"
[workspace.dependencies]
# Workspace dependencies.
# `winit` has no version here to allow using it in dev deps for docs.
winit = { path = "winit" }
winit-android = { version = "=0.31.0-beta.1", path = "winit-android" }
winit-appkit = { version = "=0.31.0-beta.1", path = "winit-appkit" }
winit-common = { version = "=0.31.0-beta.1", path = "winit-common" }
winit-core = { version = "=0.31.0-beta.1", path = "winit-core" }
winit-orbital = { version = "=0.31.0-beta.1", path = "winit-orbital" }
winit-uikit = { version = "=0.31.0-beta.1", path = "winit-uikit" }
winit-wayland = { version = "=0.31.0-beta.1", path = "winit-wayland", default-features = false }
winit-web = { version = "=0.31.0-beta.1", path = "winit-web" }
winit-win32 = { version = "=0.31.0-beta.1", path = "winit-win32" }
winit-x11 = { version = "=0.31.0-beta.1", path = "winit-x11" }
winit-android = { version = "=0.31.0-beta.2", path = "winit-android" }
winit-appkit = { version = "=0.31.0-beta.2", path = "winit-appkit" }
winit-common = { version = "=0.31.0-beta.2", path = "winit-common" }
winit-core = { version = "=0.31.0-beta.2", path = "winit-core" }
winit-orbital = { version = "=0.31.0-beta.2", path = "winit-orbital" }
winit-uikit = { version = "=0.31.0-beta.2", path = "winit-uikit" }
winit-wayland = { version = "=0.31.0-beta.2", path = "winit-wayland", default-features = false }
winit-web = { version = "=0.31.0-beta.2", path = "winit-web" }
winit-win32 = { version = "=0.31.0-beta.2", path = "winit-win32" }
winit-x11 = { version = "=0.31.0-beta.2", path = "winit-x11" }
# Core dependencies.
bitflags = "2"

View File

@@ -8,7 +8,7 @@
```toml
[dependencies]
winit = "0.31.0-beta.1"
winit = "0.31.0-beta.2"
```
## [Documentation](https://docs.rs/winit)

View File

@@ -500,10 +500,6 @@ impl EventLoop {
input_status
}
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
mut app: A,

View File

@@ -63,7 +63,7 @@
//! with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version =
//! "0.31.0-beta.1", features = [ "android-native-activity" ] }`
//! "0.31.0-beta.2", features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
//! logging as above).

View File

@@ -232,10 +232,6 @@ impl EventLoop {
&self.window_target
}
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
// time and so a layered implementation would end up using a lot of CPU due to

View File

@@ -1,4 +1,6 @@
pub mod never_return;
pub mod pump_events;
pub mod register;
pub mod run_on_demand;
use std::fmt::{self, Debug};

View File

@@ -0,0 +1,14 @@
use crate::application::ApplicationHandler;
/// Additional methods on `EventLoop` for platforms whose run method never return.
pub trait EventLoopExtNeverReturn {
/// Run the event loop and call `process::exit` once finished.
///
/// ## Platform-specific
///
/// - **iOS**: This registers the callbacks with the system and calls `UIApplicationMain`.
/// - **macOS**: Unimplemented (TODO: Should call `NSApplicationMain`).
/// - **Android/Orbital/Wayland/Windows/X11**: Unsupported.
/// - **Web**: Impossible to support properly.
fn run_app_never_return<A: ApplicationHandler + 'static>(self, app: A) -> !;
}

View File

@@ -0,0 +1,17 @@
use crate::application::ApplicationHandler;
/// Additional methods on `EventLoop` that registers it with the system event loop.
pub trait EventLoopExtRegister {
/// Initialize and register the application with the system's event loop, such that the
/// callbacks will be run later.
///
/// ## Platform-specific
///
/// - **Web**: Once the event loop has been destroyed, it's possible to reinitialize another
/// event loop by calling this function again. This can be useful if you want to recreate the
/// event loop while the WebAssembly module is still loaded. For example, this can be used to
/// recreate the event loop when switching between tabs on a single page application.
/// - **iOS/macOS**: Unimplemented.
/// - **Android/Orbital/Wayland/Windows/X11**: Unsupported.
fn register_app<A: ApplicationHandler + 'static>(self, app: A);
}

View File

@@ -6,15 +6,13 @@ use crate::{
window::Window,
};
#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these.
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand {
/// Run the application with the event loop on the calling thread.
///
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
/// closures and it is possible to return control back to the caller without
/// consuming the `EventLoop` (by using [`exit()`]) and
/// so the event loop can be re-run after it has exit.
/// state and it is possible to return control back to the caller without consuming the
/// `EventLoop` (by using [`exit()`]) and so the event loop can be re-run after it has exit.
///
/// It's expected that each run of the loop will be for orthogonal instantiations of your
/// Winit application, but internally each instantiation may re-use some common window
@@ -31,8 +29,7 @@ pub trait EventLoopExtRunOnDemand {
///
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// backend it is possible to use `EventLoopExtWeb::spawn_app()`[^1] more than once instead).
/// to the caller (specifically this is impossible on iOS and Web).
/// - No [`Window`] state can be carried between separate runs of the event loop.
///
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
@@ -51,8 +48,6 @@ pub trait EventLoopExtRunOnDemand {
/// are delivered via callbacks based on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
///
/// [^1]: `spawn_app()` is only available on the Web platforms.
///
/// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;

View File

@@ -1136,7 +1136,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
let action = if allowed {
let position = LogicalPosition::new(0, 0);
let size = LogicalSize::new(0, 0);
let ime_caps = ImeCapabilities::new().without_hint_and_purpose().with_cursor_area();
let ime_caps = ImeCapabilities::new().with_hint_and_purpose().with_cursor_area();
let request_data = ImeRequestData {
hint_and_purpose: Some((ImeHint::NONE, ImePurpose::Normal)),
// WARNING: there's nothing sensible to use here by default.

View File

@@ -481,7 +481,10 @@ impl EventLoop {
}
}
pub fn run_app<A: ApplicationHandler>(mut self, mut app: A) -> Result<(), EventLoopError> {
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
mut app: A,
) -> Result<(), EventLoopError> {
let mut start_cause = StartCause::Init;
loop {
app.new_events(&self.window_target, start_cause);

View File

@@ -238,7 +238,8 @@ impl EventLoop {
})
}
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
// Require `'static` for correctness, we won't be able to `Drop` the user's state otherwise.
pub fn run_app_never_return<A: ApplicationHandler + 'static>(self, app: A) -> ! {
let application: Option<Retained<UIApplication>> =
unsafe { msg_send![UIApplication::class(), sharedApplication] };
assert!(
@@ -250,6 +251,10 @@ impl EventLoop {
// We intentionally override neither the application nor the delegate,
// to allow the user to do so themselves!
//
// NOTE: `UIApplicationMain` is _the only way_ to start the event loop on iOS/UIKit, there
// are no other feasible options. See also:
// <https://github.com/rust-windowing/winit/pull/4165#issuecomment-2760514167>
app_state::launch(self.mtm, app, || UIApplication::main(None, None, self.mtm))
}

View File

@@ -169,10 +169,6 @@ impl EventLoop {
Ok(event_loop)
}
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
mut app: A,

View File

@@ -39,32 +39,8 @@ impl EventLoop {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
}
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
let app = Box::new(app);
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
// because this function will never return and all resources not cleaned up by the point we
// `throw` will leak, making this actually `'static`.
let app = unsafe {
std::mem::transmute::<
Box<dyn ApplicationHandler + '_>,
Box<dyn ApplicationHandler + 'static>,
>(app)
};
self.elw.run(app, false);
// Throw an exception to break out of Rust execution and use unreachable to tell the
// compiler this function won't return, giving it a return type of '!'
backend::throw(
"Using exceptions for control flow, don't mind me. This isn't actually an error!",
);
unreachable!();
}
pub fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
self.elw.run(Box::new(app), true);
pub fn register_app<A: ApplicationHandler + 'static>(self, app: A) {
self.elw.run(Box::new(app));
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {

View File

@@ -48,7 +48,6 @@ struct Execution {
exit: Cell<bool>,
runner: RefCell<RunnerEnum>,
suspended: Cell<bool>,
event_loop_recreation: Cell<bool>,
events: RefCell<VecDeque<Event>>,
id: Cell<usize>,
window: web_sys::Window,
@@ -195,7 +194,6 @@ impl Shared {
exit: Cell::new(false),
runner: RefCell::new(RunnerEnum::Pending),
suspended: Cell::new(false),
event_loop_recreation: Cell::new(false),
events: RefCell::new(VecDeque::new()),
window,
navigator,
@@ -772,9 +770,7 @@ impl Shared {
// * For each undropped `Window`:
// * The `register_redraw_request` closure.
// * The `destroy_fn` closure.
if self.0.event_loop_recreation.get() {
EventLoop::allow_event_loop_recreation();
}
EventLoop::allow_event_loop_recreation();
}
// Check if the event loop is currently closed
@@ -809,10 +805,6 @@ impl Shared {
}
}
pub fn event_loop_recreation(&self, allow: bool) {
self.0.event_loop_recreation.set(allow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.0.control_flow.get()
}

View File

@@ -56,8 +56,7 @@ impl ActiveEventLoop {
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
}
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>, event_loop_recreation: bool) {
self.runner.event_loop_recreation(event_loop_recreation);
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>) {
self.runner.start(app, self.clone());
}

View File

@@ -85,7 +85,6 @@ use std::task::{Context, Poll};
use ::web_sys::HtmlCanvasElement;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::NotSupportedError;
use winit_core::event_loop::ActiveEventLoop;
@@ -237,30 +236,6 @@ impl Default for WindowAttributesWeb {
/// Additional methods on `EventLoop` that are specific to the Web.
pub trait EventLoopExtWeb {
/// Initializes the winit event loop.
///
/// Unlike
#[cfg_attr(target_feature = "exception-handling", doc = "`run_app()`")]
#[cfg_attr(
not(target_feature = "exception-handling"),
doc = "[`run_app()`]"
)]
/// [^1], this returns immediately, and doesn't throw an exception in order to
/// satisfy its [`!`] return type.
///
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
/// by calling this function again. This can be useful if you want to recreate the event loop
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
/// event loop when switching between tabs on a single page application.
#[rustfmt::skip]
///
#[cfg_attr(
not(target_feature = "exception-handling"),
doc = "[`run_app()`]: EventLoop::run_app()"
)]
/// [^1]: `run_app()` is _not_ available on Wasm when the target supports `exception-handling`.
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
/// Sets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].

View File

@@ -25,10 +25,6 @@ pub use self::resize_scaling::ResizeScaleHandle;
pub use self::safe_area::SafeAreaHandle;
pub use self::schedule::Schedule;
pub fn throw(msg: &str) {
wasm_bindgen::throw_str(msg);
}
pub struct PageTransitionEventHandle {
_show_listener: event_handle::EventListenerHandle<dyn FnMut(PageTransitionEvent)>,
_hide_listener: event_handle::EventListenerHandle<dyn FnMut(PageTransitionEvent)>,

View File

@@ -247,10 +247,6 @@ impl EventLoop {
ActiveEventLoop::from_ref(&self.runner)
}
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
mut app: A,

View File

@@ -427,10 +427,6 @@ impl EventLoop {
&self.event_processor.target
}
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
mut app: A,

View File

@@ -1,7 +1,7 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))]
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, orbital_platform))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::time::Duration;
@@ -93,7 +93,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))]
#[cfg(not(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
orbital_platform
)))]
fn main() {
println!("This example is not supported on this platform");
}

View File

@@ -1,3 +1,20 @@
## 0.31.0-beta.2
### Added
- Add `EventLoopExtRegister::register_app` for being explicit about how the event loop runs on Web.
- Add `EventLoopExtNeverReturn::run_app_never_return` for being explicit about how the event loop runs on iOS.
### Changed
- On Web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller.
This requires passing a `'static` application to ensure that the application state will live as long as necessary.
- On Web, the event loop can now always be re-created once it has finished running.
### Fixed
- Fixed panic when calling `Window::set_ime_allowed`.
## 0.31.0-beta.1
### Added

View File

@@ -118,7 +118,7 @@ impl EventLoop {
EventLoopBuilder { platform_specific: Default::default() }
}
/// Run the application with the event loop on the calling thread.
/// Run the event loop with the given application on the calling thread.
///
/// The `app` is dropped when the event loop is shut down.
///
@@ -173,35 +173,74 @@ impl EventLoop {
/// [`ControlFlow::WaitUntil`] and life-cycle methods like [`ApplicationHandler::resumed`], but
/// it should give you an idea of how things fit together.
///
/// ## Platform-specific
/// ## Returns
///
/// - **iOS:** Will never return to the caller and so values not passed to this function will
/// *not* be dropped before the process exits.
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript
/// exception (that Rust doesn't see) that will also mean that the rest of the function is
/// never executed and any values not passed to this function will *not* be dropped.
/// The semantics of this function can be a bit confusing, because the way different platforms
/// control their event loop varies significantly.
///
/// Web applications are recommended to use
#[cfg_attr(
web_platform,
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)]
#[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^1] instead of [`run_app()`] to avoid the need for the Javascript exception trick, and to
/// make it clearer that the event loop runs asynchronously (via the browser's own,
/// internal, event loop) and doesn't block the current thread of execution like it does
/// on other platforms.
/// On most platforms (Android, macOS, Orbital, X11, Wayland, Windows), this blocks the caller,
/// runs the event loop internally, and then returns once [`ActiveEventLoop::exit`] is called.
/// See [`run_app_on_demand`] for more detailed semantics.
///
/// This function won't be available with `target_feature = "exception-handling"`.
/// On iOS, this will register the application handler, and then call [`UIApplicationMain`]
/// (which is the only way to run the system event loop), which never returns to the caller
/// (the process instead exits after the handler has been dropped). See also
/// [`run_app_never_return`].
///
/// [^1]: `spawn_app()` is only available on the Web platform.
/// On the web, this works by registering the application handler, and then immediately
/// returning to the caller. This is necessary because WebAssembly (and JavaScript) is always
/// executed in the context of the browser's own (internal) event loop, and thus we need to
/// return to avoid blocking that and allow events to later be delivered asynchronously. See
/// also [`register_app`].
///
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
/// [`run_app()`]: Self::run_app()
/// If you call this function inside `fn main`, you usually do not need to think about these
/// details.
///
/// [`UIApplicationMain`]: https://developer.apple.com/documentation/uikit/uiapplicationmain(_:_:_:_:)-1yub7?language=objc
/// [`run_app_on_demand`]: crate::event_loop::run_on_demand::EventLoopExtRunOnDemand::run_app_on_demand
/// [`run_app_never_return`]: crate::event_loop::never_return::EventLoopExtNeverReturn::run_app_never_return
/// [`register_app`]: crate::event_loop::register::EventLoopExtRegister::register_app
///
/// ## Static
///
/// To alleviate the issues noted above, this function requires that you pass in a `'static`
/// handler, to ensure that any state your application uses will be alive as long as the
/// application is running.
///
/// To be clear, you should avoid doing e.g. `event_loop.run_app(&mut app)?`, and prefer
/// `event_loop.run_app(app)?` instead.
///
/// If this requirement is prohibitive for you, consider using [`run_app_on_demand`] instead
/// (though note that this is not available on iOS and web).
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
self.event_loop.run_app(app)
#[allow(unused_mut)]
pub fn run_app<A: ApplicationHandler + 'static>(
mut self,
mut app: A,
) -> Result<(), EventLoopError> {
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
orbital_platform,
x11_platform,
wayland_platform,
))]
{
let result = self.event_loop.run_app_on_demand(&mut app);
// SAFETY: unsure that the state is dropped before the exit from the event loop.
drop(app);
result
}
#[cfg(web_platform)]
{
self.event_loop.register_app(app);
Ok(())
}
#[cfg(ios_platform)]
{
self.event_loop.run_app_never_return(app)
}
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
@@ -306,6 +345,7 @@ impl winit_core::event_loop::pump_events::EventLoopExtPumpEvents for EventLoop {
windows_platform,
macos_platform,
android_platform,
orbital_platform,
x11_platform,
wayland_platform,
docsrs,
@@ -316,6 +356,13 @@ impl winit_core::event_loop::run_on_demand::EventLoopExtRunOnDemand for EventLoo
}
}
#[cfg(any(web_platform, docsrs))]
impl winit_core::event_loop::register::EventLoopExtRegister for EventLoop {
fn register_app<A: ApplicationHandler + 'static>(self, app: A) {
self.event_loop.register_app(app)
}
}
#[cfg(android_platform)]
impl winit_android::EventLoopExtAndroid for EventLoop {
fn android_app(&self) -> &winit_android::activity::AndroidApp {
@@ -385,10 +432,6 @@ impl winit_wayland::EventLoopBuilderExtWayland for EventLoopBuilder {
#[cfg(web_platform)]
impl winit_web::EventLoopExtWeb for EventLoop {
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
self.event_loop.spawn_app(app);
}
fn set_poll_strategy(&self, strategy: winit_web::PollStrategy) {
self.event_loop.set_poll_strategy(strategy);
}

View File

@@ -147,10 +147,6 @@ impl EventLoop {
}
}
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: A,