Compare commits

..

16 Commits

Author SHA1 Message Date
Mads Marquart
09518309ab Reduce the use of custom cfgs in examples
The `x11_platform`/`wayland_platform` cfgs remain (those are a lot more
involved, so it might still makes sense to use them for clarity).
2026-03-26 06:49:09 +01:00
Mads Marquart
9bf46af6f7 AppKit: Use fn_addr_eq now that it's in MSRV (#4532) 2026-03-26 05:50:16 +01:00
Mads Marquart
464c37a94e Remove leftover apple/appkit/mod.rs (#4533) 2026-03-26 05:49:49 +01:00
Mads Marquart
557d285170 Remove symlinking between winit-appkit and winit-uikit (#4530) 2026-03-26 05:49:12 +01:00
Mads Marquart
ba856e127a Fix CI (#4546)
* Fix cargo-deny erroring on new jni-sys version
* Fix unicode-segmentation bumping MSRV
* Fix cargo-deny finding new script in android-activity
2026-03-26 05:26:54 +01:00
Charlie Tonneslan
0ffd303db6 fix(typo): dependant -> dependent (#4540) 2026-03-24 15:54:31 +01:00
Mads Marquart
5a74bf0aab Android: Add further scancode conversions (#4023)
Firefox' source at:
https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h

Seems to use some other form of scan code? So instead we try to map as
many of the codes available on `Keycode` that they do as possible.
2026-03-19 02:27:23 +01:00
Mads Marquart
117ec364f9 examples: Always use tracing helper module 2026-03-18 22:55:47 +01:00
Mads Marquart
9f789e56ee examples: Use tracing macros instead of println!
This allows the examples to work a bit better in WASM and on iOS.
2026-03-18 22:55:47 +01:00
Mads Marquart
91558169d2 example: Fix tracing registration in pump_events
Tracing subscribers must be set up before `EventLoop::new()`.
2026-03-18 22:55:47 +01:00
Mads Marquart
4998cb990f AppKit: Trace sendEvent: calls
Most events in AppKit go through `sendEvent:`, and they contain a lot of
information, so it's nice to surface this when debugging.

We could override `sendEvent:` in UIKit and track this in there too, but
that's much less important, since there the relevant events are fairly
narrowly scoped, see the link below, other events go through CFRunLoop.
https://developer.apple.com/documentation/uikit/uievent/eventtype
2026-03-18 22:55:47 +01:00
Mads Marquart
98692641c4 UIKit: Add tracing spans 2026-03-18 22:55:47 +01:00
Mads Marquart
a630b5333c Apple: Track spans across queued closures 2026-03-18 22:55:47 +01:00
Mads Marquart
ca7735f10b Apple: Use tracing spans instead of custom trace_scope! macro
Spans are more powerful, and can even optionally be emitted as events by
using `.with_span_events(tracing_subscriber::fmt::format::FmtSpan::*)`.
2026-03-18 22:55:47 +01:00
Mads Marquart
7adb805011 Apple: Trace CFRunLoop activities
Add two run loop observers that:
- Create a TRACE-level span when the run loop enters a new state.
- Drops the span when the run loop exits that state.

These spans attach information to events, such that e.g. resizing a view
produces messages like:
```
TRACE inside runloop{mode=NSEventTrackingRunLoopMode}:timers:
  winit_appkit::util: Triggered `drawRect:` target="winit_appkit::view"
TRACE inside runloop{mode=NSEventTrackingRunLoopMode}:timers:
  winit_appkit::util: Completed `drawRect:` target="winit_appkit::view"
```
2026-03-18 22:55:47 +01:00
Mads Marquart
a8c7d809b9 Use new macOS 15 cursors for resize icons (#4422)
These look slightly different from the old ones. Verified that we now
use the same cursor icons as those used in Safari 26.
2026-03-18 22:33:53 +01:00
49 changed files with 734 additions and 447 deletions

View File

@@ -136,7 +136,7 @@ jobs:
- name: Generate lockfile
# Also updates the crates.io index
run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2
run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2 && cargo update -p unicode-segmentation --precise 1.12.0
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')

View File

@@ -60,7 +60,6 @@ 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.

View File

@@ -1,4 +1,11 @@
# Using allow-invalid because this is platform-specific code
disallowed-macros = [
{ path = "std::print", reason = "works badly on web", replacement = "tracing::info" },
{ path = "std::println", reason = "works badly on web", replacement = "tracing::info" },
{ path = "std::eprint", reason = "works badly on web", replacement = "tracing::error" },
{ path = "std::eprintln", reason = "works badly on web", replacement = "tracing::error" },
{ path = "std::dbg", reason = "leftover debugging aid, remove it or use tracing" },
]
disallowed-methods = [
{ allow-invalid = true, path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ allow-invalid = true, path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },

View File

@@ -43,6 +43,9 @@ skip = [
{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" },
{ crate = "rustix@0.38", reason = "the ecosystem is in the process of migrating" },
{ crate = "linux-raw-sys@0.4", reason = "the ecosystem is in the process of migrating" },
{ crate = "jni-sys@0.3", reason = "uses the semver trick to depend on v0.4, but `ndk` hasn't been updated to v0.4 yet" },
{ crate = "thiserror@1.0", reason = "dep of `ndk` crate, yet to be updated" },
{ crate = "thiserror-impl@1.0", reason = "dep of `thiserror`" },
]
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
@@ -51,9 +54,7 @@ include-archives = true
interpreted = "deny"
[[bans.build.bypass]]
allow = [
{ path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" },
]
allow-globs = ["android-games-sdk/import-games-sdk.sh"]
crate = "android-activity"
[[bans.build.bypass]]

View File

@@ -109,6 +109,7 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::MediaStop => KeyCode::MediaStop,
Keycode::MediaNext => KeyCode::MediaTrackNext,
Keycode::MediaPrevious => KeyCode::MediaTrackPrevious,
Keycode::MediaEject => KeyCode::Eject,
Keycode::Plus => KeyCode::Equal,
Keycode::Minus => KeyCode::Minus,
@@ -131,7 +132,11 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
// These are exactly the same
Keycode::ScrollLock => KeyCode::ScrollLock,
Keycode::Eisu => KeyCode::Lang2,
Keycode::Muhenkan => KeyCode::NonConvert,
Keycode::Henkan => KeyCode::Convert,
Keycode::Yen => KeyCode::IntlYen,
Keycode::Ro => KeyCode::IntlRo,
Keycode::Kana => KeyCode::Lang1,
Keycode::KatakanaHiragana => KeyCode::KanaMode,
@@ -154,6 +159,14 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
Keycode::Wakeup => KeyCode::WakeUp,
Keycode::CapsLock => KeyCode::CapsLock,
Keycode::Help => KeyCode::Help,
Keycode::Back => KeyCode::BrowserBack,
Keycode::Forward => KeyCode::BrowserForward,
Keycode::Refresh => KeyCode::BrowserRefresh,
Keycode::Search => KeyCode::BrowserSearch,
keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())),
})
}

View File

@@ -107,8 +107,7 @@ objc2-foundation = { workspace = true, features = [
"NSThread",
"NSValue",
] }
objc2-quartz-core = { workspace = true, features = ["std", "CABase"] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler"] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }
[dev-dependencies]
winit.workspace = true

View File

@@ -1,14 +1,15 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::mem;
use std::rc::Rc;
use std::{mem, ptr};
use dispatch2::MainThreadBound;
use objc2::runtime::{Imp, Sel};
use objc2::sel;
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::MainThreadMarker;
use tracing::trace_span;
use winit_core::event::{DeviceEvent, ElementState};
use super::app_state::AppState;
@@ -21,6 +22,10 @@ static ORIGINAL: MainThreadBound<Cell<Option<SendEvent>>> = {
};
extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) {
// This can be a bit noisy, since `event` is fairly large. Note that you can use
// `RUST_LOG='trace,winit_appkit::app=warn'` if you're debugging and want TRACE-level logs but
// not this.
let _entered = trace_span!("sendEvent:", ?event).entered();
let mtm = MainThreadMarker::from(app);
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
@@ -75,9 +80,7 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
// If we've already overridden the method, don't do anything.
// FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV.
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
if overridden == method.implementation() {
if ptr::fn_addr_eq(overridden, method.implementation()) {
return;
}
@@ -98,7 +101,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 +113,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 +122,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 +131,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,14 +2,12 @@ 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 objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::NSNotification;
use winit_common::core_foundation::{EventLoopProxy, MainRunLoop};
use winit_common::event_handler::EventHandler;
use winit_core::application::ApplicationHandler;
@@ -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,17 +63,6 @@ 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,
@@ -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,20 +96,9 @@ 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) {
trace_scope!("NSApplicationDidFinishLaunchingNotification");
self.is_launched.set(true);
let app = NSApplication::sharedApplication(self.mtm);
@@ -181,7 +153,6 @@ impl AppState {
}
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationWillTerminateNotification");
let app = NSApplication::sharedApplication(self.mtm);
notify_windows_of_exit(&app);
self.event_handler.terminate();
@@ -274,12 +245,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
@@ -379,12 +345,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

@@ -5,7 +5,10 @@ use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::{AllocAnyThread, ClassType, available, msg_send, sel};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_app_kit::{
NSBitmapImageRep, NSCursor, NSCursorFrameResizeDirections, NSCursorFrameResizePosition,
NSDeviceRGBColorSpace, NSImage,
};
use objc2_foundation::{
NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString, ns_string,
};
@@ -204,23 +207,155 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(),
CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(),
CursorIcon::Crosshair => NSCursor::crosshairCursor(),
CursorIcon::EResize => NSCursor::resizeRightCursor(),
CursorIcon::NResize => NSCursor::resizeUpCursor(),
CursorIcon::WResize => NSCursor::resizeLeftCursor(),
CursorIcon::SResize => NSCursor::resizeDownCursor(),
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
CursorIcon::EResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Right,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeRightCursor()
}
},
CursorIcon::NResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Top,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeUpCursor()
}
},
CursorIcon::WResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Left,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeLeftCursor()
}
},
CursorIcon::SResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Bottom,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeDownCursor()
}
},
CursorIcon::EwResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Right,
NSCursorFrameResizeDirections::All,
)
} else {
NSCursor::resizeLeftRightCursor()
}
},
CursorIcon::NsResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Top,
NSCursorFrameResizeDirections::All,
)
} else {
NSCursor::resizeUpDownCursor()
}
},
CursorIcon::NeResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopRight,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeNorthEastCursor()
}
},
CursorIcon::NwResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopLeft,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeNorthWestCursor()
}
},
CursorIcon::SeResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::BottomRight,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeSouthEastCursor()
}
},
CursorIcon::SwResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::BottomLeft,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeSouthWestCursor()
}
},
CursorIcon::NeswResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopRight,
NSCursorFrameResizeDirections::All,
)
} else {
_windowResizeNorthEastSouthWestCursor()
}
},
CursorIcon::NwseResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopLeft,
NSCursorFrameResizeDirections::All,
)
} else {
_windowResizeNorthWestSouthEastCursor()
}
},
CursorIcon::ColResize => {
if available!(macos = 15.0) {
NSCursor::columnResizeCursor()
} else {
NSCursor::resizeLeftRightCursor()
}
},
CursorIcon::RowResize => {
if available!(macos = 15.0) {
NSCursor::rowResizeCursor()
} else {
NSCursor::resizeUpDownCursor()
}
},
CursorIcon::ZoomIn => {
if available!(macos = 15.0) {
NSCursor::zoomInCursor()
} else {
_zoomInCursor()
}
},
CursorIcon::ZoomOut => {
if available!(macos = 15.0) {
NSCursor::zoomOutCursor()
} else {
_zoomOutCursor()
}
},
CursorIcon::Help => _helpCursor(),
CursorIcon::ZoomIn if available!(macos = 15.0) => NSCursor::zoomInCursor(),
CursorIcon::ZoomIn => _zoomInCursor(),
CursorIcon::ZoomOut if available!(macos = 15.0) => NSCursor::zoomOutCursor(),
CursorIcon::ZoomOut => _zoomOutCursor(),
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
CursorIcon::NwResize => _windowResizeNorthWestCursor(),
CursorIcon::SeResize => _windowResizeSouthEastCursor(),
CursorIcon::SwResize => _windowResizeSouthWestCursor(),
CursorIcon::NeswResize => _windowResizeNorthEastSouthWestCursor(),
CursorIcon::NwseResize => _windowResizeNorthWestSouthEastCursor(),
// This is the wrong semantics for `Wait`, but it's the same as
// what's used in Safari and Chrome.
CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(),

View File

@@ -12,7 +12,9 @@ use objc2_app_kit::{
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopCommonModes};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use rwh_06::HasDisplayHandle;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver};
use tracing::debug_span;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
use winit_common::foundation::create_observer;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, RequestError};
@@ -29,7 +31,6 @@ use super::app_state::AppState;
use super::cursor::CustomCursor;
use super::event::dummy_event;
use super::monitor;
use super::notification_center::create_observer;
use crate::ActivationPolicy;
use crate::window::Window;
@@ -152,6 +153,7 @@ pub struct EventLoop {
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
_before_waiting_observer: MainRunLoopObserver,
_after_waiting_observer: MainRunLoopObserver,
}
@@ -203,6 +205,7 @@ impl EventLoop {
// `applicationDidFinishLaunching:`
unsafe { NSApplicationDidFinishLaunchingNotification },
move |notification| {
let _entered = debug_span!("NSApplicationDidFinishLaunchingNotification").entered();
if let Some(app_state) = weak_app_state.upgrade() {
app_state.did_finish_launching(notification);
}
@@ -215,6 +218,7 @@ impl EventLoop {
// `applicationWillTerminate:`
unsafe { NSApplicationWillTerminateNotification },
move |notification| {
let _entered = debug_span!("NSApplicationWillTerminateNotification").entered();
if let Some(app_state) = weak_app_state.upgrade() {
app_state.will_terminate(notification);
}
@@ -224,14 +228,20 @@ impl EventLoop {
let main_loop = MainRunLoop::get(mtm);
let mode = unsafe { kCFRunLoopCommonModes }.unwrap();
// Tracing observers have the lowest and highest orderings.
let _tracing_observers = tracing_observers(mtm).inspect(|(start, end)| {
main_loop.add_observer(start, mode);
main_loop.add_observer(end, mode);
});
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,
// Queued with the second-lowest priority (tracing observers use the lowest) to ensure
// it is processed after other observers.
CFIndex::MAX - 1,
move |_| app_state_clone.cleared(),
);
main_loop.add_observer(&_before_waiting_observer, mode);
@@ -241,8 +251,9 @@ impl EventLoop {
mtm,
CFRunLoopActivity::AfterWaiting,
true,
// Queued with the highest priority to ensure it is processed before other observers.
CFIndex::MIN,
// Queued with the second-highest priority (tracing observers use the highest) to
// ensure it is processed before other observers.
CFIndex::MIN + 1,
move |_| app_state_clone.wakeup(),
);
main_loop.add_observer(&_after_waiting_observer, mode);
@@ -253,6 +264,7 @@ impl EventLoop {
window_target: ActiveEventLoop { app_state, mtm },
_did_finish_launching_observer,
_will_terminate_observer,
_tracing_observers,
_before_waiting_observer,
_after_waiting_observer,
})

View File

@@ -76,7 +76,6 @@ mod event_loop;
mod ffi;
mod menu;
mod monitor;
mod notification_center;
mod observer;
mod view;
mod window;

View File

@@ -1,37 +1,10 @@
use objc2_core_graphics::CGError;
use tracing::trace;
use winit_core::error::OsError;
macro_rules! os_error {
($error:expr) => {{ winit_core::error::OsError::new(line!(), file!(), $error) }};
}
macro_rules! trace_scope {
($s:literal) => {
let _crate = $crate::util::TraceGuard::new(module_path!(), $s);
};
}
pub(crate) struct TraceGuard {
module_path: &'static str,
called_from_fn: &'static str,
}
impl TraceGuard {
#[inline]
pub(crate) fn new(module_path: &'static str, called_from_fn: &'static str) -> Self {
trace!(target = module_path, "Triggered `{}`", called_from_fn);
Self { module_path, called_from_fn }
}
}
impl Drop for TraceGuard {
#[inline]
fn drop(&mut self) {
trace!(target = self.module_path, "Completed `{}`", self.called_from_fn);
}
}
#[track_caller]
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
if err == CGError::Success { Ok(()) } else { Err(os_error!(format!("CGError {err:?}"))) }

View File

@@ -2,12 +2,11 @@
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
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::{AnyThread, DefinedClass, MainThreadMarker, define_class, msg_send};
use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea,
NSTrackingAreaOptions, NSView, NSWindow,
@@ -17,7 +16,7 @@ use objc2_foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use tracing::warn;
use tracing::{debug_span, trace_span};
use winit_core::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
@@ -155,7 +154,7 @@ define_class!(
// 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");
let _entered = debug_span!("NSViewFrameDidChangeNotification").entered();
// 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
@@ -170,7 +169,7 @@ define_class!(
#[unsafe(method(drawRect:))]
fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:");
let _entered = debug_span!("drawRect:").entered();
self.ivars().app_state.handle_redraw(window_id(&self.window()));
@@ -179,7 +178,7 @@ define_class!(
#[unsafe(method(acceptsFirstResponder))]
fn accepts_first_responder(&self) -> bool {
trace_scope!("acceptsFirstResponder");
let _entered = trace_span!("acceptsFirstResponder").entered();
true
}
@@ -193,13 +192,13 @@ define_class!(
// extension for using `NSTouchBar`
#[unsafe(method_id(touchBar))]
fn touch_bar(&self) -> Option<Retained<NSObject>> {
trace_scope!("touchBar");
let _entered = debug_span!("touchBar").entered();
None
}
#[unsafe(method(resetCursorRects))]
fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects");
let _entered = debug_span!("resetCursorRects").entered();
let bounds = self.bounds();
let cursor_state = self.ivars().cursor_state.borrow();
// We correctly invoke `addCursorRect` only from inside `resetCursorRects`
@@ -214,13 +213,13 @@ define_class!(
unsafe impl NSTextInputClient for WinitView {
#[unsafe(method(hasMarkedText))]
fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText");
let _entered = debug_span!("hasMarkedText").entered();
self.ivars().marked_text.borrow().length() > 0
}
#[unsafe(method(markedRange))]
fn marked_range(&self) -> NSRange {
trace_scope!("markedRange");
let _entered = debug_span!("markedRange").entered();
let length = self.ivars().marked_text.borrow().length();
if length > 0 {
NSRange::new(0, length)
@@ -232,7 +231,7 @@ define_class!(
#[unsafe(method(selectedRange))]
fn selected_range(&self) -> NSRange {
trace_scope!("selectedRange");
let _entered = debug_span!("selectedRange").entered();
// Documented to return `{NSNotFound, 0}` if there is no selection.
NSRange::new(NSNotFound as NSUInteger, 0)
}
@@ -245,7 +244,7 @@ define_class!(
_replacement_range: NSRange,
) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:");
let _entered = debug_span!("setMarkedText:selectedRange:replacementRange:").entered();
let (marked_text, string) = if let Some(string) =
string.downcast_ref::<NSAttributedString>()
@@ -299,7 +298,7 @@ define_class!(
#[unsafe(method(unmarkText))]
fn unmark_text(&self) {
trace_scope!("unmarkText");
let _entered = debug_span!("unmarkText").entered();
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
let input_context = self.inputContext().expect("input context");
@@ -316,7 +315,7 @@ define_class!(
#[unsafe(method_id(validAttributesForMarkedText))]
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
trace_scope!("validAttributesForMarkedText");
let _entered = trace_span!("validAttributesForMarkedText").entered();
NSArray::new()
}
@@ -326,13 +325,14 @@ define_class!(
_range: NSRange,
_actual_range: *mut NSRange,
) -> Option<Retained<NSAttributedString>> {
trace_scope!("attributedSubstringForProposedRange:actualRange:");
let _entered =
trace_span!("attributedSubstringForProposedRange:actualRange:").entered();
None
}
#[unsafe(method(characterIndexForPoint:))]
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
trace_scope!("characterIndexForPoint:");
let _entered = debug_span!("characterIndexForPoint:").entered();
0
}
@@ -342,7 +342,7 @@ define_class!(
_range: NSRange,
_actual_range: *mut NSRange,
) -> NSRect {
trace_scope!("firstRectForCharacterRange:actualRange:");
let _entered = debug_span!("firstRectForCharacterRange:actualRange:").entered();
// Guard when the view is no longer in a window during teardown.
let Some(window) = (**self).window() else {
@@ -358,7 +358,7 @@ define_class!(
#[unsafe(method(insertText:replacementRange:))]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("insertText:replacementRange:");
let _entered = debug_span!("insertText:replacementRange:").entered();
let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() {
string.string().to_string()
@@ -383,7 +383,7 @@ define_class!(
// "human readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[unsafe(method(doCommandBySelector:))]
fn do_command_by_selector(&self, command: Sel) {
trace_scope!("doCommandBySelector:");
let _entered = debug_span!("doCommandBySelector:").entered();
// We shouldn't forward any character from just committed text, since we'll end up
// sending it twice with some IMEs like Korean one. We'll also always send
@@ -422,7 +422,7 @@ define_class!(
impl WinitView {
#[unsafe(method(keyDown:))]
fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:");
let _entered = debug_span!("keyDown:").entered();
{
let mut prev_input_source = self.ivars().input_source.borrow_mut();
let current_input_source = self.current_input_source();
@@ -481,7 +481,7 @@ define_class!(
#[unsafe(method(keyUp:))]
fn key_up(&self, event: &NSEvent) {
trace_scope!("keyUp:");
let _entered = debug_span!("keyUp:").entered();
let event = replace_event(event, self.option_as_alt());
self.update_modifiers(&event, false);
@@ -498,14 +498,14 @@ define_class!(
#[unsafe(method(flagsChanged:))]
fn flags_changed(&self, event: &NSEvent) {
trace_scope!("flagsChanged:");
let _entered = debug_span!("flagsChanged:").entered();
self.update_modifiers(event, true);
}
#[unsafe(method(insertTab:))]
fn insert_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertTab:");
let _entered = debug_span!("insertTab:").entered();
let window = self.window();
if let Some(first_responder) = window.firstResponder() {
if *first_responder == ***self {
@@ -516,7 +516,7 @@ define_class!(
#[unsafe(method(insertBackTab:))]
fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertBackTab:");
let _entered = debug_span!("insertBackTab:").entered();
let window = self.window();
if let Some(first_responder) = window.firstResponder() {
if *first_responder == ***self {
@@ -530,7 +530,7 @@ define_class!(
#[unsafe(method(cancelOperation:))]
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self);
trace_scope!("cancelOperation:");
let _entered = debug_span!("cancelOperation:").entered();
let event = NSApplication::sharedApplication(mtm)
.currentEvent()
@@ -559,71 +559,73 @@ define_class!(
#[unsafe(method(mouseDown:))]
fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:");
let _entered = debug_span!("mouseDown:").entered();
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[unsafe(method(mouseUp:))]
fn mouse_up(&self, event: &NSEvent) {
trace_scope!("mouseUp:");
let _entered = debug_span!("mouseUp:").entered();
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[unsafe(method(rightMouseDown:))]
fn right_mouse_down(&self, event: &NSEvent) {
trace_scope!("rightMouseDown:");
let _entered = debug_span!("rightMouseDown:").entered();
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[unsafe(method(rightMouseUp:))]
fn right_mouse_up(&self, event: &NSEvent) {
trace_scope!("rightMouseUp:");
let _entered = debug_span!("rightMouseUp:").entered();
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[unsafe(method(otherMouseDown:))]
fn other_mouse_down(&self, event: &NSEvent) {
trace_scope!("otherMouseDown:");
let _entered = debug_span!("otherMouseDown:").entered();
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[unsafe(method(otherMouseUp:))]
fn other_mouse_up(&self, event: &NSEvent) {
trace_scope!("otherMouseUp:");
let _entered = debug_span!("otherMouseUp:").entered();
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
// No tracing on these because that would be overly verbose
#[unsafe(method(mouseMoved:))]
fn mouse_moved(&self, event: &NSEvent) {
let _entered = debug_span!("mouseMoved:").entered();
self.mouse_motion(event);
}
#[unsafe(method(mouseDragged:))]
fn mouse_dragged(&self, event: &NSEvent) {
let _entered = debug_span!("mouseDragged:").entered();
self.mouse_motion(event);
}
#[unsafe(method(rightMouseDragged:))]
fn right_mouse_dragged(&self, event: &NSEvent) {
let _entered = debug_span!("rightMouseDragged:").entered();
self.mouse_motion(event);
}
#[unsafe(method(otherMouseDragged:))]
fn other_mouse_dragged(&self, event: &NSEvent) {
let _entered = debug_span!("otherMouseDragged:").entered();
self.mouse_motion(event);
}
#[unsafe(method(mouseEntered:))]
fn mouse_entered(&self, event: &NSEvent) {
trace_scope!("mouseEntered:");
let _entered = debug_span!("mouseEntered:").entered();
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
@@ -637,7 +639,7 @@ define_class!(
#[unsafe(method(mouseExited:))]
fn mouse_exited(&self, event: &NSEvent) {
trace_scope!("mouseExited:");
let _entered = debug_span!("mouseExited:").entered();
let position = self.mouse_view_point(event).to_physical(self.scale_factor());
@@ -651,7 +653,7 @@ define_class!(
#[unsafe(method(scrollWheel:))]
fn scroll_wheel(&self, event: &NSEvent) {
trace_scope!("scrollWheel:");
let _entered = debug_span!("scrollWheel:").entered();
self.mouse_motion(event);
@@ -682,16 +684,15 @@ 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 });
}
#[unsafe(method(magnifyWithEvent:))]
fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:");
let _entered = debug_span!("magnifyWithEvent:").entered();
self.mouse_motion(event);
@@ -713,7 +714,7 @@ define_class!(
#[unsafe(method(smartMagnifyWithEvent:))]
fn smart_magnify_with_event(&self, event: &NSEvent) {
trace_scope!("smartMagnifyWithEvent:");
let _entered = debug_span!("smartMagnifyWithEvent:").entered();
self.mouse_motion(event);
@@ -722,7 +723,7 @@ define_class!(
#[unsafe(method(rotateWithEvent:))]
fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:");
let _entered = debug_span!("rotateWithEvent:").entered();
self.mouse_motion(event);
@@ -744,7 +745,7 @@ define_class!(
#[unsafe(method(pressureChangeWithEvent:))]
fn pressure_change_with_event(&self, event: &NSEvent) {
trace_scope!("pressureChangeWithEvent:");
let _entered = debug_span!("pressureChangeWithEvent:").entered();
self.queue_event(WindowEvent::TouchpadPressure {
device_id: None,
@@ -758,13 +759,13 @@ define_class!(
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
#[unsafe(method(_wantsKeyDownForEvent:))]
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
trace_scope!("_wantsKeyDownForEvent:");
let _entered = debug_span!("_wantsKeyDownForEvent:").entered();
true
}
#[unsafe(method(acceptsFirstMouse:))]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:");
let _entered = debug_span!("acceptsFirstMouse:").entered();
self.ivars().accepts_first_mouse
}
}
@@ -847,16 +848,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);
});
}

View File

@@ -8,6 +8,7 @@ use objc2::rc::{Retained, autoreleasepool};
use objc2::{MainThreadMarker, Message, define_class};
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
use objc2_foundation::NSObject;
use tracing::trace_span;
use winit_core::cursor::Cursor;
use winit_core::error::RequestError;
use winit_core::icon::Icon;
@@ -350,13 +351,13 @@ define_class!(
impl WinitWindow {
#[unsafe(method(canBecomeMainWindow))]
fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow");
let _entered = trace_span!("canBecomeMainWindow").entered();
true
}
#[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
let _entered = trace_span!("canBecomeKeyWindow").entered();
true
}
}
@@ -374,7 +375,7 @@ define_class!(
// it doesn't if window doesn't have NSWindowStyleMask::Titled
#[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
let _entered = trace_span!("canBecomeKeyWindow").entered();
true
}
}

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,
@@ -42,7 +41,7 @@ use objc2_foundation::{
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
NSRect, NSSize, NSString, ns_string,
};
use tracing::{trace, warn};
use tracing::{debug_span, trace, warn};
use winit_common::core_foundation::MainRunLoop;
use winit_core::cursor::Cursor;
use winit_core::error::{NotSupportedError, RequestError};
@@ -122,14 +121,14 @@ define_class!(
unsafe impl NSWindowDelegate for WindowDelegate {
#[unsafe(method(windowShouldClose:))]
fn window_should_close(&self, _: Option<&AnyObject>) -> bool {
trace_scope!("windowShouldClose:");
let _entered = debug_span!("windowShouldClose:").entered();
self.queue_event(WindowEvent::CloseRequested);
false
}
#[unsafe(method(windowWillClose:))]
fn window_will_close(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillClose:");
let _entered = debug_span!("windowWillClose:").entered();
// `setDelegate:` retains the previous value and then autoreleases it
autoreleasepool(|_| {
// Since El Capitan, we need to be careful that delegate methods can't
@@ -141,14 +140,14 @@ define_class!(
#[unsafe(method(windowDidResize:))]
fn window_did_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResize:");
let _entered = debug_span!("windowDidResize:").entered();
// NOTE: WindowEvent::SurfaceResized is reported using NSViewFrameDidChangeNotification.
self.emit_move_event();
}
#[unsafe(method(windowWillStartLiveResize:))]
fn window_will_start_live_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillStartLiveResize:");
let _entered = debug_span!("windowWillStartLiveResize:").entered();
let increments = self.ivars().surface_resize_increments.get();
self.set_resize_increments_inner(increments);
@@ -156,20 +155,20 @@ define_class!(
#[unsafe(method(windowDidEndLiveResize:))]
fn window_did_end_live_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEndLiveResize:");
let _entered = debug_span!("windowDidEndLiveResize:").entered();
self.set_resize_increments_inner(NSSize::new(1., 1.));
}
// This won't be triggered if the move was part of a resize.
#[unsafe(method(windowDidMove:))]
fn window_did_move(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidMove:");
let _entered = debug_span!("windowDidMove:").entered();
self.emit_move_event();
}
#[unsafe(method(windowDidChangeBackingProperties:))]
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeBackingProperties:");
let _entered = debug_span!("windowDidChangeBackingProperties:").entered();
let scale_factor = self.scale_factor();
if scale_factor == self.ivars().previous_scale_factor.get() {
return;
@@ -185,7 +184,7 @@ define_class!(
#[unsafe(method(windowDidBecomeKey:))]
fn window_did_become_key(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidBecomeKey:");
let _entered = debug_span!("windowDidBecomeKey:").entered();
// TODO: center the cursor if the window had mouse grab when it
// lost focus
self.queue_event(WindowEvent::Focused(true));
@@ -193,7 +192,7 @@ define_class!(
#[unsafe(method(windowDidResignKey:))]
fn window_did_resign_key(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResignKey:");
let _entered = debug_span!("windowDidResignKey:").entered();
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
// NSWindowDelegate will receive a didResignKey event despite no event
// being received when the modifiers are released. This is because
@@ -209,7 +208,7 @@ define_class!(
/// Invoked when before enter fullscreen
#[unsafe(method(windowWillEnterFullScreen:))]
fn window_will_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillEnterFullScreen:");
let _entered = debug_span!("windowWillEnterFullScreen:").entered();
self.ivars().maximized.set(self.is_zoomed());
let mut fullscreen = self.ivars().fullscreen.borrow_mut();
@@ -237,7 +236,7 @@ define_class!(
/// Invoked when before exit fullscreen
#[unsafe(method(windowWillExitFullScreen:))]
fn window_will_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowWillExitFullScreen:");
let _entered = debug_span!("windowWillExitFullScreen:").entered();
self.ivars().in_fullscreen_transition.set(true);
}
@@ -248,7 +247,7 @@ define_class!(
_: Option<&AnyObject>,
proposed_options: NSApplicationPresentationOptions,
) -> NSApplicationPresentationOptions {
trace_scope!("window:willUseFullScreenPresentationOptions:");
let _entered = debug_span!("window:willUseFullScreenPresentationOptions:").entered();
// Generally, games will want to disable the menu bar and the dock. Ideally,
// this would be configurable by the user. Unfortunately because of our
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
@@ -271,7 +270,7 @@ define_class!(
/// Invoked when entered fullscreen
#[unsafe(method(windowDidEnterFullScreen:))]
fn window_did_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidEnterFullScreen:");
let _entered = debug_span!("windowDidEnterFullScreen:").entered();
self.ivars().initial_fullscreen.set(false);
self.ivars().in_fullscreen_transition.set(false);
if let Some(target_fullscreen) = self.ivars().target_fullscreen.take() {
@@ -282,7 +281,7 @@ define_class!(
/// Invoked when exited fullscreen
#[unsafe(method(windowDidExitFullScreen:))]
fn window_did_exit_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidExitFullScreen:");
let _entered = debug_span!("windowDidExitFullScreen:").entered();
self.restore_state_from_fullscreen();
self.ivars().in_fullscreen_transition.set(false);
@@ -309,7 +308,7 @@ define_class!(
/// work you may have done to prepare to enter full-screen mode.
#[unsafe(method(windowDidFailToEnterFullScreen:))]
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidFailToEnterFullScreen:");
let _entered = debug_span!("windowDidFailToEnterFullScreen:").entered();
self.ivars().in_fullscreen_transition.set(false);
self.ivars().target_fullscreen.replace(None);
if self.ivars().initial_fullscreen.get() {
@@ -328,7 +327,7 @@ define_class!(
// Invoked when the occlusion state of the window changes
#[unsafe(method(windowDidChangeOcclusionState:))]
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeOcclusionState:");
let _entered = debug_span!("windowDidChangeOcclusionState:").entered();
let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible);
self.queue_event(WindowEvent::Occluded(!visible));
@@ -349,7 +348,7 @@ define_class!(
#[unsafe(method(windowDidChangeScreen:))]
fn window_did_change_screen(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeScreen:");
let _entered = debug_span!("windowDidChangeScreen:").entered();
let is_simple_fullscreen = self.ivars().is_simple_fullscreen.get();
if is_simple_fullscreen {
if let Some(screen) = self.window().screen() {
@@ -363,7 +362,7 @@ define_class!(
/// Invoked when the dragged image enters destination bounds or frame
#[unsafe(method(draggingEntered:))]
fn dragging_entered(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("draggingEntered:");
let _entered = debug_span!("draggingEntered:").entered();
use std::path::PathBuf;
@@ -394,7 +393,7 @@ define_class!(
#[unsafe(method(wantsPeriodicDraggingUpdates))]
fn wants_periodic_dragging_updates(&self) -> bool {
trace_scope!("wantsPeriodicDraggingUpdates:");
let _entered = debug_span!("wantsPeriodicDraggingUpdates:").entered();
true
}
@@ -402,7 +401,7 @@ define_class!(
/// modification of the dragging operation or mouse-pointer position.
#[unsafe(method(draggingUpdated:))]
fn dragging_updated(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("draggingUpdated:");
let _entered = debug_span!("draggingUpdated:").entered();
let dl = sender.draggingLocation();
let dl = self.view().convertPoint_fromView(dl, None);
@@ -417,14 +416,14 @@ define_class!(
/// Invoked when the image is released
#[unsafe(method(prepareForDragOperation:))]
fn prepare_for_drag_operation(&self, _sender: &NSObject) -> bool {
trace_scope!("prepareForDragOperation:");
let _entered = debug_span!("prepareForDragOperation:").entered();
true
}
/// Invoked after the released image has been removed from the screen
#[unsafe(method(performDragOperation:))]
fn perform_drag_operation(&self, sender: &ProtocolObject<dyn NSDraggingInfo>) -> bool {
trace_scope!("performDragOperation:");
let _entered = debug_span!("performDragOperation:").entered();
use std::path::PathBuf;
@@ -456,13 +455,13 @@ define_class!(
/// Invoked when the dragging operation is complete
#[unsafe(method(concludeDragOperation:))]
fn conclude_drag_operation(&self, _sender: Option<&NSObject>) {
trace_scope!("concludeDragOperation:");
let _entered = debug_span!("concludeDragOperation:").entered();
}
/// Invoked when the dragging operation is cancelled
#[unsafe(method(draggingExited:))]
fn dragging_exited(&self, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>) {
trace_scope!("draggingExited:");
let _entered = debug_span!("draggingExited:").entered();
let position = sender.map(|sender| {
let dl = sender.draggingLocation();
@@ -484,7 +483,7 @@ define_class!(
change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
_context: *mut c_void,
) {
trace_scope!("observeValueForKeyPath:ofObject:change:context:");
let _entered = debug_span!("observeValueForKeyPath:ofObject:change:context:").entered();
// NOTE: We don't _really_ need to check the key path, as there should only be one, but
// in the future we might want to observe other key paths.
if key_path == Some(ns_string!("effectiveAppearance")) {
@@ -904,16 +903,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);
});
}

View File

@@ -20,6 +20,9 @@ xkb = ["dep:xkbcommon-dl", "dep:smol_str"]
# CoreFoundation
core-foundation = ["dep:block2", "dep:objc2", "dep:objc2-core-foundation"]
# Foundation
foundation = ["dep:block2", "dep:objc2", "dep:objc2-foundation"]
[dependencies]
smol_str = { workspace = true, optional = true }
tracing.workspace = true
@@ -30,7 +33,7 @@ memmap2 = { workspace = true, optional = true }
x11-dl = { workspace = true, optional = true }
xkbcommon-dl = { workspace = true, optional = true }
# CoreFoundation
# Foundation / CoreFoundation
block2 = { workspace = true, optional = true }
objc2 = { workspace = true, optional = true }
objc2-core-foundation = { workspace = true, optional = true, features = [
@@ -40,6 +43,13 @@ objc2-core-foundation = { workspace = true, optional = true, features = [
"CFRunLoop",
"CFString",
] }
objc2-foundation = { workspace = true, optional = true, features = [
"std",
"block2",
"NSNotification",
"NSString",
"NSOperation",
] }
[package.metadata.docs.rs]
all-features = true

View File

@@ -9,7 +9,7 @@ use std::cell::Cell;
use objc2::MainThreadMarker;
use objc2_core_foundation::{CFRetained, CFRunLoop, CFRunLoopMode, kCFRunLoopDefaultMode};
use tracing::error;
use tracing::{Span, error};
use super::MainRunLoopObserver;
@@ -49,7 +49,8 @@ impl MainRunLoop {
/// 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:]`.
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]` to queue a closure to run
/// the next time runloop sources are processed.
///
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
@@ -73,9 +74,17 @@ impl MainRunLoop {
/// 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) {
// We use this to run a closure asynchronously at a later point, so it also makes sense to
// re-enter the current span when running the queued closure.
let span = Span::current();
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
// Running this block happens inside a `CFRunLoopSource`, but the spans that we emit for
// that are (intentionally) overwritten by entering the span from before.
let _enter = span.enter();
debug_assert!(MainThreadMarker::new().is_some());
if let Some(closure) = closure.take() {
closure()

View File

@@ -6,7 +6,9 @@
mod event_loop_proxy;
mod main_run_loop;
mod main_run_loop_observer;
mod tracing_observers;
pub use self::event_loop_proxy::*;
pub use self::main_run_loop::*;
pub use self::main_run_loop_observer::*;
pub use self::tracing_observers::*;

View File

@@ -0,0 +1,146 @@
use std::cell::RefCell;
use std::rc::Rc;
use objc2::MainThreadMarker;
use objc2_core_foundation::{CFIndex, CFRunLoop, CFRunLoopActivity, kCFRunLoopDefaultMode};
use tracing::span::EnteredSpan;
use tracing::{Level, error, field, span_enabled, trace_span};
use crate::core_foundation::MainRunLoopObserver;
/// Create two run loop observers that add TRACE-level [spans][tracing::span].
///
/// This is useful when debugging run loops, it makes it easier to see in which run loop activity an
/// event is triggered inside (if any).
///
/// When debugging these interactions, it can also be useful to configure your tracing subscriber
/// with `.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE)` to emit events upon
/// entering and exiting these stages.
pub fn tracing_observers(
mtm: MainThreadMarker,
) -> Option<(MainRunLoopObserver, MainRunLoopObserver)> {
// Observers are a bit costly, so don't create them if the tracing-level for this module is
// configured to disable them.
if !span_enabled!(Level::TRACE) {
return None;
}
/// The state that we think the runloop is currently in.
///
/// The order of activities we observe if waiting twice looks something like:
/// - CFRunLoopActivity::Entry
/// - CFRunLoopActivity::BeforeTimers
/// - CFRunLoopActivity::BeforeSources
/// - CFRunLoopActivity::BeforeWaiting
/// - CFRunLoopActivity::AfterWaiting
/// - CFRunLoopActivity::BeforeTimers
/// - CFRunLoopActivity::BeforeSources
/// - CFRunLoopActivity::BeforeWaiting
/// - CFRunLoopActivity::AfterWaiting
/// - CFRunLoopActivity::Exit
///
/// And if not waiting, it looks something like:
/// - CFRunLoopActivity::Entry
/// - CFRunLoopActivity::BeforeTimers
/// - CFRunLoopActivity::BeforeSources
/// - CFRunLoopActivity::Exit
#[derive(Default)]
#[allow(unused)] // EnteredSpans are kept around
enum RunLoopState {
/// Currently processing `Entry`/`Exit` observers.
#[default]
Entered,
/// Currently processing timers or `BeforeTimers` observers.
Timers(EnteredSpan),
/// Currently processing sources or `BeforeSources` observers.
Sources(EnteredSpan),
/// Currently waiting or processing `BeforeWaiting`/`AfterWaiting` observers.
Waiting(EnteredSpan),
}
// A list of currently entered (outer) spans and their state.
//
// This is a list because runloops can be run recursively.
let spans: Rc<RefCell<Vec<(EnteredSpan, RunLoopState)>>> = Rc::new(RefCell::new(Vec::new()));
let spans_clone = Rc::clone(&spans);
// An observer at the start of run loop activities.
let activities = CFRunLoopActivity::Entry
| CFRunLoopActivity::BeforeTimers
| CFRunLoopActivity::BeforeSources
| CFRunLoopActivity::BeforeWaiting;
let start = MainRunLoopObserver::new(mtm, activities, true, CFIndex::MIN, move |activity| {
match activity {
// Add an outer span for each runloop iteration.
CFRunLoopActivity::Entry => {
let span = trace_span!("inside runloop", mode = field::Empty);
// Get the mode dynamically, the observer may added to multiple different modes.
let mode = CFRunLoop::current().unwrap().current_mode().unwrap();
// Mode isn't interesting if it's the default mode.
if &*mode != unsafe { kCFRunLoopDefaultMode }.unwrap() {
span.record("mode", field::display(mode));
}
let entered = span.entered();
spans.borrow_mut().push((entered, RunLoopState::Entered));
},
// Add inner spans that help inspecting the state the runloop is in.
CFRunLoopActivity::BeforeTimers => {
if let Some((_, state)) = spans.borrow_mut().last_mut() {
*state = RunLoopState::Entered; // Drop any previous spans.
*state = RunLoopState::Timers(trace_span!("processing timers").entered());
} else {
error!("unbalanced observer invocations");
}
},
CFRunLoopActivity::BeforeSources => {
if let Some((_, state)) = spans.borrow_mut().last_mut() {
*state = RunLoopState::Entered; // Drop any previous spans.
*state = RunLoopState::Sources(trace_span!("processing sources").entered());
} else {
error!("unbalanced observer invocations");
}
},
CFRunLoopActivity::BeforeWaiting => {
if let Some((_, state)) = spans.borrow_mut().last_mut() {
*state = RunLoopState::Entered; // Drop any previous spans.
*state = RunLoopState::Waiting(trace_span!("waiting").entered());
} else {
error!("unbalanced observer invocations");
}
},
activity => unreachable!("unexpected activity: {activity:?}"),
}
});
// An observer at the end of run loop activities.
let activities = CFRunLoopActivity::AfterWaiting | CFRunLoopActivity::Exit;
let end = MainRunLoopObserver::new(mtm, activities, true, CFIndex::MAX, move |activity| {
match activity {
CFRunLoopActivity::AfterWaiting => {
if let Some((_, state)) = spans_clone.borrow_mut().last_mut() {
// Transition from the waiting state to the initial state.
*state = RunLoopState::Entered;
} else {
error!("unbalanced observer invocations");
}
},
CFRunLoopActivity::Exit => {
if let Some((span, state)) = spans_clone.borrow_mut().pop() {
drop(state); // Explicitly exit and drop inner span.
drop(span); // Explicitly exit and drop outer span.
} else {
error!("unbalanced observer invocations");
}
},
activity => unreachable!("unexpected activity: {activity:?}"),
}
});
Some((start, end))
}

View File

@@ -79,6 +79,10 @@ impl EventHandler {
// Allowed, happens if the handler was cleared manually
// elsewhere (such as in `applicationWillTerminate:`).
},
// We use `eprintln!` here over `tracing::error!`, since we're going to abort
// immediately after this, and it'd be annoying for the user if they didn't get
// any feedback on that if they don't have a tracing subscriber.
#[allow(clippy::disallowed_macros)]
Err(_) => {
// Note: This is not expected to ever happen, this
// module generally controls the `RefCell`, and

View File

@@ -0,0 +1,3 @@
mod notification_center;
pub use self::notification_center::*;

View File

@@ -1,4 +1,3 @@
// NOTE: This is symlinked to be contained in both the AppKit and UIKit implementations.
use std::ptr::NonNull;
use block2::RcBlock;
@@ -12,7 +11,7 @@ use objc2_foundation::{
///
/// This is used in Winit as an alternative to declaring an application delegate, as we want to
/// give the user full control over those.
pub(crate) fn create_observer(
pub fn create_observer(
center: &NSNotificationCenter,
name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static,

View File

@@ -4,5 +4,7 @@
pub mod core_foundation;
#[cfg(feature = "event-handler")]
pub mod event_handler;
#[cfg(feature = "foundation")]
pub mod foundation;
#[cfg(feature = "xkb")]
pub mod xkb;

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

@@ -69,7 +69,7 @@ objc2-ui-kit = { workspace = true, features = [
"UIViewController",
"UIWindow",
] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler"] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }
[package.metadata.docs.rs]
all-features = true

View File

@@ -12,7 +12,9 @@ use objc2_ui_kit::{
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen,
};
use rwh_06::HasDisplayHandle;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver};
use tracing::debug_span;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
use winit_common::foundation::create_observer;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
@@ -24,7 +26,6 @@ use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::{Theme, Window as CoreWindow};
use super::app_state::{AppState, send_occluded_event_for_all_windows};
use super::notification_center::create_observer;
use crate::monitor::MonitorHandle;
use crate::window::Window;
use crate::{app_state, monitor};
@@ -134,6 +135,7 @@ pub struct EventLoop {
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
_wakeup_observer: MainRunLoopObserver,
_main_events_cleared_observer: MainRunLoopObserver,
_events_cleared_observer: MainRunLoopObserver,
@@ -159,6 +161,7 @@ impl EventLoop {
// `application:didFinishLaunchingWithOptions:`
unsafe { UIApplicationDidFinishLaunchingNotification },
move |_| {
let _entered = debug_span!("UIApplicationDidFinishLaunchingNotification").entered();
app_state::did_finish_launching(mtm);
},
);
@@ -166,19 +169,27 @@ impl EventLoop {
&center,
// `applicationDidBecomeActive:`
unsafe { UIApplicationDidBecomeActiveNotification },
move |_| app_state::handle_resumed(mtm),
move |_| {
let _entered = debug_span!("UIApplicationDidBecomeActiveNotification").entered();
app_state::handle_resumed(mtm)
},
);
let _will_resign_active_observer = create_observer(
&center,
// `applicationWillResignActive:`
unsafe { UIApplicationWillResignActiveNotification },
move |_| app_state::handle_suspended(mtm),
move |_| {
let _entered = debug_span!("UIApplicationWillResignActiveNotification").entered();
app_state::handle_suspended(mtm)
},
);
let _will_enter_foreground_observer = create_observer(
&center,
// `applicationWillEnterForeground:`
unsafe { UIApplicationWillEnterForegroundNotification },
move |notification| {
let _entered =
debug_span!("UIApplicationWillEnterForegroundNotification").entered();
let app = notification.object().expect(
"UIApplicationWillEnterForegroundNotification to have application object",
);
@@ -193,6 +204,7 @@ impl EventLoop {
// `applicationDidEnterBackground:`
unsafe { UIApplicationDidEnterBackgroundNotification },
move |notification| {
let _entered = debug_span!("UIApplicationDidEnterBackgroundNotification").entered();
let app = notification.object().expect(
"UIApplicationDidEnterBackgroundNotification to have application object",
);
@@ -207,6 +219,7 @@ impl EventLoop {
// `applicationWillTerminate:`
unsafe { UIApplicationWillTerminateNotification },
move |notification| {
let _entered = debug_span!("UIApplicationWillTerminateNotification").entered();
let app = notification
.object()
.expect("UIApplicationWillTerminateNotification to have application object");
@@ -220,18 +233,29 @@ impl EventLoop {
&center,
// `applicationDidReceiveMemoryWarning:`
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| app_state::handle_memory_warning(mtm),
move |_| {
let _entered =
debug_span!("UIApplicationDidReceiveMemoryWarningNotification").entered();
app_state::handle_memory_warning(mtm)
},
);
let main_loop = MainRunLoop::get(mtm);
let mode = unsafe { kCFRunLoopDefaultMode }.unwrap();
// Tracing observers have the lowest and highest orderings.
let _tracing_observers = tracing_observers(mtm).inspect(|(start, end)| {
main_loop.add_observer(start, mode);
main_loop.add_observer(end, mode);
});
let _wakeup_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::AfterWaiting,
true,
// Queued with the highest priority to ensure it is processed before other observers.
CFIndex::MIN,
// Queued with the second-highest priority (tracing observers use the highest) to
// ensure it is processed before other observers.
CFIndex::MIN + 1,
move |_| app_state::handle_wakeup_transition(mtm),
);
main_loop.add_observer(&_wakeup_observer, mode);
@@ -241,17 +265,17 @@ impl EventLoop {
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
// `CA::Transaction::ensure_implicit` with a priority of `2000000`. 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
// The value of `2000000` 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.
// Also tested to be `2000000` on iPhone 8, iOS 13 beta 4.
0,
move |_| app_state::handle_main_events_cleared(mtm),
);
@@ -261,8 +285,9 @@ impl EventLoop {
mtm,
CFRunLoopActivity::BeforeWaiting,
true,
// Queued with the lowest priority to ensure it is processed after other observers.
CFIndex::MAX,
// Queued with the second-lowest priority (tracing observers use the lowest) to ensure
// it is processed after other observers.
CFIndex::MAX - 1,
move |_| app_state::handle_events_cleared(mtm),
);
main_loop.add_observer(&_events_cleared_observer, mode);
@@ -277,6 +302,7 @@ impl EventLoop {
_did_enter_background_observer,
_will_terminate_observer,
_did_receive_memory_warning_observer,
_tracing_observers,
_wakeup_observer,
_main_events_cleared_observer,
_events_cleared_observer,

View File

@@ -103,7 +103,6 @@
mod app_state;
mod event_loop;
mod monitor;
mod notification_center;
mod view;
mod view_controller;
mod window;

View File

@@ -1 +0,0 @@
../../winit-appkit/src/notification_center.rs

View File

@@ -13,7 +13,7 @@ use objc2_ui_kit::{
UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer, UITextInputTraits, UITouch,
UITouchPhase, UITouchType, UITraitEnvironment, UIView,
};
use tracing::debug;
use tracing::{debug, debug_span, trace_span};
use winit_core::event::{
ButtonSource, ElementState, FingerId, Force, KeyEvent, PointerKind, PointerSource,
TabletToolAngle, TabletToolButton, TabletToolData, TabletToolKind, TouchPhase, WindowEvent,
@@ -48,6 +48,7 @@ define_class!(
impl WinitView {
#[unsafe(method(drawRect:))]
fn draw_rect(&self, rect: CGRect) {
let _entered = debug_span!("drawRect:").entered();
let mtm = MainThreadMarker::new().unwrap();
let window = self.window().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
@@ -59,6 +60,7 @@ define_class!(
#[unsafe(method(layoutSubviews))]
fn layout_subviews(&self) {
let _entered = debug_span!("layoutSubviews").entered();
let mtm = MainThreadMarker::new().unwrap();
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
@@ -79,6 +81,7 @@ define_class!(
#[unsafe(method(setContentScaleFactor:))]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
let _entered = debug_span!("setContentScaleFactor:").entered();
let mtm = MainThreadMarker::new().unwrap();
let _: () =
unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] };
@@ -124,6 +127,7 @@ define_class!(
#[unsafe(method(safeAreaInsetsDidChange))]
fn safe_area_changed(&self) {
let _entered = debug_span!("safeAreaInsetsDidChange").entered();
debug!("safeAreaInsetsDidChange was called, requesting redraw");
// When the safe area changes we want to make sure to emit a redraw event
self.setNeedsDisplay();
@@ -131,26 +135,31 @@ define_class!(
#[unsafe(method(touchesBegan:withEvent:))]
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
let _entered = debug_span!("touchesBegan:withEvent:").entered();
self.handle_touches(touches)
}
#[unsafe(method(touchesMoved:withEvent:))]
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
let _entered = debug_span!("touchesMoved:withEvent:").entered();
self.handle_touches(touches)
}
#[unsafe(method(touchesEnded:withEvent:))]
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
let _entered = debug_span!("touchesEnded:withEvent:").entered();
self.handle_touches(touches)
}
#[unsafe(method(touchesCancelled:withEvent:))]
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
let _entered = debug_span!("touchesCancelled:withEvent:").entered();
self.handle_touches(touches)
}
#[unsafe(method(pinchGesture:))]
fn pinch_gesture(&self, recognizer: &UIPinchGestureRecognizer) {
let _entered = debug_span!("pinchGesture:").entered();
let window = self.window().unwrap();
let (phase, delta) = match recognizer.state() {
@@ -185,6 +194,7 @@ define_class!(
#[unsafe(method(doubleTapGesture:))]
fn double_tap_gesture(&self, recognizer: &UITapGestureRecognizer) {
let _entered = debug_span!("doubleTapGesture:").entered();
let window = self.window().unwrap();
if recognizer.state() == UIGestureRecognizerState::Ended {
@@ -200,6 +210,7 @@ define_class!(
#[unsafe(method(rotationGesture:))]
fn rotation_gesture(&self, recognizer: &UIRotationGestureRecognizer) {
let _entered = debug_span!("rotationGesture:").entered();
let window = self.window().unwrap();
let (phase, delta) = match recognizer.state() {
@@ -244,6 +255,7 @@ define_class!(
#[unsafe(method(panGesture:))]
fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) {
let _entered = debug_span!("panGesture:").entered();
let window = self.window().unwrap();
let translation = recognizer.translationInView(Some(self));
@@ -296,6 +308,7 @@ define_class!(
#[unsafe(method(canBecomeFirstResponder))]
fn can_become_first_responder(&self) -> bool {
let _entered = trace_span!("canBecomeFirstResponder").entered();
true
}
}
@@ -309,6 +322,10 @@ define_class!(
_gesture_recognizer: &UIGestureRecognizer,
_other_gesture_recognizer: &UIGestureRecognizer,
) -> bool {
let _entered = trace_span!(
"gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:"
)
.entered();
true
}
}
@@ -318,16 +335,19 @@ define_class!(
unsafe impl UIKeyInput for WinitView {
#[unsafe(method(hasText))]
fn has_text(&self) -> bool {
let _entered = debug_span!("hasText").entered();
true
}
#[unsafe(method(insertText:))]
fn insert_text(&self, text: &NSString) {
let _entered = debug_span!("insertText:").entered();
self.handle_insert_text(text)
}
#[unsafe(method(deleteBackward))]
fn delete_backward(&self) {
let _entered = debug_span!("deleteBackward").entered();
self.handle_delete_backward()
}
}

View File

@@ -7,6 +7,7 @@ use objc2_ui_kit::{
UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle,
UIUserInterfaceIdiom, UIView, UIViewController,
};
use tracing::trace_span;
use crate::{ScreenEdge, StatusBarStyle, ValidOrientations, WindowAttributesIos};
@@ -28,31 +29,37 @@ define_class!(
impl WinitViewController {
#[unsafe(method(shouldAutorotate))]
fn should_autorotate(&self) -> bool {
let _entered = trace_span!("shouldAutorotate").entered();
true
}
#[unsafe(method(prefersStatusBarHidden))]
fn prefers_status_bar_hidden(&self) -> bool {
let _entered = trace_span!("prefersStatusBarHidden").entered();
self.ivars().prefers_status_bar_hidden.get()
}
#[unsafe(method(preferredStatusBarStyle))]
fn preferred_status_bar_style(&self) -> UIStatusBarStyle {
let _entered = trace_span!("preferredStatusBarStyle").entered();
self.ivars().preferred_status_bar_style.get()
}
#[unsafe(method(prefersHomeIndicatorAutoHidden))]
fn prefers_home_indicator_auto_hidden(&self) -> bool {
let _entered = trace_span!("prefersHomeIndicatorAutoHidden").entered();
self.ivars().prefers_home_indicator_auto_hidden.get()
}
#[unsafe(method(supportedInterfaceOrientations))]
fn supported_orientations(&self) -> UIInterfaceOrientationMask {
let _entered = trace_span!("supportedInterfaceOrientations").entered();
self.ivars().supported_orientations.get()
}
#[unsafe(method(preferredScreenEdgesDeferringSystemGestures))]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
let _entered = trace_span!("preferredScreenEdgesDeferringSystemGestures").entered();
self.ivars().preferred_screen_edges_deferring_system_gestures.get()
}
}

View File

@@ -16,7 +16,7 @@ use objc2_ui_kit::{
UIApplication, UICoordinateSpace, UIEdgeInsets, UIResponder, UIScreen,
UIScreenOverscanCompensation, UIViewController, UIWindow,
};
use tracing::{debug, warn};
use tracing::{debug, debug_span, warn};
use winit_core::cursor::Cursor;
use winit_core::error::{NotSupportedError, RequestError};
use winit_core::event::WindowEvent;
@@ -46,6 +46,7 @@ define_class!(
impl WinitUIWindow {
#[unsafe(method(becomeKeyWindow))]
fn become_key_window(&self) {
let _entered = debug_span!("becomeKeyWindow").entered();
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
window_id: self.id(),
@@ -56,6 +57,7 @@ define_class!(
#[unsafe(method(resignKeyWindow))]
fn resign_key_window(&self) {
let _entered = debug_span!("resignKeyWindow").entered();
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::Window {
window_id: self.id(),

View File

@@ -526,7 +526,7 @@ impl MonitorHandler {
},
};
// Notifying `Future`s is not dependant on the lifetime of the runner,
// Notifying `Future`s is not dependent on the lifetime of the runner,
// because they can outlive it.
if let Some(runner) = runner.upgrade() {
if let Some(details) = details {
@@ -761,7 +761,7 @@ impl MonitorPermissionFuture {
wasm_bindgen_futures::spawn_local(async move {
match future.await {
Ok(details) => {
// Notifying `Future`s is not dependant on the lifetime of the runner, because
// Notifying `Future`s is not dependent on the lifetime of the runner, because
// they can outlive it.
notifier.notify(Ok(()));

View File

@@ -387,11 +387,7 @@ impl LayoutCache {
let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id);
let key = match unicode {
ToUnicodeResult::Str(str) => Key::Character(SmolStr::new(str)),
ToUnicodeResult::Dead(dead_char) => {
// println!("{:?} - {:?} produced dead {:?}", key_code, mod_state,
// dead_char);
Key::Dead(dead_char)
},
ToUnicodeResult::Dead(dead_char) => Key::Dead(dead_char),
ToUnicodeResult::None => {
let has_alt = mod_state.contains(WindowsModifiers::ALT);
let has_ctrl = mod_state.contains(WindowsModifiers::CONTROL);

View File

@@ -66,7 +66,6 @@ impl XConnection {
// All util functions that abstract an async function will return a `Flusher`.
pub fn flush_requests(&self) -> Result<(), XError> {
unsafe { (self.xlib.XFlush)(self.display) };
// println!("XFlush");
// This isn't necessarily a useful time to check for errors (since our request hasn't
// necessarily been processed yet)
self.check_errors()
@@ -74,7 +73,6 @@ impl XConnection {
pub fn sync_with_server(&self) -> Result<(), XError> {
unsafe { (self.xlib.XSync)(self.display, ffi::False) };
// println!("XSync");
self.check_errors()
}
}

View File

@@ -1,7 +1,9 @@
use cfg_aliases::cfg_aliases;
// Only relevant for examples and Winit, our usage of println! is fine here.
#[allow(clippy::disallowed_macros)]
fn main() {
// The script doesn't depend on our code.
// Dummy invocation to enable change-tracking in build scripts.
println!("cargo:rerun-if-changed=build.rs");
// Setup cfg aliases.

View File

@@ -10,7 +10,7 @@ use std::fmt::Debug;
use std::num::NonZeroU32;
use std::sync::Arc;
use std::sync::mpsc::{self, Receiver, Sender};
#[cfg(not(web_platform))]
#[cfg(not(target_family = "wasm"))]
use std::time::Instant;
use std::{fmt, mem};
@@ -18,7 +18,7 @@ use cursor_icon::CursorIcon;
use rwh_06::{DisplayHandle, HasDisplayHandle};
use softbuffer::{Context, Surface};
use tracing::{error, info};
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
use web_time::Instant;
use winit::application::ApplicationHandler;
use winit::cursor::{Cursor, CustomCursor, CustomCursorSource};
@@ -29,13 +29,15 @@ use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::icon::{Icon, RgbaIcon};
use winit::keyboard::{Key, ModifiersState};
use winit::monitor::Fullscreen;
#[cfg(macos_platform)]
#[cfg(all(target_vendor = "apple", not(target_os = "macos")))]
use winit::platform::ios::WindowExtIOS;
#[cfg(target_os = "macos")]
use winit::platform::macos::{OptionAsAlt, WindowAttributesMacOS, WindowExtMacOS};
#[cfg(any(x11_platform, wayland_platform))]
use winit::platform::startup_notify::{self, EventLoopExtStartupNotify, WindowExtStartupNotify};
#[cfg(wayland_platform)]
use winit::platform::wayland::{ActiveEventLoopExtWayland, WindowAttributesWayland};
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
use winit::platform::web::{ActiveEventLoopExtWeb, WindowAttributesWeb};
#[cfg(x11_platform)]
use winit::platform::x11::{ActiveEventLoopExtX11, WindowAttributesX11};
@@ -52,7 +54,7 @@ mod fill;
const BORDER_SIZE: f64 = 20.;
fn main() -> Result<(), Box<dyn Error>> {
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
console_error_panic_hook::set_once();
tracing_init::init();
@@ -61,7 +63,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let (sender, receiver) = mpsc::channel();
// Wire the user event from another thread.
#[cfg(not(web_platform))]
#[cfg(not(target_family = "wasm"))]
{
let event_loop_proxy = event_loop.create_proxy();
let sender = sender.clone();
@@ -143,34 +145,68 @@ impl Application {
#[cfg(x11_platform)]
if event_loop.is_x11() {
window_attributes = window_attributes
.with_platform_attributes(Box::new(window_attributes_x11(event_loop)?));
let mut attrs = WindowAttributesX11::default();
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
info!("Using token {:?} to activate a window", token);
attrs = attrs.with_activation_token(token);
}
match std::env::var("X11_VISUAL_ID") {
Ok(visual_id_str) => {
info!("Using X11 visual id {visual_id_str}");
let visual_id = visual_id_str.parse().expect("invalid X11_VISUAL_ID");
attrs = attrs.with_x11_visual(visual_id);
},
Err(_) => {
info!("Set the X11_VISUAL_ID env var to request specific X11 visual")
},
}
match std::env::var("X11_SCREEN_ID") {
Ok(screen_id_str) => {
info!("Placing the window on X11 screen {screen_id_str}");
let screen_id = screen_id_str.parse().expect("invalid X11_SCREEN_ID");
attrs = attrs.with_x11_screen(screen_id);
},
Err(_) => {
info!("Set the X11_SCREEN_ID env var to place the window on non-default screen")
},
}
window_attributes = window_attributes.with_platform_attributes(Box::new(attrs));
}
#[cfg(wayland_platform)]
if event_loop.is_wayland() {
window_attributes = window_attributes
.with_platform_attributes(Box::new(window_attributes_wayland(event_loop)));
let mut attrs = WindowAttributesWayland::default();
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
info!("Using token {:?} to activate a window", token);
attrs = attrs.with_activation_token(token);
}
window_attributes = window_attributes.with_platform_attributes(Box::new(attrs));
}
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
if let Some(tab_id) = _tab_id {
let window_attributes_macos =
Box::new(WindowAttributesMacOS::default().with_tabbing_identifier(&tab_id));
window_attributes = window_attributes.with_platform_attributes(window_attributes_macos);
let attrs = WindowAttributesMacOS::default().with_tabbing_identifier(&tab_id);
window_attributes = window_attributes.with_platform_attributes(Box::new(attrs));
}
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
{
window_attributes =
window_attributes.with_platform_attributes(Box::new(window_attributes_web()));
let attrs = WindowAttributesWeb::default().with_append(true);
window_attributes = window_attributes.with_platform_attributes(Box::new(attrs));
}
let window = event_loop.create_window(window_attributes)?;
#[cfg(ios_platform)]
#[cfg(all(target_vendor = "apple", not(target_os = "macos")))]
{
use winit::platform::ios::WindowExtIOS;
window.recognize_doubletap_gesture(true);
window.recognize_pinch_gesture(true);
window.recognize_rotation_gesture(true);
@@ -186,7 +222,7 @@ impl Application {
fn handle_action_from_proxy(&mut self, _event_loop: &dyn ActiveEventLoop, action: Action) {
match action {
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
Action::DumpMonitors => self.dump_monitors(_event_loop),
Action::Message => {
info!("User wake up");
@@ -234,11 +270,11 @@ impl Application {
Action::ToggleResizable => window.toggle_resizable(),
Action::ToggleDecorations => window.toggle_decorations(),
Action::ToggleFullscreen => window.toggle_fullscreen(),
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Action::ToggleSimpleFullscreen => {
window.window.set_simple_fullscreen(!window.window.simple_fullscreen());
},
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Action::ToggleBorderlessGame => {
let current = window.window.is_borderless_game();
window.window.set_borderless_game(!current);
@@ -253,13 +289,13 @@ impl Application {
error!("Error creating custom cursor: {err}");
}
},
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
Action::UrlCustomCursor => {
if let Err(err) = window.url_custom_cursor(event_loop) {
error!("Error creating custom cursor from URL: {err}");
}
},
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
Action::AnimationCustomCursor => {
if let Err(err) = self
.custom_cursors
@@ -274,7 +310,7 @@ impl Application {
Action::DragResizeWindow => window.drag_resize_window(),
Action::ShowWindowMenu => window.show_menu(),
Action::PrintHelp => self.print_help(),
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
Action::SetTheme(theme) => {
window.window.set_theme(theme);
@@ -282,7 +318,7 @@ impl Application {
let actual_theme = theme.or_else(|| window.window.theme()).unwrap_or(Theme::Dark);
window.set_draw_theme(actual_theme);
},
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Action::CreateNewTab => {
let tab_id = window.window.tabbing_identifier();
if let Err(err) = self.create_window(event_loop, Some(tab_id)) {
@@ -290,7 +326,7 @@ impl Application {
}
},
Action::RequestResize => window.swap_dimensions(),
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
Action::DumpMonitors => {
let future = event_loop.request_detailed_monitor_permission();
let proxy = event_loop.create_proxy();
@@ -304,7 +340,7 @@ impl Application {
proxy.wake_up();
});
},
#[cfg(not(web_platform))]
#[cfg(not(target_family = "wasm"))]
Action::DumpMonitors => self.dump_monitors(event_loop),
Action::Message => {
self.sender.send(Action::Message).unwrap();
@@ -635,7 +671,7 @@ struct WindowState {
/// The amount of pan of the window.
panned: PhysicalPosition<f32>,
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
option_as_alt: OptionAsAlt,
// Cursor states.
@@ -659,7 +695,7 @@ impl WindowState {
let size = window.surface_size();
let mut state = Self {
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
option_as_alt: window.option_as_alt(),
custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1,
cursor_grab: CursorGrabMode::None,
@@ -758,7 +794,7 @@ impl WindowState {
}
}
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
fn cycle_option_as_alt(&mut self) {
self.option_as_alt = match self.option_as_alt {
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
@@ -804,7 +840,7 @@ impl WindowState {
}
/// Custom cursor from an URL.
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
fn url_custom_cursor(
&mut self,
event_loop: &dyn ActiveEventLoop,
@@ -817,7 +853,7 @@ impl WindowState {
}
/// Custom cursor from a URL.
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
fn animation_custom_cursor(
&mut self,
event_loop: &dyn ActiveEventLoop,
@@ -1003,27 +1039,27 @@ enum Action {
ToggleDecorations,
ToggleResizable,
ToggleFullscreen,
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
ToggleSimpleFullscreen,
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
ToggleBorderlessGame,
ToggleMaximize,
Minimize,
NextCursor,
NextCustomCursor,
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
UrlCustomCursor,
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
AnimationCustomCursor,
CycleCursorGrab,
PrintHelp,
DragWindow,
DragResizeWindow,
ShowWindowMenu,
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
CycleOptionAsAlt,
SetTheme(Option<Theme>),
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
CreateNewTab,
RequestResize,
DumpMonitors,
@@ -1042,35 +1078,35 @@ impl Action {
Action::ToggleDecorations => "Toggle decorations",
Action::ToggleResizable => "Toggle window resizable state",
Action::ToggleFullscreen => "Toggle fullscreen",
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Action::ToggleSimpleFullscreen => "Toggle simple fullscreen",
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Action::ToggleBorderlessGame => "Toggle borderless game mode",
Action::ToggleMaximize => "Maximize",
Action::Minimize => "Minimize",
Action::ToggleResizeIncrements => "Use resize increments when resizing window",
Action::NextCursor => "Advance the cursor to the next value",
Action::NextCustomCursor => "Advance custom cursor to the next value",
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
Action::UrlCustomCursor => "Custom cursor from an URL",
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
Action::AnimationCustomCursor => "Custom cursor from an animation",
Action::CycleCursorGrab => "Cycle through cursor grab mode",
Action::PrintHelp => "Print help",
Action::DragWindow => "Start window drag",
Action::DragResizeWindow => "Start window drag-resize",
Action::ShowWindowMenu => "Show window menu",
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Action::CycleOptionAsAlt => "Cycle option as alt mode",
Action::SetTheme(None) => "Change to the system theme",
Action::SetTheme(Some(Theme::Light)) => "Change to a light theme",
Action::SetTheme(Some(Theme::Dark)) => "Change to a dark theme",
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Action::CreateNewTab => "Create new tab",
Action::RequestResize => "Request a resize",
#[cfg(not(web_platform))]
#[cfg(not(target_family = "wasm"))]
Action::DumpMonitors => "Dump monitor information",
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
Action::DumpMonitors => {
"Request permission to query detailed monitor information and dump monitor \
information"
@@ -1097,7 +1133,7 @@ fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
CustomCursorSource::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
}
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
fn url_custom_cursor() -> CustomCursorSource {
use std::sync::atomic::{AtomicU64, Ordering};
@@ -1162,60 +1198,6 @@ fn mouse_button_to_string(button: MouseButton) -> Cow<'static, str> {
.into()
}
#[cfg(web_platform)]
fn window_attributes_web() -> WindowAttributesWeb {
WindowAttributesWeb::default().with_append(true)
}
#[cfg(wayland_platform)]
fn window_attributes_wayland(event_loop: &dyn ActiveEventLoop) -> WindowAttributesWayland {
let mut window_attributes = WindowAttributesWayland::default();
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
info!("Using token {:?} to activate a window", token);
window_attributes = window_attributes.with_activation_token(token);
}
window_attributes
}
#[cfg(x11_platform)]
fn window_attributes_x11(
event_loop: &dyn ActiveEventLoop,
) -> Result<WindowAttributesX11, Box<dyn Error>> {
let mut window_attributes = WindowAttributesX11::default();
#[cfg(any(x11_platform, wayland_platform))]
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
info!("Using token {:?} to activate a window", token);
window_attributes = window_attributes.with_activation_token(token);
}
match std::env::var("X11_VISUAL_ID") {
Ok(visual_id_str) => {
info!("Using X11 visual id {visual_id_str}");
let visual_id = visual_id_str.parse()?;
window_attributes = window_attributes.with_x11_visual(visual_id);
},
Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"),
}
match std::env::var("X11_SCREEN_ID") {
Ok(screen_id_str) => {
info!("Placing the window on X11 screen {screen_id_str}");
let screen_id = screen_id_str.parse()?;
window_attributes = window_attributes.with_x11_screen(screen_id);
},
Err(_) => {
info!("Set the X11_SCREEN_ID env variable to place the window on non-default screen")
},
}
Ok(window_attributes)
}
/// Cursor list to cycle through.
const CURSORS: &[CursorIcon] = &[
CursorIcon::Default,
@@ -1259,7 +1241,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp),
Binding::new("F", ModifiersState::SHIFT, Action::ToggleAnimatedFillColor),
Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen),
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Binding::new("F", ModifiersState::ALT, Action::ToggleSimpleFullscreen),
Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations),
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
@@ -1276,13 +1258,13 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
// C.
Binding::new("C", ModifiersState::CONTROL, Action::NextCursor),
Binding::new("C", ModifiersState::ALT, Action::NextCustomCursor),
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
Binding::new(
"C",
ModifiersState::CONTROL.union(ModifiersState::SHIFT),
Action::UrlCustomCursor,
),
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
Binding::new(
"C",
ModifiersState::ALT.union(ModifiersState::SHIFT),
@@ -1293,11 +1275,11 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)),
Binding::new("K", ModifiersState::META, Action::SetTheme(Some(Theme::Light))),
Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))),
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Binding::new("T", ModifiersState::META, Action::CreateNewTab),
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
#[cfg(macos_platform)]
#[cfg(target_os = "macos")]
Binding::new("B", ModifiersState::CONTROL, Action::ToggleBorderlessGame),
Binding::new("S", ModifiersState::ALT, Action::EmitSurfaceSize),
Binding::new("S", ModifiersState::CONTROL, Action::Message),

View File

@@ -1,8 +1,9 @@
#[cfg(any(x11_platform, macos_platform, windows_platform))]
#[cfg(any(x11_platform, target_os = "macos", target_os = "windows"))]
#[allow(deprecated)]
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use tracing::info;
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize, Position};
use winit::event::{ElementState, KeyEvent, WindowEvent};
@@ -38,7 +39,7 @@ fn main() -> Result<(), impl std::error::Error> {
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
info!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), WindowData::new(window, 0xffbbbbbb));
@@ -56,12 +57,12 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.exit();
},
WindowEvent::PointerEntered { device_id: _, .. } => {
// On x11, println when the cursor entered in a window even if the child window
// On x11, log when the cursor entered in a window even if the child window
// is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the
// parent window, so we also can see this log when we move
// the cursor around (200, 200) in parent window.
println!("cursor entered in the window {window_id:?}");
info!("cursor entered in the window {window_id:?}");
},
WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. },
@@ -75,7 +76,7 @@ fn main() -> Result<(), impl std::error::Error> {
let child_window =
spawn_child_window(parent_window.window.as_ref(), event_loop, child_index);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
info!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, WindowData::new(child_window, child_color));
},
WindowEvent::RedrawRequested => {
@@ -120,7 +121,7 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.run_app(Application::default())
}
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))]
#[cfg(not(any(x11_platform, target_os = "macos", target_os = "windows")))]
fn main() {
panic!(
"This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature \

View File

@@ -1,12 +1,13 @@
#![allow(clippy::single_match)]
use std::thread;
#[cfg(not(web_platform))]
use std::time;
use std::time::Duration;
#[cfg(not(target_family = "wasm"))]
use std::time::Instant;
use ::tracing::{info, warn};
#[cfg(web_platform)]
use web_time as time;
use tracing::{info, warn};
#[cfg(target_family = "wasm")]
use web_time::Instant;
use winit::application::ApplicationHandler;
use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
@@ -18,8 +19,8 @@ mod fill;
#[path = "util/tracing.rs"]
mod tracing;
const WAIT_TIME: time::Duration = time::Duration::from_millis(100);
const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100);
const WAIT_TIME: Duration = Duration::from_millis(100);
const POLL_SLEEP_TIME: Duration = Duration::from_millis(100);
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
@@ -30,7 +31,7 @@ enum Mode {
}
fn main() -> Result<(), impl std::error::Error> {
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
console_error_panic_hook::set_once();
tracing::init();
@@ -129,8 +130,7 @@ impl ApplicationHandler for ControlFlowDemo {
Mode::Wait => event_loop.set_control_flow(ControlFlow::Wait),
Mode::WaitUntil => {
if !self.wait_cancelled {
event_loop
.set_control_flow(ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME));
event_loop.set_control_flow(ControlFlow::WaitUntil(Instant::now() + WAIT_TIME));
}
},
Mode::Poll => {

View File

@@ -1,5 +1,6 @@
use std::error::Error;
use tracing::info;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
@@ -49,7 +50,7 @@ impl ApplicationHandler for Application {
| WindowEvent::DragEntered { .. }
| WindowEvent::DragMoved { .. }
| WindowEvent::DragDropped { .. } => {
println!("{event:?}");
info!("{event:?}");
},
WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap();

View File

@@ -14,7 +14,7 @@ use winit::application::ApplicationHandler;
use winit::event::{Ime, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState, NamedKey};
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
use winit::platform::web::WindowAttributesWeb;
use winit::window::{
ImeCapabilities, ImeEnableRequest, ImeHint, ImePurpose, ImeRequest, ImeRequestData,
@@ -68,15 +68,15 @@ impl TextInputState {
impl ApplicationHandler for App {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
#[cfg(not(web_platform))]
#[cfg(not(target_family = "wasm"))]
let window_attributes = WindowAttributes::default();
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
let window_attributes = WindowAttributes::default()
.with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true)));
self.window = match event_loop.create_window(window_attributes) {
Ok(window) => Some(window),
Err(err) => {
eprintln!("error creating window: {err}");
error!("error creating window: {err}");
event_loop.exit();
return;
},
@@ -339,14 +339,14 @@ fn preedit_with_cursor_checked(text: &str, cursor: usize, anchor: usize) -> Resu
}
fn main() -> Result<(), Box<dyn Error>> {
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
console_error_panic_hook::set_once();
tracing_init::init();
let event_loop = EventLoop::new()?;
println!(
info!(
r#"This showcases the use of an input method engine (IME) by emulating a text edit field.
Use CTRL+i to toggle IME support.
Use CTRL+p to cycle content purpose values.

View File

@@ -1,12 +1,19 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, android_platform,))]
#[cfg(any(
target_os = "windows",
target_os = "macos",
x11_platform,
wayland_platform,
target_os = "android",
))]
fn main() -> std::process::ExitCode {
use std::process::ExitCode;
use std::thread::sleep;
use std::time::Duration;
use tracing::info;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::pump_events::{EventLoopExtPumpEvents, PumpStatus};
@@ -15,6 +22,8 @@ fn main() -> std::process::ExitCode {
#[path = "util/fill.rs"]
mod fill;
#[path = "util/tracing.rs"]
mod tracing;
#[derive(Default, Debug)]
struct PumpDemo {
@@ -33,7 +42,7 @@ fn main() -> std::process::ExitCode {
_window_id: WindowId,
event: WindowEvent,
) {
println!("{event:?}");
info!("{event:?}");
let window = match self.window.as_ref() {
Some(window) => window,
@@ -51,9 +60,9 @@ fn main() -> std::process::ExitCode {
}
}
let mut event_loop = EventLoop::new().unwrap();
tracing::init();
tracing_subscriber::fmt::init();
let mut event_loop = EventLoop::new().unwrap();
let mut app = PumpDemo::default();
@@ -69,12 +78,18 @@ fn main() -> std::process::ExitCode {
//
// Since `pump_events` doesn't block it will be important to
// throttle the loop in the app somehow.
println!("Update()");
info!("Update()");
sleep(Duration::from_millis(16));
}
}
#[cfg(any(ios_platform, web_platform, orbital_platform))]
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
x11_platform,
wayland_platform,
target_os = "android",
)))]
fn main() {
println!("This platform doesn't support pump_events.");
panic!("This platform doesn't support pump_events.")
}

View File

@@ -1,10 +1,17 @@
#![allow(clippy::single_match)]
// Limit this example to only compatible platforms.
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, orbital_platform))]
#[cfg(any(
target_os = "windows",
target_os = "macos",
x11_platform,
wayland_platform,
target_os = "redox"
))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::time::Duration;
use tracing::info;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand;
@@ -13,6 +20,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[path = "util/fill.rs"]
mod fill;
#[path = "util/tracing.rs"]
mod tracing;
#[derive(Default, Debug)]
struct App {
@@ -44,10 +53,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
event: WindowEvent,
) {
if event == WindowEvent::Destroyed && self.window_id == Some(window_id) {
println!(
"--------------------------------------------------------- Window {} Destroyed",
self.idx
);
info!("Window {} Destroyed", self.idx);
self.window_id = None;
event_loop.exit();
return;
@@ -60,11 +66,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
match event {
WindowEvent::CloseRequested => {
println!(
"--------------------------------------------------------- Window {} \
CloseRequested",
self.idx
);
info!("Window {} CloseRequested", self.idx);
fill::cleanup_window(window.as_ref());
self.window = None;
},
@@ -76,30 +78,30 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
tracing_subscriber::fmt::init();
tracing::init();
let mut event_loop = EventLoop::new().unwrap();
let mut app = App { idx: 1, ..Default::default() };
event_loop.run_app_on_demand(&mut app)?;
println!("--------------------------------------------------------- Finished first loop");
println!("--------------------------------------------------------- Waiting 5 seconds");
info!("Finished first loop");
info!("Waiting 5 seconds");
std::thread::sleep(Duration::from_secs(5));
app.idx += 1;
event_loop.run_app_on_demand(&mut app)?;
println!("--------------------------------------------------------- Finished second loop");
info!("Finished second loop");
Ok(())
}
#[cfg(not(any(
windows_platform,
macos_platform,
target_os = "windows",
target_os = "macos",
x11_platform,
wayland_platform,
orbital_platform
target_os = "redox"
)))]
fn main() {
println!("This example is not supported on this platform");
panic!("This example is not supported on this platform")
}

View File

@@ -12,11 +12,11 @@ use std::collections::HashMap;
use std::mem;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
#[cfg(not(web_platform))]
#[cfg(not(target_family = "wasm"))]
use std::time::Instant;
use softbuffer::{Context, Surface};
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
use web_time::Instant;
use winit::window::{Window, WindowId};

View File

@@ -1,4 +1,4 @@
#[cfg(not(web_platform))]
#[cfg(not(target_family = "wasm"))]
pub fn init() {
use tracing_subscriber::filter::{EnvFilter, LevelFilter};
@@ -9,7 +9,7 @@ pub fn init() {
.init();
}
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
pub fn init() {
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
@@ -23,3 +23,6 @@ pub fn init() {
)
.init();
}
#[allow(unused_imports)]
pub use ::tracing::*;

View File

@@ -1,13 +1,11 @@
//! Simple winit window example.
use std::error::Error;
use std::time::Instant;
use tracing::{error, info};
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
#[cfg(web_platform)]
use winit::platform::web::WindowAttributesWeb;
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
@@ -22,32 +20,26 @@ struct App {
impl ApplicationHandler for App {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
#[cfg(not(web_platform))]
let window_attributes = WindowAttributes::default();
#[cfg(web_platform)]
let window_attributes = WindowAttributes::default()
.with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true)));
#[cfg(target_family = "wasm")]
let window_attributes = window_attributes.with_platform_attributes(Box::new(
winit::platform::web::WindowAttributesWeb::default().with_append(true),
));
self.window = match event_loop.create_window(window_attributes) {
Ok(window) => Some(window),
Err(err) => {
eprintln!("error creating window: {err}");
error!("error creating window: {err}");
event_loop.exit();
return;
},
}
}
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) {
info!("{event:?}");
match event {
WindowEvent::CloseRequested => {
println!("Close was requested; stopping");
info!("Close was requested; stopping");
event_loop.exit();
},
WindowEvent::SurfaceResized(_) => {
@@ -69,25 +61,15 @@ 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>> {
#[cfg(web_platform)]
#[cfg(target_family = "wasm")]
console_error_panic_hook::set_once();
tracing::init();

View File

@@ -11,6 +11,8 @@ fn main() -> Result<(), Box<dyn Error>> {
#[path = "util/fill.rs"]
mod fill;
#[path = "util/tracing.rs"]
mod tracing;
#[derive(Debug)]
pub struct XEmbedDemo {
@@ -58,7 +60,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.ok_or("Expected a 32-bit X11 window ID as the first argument.")?
.parse::<u32>()?;
tracing_subscriber::fmt::init();
tracing::init();
let event_loop = EventLoop::new()?;
Ok(event_loop.run_app(XEmbedDemo { parent_window_id, window: None })?)
@@ -66,6 +68,5 @@ fn main() -> Result<(), Box<dyn Error>> {
#[cfg(not(x11_platform))]
fn main() -> Result<(), Box<dyn Error>> {
println!("This example is only supported on X11 platforms.");
Ok(())
panic!("This example is only supported on X11 platforms.")
}

View File

@@ -46,6 +46,8 @@ changelog entry.
- 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`.
- Use new macOS 15 cursors for resize icons.
- On Android, added scancode conversions for more obscure key codes.
### Changed

View File

@@ -29,7 +29,7 @@ use crate::platform_impl;
///
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
///
/// Note that this cannot be shared across threads (due to platform-dependant logic
/// Note that this cannot be shared across threads (due to platform-dependent logic
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access,
/// the [`Window`] created from this _can_ be sent to an other thread, and the
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
@@ -88,7 +88,7 @@ impl EventLoopBuilder {
)]
#[inline]
pub fn build(&mut self) -> Result<EventLoop, EventLoopError> {
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
let _entered = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
// Certain platforms accept a mutable reference in their API.
#[allow(clippy::unnecessary_mut_passed)]
@@ -262,7 +262,7 @@ impl EventLoop {
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn listen_device_events(&self, allowed: DeviceEvents) {
let _span = tracing::debug_span!(
let _entered = tracing::debug_span!(
"winit::EventLoop::listen_device_events",
allowed = ?allowed
)