Compare commits

..

1 Commits

Author SHA1 Message Date
Mads Marquart
99f7bb6b44 Add timestamps to events 2026-03-18 18:18:25 +01:00
64 changed files with 705 additions and 1295 deletions

View File

@@ -136,7 +136,7 @@ jobs:
- name: Generate lockfile - name: Generate lockfile
# Also updates the crates.io index # Also updates the crates.io index
run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2 && cargo update -p unicode-segmentation --precise 1.12.0 && cargo update -p wayland-protocols --precise 0.32.12 run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2
- name: Install GCC Multilib - name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
@@ -290,14 +290,31 @@ jobs:
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }} key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
cargo-deny: cargo-deny:
name: Run cargo-deny name: Run cargo-deny on ${{ matrix.platform.name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
strategy:
fail-fast: false
matrix:
platform:
- { name: 'Android', target: aarch64-linux-android }
- { name: 'iOS', target: aarch64-apple-ios }
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
- { name: 'macOS', target: aarch64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'Web', target: wasm32-unknown-unknown }
- { name: 'Windows GNU', target: x86_64-pc-windows-gnu }
- { name: 'Windows MSVC', target: x86_64-pc-windows-msvc }
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: taiki-e/checkout-action@v1
- uses: EmbarkStudios/cargo-deny-action@v2 - uses: EmbarkStudios/cargo-deny-action@v2
with: with:
command: check
log-level: error log-level: error
manifest-path: winit/Cargo.toml
arguments: --all-features --target ${{ matrix.platform.target }}
eslint: eslint:
name: ESLint name: ESLint

View File

@@ -32,7 +32,7 @@ jobs:
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=serde,mint,android-native-activity cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=serde,mint,android-native-activity
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v6 uses: actions/configure-pages@v5
- name: Fix permissions - name: Fix permissions
run: | run: |
@@ -47,4 +47,4 @@ jobs:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v5 uses: actions/deploy-pages@v4

View File

@@ -60,6 +60,7 @@ objc2-core-foundation = { version = "0.3.2", default-features = false }
objc2-core-graphics = { 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-core-video = { version = "0.3.2", default-features = false }
objc2-foundation = { 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 } objc2-ui-kit = { version = "0.3.2", default-features = false }
# Windows dependencies. # Windows dependencies.

View File

@@ -1,11 +1,4 @@
# Using allow-invalid because this is platform-specific code # 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 = [ 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::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" }, { 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

@@ -1,25 +1,27 @@
# https://embarkstudios.github.io/cargo-deny # https://embarkstudios.github.io/cargo-deny
# cargo install cargo-deny # cargo install cargo-deny
# cargo update && cargo deny check # cargo update && cargo deny --target aarch64-apple-ios check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
[graph] [graph]
all-features = true all-features = true
exclude-dev = true exclude-dev = true
targets = [ targets = [
"aarch64-apple-darwin", { triple = "aarch64-apple-darwin" },
"aarch64-apple-ios", { triple = "aarch64-apple-ios" },
"aarch64-linux-android", { triple = "aarch64-linux-android" },
"i686-pc-windows-gnu", { triple = "i686-pc-windows-gnu" },
"i686-pc-windows-msvc", { triple = "i686-pc-windows-msvc" },
"i686-unknown-linux-gnu", { triple = "i686-unknown-linux-gnu" },
{ triple = "wasm32-unknown-unknown", features = [ { triple = "wasm32-unknown-unknown", features = [
"atomics", "atomics",
] }, ] },
"x86_64-apple-darwin", { triple = "x86_64-apple-darwin" },
"x86_64-apple-ios", { triple = "x86_64-apple-ios" },
"x86_64-pc-windows-gnu", { triple = "x86_64-pc-windows-gnu" },
"x86_64-pc-windows-msvc", { triple = "x86_64-pc-windows-msvc" },
"x86_64-unknown-linux-gnu", { triple = "x86_64-unknown-linux-gnu" },
"x86_64-unknown-redox", { triple = "x86_64-unknown-redox" },
] ]
[licenses] [licenses]
@@ -31,7 +33,6 @@ allow = [
"MIT", # https://tldrlegal.com/license/mit-license "MIT", # https://tldrlegal.com/license/mit-license
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html "Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
"Zlib", # https://spdx.org/licenses/Zlib.html "Zlib", # https://spdx.org/licenses/Zlib.html
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/
] ]
confidence-threshold = 1.0 confidence-threshold = 1.0
private = { ignore = true } private = { ignore = true }
@@ -39,23 +40,46 @@ private = { ignore = true }
[bans] [bans]
multiple-versions = "deny" multiple-versions = "deny"
skip = [ skip = [
{ 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 = "bitflags@1", reason = "the ecosystem is in the process of migrating" },
{ crate = "thiserror@1.0", reason = "dep of `ndk` crate, yet to be updated" }, { crate = "rustix@0.38", reason = "the ecosystem is in the process of migrating" },
{ crate = "thiserror-impl@1.0", reason = "dep of `thiserror`" }, { crate = "linux-raw-sys@0.4", reason = "the ecosystem is in the process of migrating" },
{ crate = "objc2@0.5", reason = "used by crossfont" },
{ crate = "objc2-foundation@0.2", reason = "used by crossfont" },
]
skip-tree = [
{ crate = "windows-sys", reason = "foundational but bumps fairly often, nothing we can do about it not having a shared version" },
] ]
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
[bans.build] [bans.build]
bypass = [
{ crate = "android-activity", allow-globs = ["android-games-sdk/import-games-sdk.sh"] },
{ crate = "freetype-sys", allow-globs = ["freetype2/*"] },
# `crossfont` still depends (partially transitively) on `winapi`.
{ crate = "winapi-i686-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
{ crate = "winapi-x86_64-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
]
include-archives = true include-archives = true
interpreted = "deny" interpreted = "deny"
[[bans.build.bypass]]
allow = [
{ path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" },
]
crate = "android-activity"
[[bans.build.bypass]]
allow-globs = ["ci/*", "githooks/*"]
crate = "zerocopy"
[[bans.build.bypass]]
allow-globs = ["cherry-pick-stable.sh"]
crate = "libc"
[[bans.build.bypass]]
allow-globs = ["freetype2/*"]
crate = "freetype-sys"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_i686_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_i686_msvc"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_x86_64_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_x86_64_msvc"

View File

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

View File

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

View File

@@ -1,15 +1,14 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::Cell; use std::cell::Cell;
use std::mem;
use std::rc::Rc; use std::rc::Rc;
use std::{mem, ptr};
use dispatch2::MainThreadBound; use dispatch2::MainThreadBound;
use objc2::runtime::{Imp, Sel}; use objc2::runtime::{Imp, Sel};
use objc2::sel; use objc2::sel;
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType}; use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::MainThreadMarker; use objc2_foundation::MainThreadMarker;
use tracing::trace_span;
use winit_core::event::{DeviceEvent, ElementState}; use winit_core::event::{DeviceEvent, ElementState};
use super::app_state::AppState; use super::app_state::AppState;
@@ -22,10 +21,6 @@ static ORIGINAL: MainThreadBound<Cell<Option<SendEvent>>> = {
}; };
extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) { 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); let mtm = MainThreadMarker::from(app);
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key. // Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
@@ -80,7 +75,9 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) }; let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
// If we've already overridden the method, don't do anything. // If we've already overridden the method, don't do anything.
if ptr::fn_addr_eq(overridden, method.implementation()) { // 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() {
return; return;
} }
@@ -101,6 +98,7 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
} }
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) { fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
let time = app_state.event_time(event);
let event_type = event.r#type(); let event_type = event.r#type();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match event_type { match event_type {
@@ -113,7 +111,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
if delta_x != 0.0 || delta_y != 0.0 { if delta_x != 0.0 || delta_y != 0.0 {
app_state.maybe_queue_with_handler(move |app, event_loop| { app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::PointerMotion { app.device_event(event_loop, None, time, DeviceEvent::PointerMotion {
delta: (delta_x, delta_y), delta: (delta_x, delta_y),
}); });
}); });
@@ -122,7 +120,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => { NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
let button = event.buttonNumber() as u32; let button = event.buttonNumber() as u32;
app_state.maybe_queue_with_handler(move |app, event_loop| { app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::Button { app.device_event(event_loop, None, time, DeviceEvent::Button {
button, button,
state: ElementState::Pressed, state: ElementState::Pressed,
}); });
@@ -131,7 +129,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => { NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
let button = event.buttonNumber() as u32; let button = event.buttonNumber() as u32;
app_state.maybe_queue_with_handler(move |app, event_loop| { app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::Button { app.device_event(event_loop, None, time, DeviceEvent::Button {
button, button,
state: ElementState::Released, state: ElementState::Released,
}); });

View File

@@ -2,12 +2,14 @@ use std::cell::{Cell, OnceCell, RefCell};
use std::mem; use std::mem;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::{Duration, Instant};
use dispatch2::MainThreadBound; use dispatch2::MainThreadBound;
use objc2::MainThreadMarker; use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSEvent, NSRunningApplication};
use objc2_foundation::NSNotification; use objc2_foundation::{NSNotification, NSTimeInterval};
use objc2_quartz_core::CACurrentMediaTime;
use tracing::warn;
use winit_common::core_foundation::{EventLoopProxy, MainRunLoop}; use winit_common::core_foundation::{EventLoopProxy, MainRunLoop};
use winit_common::event_handler::EventHandler; use winit_common::event_handler::EventHandler;
use winit_core::application::ApplicationHandler; use winit_core::application::ApplicationHandler;
@@ -43,6 +45,8 @@ pub(super) struct AppState {
start_time: Cell<Option<Instant>>, start_time: Cell<Option<Instant>>,
wait_timeout: Cell<Option<Instant>>, wait_timeout: Cell<Option<Instant>>,
pending_redraw: RefCell<Vec<WindowId>>, pending_redraw: RefCell<Vec<WindowId>>,
startup_instant: Instant,
startup_timestamp: NSTimeInterval,
// NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and // 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. // as such should be careful to not add fields that, in turn, strongly reference those.
} }
@@ -63,6 +67,17 @@ impl AppState {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop)); 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 { let this = Rc::new(Self {
mtm, mtm,
activation_policy, activation_policy,
@@ -83,6 +98,8 @@ impl AppState {
start_time: Cell::new(None), start_time: Cell::new(None),
wait_timeout: Cell::new(None), wait_timeout: Cell::new(None),
pending_redraw: RefCell::new(vec![]), pending_redraw: RefCell::new(vec![]),
startup_instant,
startup_timestamp,
}); });
GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this)) GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this))
@@ -96,9 +113,20 @@ impl AppState {
.clone() .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, // NOTE: This notification will, globally, only be emitted once,
// no matter how many `EventLoop`s the user creates. // no matter how many `EventLoop`s the user creates.
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) { pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationDidFinishLaunchingNotification");
self.is_launched.set(true); self.is_launched.set(true);
let app = NSApplication::sharedApplication(self.mtm); let app = NSApplication::sharedApplication(self.mtm);
@@ -153,6 +181,7 @@ impl AppState {
} }
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) { pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationWillTerminateNotification");
let app = NSApplication::sharedApplication(self.mtm); let app = NSApplication::sharedApplication(self.mtm);
notify_windows_of_exit(&app); notify_windows_of_exit(&app);
self.event_handler.terminate(); self.event_handler.terminate();
@@ -245,7 +274,12 @@ impl AppState {
// -> Don't go back into the event handler when our callstack originates from there // -> Don't go back into the event handler when our callstack originates from there
if !self.event_handler.in_use() { if !self.event_handler.in_use() {
self.with_handler(|app, event_loop| { self.with_handler(|app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested); app.window_event(
event_loop,
window_id,
Instant::now(),
WindowEvent::RedrawRequested,
);
}); });
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested // `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
@@ -345,7 +379,12 @@ impl AppState {
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut()); let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw { for window_id in redraw {
self.with_handler(|app, event_loop| { self.with_handler(|app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested); app.window_event(
event_loop,
window_id,
Instant::now(),
WindowEvent::RedrawRequested,
);
}); });
} }
self.with_handler(|app, event_loop| { self.with_handler(|app, event_loop| {

View File

@@ -5,10 +5,7 @@ use std::sync::OnceLock;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::{AllocAnyThread, ClassType, available, msg_send, sel}; use objc2::{AllocAnyThread, ClassType, available, msg_send, sel};
use objc2_app_kit::{ use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
NSBitmapImageRep, NSCursor, NSCursorFrameResizeDirections, NSCursorFrameResizePosition,
NSDeviceRGBColorSpace, NSImage,
};
use objc2_foundation::{ use objc2_foundation::{
NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString, ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString, ns_string,
}; };
@@ -207,155 +204,23 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(), CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(),
CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(), CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(),
CursorIcon::Crosshair => NSCursor::crosshairCursor(), CursorIcon::Crosshair => NSCursor::crosshairCursor(),
CursorIcon::EResize => { CursorIcon::EResize => NSCursor::resizeRightCursor(),
if available!(macos = 15.0) { CursorIcon::NResize => NSCursor::resizeUpCursor(),
NSCursor::frameResizeCursorFromPosition_inDirections( CursorIcon::WResize => NSCursor::resizeLeftCursor(),
NSCursorFrameResizePosition::Right, CursorIcon::SResize => NSCursor::resizeDownCursor(),
NSCursorFrameResizeDirections::Outward, CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
) CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
} 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::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 // This is the wrong semantics for `Wait`, but it's the same as
// what's used in Safari and Chrome. // what's used in Safari and Chrome.
CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(), CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(),

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,37 @@
use objc2_core_graphics::CGError; use objc2_core_graphics::CGError;
use tracing::trace;
use winit_core::error::OsError; use winit_core::error::OsError;
macro_rules! os_error { macro_rules! os_error {
($error:expr) => {{ winit_core::error::OsError::new(line!(), file!(), $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] #[track_caller]
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> { pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
if err == CGError::Success { Ok(()) } else { Err(os_error!(format!("CGError {err:?}"))) } if err == CGError::Success { Ok(()) } else { Err(os_error!(format!("CGError {err:?}"))) }

View File

@@ -2,21 +2,22 @@
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::rc::Rc; use std::rc::Rc;
use std::time::Instant;
use dpi::{LogicalPosition, PhysicalSize}; use dpi::{LogicalPosition, LogicalSize};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel}; use objc2::runtime::{AnyObject, Sel};
use objc2::{AnyThread, DefinedClass, MainThreadMarker, define_class, msg_send}; use objc2::{AnyThread, DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea, NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea,
NSTrackingAreaOptions, NSView, NSViewLayerContentsRedrawPolicy, NSWindow, NSTrackingAreaOptions, NSView, NSWindow,
}; };
use objc2_core_foundation::CGRect; use objc2_core_foundation::CGRect;
use objc2_foundation::{ use objc2_foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
}; };
use tracing::{debug_span, trace_span}; use tracing::warn;
use winit_core::event::{ use winit_core::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent, PointerKind, PointerSource, TouchPhase, WindowEvent,
@@ -154,32 +155,22 @@ define_class!(
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`. // Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
#[unsafe(method(viewFrameDidChangeNotification:))] #[unsafe(method(viewFrameDidChangeNotification:))]
fn frame_did_change(&self, _notification: Option<&AnyObject>) { fn frame_did_change(&self, _notification: Option<&AnyObject>) {
let _entered = debug_span!("NSViewFrameDidChangeNotification").entered(); trace_scope!("NSViewFrameDidChangeNotification");
// Emit resize event here rather than from windowDidResize because: // 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 // 1. When a new window is created as a tab, the frame size may change without a window
// resize occurring. // resize occurring.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong // 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
// size (includes tab height). // size (includes tab height).
self.surface_resized(); let rect = self.frame();
// During live resize, AppKit may not let the normal event loop reach its next redraw let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
// point before stretching the current layer contents. Redraw immediately after the let size = logical_size.to_physical::<u32>(self.scale_factor());
// app has observed the new surface size. self.queue_event(WindowEvent::SurfaceResized(size));
self.redraw_during_live_resize();
}
#[unsafe(method(viewDidChangeBackingProperties))]
fn view_did_change_backing_properties(&self) {
let _entered = debug_span!("viewDidChangeBackingProperties").entered();
// Moving between displays or changing scale can alter the drawable backing size
// without a matching frame-size change.
self.surface_resized();
self.redraw_during_live_resize();
} }
#[unsafe(method(drawRect:))] #[unsafe(method(drawRect:))]
fn draw_rect(&self, _rect: NSRect) { fn draw_rect(&self, _rect: NSRect) {
let _entered = debug_span!("drawRect:").entered(); trace_scope!("drawRect:");
self.ivars().app_state.handle_redraw(window_id(&self.window())); self.ivars().app_state.handle_redraw(window_id(&self.window()));
@@ -188,7 +179,7 @@ define_class!(
#[unsafe(method(acceptsFirstResponder))] #[unsafe(method(acceptsFirstResponder))]
fn accepts_first_responder(&self) -> bool { fn accepts_first_responder(&self) -> bool {
let _entered = trace_span!("acceptsFirstResponder").entered(); trace_scope!("acceptsFirstResponder");
true true
} }
@@ -202,13 +193,13 @@ define_class!(
// extension for using `NSTouchBar` // extension for using `NSTouchBar`
#[unsafe(method_id(touchBar))] #[unsafe(method_id(touchBar))]
fn touch_bar(&self) -> Option<Retained<NSObject>> { fn touch_bar(&self) -> Option<Retained<NSObject>> {
let _entered = debug_span!("touchBar").entered(); trace_scope!("touchBar");
None None
} }
#[unsafe(method(resetCursorRects))] #[unsafe(method(resetCursorRects))]
fn reset_cursor_rects(&self) { fn reset_cursor_rects(&self) {
let _entered = debug_span!("resetCursorRects").entered(); trace_scope!("resetCursorRects");
let bounds = self.bounds(); let bounds = self.bounds();
let cursor_state = self.ivars().cursor_state.borrow(); let cursor_state = self.ivars().cursor_state.borrow();
// We correctly invoke `addCursorRect` only from inside `resetCursorRects` // We correctly invoke `addCursorRect` only from inside `resetCursorRects`
@@ -223,13 +214,13 @@ define_class!(
unsafe impl NSTextInputClient for WinitView { unsafe impl NSTextInputClient for WinitView {
#[unsafe(method(hasMarkedText))] #[unsafe(method(hasMarkedText))]
fn has_marked_text(&self) -> bool { fn has_marked_text(&self) -> bool {
let _entered = debug_span!("hasMarkedText").entered(); trace_scope!("hasMarkedText");
self.ivars().marked_text.borrow().length() > 0 self.ivars().marked_text.borrow().length() > 0
} }
#[unsafe(method(markedRange))] #[unsafe(method(markedRange))]
fn marked_range(&self) -> NSRange { fn marked_range(&self) -> NSRange {
let _entered = debug_span!("markedRange").entered(); trace_scope!("markedRange");
let length = self.ivars().marked_text.borrow().length(); let length = self.ivars().marked_text.borrow().length();
if length > 0 { if length > 0 {
NSRange::new(0, length) NSRange::new(0, length)
@@ -241,7 +232,7 @@ define_class!(
#[unsafe(method(selectedRange))] #[unsafe(method(selectedRange))]
fn selected_range(&self) -> NSRange { fn selected_range(&self) -> NSRange {
let _entered = debug_span!("selectedRange").entered(); trace_scope!("selectedRange");
// Documented to return `{NSNotFound, 0}` if there is no selection. // Documented to return `{NSNotFound, 0}` if there is no selection.
NSRange::new(NSNotFound as NSUInteger, 0) NSRange::new(NSNotFound as NSUInteger, 0)
} }
@@ -254,7 +245,7 @@ define_class!(
_replacement_range: NSRange, _replacement_range: NSRange,
) { ) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text. // TODO: Use _replacement_range, requires changing the event to report surrounding text.
let _entered = debug_span!("setMarkedText:selectedRange:replacementRange:").entered(); trace_scope!("setMarkedText:selectedRange:replacementRange:");
let (marked_text, string) = if let Some(string) = let (marked_text, string) = if let Some(string) =
string.downcast_ref::<NSAttributedString>() string.downcast_ref::<NSAttributedString>()
@@ -283,31 +274,32 @@ define_class!(
self.ivars().ime_state.set(ImeState::Ground); self.ivars().ime_state.set(ImeState::Ground);
} }
let string = string.to_string();
let cursor_range = if string.is_empty() { let cursor_range = if string.is_empty() {
// An empty string basically means that there's no preedit, so indicate that by // An empty string basically means that there's no preedit, so indicate that by
// sending a `None` cursor range. // sending a `None` cursor range.
None None
} else { } else {
// Convert the selected range from UTF-16 code unit indices to UTF-8 byte // Clamp to string length to avoid NSRangeException from out-of-bounds
// offsets. `utf16_to_utf8_offset` is defensive: it snaps an offset that would // indices sent by macOS IME (e.g. native Pinyin, see
// split a surrogate pair down to the character boundary and clamps an // https://github.com/alacritty/alacritty/issues/8791).
// out-of-bounds offset to the string length, so no `NSRangeException` is let len = string.length();
// possible and the resulting range can never be inverted (`lower <= upper`). let location = selected_range.location.min(len);
// IMEs are known to send both mid-surrogate and out-of-bounds offsets (e.g. let end = selected_range.end().min(len);
// native Pinyin, see https://github.com/alacritty/alacritty/issues/8791). // Convert the selected range from UTF-16 indices to UTF-8 indices.
let lowerbound_utf8 = utf16_to_utf8_offset(&string, selected_range.location); let sub_string_a = string.substringToIndex(location);
let upperbound_utf8 = utf16_to_utf8_offset(&string, selected_range.end()); let sub_string_b = string.substringToIndex(end);
let lowerbound_utf8 = sub_string_a.len();
let upperbound_utf8 = sub_string_b.len();
Some((lowerbound_utf8, upperbound_utf8)) Some((lowerbound_utf8, upperbound_utf8))
}; };
// Send WindowEvent for updating marked text // Send WindowEvent for updating marked text
self.queue_event(WindowEvent::Ime(Ime::Preedit(string, cursor_range))); self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
} }
#[unsafe(method(unmarkText))] #[unsafe(method(unmarkText))]
fn unmark_text(&self) { fn unmark_text(&self) {
let _entered = debug_span!("unmarkText").entered(); trace_scope!("unmarkText");
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new(); *self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
let input_context = self.inputContext().expect("input context"); let input_context = self.inputContext().expect("input context");
@@ -324,7 +316,7 @@ define_class!(
#[unsafe(method_id(validAttributesForMarkedText))] #[unsafe(method_id(validAttributesForMarkedText))]
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> { fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
let _entered = trace_span!("validAttributesForMarkedText").entered(); trace_scope!("validAttributesForMarkedText");
NSArray::new() NSArray::new()
} }
@@ -334,14 +326,13 @@ define_class!(
_range: NSRange, _range: NSRange,
_actual_range: *mut NSRange, _actual_range: *mut NSRange,
) -> Option<Retained<NSAttributedString>> { ) -> Option<Retained<NSAttributedString>> {
let _entered = trace_scope!("attributedSubstringForProposedRange:actualRange:");
trace_span!("attributedSubstringForProposedRange:actualRange:").entered();
None None
} }
#[unsafe(method(characterIndexForPoint:))] #[unsafe(method(characterIndexForPoint:))]
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger { fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
let _entered = debug_span!("characterIndexForPoint:").entered(); trace_scope!("characterIndexForPoint:");
0 0
} }
@@ -351,7 +342,7 @@ define_class!(
_range: NSRange, _range: NSRange,
_actual_range: *mut NSRange, _actual_range: *mut NSRange,
) -> NSRect { ) -> NSRect {
let _entered = debug_span!("firstRectForCharacterRange:actualRange:").entered(); trace_scope!("firstRectForCharacterRange:actualRange:");
// Guard when the view is no longer in a window during teardown. // Guard when the view is no longer in a window during teardown.
let Some(window) = (**self).window() else { let Some(window) = (**self).window() else {
@@ -367,7 +358,7 @@ define_class!(
#[unsafe(method(insertText:replacementRange:))] #[unsafe(method(insertText:replacementRange:))]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) { fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text. // TODO: Use _replacement_range, requires changing the event to report surrounding text.
let _entered = debug_span!("insertText:replacementRange:").entered(); trace_scope!("insertText:replacementRange:");
let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() { let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() {
string.string().to_string() string.string().to_string()
@@ -392,7 +383,7 @@ define_class!(
// "human readable" character happens, i.e. newlines, tabs, and Ctrl+C. // "human readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[unsafe(method(doCommandBySelector:))] #[unsafe(method(doCommandBySelector:))]
fn do_command_by_selector(&self, command: Sel) { fn do_command_by_selector(&self, command: Sel) {
let _entered = debug_span!("doCommandBySelector:").entered(); trace_scope!("doCommandBySelector:");
// We shouldn't forward any character from just committed text, since we'll end up // 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 // sending it twice with some IMEs like Korean one. We'll also always send
@@ -431,7 +422,7 @@ define_class!(
impl WinitView { impl WinitView {
#[unsafe(method(keyDown:))] #[unsafe(method(keyDown:))]
fn key_down(&self, event: &NSEvent) { fn key_down(&self, event: &NSEvent) {
let _entered = debug_span!("keyDown:").entered(); trace_scope!("keyDown:");
{ {
let mut prev_input_source = self.ivars().input_source.borrow_mut(); let mut prev_input_source = self.ivars().input_source.borrow_mut();
let current_input_source = self.current_input_source(); let current_input_source = self.current_input_source();
@@ -490,7 +481,7 @@ define_class!(
#[unsafe(method(keyUp:))] #[unsafe(method(keyUp:))]
fn key_up(&self, event: &NSEvent) { fn key_up(&self, event: &NSEvent) {
let _entered = debug_span!("keyUp:").entered(); trace_scope!("keyUp:");
let event = replace_event(event, self.option_as_alt()); let event = replace_event(event, self.option_as_alt());
self.update_modifiers(&event, false); self.update_modifiers(&event, false);
@@ -507,14 +498,14 @@ define_class!(
#[unsafe(method(flagsChanged:))] #[unsafe(method(flagsChanged:))]
fn flags_changed(&self, event: &NSEvent) { fn flags_changed(&self, event: &NSEvent) {
let _entered = debug_span!("flagsChanged:").entered(); trace_scope!("flagsChanged:");
self.update_modifiers(event, true); self.update_modifiers(event, true);
} }
#[unsafe(method(insertTab:))] #[unsafe(method(insertTab:))]
fn insert_tab(&self, _sender: Option<&AnyObject>) { fn insert_tab(&self, _sender: Option<&AnyObject>) {
let _entered = debug_span!("insertTab:").entered(); trace_scope!("insertTab:");
let window = self.window(); let window = self.window();
if let Some(first_responder) = window.firstResponder() { if let Some(first_responder) = window.firstResponder() {
if *first_responder == ***self { if *first_responder == ***self {
@@ -525,7 +516,7 @@ define_class!(
#[unsafe(method(insertBackTab:))] #[unsafe(method(insertBackTab:))]
fn insert_back_tab(&self, _sender: Option<&AnyObject>) { fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
let _entered = debug_span!("insertBackTab:").entered(); trace_scope!("insertBackTab:");
let window = self.window(); let window = self.window();
if let Some(first_responder) = window.firstResponder() { if let Some(first_responder) = window.firstResponder() {
if *first_responder == ***self { if *first_responder == ***self {
@@ -539,7 +530,7 @@ define_class!(
#[unsafe(method(cancelOperation:))] #[unsafe(method(cancelOperation:))]
fn cancel_operation(&self, _sender: Option<&AnyObject>) { fn cancel_operation(&self, _sender: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self); let mtm = MainThreadMarker::from(self);
let _entered = debug_span!("cancelOperation:").entered(); trace_scope!("cancelOperation:");
let event = NSApplication::sharedApplication(mtm) let event = NSApplication::sharedApplication(mtm)
.currentEvent() .currentEvent()
@@ -568,73 +559,71 @@ define_class!(
#[unsafe(method(mouseDown:))] #[unsafe(method(mouseDown:))]
fn mouse_down(&self, event: &NSEvent) { fn mouse_down(&self, event: &NSEvent) {
let _entered = debug_span!("mouseDown:").entered(); trace_scope!("mouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[unsafe(method(mouseUp:))] #[unsafe(method(mouseUp:))]
fn mouse_up(&self, event: &NSEvent) { fn mouse_up(&self, event: &NSEvent) {
let _entered = debug_span!("mouseUp:").entered(); trace_scope!("mouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
#[unsafe(method(rightMouseDown:))] #[unsafe(method(rightMouseDown:))]
fn right_mouse_down(&self, event: &NSEvent) { fn right_mouse_down(&self, event: &NSEvent) {
let _entered = debug_span!("rightMouseDown:").entered(); trace_scope!("rightMouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[unsafe(method(rightMouseUp:))] #[unsafe(method(rightMouseUp:))]
fn right_mouse_up(&self, event: &NSEvent) { fn right_mouse_up(&self, event: &NSEvent) {
let _entered = debug_span!("rightMouseUp:").entered(); trace_scope!("rightMouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
#[unsafe(method(otherMouseDown:))] #[unsafe(method(otherMouseDown:))]
fn other_mouse_down(&self, event: &NSEvent) { fn other_mouse_down(&self, event: &NSEvent) {
let _entered = debug_span!("otherMouseDown:").entered(); trace_scope!("otherMouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[unsafe(method(otherMouseUp:))] #[unsafe(method(otherMouseUp:))]
fn other_mouse_up(&self, event: &NSEvent) { fn other_mouse_up(&self, event: &NSEvent) {
let _entered = debug_span!("otherMouseUp:").entered(); trace_scope!("otherMouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
// No tracing on these because that would be overly verbose
#[unsafe(method(mouseMoved:))] #[unsafe(method(mouseMoved:))]
fn mouse_moved(&self, event: &NSEvent) { fn mouse_moved(&self, event: &NSEvent) {
let _entered = debug_span!("mouseMoved:").entered();
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(mouseDragged:))] #[unsafe(method(mouseDragged:))]
fn mouse_dragged(&self, event: &NSEvent) { fn mouse_dragged(&self, event: &NSEvent) {
let _entered = debug_span!("mouseDragged:").entered();
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(rightMouseDragged:))] #[unsafe(method(rightMouseDragged:))]
fn right_mouse_dragged(&self, event: &NSEvent) { fn right_mouse_dragged(&self, event: &NSEvent) {
let _entered = debug_span!("rightMouseDragged:").entered();
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(otherMouseDragged:))] #[unsafe(method(otherMouseDragged:))]
fn other_mouse_dragged(&self, event: &NSEvent) { fn other_mouse_dragged(&self, event: &NSEvent) {
let _entered = debug_span!("otherMouseDragged:").entered();
self.mouse_motion(event); self.mouse_motion(event);
} }
#[unsafe(method(mouseEntered:))] #[unsafe(method(mouseEntered:))]
fn mouse_entered(&self, event: &NSEvent) { fn mouse_entered(&self, event: &NSEvent) {
let _entered = debug_span!("mouseEntered:").entered(); trace_scope!("mouseEntered:");
let position = self.mouse_view_point(event).to_physical(self.scale_factor()); let position = self.mouse_view_point(event).to_physical(self.scale_factor());
@@ -648,7 +637,7 @@ define_class!(
#[unsafe(method(mouseExited:))] #[unsafe(method(mouseExited:))]
fn mouse_exited(&self, event: &NSEvent) { fn mouse_exited(&self, event: &NSEvent) {
let _entered = debug_span!("mouseExited:").entered(); trace_scope!("mouseExited:");
let position = self.mouse_view_point(event).to_physical(self.scale_factor()); let position = self.mouse_view_point(event).to_physical(self.scale_factor());
@@ -662,7 +651,7 @@ define_class!(
#[unsafe(method(scrollWheel:))] #[unsafe(method(scrollWheel:))]
fn scroll_wheel(&self, event: &NSEvent) { fn scroll_wheel(&self, event: &NSEvent) {
let _entered = debug_span!("scrollWheel:").entered(); trace_scope!("scrollWheel:");
self.mouse_motion(event); self.mouse_motion(event);
@@ -693,15 +682,16 @@ define_class!(
self.update_modifiers(event, false); 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| { self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta }) app.device_event(event_loop, None, time, DeviceEvent::MouseWheel { delta })
}); });
self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase }); self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase });
} }
#[unsafe(method(magnifyWithEvent:))] #[unsafe(method(magnifyWithEvent:))]
fn magnify_with_event(&self, event: &NSEvent) { fn magnify_with_event(&self, event: &NSEvent) {
let _entered = debug_span!("magnifyWithEvent:").entered(); trace_scope!("magnifyWithEvent:");
self.mouse_motion(event); self.mouse_motion(event);
@@ -723,7 +713,7 @@ define_class!(
#[unsafe(method(smartMagnifyWithEvent:))] #[unsafe(method(smartMagnifyWithEvent:))]
fn smart_magnify_with_event(&self, event: &NSEvent) { fn smart_magnify_with_event(&self, event: &NSEvent) {
let _entered = debug_span!("smartMagnifyWithEvent:").entered(); trace_scope!("smartMagnifyWithEvent:");
self.mouse_motion(event); self.mouse_motion(event);
@@ -732,7 +722,7 @@ define_class!(
#[unsafe(method(rotateWithEvent:))] #[unsafe(method(rotateWithEvent:))]
fn rotate_with_event(&self, event: &NSEvent) { fn rotate_with_event(&self, event: &NSEvent) {
let _entered = debug_span!("rotateWithEvent:").entered(); trace_scope!("rotateWithEvent:");
self.mouse_motion(event); self.mouse_motion(event);
@@ -754,7 +744,7 @@ define_class!(
#[unsafe(method(pressureChangeWithEvent:))] #[unsafe(method(pressureChangeWithEvent:))]
fn pressure_change_with_event(&self, event: &NSEvent) { fn pressure_change_with_event(&self, event: &NSEvent) {
let _entered = debug_span!("pressureChangeWithEvent:").entered(); trace_scope!("pressureChangeWithEvent:");
self.queue_event(WindowEvent::TouchpadPressure { self.queue_event(WindowEvent::TouchpadPressure {
device_id: None, device_id: None,
@@ -768,13 +758,13 @@ define_class!(
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816 // https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
#[unsafe(method(_wantsKeyDownForEvent:))] #[unsafe(method(_wantsKeyDownForEvent:))]
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool { fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
let _entered = debug_span!("_wantsKeyDownForEvent:").entered(); trace_scope!("_wantsKeyDownForEvent:");
true true
} }
#[unsafe(method(acceptsFirstMouse:))] #[unsafe(method(acceptsFirstMouse:))]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
let _entered = debug_span!("acceptsFirstMouse:").entered(); trace_scope!("acceptsFirstMouse:");
self.ivars().accepts_first_mouse self.ivars().accepts_first_mouse
} }
} }
@@ -805,10 +795,6 @@ impl WinitView {
let this: Retained<Self> = unsafe { msg_send![super(this), init] }; let this: Retained<Self> = unsafe { msg_send![super(this), init] };
*this.ivars().input_source.borrow_mut() = this.current_input_source(); *this.ivars().input_source.borrow_mut() = this.current_input_source();
// Ask AppKit to redisplay the layer while the view is being resized so layer-backed
// surfaces keep painting.
this.setLayerContentsRedrawPolicy(NSViewLayerContentsRedrawPolicy::DuringViewResize);
// `MouseEnteredAndExited` enables receiving events through `mouseEntered:` and // `MouseEnteredAndExited` enables receiving events through `mouseEntered:` and
// `mouseExited:`. // `mouseExited:`.
// //
@@ -861,43 +847,19 @@ impl WinitView {
} }
fn queue_event(&self, event: WindowEvent) { fn queue_event(&self, event: WindowEvent) {
let app = NSApplication::sharedApplication(self.mtm());
let window_id = window_id(&self.window()); let window_id = window_id(&self.window());
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { let time = if let Some(nsevent) = app.currentEvent() {
app.window_event(event_loop, window_id, event); self.ivars().app_state.event_time(&nsevent)
}); } else {
} warn!("queued event with wrong timestamp, no active NSEvent found");
Instant::now()
fn surface_resized(&self) {
let Some(window) = (**self).window() else {
return;
}; };
let size = self.surface_size();
let window_id = window_id(&window);
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::SurfaceResized(size)); app.window_event(event_loop, window_id, time, event);
}); });
} }
/// Returns the drawable size from the view's backing-coordinate bounds.
pub(super) fn surface_size(&self) -> PhysicalSize<u32> {
// The view bounds are authoritative for full-size content views and during live resize.
// Deriving this from the window frame can exclude custom titlebar content or be stale.
let backing_bounds = self.convertRectToBacking(self.bounds());
PhysicalSize::new(
backing_bounds.size.width.round().max(0.0) as u32,
backing_bounds.size.height.round().max(0.0) as u32,
)
}
fn redraw_during_live_resize(&self) {
let Some(window) = (**self).window() else {
return;
};
if window.inLiveResize() {
self.ivars().app_state.handle_redraw(window_id(&window));
}
}
fn scale_factor(&self) -> f64 { fn scale_factor(&self) -> f64 {
self.window().backingScaleFactor() as f64 self.window().backingScaleFactor() as f64
} }
@@ -1214,92 +1176,3 @@ fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEven
event.copy() event.copy()
} }
} }
/// Convert a UTF-16 code unit offset into the corresponding UTF-8 byte offset within `s`.
///
/// IMEs are not required to send well-formed offsets, so this is defensive: an offset that
/// would split a surrogate pair is snapped down to the start of that character, and an
/// out-of-bounds offset is clamped to the end of the string (e.g. native Pinyin sends
/// out-of-bounds indices, see <https://github.com/alacritty/alacritty/issues/8791>).
///
/// The mapping is monotone non-decreasing, so applying it to the location and end of an
/// `NSRange` (where `location <= end`) can never produce an inverted byte range.
fn utf16_to_utf8_offset(s: &str, utf16_offset: usize) -> usize {
let mut utf16_pos = 0;
for (utf8_pos, ch) in s.char_indices() {
if utf16_pos >= utf16_offset {
return utf8_pos;
}
utf16_pos += ch.len_utf16();
// The target offset lands strictly inside this character's UTF-16 representation,
// i.e. it splits a surrogate pair: snap down to the character boundary.
if utf16_pos > utf16_offset {
return utf8_pos;
}
}
s.len()
}
#[cfg(test)]
mod tests {
use super::*;
/// Apply the UTF-16 -> UTF-8 conversion to both ends of a `selectedRange {loc, len}`,
/// mirroring what `set_marked_text` does for the emitted `Ime::Preedit` cursor range.
fn convert(s: &str, loc: usize, len: usize) -> (usize, usize) {
(utf16_to_utf8_offset(s, loc), utf16_to_utf8_offset(s, loc + len))
}
#[test]
fn mid_surrogate_offset_snaps_down() {
// "😀a": 😀 is one char = 2 UTF-16 units = 4 UTF-8 bytes; offset 1 is mid-pair.
assert_eq!(utf16_to_utf8_offset("\u{1F600}a", 1), 0);
// Offset 2 is the boundary just after the pair.
assert_eq!(utf16_to_utf8_offset("\u{1F600}a", 2), 4);
}
#[test]
fn no_longer_inverted() {
// "a😀b" with selectedRange {1,1}: previously emitted (1, 0) -- lower > upper, a
// slice-panic vector. The boundary-snapping conversion keeps lower <= upper.
assert_eq!(convert("a\u{1F600}b", 1, 1), (1, 1));
}
#[test]
fn prefix_preserved_on_mid_pair_collapse() {
// "a😀b" with selectedRange {2,0}: previously collapsed to (0, 0), discarding the
// valid "a" prefix; now snaps to the char boundary after "a".
assert_eq!(convert("a\u{1F600}b", 2, 0), (1, 1));
}
#[test]
fn out_of_bounds_clamps_to_len() {
// Subsumes the #4494 `.min(len)` clamp: an out-of-bounds offset maps to the string
// length instead of triggering an NSRangeException.
assert_eq!(convert("\u{1F600}a", 99, 0), (5, 5));
}
#[test]
fn well_formed_inputs_are_identity() {
// The common case (well-formed boundary indices) must be byte-for-byte unchanged.
assert_eq!(convert("a\u{1F600}b", 3, 0), (5, 5));
assert_eq!(convert("a\u{1F600}b", 4, 0), (6, 6));
// BMP multi-byte (Japanese): each char is 1 UTF-16 unit and 3 UTF-8 bytes.
assert_eq!(convert("\u{3053}\u{3093}", 1, 1), (3, 6));
}
#[test]
fn monotone_non_decreasing() {
// Sweep every UTF-16 offset (including out-of-bounds) over a string mixing BMP and
// non-BMP characters and assert the conversion never goes backwards, which is what
// guarantees `lower <= upper` for any `NSRange`.
let s = "a\u{1F600}b\u{3053}\u{1F4A9}c";
let mut prev = 0;
for off in 0..=20 {
let cur = utf16_to_utf8_offset(s, off);
assert!(cur >= prev, "non-monotone at offset {off}: {cur} < {prev}");
assert!(cur <= s.len(), "offset {off} mapped past end: {cur} > {}", s.len());
prev = cur;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ use std::cell::Cell;
use objc2::MainThreadMarker; use objc2::MainThreadMarker;
use objc2_core_foundation::{CFRetained, CFRunLoop, CFRunLoopMode, kCFRunLoopDefaultMode}; use objc2_core_foundation::{CFRetained, CFRunLoop, CFRunLoopMode, kCFRunLoopDefaultMode};
use tracing::{Span, error}; use tracing::error;
use super::MainRunLoopObserver; use super::MainRunLoopObserver;
@@ -49,8 +49,7 @@ impl MainRunLoop {
/// This queuing could be implemented in the following several ways with subtle differences in /// 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: /// timing. This list is sorted in rough order in which they are run:
/// ///
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]` to queue a closure to run /// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
/// the next time runloop sources are processed.
/// ///
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the /// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both /// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
@@ -74,17 +73,9 @@ impl MainRunLoop {
/// put the event at the very front of the queue, to be handled as soon as possible after /// 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. /// handling whatever event it's currently handling.
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) { 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()>`. // Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure)); let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || { 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()); debug_assert!(MainThreadMarker::new().is_some());
if let Some(closure) = closure.take() { if let Some(closure) = closure.take() {
closure() closure()

View File

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

View File

@@ -1,146 +0,0 @@
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,10 +79,6 @@ impl EventHandler {
// Allowed, happens if the handler was cleared manually // Allowed, happens if the handler was cleared manually
// elsewhere (such as in `applicationWillTerminate:`). // 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(_) => { Err(_) => {
// Note: This is not expected to ever happen, this // Note: This is not expected to ever happen, this
// module generally controls the `RefCell`, and // module generally controls the `RefCell`, and

View File

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

View File

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

View File

@@ -1,4 +1,9 @@
//! End user application handling. //! 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::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::event_loop::ActiveEventLoop; use crate::event_loop::ActiveEventLoop;
@@ -192,10 +197,14 @@ pub trait ApplicationHandler {
} }
/// Emitted when the OS sends an event to a winit window. /// 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( fn window_event(
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
timestamp: Instant,
event: WindowEvent, event: WindowEvent,
); );
@@ -206,9 +215,10 @@ pub trait ApplicationHandler {
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
timestamp: Instant,
event: DeviceEvent, event: DeviceEvent,
) { ) {
let _ = (event_loop, device_id, event); let _ = (event_loop, device_id, timestamp, event);
} }
/// Emitted when the event loop is about to block and wait for new events. /// Emitted when the event loop is about to block and wait for new events.
@@ -372,9 +382,10 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
timestamp: Instant,
event: WindowEvent, event: WindowEvent,
) { ) {
(**self).window_event(event_loop, window_id, event); (**self).window_event(event_loop, window_id, timestamp, event);
} }
#[inline] #[inline]
@@ -382,9 +393,10 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
timestamp: Instant,
event: DeviceEvent, event: DeviceEvent,
) { ) {
(**self).device_event(event_loop, device_id, event); (**self).device_event(event_loop, device_id, timestamp, event);
} }
#[inline] #[inline]
@@ -440,9 +452,10 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
window_id: WindowId, window_id: WindowId,
timestamp: Instant,
event: WindowEvent, event: WindowEvent,
) { ) {
(**self).window_event(event_loop, window_id, event); (**self).window_event(event_loop, window_id, timestamp, event);
} }
#[inline] #[inline]
@@ -450,9 +463,10 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
&mut self, &mut self,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>, device_id: Option<DeviceId>,
timestamp: Instant,
event: DeviceEvent, event: DeviceEvent,
) { ) {
(**self).device_event(event_loop, device_id, event); (**self).device_event(event_loop, device_id, timestamp, event);
} }
#[inline] #[inline]

View File

@@ -269,20 +269,6 @@ pub enum WindowEvent {
button: ButtonSource, button: ButtonSource,
}, },
/// Multi-finger hold gesture on the touchpad or touchscreen without movement.
///
/// The `phase` field indicates the lifecycle of the hold gesture:
/// - `Started`: One or more fingers are in contact with the touchpad/touchscreen.
/// - `Ended`: All fingers have been lifted from the touchpad/touchscreen.
/// - `Cancelled`: The hold gesture was interrupted, for example when another finger touches the
/// touchpad (causing a new `Started` event with more fingers), or when movement begins and
/// transitions to other gestures like pinch, pan, or rotation.
///
/// ## Platform-specific
///
/// - Only available on **Wayland**.
HoldGesture { device_id: Option<DeviceId>, phase: TouchPhase },
/// Two-finger pinch gesture, often used for magnification. /// Two-finger pinch gesture, often used for magnification.
/// ///
/// ## Platform-specific /// ## Platform-specific
@@ -1020,17 +1006,9 @@ pub enum Ime {
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TouchPhase { pub enum TouchPhase {
/// Initial touch contact or gesture start, for example when one or more fingers touch the
/// screen or touchpad.
Started, Started,
/// The touch contact point changed, for example without lifting the finger.
Moved, Moved,
/// All touch contact points have been lifted from the touchscreen or touchpad.
///
/// This event is important as it should clear any state or event in flight that was
/// generated by the preceding `Started` and `Moved` events.
Ended, Ended,
/// The event was cancelled and should cancel any event in flight and clear state.
Cancelled, Cancelled,
} }

View File

@@ -880,8 +880,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// ## Platform-specific /// ## Platform-specific
/// ///
/// - **Android / iOS / X11 / Web / Windows:** Unsupported. /// - **Android / iOS / X11 / Web / Windows:** Unsupported.
/// - **Wayland:** Only works with `org_kde_kwin_blur_manager` or /// - **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
/// `ext_background_effect_manager_v1` protocol.
fn set_blur(&self, blur: bool); fn set_blur(&self, blur: bool);
/// Modifies the window's visibility. /// Modifies the window's visibility.

View File

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

View File

@@ -12,9 +12,7 @@ use objc2_ui_kit::{
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen, UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen,
}; };
use rwh_06::HasDisplayHandle; use rwh_06::HasDisplayHandle;
use tracing::debug_span; use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver};
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
use winit_common::foundation::create_observer;
use winit_core::application::ApplicationHandler; use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource}; use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, NotSupportedError, RequestError}; use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
@@ -26,6 +24,7 @@ use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::{Theme, Window as CoreWindow}; use winit_core::window::{Theme, Window as CoreWindow};
use super::app_state::{AppState, send_occluded_event_for_all_windows}; use super::app_state::{AppState, send_occluded_event_for_all_windows};
use super::notification_center::create_observer;
use crate::monitor::MonitorHandle; use crate::monitor::MonitorHandle;
use crate::window::Window; use crate::window::Window;
use crate::{app_state, monitor}; use crate::{app_state, monitor};
@@ -135,7 +134,6 @@ pub struct EventLoop {
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
_wakeup_observer: MainRunLoopObserver, _wakeup_observer: MainRunLoopObserver,
_main_events_cleared_observer: MainRunLoopObserver, _main_events_cleared_observer: MainRunLoopObserver,
_events_cleared_observer: MainRunLoopObserver, _events_cleared_observer: MainRunLoopObserver,
@@ -161,7 +159,6 @@ impl EventLoop {
// `application:didFinishLaunchingWithOptions:` // `application:didFinishLaunchingWithOptions:`
unsafe { UIApplicationDidFinishLaunchingNotification }, unsafe { UIApplicationDidFinishLaunchingNotification },
move |_| { move |_| {
let _entered = debug_span!("UIApplicationDidFinishLaunchingNotification").entered();
app_state::did_finish_launching(mtm); app_state::did_finish_launching(mtm);
}, },
); );
@@ -169,27 +166,19 @@ impl EventLoop {
&center, &center,
// `applicationDidBecomeActive:` // `applicationDidBecomeActive:`
unsafe { UIApplicationDidBecomeActiveNotification }, unsafe { UIApplicationDidBecomeActiveNotification },
move |_| { move |_| app_state::handle_resumed(mtm),
let _entered = debug_span!("UIApplicationDidBecomeActiveNotification").entered();
app_state::handle_resumed(mtm)
},
); );
let _will_resign_active_observer = create_observer( let _will_resign_active_observer = create_observer(
&center, &center,
// `applicationWillResignActive:` // `applicationWillResignActive:`
unsafe { UIApplicationWillResignActiveNotification }, unsafe { UIApplicationWillResignActiveNotification },
move |_| { move |_| app_state::handle_suspended(mtm),
let _entered = debug_span!("UIApplicationWillResignActiveNotification").entered();
app_state::handle_suspended(mtm)
},
); );
let _will_enter_foreground_observer = create_observer( let _will_enter_foreground_observer = create_observer(
&center, &center,
// `applicationWillEnterForeground:` // `applicationWillEnterForeground:`
unsafe { UIApplicationWillEnterForegroundNotification }, unsafe { UIApplicationWillEnterForegroundNotification },
move |notification| { move |notification| {
let _entered =
debug_span!("UIApplicationWillEnterForegroundNotification").entered();
let app = notification.object().expect( let app = notification.object().expect(
"UIApplicationWillEnterForegroundNotification to have application object", "UIApplicationWillEnterForegroundNotification to have application object",
); );
@@ -204,7 +193,6 @@ impl EventLoop {
// `applicationDidEnterBackground:` // `applicationDidEnterBackground:`
unsafe { UIApplicationDidEnterBackgroundNotification }, unsafe { UIApplicationDidEnterBackgroundNotification },
move |notification| { move |notification| {
let _entered = debug_span!("UIApplicationDidEnterBackgroundNotification").entered();
let app = notification.object().expect( let app = notification.object().expect(
"UIApplicationDidEnterBackgroundNotification to have application object", "UIApplicationDidEnterBackgroundNotification to have application object",
); );
@@ -219,7 +207,6 @@ impl EventLoop {
// `applicationWillTerminate:` // `applicationWillTerminate:`
unsafe { UIApplicationWillTerminateNotification }, unsafe { UIApplicationWillTerminateNotification },
move |notification| { move |notification| {
let _entered = debug_span!("UIApplicationWillTerminateNotification").entered();
let app = notification let app = notification
.object() .object()
.expect("UIApplicationWillTerminateNotification to have application object"); .expect("UIApplicationWillTerminateNotification to have application object");
@@ -233,29 +220,18 @@ impl EventLoop {
&center, &center,
// `applicationDidReceiveMemoryWarning:` // `applicationDidReceiveMemoryWarning:`
unsafe { UIApplicationDidReceiveMemoryWarningNotification }, unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| { move |_| app_state::handle_memory_warning(mtm),
let _entered =
debug_span!("UIApplicationDidReceiveMemoryWarningNotification").entered();
app_state::handle_memory_warning(mtm)
},
); );
let main_loop = MainRunLoop::get(mtm); let main_loop = MainRunLoop::get(mtm);
let mode = unsafe { kCFRunLoopDefaultMode }.unwrap(); 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( let _wakeup_observer = MainRunLoopObserver::new(
mtm, mtm,
CFRunLoopActivity::AfterWaiting, CFRunLoopActivity::AfterWaiting,
true, true,
// Queued with the second-highest priority (tracing observers use the highest) to // Queued with the highest priority to ensure it is processed before other observers.
// ensure it is processed before other observers. CFIndex::MIN,
CFIndex::MIN + 1,
move |_| app_state::handle_wakeup_transition(mtm), move |_| app_state::handle_wakeup_transition(mtm),
); );
main_loop.add_observer(&_wakeup_observer, mode); main_loop.add_observer(&_wakeup_observer, mode);
@@ -265,17 +241,17 @@ impl EventLoop {
CFRunLoopActivity::BeforeWaiting, CFRunLoopActivity::BeforeWaiting,
true, true,
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in // Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `2000000`. We set the // `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the
// main_end priority to be 0, in order to send `AboutToWait` before `RedrawRequested`. // main_end priority to be 0, in order to send `AboutToWait` before `RedrawRequested`.
// This value was chosen conservatively to guard against apple using different // 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 // 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 // it so happens that it's too conservative, the main symptom would be non-redraw
// events coming in after `AboutToWait`. // events coming in after `AboutToWait`.
// //
// The value of `2000000` was determined by inspecting stack traces and the associated // The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
// //
// Also tested to be `2000000` on iPhone 8, iOS 13 beta 4. // Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
0, 0,
move |_| app_state::handle_main_events_cleared(mtm), move |_| app_state::handle_main_events_cleared(mtm),
); );
@@ -285,9 +261,8 @@ impl EventLoop {
mtm, mtm,
CFRunLoopActivity::BeforeWaiting, CFRunLoopActivity::BeforeWaiting,
true, true,
// Queued with the second-lowest priority (tracing observers use the lowest) to ensure // Queued with the lowest priority to ensure it is processed after other observers.
// it is processed after other observers. CFIndex::MAX,
CFIndex::MAX - 1,
move |_| app_state::handle_events_cleared(mtm), move |_| app_state::handle_events_cleared(mtm),
); );
main_loop.add_observer(&_events_cleared_observer, mode); main_loop.add_observer(&_events_cleared_observer, mode);
@@ -302,7 +277,6 @@ impl EventLoop {
_did_enter_background_observer, _did_enter_background_observer,
_will_terminate_observer, _will_terminate_observer,
_did_receive_memory_warning_observer, _did_receive_memory_warning_observer,
_tracing_observers,
_wakeup_observer, _wakeup_observer,
_main_events_cleared_observer, _main_events_cleared_observer,
_events_cleared_observer, _events_cleared_observer,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -40,7 +40,7 @@ sctk = { package = "smithay-client-toolkit", version = "0.20.0", default-feature
sctk-adwaita = { version = "0.11.0", default-features = false, optional = true } sctk-adwaita = { version = "0.11.0", default-features = false, optional = true }
wayland-backend = { version = "0.3.10", default-features = false, features = ["client_system"] } wayland-backend = { version = "0.3.10", default-features = false, features = ["client_system"] }
wayland-client = "0.31.10" wayland-client = "0.31.10"
wayland-protocols = { version = "0.32.11", features = ["staging", "unstable"] } wayland-protocols = { version = "0.32.8", features = ["staging"] }
wayland-protocols-plasma = { version = "0.3.8", features = ["client"] } wayland-protocols-plasma = { version = "0.3.8", features = ["client"] }
winit-common = { workspace = true, features = ["xkb", "wayland"] } winit-common = { workspace = true, features = ["xkb", "wayland"] }

View File

@@ -12,7 +12,6 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::
use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
use tracing::warn; use tracing::warn;
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_hold_v1::ZwpPointerGestureHoldV1;
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1; use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1;
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::ZwpTabletSeatV2; use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::ZwpTabletSeatV2;
use winit_core::event::WindowEvent; use winit_core::event::WindowEvent;
@@ -61,9 +60,6 @@ pub struct WinitSeatState {
/// The pinch pointer gesture bound on the seat. /// The pinch pointer gesture bound on the seat.
pointer_gesture_pinch: Option<ZwpPointerGesturePinchV1>, pointer_gesture_pinch: Option<ZwpPointerGesturePinchV1>,
/// The hold pointer gesture bound on the seat.
pointer_gesture_hold: Option<ZwpPointerGestureHoldV1>,
/// The keyboard bound on the seat. /// The keyboard bound on the seat.
keyboard_state: Option<KeyboardState>, keyboard_state: Option<KeyboardState>,
@@ -145,14 +141,6 @@ impl SeatHandler for WinitState {
) )
}); });
seat_state.pointer_gesture_hold = self.pointer_gestures.as_ref().map(|manager| {
manager.get_hold_gesture(
themed_pointer.pointer(),
queue_handle,
PointerGestureData::default(),
)
});
let themed_pointer = Arc::new(themed_pointer); let themed_pointer = Arc::new(themed_pointer);
// Register cursor surface. // Register cursor surface.
@@ -221,10 +209,6 @@ impl SeatHandler for WinitState {
pointer_gesture_pinch.destroy(); pointer_gesture_pinch.destroy();
} }
if let Some(pointer_gesture_hold) = seat_state.pointer_gesture_hold.take() {
pointer_gesture_hold.destroy();
}
if let Some(pointer) = seat_state.pointer.take() { if let Some(pointer) = seat_state.pointer.take() {
let pointer_data = pointer.pointer().winit_data(); let pointer_data = pointer.pointer().winit_data();

View File

@@ -7,12 +7,9 @@ use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch}; use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch};
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::{ use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::{
Event as PinchEvent, ZwpPointerGesturePinchV1, Event, ZwpPointerGesturePinchV1,
}; };
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gestures_v1::ZwpPointerGesturesV1; use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gestures_v1::ZwpPointerGesturesV1;
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_hold_v1::{
Event as HoldEvent, ZwpPointerGestureHoldV1,
};
use winit_core::event::{TouchPhase, WindowEvent}; use winit_core::event::{TouchPhase, WindowEvent};
use winit_core::window::WindowId; use winit_core::window::WindowId;
@@ -30,7 +27,7 @@ impl PointerGesturesState {
globals: &GlobalList, globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>, queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> { ) -> Result<Self, BindError> {
let pointer_gestures = globals.bind(queue_handle, 3..=3, GlobalData)?; let pointer_gestures = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { pointer_gestures }) Ok(Self { pointer_gestures })
} }
} }
@@ -73,49 +70,6 @@ impl Dispatch<ZwpPointerGesturesV1, GlobalData, WinitState> for PointerGesturesS
} }
} }
impl Dispatch<ZwpPointerGestureHoldV1, PointerGestureData, WinitState> for PointerGesturesState {
fn event(
state: &mut WinitState,
_proxy: &ZwpPointerGestureHoldV1,
event: <ZwpPointerGestureHoldV1 as wayland_client::Proxy>::Event,
data: &PointerGestureData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
let mut pointer_gesture_data = data.inner.lock().unwrap();
let (window_id, phase) = match event {
HoldEvent::Begin { surface, fingers, .. } => {
if fingers < 2 {
return;
}
let window_id = crate::make_wid(&surface);
pointer_gesture_data.window_id = Some(window_id);
(window_id, TouchPhase::Started)
},
HoldEvent::End { cancelled, .. } => {
let window_id = match pointer_gesture_data.window_id {
Some(window_id) => window_id,
_ => return,
};
// Reset the state.
*pointer_gesture_data = Default::default();
let phase = if cancelled == 0 { TouchPhase::Ended } else { TouchPhase::Cancelled };
(window_id, phase)
},
_ => return,
};
state
.events_sink
.push_window_event(WindowEvent::HoldGesture { device_id: None, phase }, window_id);
}
}
impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for PointerGesturesState { impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for PointerGesturesState {
fn event( fn event(
state: &mut WinitState, state: &mut WinitState,
@@ -127,7 +81,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
) { ) {
let mut pointer_gesture_data = data.inner.lock().unwrap(); let mut pointer_gesture_data = data.inner.lock().unwrap();
let (window_id, phase, pan_delta, pinch_delta, rotation_delta) = match event { let (window_id, phase, pan_delta, pinch_delta, rotation_delta) = match event {
PinchEvent::Begin { surface, fingers, .. } => { Event::Begin { surface, fingers, .. } => {
// We only support two fingers for now. // We only support two fingers for now.
if fingers != 2 { if fingers != 2 {
return; return;
@@ -146,7 +100,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
(window_id, TouchPhase::Started, PhysicalPosition::new(0., 0.), 0., 0.) (window_id, TouchPhase::Started, PhysicalPosition::new(0., 0.), 0., 0.)
}, },
PinchEvent::Update { dx, dy, scale: pinch, rotation, .. } => { Event::Update { dx, dy, scale: pinch, rotation, .. } => {
let window_id = match pointer_gesture_data.window_id { let window_id = match pointer_gesture_data.window_id {
Some(window_id) => window_id, Some(window_id) => window_id,
_ => return, _ => return,
@@ -167,7 +121,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
let rotation_delta = -rotation as f32; let rotation_delta = -rotation as f32;
(window_id, TouchPhase::Moved, pan_delta, pinch_delta, rotation_delta) (window_id, TouchPhase::Moved, pan_delta, pinch_delta, rotation_delta)
}, },
PinchEvent::End { cancelled, .. } => { Event::End { cancelled, .. } => {
let window_id = match pointer_gesture_data.window_id { let window_id = match pointer_gesture_data.window_id {
Some(window_id) => window_id, Some(window_id) => window_id,
_ => return, _ => return,
@@ -201,4 +155,3 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState); delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState);
delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: PointerGestureData] => PointerGesturesState); delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: PointerGestureData] => PointerGesturesState);
delegate_dispatch!(WinitState: [ZwpPointerGestureHoldV1: PointerGestureData] => PointerGesturesState);

View File

@@ -29,7 +29,7 @@ use crate::seat::{
PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState, PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState,
WinitPointerData, WinitPointerDataExt, WinitSeatState, WinitPointerData, WinitPointerDataExt, WinitSeatState,
}; };
use crate::types::bgr_effects::BgrEffectManager; use crate::types::kwin_blur::KWinBlurManager;
use crate::types::wp_fractional_scaling::FractionalScalingManager; use crate::types::wp_fractional_scaling::FractionalScalingManager;
use crate::types::wp_tablet_input_v2::TabletManager; use crate::types::wp_tablet_input_v2::TabletManager;
use crate::types::wp_viewporter::ViewporterState; use crate::types::wp_viewporter::ViewporterState;
@@ -116,8 +116,8 @@ pub struct WinitState {
/// Fractional scaling manager. /// Fractional scaling manager.
pub fractional_scaling_manager: Option<FractionalScalingManager>, pub fractional_scaling_manager: Option<FractionalScalingManager>,
/// Blur manager. /// KWin blur manager.
pub blur_manager: Option<BgrEffectManager>, pub kwin_blur_manager: Option<KWinBlurManager>,
/// Loop handle to re-register event sources, such as keyboard repeat. /// Loop handle to re-register event sources, such as keyboard repeat.
pub loop_handle: LoopHandle<'static, Self>, pub loop_handle: LoopHandle<'static, Self>,
@@ -192,7 +192,7 @@ impl WinitState {
window_events_sink: Default::default(), window_events_sink: Default::default(),
viewporter_state, viewporter_state,
fractional_scaling_manager, fractional_scaling_manager,
blur_manager: BgrEffectManager::new(globals, queue_handle).ok(), kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(),
seats, seats,
text_input_state: TextInputState::new(globals, queue_handle).ok(), text_input_state: TextInputState::new(globals, queue_handle).ok(),

View File

@@ -1,85 +0,0 @@
use sctk::compositor::Region;
use sctk::reexports::client::QueueHandle;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::protocols::ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::state::WinitState;
use crate::types::ext_background_effect::ExtBackgroundEffectManager;
use crate::types::kwin_blur::KWinBlurManager;
/// Wrapper around various background effects for [`WlSurface`].
#[derive(Debug, Clone)]
pub enum BgrEffectManager {
Ext(ExtBackgroundEffectManager),
KWin(KWinBlurManager),
}
impl BgrEffectManager {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
ExtBackgroundEffectManager::new(globals, queue_handle)
.map(Self::Ext)
.or_else(|_| KWinBlurManager::new(globals, queue_handle).map(Self::KWin))
}
/// Creates a new blur effect for the surface.
pub fn new_blur_effect(
&mut self,
surface: &WlSurface,
queue_handle: &QueueHandle<WinitState>,
) -> SurfaceBlurEffect {
match self {
BgrEffectManager::Ext(mgr) => SurfaceBlurEffect::Ext(mgr.blur(surface, queue_handle)),
BgrEffectManager::KWin(mgr) => SurfaceBlurEffect::Kwin(
mgr.blur(surface, queue_handle),
mgr.clone(),
surface.clone(),
),
}
}
}
#[derive(Debug)]
pub enum SurfaceBlurEffect {
Ext(ExtBackgroundEffectSurfaceV1),
Kwin(OrgKdeKwinBlur, KWinBlurManager, WlSurface),
}
impl SurfaceBlurEffect {
/// Returns `true` if the main surface commit is required.
///
/// `None` clears the blur.
#[must_use]
pub fn set_blur(&self, region: Option<&Region>) -> bool {
let region = region.map(|region| region.wl_region());
match self {
SurfaceBlurEffect::Ext(surface) => {
surface.set_blur_region(region);
true
},
SurfaceBlurEffect::Kwin(blur, ..) => {
blur.set_region(region);
blur.commit();
true
},
}
}
}
impl Drop for SurfaceBlurEffect {
fn drop(&mut self) {
match self {
SurfaceBlurEffect::Ext(surface) => surface.destroy(),
SurfaceBlurEffect::Kwin(blur, mgr, wl_surface) => {
blur.set_region(None);
blur.commit();
blur.release();
mgr.unset(wl_surface);
},
}
}
}

View File

@@ -1,59 +0,0 @@
use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch};
use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_manager_v1::ExtBackgroundEffectManagerV1;
use wayland_protocols::ext::background_effect::v1::client::ext_background_effect_surface_v1::ExtBackgroundEffectSurfaceV1;
use crate::state::WinitState;
#[derive(Debug, Clone)]
pub struct ExtBackgroundEffectManager {
manager: ExtBackgroundEffectManagerV1,
}
impl ExtBackgroundEffectManager {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { manager })
}
pub fn blur(
&mut self,
surface: &WlSurface,
queue_handle: &QueueHandle<WinitState>,
) -> ExtBackgroundEffectSurfaceV1 {
self.manager.get_background_effect(surface, queue_handle, ())
}
}
impl Dispatch<ExtBackgroundEffectManagerV1, GlobalData, WinitState> for ExtBackgroundEffectManager {
fn event(
_: &mut WinitState,
_: &ExtBackgroundEffectManagerV1,
_: <ExtBackgroundEffectManagerV1 as Proxy>::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ExtBackgroundEffectSurfaceV1, (), WinitState> for ExtBackgroundEffectManager {
fn event(
_: &mut WinitState,
_: &ExtBackgroundEffectSurfaceV1,
_: <ExtBackgroundEffectSurfaceV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<WinitState>,
) {
// There is no event
}
}
delegate_dispatch!(WinitState: [ExtBackgroundEffectManagerV1: GlobalData] => ExtBackgroundEffectManager);
delegate_dispatch!(WinitState: [ExtBackgroundEffectSurfaceV1: ()] => ExtBackgroundEffectManager);

View File

@@ -1,8 +1,6 @@
//! Wayland protocol implementation boilerplate. //! Wayland protocol implementation boilerplate.
pub mod bgr_effects;
pub mod cursor; pub mod cursor;
pub mod ext_background_effect;
pub mod kwin_blur; pub mod kwin_blur;
pub mod wp_fractional_scaling; pub mod wp_fractional_scaling;
pub mod wp_tablet_input_v2; pub mod wp_tablet_input_v2;

View File

@@ -127,8 +127,7 @@ impl Window {
// Set transparency hint. // Set transparency hint.
window_state.set_transparent(attributes.transparent); window_state.set_transparent(attributes.transparent);
// Set blur. window_state.set_blur(attributes.blur);
let _ = window_state.set_blur(attributes.blur);
// Set the decorations hint. // Set the decorations hint.
window_state.set_decorate(attributes.decorations); window_state.set_decorate(attributes.decorations);
@@ -499,9 +498,7 @@ impl CoreWindow for Window {
#[inline] #[inline]
fn set_blur(&self, blur: bool) { fn set_blur(&self, blur: bool) {
if self.window_state.lock().unwrap().set_blur(blur) { self.window_state.lock().unwrap().set_blur(blur);
self.request_redraw();
}
} }
#[inline] #[inline]

View File

@@ -29,6 +29,7 @@ use sctk::shm::slot::SlotPool;
use sctk::subcompositor::SubcompositorState; use sctk::subcompositor::SubcompositorState;
use tracing::{info, warn}; use tracing::{info, warn};
use wayland_protocols::xdg::toplevel_icon::v1::client::xdg_toplevel_icon_manager_v1::XdgToplevelIconManagerV1; use wayland_protocols::xdg::toplevel_icon::v1::client::xdg_toplevel_icon_manager_v1::XdgToplevelIconManagerV1;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use winit_core::cursor::{CursorIcon, CustomCursor as CoreCustomCursor}; use winit_core::cursor::{CursorIcon, CustomCursor as CoreCustomCursor};
use winit_core::error::{NotSupportedError, RequestError}; use winit_core::error::{NotSupportedError, RequestError};
use winit_core::window::{ use winit_core::window::{
@@ -42,8 +43,8 @@ use crate::seat::{
ZwpTextInputV3Ext, ZwpTextInputV3Ext,
}; };
use crate::state::{WindowCompositorUpdate, WinitState}; use crate::state::{WindowCompositorUpdate, WinitState};
use crate::types::bgr_effects::{BgrEffectManager, SurfaceBlurEffect};
use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor}; use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor};
use crate::types::kwin_blur::KWinBlurManager;
use crate::types::xdg_toplevel_icon_manager::ToplevelIcon; use crate::types::xdg_toplevel_icon_manager::ToplevelIcon;
#[cfg(feature = "sctk-adwaita")] #[cfg(feature = "sctk-adwaita")]
@@ -155,8 +156,8 @@ pub struct WindowState {
viewport: Option<WpViewport>, viewport: Option<WpViewport>,
fractional_scale: Option<WpFractionalScaleV1>, fractional_scale: Option<WpFractionalScaleV1>,
blur: Option<SurfaceBlurEffect>, blur: Option<OrgKdeKwinBlur>,
blur_manager: Option<BgrEffectManager>, blur_manager: Option<KWinBlurManager>,
/// Whether the client side decorations have pending move operations. /// Whether the client side decorations have pending move operations.
/// ///
@@ -205,7 +206,7 @@ impl WindowState {
toplevel_icon: None, toplevel_icon: None,
xdg_toplevel_icon_manager, xdg_toplevel_icon_manager,
blur: None, blur: None,
blur_manager: winit_state.blur_manager.clone(), blur_manager: winit_state.kwin_blur_manager.clone(),
compositor, compositor,
handle, handle,
csd_fails: false, csd_fails: false,
@@ -741,13 +742,6 @@ impl WindowState {
// Set surface size without the borders. // Set surface size without the borders.
viewport.set_destination(self.size.width as _, self.size.height as _); viewport.set_destination(self.size.width as _, self.size.height as _);
} }
// Update blur region with new size.
if self.blur.is_some() {
// NOTE: either user resized or configure, in both cases
// the redraw scheduling is done on the caller side.
let _ = self.set_blur(true);
}
} }
/// Get the scale factor of the window. /// Get the scale factor of the window.
@@ -1119,37 +1113,20 @@ impl WindowState {
} }
} }
/// Make window background blurred. /// Make window background blurred
/// #[inline]
/// Returns `true` if redraw is required. pub fn set_blur(&mut self, blurred: bool) {
#[must_use] if blurred && self.blur.is_none() {
pub fn set_blur(&mut self, blurred: bool) -> bool { if let Some(blur_manager) = self.blur_manager.as_ref() {
if !blurred { let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle);
self.blur = None; blur.commit();
return true; self.blur = Some(blur);
} } else {
info!("Blur manager unavailable, unable to change blur")
let mgr = match self.blur_manager.as_mut() { }
Some(mgr) => mgr, } else if !blurred && self.blur.is_some() {
None => { self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface());
info!("Blur manager unavailable, unable to change blur"); self.blur.take().unwrap().release();
return false;
},
};
let blur = match self.blur.as_ref() {
Some(blur) => blur,
None => {
self.blur = Some(mgr.new_blur_effect(self.window.wl_surface(), &self.queue_handle));
self.blur.as_ref().unwrap()
},
};
if let Ok(region) = Region::new(&*self.compositor) {
region.add(0, 0, i32::MAX, i32::MAX);
blur.set_blur(Some(&region))
} else {
false
} }
} }
@@ -1247,6 +1224,10 @@ impl WindowState {
impl Drop for WindowState { impl Drop for WindowState {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(blur) = self.blur.take() {
blur.release();
}
if let Some(fs) = self.fractional_scale.take() { if let Some(fs) = self.fractional_scale.take() {
fs.destroy(); fs.destroy();
} }

View File

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

View File

@@ -51,12 +51,12 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
SystemParametersInfoW, TranslateMessage, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE, SystemParametersInfoW, TranslateMessage, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CLOSE,
WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO,
WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION,
WM_INPUT, WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS,
WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH,
WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WMSZ_BOTTOM, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WMSZ_BOTTOM,
WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT,
WMSZ_TOPRIGHT, WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WMSZ_TOPRIGHT, WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW,
WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
@@ -1508,21 +1508,6 @@ unsafe fn public_window_callback_inner(
result = ProcResult::Value(unsafe { DefWindowProcW(window, msg, wparam, lparam) }); result = ProcResult::Value(unsafe { DefWindowProcW(window, msg, wparam, lparam) });
}, },
WM_INPUTLANGCHANGE => {
// Refresh the cached keyboard layout for the newly activated input
// language. This message is sent (by Windows or by layout switchers
// such as Punto Switcher) after the layout changes. Refreshing the
// cache here prevents a freeze that otherwise occurs when switching
// layout via such tools. We still defer to `DefWindowProc` so the
// message keeps propagating to first-level child windows, as the
// Win32 documentation requires.
{
let mut layouts = LAYOUT_CACHE.lock().unwrap();
layouts.get_current_layout();
}
result = ProcResult::DefWindowProc(wparam);
},
// this is necessary for us to maintain minimize/restore state // this is necessary for us to maintain minimize/restore state
WM_SYSCOMMAND => { WM_SYSCOMMAND => {
if wparam == SC_RESTORE as usize { if wparam == SC_RESTORE as usize {

View File

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

View File

@@ -1757,7 +1757,7 @@ impl EventProcessor {
.find(|prev_monitor| prev_monitor.name == new_monitor.name) .find(|prev_monitor| prev_monitor.name == new_monitor.name)
.map(|prev_monitor| prev_monitor.scale_factor); .map(|prev_monitor| prev_monitor.scale_factor);
if Some(new_monitor.scale_factor) != maybe_prev_scale_factor { if Some(new_monitor.scale_factor) != maybe_prev_scale_factor {
for window in self.target.windows.borrow().values().filter_map(|w| w.upgrade()) { for window in self.target.windows.borrow().iter().filter_map(|(_, w)| w.upgrade()) {
window.refresh_dpi_for_monitor( window.refresh_dpi_for_monitor(
&new_monitor, &new_monitor,
maybe_prev_scale_factor, maybe_prev_scale_factor,

View File

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

View File

@@ -76,13 +76,9 @@ winit-core.workspace = true
[dev-dependencies] [dev-dependencies]
image = { workspace = true, features = ["png"] } image = { workspace = true, features = ["png"] }
softbuffer.workspace = true
tracing = { workspace = true, features = ["log"] } tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter"] } tracing-subscriber = { workspace = true, features = ["env-filter"] }
# Launching a window without drawing to it has unpredictable results varying from platform to
# platform. We use the `softbuffer` crate in our examples because of its ease of use to avoid
# confusion around this. `glutin` or `wgpu` could also be used to fill the window buffer, but they
# are more complicated to set up.
softbuffer.workspace = true
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
winit-android.workspace = true winit-android.workspace = true

View File

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

View File

@@ -15,6 +15,7 @@ use std::time::Instant;
use std::{fmt, mem}; use std::{fmt, mem};
use cursor_icon::CursorIcon; use cursor_icon::CursorIcon;
use rwh_06::{DisplayHandle, HasDisplayHandle};
use softbuffer::{Context, Surface}; use softbuffer::{Context, Surface};
use tracing::{error, info}; use tracing::{error, info};
#[cfg(web_platform)] #[cfg(web_platform)]
@@ -24,7 +25,7 @@ use winit::cursor::{Cursor, CustomCursor, CustomCursorSource};
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::error::RequestError; use winit::error::RequestError;
use winit::event::{DeviceEvent, DeviceId, MouseButton, MouseScrollDelta, WindowEvent}; use winit::event::{DeviceEvent, DeviceId, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::icon::{Icon, RgbaIcon}; use winit::icon::{Icon, RgbaIcon};
use winit::keyboard::{Key, ModifiersState}; use winit::keyboard::{Key, ModifiersState};
use winit::monitor::Fullscreen; use winit::monitor::Fullscreen;
@@ -44,6 +45,9 @@ use winit_core::application::macos::ApplicationHandlerExtMacOS;
#[path = "util/tracing.rs"] #[path = "util/tracing.rs"]
mod tracing_init; mod tracing_init;
#[path = "util/fill.rs"]
mod fill;
/// The amount of points to around the window for drag resize direction calculations. /// The amount of points to around the window for drag resize direction calculations.
const BORDER_SIZE: f64 = 20.; const BORDER_SIZE: f64 = 20.;
@@ -90,12 +94,20 @@ struct Application {
/// Drawing context. /// Drawing context.
/// ///
/// With OpenGL it could be EGLDisplay. /// With OpenGL it could be EGLDisplay.
context: Context<OwnedDisplayHandle>, context: Option<Context<DisplayHandle<'static>>>,
} }
impl Application { impl Application {
fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self { fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
let context = Context::new(event_loop.owned_display_handle()).unwrap(); // SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
let context = Some(
Context::new(unsafe {
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
event_loop.display_handle().unwrap(),
)
})
.unwrap(),
);
// You'll have to choose an icon size at your own discretion. On X11, the desired size // You'll have to choose an icon size at your own discretion. On X11, the desired size
// varies by WM, and on Windows, you still have to account for screen scaling. Here // varies by WM, and on Windows, you still have to account for screen scaling. Here
@@ -529,7 +541,16 @@ impl ApplicationHandler for Application {
WindowEvent::DoubleTapGesture { .. } => { WindowEvent::DoubleTapGesture { .. } => {
info!("Smart zoom"); info!("Smart zoom");
}, },
_ => (), WindowEvent::TouchpadPressure { .. }
| WindowEvent::DragLeft { .. }
| WindowEvent::KeyboardInput { .. }
| WindowEvent::PointerEntered { .. }
| WindowEvent::DragEntered { .. }
| WindowEvent::DragMoved { .. }
| WindowEvent::DragDropped { .. }
| WindowEvent::Destroyed
| WindowEvent::Ime(_)
| WindowEvent::Moved(_) => (),
} }
} }
@@ -584,7 +605,9 @@ impl ApplicationHandlerExtMacOS for Application {
/// State of the window. /// State of the window.
struct WindowState { struct WindowState {
/// Render surface. /// Render surface.
surface: Surface<OwnedDisplayHandle, Arc<dyn Window>>, ///
/// NOTE: This surface must be dropped before the `Window`.
surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>,
/// The actual winit Window. /// The actual winit Window.
window: Arc<dyn Window>, window: Arc<dyn Window>,
/// The window theme we're drawing with. /// The window theme we're drawing with.
@@ -625,7 +648,9 @@ impl WindowState {
fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> { fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> {
let window: Arc<dyn Window> = Arc::from(window); let window: Arc<dyn Window> = Arc::from(window);
let surface = Surface::new(&app.context, Arc::clone(&window))?; // SAFETY: the surface is dropped before the `window` which provided it with handle, thus
// it doesn't outlive it.
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
let theme = window.theme().unwrap_or(Theme::Dark); let theme = window.theme().unwrap_or(Theme::Dark);
info!("Theme: {theme:?}"); info!("Theme: {theme:?}");
@@ -912,40 +937,35 @@ impl WindowState {
return Ok(()); return Ok(());
} }
if self.animated_fill_color {
fill::fill_window_with_animated_color(&*self.window, self.start_time);
return Ok(());
}
let mut buffer = self.surface.buffer_mut()?; let mut buffer = self.surface.buffer_mut()?;
if self.animated_fill_color { // Draw a different color inside the safe area
// Fill the entire buffer with a single color. let surface_size = self.window.surface_size();
let time = self.start_time.elapsed().as_secs_f32() * 1.5; let insets = self.window.safe_area();
let blue = (time.sin() * 255.0) as u32; for y in 0..surface_size.height {
let green = ((time.cos() * 255.0) as u32) << 8; for x in 0..surface_size.width {
let red = ((1.0 - time.sin() * 255.0) as u32) << 16; let index = y as usize * surface_size.width as usize + x as usize;
let color = red | green | blue; if insets.left <= x
buffer.fill(color); && x <= (surface_size.width - insets.right)
} else { && insets.top <= y
// Draw a different color inside the safe area && y <= (surface_size.height - insets.bottom)
let surface_size = self.window.surface_size(); {
let insets = self.window.safe_area(); // In safe area
for y in 0..surface_size.height { buffer[index] = match self.theme {
for x in 0..surface_size.width { Theme::Light => 0xffe8e8e8, // Light gray
let index = y as usize * surface_size.width as usize + x as usize; Theme::Dark => 0xff525252, // Medium gray
if insets.left <= x };
&& x <= (surface_size.width - insets.right) } else {
&& insets.top <= y // Outside safe area
&& y <= (surface_size.height - insets.bottom) buffer[index] = match self.theme {
{ Theme::Light => 0xffffffff, // White
// In safe area Theme::Dark => 0xff181818, // Dark gray
buffer[index] = match self.theme { };
Theme::Light => 0xffe8e8e8, // Light gray
Theme::Dark => 0xff525252, // Medium gray
};
} else {
// Outside safe area
buffer[index] = match self.theme {
Theme::Light => 0xffffffff, // White
Theme::Dark => 0xff181818, // Dark gray
};
}
} }
} }
} }

View File

@@ -3,12 +3,10 @@
fn main() -> Result<(), impl std::error::Error> { fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap; use std::collections::HashMap;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize, Position}; use winit::dpi::{LogicalPosition, LogicalSize, Position};
use winit::event::{ElementState, KeyEvent, WindowEvent}; use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::raw_window_handle::HasRawWindowHandle; use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
@@ -17,20 +15,18 @@ fn main() -> Result<(), impl std::error::Error> {
#[derive(Debug)] #[derive(Debug)]
struct WindowData { struct WindowData {
surface: Surface<OwnedDisplayHandle, Box<dyn Window>>, window: Box<dyn Window>,
color: u32, color: u32,
} }
impl WindowData { impl WindowData {
fn new(context: &Context<OwnedDisplayHandle>, window: Box<dyn Window>, color: u32) -> Self { fn new(window: Box<dyn Window>, color: u32) -> Self {
let surface = Surface::new(context, window).unwrap(); Self { window, color }
Self { surface, color }
} }
} }
#[derive(Debug)] #[derive(Default, Debug)]
struct Application { struct Application {
context: Context<OwnedDisplayHandle>,
parent_window_id: Option<WindowId>, parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, WindowData>, windows: HashMap<WindowId, WindowData>,
} }
@@ -42,10 +38,10 @@ fn main() -> Result<(), impl std::error::Error> {
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32)); .with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap(); let window = event_loop.create_window(attributes).unwrap();
info!("Parent window id: {:?})", window.id()); println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id()); self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), WindowData::new(&self.context, window, 0xffbbbbbb)); self.windows.insert(window.id(), WindowData::new(window, 0xffbbbbbb));
} }
fn window_event( fn window_event(
@@ -60,12 +56,12 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.exit(); event_loop.exit();
}, },
WindowEvent::PointerEntered { device_id: _, .. } => { WindowEvent::PointerEntered { device_id: _, .. } => {
// On x11, log when the cursor entered in a window even if the child window // On x11, println when the cursor entered in a window even if the child window
// is created by some key inputs. // is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the // 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 // parent window, so we also can see this log when we move
// the cursor around (200, 200) in parent window. // the cursor around (200, 200) in parent window.
info!("cursor entered in the window {window_id:?}"); println!("cursor entered in the window {window_id:?}");
}, },
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. }, event: KeyEvent { state: ElementState::Pressed, .. },
@@ -76,24 +72,18 @@ fn main() -> Result<(), impl std::error::Error> {
0xff000000 + 3_u32.pow((child_index + 2).rem_euclid(16) as u32); 0xff000000 + 3_u32.pow((child_index + 2).rem_euclid(16) as u32);
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap(); let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window( let child_window =
parent_window.surface.window().as_ref(), spawn_child_window(parent_window.window.as_ref(), event_loop, child_index);
event_loop,
child_index,
);
let child_id = child_window.id(); let child_id = child_window.id();
info!("Child window created with id: {child_id:?}"); println!("Child window created with id: {child_id:?}");
self.windows.insert( self.windows.insert(child_id, WindowData::new(child_window, child_color));
child_id,
WindowData::new(&self.context, child_window, child_color),
);
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get_mut(&window_id) { if let Some(window) = self.windows.get(&window_id) {
if window_id == self.parent_window_id.unwrap() { if window_id == self.parent_window_id.unwrap() {
fill::fill(&mut window.surface); fill::fill_window(window.window.as_ref());
} else { } else {
fill::fill_with_color(&mut window.surface, window.color); fill::fill_window_with_color(window.window.as_ref(), window.color);
} }
} }
}, },
@@ -127,8 +117,7 @@ fn main() -> Result<(), impl std::error::Error> {
} }
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let context = Context::new(event_loop.owned_display_handle()).unwrap(); event_loop.run_app(Application::default())
event_loop.run_app(Application { context, parent_window_id: None, windows: HashMap::new() })
} }
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))] #[cfg(not(any(x11_platform, macos_platform, windows_platform)))]

View File

@@ -4,13 +4,12 @@ use std::thread;
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
use std::time; use std::time;
use softbuffer::{Context, Surface}; use ::tracing::{info, warn};
use tracing::{info, warn};
#[cfg(web_platform)] #[cfg(web_platform)]
use web_time as time; use web_time as time;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent}; use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::{Key, NamedKey}; use winit::keyboard::{Key, NamedKey};
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
@@ -53,7 +52,7 @@ struct ControlFlowDemo {
request_redraw: bool, request_redraw: bool,
wait_cancelled: bool, wait_cancelled: bool,
close_requested: bool, close_requested: bool,
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>, window: Option<Box<dyn Window>>,
} }
impl ApplicationHandler for ControlFlowDemo { impl ApplicationHandler for ControlFlowDemo {
@@ -70,10 +69,7 @@ impl ApplicationHandler for ControlFlowDemo {
let window_attributes = WindowAttributes::default().with_title( let window_attributes = WindowAttributes::default().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.", "Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
); );
let window = event_loop.create_window(window_attributes).unwrap(); self.window = Some(event_loop.create_window(window_attributes).unwrap());
let context = Context::new(event_loop.owned_display_handle()).unwrap();
let surface = Surface::new(&context, window).unwrap();
self.surface = Some(surface);
} }
fn window_event( fn window_event(
@@ -116,9 +112,9 @@ impl ApplicationHandler for ControlFlowDemo {
_ => (), _ => (),
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
let surface = self.surface.as_mut().unwrap(); let window = self.window.as_ref().unwrap();
surface.window().pre_present_notify(); window.pre_present_notify();
fill::fill(surface); fill::fill_window(window.as_ref());
}, },
_ => (), _ => (),
} }
@@ -126,7 +122,7 @@ impl ApplicationHandler for ControlFlowDemo {
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
if self.request_redraw && !self.wait_cancelled && !self.close_requested { if self.request_redraw && !self.wait_cancelled && !self.close_requested {
self.surface.as_ref().unwrap().window().request_redraw(); self.window.as_ref().unwrap().request_redraw();
} }
match self.mode { match self.mode {

View File

@@ -1,10 +1,8 @@
use std::error::Error; use std::error::Error;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -17,24 +15,27 @@ fn main() -> Result<(), Box<dyn Error>> {
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
let app = Application::default(); let app = Application::new();
Ok(event_loop.run_app(app)?) Ok(event_loop.run_app(app)?)
} }
/// Application state and event handling. /// Application state and event handling.
#[derive(Default, Debug)] #[derive(Debug)]
struct Application { struct Application {
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>, window: Option<Box<dyn Window>>,
}
impl Application {
fn new() -> Self {
Self { window: None }
}
} }
impl ApplicationHandler for Application { impl ApplicationHandler for Application {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = let window_attributes =
WindowAttributes::default().with_title("Drag and drop files on me!"); WindowAttributes::default().with_title("Drag and drop files on me!");
let window = event_loop.create_window(window_attributes).unwrap(); self.window = Some(event_loop.create_window(window_attributes).unwrap());
let context = Context::new(event_loop.owned_display_handle()).unwrap();
let surface = Surface::new(&context, window).unwrap();
self.surface = Some(surface);
} }
fn window_event( fn window_event(
@@ -48,12 +49,12 @@ impl ApplicationHandler for Application {
| WindowEvent::DragEntered { .. } | WindowEvent::DragEntered { .. }
| WindowEvent::DragMoved { .. } | WindowEvent::DragMoved { .. }
| WindowEvent::DragDropped { .. } => { | WindowEvent::DragDropped { .. } => {
info!("{event:?}"); println!("{event:?}");
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
let surface = self.surface.as_mut().unwrap(); let window = self.window.as_ref().unwrap();
surface.window().pre_present_notify(); window.pre_present_notify();
fill::fill(surface); fill::fill_window(window.as_ref());
}, },
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
event_loop.exit(); event_loop.exit();

View File

@@ -9,11 +9,10 @@ use std::cmp;
use std::error::Error; use std::error::Error;
use dpi::{LogicalPosition, PhysicalSize}; use dpi::{LogicalPosition, PhysicalSize};
use softbuffer::{Context, Surface};
use tracing::{error, info}; use tracing::{error, info};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::{Ime, WindowEvent}; use winit::event::{Ime, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState, NamedKey}; use winit::keyboard::{Key, ModifiersState, NamedKey};
#[cfg(web_platform)] #[cfg(web_platform)]
use winit::platform::web::WindowAttributesWeb; use winit::platform::web::WindowAttributesWeb;
@@ -31,7 +30,7 @@ const IME_CURSOR_SIZE: PhysicalSize<u32> = PhysicalSize::new(20, 20);
#[derive(Debug)] #[derive(Debug)]
struct App { struct App {
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>, window: Option<Box<dyn Window>>,
input_state: TextInputState, input_state: TextInputState,
modifiers: ModifiersState, modifiers: ModifiersState,
} }
@@ -74,12 +73,14 @@ impl ApplicationHandler for App {
#[cfg(web_platform)] #[cfg(web_platform)]
let window_attributes = WindowAttributes::default() let window_attributes = WindowAttributes::default()
.with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true))); .with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true)));
let window = event_loop.create_window(window_attributes).expect("failed creating window"); self.window = match event_loop.create_window(window_attributes) {
Ok(window) => Some(window),
let context = Err(err) => {
Context::new(event_loop.owned_display_handle()).expect("failed creating context"); eprintln!("error creating window: {err}");
let surface = Surface::new(&context, window).expect("failed creating surface"); event_loop.exit();
self.surface = Some(surface); return;
},
};
// Allow IME out of the box. // Allow IME out of the box.
let enable_request = ImeEnableRequest::new( let enable_request = ImeEnableRequest::new(
@@ -100,13 +101,11 @@ impl ApplicationHandler for App {
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
info!("Close was requested; stopping"); info!("Close was requested; stopping");
self.surface = None; self.window = None;
event_loop.exit(); event_loop.exit();
}, },
WindowEvent::SurfaceResized(surface_size) => { WindowEvent::SurfaceResized(_) => {
let surface = self.surface.as_mut().expect("resize event without a surface"); self.window.as_ref().expect("resize event without a window").request_redraw();
fill::resize(surface, surface_size);
surface.window().request_redraw();
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
// Redraw the application. // Redraw the application.
@@ -115,13 +114,13 @@ impl ApplicationHandler for App {
// this event rather than in AboutToWait, since rendering in here allows // this event rather than in AboutToWait, since rendering in here allows
// the program to gracefully handle redraws requested by the OS. // the program to gracefully handle redraws requested by the OS.
let surface = self.surface.as_mut().expect("redraw event without a surface"); let window = self.window.as_ref().expect("redraw request without a window");
// Notify that you're about to draw. // Notify that you're about to draw.
surface.window().pre_present_notify(); window.pre_present_notify();
// Draw. // Draw.
fill::fill(surface); fill::fill_window(window.as_ref());
// For contiguous redraw loop you can request a redraw from here. // For contiguous redraw loop you can request a redraw from here.
// window.request_redraw(); // window.request_redraw();
@@ -215,14 +214,14 @@ impl App {
} }
fn handle_ime_event(&mut self, event: Ime) { fn handle_ime_event(&mut self, event: Ime) {
let surface = self.surface.as_ref().expect("IME request without a window"); let window = self.window.as_ref().expect("IME request without a window");
match event { match event {
Ime::Enabled => info!("IME enabled for Window={:?}", surface.window().id()), Ime::Enabled => info!("IME enabled for Window={:?}", window.id()),
Ime::Preedit(text, caret_pos) => info!("Preedit: {text}, with caret at {caret_pos:?}"), Ime::Preedit(text, caret_pos) => info!("Preedit: {text}, with caret at {caret_pos:?}"),
Ime::Commit(text) => { Ime::Commit(text) => {
self.input_state.append_text(&text); self.input_state.append_text(&text);
let request_data = self.get_ime_update(); let request_data = self.get_ime_update();
surface.window().request_ime_update(ImeRequest::Update(request_data)).unwrap(); window.request_ime_update(ImeRequest::Update(request_data)).unwrap();
self.print_input_state(); self.print_input_state();
}, },
Ime::DeleteSurrounding { before_bytes, after_bytes } => { Ime::DeleteSurrounding { before_bytes, after_bytes } => {
@@ -247,7 +246,7 @@ impl App {
error!("Buggy IME tried to delete with indices not on char boundary."); error!("Buggy IME tried to delete with indices not on char boundary.");
} }
}, },
Ime::Disabled => info!("IME disabled for Window={:?}", surface.window().id()), Ime::Disabled => info!("IME disabled for Window={:?}", window.id()),
} }
} }
@@ -313,7 +312,7 @@ impl App {
} }
fn window(&self) -> &dyn Window { fn window(&self) -> &dyn Window {
self.surface.as_ref().unwrap().window().as_ref() self.window.as_ref().unwrap().as_ref()
} }
} }
@@ -347,7 +346,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
info!( println!(
r#"This showcases the use of an input method engine (IME) by emulating a text edit field. 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+i to toggle IME support.
Use CTRL+p to cycle content purpose values. Use CTRL+p to cycle content purpose values.
@@ -356,7 +355,7 @@ Use CTRL+h to cycle content hint permutations.
); );
let app = App { let app = App {
surface: None, window: None,
input_state: TextInputState { input_state: TextInputState {
ime_enabled: true, ime_enabled: true,
contents: String::new(), contents: String::new(),

View File

@@ -7,31 +7,24 @@ fn main() -> std::process::ExitCode {
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::pump_events::{EventLoopExtPumpEvents, PumpStatus}; use winit::event_loop::pump_events::{EventLoopExtPumpEvents, PumpStatus};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[path = "util/tracing.rs"]
mod tracing;
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct PumpDemo { struct PumpDemo {
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>, window: Option<Box<dyn Window>>,
} }
impl ApplicationHandler for PumpDemo { impl ApplicationHandler for PumpDemo {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default().with_title("A fantastic window!"); let window_attributes = WindowAttributes::default().with_title("A fantastic window!");
let window = event_loop.create_window(window_attributes).unwrap(); self.window = Some(event_loop.create_window(window_attributes).unwrap());
let context = Context::new(event_loop.owned_display_handle()).unwrap();
self.surface = Some(Surface::new(&context, window).unwrap());
} }
fn window_event( fn window_event(
@@ -40,28 +33,28 @@ fn main() -> std::process::ExitCode {
_window_id: WindowId, _window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
info!("{event:?}"); println!("{event:?}");
let surface = match self.surface.as_mut() { let window = match self.window.as_ref() {
Some(surface) => surface, Some(window) => window,
None => return, None => return,
}; };
match event { match event {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
fill::fill(surface); fill::fill_window(window.as_ref());
surface.window().request_redraw(); window.request_redraw();
}, },
_ => (), _ => (),
} }
} }
} }
tracing::init();
let mut event_loop = EventLoop::new().unwrap(); let mut event_loop = EventLoop::new().unwrap();
tracing_subscriber::fmt::init();
let mut app = PumpDemo::default(); let mut app = PumpDemo::default();
loop { loop {
@@ -76,12 +69,12 @@ fn main() -> std::process::ExitCode {
// //
// Since `pump_events` doesn't block it will be important to // Since `pump_events` doesn't block it will be important to
// throttle the loop in the app somehow. // throttle the loop in the app somehow.
info!("Update()"); println!("Update()");
sleep(Duration::from_millis(16)); sleep(Duration::from_millis(16));
} }
} }
#[cfg(any(ios_platform, web_platform, orbital_platform))] #[cfg(any(ios_platform, web_platform, orbital_platform))]
fn main() { fn main() {
panic!("This platform doesn't support pump_events.") println!("This platform doesn't support pump_events.");
} }

View File

@@ -5,43 +5,36 @@
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::time::Duration; use std::time::Duration;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand; use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand;
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[path = "util/tracing.rs"]
mod tracing;
#[derive(Debug)] #[derive(Default, Debug)]
struct App { struct App {
context: Context<OwnedDisplayHandle>,
idx: usize, idx: usize,
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
window_id: Option<WindowId>, window_id: Option<WindowId>,
window: Option<Box<dyn Window>>,
} }
impl ApplicationHandler for App { impl ApplicationHandler for App {
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
if let Some(surface) = self.surface.as_ref() { if let Some(window) = self.window.as_ref() {
surface.window().request_redraw(); window.request_redraw();
} }
} }
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default() let window_attributes = WindowAttributes::default()
.with_title(format!("Fantastic window number {}!", self.idx)) .with_title("Fantastic window number one!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0)); .with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0));
let window = event_loop.create_window(window_attributes).unwrap(); let window = event_loop.create_window(window_attributes).unwrap();
self.window_id = Some(window.id()); self.window_id = Some(window.id());
self.window = Some(window);
let surface = Surface::new(&self.context, window).unwrap();
self.surface = Some(surface);
} }
fn window_event( fn window_event(
@@ -51,44 +44,52 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
event: WindowEvent, event: WindowEvent,
) { ) {
if event == WindowEvent::Destroyed && self.window_id == Some(window_id) { if event == WindowEvent::Destroyed && self.window_id == Some(window_id) {
info!("Window {} Destroyed", self.idx); println!(
"--------------------------------------------------------- Window {} Destroyed",
self.idx
);
self.window_id = None; self.window_id = None;
event_loop.exit(); event_loop.exit();
return; return;
} }
let Some(surface) = self.surface.as_mut() else { let window = match self.window.as_mut() {
return; Some(window) => window,
None => return,
}; };
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
info!("Window {} CloseRequested", self.idx); println!(
self.surface = None; "--------------------------------------------------------- Window {} \
CloseRequested",
self.idx
);
fill::cleanup_window(window.as_ref());
self.window = None;
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
fill::fill(surface); fill::fill_window(window.as_ref());
}, },
_ => (), _ => (),
} }
} }
} }
tracing::init(); tracing_subscriber::fmt::init();
let mut event_loop = EventLoop::new().unwrap(); let mut event_loop = EventLoop::new().unwrap();
let context = Context::new(event_loop.owned_display_handle()).unwrap(); let mut app = App { idx: 1, ..Default::default() };
let mut app = App { context, idx: 1, surface: None, window_id: None };
event_loop.run_app_on_demand(&mut app)?; event_loop.run_app_on_demand(&mut app)?;
info!("Finished first loop"); println!("--------------------------------------------------------- Finished first loop");
info!("Waiting 5 seconds"); println!("--------------------------------------------------------- Waiting 5 seconds");
std::thread::sleep(Duration::from_secs(5)); std::thread::sleep(Duration::from_secs(5));
app.idx += 1; app.idx += 1;
event_loop.run_app_on_demand(&mut app)?; event_loop.run_app_on_demand(&mut app)?;
info!("Finished second loop"); println!("--------------------------------------------------------- Finished second loop");
Ok(()) Ok(())
} }
@@ -100,5 +101,5 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
orbital_platform orbital_platform
)))] )))]
fn main() { fn main() {
panic!("This example is not supported on this platform") println!("This example is not supported on this platform");
} }

View File

@@ -1,43 +1,115 @@
//! Fill the window buffer with a solid color.
//!
//! Launching a window without drawing to it has unpredictable results varying from platform to
//! platform. In order to have well-defined examples, this module provides an easy way to
//! fill the window buffer with a solid color.
//!
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
//! also be used to fill the window buffer, but they are more complicated to use.
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32; use std::num::NonZeroU32;
#[cfg(not(web_platform))]
use std::time::Instant;
use rwh_06::{HasDisplayHandle, HasWindowHandle}; use softbuffer::{Context, Surface};
use softbuffer::Surface; #[cfg(web_platform)]
use winit::window::Window; use web_time::Instant;
use winit::window::{Window, WindowId};
/// Resize the surface. thread_local! {
pub fn resize( // NOTE: You should never do things like that, create context and drop it before
surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle>, // you drop the event loop. We do this for brevity to not blow up examples. We use
surface_size: dpi::PhysicalSize<u32>, // ManuallyDrop to prevent destructors from running.
) {
// Handle zero-sized buffers.
// //
// FIXME(madsmtm): This should be done by softbuffer internally in the future: // A static, thread-local map of graphics contexts to open windows.
// https://github.com/rust-windowing/softbuffer/issues/238 static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
let (Some(width), Some(height)) =
(NonZeroU32::new(surface_size.width), NonZeroU32::new(surface_size.height))
else {
return;
};
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
} }
/// Fill the window buffer with a solid color. /// The graphics context used to draw to a window.
pub fn fill_with_color( struct GraphicsContext {
surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle + AsRef<dyn Window>>, /// The global softbuffer context.
color: u32, context: RefCell<Context<&'static dyn Window>>,
) {
let surface_size = surface.window().as_ref().surface_size();
resize(surface, surface_size);
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer"); /// The hash map of window IDs to surfaces.
buffer.fill(color); surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>,
buffer.present().expect("Failed to present the softbuffer buffer"); }
impl GraphicsContext {
fn new(w: &dyn Window) -> Self {
Self {
context: RefCell::new(
Context::new(unsafe { mem::transmute::<&'_ dyn Window, &'static dyn Window>(w) })
.expect("Failed to create a softbuffer context"),
),
surfaces: HashMap::new(),
}
}
fn create_surface(
&mut self,
window: &dyn Window,
) -> &mut Surface<&'static dyn Window, &'static dyn Window> {
self.surfaces.entry(window.id()).or_insert_with(|| {
Surface::new(&self.context.borrow(), unsafe {
mem::transmute::<&'_ dyn Window, &'static dyn Window>(window)
})
.expect("Failed to create a softbuffer surface")
})
}
fn destroy_surface(&mut self, window: &dyn Window) {
self.surfaces.remove(&window.id());
}
}
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
GC.with(|gc| {
let size = window.surface_size();
let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
else {
return;
};
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface = gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
// Fill a buffer with a solid color
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
buffer.fill(color);
buffer.present().expect("Failed to present the softbuffer buffer");
})
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn fill( pub fn fill_window(window: &dyn Window) {
surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle + AsRef<dyn Window>>, fill_window_with_color(window, 0xff181818);
) { }
fill_with_color(surface, 0xff181818);
#[allow(dead_code)]
pub fn fill_window_with_animated_color(window: &dyn Window, start: Instant) {
let time = start.elapsed().as_secs_f32() * 1.5;
let blue = (time.sin() * 255.0) as u32;
let green = ((time.cos() * 255.0) as u32) << 8;
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
let color = red | green | blue;
fill_window_with_color(window, color);
}
#[allow(dead_code)]
pub fn cleanup_window(window: &dyn Window) {
GC.with(|gc| {
let mut gc = gc.borrow_mut();
if let Some(context) = gc.as_mut() {
context.destroy_surface(window);
}
});
} }

View File

@@ -23,6 +23,3 @@ pub fn init() {
) )
.init(); .init();
} }
#[allow(unused_imports)]
pub use ::tracing::*;

View File

@@ -1,12 +1,11 @@
//! Simple winit window example. //! Simple winit window example.
use std::error::Error; use std::error::Error;
use std::time::Instant;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
#[cfg(web_platform)] #[cfg(web_platform)]
use winit::platform::web::WindowAttributesWeb; use winit::platform::web::WindowAttributesWeb;
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
@@ -18,7 +17,7 @@ mod tracing;
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct App { struct App {
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>, window: Option<Box<dyn Window>>,
} }
impl ApplicationHandler for App { impl ApplicationHandler for App {
@@ -28,25 +27,31 @@ impl ApplicationHandler for App {
#[cfg(web_platform)] #[cfg(web_platform)]
let window_attributes = WindowAttributes::default() let window_attributes = WindowAttributes::default()
.with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true))); .with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true)));
let window = event_loop.create_window(window_attributes).expect("failed creating window"); self.window = match event_loop.create_window(window_attributes) {
Ok(window) => Some(window),
let context = Err(err) => {
Context::new(event_loop.owned_display_handle()).expect("failed creating context"); eprintln!("error creating window: {err}");
let surface = Surface::new(&context, window).expect("failed creating surface"); event_loop.exit();
self.surface = Some(surface); return;
},
}
} }
fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) { fn window_event(
info!("{event:?}"); &mut self,
event_loop: &dyn ActiveEventLoop,
_: WindowId,
timestamp: Instant,
event: WindowEvent,
) {
::tracing::info!("{:?}: {event:?}", timestamp.elapsed());
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
info!("Close was requested; stopping"); println!("Close was requested; stopping");
event_loop.exit(); event_loop.exit();
}, },
WindowEvent::SurfaceResized(surface_size) => { WindowEvent::SurfaceResized(_) => {
let surface = self.surface.as_mut().expect("resize event without a surface"); self.window.as_ref().expect("resize event without a window").request_redraw();
fill::resize(surface, surface_size);
surface.window().request_redraw();
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
// Redraw the application. // Redraw the application.
@@ -55,22 +60,30 @@ impl ApplicationHandler for App {
// this event rather than in AboutToWait, since rendering in here allows // this event rather than in AboutToWait, since rendering in here allows
// the program to gracefully handle redraws requested by the OS. // the program to gracefully handle redraws requested by the OS.
let surface = self.surface.as_mut().expect("redraw event without a surface"); let window = self.window.as_ref().expect("redraw request without a window");
// Notify that you're about to draw. // Notify that you're about to draw.
surface.window().pre_present_notify(); window.pre_present_notify();
// Draw. // Draw.
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer"); fill::fill_window(window.as_ref());
buffer.fill(0xff181818);
buffer.present().expect("Failed to present the softbuffer buffer");
// For contiguous redraw loop you can request a redraw from here. // 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>> { fn main() -> Result<(), Box<dyn Error>> {

View File

@@ -3,22 +3,19 @@ use std::error::Error;
#[cfg(x11_platform)] #[cfg(x11_platform)]
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::x11::WindowAttributesX11; use winit::platform::x11::WindowAttributesX11;
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[path = "util/tracing.rs"]
mod tracing;
#[derive(Debug)] #[derive(Debug)]
pub struct XEmbedDemo { pub struct XEmbedDemo {
parent_window_id: u32, parent_window_id: u32,
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>, window: Option<Box<dyn Window>>,
} }
impl ApplicationHandler for XEmbedDemo { impl ApplicationHandler for XEmbedDemo {
@@ -30,10 +27,7 @@ fn main() -> Result<(), Box<dyn Error>> {
WindowAttributesX11::default().with_embed_parent_window(self.parent_window_id); WindowAttributesX11::default().with_embed_parent_window(self.parent_window_id);
window_attributes = window_attributes.with_platform_attributes(Box::new(x11_attrs)); window_attributes = window_attributes.with_platform_attributes(Box::new(x11_attrs));
let window = event_loop.create_window(window_attributes).unwrap(); self.window = Some(event_loop.create_window(window_attributes).unwrap());
let context = Context::new(event_loop.owned_display_handle()).unwrap();
let surface = Surface::new(&context, window).unwrap();
self.surface = Some(surface);
} }
fn window_event( fn window_event(
@@ -42,19 +36,19 @@ fn main() -> Result<(), Box<dyn Error>> {
_window_id: WindowId, _window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
let window = self.window.as_ref().unwrap();
match event { match event {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
let surface = self.surface.as_mut().unwrap(); window.pre_present_notify();
surface.window().pre_present_notify(); fill::fill_window(window.as_ref());
fill::fill(surface);
}, },
_ => (), _ => (),
} }
} }
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
self.surface.as_ref().unwrap().window().request_redraw(); self.window.as_ref().unwrap().request_redraw();
} }
} }
@@ -64,13 +58,14 @@ fn main() -> Result<(), Box<dyn Error>> {
.ok_or("Expected a 32-bit X11 window ID as the first argument.")? .ok_or("Expected a 32-bit X11 window ID as the first argument.")?
.parse::<u32>()?; .parse::<u32>()?;
tracing::init(); tracing_subscriber::fmt::init();
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
Ok(event_loop.run_app(XEmbedDemo { parent_window_id, surface: None })?) Ok(event_loop.run_app(XEmbedDemo { parent_window_id, window: None })?)
} }
#[cfg(not(x11_platform))] #[cfg(not(x11_platform))]
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
panic!("This example is only supported on X11 platforms.") println!("This example is only supported on X11 platforms.");
Ok(())
} }

View File

@@ -46,10 +46,6 @@ changelog entry.
- On iOS, add Apple Pencil support with force, altitude, and azimuth data. - On iOS, add Apple Pencil support with force, altitude, and azimuth data.
- On Redox, add support for missing keyboard scancodes. - On Redox, add support for missing keyboard scancodes.
- Implement `Send` and `Sync` for `OwnedDisplayHandle`. - Implement `Send` and `Sync` for `OwnedDisplayHandle`.
- Use new macOS 15 cursors for resize icons.
- On Android, added scancode conversions for more obscure key codes.
- On Wayland, added `HoldGesture` event for multi-finger hold gestures
- On Wayland, added ext-background-effect-v1 support.
### Changed ### Changed
@@ -58,12 +54,7 @@ changelog entry.
### Fixed ### Fixed
- On Windows, fix a freeze that occurs when the keyboard layout is switched by
tools such as Punto Switcher. The `WM_INPUTLANGCHANGE` message is now handled
to refresh the cached keyboard layout, while still deferring to
`DefWindowProc` for normal propagation.
- On Redox, handle `EINTR` when reading from `event_socket` instead of panicking. - On Redox, handle `EINTR` when reading from `event_socket` instead of panicking.
- On Wayland, switch from using the `ahash` hashing algorithm to `foldhash`. - On Wayland, switch from using the `ahash` hashing algorithm to `foldhash`.
- On macOS, fix borderless game presentation options not sticking after switching spaces. - On macOS, fix borderless game presentation options not sticking after switching spaces.
- On macOS, fix IME being locked on (regardless of requests to disable) after being enabled once. - On macOS, fix IME being locked on (regardless of requests to disable) after being enabled once.
- On macOS, fix a panic and incorrect cursor position in Ime::Preedit when the preedit string contains special characters (ie. emojis) caused by incorrect UTF-16 to UTF-8 offset conversion.

View File

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