Compare commits

..

26 Commits

Author SHA1 Message Date
Vitaly Kravchenko
27e17e3f29 Fix AppKit live resize redraw timing 2026-06-24 17:03:24 +02:00
Simon Hausmann
56fde0791b Fix CI
Pin wayland-protocols to the previous version that still compiles with
our MSRV.
2026-06-24 16:58:57 +02:00
William-Selna
4ef144b519 macOS: make Ime::Preedit cursor range surrogate-safe in setMarkedText
`setMarkedText:selectedRange:replacementRange:` converted the IME's UTF-16
`selectedRange` into UTF-8 byte offsets by taking `substringToIndex:` prefixes
and measuring them with `NSString::len()`
(`lengthOfBytesUsingEncoding:NSUTF8StringEncoding`). When an index falls inside
a surrogate pair, the prefix ends in a lone high surrogate, which UTF-8 cannot
represent, so `lengthOfBytesUsingEncoding:` returns 0 for the entire prefix and
the offset silently collapses to 0.
2026-06-20 01:34:43 +09:00
Mads Marquart
7fe8c206ae chore: avoid unsafe in examples
By using `OwnedDisplayHandle`, which uses reference-counting, instead of
`DisplayHandle<'static>` (where the user has to guarantee that the
Application doesn't outlive the event loop).
2026-06-15 03:36:22 +09:00
Sergey Kikevich
fb78ecbb03 windows: fix freeze on keyboard layout switch
Any winit app on Windows freezes when the keyboard layout is switched by
tools such as Punto Switcher. A minidump of the frozen process shows the
main thread blocked in MsgWaitForMultipleObjectsEx (winit's event-loop
wait), re-entered during message dispatch through the switcher's injected
global hook (pshook64.dll, via CallNextHookEx) — a re-entrant Win32
message-loop wait, not a mutex deadlock (no LAYOUT_CACHE frames present).

Handle WM_INPUTLANGCHANGE to refresh the cached keyboard layout, then
defer to DefWindowProc so the message still propagates to first-level
child windows as the Win32 docs require. The cache refresh is the minimal
change that stops the freeze (verified on Windows 11 by isolation
variants); update_modifiers and swallowing the message are not needed.
2026-06-15 03:29:35 +09:00
Martin Marmsoler
850d5f59a7 winit-wayland: add pointer gesture hold 2026-06-13 02:36:23 +09:00
Martin Marmsoler
81b2729765 winit-x11: fix clippy lint 2026-06-05 02:25:58 +09:00
Kirill Chibisov
c4afadbfab winit-wayland: use ext-background-effect if available
A more modern protocol compared to the KDE one.
2026-04-04 23:59:33 +09:00
dependabot[bot]
b5252f1366 chore: bump actions/configure-pages (#4550) 2026-03-28 07:44:01 +01:00
Mads Marquart
f93a223da9 Clean up cargo-deny
Remove the matrix in the CI action; EmbarkStudios/cargo-deny#324 hasn't been resolved yet, but since we've split Winit out into multiple crates, there's still value in minimizing dependency conflicts even if they won't be hit by users, since e.g. Rust-Analyzer will by default check the entire workspace (and thus download and compile duplicate dependencies).
2026-03-28 10:28:31 +09:00
dependabot[bot]
d75a0dada0 chore: bump actions/deploy-pages from 4 to 5 (#4545) 2026-03-26 06:32:41 +01:00
Mads Marquart
9bf46af6f7 AppKit: Use fn_addr_eq now that it's in MSRV (#4532) 2026-03-26 05:50:16 +01:00
Mads Marquart
464c37a94e Remove leftover apple/appkit/mod.rs (#4533) 2026-03-26 05:49:49 +01:00
Mads Marquart
557d285170 Remove symlinking between winit-appkit and winit-uikit (#4530) 2026-03-26 05:49:12 +01:00
Mads Marquart
ba856e127a Fix CI (#4546)
* Fix cargo-deny erroring on new jni-sys version
* Fix unicode-segmentation bumping MSRV
* Fix cargo-deny finding new script in android-activity
2026-03-26 05:26:54 +01:00
Charlie Tonneslan
0ffd303db6 fix(typo): dependant -> dependent (#4540) 2026-03-24 15:54:31 +01:00
Mads Marquart
5a74bf0aab Android: Add further scancode conversions (#4023)
Firefox' source at:
https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h

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

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

These spans attach information to events, such that e.g. resizing a view
produces messages like:
```
TRACE inside runloop{mode=NSEventTrackingRunLoopMode}:timers:
  winit_appkit::util: Triggered `drawRect:` target="winit_appkit::view"
TRACE inside runloop{mode=NSEventTrackingRunLoopMode}:timers:
  winit_appkit::util: Completed `drawRect:` target="winit_appkit::view"
```
2026-03-18 22:55:47 +01:00
Mads Marquart
a8c7d809b9 Use new macOS 15 cursors for resize icons (#4422)
These look slightly different from the old ones. Verified that we now
use the same cursor icons as those used in Safari 26.
2026-03-18 22:33:53 +01:00
64 changed files with 1296 additions and 706 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 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
- 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,31 +290,14 @@ 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 on ${{ matrix.platform.name }} name: Run cargo-deny
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@v5 uses: actions/configure-pages@v6
- 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@v4 uses: actions/deploy-pages@v5

View File

@@ -60,7 +60,6 @@ objc2-core-foundation = { version = "0.3.2", default-features = false }
objc2-core-graphics = { version = "0.3.2", default-features = false } objc2-core-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,4 +1,11 @@
# 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,27 +1,25 @@
# https://embarkstudios.github.io/cargo-deny # https://embarkstudios.github.io/cargo-deny
# cargo install cargo-deny # cargo install cargo-deny
# cargo update && cargo deny --target aarch64-apple-ios check # cargo update && cargo deny 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 = [
{ triple = "aarch64-apple-darwin" }, "aarch64-apple-darwin",
{ triple = "aarch64-apple-ios" }, "aarch64-apple-ios",
{ triple = "aarch64-linux-android" }, "aarch64-linux-android",
{ triple = "i686-pc-windows-gnu" }, "i686-pc-windows-gnu",
{ triple = "i686-pc-windows-msvc" }, "i686-pc-windows-msvc",
{ triple = "i686-unknown-linux-gnu" }, "i686-unknown-linux-gnu",
{ triple = "wasm32-unknown-unknown", features = [ { triple = "wasm32-unknown-unknown", features = [
"atomics", "atomics",
] }, ] },
{ triple = "x86_64-apple-darwin" }, "x86_64-apple-darwin",
{ triple = "x86_64-apple-ios" }, "x86_64-apple-ios",
{ triple = "x86_64-pc-windows-gnu" }, "x86_64-pc-windows-gnu",
{ triple = "x86_64-pc-windows-msvc" }, "x86_64-pc-windows-msvc",
{ triple = "x86_64-unknown-linux-gnu" }, "x86_64-unknown-linux-gnu",
{ triple = "x86_64-unknown-redox" }, "x86_64-unknown-redox",
] ]
[licenses] [licenses]
@@ -33,6 +31,7 @@ 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 }
@@ -40,46 +39,23 @@ private = { ignore = true }
[bans] [bans]
multiple-versions = "deny" multiple-versions = "deny"
skip = [ skip = [
{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" }, { crate = "jni-sys@0.3", reason = "uses the semver trick to depend on v0.4, but `ndk` hasn't been updated to v0.4 yet" },
{ crate = "rustix@0.38", reason = "the ecosystem is in the process of migrating" }, { crate = "thiserror@1.0", reason = "dep of `ndk` crate, yet to be updated" },
{ crate = "linux-raw-sys@0.4", reason = "the ecosystem is in the process of migrating" }, { crate = "thiserror-impl@1.0", reason = "dep of `thiserror`" },
{ 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,6 +109,7 @@ 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,
@@ -131,7 +132,11 @@ 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,
@@ -154,6 +159,14 @@ 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,8 +107,7 @@ objc2-foundation = { workspace = true, features = [
"NSThread", "NSThread",
"NSValue", "NSValue",
] } ] }
objc2-quartz-core = { workspace = true, features = ["std", "CABase"] } winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler"] }
[dev-dependencies] [dev-dependencies]
winit.workspace = true winit.workspace = true

View File

@@ -1,14 +1,15 @@
#![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;
@@ -21,6 +22,10 @@ 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.
@@ -75,9 +80,7 @@ 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.
// FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV. if ptr::fn_addr_eq(overridden, method.implementation()) {
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
if overridden == method.implementation() {
return; return;
} }
@@ -98,7 +101,6 @@ pub(crate) fn override_send_event(global_app: &NSApplication) {
} }
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) { 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 {
@@ -111,7 +113,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, time, DeviceEvent::PointerMotion { app.device_event(event_loop, None, DeviceEvent::PointerMotion {
delta: (delta_x, delta_y), delta: (delta_x, delta_y),
}); });
}); });
@@ -120,7 +122,7 @@ fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => { 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, time, DeviceEvent::Button { app.device_event(event_loop, None, DeviceEvent::Button {
button, button,
state: ElementState::Pressed, state: ElementState::Pressed,
}); });
@@ -129,7 +131,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, time, DeviceEvent::Button { app.device_event(event_loop, None, DeviceEvent::Button {
button, button,
state: ElementState::Released, state: ElementState::Released,
}); });

View File

@@ -2,14 +2,12 @@ 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::{Duration, Instant}; use std::time::Instant;
use dispatch2::MainThreadBound; use dispatch2::MainThreadBound;
use objc2::MainThreadMarker; use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSEvent, NSRunningApplication}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::{NSNotification, NSTimeInterval}; use objc2_foundation::NSNotification;
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;
@@ -45,8 +43,6 @@ 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.
} }
@@ -67,17 +63,6 @@ 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,
@@ -98,8 +83,6 @@ 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))
@@ -113,20 +96,9 @@ 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);
@@ -181,7 +153,6 @@ 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();
@@ -274,12 +245,7 @@ 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( app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
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
@@ -379,12 +345,7 @@ 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( app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
event_loop,
window_id,
Instant::now(),
WindowEvent::RedrawRequested,
);
}); });
} }
self.with_handler(|app, event_loop| { self.with_handler(|app, event_loop| {

View File

@@ -5,7 +5,10 @@ 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::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage}; use objc2_app_kit::{
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,
}; };
@@ -204,23 +207,155 @@ 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 => NSCursor::resizeRightCursor(), CursorIcon::EResize => {
CursorIcon::NResize => NSCursor::resizeUpCursor(), if available!(macos = 15.0) {
CursorIcon::WResize => NSCursor::resizeLeftCursor(), NSCursor::frameResizeCursorFromPosition_inDirections(
CursorIcon::SResize => NSCursor::resizeDownCursor(), NSCursorFrameResizePosition::Right,
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(), NSCursorFrameResizeDirections::Outward,
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,7 +12,9 @@ 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 winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver}; use tracing::debug_span;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
use winit_common::foundation::create_observer;
use winit_core::application::ApplicationHandler; use winit_core::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};
@@ -29,7 +31,6 @@ 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;
@@ -152,6 +153,7 @@ 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,
} }
@@ -203,6 +205,7 @@ 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);
} }
@@ -215,6 +218,7 @@ 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);
} }
@@ -224,14 +228,20 @@ 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 lowest priority to ensure it is processed after other observers. // Queued with the second-lowest priority (tracing observers use the lowest) to ensure
// Without that, we'd get a `LoopExiting` after `AboutToWait`. // it is processed after other observers.
CFIndex::MAX, CFIndex::MAX - 1,
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);
@@ -241,8 +251,9 @@ impl EventLoop {
mtm, mtm,
CFRunLoopActivity::AfterWaiting, CFRunLoopActivity::AfterWaiting,
true, true,
// Queued with the highest priority to ensure it is processed before other observers. // Queued with the second-highest priority (tracing observers use the highest) to
CFIndex::MIN, // ensure it is processed before other observers.
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);
@@ -253,6 +264,7 @@ 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,7 +76,6 @@ 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,37 +1,10 @@
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,22 +2,21 @@
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, LogicalSize}; use dpi::{LogicalPosition, PhysicalSize};
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel}; use objc2::runtime::{AnyObject, Sel};
use objc2::{AnyThread, DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send}; use objc2::{AnyThread, DefinedClass, MainThreadMarker, define_class, msg_send};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea, NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingArea,
NSTrackingAreaOptions, NSView, NSWindow, NSTrackingAreaOptions, NSView, NSViewLayerContentsRedrawPolicy, 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::warn; use tracing::{debug_span, trace_span};
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,
@@ -155,22 +154,32 @@ 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>) {
trace_scope!("NSViewFrameDidChangeNotification"); let _entered = debug_span!("NSViewFrameDidChangeNotification").entered();
// 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).
let rect = self.frame(); self.surface_resized();
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); // During live resize, AppKit may not let the normal event loop reach its next redraw
let size = logical_size.to_physical::<u32>(self.scale_factor()); // point before stretching the current layer contents. Redraw immediately after the
self.queue_event(WindowEvent::SurfaceResized(size)); // app has observed the new surface 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) {
trace_scope!("drawRect:"); let _entered = debug_span!("drawRect:").entered();
self.ivars().app_state.handle_redraw(window_id(&self.window())); self.ivars().app_state.handle_redraw(window_id(&self.window()));
@@ -179,7 +188,7 @@ define_class!(
#[unsafe(method(acceptsFirstResponder))] #[unsafe(method(acceptsFirstResponder))]
fn accepts_first_responder(&self) -> bool { fn accepts_first_responder(&self) -> bool {
trace_scope!("acceptsFirstResponder"); let _entered = trace_span!("acceptsFirstResponder").entered();
true true
} }
@@ -193,13 +202,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>> {
trace_scope!("touchBar"); let _entered = debug_span!("touchBar").entered();
None None
} }
#[unsafe(method(resetCursorRects))] #[unsafe(method(resetCursorRects))]
fn reset_cursor_rects(&self) { fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects"); let _entered = debug_span!("resetCursorRects").entered();
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`
@@ -214,13 +223,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 {
trace_scope!("hasMarkedText"); let _entered = debug_span!("hasMarkedText").entered();
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 {
trace_scope!("markedRange"); let _entered = debug_span!("markedRange").entered();
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)
@@ -232,7 +241,7 @@ define_class!(
#[unsafe(method(selectedRange))] #[unsafe(method(selectedRange))]
fn selected_range(&self) -> NSRange { fn selected_range(&self) -> NSRange {
trace_scope!("selectedRange"); let _entered = debug_span!("selectedRange").entered();
// 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)
} }
@@ -245,7 +254,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.
trace_scope!("setMarkedText:selectedRange:replacementRange:"); let _entered = debug_span!("setMarkedText:selectedRange:replacementRange:").entered();
let (marked_text, string) = if let Some(string) = let (marked_text, string) = if let Some(string) =
string.downcast_ref::<NSAttributedString>() string.downcast_ref::<NSAttributedString>()
@@ -274,32 +283,31 @@ 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 {
// Clamp to string length to avoid NSRangeException from out-of-bounds // Convert the selected range from UTF-16 code unit indices to UTF-8 byte
// indices sent by macOS IME (e.g. native Pinyin, see // offsets. `utf16_to_utf8_offset` is defensive: it snaps an offset that would
// https://github.com/alacritty/alacritty/issues/8791). // split a surrogate pair down to the character boundary and clamps an
let len = string.length(); // out-of-bounds offset to the string length, so no `NSRangeException` is
let location = selected_range.location.min(len); // possible and the resulting range can never be inverted (`lower <= upper`).
let end = selected_range.end().min(len); // IMEs are known to send both mid-surrogate and out-of-bounds offsets (e.g.
// Convert the selected range from UTF-16 indices to UTF-8 indices. // native Pinyin, see https://github.com/alacritty/alacritty/issues/8791).
let sub_string_a = string.substringToIndex(location); let lowerbound_utf8 = utf16_to_utf8_offset(&string, selected_range.location);
let sub_string_b = string.substringToIndex(end); let upperbound_utf8 = utf16_to_utf8_offset(&string, selected_range.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.to_string(), cursor_range))); self.queue_event(WindowEvent::Ime(Ime::Preedit(string, cursor_range)));
} }
#[unsafe(method(unmarkText))] #[unsafe(method(unmarkText))]
fn unmark_text(&self) { fn unmark_text(&self) {
trace_scope!("unmarkText"); let _entered = debug_span!("unmarkText").entered();
*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");
@@ -316,7 +324,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>> {
trace_scope!("validAttributesForMarkedText"); let _entered = trace_span!("validAttributesForMarkedText").entered();
NSArray::new() NSArray::new()
} }
@@ -326,13 +334,14 @@ define_class!(
_range: NSRange, _range: NSRange,
_actual_range: *mut NSRange, _actual_range: *mut NSRange,
) -> Option<Retained<NSAttributedString>> { ) -> Option<Retained<NSAttributedString>> {
trace_scope!("attributedSubstringForProposedRange:actualRange:"); let _entered =
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 {
trace_scope!("characterIndexForPoint:"); let _entered = debug_span!("characterIndexForPoint:").entered();
0 0
} }
@@ -342,7 +351,7 @@ define_class!(
_range: NSRange, _range: NSRange,
_actual_range: *mut NSRange, _actual_range: *mut NSRange,
) -> NSRect { ) -> NSRect {
trace_scope!("firstRectForCharacterRange:actualRange:"); let _entered = debug_span!("firstRectForCharacterRange:actualRange:").entered();
// 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 {
@@ -358,7 +367,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.
trace_scope!("insertText:replacementRange:"); let _entered = debug_span!("insertText:replacementRange:").entered();
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()
@@ -383,7 +392,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) {
trace_scope!("doCommandBySelector:"); let _entered = debug_span!("doCommandBySelector:").entered();
// 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
@@ -422,7 +431,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) {
trace_scope!("keyDown:"); let _entered = debug_span!("keyDown:").entered();
{ {
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();
@@ -481,7 +490,7 @@ define_class!(
#[unsafe(method(keyUp:))] #[unsafe(method(keyUp:))]
fn key_up(&self, event: &NSEvent) { fn key_up(&self, event: &NSEvent) {
trace_scope!("keyUp:"); let _entered = debug_span!("keyUp:").entered();
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);
@@ -498,14 +507,14 @@ define_class!(
#[unsafe(method(flagsChanged:))] #[unsafe(method(flagsChanged:))]
fn flags_changed(&self, event: &NSEvent) { fn flags_changed(&self, event: &NSEvent) {
trace_scope!("flagsChanged:"); let _entered = debug_span!("flagsChanged:").entered();
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>) {
trace_scope!("insertTab:"); let _entered = debug_span!("insertTab:").entered();
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 {
@@ -516,7 +525,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>) {
trace_scope!("insertBackTab:"); let _entered = debug_span!("insertBackTab:").entered();
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 {
@@ -530,7 +539,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);
trace_scope!("cancelOperation:"); let _entered = debug_span!("cancelOperation:").entered();
let event = NSApplication::sharedApplication(mtm) let event = NSApplication::sharedApplication(mtm)
.currentEvent() .currentEvent()
@@ -559,71 +568,73 @@ define_class!(
#[unsafe(method(mouseDown:))] #[unsafe(method(mouseDown:))]
fn mouse_down(&self, event: &NSEvent) { fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:"); let _entered = debug_span!("mouseDown:").entered();
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) {
trace_scope!("mouseUp:"); let _entered = debug_span!("mouseUp:").entered();
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) {
trace_scope!("rightMouseDown:"); let _entered = debug_span!("rightMouseDown:").entered();
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) {
trace_scope!("rightMouseUp:"); let _entered = debug_span!("rightMouseUp:").entered();
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) {
trace_scope!("otherMouseDown:"); let _entered = debug_span!("otherMouseDown:").entered();
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) {
trace_scope!("otherMouseUp:"); let _entered = debug_span!("otherMouseUp:").entered();
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) {
trace_scope!("mouseEntered:"); let _entered = debug_span!("mouseEntered:").entered();
let position = self.mouse_view_point(event).to_physical(self.scale_factor()); let position = self.mouse_view_point(event).to_physical(self.scale_factor());
@@ -637,7 +648,7 @@ define_class!(
#[unsafe(method(mouseExited:))] #[unsafe(method(mouseExited:))]
fn mouse_exited(&self, event: &NSEvent) { fn mouse_exited(&self, event: &NSEvent) {
trace_scope!("mouseExited:"); let _entered = debug_span!("mouseExited:").entered();
let position = self.mouse_view_point(event).to_physical(self.scale_factor()); let position = self.mouse_view_point(event).to_physical(self.scale_factor());
@@ -651,7 +662,7 @@ define_class!(
#[unsafe(method(scrollWheel:))] #[unsafe(method(scrollWheel:))]
fn scroll_wheel(&self, event: &NSEvent) { fn scroll_wheel(&self, event: &NSEvent) {
trace_scope!("scrollWheel:"); let _entered = debug_span!("scrollWheel:").entered();
self.mouse_motion(event); self.mouse_motion(event);
@@ -682,16 +693,15 @@ 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, time, DeviceEvent::MouseWheel { delta }) app.device_event(event_loop, None, 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) {
trace_scope!("magnifyWithEvent:"); let _entered = debug_span!("magnifyWithEvent:").entered();
self.mouse_motion(event); self.mouse_motion(event);
@@ -713,7 +723,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) {
trace_scope!("smartMagnifyWithEvent:"); let _entered = debug_span!("smartMagnifyWithEvent:").entered();
self.mouse_motion(event); self.mouse_motion(event);
@@ -722,7 +732,7 @@ define_class!(
#[unsafe(method(rotateWithEvent:))] #[unsafe(method(rotateWithEvent:))]
fn rotate_with_event(&self, event: &NSEvent) { fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:"); let _entered = debug_span!("rotateWithEvent:").entered();
self.mouse_motion(event); self.mouse_motion(event);
@@ -744,7 +754,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) {
trace_scope!("pressureChangeWithEvent:"); let _entered = debug_span!("pressureChangeWithEvent:").entered();
self.queue_event(WindowEvent::TouchpadPressure { self.queue_event(WindowEvent::TouchpadPressure {
device_id: None, device_id: None,
@@ -758,13 +768,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 {
trace_scope!("_wantsKeyDownForEvent:"); let _entered = debug_span!("_wantsKeyDownForEvent:").entered();
true true
} }
#[unsafe(method(acceptsFirstMouse:))] #[unsafe(method(acceptsFirstMouse:))]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:"); let _entered = debug_span!("acceptsFirstMouse:").entered();
self.ivars().accepts_first_mouse self.ivars().accepts_first_mouse
} }
} }
@@ -795,6 +805,10 @@ 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:`.
// //
@@ -847,19 +861,43 @@ 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());
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, time, event); app.window_event(event_loop, window_id, event);
}); });
} }
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| {
app.window_event(event_loop, window_id, WindowEvent::SurfaceResized(size));
});
}
/// 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
} }
@@ -1176,3 +1214,92 @@ 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,6 +8,7 @@ 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;
@@ -350,13 +351,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 {
trace_scope!("canBecomeMainWindow"); let _entered = trace_span!("canBecomeMainWindow").entered();
true true
} }
#[unsafe(method(canBecomeKeyWindow))] #[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool { fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow"); let _entered = trace_span!("canBecomeKeyWindow").entered();
true true
} }
} }
@@ -374,7 +375,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 {
trace_scope!("canBecomeKeyWindow"); let _entered = trace_span!("canBecomeKeyWindow").entered();
true true
} }
} }

View File

@@ -5,7 +5,6 @@ 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,
@@ -42,7 +41,7 @@ use objc2_foundation::{
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint, NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
NSRect, NSSize, NSString, ns_string, NSRect, NSSize, NSString, ns_string,
}; };
use tracing::{trace, warn}; use tracing::{debug_span, 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};
@@ -122,14 +121,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 {
trace_scope!("windowShouldClose:"); let _entered = debug_span!("windowShouldClose:").entered();
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>) {
trace_scope!("windowWillClose:"); let _entered = debug_span!("windowWillClose:").entered();
// `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
@@ -141,14 +140,14 @@ define_class!(
#[unsafe(method(windowDidResize:))] #[unsafe(method(windowDidResize:))]
fn window_did_resize(&self, _: Option<&AnyObject>) { fn window_did_resize(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidResize:"); let _entered = debug_span!("windowDidResize:").entered();
// 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>) {
trace_scope!("windowWillStartLiveResize:"); let _entered = debug_span!("windowWillStartLiveResize:").entered();
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);
@@ -156,20 +155,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>) {
trace_scope!("windowDidEndLiveResize:"); let _entered = debug_span!("windowDidEndLiveResize:").entered();
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>) {
trace_scope!("windowDidMove:"); let _entered = debug_span!("windowDidMove:").entered();
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>) {
trace_scope!("windowDidChangeBackingProperties:"); let _entered = debug_span!("windowDidChangeBackingProperties:").entered();
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;
@@ -185,7 +184,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>) {
trace_scope!("windowDidBecomeKey:"); let _entered = debug_span!("windowDidBecomeKey:").entered();
// 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));
@@ -193,7 +192,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>) {
trace_scope!("windowDidResignKey:"); let _entered = debug_span!("windowDidResignKey:").entered();
// 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
@@ -209,7 +208,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>) {
trace_scope!("windowWillEnterFullScreen:"); let _entered = debug_span!("windowWillEnterFullScreen:").entered();
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();
@@ -237,7 +236,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>) {
trace_scope!("windowWillExitFullScreen:"); let _entered = debug_span!("windowWillExitFullScreen:").entered();
self.ivars().in_fullscreen_transition.set(true); self.ivars().in_fullscreen_transition.set(true);
} }
@@ -248,7 +247,7 @@ define_class!(
_: Option<&AnyObject>, _: Option<&AnyObject>,
proposed_options: NSApplicationPresentationOptions, proposed_options: NSApplicationPresentationOptions,
) -> NSApplicationPresentationOptions { ) -> NSApplicationPresentationOptions {
trace_scope!("window:willUseFullScreenPresentationOptions:"); let _entered = debug_span!("window:willUseFullScreenPresentationOptions:").entered();
// Generally, games will want to disable the menu bar and the dock. Ideally, // 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
@@ -271,7 +270,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>) {
trace_scope!("windowDidEnterFullScreen:"); let _entered = debug_span!("windowDidEnterFullScreen:").entered();
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() {
@@ -282,7 +281,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>) {
trace_scope!("windowDidExitFullScreen:"); let _entered = debug_span!("windowDidExitFullScreen:").entered();
self.restore_state_from_fullscreen(); self.restore_state_from_fullscreen();
self.ivars().in_fullscreen_transition.set(false); self.ivars().in_fullscreen_transition.set(false);
@@ -309,7 +308,7 @@ define_class!(
/// work you may have done to prepare to enter full-screen mode. /// 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>) {
trace_scope!("windowDidFailToEnterFullScreen:"); let _entered = debug_span!("windowDidFailToEnterFullScreen:").entered();
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() {
@@ -328,7 +327,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>) {
trace_scope!("windowDidChangeOcclusionState:"); let _entered = debug_span!("windowDidChangeOcclusionState:").entered();
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));
@@ -349,7 +348,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>) {
trace_scope!("windowDidChangeScreen:"); let _entered = debug_span!("windowDidChangeScreen:").entered();
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() {
@@ -363,7 +362,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 {
trace_scope!("draggingEntered:"); let _entered = debug_span!("draggingEntered:").entered();
use std::path::PathBuf; use std::path::PathBuf;
@@ -394,7 +393,7 @@ define_class!(
#[unsafe(method(wantsPeriodicDraggingUpdates))] #[unsafe(method(wantsPeriodicDraggingUpdates))]
fn wants_periodic_dragging_updates(&self) -> bool { fn wants_periodic_dragging_updates(&self) -> bool {
trace_scope!("wantsPeriodicDraggingUpdates:"); let _entered = debug_span!("wantsPeriodicDraggingUpdates:").entered();
true true
} }
@@ -402,7 +401,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 {
trace_scope!("draggingUpdated:"); let _entered = debug_span!("draggingUpdated:").entered();
let dl = sender.draggingLocation(); let dl = sender.draggingLocation();
let dl = self.view().convertPoint_fromView(dl, None); let dl = self.view().convertPoint_fromView(dl, None);
@@ -417,14 +416,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 {
trace_scope!("prepareForDragOperation:"); let _entered = debug_span!("prepareForDragOperation:").entered();
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 {
trace_scope!("performDragOperation:"); let _entered = debug_span!("performDragOperation:").entered();
use std::path::PathBuf; use std::path::PathBuf;
@@ -456,13 +455,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>) {
trace_scope!("concludeDragOperation:"); let _entered = debug_span!("concludeDragOperation:").entered();
} }
/// 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>>) {
trace_scope!("draggingExited:"); let _entered = debug_span!("draggingExited:").entered();
let position = sender.map(|sender| { let position = sender.map(|sender| {
let dl = sender.draggingLocation(); let dl = sender.draggingLocation();
@@ -484,7 +483,7 @@ define_class!(
change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>, change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
_context: *mut c_void, _context: *mut c_void,
) { ) {
trace_scope!("observeValueForKeyPath:ofObject:change:context:"); let _entered = debug_span!("observeValueForKeyPath:ofObject:change:context:").entered();
// NOTE: We don't _really_ need to check the key path, as there should only be one, but // 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")) {
@@ -904,26 +903,16 @@ 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, time, event); app.window_event(event_loop, window_id, 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 content_size = window.contentRectForFrameRect(window.frame()).size; let suggested_size = self.view().surface_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,
@@ -1048,9 +1037,7 @@ impl WindowDelegate {
#[inline] #[inline]
pub fn surface_size(&self) -> PhysicalSize<u32> { pub fn surface_size(&self) -> PhysicalSize<u32> {
let content_rect = self.window().contentRectForFrameRect(self.window().frame()); self.view().surface_size()
let logical = LogicalSize::new(content_rect.size.width, content_rect.size.height);
logical.to_physical(self.scale_factor())
} }
#[inline] #[inline]

View File

@@ -20,6 +20,9 @@ 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
@@ -30,7 +33,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 }
# CoreFoundation # Foundation / 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 = [
@@ -40,6 +43,13 @@ 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::error; use tracing::{Span, error};
use super::MainRunLoopObserver; use super::MainRunLoopObserver;
@@ -49,7 +49,8 @@ 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:]`. /// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]` to queue a closure to run
/// the next time runloop sources are processed.
/// ///
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the /// 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
@@ -73,9 +74,17 @@ 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,7 +6,9 @@
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

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

View File

@@ -79,6 +79,10 @@ impl EventHandler {
// Allowed, happens if the handler was cleared manually // 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

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

View File

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

View File

@@ -4,5 +4,7 @@
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,9 +1,4 @@
//! 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;
@@ -197,14 +192,10 @@ 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,
); );
@@ -215,10 +206,9 @@ 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, timestamp, event); let _ = (event_loop, device_id, 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.
@@ -382,10 +372,9 @@ 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, timestamp, event); (**self).window_event(event_loop, window_id, event);
} }
#[inline] #[inline]
@@ -393,10 +382,9 @@ 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, timestamp, event); (**self).device_event(event_loop, device_id, event);
} }
#[inline] #[inline]
@@ -452,10 +440,9 @@ 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, timestamp, event); (**self).window_event(event_loop, window_id, event);
} }
#[inline] #[inline]
@@ -463,10 +450,9 @@ 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, timestamp, event); (**self).device_event(event_loop, device_id, event);
} }
#[inline] #[inline]

View File

@@ -269,6 +269,20 @@ 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
@@ -1006,9 +1020,17 @@ 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,7 +880,8 @@ 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 protocol. /// - **Wayland:** Only works with `org_kde_kwin_blur_manager` or
/// `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"] } winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@@ -12,7 +12,9 @@ use objc2_ui_kit::{
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen, UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen,
}; };
use rwh_06::HasDisplayHandle; use rwh_06::HasDisplayHandle;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver}; use tracing::debug_span;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
use winit_common::foundation::create_observer;
use winit_core::application::ApplicationHandler; use winit_core::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};
@@ -24,7 +26,6 @@ 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};
@@ -134,6 +135,7 @@ 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,
@@ -159,6 +161,7 @@ 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);
}, },
); );
@@ -166,19 +169,27 @@ impl EventLoop {
&center, &center,
// `applicationDidBecomeActive:` // `applicationDidBecomeActive:`
unsafe { UIApplicationDidBecomeActiveNotification }, unsafe { UIApplicationDidBecomeActiveNotification },
move |_| app_state::handle_resumed(mtm), move |_| {
let _entered = debug_span!("UIApplicationDidBecomeActiveNotification").entered();
app_state::handle_resumed(mtm)
},
); );
let _will_resign_active_observer = create_observer( let _will_resign_active_observer = create_observer(
&center, &center,
// `applicationWillResignActive:` // `applicationWillResignActive:`
unsafe { UIApplicationWillResignActiveNotification }, unsafe { UIApplicationWillResignActiveNotification },
move |_| app_state::handle_suspended(mtm), move |_| {
let _entered = debug_span!("UIApplicationWillResignActiveNotification").entered();
app_state::handle_suspended(mtm)
},
); );
let _will_enter_foreground_observer = create_observer( 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",
); );
@@ -193,6 +204,7 @@ 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",
); );
@@ -207,6 +219,7 @@ 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");
@@ -220,18 +233,29 @@ impl EventLoop {
&center, &center,
// `applicationDidReceiveMemoryWarning:` // `applicationDidReceiveMemoryWarning:`
unsafe { UIApplicationDidReceiveMemoryWarningNotification }, unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| app_state::handle_memory_warning(mtm), move |_| {
let _entered =
debug_span!("UIApplicationDidReceiveMemoryWarningNotification").entered();
app_state::handle_memory_warning(mtm)
},
); );
let main_loop = MainRunLoop::get(mtm); let 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 highest priority to ensure it is processed before other observers. // Queued with the second-highest priority (tracing observers use the highest) to
CFIndex::MIN, // ensure it is processed before other observers.
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);
@@ -241,17 +265,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 `0x1e8480`. We set the // `CA::Transaction::ensure_implicit` with a priority of `2000000`. We set the
// main_end priority to be 0, in order to send `AboutToWait` before `RedrawRequested`. // 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 `0x1e8480` was determined by inspecting stack traces and the associated // The value of `2000000` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
// //
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4. // Also tested to be `2000000` on iPhone 8, iOS 13 beta 4.
0, 0,
move |_| app_state::handle_main_events_cleared(mtm), move |_| app_state::handle_main_events_cleared(mtm),
); );
@@ -261,8 +285,9 @@ impl EventLoop {
mtm, mtm,
CFRunLoopActivity::BeforeWaiting, CFRunLoopActivity::BeforeWaiting,
true, true,
// Queued with the lowest priority to ensure it is processed after other observers. // Queued with the second-lowest priority (tracing observers use the lowest) to ensure
CFIndex::MAX, // it is processed after other observers.
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);
@@ -277,6 +302,7 @@ 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,7 +103,6 @@
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

@@ -1 +0,0 @@
../../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; use tracing::{debug, debug_span, trace_span};
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,6 +48,7 @@ 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 {
@@ -59,6 +60,7 @@ 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] };
@@ -79,6 +81,7 @@ 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] };
@@ -124,6 +127,7 @@ 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();
@@ -131,26 +135,31 @@ 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() {
@@ -185,6 +194,7 @@ 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 {
@@ -200,6 +210,7 @@ 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() {
@@ -244,6 +255,7 @@ 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));
@@ -296,6 +308,7 @@ 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
} }
} }
@@ -309,6 +322,10 @@ 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
} }
} }
@@ -318,16 +335,19 @@ 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,6 +7,7 @@ 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};
@@ -28,31 +29,37 @@ 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, warn}; use tracing::{debug, debug_span, 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,6 +46,7 @@ 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(),
@@ -56,6 +57,7 @@ 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.8", features = ["staging"] } wayland-protocols = { version = "0.32.11", features = ["staging", "unstable"] }
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,6 +12,7 @@ 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;
@@ -60,6 +61,9 @@ 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>,
@@ -141,6 +145,14 @@ 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.
@@ -209,6 +221,10 @@ 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,9 +7,12 @@ 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, ZwpPointerGesturePinchV1, Event as PinchEvent, 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;
@@ -27,7 +30,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, 1..=1, GlobalData)?; let pointer_gestures = globals.bind(queue_handle, 3..=3, GlobalData)?;
Ok(Self { pointer_gestures }) Ok(Self { pointer_gestures })
} }
} }
@@ -70,6 +73,49 @@ 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,
@@ -81,7 +127,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 {
Event::Begin { surface, fingers, .. } => { PinchEvent::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;
@@ -100,7 +146,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.)
}, },
Event::Update { dx, dy, scale: pinch, rotation, .. } => { PinchEvent::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,
@@ -121,7 +167,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)
}, },
Event::End { cancelled, .. } => { PinchEvent::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,
@@ -155,3 +201,4 @@ 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::kwin_blur::KWinBlurManager; use crate::types::bgr_effects::BgrEffectManager;
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>,
/// KWin blur manager. /// Blur manager.
pub kwin_blur_manager: Option<KWinBlurManager>, pub blur_manager: Option<BgrEffectManager>,
/// 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,
kwin_blur_manager: KWinBlurManager::new(globals, queue_handle).ok(), blur_manager: BgrEffectManager::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

@@ -0,0 +1,85 @@
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

@@ -0,0 +1,59 @@
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,6 +1,8 @@
//! 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,7 +127,8 @@ impl Window {
// Set transparency hint. // Set transparency hint.
window_state.set_transparent(attributes.transparent); window_state.set_transparent(attributes.transparent);
window_state.set_blur(attributes.blur); // Set 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);
@@ -498,7 +499,9 @@ impl CoreWindow for Window {
#[inline] #[inline]
fn set_blur(&self, blur: bool) { fn set_blur(&self, blur: bool) {
self.window_state.lock().unwrap().set_blur(blur); if self.window_state.lock().unwrap().set_blur(blur) {
self.request_redraw();
}
} }
#[inline] #[inline]

View File

@@ -29,7 +29,6 @@ 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::{
@@ -43,8 +42,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")]
@@ -156,8 +155,8 @@ pub struct WindowState {
viewport: Option<WpViewport>, viewport: Option<WpViewport>,
fractional_scale: Option<WpFractionalScaleV1>, fractional_scale: Option<WpFractionalScaleV1>,
blur: Option<OrgKdeKwinBlur>, blur: Option<SurfaceBlurEffect>,
blur_manager: Option<KWinBlurManager>, blur_manager: Option<BgrEffectManager>,
/// Whether the client side decorations have pending move operations. /// Whether the client side decorations have pending move operations.
/// ///
@@ -206,7 +205,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.kwin_blur_manager.clone(), blur_manager: winit_state.blur_manager.clone(),
compositor, compositor,
handle, handle,
csd_fails: false, csd_fails: false,
@@ -742,6 +741,13 @@ 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.
@@ -1113,20 +1119,37 @@ impl WindowState {
} }
} }
/// Make window background blurred /// Make window background blurred.
#[inline] ///
pub fn set_blur(&mut self, blurred: bool) { /// Returns `true` if redraw is required.
if blurred && self.blur.is_none() { #[must_use]
if let Some(blur_manager) = self.blur_manager.as_ref() { pub fn set_blur(&mut self, blurred: bool) -> bool {
let blur = blur_manager.blur(self.window.wl_surface(), &self.queue_handle); if !blurred {
blur.commit(); self.blur = None;
self.blur = Some(blur); return true;
} else { }
info!("Blur manager unavailable, unable to change blur")
} let mgr = match self.blur_manager.as_mut() {
} else if !blurred && self.blur.is_some() { Some(mgr) => mgr,
self.blur_manager.as_ref().unwrap().unset(self.window.wl_surface()); None => {
self.blur.take().unwrap().release(); info!("Blur manager unavailable, unable to change blur");
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
} }
} }
@@ -1224,10 +1247,6 @@ 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 dependant on the lifetime of the runner, // Notifying `Future`s is not dependent 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 dependant on the lifetime of the runner, because // Notifying `Future`s is not dependent on the lifetime of the runner, because
// they can outlive it. // 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_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_INPUT, WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP,
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL,
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT,
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR,
WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP,
WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WMSZ_BOTTOM, WM_TOUCH, 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,6 +1508,21 @@ 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,11 +387,7 @@ 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) => { ToUnicodeResult::Dead(dead_char) => Key::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().iter().filter_map(|(_, w)| w.upgrade()) { for window in self.target.windows.borrow().values().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,7 +66,6 @@ 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()
@@ -74,7 +73,6 @@ 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,9 +76,13 @@ 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,7 +1,9 @@
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() {
// The script doesn't depend on our code. // Dummy invocation to enable change-tracking in build scripts.
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
// Setup cfg aliases. // Setup cfg aliases.

View File

@@ -15,7 +15,6 @@ 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)]
@@ -25,7 +24,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}; use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
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;
@@ -45,9 +44,6 @@ 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.;
@@ -94,20 +90,12 @@ struct Application {
/// Drawing context. /// Drawing context.
/// ///
/// With OpenGL it could be EGLDisplay. /// With OpenGL it could be EGLDisplay.
context: Option<Context<DisplayHandle<'static>>>, context: Context<OwnedDisplayHandle>,
} }
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 {
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe. let context = Context::new(event_loop.owned_display_handle()).unwrap();
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
@@ -541,16 +529,7 @@ 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(_) => (),
} }
} }
@@ -605,9 +584,7 @@ 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.
@@ -648,9 +625,7 @@ 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);
// SAFETY: the surface is dropped before the `window` which provided it with handle, thus let surface = Surface::new(&app.context, Arc::clone(&window))?;
// 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:?}");
@@ -937,35 +912,40 @@ 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()?;
// Draw a different color inside the safe area if self.animated_fill_color {
let surface_size = self.window.surface_size(); // Fill the entire buffer with a single color.
let insets = self.window.safe_area(); let time = self.start_time.elapsed().as_secs_f32() * 1.5;
for y in 0..surface_size.height { let blue = (time.sin() * 255.0) as u32;
for x in 0..surface_size.width { let green = ((time.cos() * 255.0) as u32) << 8;
let index = y as usize * surface_size.width as usize + x as usize; let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
if insets.left <= x let color = red | green | blue;
&& x <= (surface_size.width - insets.right) buffer.fill(color);
&& insets.top <= y } else {
&& y <= (surface_size.height - insets.bottom) // Draw a different color inside the safe area
{ let surface_size = self.window.surface_size();
// In safe area let insets = self.window.safe_area();
buffer[index] = match self.theme { for y in 0..surface_size.height {
Theme::Light => 0xffe8e8e8, // Light gray for x in 0..surface_size.width {
Theme::Dark => 0xff525252, // Medium gray let index = y as usize * surface_size.width as usize + x as usize;
}; if insets.left <= x
} else { && x <= (surface_size.width - insets.right)
// Outside safe area && insets.top <= y
buffer[index] = match self.theme { && y <= (surface_size.height - insets.bottom)
Theme::Light => 0xffffffff, // White {
Theme::Dark => 0xff181818, // Dark gray // In safe area
}; 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,10 +3,12 @@
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}; use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::raw_window_handle::HasRawWindowHandle; use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
@@ -15,18 +17,20 @@ fn main() -> Result<(), impl std::error::Error> {
#[derive(Debug)] #[derive(Debug)]
struct WindowData { struct WindowData {
window: Box<dyn Window>, surface: Surface<OwnedDisplayHandle, Box<dyn Window>>,
color: u32, color: u32,
} }
impl WindowData { impl WindowData {
fn new(window: Box<dyn Window>, color: u32) -> Self { fn new(context: &Context<OwnedDisplayHandle>, window: Box<dyn Window>, color: u32) -> Self {
Self { window, color } let surface = Surface::new(context, window).unwrap();
Self { surface, color }
} }
} }
#[derive(Default, Debug)] #[derive(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>,
} }
@@ -38,10 +42,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();
println!("Parent window id: {:?})", window.id()); info!("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(window, 0xffbbbbbb)); self.windows.insert(window.id(), WindowData::new(&self.context, window, 0xffbbbbbb));
} }
fn window_event( fn window_event(
@@ -56,12 +60,12 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.exit(); event_loop.exit();
}, },
WindowEvent::PointerEntered { device_id: _, .. } => { WindowEvent::PointerEntered { device_id: _, .. } => {
// On x11, println when the cursor entered in a window even if the child window // On x11, log when the cursor entered in a window even if the child window
// is created by some key inputs. // 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.
println!("cursor entered in the window {window_id:?}"); info!("cursor entered in the window {window_id:?}");
}, },
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. }, event: KeyEvent { state: ElementState::Pressed, .. },
@@ -72,18 +76,24 @@ 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 = let child_window = spawn_child_window(
spawn_child_window(parent_window.window.as_ref(), event_loop, child_index); parent_window.surface.window().as_ref(),
event_loop,
child_index,
);
let child_id = child_window.id(); let child_id = child_window.id();
println!("Child window created with id: {child_id:?}"); info!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, WindowData::new(child_window, child_color)); self.windows.insert(
child_id,
WindowData::new(&self.context, child_window, child_color),
);
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) { if let Some(window) = self.windows.get_mut(&window_id) {
if window_id == self.parent_window_id.unwrap() { if window_id == self.parent_window_id.unwrap() {
fill::fill_window(window.window.as_ref()); fill::fill(&mut window.surface);
} else { } else {
fill::fill_window_with_color(window.window.as_ref(), window.color); fill::fill_with_color(&mut window.surface, window.color);
} }
} }
}, },
@@ -117,7 +127,8 @@ fn main() -> Result<(), impl std::error::Error> {
} }
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
event_loop.run_app(Application::default()) let context = Context::new(event_loop.owned_display_handle()).unwrap();
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,12 +4,13 @@ use std::thread;
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
use std::time; use std::time;
use ::tracing::{info, warn}; use softbuffer::{Context, Surface};
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}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, OwnedDisplayHandle};
use winit::keyboard::{Key, NamedKey}; use winit::keyboard::{Key, NamedKey};
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
@@ -52,7 +53,7 @@ struct ControlFlowDemo {
request_redraw: bool, request_redraw: bool,
wait_cancelled: bool, wait_cancelled: bool,
close_requested: bool, close_requested: bool,
window: Option<Box<dyn Window>>, surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
} }
impl ApplicationHandler for ControlFlowDemo { impl ApplicationHandler for ControlFlowDemo {
@@ -69,7 +70,10 @@ 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.",
); );
self.window = Some(event_loop.create_window(window_attributes).unwrap()); let window = 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(
@@ -112,9 +116,9 @@ impl ApplicationHandler for ControlFlowDemo {
_ => (), _ => (),
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap(); 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);
}, },
_ => (), _ => (),
} }
@@ -122,7 +126,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.window.as_ref().unwrap().request_redraw(); self.surface.as_ref().unwrap().window().request_redraw();
} }
match self.mode { match self.mode {

View File

@@ -1,8 +1,10 @@
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}; use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
@@ -15,27 +17,24 @@ fn main() -> Result<(), Box<dyn Error>> {
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
let app = Application::new(); let app = Application::default();
Ok(event_loop.run_app(app)?) Ok(event_loop.run_app(app)?)
} }
/// Application state and event handling. /// Application state and event handling.
#[derive(Debug)] #[derive(Default, Debug)]
struct Application { struct Application {
window: Option<Box<dyn Window>>, surface: Option<Surface<OwnedDisplayHandle, 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!");
self.window = Some(event_loop.create_window(window_attributes).unwrap()); let window = 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(
@@ -49,12 +48,12 @@ impl ApplicationHandler for Application {
| WindowEvent::DragEntered { .. } | WindowEvent::DragEntered { .. }
| WindowEvent::DragMoved { .. } | WindowEvent::DragMoved { .. }
| WindowEvent::DragDropped { .. } => { | WindowEvent::DragDropped { .. } => {
println!("{event:?}"); info!("{event:?}");
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap(); 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);
}, },
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
event_loop.exit(); event_loop.exit();

View File

@@ -9,10 +9,11 @@ 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}; use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
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;
@@ -30,7 +31,7 @@ const IME_CURSOR_SIZE: PhysicalSize<u32> = PhysicalSize::new(20, 20);
#[derive(Debug)] #[derive(Debug)]
struct App { struct App {
window: Option<Box<dyn Window>>, surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
input_state: TextInputState, input_state: TextInputState,
modifiers: ModifiersState, modifiers: ModifiersState,
} }
@@ -73,14 +74,12 @@ 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)));
self.window = match event_loop.create_window(window_attributes) { let window = event_loop.create_window(window_attributes).expect("failed creating window");
Ok(window) => Some(window),
Err(err) => { let context =
eprintln!("error creating window: {err}"); Context::new(event_loop.owned_display_handle()).expect("failed creating context");
event_loop.exit(); let surface = Surface::new(&context, window).expect("failed creating surface");
return; self.surface = Some(surface);
},
};
// Allow IME out of the box. // Allow IME out of the box.
let enable_request = ImeEnableRequest::new( let enable_request = ImeEnableRequest::new(
@@ -101,11 +100,13 @@ impl ApplicationHandler for App {
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
info!("Close was requested; stopping"); info!("Close was requested; stopping");
self.window = None; self.surface = None;
event_loop.exit(); event_loop.exit();
}, },
WindowEvent::SurfaceResized(_) => { WindowEvent::SurfaceResized(surface_size) => {
self.window.as_ref().expect("resize event without a window").request_redraw(); let surface = self.surface.as_mut().expect("resize event without a surface");
fill::resize(surface, surface_size);
surface.window().request_redraw();
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
// Redraw the application. // Redraw the application.
@@ -114,13 +115,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 window = self.window.as_ref().expect("redraw request without a window"); let surface = self.surface.as_mut().expect("redraw event without a surface");
// Notify that you're about to draw. // Notify that you're about to draw.
window.pre_present_notify(); surface.window().pre_present_notify();
// Draw. // Draw.
fill::fill_window(window.as_ref()); fill::fill(surface);
// 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();
@@ -214,14 +215,14 @@ impl App {
} }
fn handle_ime_event(&mut self, event: Ime) { fn handle_ime_event(&mut self, event: Ime) {
let window = self.window.as_ref().expect("IME request without a window"); let surface = self.surface.as_ref().expect("IME request without a window");
match event { match event {
Ime::Enabled => info!("IME enabled for Window={:?}", window.id()), Ime::Enabled => info!("IME enabled for Window={:?}", surface.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();
window.request_ime_update(ImeRequest::Update(request_data)).unwrap(); surface.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 } => {
@@ -246,7 +247,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={:?}", window.id()), Ime::Disabled => info!("IME disabled for Window={:?}", surface.window().id()),
} }
} }
@@ -312,7 +313,7 @@ impl App {
} }
fn window(&self) -> &dyn Window { fn window(&self) -> &dyn Window {
self.window.as_ref().unwrap().as_ref() self.surface.as_ref().unwrap().window().as_ref()
} }
} }
@@ -346,7 +347,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
println!( info!(
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.
@@ -355,7 +356,7 @@ Use CTRL+h to cycle content hint permutations.
); );
let app = App { let app = App {
window: None, surface: None,
input_state: TextInputState { input_state: TextInputState {
ime_enabled: true, ime_enabled: true,
contents: String::new(), contents: String::new(),

View File

@@ -7,24 +7,31 @@ 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}; use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
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 {
window: Option<Box<dyn Window>>, surface: Option<Surface<OwnedDisplayHandle, 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!");
self.window = Some(event_loop.create_window(window_attributes).unwrap()); let window = 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(
@@ -33,27 +40,27 @@ fn main() -> std::process::ExitCode {
_window_id: WindowId, _window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
println!("{event:?}"); info!("{event:?}");
let window = match self.window.as_ref() { let surface = match self.surface.as_mut() {
Some(window) => window, Some(surface) => surface,
None => return, None => return,
}; };
match event { match event {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
fill::fill_window(window.as_ref()); fill::fill(surface);
window.request_redraw(); surface.window().request_redraw();
}, },
_ => (), _ => (),
} }
} }
} }
let mut event_loop = EventLoop::new().unwrap(); tracing::init();
tracing_subscriber::fmt::init(); let mut event_loop = EventLoop::new().unwrap();
let mut app = PumpDemo::default(); let mut app = PumpDemo::default();
@@ -69,12 +76,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.
println!("Update()"); info!("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() {
println!("This platform doesn't support pump_events."); panic!("This platform doesn't support pump_events.")
} }

View File

@@ -5,36 +5,43 @@
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}; use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
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(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(window) = self.window.as_ref() { if let Some(surface) = self.surface.as_ref() {
window.request_redraw(); surface.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("Fantastic window number one!") .with_title(format!("Fantastic window number {}!", self.idx))
.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(
@@ -44,52 +51,44 @@ 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) {
println!( info!("Window {} Destroyed", self.idx);
"--------------------------------------------------------- Window {} Destroyed",
self.idx
);
self.window_id = None; self.window_id = None;
event_loop.exit(); event_loop.exit();
return; return;
} }
let window = match self.window.as_mut() { let Some(surface) = self.surface.as_mut() else {
Some(window) => window, return;
None => return,
}; };
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
println!( info!("Window {} CloseRequested", self.idx);
"--------------------------------------------------------- Window {} \ self.surface = None;
CloseRequested",
self.idx
);
fill::cleanup_window(window.as_ref());
self.window = None;
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
fill::fill_window(window.as_ref()); fill::fill(surface);
}, },
_ => (), _ => (),
} }
} }
} }
tracing_subscriber::fmt::init(); tracing::init();
let mut event_loop = EventLoop::new().unwrap(); let mut event_loop = EventLoop::new().unwrap();
let mut app = App { idx: 1, ..Default::default() }; let context = Context::new(event_loop.owned_display_handle()).unwrap();
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)?;
println!("--------------------------------------------------------- Finished first loop"); info!("Finished first loop");
println!("--------------------------------------------------------- Waiting 5 seconds"); info!("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)?;
println!("--------------------------------------------------------- Finished second loop"); info!("Finished second loop");
Ok(()) Ok(())
} }
@@ -101,5 +100,5 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
orbital_platform orbital_platform
)))] )))]
fn main() { fn main() {
println!("This example is not supported on this platform"); panic!("This example is not supported on this platform")
} }

View File

@@ -1,115 +1,43 @@
//! 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 softbuffer::{Context, Surface}; use rwh_06::{HasDisplayHandle, HasWindowHandle};
#[cfg(web_platform)] use softbuffer::Surface;
use web_time::Instant; use winit::window::Window;
use winit::window::{Window, WindowId};
thread_local! { /// Resize the surface.
// NOTE: You should never do things like that, create context and drop it before pub fn resize(
// you drop the event loop. We do this for brevity to not blow up examples. We use surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle>,
// ManuallyDrop to prevent destructors from running. surface_size: dpi::PhysicalSize<u32>,
) {
// Handle zero-sized buffers.
// //
// A static, thread-local map of graphics contexts to open windows. // FIXME(madsmtm): This should be done by softbuffer internally in the future:
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) }; // https://github.com/rust-windowing/softbuffer/issues/238
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");
} }
/// The graphics context used to draw to a window. /// Fill the window buffer with a solid color.
struct GraphicsContext { pub fn fill_with_color(
/// The global softbuffer context. surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle + AsRef<dyn Window>>,
context: RefCell<Context<&'static dyn Window>>, color: u32,
) {
let surface_size = surface.window().as_ref().surface_size();
resize(surface, surface_size);
/// The hash map of window IDs to surfaces. let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>, buffer.fill(color);
} 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_window(window: &dyn Window) { pub fn fill(
fill_window_with_color(window, 0xff181818); surface: &mut Surface<impl HasDisplayHandle, impl HasWindowHandle + AsRef<dyn Window>>,
} ) {
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,3 +23,6 @@ pub fn init() {
) )
.init(); .init();
} }
#[allow(unused_imports)]
pub use ::tracing::*;

View File

@@ -1,11 +1,12 @@
//! 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}; use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
#[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};
@@ -17,7 +18,7 @@ mod tracing;
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct App { struct App {
window: Option<Box<dyn Window>>, surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
} }
impl ApplicationHandler for App { impl ApplicationHandler for App {
@@ -27,31 +28,25 @@ 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)));
self.window = match event_loop.create_window(window_attributes) { let window = event_loop.create_window(window_attributes).expect("failed creating window");
Ok(window) => Some(window),
Err(err) => { let context =
eprintln!("error creating window: {err}"); Context::new(event_loop.owned_display_handle()).expect("failed creating context");
event_loop.exit(); let surface = Surface::new(&context, window).expect("failed creating surface");
return; self.surface = Some(surface);
},
}
} }
fn window_event( fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) {
&mut self, info!("{event:?}");
event_loop: &dyn ActiveEventLoop,
_: WindowId,
timestamp: Instant,
event: WindowEvent,
) {
::tracing::info!("{:?}: {event:?}", timestamp.elapsed());
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
println!("Close was requested; stopping"); info!("Close was requested; stopping");
event_loop.exit(); event_loop.exit();
}, },
WindowEvent::SurfaceResized(_) => { WindowEvent::SurfaceResized(surface_size) => {
self.window.as_ref().expect("resize event without a window").request_redraw(); let surface = self.surface.as_mut().expect("resize event without a surface");
fill::resize(surface, surface_size);
surface.window().request_redraw();
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
// Redraw the application. // Redraw the application.
@@ -60,30 +55,22 @@ 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 window = self.window.as_ref().expect("redraw request without a window"); let surface = self.surface.as_mut().expect("redraw event without a surface");
// Notify that you're about to draw. // Notify that you're about to draw.
window.pre_present_notify(); surface.window().pre_present_notify();
// Draw. // Draw.
fill::fill_window(window.as_ref()); let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
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,19 +3,22 @@ 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}; use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
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,
window: Option<Box<dyn Window>>, surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
} }
impl ApplicationHandler for XEmbedDemo { impl ApplicationHandler for XEmbedDemo {
@@ -27,7 +30,10 @@ 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));
self.window = Some(event_loop.create_window(window_attributes).unwrap()); let window = 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(
@@ -36,19 +42,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 => {
window.pre_present_notify(); let surface = self.surface.as_mut().unwrap();
fill::fill_window(window.as_ref()); surface.window().pre_present_notify();
fill::fill(surface);
}, },
_ => (), _ => (),
} }
} }
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
self.window.as_ref().unwrap().request_redraw(); self.surface.as_ref().unwrap().window().request_redraw();
} }
} }
@@ -58,14 +64,13 @@ 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_subscriber::fmt::init(); tracing::init();
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
Ok(event_loop.run_app(XEmbedDemo { parent_window_id, window: None })?) Ok(event_loop.run_app(XEmbedDemo { parent_window_id, surface: None })?)
} }
#[cfg(not(x11_platform))] #[cfg(not(x11_platform))]
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
println!("This example is only supported on X11 platforms."); panic!("This example is only supported on X11 platforms.")
Ok(())
} }

View File

@@ -46,6 +46,10 @@ 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
@@ -54,7 +58,12 @@ 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-dependant logic /// Note that this cannot be shared across threads (due to platform-dependent logic
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, /// 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 _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered(); let _entered = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
// Certain platforms accept a mutable reference in their API. // 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 _span = tracing::debug_span!( let _entered = tracing::debug_span!(
"winit::EventLoop::listen_device_events", "winit::EventLoop::listen_device_events",
allowed = ?allowed allowed = ?allowed
) )