Compare commits

..

5 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
Kirill Chibisov
e8bccfff4f Winit version 0.31.0-beta.1 2025-11-16 13:32:44 +09:00
57 changed files with 851 additions and 1142 deletions

View File

@@ -1,8 +1,8 @@
name: iOS bug
description: Create an iOS/UIKit-specific bug report
description: Create an iOS-specific bug report
labels:
- B - bug
- DS - uikit
- DS - ios
body:
- type: markdown
attributes:

View File

@@ -1,8 +1,8 @@
name: macOS bug
name: MacOS bug
description: Create a macOS-specific bug report
labels:
- B - bug
- DS - appkit
- DS - macos
body:
- type: markdown
attributes:

View File

@@ -2,7 +2,7 @@ name: Windows bug
description: Create a Windows-specific bug report
labels:
- B - bug
- DS - win32
- DS - windows
body:
- type: markdown
attributes:

View File

@@ -124,7 +124,7 @@ jobs:
# the cache has been downloaded.
#
# This could be avoided if we added Cargo.lock to the repository.
uses: actions/cache/restore@v5
uses: actions/cache/restore@v4
with:
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
path: |
@@ -145,7 +145,7 @@ jobs:
- name: Cache cargo-apk
if: contains(matrix.platform.target, 'android')
id: cargo-apk-cache
uses: actions/cache@v5
uses: actions/cache@v4
with:
path: ~/.cargo/bin/cargo-apk
# Change this key if we update the required cargo-apk version
@@ -160,7 +160,7 @@ jobs:
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
run: cargo install cargo-apk --version=^0.9.7 --locked
- uses: taiki-e/cache-cargo-install-action@v3
- uses: taiki-e/cache-cargo-install-action@v2
if: contains(matrix.platform.target, 'wasm32') && matrix.toolchain == 'nightly'
with:
tool: wasm-bindgen-cli
@@ -281,7 +281,7 @@ jobs:
# See restore step above
- name: Save cache of cargo folder
uses: actions/cache/save@v5
uses: actions/cache/save@v4
with:
path: |
~/.cargo/registry/index/

View File

@@ -19,7 +19,7 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@master
with:

View File

@@ -39,7 +39,7 @@ tracing = { version = "0.1.40", default-features = false }
# Dev dependencies.
image = { version = "0.25.0", default-features = false }
softbuffer = { version = "0.4.8", default-features = false, features = [
softbuffer = { version = "0.4.6", default-features = false, features = [
"x11",
"x11-dlopen",
"wayland",
@@ -60,17 +60,16 @@ objc2-core-foundation = { version = "0.3.2", default-features = false }
objc2-core-graphics = { version = "0.3.2", default-features = false }
objc2-core-video = { version = "0.3.2", default-features = false }
objc2-foundation = { version = "0.3.2", default-features = false }
objc2-quartz-core = { version = "0.3.2", default-features = false }
objc2-ui-kit = { version = "0.3.2", default-features = false }
# Windows dependencies.
unicode-segmentation = "1.7.1"
windows-sys = "0.61"
windows-sys = "0.59.0"
# Linux dependencies.
ahash = { version = "0.8.7", features = ["no-rng"] }
bytemuck = { version = "1.13.1", default-features = false }
calloop = "0.14.3"
foldhash = { version = "0.2.0", default-features = false, features = ["std"] }
libc = "0.2.64"
memmap2 = "0.9.0"
percent-encoding = "2.0"
@@ -80,9 +79,8 @@ x11rb = { version = "0.13.0", default-features = false }
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" }
redox_syscall = "0.5.7"
# Web dependencies.
atomic-waker = "1"

View File

@@ -32,7 +32,6 @@ allow = [
"ISC", # https://tldrlegal.com/license/isc-license
"MIT", # https://tldrlegal.com/license/mit-license
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
"Zlib", # https://spdx.org/licenses/Zlib.html
]
confidence-threshold = 1.0
private = { ignore = true }

View File

@@ -84,18 +84,36 @@ pub trait Pixel: Copy + Into<f64> {
}
}
macro_rules! pixel_int_impl {
($($t:ty),*) => {$(
impl Pixel for $t {
fn from_f64(f: f64) -> Self {
round(f) as $t
}
}
)*}
impl Pixel for u8 {
fn from_f64(f: f64) -> Self {
round(f) as u8
}
}
impl Pixel for u16 {
fn from_f64(f: f64) -> Self {
round(f) as u16
}
}
impl Pixel for u32 {
fn from_f64(f: f64) -> Self {
round(f) as u32
}
}
impl Pixel for i8 {
fn from_f64(f: f64) -> Self {
round(f) as i8
}
}
impl Pixel for i16 {
fn from_f64(f: f64) -> Self {
round(f) as i16
}
}
impl Pixel for i32 {
fn from_f64(f: f64) -> Self {
round(f) as i32
}
}
pixel_int_impl!(u8, u16, u32, i8, i16, i32);
impl Pixel for f32 {
fn from_f64(f: f64) -> Self {
f as f32
@@ -360,48 +378,6 @@ impl<P: Pixel> From<LogicalUnit<P>> for PixelUnit {
}
}
macro_rules! vec2_from_impls {
($t:ident, $a:ident, $b:ident, $mint_ty:ident) => {
impl<P: Pixel, X: Pixel> From<(X, X)> for $t<P> {
fn from(($a, $b): (X, X)) -> Self {
Self::new($a.cast(), $b.cast())
}
}
impl<P: Pixel, X: Pixel> From<$t<P>> for (X, X) {
fn from(p: $t<P>) -> Self {
(p.$a.cast(), p.$b.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for $t<P> {
fn from([$a, $b]: [X; 2]) -> Self {
Self::new($a.cast(), $b.cast())
}
}
impl<P: Pixel, X: Pixel> From<$t<P>> for [X; 2] {
fn from(p: $t<P>) -> Self {
[p.$a.cast(), p.$b.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::$mint_ty<P>> for $t<P> {
fn from(p: mint::$mint_ty<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<$t<P>> for mint::$mint_ty<P> {
fn from(p: $t<P>) -> Self {
Self { x: p.$a, y: p.$b }
}
}
};
}
/// A position represented in logical pixels.
///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
@@ -444,7 +420,43 @@ impl<P: Pixel> LogicalPosition<P> {
}
}
vec2_from_impls!(LogicalPosition, x, y, Point2);
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
fn from((x, y): (X, X)) -> LogicalPosition<P> {
LogicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
fn from(p: LogicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
LogicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
fn from(p: LogicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
fn from(p: LogicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.y }
}
}
/// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
@@ -484,7 +496,43 @@ impl<P: Pixel> PhysicalPosition<P> {
}
}
vec2_from_impls!(PhysicalPosition, x, y, Point2);
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
PhysicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
fn from(p: PhysicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
PhysicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
fn from(p: PhysicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
fn from(p: PhysicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.y }
}
}
/// A size represented in logical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
@@ -524,7 +572,43 @@ impl<P: Pixel> LogicalSize<P> {
}
}
vec2_from_impls!(LogicalSize, width, height, Vector2);
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
fn from((x, y): (X, X)) -> LogicalSize<P> {
LogicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) {
fn from(s: LogicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
fn from([x, y]: [X; 2]) -> LogicalSize<P> {
LogicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] {
fn from(s: LogicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
fn from(s: LogicalSize<P>) -> Self {
mint::Vector2 { x: s.width, y: s.height }
}
}
/// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
@@ -561,7 +645,43 @@ impl<P: Pixel> PhysicalSize<P> {
}
}
vec2_from_impls!(PhysicalSize, width, height, Vector2);
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
fn from((x, y): (X, X)) -> PhysicalSize<P> {
PhysicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) {
fn from(s: PhysicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
fn from([x, y]: [X; 2]) -> PhysicalSize<P> {
PhysicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] {
fn from(s: PhysicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
fn from(s: PhysicalSize<P>) -> Self {
mint::Vector2 { x: s.width, y: s.height }
}
}
/// A size that's either physical or logical.
#[derive(Debug, Copy, Clone, PartialEq)]

View File

@@ -473,23 +473,16 @@ impl EventLoop {
&mut self.combining_accent,
);
let logical_key = keycodes::to_logical(key_char, keycode);
let text = if state == event::ElementState::Pressed {
logical_key.to_text().map(smol_str::SmolStr::new)
} else {
None
};
let event = event::WindowEvent::KeyboardInput {
device_id: Some(DeviceId::from_raw(key.device_id() as i64)),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
logical_key,
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: text.clone(),
text_with_all_modifiers: text,
text: None,
text_with_all_modifiers: None,
key_without_modifiers: keycodes::to_logical(key_char, keycode),
},
is_synthetic: false,

View File

@@ -51,7 +51,6 @@ objc2-app-kit = { workspace = true, features = [
"NSScreen",
"NSTextInputClient",
"NSTextInputContext",
"NSTrackingArea",
"NSToolbar",
"NSView",
"NSWindow",
@@ -107,7 +106,6 @@ objc2-foundation = { workspace = true, features = [
"NSThread",
"NSValue",
] }
objc2-quartz-core = { workspace = true, features = ["std", "CABase"] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler"] }
[dev-dependencies]

View File

@@ -98,7 +98,6 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
}
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
let time = app_state.event_time(event);
let event_type = event.r#type();
#[allow(non_upper_case_globals)]
match event_type {
@@ -111,7 +110,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
if delta_x != 0.0 || delta_y != 0.0 {
app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, time, DeviceEvent::PointerMotion {
app.device_event(event_loop, None, DeviceEvent::PointerMotion {
delta: (delta_x, delta_y),
});
});
@@ -120,7 +119,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
let button = event.buttonNumber() as u32;
app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, time, DeviceEvent::Button {
app.device_event(event_loop, None, DeviceEvent::Button {
button,
state: ElementState::Pressed,
});
@@ -129,7 +128,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
let button = event.buttonNumber() as u32;
app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, time, DeviceEvent::Button {
app.device_event(event_loop, None, DeviceEvent::Button {
button,
state: ElementState::Released,
});

View File

@@ -2,15 +2,13 @@ use std::cell::{Cell, OnceCell, RefCell};
use std::mem;
use std::rc::Rc;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::time::Instant;
use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSEvent, NSRunningApplication};
use objc2_foundation::{NSNotification, NSTimeInterval};
use objc2_quartz_core::CACurrentMediaTime;
use tracing::warn;
use winit_common::core_foundation::{EventLoopProxy, MainRunLoop};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::NSNotification;
use winit_common::core_foundation::EventLoopProxy;
use winit_common::event_handler::EventHandler;
use winit_core::application::ApplicationHandler;
use winit_core::event::{StartCause, WindowEvent};
@@ -19,7 +17,7 @@ use winit_core::window::WindowId;
use super::event_loop::{ActiveEventLoop, notify_windows_of_exit, stop_app_immediately};
use super::menu;
use super::observer::EventLoopWaker;
use super::observer::{EventLoopWaker, RunLoop};
#[derive(Debug)]
pub(super) struct AppState {
@@ -27,7 +25,7 @@ pub(super) struct AppState {
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
run_loop: MainRunLoop,
run_loop: RunLoop,
event_loop_proxy: Arc<EventLoopProxy>,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
@@ -45,8 +43,6 @@ pub(super) struct AppState {
start_time: Cell<Option<Instant>>,
wait_timeout: Cell<Option<Instant>>,
pending_redraw: RefCell<Vec<WindowId>>,
startup_instant: Instant,
startup_timestamp: NSTimeInterval,
// NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and
// as such should be careful to not add fields that, in turn, strongly reference those.
}
@@ -67,23 +63,12 @@ impl AppState {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}));
// Prime dylib caches etc.
let _ = CACurrentMediaTime();
// Find the current Rust timestamp.
let startup_instant = Instant::now();
// Find the timestamp
//
// `NSProcessInfo::processInfo().systemUptime()` needs the required reason manifest,
// `CACurrentMediaTime` (currently) doesn't, so we use that instead.
let startup_timestamp = CACurrentMediaTime();
let this = Rc::new(Self {
mtm,
activation_policy,
default_menu,
activate_ignoring_other_apps,
run_loop: MainRunLoop::get(mtm),
run_loop: RunLoop::main(mtm),
event_loop_proxy,
event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false),
@@ -98,8 +83,6 @@ impl AppState {
start_time: Cell::new(None),
wait_timeout: Cell::new(None),
pending_redraw: RefCell::new(vec![]),
startup_instant,
startup_timestamp,
});
GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this))
@@ -113,16 +96,6 @@ impl AppState {
.clone()
}
pub(crate) fn event_time(&self, event: &NSEvent) -> Instant {
if event.timestamp() == 0.0 {
warn!(?event, "got zero timestamp");
return Instant::now();
}
let duration_since_startup = event.timestamp() - self.startup_timestamp;
let duration_since_startup = Duration::from_secs_f64(duration_since_startup as f64);
self.startup_instant + duration_since_startup
}
// NOTE: This notification will, globally, only be emitted once,
// no matter how many `EventLoop`s the user creates.
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
@@ -274,12 +247,7 @@ impl AppState {
// -> Don't go back into the event handler when our callstack originates from there
if !self.event_handler.in_use() {
self.with_handler(|app, event_loop| {
app.window_event(
event_loop,
window_id,
Instant::now(),
WindowEvent::RedrawRequested,
);
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
});
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
@@ -297,7 +265,7 @@ impl AppState {
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
self.run_loop.wake_up();
self.run_loop.wakeup();
}
#[track_caller]
@@ -342,7 +310,6 @@ impl AppState {
// Called by RunLoopObserver after finishing waiting for new events
pub fn wakeup(self: &Rc<Self>) {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// (we have registered to observe all modes, including modal event loops).
if !self.event_handler.ready() || !self.is_running() {
return;
}
@@ -371,7 +338,8 @@ impl AppState {
// Called by RunLoopObserver before waiting for new events
pub fn cleared(self: &Rc<Self>) {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// (we have registered to observe all modes, including modal event loops).
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
// we're about to return to the `CFRunLoop` to poll for new events?
if !self.event_handler.ready() || !self.is_running() {
return;
}
@@ -379,12 +347,7 @@ impl AppState {
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw {
self.with_handler(|app, event_loop| {
app.window_event(
event_loop,
window_id,
Instant::now(),
WindowEvent::RedrawRequested,
);
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
});
}
self.with_handler(|app, event_loop| {

View File

@@ -9,10 +9,8 @@ use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSWindow,
};
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopCommonModes};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use rwh_06::HasDisplayHandle;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver};
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, RequestError};
@@ -30,6 +28,7 @@ use super::cursor::CustomCursor;
use super::event::dummy_event;
use super::monitor;
use super::notification_center::create_observer;
use super::observer::setup_control_flow_observers;
use crate::ActivationPolicy;
use crate::window::Window;
@@ -151,9 +150,6 @@ pub struct EventLoop {
// Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_before_waiting_observer: MainRunLoopObserver,
_after_waiting_observer: MainRunLoopObserver,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -221,31 +217,7 @@ impl EventLoop {
},
);
let main_loop = MainRunLoop::get(mtm);
let mode = unsafe { kCFRunLoopCommonModes }.unwrap();
let app_state_clone = Rc::clone(&app_state);
let _before_waiting_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::BeforeWaiting,
true,
// Queued with the lowest priority to ensure it is processed after other observers.
// Without that, we'd get a `LoopExiting` after `AboutToWait`.
CFIndex::MAX,
move |_| app_state_clone.cleared(),
);
main_loop.add_observer(&_before_waiting_observer, mode);
let app_state_clone = Rc::clone(&app_state);
let _after_waiting_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::AfterWaiting,
true,
// Queued with the highest priority to ensure it is processed before other observers.
CFIndex::MIN,
move |_| app_state_clone.wakeup(),
);
main_loop.add_observer(&_after_waiting_observer, mode);
setup_control_flow_observers(mtm);
Ok(EventLoop {
app,
@@ -253,8 +225,6 @@ impl EventLoop {
window_target: ActiveEventLoop { app_state, mtm },
_did_finish_launching_observer,
_will_terminate_observer,
_before_waiting_observer,
_after_waiting_observer,
})
}

View File

@@ -1,10 +1,171 @@
//! Utilities for working with `CFRunLoop`.
//!
//! See Apple's documentation on Run Loops for details:
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
use std::cell::Cell;
use std::ffi::c_void;
use std::ptr;
use std::time::Instant;
use objc2::MainThreadMarker;
use objc2_core_foundation::{
CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopTimer, kCFRunLoopCommonModes,
CFAbsoluteTimeGetCurrent, CFIndex, CFRetained, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver,
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopTimer, kCFRunLoopCommonModes,
kCFRunLoopDefaultMode,
};
use tracing::error;
use super::app_state::AppState;
// begin is queued with the highest priority to ensure it is processed before other observers
extern "C-unwind" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_info: *mut c_void,
) {
match activity {
CFRunLoopActivity::AfterWaiting => {
AppState::get(MainThreadMarker::new().unwrap()).wakeup();
},
_ => unreachable!(),
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopExiting would get sent after AboutToWait
extern "C-unwind" fn control_flow_end_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_info: *mut c_void,
) {
match activity {
CFRunLoopActivity::BeforeWaiting => {
AppState::get(MainThreadMarker::new().unwrap()).cleared();
},
CFRunLoopActivity::Exit => (), // unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
#[derive(Debug)]
pub struct RunLoop(CFRetained<CFRunLoop>);
impl RunLoop {
pub fn main(mtm: MainThreadMarker) -> Self {
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
let _ = mtm;
RunLoop(CFRunLoop::main().unwrap())
}
pub fn wakeup(&self) {
self.0.wake_up();
}
unsafe fn add_observer(
&self,
flags: CFRunLoopActivity,
// The lower the value, the sooner this will run
priority: CFIndex,
handler: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) {
let observer =
unsafe { CFRunLoopObserver::new(None, flags.0, true, priority, handler, context) }
.unwrap();
self.0.add_observer(Some(&observer), unsafe { kCFRunLoopCommonModes });
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
/// event sources are processed.
///
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
///
/// # Implementation
///
/// This queuing could be implemented in the following several ways with subtle differences in
/// timing. This list is sorted in rough order in which they are run:
///
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
///
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
///
/// a. `atStart = true`.
///
/// b. `atStart = false`.
///
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
/// respect the ordering that runloop events have.
///
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
/// want the event to be queued in a way that preserves the order the events originally arrived
/// in.
///
/// As an example, let's assume that we receive two events from the user, a mouse click which we
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
/// put the event at the very front of the queue, to be handled as soon as possible after
/// handling whatever event it's currently handling.
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
if let Some(closure) = closure.take() {
closure()
} else {
error!("tried to execute queued closure on main thread twice");
}
});
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
// - `NSConnectionReplyMode`: TODO.
//
// We only want to run event handlers in the default mode, as we support running a blocking
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
// resizing such panel window enters the event tracking run loop mode, so we can't directly
// trigger events inside that mode either.
//
// Any events that are queued while running a modal or when live-resizing will instead wait,
// and be delivered to the application afterwards.
//
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
unsafe { self.0.perform_block(Some(mode), Some(&block)) }
}
}
pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
let run_loop = RunLoop::main(mtm);
unsafe {
let mut context = CFRunLoopObserverContext {
info: ptr::null_mut(),
version: 0,
retain: None,
release: None,
copyDescription: None,
};
run_loop.add_observer(
CFRunLoopActivity::AfterWaiting,
CFIndex::MIN,
Some(control_flow_begin_handler),
&mut context as *mut _,
);
run_loop.add_observer(
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
CFIndex::MAX,
Some(control_flow_end_handler),
&mut context as *mut _,
);
}
}
#[derive(Debug)]
pub struct EventLoopWaker {

View File

@@ -1,23 +1,22 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::ptr;
use std::rc::Rc;
use std::time::Instant;
use dpi::{LogicalPosition, LogicalSize};
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel};
use objc2::{AnyThread, DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send};
use objc2::{DefinedClass, MainThreadMarker, define_class, msg_send};
use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea,
NSTrackingAreaOptions, NSView, NSWindow,
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSWindow,
};
use objc2_core_foundation::CGRect;
use objc2_foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use tracing::warn;
use winit_core::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
@@ -120,6 +119,7 @@ pub struct ViewState {
ime_size: Cell<NSSize>,
modifiers: Cell<Modifiers>,
phys_modifiers: RefCell<HashMap<Key, ModLocationMask>>,
tracking_rect: Cell<Option<NSTrackingRectTag>>,
ime_state: Cell<ImeState>,
input_source: RefCell<String>,
@@ -131,6 +131,7 @@ pub struct ViewState {
/// True if the current key event should be forwarded
/// to the application, even during IME
forward_key_to_app: Cell<bool>,
marked_text: RefCell<Retained<NSMutableAttributedString>>,
accepts_first_mouse: bool,
@@ -152,17 +153,41 @@ define_class!(
true
}
#[unsafe(method(viewDidMoveToWindow))]
fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
let rect = self.frame();
let tracking_rect = unsafe {
self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false)
};
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect));
}
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
#[unsafe(method(viewFrameDidChangeNotification:))]
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
trace_scope!("NSViewFrameDidChangeNotification");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
self.removeTrackingRect(tracking_rect);
}
let rect = self.frame();
let tracking_rect = unsafe {
self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false)
};
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect));
// Emit resize event here rather than from windowDidResize because:
// 1. When a new window is created as a tab, the frame size may change without a window
// resize occurring.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
// size (includes tab height).
let rect = self.frame();
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let size = logical_size.to_physical::<u32>(self.scale_factor());
self.queue_event(WindowEvent::SurfaceResized(size));
@@ -279,15 +304,9 @@ define_class!(
// sending a `None` cursor range.
None
} else {
// Clamp to string length to avoid NSRangeException from out-of-bounds
// indices sent by macOS IME (e.g. native Pinyin, see
// https://github.com/alacritty/alacritty/issues/8791).
let len = string.length();
let location = selected_range.location.min(len);
let end = selected_range.end().min(len);
// Convert the selected range from UTF-16 indices to UTF-8 indices.
let sub_string_a = string.substringToIndex(location);
let sub_string_b = string.substringToIndex(end);
let sub_string_a = string.substringToIndex(selected_range.location);
let sub_string_b = string.substringToIndex(selected_range.end());
let lowerbound_utf8 = sub_string_a.len();
let upperbound_utf8 = sub_string_b.len();
Some((lowerbound_utf8, upperbound_utf8))
@@ -682,9 +701,8 @@ define_class!(
self.update_modifiers(event, false);
let time = self.ivars().app_state.event_time(event);
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, time, DeviceEvent::MouseWheel { delta })
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta })
});
self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase });
}
@@ -784,6 +802,7 @@ impl WinitView {
ime_size: Default::default(),
modifiers: Default::default(),
phys_modifiers: Default::default(),
tracking_rect: Default::default(),
ime_state: Default::default(),
input_source: Default::default(),
ime_capabilities: Default::default(),
@@ -793,52 +812,9 @@ impl WinitView {
option_as_alt: Cell::new(option_as_alt),
});
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
*this.ivars().input_source.borrow_mut() = this.current_input_source();
// `MouseEnteredAndExited` enables receiving events through `mouseEntered:` and
// `mouseExited:`.
//
// `MouseMoved` enables receiving events through `mouseMoved:`
//
// We do not set `CursorUpdate` because it is part of the "flexible" alternative to
// `cursorRect` based cursor image updates, and we currently still use
// `cursorRect`s. We also can't really switch to this approach because "The
// cursorUpdate(with:) message is not sent when the NSTrackingCursorUpdate option is
// specified along with [`ActiveAlways`]."
//
// `ActiveAlways` indicates we want to receive events when the window is not
// focused ("key window" in Cocoa terms), which matches the behavior on other
// platforms.
//
// We do not set `AssumeInside` because we want to avoid emitting `Left` events without a
// correspondering `Entered` to our consumers, and not setting this flag tells AppKit to
// handle this for us by synthesizing entry and exit events in some cases.
//
// `InVisibleRect` instructs the tracking area's `owner` (our `NSView`) to ignore the value
// we provide in `rect` and keep the tracking area's bounds up to date with the
// current view bounds automatically.
//
// We do not set `EnabledDuringMouseDrag` to match the platform behavior on Windows
// and Wayland, since neither emit events while being dragged over with an empty
// cursor without focus.
//
// See also https://developer.apple.com/documentation/appkit/nstrackingareaoptions.
// Safety: the type of `owner` should be `NSView` and is.
// The type of `user_info` is irrelevant because it is None.
this.addTrackingArea(&*unsafe {
NSTrackingArea::initWithRect_options_owner_userInfo(
NSTrackingArea::alloc(),
NSRect::ZERO,
NSTrackingAreaOptions::MouseEnteredAndExited
| NSTrackingAreaOptions::MouseMoved
| NSTrackingAreaOptions::ActiveAlways
| NSTrackingAreaOptions::InVisibleRect,
Some(&this),
None,
)
});
this
}
@@ -847,16 +823,9 @@ impl WinitView {
}
fn queue_event(&self, event: WindowEvent) {
let app = NSApplication::sharedApplication(self.mtm());
let window_id = window_id(&self.window());
let time = if let Some(nsevent) = app.currentEvent() {
self.ivars().app_state.event_time(&nsevent)
} else {
warn!("queued event with wrong timestamp, no active NSEvent found");
Instant::now()
};
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, time, event);
app.window_event(event_loop, window_id, event);
});
}
@@ -897,33 +866,24 @@ impl WinitView {
false
}
}
pub(super) fn enable_ime(&self, capabilities: ImeCapabilities) {
// This seems reasonable but the prior behavior of `set_ime_allowed` doesn't do this
// (it was also broken but let's not break things worse)
// if self.ivars().ime_capabilities.get().is_none() {
// self.ivars().ime_state.set(ImeState::Ground);
// }
pub(super) fn set_ime_allowed(&self, capabilities: Option<ImeCapabilities>) {
if self.ivars().ime_capabilities.get().is_some() {
return;
}
self.ivars().ime_capabilities.set(capabilities);
if capabilities.is_some() {
return;
}
// Clear markedText
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
// why are we disabling things in an enable fn? who knows. it's what the previous one did
// though
if self.ivars().ime_state.get() != ImeState::Disabled {
self.ivars().ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled));
}
self.ivars().ime_capabilities.set(Some(capabilities));
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
}
pub(super) fn disable_ime(&self) {
// see above
self.ivars().ime_capabilities.set(None);
if self.ivars().ime_state.get() != ImeState::Disabled {
self.ivars().ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled));
}
// we probably don't need to do this, but again this mirrors the prior behavior of
// `set_ime_allowed`
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
}
pub(super) fn ime_capabilities(&self) -> Option<ImeCapabilities> {

View File

@@ -5,7 +5,6 @@ use std::ffi::c_void;
use std::ptr;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
@@ -43,7 +42,6 @@ use objc2_foundation::{
NSRect, NSSize, NSString, ns_string,
};
use tracing::{trace, warn};
use winit_common::core_foundation::MainRunLoop;
use winit_core::cursor::Cursor;
use winit_core::error::{NotSupportedError, RequestError};
use winit_core::event::{SurfaceSizeWriter, WindowEvent};
@@ -58,6 +56,7 @@ use super::app_state::AppState;
use super::cursor::{CustomCursor, cursor_from_icon};
use super::ffi;
use super::monitor::{self, MonitorHandle, flip_window_screen_coordinates, get_display_id};
use super::observer::RunLoop;
use super::util::cgerr;
use super::view::WinitView;
use super::window::{WinitPanel, WinitWindow, window_id};
@@ -178,7 +177,7 @@ define_class!(
let mtm = MainThreadMarker::from(self);
let this = self.retain();
MainRunLoop::get(mtm).queue_closure(move || {
RunLoop::main(mtm).queue_closure(move || {
this.handle_scale_factor_changed(scale_factor);
});
}
@@ -331,20 +330,6 @@ define_class!(
trace_scope!("windowDidChangeOcclusionState:");
let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible);
self.queue_event(WindowEvent::Occluded(!visible));
// We do this here rather than in `window:willUseFullScreenPresentationOptions`
// because doing it there leaves the menu bar interactable despite being hidden.
// Moving it here produces the most consistent results.
if self.is_borderless_game()
&& matches!(*self.ivars().fullscreen.borrow(), Some(Fullscreen::Borderless(_)))
{
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
app.setPresentationOptions(
NSApplicationPresentationOptions::HideDock
| NSApplicationPresentationOptions::HideMenuBar,
);
}
}
#[unsafe(method(windowDidChangeScreen:))]
@@ -904,16 +889,9 @@ impl WindowDelegate {
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
let app = NSApplication::sharedApplication(self.mtm());
let window_id = window_id(self.window());
let time = if let Some(nsevent) = app.currentEvent() {
self.ivars().app_state.event_time(&nsevent)
} else {
warn!("queued event with wrong timestamp, no active NSEvent found");
Instant::now()
};
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, time, event);
app.window_event(event_loop, window_id, event);
});
}
@@ -1556,7 +1534,7 @@ impl WindowDelegate {
}
match (old_fullscreen, fullscreen) {
(None, Some(_)) => {
(None, Some(fullscreen)) => {
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
// set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`.
@@ -1567,6 +1545,16 @@ impl WindowDelegate {
self.ivars().saved_style.set(Some(curr_mask));
}
// In borderless games, we want to disable the dock and menu bar
// by setting the presentation options. We do this here rather than in
// `window:willUseFullScreenPresentationOptions` because for some reason
// the menu bar remains interactable despite being hidden.
if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) {
let presentation_options = NSApplicationPresentationOptions::HideDock
| NSApplicationPresentationOptions::HideMenuBar;
app.setPresentationOptions(presentation_options);
}
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Borderless(_)), None) => {
@@ -1694,7 +1682,7 @@ impl WindowDelegate {
if current_caps.is_some() {
return Err(ImeRequestError::AlreadyEnabled);
}
self.view().enable_ime(capabilities);
self.view().set_ime_allowed(Some(capabilities));
request_data
},
ImeRequest::Update(request_data) => {
@@ -1704,7 +1692,7 @@ impl WindowDelegate {
request_data
},
ImeRequest::Disable => {
self.view().disable_ime();
self.view().set_ime_allowed(None);
return Ok(());
},
};

View File

@@ -18,7 +18,7 @@ x11 = ["xkbcommon-dl?/x11", "dep:x11-dl"]
xkb = ["dep:xkbcommon-dl", "dep:smol_str"]
# CoreFoundation
core-foundation = ["dep:block2", "dep:objc2", "dep:objc2-core-foundation"]
core-foundation = ["dep:objc2", "dep:objc2-core-foundation"]
[dependencies]
smol_str = { workspace = true, optional = true }
@@ -31,12 +31,9 @@ x11-dl = { workspace = true, optional = true }
xkbcommon-dl = { workspace = true, optional = true }
# CoreFoundation
block2 = { workspace = true, optional = true }
objc2 = { workspace = true, optional = true }
objc2-core-foundation = { workspace = true, optional = true, features = [
"std",
"block2",
"objc2",
"CFRunLoop",
"CFString",
] }

View File

@@ -1,127 +0,0 @@
//! Various utilities for interfacing with the main run loop.
//!
//! These allow for using closures without the `Send + Sync` requirement that is otherwise required
//! when interfacing with run loops.
//!
//! See also <https://github.com/madsmtm/objc2/issues/696> for work on this at a lower level.
use std::cell::Cell;
use objc2::MainThreadMarker;
use objc2_core_foundation::{CFRetained, CFRunLoop, CFRunLoopMode, kCFRunLoopDefaultMode};
use tracing::error;
use super::MainRunLoopObserver;
/// Wrapper around [`CFRunLoop::main`].
#[derive(Debug)]
pub struct MainRunLoop {
/// This is on the main thread.
_mtm: MainThreadMarker,
/// A cached reference to the main run loop.
main_run_loop: CFRetained<CFRunLoop>,
}
impl MainRunLoop {
/// Get the main run loop.
pub fn get(_mtm: MainThreadMarker) -> Self {
let main_run_loop = CFRunLoop::main().unwrap();
Self { _mtm, main_run_loop }
}
/// Get a reference to the underlying [`CFRunLoop`].
pub fn run_loop(&self) -> &CFRunLoop {
&self.main_run_loop
}
/// Wake the main run loop.
pub fn wake_up(&self) {
self.main_run_loop.wake_up();
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
/// event sources are processed.
///
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
///
/// # Implementation
///
/// This queuing could be implemented in the following several ways with subtle differences in
/// timing. This list is sorted in rough order in which they are run:
///
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
///
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
///
/// a. `atStart = true`.
///
/// b. `atStart = false`.
///
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
/// respect the ordering that runloop events have.
///
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
/// want the event to be queued in a way that preserves the order the events originally arrived
/// in.
///
/// As an example, let's assume that we receive two events from the user, a mouse click which we
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
/// put the event at the very front of the queue, to be handled as soon as possible after
/// handling whatever event it's currently handling.
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
debug_assert!(MainThreadMarker::new().is_some());
if let Some(closure) = closure.take() {
closure()
} else {
error!("tried to execute queued closure on main thread twice");
}
});
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
// - `NSConnectionReplyMode`: TODO.
//
// We only want to run event handlers in the default mode, as we support running a blocking
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
// resizing such panel window enters the event tracking run loop mode, so we can't directly
// trigger events inside that mode either.
//
// Any events that are queued while running a modal or when live-resizing will instead wait,
// and be delivered to the application afterwards.
//
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
let _ = self._mtm;
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
//
// Additionally, we have a `MainThreadMarker` here, which means we know we're on the main
// thread. We also know that the run loop is the main-thread run loop, so scheduling a
// non-`Send` block to that is allowed.
unsafe { self.main_run_loop.perform_block(Some(mode), Some(&block)) }
}
/// Add an observer to the main run loop.
pub fn add_observer(&self, observer: &MainRunLoopObserver, mode: &CFRunLoopMode) {
// Accessing the `MainObserver`'s observer is fine here, since we're adding it to the main
// run loop (which is on the same thread that the observer was created on).
self.main_run_loop.add_observer(Some(&observer.observer), Some(mode));
}
/// Remove an observer from the main run loop.
///
/// This is also done automatically when the [`MainRunLoopObserver`] is dropped.
pub fn remove_observer(&self, observer: &MainRunLoopObserver, mode: &CFRunLoopMode) {
// Same as in `add_observer`, accessing the main loop's observer is fine.
self.main_run_loop.add_observer(Some(&observer.observer), Some(mode));
}
}

View File

@@ -1,55 +0,0 @@
use block2::RcBlock;
use objc2::MainThreadMarker;
use objc2_core_foundation::{
CFIndex, CFRetained, CFRunLoopActivity, CFRunLoopObserver, kCFAllocatorDefault,
};
/// A [`CFRunLoopObserver`] on the main thread.
///
/// This type has "ownership" semantics, and invalidates the observer when dropped.
#[derive(Debug)]
pub struct MainRunLoopObserver {
/// This must be private, otherwise the user might add it to an arbitrary run loop (but this
/// observer is not designed to only be on the main thread).
pub(crate) observer: CFRetained<CFRunLoopObserver>,
}
impl MainRunLoopObserver {
/// Create a new run loop observer that observes the main run loop.
pub fn new(
mtm: MainThreadMarker,
activities: CFRunLoopActivity,
repeats: bool,
// The lower the value, the sooner this will run (inverse of a "priority").
order: CFIndex,
callback: impl Fn(CFRunLoopActivity) + 'static,
) -> Self {
let block = RcBlock::new(move |_: *mut _, activity| {
debug_assert!(MainThreadMarker::new().is_some());
callback(activity)
});
let _ = mtm;
// SAFETY: The callback is not Send + Sync, which would normally be unsound, but since we
// restrict the callback to only ever be on the main thread (by taking `MainThreadMarker`,
// and in `MainRunLoop::add_observer`), the callback doesn't have to be thread safe.
let observer = unsafe {
CFRunLoopObserver::with_handler(
kCFAllocatorDefault,
activities.0,
repeats,
order,
Some(&block),
)
}
.unwrap();
Self { observer }
}
}
impl Drop for MainRunLoopObserver {
fn drop(&mut self) {
self.observer.invalidate();
}
}

View File

@@ -1,12 +1,3 @@
//! Various wrappers around [`CFRunLoop`][objc2_core_foundation::CFRunLoop].
//!
//! See Apple's documentation on Run Loops for details:
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
mod event_loop_proxy;
mod main_run_loop;
mod main_run_loop_observer;
pub use self::event_loop_proxy::*;
pub use self::main_run_loop::*;
pub use self::main_run_loop_observer::*;
pub use event_loop_proxy::*;

View File

@@ -1,9 +1,4 @@
//! End user application handling.
#[cfg(not(target_family = "wasm"))]
use std::time::Instant;
#[cfg(target_family = "wasm")]
use web_time::Instant;
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::event_loop::ActiveEventLoop;
@@ -197,14 +192,10 @@ pub trait ApplicationHandler {
}
/// Emitted when the OS sends an event to a winit window.
///
/// Contains the ID of the window, the event, and the time the event was received. Note that
/// since events are queued, the time will differ from [`Instant::now()`].
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
timestamp: Instant,
event: WindowEvent,
);
@@ -215,10 +206,9 @@ pub trait ApplicationHandler {
&mut self,
event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>,
timestamp: Instant,
event: DeviceEvent,
) {
let _ = (event_loop, device_id, timestamp, event);
let _ = (event_loop, device_id, event);
}
/// Emitted when the event loop is about to block and wait for new events.
@@ -382,10 +372,9 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
timestamp: Instant,
event: WindowEvent,
) {
(**self).window_event(event_loop, window_id, timestamp, event);
(**self).window_event(event_loop, window_id, event);
}
#[inline]
@@ -393,10 +382,9 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
&mut self,
event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>,
timestamp: Instant,
event: DeviceEvent,
) {
(**self).device_event(event_loop, device_id, timestamp, event);
(**self).device_event(event_loop, device_id, event);
}
#[inline]
@@ -452,10 +440,9 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
timestamp: Instant,
event: WindowEvent,
) {
(**self).window_event(event_loop, window_id, timestamp, event);
(**self).window_event(event_loop, window_id, event);
}
#[inline]
@@ -463,10 +450,9 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
&mut self,
event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>,
timestamp: Instant,
event: DeviceEvent,
) {
(**self).device_event(event_loop, device_id, timestamp, event);
(**self).device_event(event_loop, device_id, event);
}
#[inline]

View File

@@ -156,8 +156,6 @@ pub enum WindowEvent {
Ime(Ime),
/// The pointer has moved on the window.
///
/// Should be emitted regardless of window focus.
PointerMoved {
device_id: Option<DeviceId>,
@@ -186,8 +184,6 @@ pub enum WindowEvent {
},
/// The pointer has entered the window.
///
/// Should be emitted regardless of window focus.
PointerEntered {
device_id: Option<DeviceId>,
@@ -213,8 +209,6 @@ pub enum WindowEvent {
},
/// The pointer has left the window.
///
/// Should be emitted regardless of window focus.
PointerLeft {
device_id: Option<DeviceId>,
@@ -522,19 +516,6 @@ impl From<PointerSource> for PointerKind {
/// system.
#[derive(Clone, Debug, PartialEq)]
pub enum ButtonSource {
/// ## Platform-specific
///
/// ### macOS
///
/// Users may expect holding [<kbd>CTRL</kbd>](ModifiersState::CONTROL) while
/// clicking [`MouseButton::Left`] to result in a "secondary" click, but the way these
/// clicks behave natively is slightly different from how a physical secondary
/// button press would, depending on the content under the cursor when clicked. If
/// applications want this behavior they should implement it themselves by interpreting
/// [`Left`](MouseButton::Left) clicks as secondary when
/// [<kbd>CTRL</kbd>](ModifiersState::CONTROL) is held and their internal logic deems it
/// appropriate for the content under the pointer.
/// See also https://github.com/rust-windowing/winit/issues/4469.
Mouse(MouseButton),
/// See [`PointerSource::Touch`] for more details.
///
@@ -810,7 +791,7 @@ pub struct KeyEvent {
///
/// ## Platform-specific
///
/// - **Android:** This field is always the same value as `text`.
/// - **Android:** Unimplemented, this field is always the same value as `text`.
/// - **iOS:** Unimplemented, this field is always the same value as `text`.
/// - **Web:** Unsupported, this field is always the same value as `text`.
pub text_with_all_modifiers: Option<SmolStr>,

View File

@@ -176,11 +176,11 @@ pub trait EventLoopProxyProvider: Send + Sync + Debug {
/// - A reference-counted pointer to the underlying type.
#[derive(Clone)]
pub struct OwnedDisplayHandle {
pub(crate) handle: Arc<dyn HasDisplayHandle + Send + Sync>,
pub(crate) handle: Arc<dyn HasDisplayHandle>,
}
impl OwnedDisplayHandle {
pub fn new(handle: Arc<dyn HasDisplayHandle + Send + Sync>) -> Self {
pub fn new(handle: Arc<dyn HasDisplayHandle>) -> Self {
Self { handle }
}
}

View File

@@ -29,13 +29,11 @@ pub enum NativeKeyCode {
Windows(u16),
/// An XKB "keycode".
Xkb(u32),
/// An OpenHarmony "scancode".
Ohos(u32),
}
impl std::fmt::Debug for NativeKeyCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use NativeKeyCode::{Android, MacOS, Ohos, Unidentified, Windows, Xkb};
use NativeKeyCode::{Android, MacOS, Unidentified, Windows, Xkb};
let mut debug_tuple;
match self {
Unidentified => {
@@ -57,10 +55,6 @@ impl std::fmt::Debug for NativeKeyCode {
debug_tuple = f.debug_tuple("Xkb");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Ohos(code) => {
debug_tuple = f.debug_tuple("OpenHarmony");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
}
debug_tuple.finish()
}
@@ -91,13 +85,11 @@ pub enum NativeKey {
Xkb(u32),
/// A "key value string".
Web(SmolStr),
/// An OpenHarmony "keycode", which is similar to a "virtual-key code" on Windows.
Ohos(u32),
}
impl std::fmt::Debug for NativeKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use NativeKey::{Android, MacOS, Ohos, Unidentified, Web, Windows, Xkb};
use NativeKey::{Android, MacOS, Unidentified, Web, Windows, Xkb};
let mut debug_tuple;
match self {
Unidentified => {
@@ -123,10 +115,6 @@ impl std::fmt::Debug for NativeKey {
debug_tuple = f.debug_tuple("Web");
debug_tuple.field(code);
},
Ohos(code) => {
debug_tuple = f.debug_tuple("OpenHarmony");
debug_tuple.field(code);
},
}
debug_tuple.finish()
}
@@ -141,7 +129,6 @@ impl From<NativeKeyCode> for NativeKey {
NativeKeyCode::MacOS(x) => NativeKey::MacOS(x),
NativeKeyCode::Windows(x) => NativeKey::Windows(x),
NativeKeyCode::Xkb(x) => NativeKey::Xkb(x),
NativeKeyCode::Ohos(x) => NativeKey::Ohos(x),
}
}
}

View File

@@ -834,7 +834,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Orbital:** Always returns [`None`].
/// - **iOS / Android / Web / Wayland / Orbital:** Always returns [`None`].
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>>;
/// Sets resize increments of the surface.
@@ -846,6 +846,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
///
/// - **macOS:** Increments are converted to logical size and then macOS rounds them to whole
/// numbers.
/// - **Wayland:** Not implemented.
/// - **iOS / Android / Web / Orbital:** Unsupported.
fn set_surface_resize_increments(&self, increments: Option<Size>);
@@ -1690,22 +1691,6 @@ pub enum ImeSurroundingTextError {
AnchorBadPosition,
}
impl fmt::Display for ImeSurroundingTextError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ImeSurroundingTextError::TextTooLong => write!(f, "text exceeds maximum length"),
ImeSurroundingTextError::CursorBadPosition => {
write!(f, "cursor is not at a valid text index")
},
ImeSurroundingTextError::AnchorBadPosition => {
write!(f, "anchor is not at a valid text index")
},
}
}
}
impl std::error::Error for ImeSurroundingTextError {}
/// Defines the text surrounding the caret
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

View File

@@ -21,6 +21,5 @@ tracing.workspace = true
winit-core.workspace = true
# Platform-specific
libredox.workspace = true
orbclient.workspace = true
redox_event.workspace = true
redox_syscall.workspace = true

View File

@@ -10,7 +10,6 @@ use orbclient::{
ButtonEvent, EventOption, FocusEvent, HoverEvent, KeyEvent, MouseEvent, MouseRelativeEvent,
MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent,
};
use redox_event::{EventFlags, EventQueue};
use smol_str::SmolStr;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
@@ -101,7 +100,6 @@ fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
orbclient::K_LEFT => (KeyCode::ArrowLeft, Some(NamedKey::ArrowLeft)),
orbclient::K_LEFT_SHIFT => (KeyCode::ShiftLeft, Some(NamedKey::Shift)),
orbclient::K_MINUS => (KeyCode::Minus, None),
orbclient::K_NUM_0 => (KeyCode::Numpad0, None),
orbclient::K_NUM_1 => (KeyCode::Numpad1, None),
orbclient::K_NUM_2 => (KeyCode::Numpad2, None),
@@ -112,20 +110,12 @@ fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
orbclient::K_NUM_7 => (KeyCode::Numpad7, None),
orbclient::K_NUM_8 => (KeyCode::Numpad8, None),
orbclient::K_NUM_9 => (KeyCode::Numpad9, None),
orbclient::K_NUM_ASTERISK => (KeyCode::NumpadMultiply, None),
orbclient::K_NUM_ENTER => (KeyCode::NumpadEnter, Some(NamedKey::Enter)),
orbclient::K_NUM_MINUS => (KeyCode::NumpadSubtract, None),
orbclient::K_NUM_PLUS => (KeyCode::NumpadAdd, None),
orbclient::K_NUM_SLASH => (KeyCode::NumpadDivide, None),
orbclient::K_NUM_PERIOD => (KeyCode::NumpadDecimal, None),
orbclient::K_PERIOD => (KeyCode::Period, None),
orbclient::K_PGDN => (KeyCode::PageDown, Some(NamedKey::PageDown)),
orbclient::K_PGUP => (KeyCode::PageUp, Some(NamedKey::PageUp)),
orbclient::K_QUOTE => (KeyCode::Quote, None),
orbclient::K_RIGHT => (KeyCode::ArrowRight, Some(NamedKey::ArrowRight)),
orbclient::K_RIGHT_SHIFT => (KeyCode::ShiftRight, Some(NamedKey::Shift)),
orbclient::K_RIGHT_SUPER => (KeyCode::MetaRight, Some(NamedKey::Meta)),
orbclient::K_SEMICOLON => (KeyCode::Semicolon, None),
orbclient::K_SLASH => (KeyCode::Slash, None),
orbclient::K_SPACE => (KeyCode::Space, None),
@@ -137,20 +127,6 @@ fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
orbclient::K_VOLUME_TOGGLE => (KeyCode::AudioVolumeMute, Some(NamedKey::AudioVolumeMute)),
orbclient::K_VOLUME_UP => (KeyCode::AudioVolumeUp, Some(NamedKey::AudioVolumeUp)),
orbclient::K_INS => (KeyCode::Insert, Some(NamedKey::Insert)),
orbclient::K_PRTSC => (KeyCode::PrintScreen, Some(NamedKey::PrintScreen)),
orbclient::K_NUM => (KeyCode::NumLock, Some(NamedKey::NumLock)),
orbclient::K_SCROLL => (KeyCode::ScrollLock, Some(NamedKey::ScrollLock)),
orbclient::K_APP => (KeyCode::ContextMenu, Some(NamedKey::ContextMenu)),
orbclient::K_MEDIA_FAST_FORWARD => {
(KeyCode::MediaFastForward, Some(NamedKey::MediaFastForward))
},
orbclient::K_MEDIA_REWIND => (KeyCode::MediaRewind, Some(NamedKey::MediaRewind)),
orbclient::K_MEDIA_STOP => (KeyCode::MediaStop, Some(NamedKey::MediaStop)),
orbclient::K_POWER => (KeyCode::Power, Some(NamedKey::Power)),
_ => return (PhysicalKey::Unidentified(NativeKeyCode::Unidentified), None),
};
(PhysicalKey::Code(key_code), named_key_opt)
@@ -312,12 +288,16 @@ impl EventLoop {
let (user_events_sender, user_events_receiver) = mpsc::sync_channel(1);
let event_socket =
Arc::new(EventQueue::new().map_err(|error| os_error!(format!("{error}")))?);
Arc::new(RedoxSocket::event().map_err(|error| os_error!(format!("{error}")))?);
let wake_socket = TimeSocket::open().map_err(|error| os_error!(format!("{error}")))?;
event_socket
.subscribe(wake_socket.0.fd(), EventSource::Time, EventFlags::READ)
.write(&syscall::Event {
id: wake_socket.0.fd,
flags: syscall::EventFlags::EVENT_READ,
data: wake_socket.0.fd,
})
.map_err(|error| os_error!(format!("{error}")))?;
Ok(Self {
@@ -518,7 +498,7 @@ impl EventLoop {
let mut creates = self.window_target.creates.lock().unwrap();
creates.pop_front()
} {
let window_id = WindowId::from_raw(window.fd());
let window_id = WindowId::from_raw(window.fd);
let mut buf: [u8; 4096] = [0; 4096];
let path = window.fpath(&mut buf).expect("failed to read properties");
@@ -542,18 +522,18 @@ impl EventLoop {
} {
app.window_event(&self.window_target, destroy_id, event::WindowEvent::Destroyed);
self.windows
.retain(|(window, _event_state)| WindowId::from_raw(window.fd()) != destroy_id);
.retain(|(window, _event_state)| WindowId::from_raw(window.fd) != destroy_id);
}
// Handle window events.
let mut i = 0;
// While loop is used here because the same window may be processed more than once.
while let Some((window, event_state)) = self.windows.get_mut(i) {
let window_id = WindowId::from_raw(window.fd());
let window_id = WindowId::from_raw(window.fd);
let mut event_buf = [0u8; 16 * mem::size_of::<orbclient::Event>()];
let count = libredox::call::read(window.fd(), &mut event_buf)
.expect("failed to read window events");
let count =
syscall::read(window.fd, &mut event_buf).expect("failed to read window events");
// Safety: orbclient::Event is a packed struct designed to be transferred over a
// socket.
let events = unsafe {
@@ -633,7 +613,11 @@ impl EventLoop {
self.window_target
.event_socket
.subscribe(timeout_socket.0.fd(), EventSource::Time, EventFlags::READ)
.write(&syscall::Event {
id: timeout_socket.0.fd,
flags: syscall::EventFlags::EVENT_READ,
data: 0,
})
.unwrap();
let start = Instant::now();
@@ -642,7 +626,7 @@ impl EventLoop {
if let Some(duration) = instant.checked_duration_since(start) {
time.tv_sec += duration.as_secs() as i64;
time.tv_nsec += duration.subsec_nanos() as i64;
time.tv_nsec += duration.subsec_nanos() as i32;
// Normalize timespec so tv_nsec is not greater than one second.
while time.tv_nsec >= 1_000_000_000 {
time.tv_sec += 1;
@@ -654,22 +638,12 @@ impl EventLoop {
}
// 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());
},
}
};
let mut event = syscall::Event::default();
self.window_target.event_socket.read(&mut event).unwrap();
// 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) =>
{
Some(requested_resume) if event.id == timeout_socket.0.fd => {
// If the event is from the special timeout socket, report that resume
// time was reached.
start_cause = StartCause::ResumeTimeReached { start, requested_resume };
@@ -707,13 +681,6 @@ impl EventLoopProxyProvider for EventLoopProxy {
impl Unpin for EventLoopProxy {}
redox_event::user_data! {
pub enum EventSource {
Orbital,
Time,
}
}
#[derive(Debug)]
pub struct ActiveEventLoop {
control_flow: Cell<ControlFlow>,
@@ -721,7 +688,7 @@ pub struct ActiveEventLoop {
pub(super) creates: Mutex<VecDeque<Arc<RedoxSocket>>>,
pub(super) redraws: Arc<Mutex<VecDeque<WindowId>>>,
pub(super) destroys: Arc<Mutex<VecDeque<WindowId>>>,
pub(super) event_socket: Arc<EventQueue<EventSource>>,
pub(super) event_socket: Arc<RedoxSocket>,
pub(super) event_loop_proxy: Arc<EventLoopProxy>,
}

View File

@@ -3,12 +3,7 @@
//! Redox OS has some functionality not yet present that will be implemented
//! when its orbital display server provides it.
use std::fs::{File, OpenOptions};
use std::io::{Read, Result, Write};
use std::os::fd::AsRawFd;
use std::{fmt, mem, slice, str};
use libredox::data::TimeSpec;
use std::{fmt, str};
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
@@ -21,11 +16,15 @@ pub mod window;
#[derive(Debug)]
struct RedoxSocket {
fd: File,
fd: usize,
}
impl RedoxSocket {
fn orbital(properties: &WindowProperties<'_>) -> Result<Self> {
fn event() -> syscall::Result<Self> {
Self::open_raw("/scheme/event")
}
fn orbital(properties: &WindowProperties<'_>) -> syscall::Result<Self> {
Self::open_raw(&format!("{properties}"))
}
@@ -33,27 +32,30 @@ impl RedoxSocket {
// non-socket path is used, it could cause read and write to not function as expected. For
// example, the seek would change in a potentially unpredictable way if either read or write
// were called at the same time by multiple threads.
fn open_raw(path: &str) -> Result<Self> {
let fd = OpenOptions::new().read(true).write(true).open(path)?;
fn open_raw(path: &str) -> syscall::Result<Self> {
let fd = syscall::open(path, syscall::O_RDWR | syscall::O_CLOEXEC)?;
Ok(Self { fd })
}
fn fd(&self) -> usize {
self.fd.as_raw_fd() as usize
fn read(&self, buf: &mut [u8]) -> syscall::Result<()> {
let count = syscall::read(self.fd, buf)?;
if count == buf.len() { Ok(()) } else { Err(syscall::Error::new(syscall::EINVAL)) }
}
fn read(&self, buf: &mut [u8]) -> Result<()> {
(&self.fd).read_exact(buf)
fn write(&self, buf: &[u8]) -> syscall::Result<()> {
let count = syscall::write(self.fd, buf)?;
if count == buf.len() { Ok(()) } else { Err(syscall::Error::new(syscall::EINVAL)) }
}
fn write(&self, buf: &[u8]) -> Result<()> {
(&self.fd).write_all(buf)
fn fpath<'a>(&self, buf: &'a mut [u8]) -> syscall::Result<&'a str> {
let count = syscall::fpath(self.fd, buf)?;
str::from_utf8(&buf[..count]).map_err(|_err| syscall::Error::new(syscall::EINVAL))
}
}
fn fpath<'a>(&self, buf: &'a mut [u8]) -> Result<&'a str> {
let count = libredox::call::fpath(self.fd(), buf)?;
str::from_utf8(&buf[..count])
.map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidData))
impl Drop for RedoxSocket {
fn drop(&mut self) {
let _ = syscall::close(self.fd);
}
}
@@ -61,36 +63,26 @@ impl RedoxSocket {
struct TimeSocket(RedoxSocket);
impl TimeSocket {
fn open() -> Result<Self> {
fn open() -> syscall::Result<Self> {
RedoxSocket::open_raw("/scheme/time/4").map(Self)
}
// Read current time.
fn current_time(&self) -> Result<TimeSpec> {
let mut timespec: libredox::data::TimeSpec = unsafe { mem::zeroed() };
let timespec_bytes = unsafe {
slice::from_raw_parts_mut(
&mut timespec as *mut _ as *mut u8,
mem::size_of::<TimeSpec>(),
)
};
self.0.read(timespec_bytes)?;
fn current_time(&self) -> syscall::Result<syscall::TimeSpec> {
let mut timespec = syscall::TimeSpec::default();
self.0.read(&mut timespec)?;
Ok(timespec)
}
// Write a timeout.
fn timeout(&self, timespec: &TimeSpec) -> Result<()> {
let timespec_bytes = unsafe {
slice::from_raw_parts(timespec as *const _ as *const u8, mem::size_of::<TimeSpec>())
};
self.0.write(timespec_bytes)
fn timeout(&self, timespec: &syscall::TimeSpec) -> syscall::Result<()> {
self.0.write(timespec)
}
// Wake immediately.
fn wake(&self) -> Result<()> {
fn wake(&self) -> syscall::Result<()> {
// Writing a default TimeSpec will always trigger a time event.
let timespec: TimeSpec = unsafe { mem::zeroed() };
self.timeout(&timespec)
self.timeout(&syscall::TimeSpec::default())
}
}
@@ -105,7 +97,7 @@ struct WindowProperties<'a> {
impl<'a> WindowProperties<'a> {
fn new(path: &'a str) -> Self {
// /scheme/orbital/flags/x/y/w/h/t
// orbital:flags/x/y/w/h/t
let mut parts = path.splitn(6, '/');
let flags = parts.next().unwrap_or("");
let x = parts.next().map_or(0, |part| part.parse::<i32>().unwrap_or(0));

View File

@@ -3,13 +3,12 @@ use std::iter;
use std::sync::{Arc, Mutex};
use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use redox_event::EventFlags;
use winit_core::cursor::Cursor;
use winit_core::error::{NotSupportedError, RequestError};
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use winit_core::window::{self, Window as CoreWindow, WindowId};
use crate::event_loop::{ActiveEventLoop, EventLoopProxy, EventSource};
use crate::event_loop::{ActiveEventLoop, EventLoopProxy};
use crate::{RedoxSocket, WindowProperties};
// These values match the values uses in the `window_new` function in orbital:
@@ -104,7 +103,13 @@ impl Window {
.expect("failed to open window");
// Add to event socket.
el.event_socket.subscribe(window.fd(), EventSource::Orbital, EventFlags::READ).unwrap();
el.event_socket
.write(&syscall::Event {
id: window.fd,
flags: syscall::EventFlags::EVENT_READ,
data: window.fd,
})
.unwrap();
let window_socket = Arc::new(window);
@@ -141,7 +146,7 @@ impl Window {
#[inline]
fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
let handle = rwh_06::OrbitalWindowHandle::new({
let window = self.window_socket.fd() as *mut _;
let window = self.window_socket.fd as *mut _;
std::ptr::NonNull::new(window).expect("orbital fd should never be null")
});
Ok(rwh_06::RawWindowHandle::Orbital(handle))
@@ -155,7 +160,7 @@ impl Window {
impl CoreWindow for Window {
fn id(&self) -> WindowId {
WindowId::from_raw(self.window_socket.fd())
WindowId::from_raw(self.window_socket.fd)
}
fn ime_capabilities(&self) -> Option<window::ImeCapabilities> {

View File

@@ -1,9 +1,13 @@
use std::ffi::c_void;
use std::ptr;
use std::sync::Arc;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{ClassType, MainThreadMarker, msg_send};
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopDefaultMode};
use objc2_core_foundation::{
CFIndex, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, kCFRunLoopDefaultMode,
};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification,
@@ -12,7 +16,6 @@ use objc2_ui_kit::{
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen,
};
use rwh_06::HasDisplayHandle;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver};
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
@@ -133,10 +136,6 @@ pub struct EventLoop {
_did_enter_background_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_wakeup_observer: MainRunLoopObserver,
_main_events_cleared_observer: MainRunLoopObserver,
_events_cleared_observer: MainRunLoopObserver,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -152,6 +151,9 @@ impl EventLoop {
return Err(EventLoopError::RecreationAttempt);
}
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
let center = NSNotificationCenter::defaultCenter();
let _did_finish_launching_observer = create_observer(
@@ -223,50 +225,6 @@ impl EventLoop {
move |_| app_state::handle_memory_warning(mtm),
);
let main_loop = MainRunLoop::get(mtm);
let mode = unsafe { kCFRunLoopDefaultMode }.unwrap();
let _wakeup_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::AfterWaiting,
true,
// Queued with the highest priority to ensure it is processed before other observers.
CFIndex::MIN,
move |_| app_state::handle_wakeup_transition(mtm),
);
main_loop.add_observer(&_wakeup_observer, mode);
let _main_events_cleared_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::BeforeWaiting,
true,
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the
// main_end priority to be 0, in order to send `AboutToWait` before `RedrawRequested`.
// This value was chosen conservatively to guard against apple using different
// priorities for their redraw observers in different OS's or on different devices. If
// it so happens that it's too conservative, the main symptom would be non-redraw
// events coming in after `AboutToWait`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
//
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
0,
move |_| app_state::handle_main_events_cleared(mtm),
);
main_loop.add_observer(&_main_events_cleared_observer, mode);
let _events_cleared_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::BeforeWaiting,
true,
// Queued with the lowest priority to ensure it is processed after other observers.
CFIndex::MAX,
move |_| app_state::handle_events_cleared(mtm),
);
main_loop.add_observer(&_events_cleared_observer, mode);
Ok(EventLoop {
mtm,
window_target: ActiveEventLoop { mtm },
@@ -277,9 +235,6 @@ impl EventLoop {
_did_enter_background_observer,
_will_terminate_observer,
_did_receive_memory_warning_observer,
_wakeup_observer,
_main_events_cleared_observer,
_events_cleared_observer,
})
}
@@ -307,3 +262,97 @@ impl EventLoop {
&self.window_target
}
}
fn setup_control_flow_observers() {
unsafe {
// begin is queued with the highest priority to ensure it is processed before other
// observers
extern "C-unwind" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(),
}
}
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `AboutToWait`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
//
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
extern "C-unwind" fn control_flow_main_end_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm),
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
extern "C-unwind" fn control_flow_end_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm),
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
_ => unreachable!(),
}
}
let main_loop = CFRunLoop::main().unwrap();
let begin_observer = CFRunLoopObserver::new(
None,
CFRunLoopActivity::AfterWaiting.0,
true,
CFIndex::MIN,
Some(control_flow_begin_handler),
ptr::null_mut(),
)
.unwrap();
main_loop.add_observer(Some(&begin_observer), kCFRunLoopDefaultMode);
let main_end_observer = CFRunLoopObserver::new(
None,
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
true,
0, // see comment on `control_flow_main_end_handler`
Some(control_flow_main_end_handler),
ptr::null_mut(),
)
.unwrap();
main_loop.add_observer(Some(&main_end_observer), kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserver::new(
None,
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
true,
CFIndex::MAX,
Some(control_flow_end_handler),
ptr::null_mut(),
)
.unwrap();
main_loop.add_observer(Some(&end_observer), kCFRunLoopDefaultMode);
}
}

View File

@@ -15,8 +15,8 @@ use objc2_ui_kit::{
};
use tracing::debug;
use winit_core::event::{
ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource,
TabletToolAngle, TabletToolButton, TabletToolData, TabletToolKind, TouchPhase, WindowEvent,
ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource, TouchPhase,
WindowEvent,
};
use winit_core::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
@@ -461,18 +461,19 @@ impl WinitView {
let window = self.window().unwrap();
let mut touch_events = Vec::new();
for touch in touches {
if let UITouchType::Pencil = touch.r#type() {
continue;
}
let logical_location = touch.locationInView(None);
let touch_type = touch.r#type();
let force = if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
let use_force = match touch_type {
UITouchType::Pencil => true,
_ => {
let trait_collection = self.traitCollection();
trait_collection.forceTouchCapability() == UIForceTouchCapability::Available
},
};
if use_force {
let force = if let UITouchType::Pencil = touch_type {
None
} else if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
let trait_collection = self.traitCollection();
let touch_capability = trait_collection.forceTouchCapability();
// Both the OS _and_ the device need to be checked for force touch support.
if touch_capability == UIForceTouchCapability::Available {
let force = touch.force();
let max_possible_force = touch.maximumPossibleForce();
Some(Force::Calibrated {
@@ -528,7 +529,7 @@ impl WinitView {
primary,
position,
kind: if let UITouchType::Pencil = touch_type {
PointerKind::TabletTool(TabletToolKind::Pencil)
PointerKind::Unknown
} else {
PointerKind::Touch(finger_id)
},
@@ -542,12 +543,7 @@ impl WinitView {
state: ElementState::Pressed,
position,
button: if let UITouchType::Pencil = touch_type {
let tool_data = self.tablet_tool_data_for_pencil(&touch);
ButtonSource::TabletTool {
kind: TabletToolKind::Pencil,
button: TabletToolButton::Contact,
data: tool_data,
}
ButtonSource::Unknown(0)
} else {
ButtonSource::Touch { finger_id, force }
},
@@ -556,11 +552,7 @@ impl WinitView {
},
UITouchPhase::Moved => {
let (primary, source) = if let UITouchType::Pencil = touch_type {
let tool_data = self.tablet_tool_data_for_pencil(&touch);
(true, PointerSource::TabletTool {
kind: TabletToolKind::Pencil,
data: tool_data,
})
(true, PointerSource::Unknown)
} else {
(ivars.primary_finger.get().unwrap() == finger_id, PointerSource::Touch {
finger_id,
@@ -600,12 +592,7 @@ impl WinitView {
state: ElementState::Released,
position,
button: if let UITouchType::Pencil = touch_type {
let tool_data = self.tablet_tool_data_for_pencil(&touch);
ButtonSource::TabletTool {
kind: TabletToolKind::Pencil,
button: TabletToolButton::Contact,
data: tool_data,
}
ButtonSource::Unknown(0)
} else {
ButtonSource::Touch { finger_id, force }
},
@@ -620,7 +607,7 @@ impl WinitView {
primary,
position: Some(position),
kind: if let UITouchType::Pencil = touch_type {
PointerKind::TabletTool(TabletToolKind::Pencil)
PointerKind::Unknown
} else {
PointerKind::Touch(finger_id)
},
@@ -634,32 +621,6 @@ impl WinitView {
app_state::handle_nonuser_events(mtm, touch_events);
}
fn tablet_tool_data_for_pencil(&self, touch: &UITouch) -> TabletToolData {
let force = if available!(ios = 9.0, tvos = 9.0, visionos = 1.0) {
let force_val = touch.force();
let max_force = touch.maximumPossibleForce();
Some(Force::Calibrated { force: force_val as _, max_possible_force: max_force as _ })
} else {
None
};
let angle = if available!(ios = 9.1, tvos = 9.0, visionos = 1.0) {
let altitude = touch.altitudeAngle();
let azimuth = touch.azimuthAngleInView(Some(self));
Some(TabletToolAngle { altitude: altitude as _, azimuth: azimuth as _ })
} else {
None
};
TabletToolData {
force,
tangential_force: None, // iOS doesn't provide barrel pressure
twist: None, // iOS doesn't provide rotation/twist
tilt: None, // Will be calculated from angle if needed
angle,
}
}
fn handle_insert_text(&self, text: &NSString) {
let window = self.window().unwrap();
let window_id = window.id();

View File

@@ -29,8 +29,8 @@ tracing.workspace = true
winit-core.workspace = true
# Platform-specific
ahash.workspace = true
calloop.workspace = true
foldhash.workspace = true
libc.workspace = true
memmap2.workspace = true
rustix = { workspace = true, features = ["std", "system", "thread", "process", "event", "pipe"] }

View File

@@ -14,9 +14,6 @@
//! * `wayland-csd-adwaita-crossfont`.
//! * `wayland-csd-adwaita-notitle`.
//! * `wayland-csd-adwaita-notitlebar`.
#![allow(clippy::mutable_key_type)]
use std::ffi::c_void;
use std::ptr::NonNull;

View File

@@ -2,7 +2,7 @@
use std::sync::Arc;
use foldhash::HashMap;
use ahash::AHashMap;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
@@ -43,7 +43,7 @@ pub struct WinitSeatState {
touch: Option<WlTouch>,
/// The mapping from touched points to the surfaces they're present.
touch_map: HashMap<i32, TouchPoint>,
touch_map: AHashMap<i32, TouchPoint>,
/// Id of the first touch event.
first_touch_id: Option<i32>,

View File

@@ -150,7 +150,7 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
// 6. Place cursor inside preedit text.
if let Some(DeleteSurroundingText { before, after }) =
text_input_data.pending_delete.take()
text_input_data.pending_delete
{
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::DeleteSurrounding {

View File

@@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use foldhash::HashMap;
use ahash::AHashMap;
use sctk::compositor::{CompositorHandler, CompositorState};
use sctk::output::{OutputHandler, OutputState};
use sctk::reexports::calloop::LoopHandle;
@@ -62,10 +62,10 @@ pub struct WinitState {
pub xdg_shell: XdgShell,
/// The currently present windows.
pub windows: RefCell<HashMap<WindowId, Arc<Mutex<WindowState>>>>,
pub windows: RefCell<AHashMap<WindowId, Arc<Mutex<WindowState>>>>,
/// The requests from the `Window` to EventLoop, such as close operations and redraw requests.
pub window_requests: RefCell<HashMap<WindowId, Arc<WindowRequests>>>,
pub window_requests: RefCell<AHashMap<WindowId, Arc<WindowRequests>>>,
/// The events that were generated directly from the window.
pub window_events_sink: Arc<Mutex<EventSink>>,
@@ -74,10 +74,10 @@ pub struct WinitState {
pub window_compositor_updates: Vec<WindowCompositorUpdate>,
/// Currently handled seats.
pub seats: HashMap<ObjectId, WinitSeatState>,
pub seats: AHashMap<ObjectId, WinitSeatState>,
/// Currently present cursor surfaces.
pub pointer_surfaces: HashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
pub pointer_surfaces: AHashMap<ObjectId, Arc<ThemedPointer<WinitPointerData>>>,
/// The state of the text input on the client.
pub text_input_state: Option<TextInputState>,
@@ -156,7 +156,7 @@ impl WinitState {
let seat_state = SeatState::new(globals, queue_handle);
let mut seats = HashMap::default();
let mut seats = AHashMap::default();
for seat in seat_state.seats() {
seats.insert(seat.id(), WinitSeatState::new());
}

View File

@@ -21,13 +21,6 @@ use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_tool_v2::{
ButtonState, Event as ToolEvent, Type as ToolType, ZwpTabletToolV2,
};
use sctk::reexports::protocols::wp::tablet::zv2::client::zwp_tablet_v2::ZwpTabletV2;
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_pad_dial_v2::ZwpTabletPadDialV2;
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_pad_group_v2::{
self, ZwpTabletPadGroupV2,
};
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_pad_ring_v2::ZwpTabletPadRingV2;
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_pad_strip_v2::ZwpTabletPadStripV2;
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_pad_v2;
use winit_core::event::{
ButtonSource, ElementState, Force, PointerKind, PointerSource, TabletToolButton,
TabletToolData as CoreTabletToolData, TabletToolKind, TabletToolTilt, WindowEvent,
@@ -331,10 +324,6 @@ impl Dispatch<ZwpTabletV2, (), WinitState> for TabletManager {
}
impl Dispatch<ZwpTabletPadV2, (), WinitState> for TabletManager {
event_created_child!(WinitState, ZwpTabletPadV2, [
zwp_tablet_pad_v2::EVT_GROUP_OPCODE => (ZwpTabletPadGroupV2, Default::default()),
]);
fn event(
_: &mut WinitState,
_: &ZwpTabletPadV2,
@@ -346,67 +335,9 @@ impl Dispatch<ZwpTabletPadV2, (), WinitState> for TabletManager {
}
}
impl Dispatch<ZwpTabletPadGroupV2, (), WinitState> for TabletManager {
event_created_child!(WinitState, ZwpTabletPadGroupV2, [
zwp_tablet_pad_group_v2::EVT_RING_OPCODE => (ZwpTabletPadRingV2, Default::default()),
zwp_tablet_pad_group_v2::EVT_STRIP_OPCODE => (ZwpTabletPadStripV2, Default::default()),
zwp_tablet_pad_group_v2::EVT_DIAL_OPCODE => (ZwpTabletPadDialV2, Default::default()),
]);
fn event(
_: &mut WinitState,
_: &ZwpTabletPadGroupV2,
_: <ZwpTabletPadGroupV2 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ZwpTabletPadRingV2, (), WinitState> for TabletManager {
fn event(
_: &mut WinitState,
_: &ZwpTabletPadRingV2,
_: <ZwpTabletPadRingV2 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ZwpTabletPadStripV2, (), WinitState> for TabletManager {
fn event(
_: &mut WinitState,
_: &ZwpTabletPadStripV2,
_: <ZwpTabletPadStripV2 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ZwpTabletPadDialV2, (), WinitState> for TabletManager {
fn event(
_: &mut WinitState,
_: &ZwpTabletPadDialV2,
_: <ZwpTabletPadDialV2 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<WinitState>,
) {
}
}
delegate_dispatch!(WinitState: [ZwpTabletManagerV2: GlobalData] => TabletManager);
delegate_dispatch!(WinitState: [ZwpTabletManagerV2: ()] => TabletManager);
delegate_dispatch!(WinitState: [ZwpTabletSeatV2: ()] => TabletManager);
delegate_dispatch!(WinitState: [ZwpTabletV2: ()] => TabletManager);
delegate_dispatch!(WinitState: [ZwpTabletToolV2: TabletToolData] => TabletManager);
delegate_dispatch!(WinitState: [ZwpTabletPadV2: ()] => TabletManager);
delegate_dispatch!(WinitState: [ZwpTabletPadGroupV2: ()] => TabletManager);
delegate_dispatch!(WinitState: [ZwpTabletPadRingV2: ()] => TabletManager);
delegate_dispatch!(WinitState: [ZwpTabletPadStripV2: ()] => TabletManager);
delegate_dispatch!(WinitState: [ZwpTabletPadDialV2: ()] => TabletManager);

View File

@@ -171,12 +171,6 @@ impl Window {
Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor),
}
// Apply resize increments.
if let Some(increments) = attributes.surface_resize_increments {
let increments = increments.to_logical(window_state.scale_factor());
window_state.set_resize_increments(Some(increments));
}
// Activate the window when the token is passed.
if let (Some(xdg_activation), Some(token)) = (xdg_activation.as_ref(), activation_token) {
xdg_activation.activate(token.into_raw(), &surface);
@@ -376,18 +370,11 @@ impl CoreWindow for Window {
}
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
let window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor();
window_state
.resize_increments()
.map(|size| super::logical_to_physical_rounded(size, scale_factor))
None
}
fn set_surface_resize_increments(&self, increments: Option<Size>) {
let mut window_state = self.window_state.lock().unwrap();
let scale_factor = window_state.scale_factor();
let increments = increments.map(|size| size.to_logical(scale_factor));
window_state.set_resize_increments(increments);
fn set_surface_resize_increments(&self, _increments: Option<Size>) {
warn!("`set_surface_resize_increments` is not implemented for Wayland");
}
fn set_title(&self, title: &str) {

View File

@@ -4,8 +4,8 @@ use std::num::NonZeroU32;
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;
use ahash::HashSet;
use dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Size};
use foldhash::HashSet;
use sctk::compositor::{CompositorState, Region, SurfaceData, SurfaceDataExt};
use sctk::globals::GlobalData;
use sctk::reexports::client::backend::ObjectId;
@@ -140,7 +140,6 @@ pub struct WindowState {
/// Min size.
min_surface_size: LogicalSize<u32>,
max_surface_size: Option<LogicalSize<u32>>,
resize_increments: Option<LogicalSize<u32>>,
/// The size of the window when no states were applied to it. The primary use for it
/// is to fallback to original window size, before it was maximized, if the compositor
@@ -224,7 +223,6 @@ impl WindowState {
last_configure: None,
max_surface_size: None,
min_surface_size: MIN_WINDOW_SIZE,
resize_increments: None,
pointer_constraints,
pointers: Default::default(),
queue_handle: queue_handle.clone(),
@@ -363,42 +361,6 @@ impl WindowState {
.unwrap_or(new_size.height);
}
// Apply size increments.
//
// We conditionally apply increments to avoid conflicts with the compositor's layout rules:
// 1. If the window is floating (constrain == true), we snap to increments to ensure the
// app's grid alignment.
// 2. If the user is interactively resizing (is_resizing), we snap the size to provide
// feedback.
//
// However, we MUST NOT snap if the compositor enforces a specific size (constrain == false,
// or states like Maximized/Tiled). Snapping in these cases (e.g. corner tiling) would
// shrink the window below the allocated area, creating visible gaps between valid
// windows or screen edges.
if (constrain || configure.is_resizing())
&& !configure.is_maximized()
&& !configure.is_fullscreen()
&& !configure.is_tiled()
{
if let Some(increments) = self.resize_increments {
// We use min size as a base size for the increments, similar to how X11 does it.
//
// This ensures that we can always reach the min size and the increments are
// calculated from it.
let (delta_width, delta_height) = (
new_size.width.saturating_sub(self.min_surface_size.width),
new_size.height.saturating_sub(self.min_surface_size.height),
);
let width = self.min_surface_size.width
+ (delta_width / increments.width) * increments.width;
let height = self.min_surface_size.height
+ (delta_height / increments.height) * increments.height;
new_size = (width, height).into();
}
}
let new_state = configure.state;
let old_state = self.last_configure.as_ref().map(|configure| configure.state);
@@ -787,18 +749,6 @@ impl WindowState {
self.selected_cursor = SelectedCursor::Custom(cursor);
}
/// Set the resize increments of the window.
pub fn set_resize_increments(&mut self, increments: Option<LogicalSize<u32>>) {
self.resize_increments = increments;
// NOTE: We don't update the window size here, because it will be done on the next resize
// or configure event.
}
/// Get the resize increments of the window.
pub fn resize_increments(&self) -> Option<LogicalSize<u32>> {
self.resize_increments
}
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
self.apply_on_pointer(|pointer, data| {
let surface = pointer.surface();

View File

@@ -3,7 +3,7 @@ use std::sync::LazyLock;
/// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode
use std::{ffi::c_void, ptr};
use windows_sys::Win32::Foundation::{HWND, LPARAM, S_OK, WPARAM};
use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, S_OK, WPARAM};
use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
use windows_sys::Win32::UI::Accessibility::{HCF_HIGHCONTRASTON, HIGHCONTRASTA};
use windows_sys::Win32::UI::Controls::SetWindowTheme;
@@ -11,7 +11,7 @@ use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow;
use windows_sys::Win32::UI::WindowsAndMessaging::{
DefWindowProcW, SPI_GETHIGHCONTRAST, SystemParametersInfoA, WM_NCACTIVATE,
};
use windows_sys::core::{BOOL, PCSTR, PCWSTR};
use windows_sys::core::{PCSTR, PCWSTR};
use windows_sys::w;
use winit_core::window::Theme;

View File

@@ -3,9 +3,9 @@
use std::ffi::c_void;
use windows_sys::Win32::Foundation::{HWND, POINTL};
use windows_sys::Win32::Foundation::{BOOL, HWND, POINTL};
use windows_sys::Win32::System::Com::{FORMATETC, STGMEDIUM};
use windows_sys::core::{BOOL, GUID, HRESULT};
use windows_sys::core::{GUID, HRESULT};
pub type IUnknown = *mut c_void;
pub type IAdviseSink = *mut c_void;

View File

@@ -528,11 +528,9 @@ fn main_thread_id() -> u32 {
//
// See: https://doc.rust-lang.org/stable/reference/abi.html#the-link_section-attribute
#[unsafe(link_section = ".CRT$XCU")]
static INIT_MAIN_THREAD_ID: unsafe extern "C" fn() = {
unsafe extern "C" fn initer() {
unsafe {
MAIN_THREAD_ID = GetCurrentThreadId();
}
static INIT_MAIN_THREAD_ID: unsafe fn() = {
unsafe fn initer() {
unsafe { MAIN_THREAD_ID = GetCurrentThreadId() };
}
initer
};
@@ -1503,9 +1501,9 @@ unsafe fn public_window_callback_inner(
},
WM_IME_SETCONTEXT => {
// IME UI visibility flags are in lparam.
let lparam = lparam & !(ISC_SHOWUICOMPOSITIONWINDOW as isize);
result = ProcResult::Value(unsafe { DefWindowProcW(window, msg, wparam, lparam) });
// Hide composing text drawn by IME.
let wparam = wparam & (!ISC_SHOWUICOMPOSITIONWINDOW as usize);
result = ProcResult::DefWindowProc(wparam);
},
// this is necessary for us to maintain minimize/restore state

View File

@@ -4,14 +4,13 @@ use std::num::{NonZeroU16, NonZeroU32};
use std::{io, iter, mem, ptr};
use dpi::{PhysicalPosition, PhysicalSize};
use windows_sys::Win32::Foundation::{HWND, LPARAM, POINT, RECT};
use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, POINT, RECT};
use windows_sys::Win32::Graphics::Gdi::{
DEVMODEW, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH,
ENUM_CURRENT_SETTINGS, EnumDisplayMonitors, EnumDisplaySettingsExW, GetMonitorInfoW, HDC,
HMONITOR, MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTOPRIMARY, MONITORINFO, MONITORINFOEXW,
MonitorFromPoint, MonitorFromWindow,
};
use windows_sys::core::BOOL;
use winit_core::monitor::{MonitorHandleProvider, VideoMode};
use super::util::decode_wide;

View File

@@ -6,7 +6,7 @@ use std::sync::LazyLock;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{io, mem, ptr};
use windows_sys::Win32::Foundation::{HANDLE, HMODULE, HWND, NTSTATUS, POINT, RECT};
use windows_sys::Win32::Foundation::{BOOL, HANDLE, HMODULE, HWND, NTSTATUS, POINT, RECT};
use windows_sys::Win32::Graphics::Gdi::{ClientToScreen, HMONITOR};
use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW;
@@ -23,7 +23,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_MAXIMIZE,
ShowCursor, WINDOW_LONG_PTR_INDEX, WINDOWPLACEMENT,
};
use windows_sys::core::{BOOL, HRESULT, PCWSTR};
use windows_sys::core::{HRESULT, PCWSTR};
use winit_core::cursor::CursorIcon;
use winit_core::event::DeviceId;

View File

@@ -50,13 +50,8 @@ impl ImeInner {
}
pub unsafe fn close_im_if_necessary(&self) -> Result<bool, XError> {
if !self.is_destroyed {
if let Some(im) = &self.im {
unsafe { close_im(&self.xconn, im.im) }?;
Ok(true)
} else {
Ok(false)
}
if !self.is_destroyed && self.im.is_some() {
unsafe { close_im(&self.xconn, self.im.as_ref().unwrap().im) }.map(|_| true)
} else {
Ok(false)
}

View File

@@ -60,18 +60,16 @@ impl Iterator for KeymapIter<'_> {
impl XConnection {
pub fn query_keymap(&self) -> Keymap {
let keys = self
.xcb_connection()
.query_keymap()
.expect("Failed to query keymap")
.reply()
.expect("Missing reply")
.keys;
let mut keys = [0; 32];
unsafe {
(self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char);
}
Keymap { keys }
}
}
fn first_bit(b: u8) -> u8 {
b & b.wrapping_neg()
1 << b.trailing_zeros()
}

View File

@@ -26,9 +26,10 @@ use winit_core::window::{
};
use x11rb::connection::{Connection, RequestConnection};
use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification};
use x11rb::protocol::shape::{ConnectionExt as ShapeExt, SK, SO};
use x11rb::protocol::shape::SK;
use x11rb::protocol::sync::{ConnectionExt as _, Int64};
use x11rb::protocol::xproto::{self, ClipOrdering, ConnectionExt as _, Rectangle};
use x11rb::protocol::xfixes::{ConnectionExt, RegionWrapper};
use x11rb::protocol::xproto::{self, ConnectionExt as _, Rectangle};
use x11rb::protocol::{randr, xinput};
use crate::atoms::*;
@@ -1966,15 +1967,6 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
// Implement cursor hittest for X11 by either setting an empty or full window input shape.
// In X11, every window has two "shapes":
// * Bounding shape: defines the visible outline of the window.
// * Input shape: defines the region of the window that receives pointer/keyboard events.
// If the input shape is the full window rectangle, the window behaves normally.
// If the input shape is empty, the window is completely clickthrough.
// Here, we implement hit test by mapping `hittest = true` to "restore a full input shape"
// and `hittest = false` to "clear the input shape" (empty list of rectangles).
let mut rectangles: Vec<Rectangle> = Vec::new();
if hittest {
let size = self.surface_size();
@@ -1985,17 +1977,11 @@ impl UnownedWindow {
height: size.height as u16,
})
}
let region = RegionWrapper::create_region(self.xconn.xcb_connection(), &rectangles)
.map_err(|_e| RequestError::Ignored)?;
self.xconn
.xcb_connection()
.shape_rectangles(
SO::SET,
SK::INPUT,
ClipOrdering::UNSORTED,
self.xwindow,
0,
0,
&rectangles,
)
.xfixes_set_window_shape_region(self.xwindow, SK::INPUT, 0, 0, region.region())
.map_err(|_e| RequestError::Ignored)?;
self.shared_state_lock().cursor_hittest = Some(hittest);
Ok(())

View File

@@ -250,7 +250,9 @@ impl XConnection {
// Store the timestamp in the slot if it's greater than the last one.
let mut last_timestamp = self.timestamp.load(Ordering::Relaxed);
loop {
if (timestamp as i32).wrapping_sub(last_timestamp as i32) <= 0 {
let wrapping_sub = |a: xproto::Timestamp, b: xproto::Timestamp| (a as i32) - (b as i32);
if wrapping_sub(timestamp, last_timestamp) <= 0 {
break;
}

View File

@@ -76,10 +76,12 @@ winit-core.workspace = true
[dev-dependencies]
image = { workspace = true, features = ["png"] }
softbuffer.workspace = true
tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
softbuffer.workspace = true
[target.'cfg(target_os = "android")'.dependencies]
winit-android.workspace = true

View File

@@ -7,18 +7,21 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::error::Error;
use std::fmt::Debug;
#[cfg(not(android_platform))]
use std::num::NonZeroU32;
use std::sync::Arc;
use std::sync::mpsc::{self, Receiver, Sender};
#[cfg(not(web_platform))]
#[cfg(all(not(android_platform), not(web_platform)))]
use std::time::Instant;
use std::{fmt, mem};
use cursor_icon::CursorIcon;
#[cfg(not(android_platform))]
use rwh_06::{DisplayHandle, HasDisplayHandle};
#[cfg(not(android_platform))]
use softbuffer::{Context, Surface};
use tracing::{error, info};
#[cfg(web_platform)]
#[cfg(all(web_platform, not(android_platform)))]
use web_time::Instant;
use winit::application::ApplicationHandler;
use winit::cursor::{Cursor, CustomCursor, CustomCursorSource};
@@ -94,12 +97,14 @@ struct Application {
/// Drawing context.
///
/// With OpenGL it could be EGLDisplay.
#[cfg(not(android_platform))]
context: Option<Context<DisplayHandle<'static>>>,
}
impl Application {
fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
#[cfg(not(android_platform))]
let context = Some(
Context::new(unsafe {
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
@@ -125,7 +130,15 @@ impl Application {
.into_iter()
.collect();
Self { receiver, sender, context, custom_cursors, icon, windows: Default::default() }
Self {
receiver,
sender,
#[cfg(not(android_platform))]
context,
custom_cursors,
icon,
windows: Default::default(),
}
}
fn create_window(
@@ -238,12 +251,6 @@ impl Application {
Action::ToggleSimpleFullscreen => {
window.window.set_simple_fullscreen(!window.window.simple_fullscreen());
},
#[cfg(macos_platform)]
Action::ToggleBorderlessGame => {
let current = window.window.is_borderless_game();
window.window.set_borderless_game(!current);
info!("Borderless game: {}", !current);
},
Action::ToggleMaximize => window.toggle_maximize(),
Action::Minimize => window.minimize(),
Action::NextCursor => window.next_cursor(),
@@ -607,6 +614,7 @@ struct WindowState {
/// Render surface.
///
/// NOTE: This surface must be dropped before the `Window`.
#[cfg(not(android_platform))]
surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>,
/// The actual winit Window.
window: Arc<dyn Window>,
@@ -615,6 +623,7 @@ struct WindowState {
/// Fill the window with animated color
animated_fill_color: bool,
/// The application start time. Used for color fill animation
#[cfg(not(android_platform))]
start_time: Instant,
/// Redraw continuously
continuous_redraw: bool,
@@ -650,6 +659,7 @@ impl WindowState {
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus
// it doesn't outlive it.
#[cfg(not(android_platform))]
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
let theme = window.theme().unwrap_or(Theme::Dark);
@@ -664,12 +674,14 @@ impl WindowState {
custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1,
cursor_grab: CursorGrabMode::None,
named_idx,
#[cfg(not(android_platform))]
surface,
window,
theme,
animated_fill_color: false,
continuous_redraw: false,
emit_surface_size: false,
#[cfg(not(android_platform))]
start_time: Instant::now(),
cursor_position: Default::default(),
cursor_hidden: Default::default(),
@@ -841,12 +853,15 @@ impl WindowState {
/// Resize the surface to the new size.
fn resize(&mut self, size: PhysicalSize<u32>) {
info!("Surface resized to {size:?}");
let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) {
(Some(width), Some(height)) => (width, height),
_ => return,
};
self.surface.resize(width, height).expect("failed to resize inner buffer");
#[cfg(not(android_platform))]
{
let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
(Some(width), Some(height)) => (width, height),
_ => return,
};
self.surface.resize(width, height).expect("failed to resize inner buffer");
}
self.window.request_redraw();
}
@@ -931,6 +946,7 @@ impl WindowState {
}
/// Draw the window contents.
#[cfg(not(android_platform))]
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
if self.occluded {
info!("Skipping drawing occluded window={:?}", self.window.id());
@@ -976,6 +992,12 @@ impl WindowState {
Ok(())
}
#[cfg(android_platform)]
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
info!("Drawing but without rendering...");
Ok(())
}
}
struct Binding<T: Eq> {
@@ -1005,8 +1027,6 @@ enum Action {
ToggleFullscreen,
#[cfg(macos_platform)]
ToggleSimpleFullscreen,
#[cfg(macos_platform)]
ToggleBorderlessGame,
ToggleMaximize,
Minimize,
NextCursor,
@@ -1044,8 +1064,6 @@ impl Action {
Action::ToggleFullscreen => "Toggle fullscreen",
#[cfg(macos_platform)]
Action::ToggleSimpleFullscreen => "Toggle simple fullscreen",
#[cfg(macos_platform)]
Action::ToggleBorderlessGame => "Toggle borderless game mode",
Action::ToggleMaximize => "Maximize",
Action::Minimize => "Minimize",
Action::ToggleResizeIncrements => "Use resize increments when resizing window",
@@ -1297,8 +1315,6 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("T", ModifiersState::META, Action::CreateNewTab),
#[cfg(macos_platform)]
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
#[cfg(macos_platform)]
Binding::new("B", ModifiersState::CONTROL, Action::ToggleBorderlessGame),
Binding::new("S", ModifiersState::ALT, Action::EmitSurfaceSize),
Binding::new("S", ModifiersState::CONTROL, Action::Message),
];

View File

@@ -7,109 +7,150 @@
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
//! also be used to fill the window buffer, but they are more complicated to use.
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
#[cfg(not(web_platform))]
use std::time::Instant;
#[allow(unused_imports)]
pub use platform::cleanup_window;
#[allow(unused_imports)]
pub use platform::fill_window;
#[allow(unused_imports)]
pub use platform::fill_window_with_animated_color;
#[allow(unused_imports)]
pub use platform::fill_window_with_color;
use softbuffer::{Context, Surface};
#[cfg(web_platform)]
use web_time::Instant;
use winit::window::{Window, WindowId};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod platform {
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
#[cfg(all(not(android_platform), not(web_platform)))]
use std::time::Instant;
thread_local! {
// NOTE: You should never do things like that, create context and drop it before
// you drop the event loop. We do this for brevity to not blow up examples. We use
// ManuallyDrop to prevent destructors from running.
//
// A static, thread-local map of graphics contexts to open windows.
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
}
use softbuffer::{Context, Surface};
#[cfg(all(web_platform, not(android_platform)))]
use web_time::Instant;
use winit::window::{Window, WindowId};
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
context: RefCell<Context<&'static dyn Window>>,
thread_local! {
// NOTE: You should never do things like that, create context and drop it before
// you drop the event loop. We do this for brevity to not blow up examples. We use
// ManuallyDrop to prevent destructors from running.
//
// A static, thread-local map of graphics contexts to open windows.
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
}
/// The hash map of window IDs to surfaces.
surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>,
}
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
context: RefCell<Context<&'static dyn Window>>,
impl GraphicsContext {
fn new(w: &dyn Window) -> Self {
Self {
context: RefCell::new(
Context::new(unsafe { mem::transmute::<&'_ dyn Window, &'static dyn Window>(w) })
/// The hash map of window IDs to surfaces.
surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>,
}
impl GraphicsContext {
fn new(w: &dyn Window) -> Self {
Self {
context: RefCell::new(
Context::new(unsafe {
mem::transmute::<&'_ dyn Window, &'static dyn Window>(w)
})
.expect("Failed to create a softbuffer context"),
),
surfaces: HashMap::new(),
),
surfaces: HashMap::new(),
}
}
fn create_surface(
&mut self,
window: &dyn Window,
) -> &mut Surface<&'static dyn Window, &'static dyn Window> {
self.surfaces.entry(window.id()).or_insert_with(|| {
Surface::new(&self.context.borrow(), unsafe {
mem::transmute::<&'_ dyn Window, &'static dyn Window>(window)
})
.expect("Failed to create a softbuffer surface")
})
}
fn destroy_surface(&mut self, window: &dyn Window) {
self.surfaces.remove(&window.id());
}
}
fn create_surface(
&mut self,
window: &dyn Window,
) -> &mut Surface<&'static dyn Window, &'static dyn Window> {
self.surfaces.entry(window.id()).or_insert_with(|| {
Surface::new(&self.context.borrow(), unsafe {
mem::transmute::<&'_ dyn Window, &'static dyn Window>(window)
})
.expect("Failed to create a softbuffer surface")
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
GC.with(|gc| {
let size = window.surface_size();
let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
else {
return;
};
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface =
gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
// Fill a buffer with a solid color
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
buffer.fill(color);
buffer.present().expect("Failed to present the softbuffer buffer");
})
}
fn destroy_surface(&mut self, window: &dyn Window) {
self.surfaces.remove(&window.id());
#[allow(dead_code)]
pub fn fill_window(window: &dyn Window) {
fill_window_with_color(window, 0xff181818);
}
#[allow(dead_code)]
pub fn fill_window_with_animated_color(window: &dyn Window, start: Instant) {
let time = start.elapsed().as_secs_f32() * 1.5;
let blue = (time.sin() * 255.0) as u32;
let green = ((time.cos() * 255.0) as u32) << 8;
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
let color = red | green | blue;
fill_window_with_color(window, color);
}
#[allow(dead_code)]
pub fn cleanup_window(window: &dyn Window) {
GC.with(|gc| {
let mut gc = gc.borrow_mut();
if let Some(context) = gc.as_mut() {
context.destroy_surface(window);
}
});
}
}
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
GC.with(|gc| {
let size = window.surface_size();
let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
else {
return;
};
#[cfg(any(target_os = "android", target_os = "ios"))]
mod platform {
#[allow(dead_code)]
pub fn fill_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms.
}
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface = gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
#[allow(dead_code)]
pub fn fill_window_with_color(_window: &dyn winit::window::Window, _color: u32) {
// No-op on mobile platforms.
}
// Fill a buffer with a solid color
#[allow(dead_code)]
pub fn fill_window_with_animated_color(
_window: &dyn winit::window::Window,
_start: std::time::Instant,
) {
// No-op on mobile platforms.
}
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
buffer.fill(color);
buffer.present().expect("Failed to present the softbuffer buffer");
})
}
#[allow(dead_code)]
pub fn fill_window(window: &dyn Window) {
fill_window_with_color(window, 0xff181818);
}
#[allow(dead_code)]
pub fn fill_window_with_animated_color(window: &dyn Window, start: Instant) {
let time = start.elapsed().as_secs_f32() * 1.5;
let blue = (time.sin() * 255.0) as u32;
let green = ((time.cos() * 255.0) as u32) << 8;
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
let color = red | green | blue;
fill_window_with_color(window, color);
}
#[allow(dead_code)]
pub fn cleanup_window(window: &dyn Window) {
GC.with(|gc| {
let mut gc = gc.borrow_mut();
if let Some(context) = gc.as_mut() {
context.destroy_surface(window);
}
});
#[allow(dead_code)]
pub fn cleanup_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms.
}
}

View File

@@ -1,7 +1,6 @@
//! Simple winit window example.
use std::error::Error;
use std::time::Instant;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
@@ -37,14 +36,8 @@ impl ApplicationHandler for App {
}
}
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
_: WindowId,
timestamp: Instant,
event: WindowEvent,
) {
::tracing::info!("{:?}: {event:?}", timestamp.elapsed());
fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) {
println!("{event:?}");
match event {
WindowEvent::CloseRequested => {
println!("Close was requested; stopping");
@@ -69,21 +62,11 @@ impl ApplicationHandler for App {
fill::fill_window(window.as_ref());
// For contiguous redraw loop you can request a redraw from here.
window.request_redraw();
// window.request_redraw();
},
_ => (),
}
}
fn device_event(
&mut self,
_event_loop: &dyn ActiveEventLoop,
_device_id: Option<winit::event::DeviceId>,
timestamp: Instant,
event: winit::event::DeviceEvent,
) {
::tracing::info!("{:?}: {event:?}", timestamp.elapsed());
}
}
fn main() -> Result<(), Box<dyn Error>> {

View File

@@ -39,22 +39,3 @@ The migration guide could reference other migration examples in the current
changelog entry.
## Unreleased
### Added
- 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.
- Implement `Send` and `Sync` for `OwnedDisplayHandle`.
### Changed
- Updated `windows-sys` to `v0.61`.
- On older macOS versions (tested up to 12.7.6), applications now receive mouse movement events for unfocused windows, matching the behavior on other platforms.
### Fixed
- On Redox, handle `EINTR` when reading from `event_socket` instead of panicking.
- On Wayland, switch from using the `ahash` hashing algorithm to `foldhash`.
- On macOS, fix borderless game presentation options not sticking after switching spaces.
- On macOS, fix IME being locked on (regardless of requests to disable) after being enabled once.

View File

@@ -1,18 +1,3 @@
## 0.30.13
### Added
- On Wayland, add `Window::set_resize_increments`.
### Fixed
- On macOS, fixed crash when dragging non-file content onto window.
- On X11, fix `set_hittest` not working on some window managers.
- On X11, fix debug mode overflow panic in `set_timestamp`.
- On macOS, fix crash in `set_marked_text` when native Pinyin IME sends out-of-bounds `selected_range`.
- On Windows, fix `WM_IME_SETCONTEXT` IME UI flag masking on `lParam`.
- On Android, populate `KeyEvent::text` and `KeyEvent::text_with_all_modifiers` via `Key::to_text()`
## 0.30.12
### Fixed

View File

@@ -249,3 +249,4 @@
- On Web, device events are emitted regardless of cursor type.
- On Wayland, `axis_value120` scroll events now generate `MouseScrollDelta::LineDelta`
- On X11, mouse scroll button events no longer cause duplicated `WindowEvent::MouseWheel` events.
- On macOS, fixed crash when dragging non-file content onto window.

View File

@@ -28,8 +28,3 @@ fn custom_cursor_send() {
needs_send::<winit::cursor::CustomCursorSource>();
needs_send::<winit::cursor::CustomCursor>();
}
#[test]
fn owned_display_handle_send() {
needs_send::<winit::event_loop::OwnedDisplayHandle>();
}

View File

@@ -21,8 +21,3 @@ fn custom_cursor_sync() {
needs_sync::<winit::cursor::CustomCursorSource>();
needs_sync::<winit::cursor::CustomCursor>();
}
#[test]
fn owned_display_handle_sync() {
needs_sync::<winit::event_loop::OwnedDisplayHandle>();
}