Compare commits

..

22 Commits

Author SHA1 Message Date
Mads Marquart
89687656d6 Emit suspended on shutdown 2024-05-29 14:28:36 +02:00
Mads Marquart
9027d8f350 Verify event order
When debug assertions are enabled, check that events are emitted in the
expected order, and if not, log an error.
2024-05-29 14:28:35 +02:00
Mads Marquart
5d8091fc7f Implement ApplicationHandler for &mut A and Box<A> (#3709) 2024-05-29 11:51:53 +02:00
Mads Marquart
d7abe0316e Update objc2 to v0.2.2 (#3702)
- Use new `bitflags!` support.
- Use `objc2-ui-kit`.
- Change usage of `Id` to `Retained`.
2024-05-27 14:49:22 +02:00
Diggory Hardy
5ea20fc905 event_loop: add is_x11 and is_wayland on EventLoop 2024-05-26 17:38:10 +04:00
Golden Water
e108fa2fbf Resize when size changes on scale change on macOS
This fixes an issue where the window glitched due to resize
when the user doesn't actually change the window, but macOS
function to update window size was still called.
2024-05-23 20:40:07 +04:00
Kirill Chibisov
fff6788c12 chore: explicitly use cfg_aliases 0.2.1
This correctly handles recent nightly lint that requires to explicitly
define the CFG guards.
2024-05-22 15:51:29 +04:00
Kirill Chibisov
3e8fa41073 event_loop: remove deprecated run APIs
The APIs are not well suited for the `&dyn ApplicationHandler` model and
`Box<dyn EventLoop>` structure, thus remove them.
2024-05-20 20:27:36 +04:00
Kevin Müller
2b1c8cea1b bugfix: Replace pointer dereference with read_unaligned
On Raspberry Pi, using the Rust crate eframe caused the program to crash on
mouse movement. The Backtrace lead to this specific line of code, and the exact
error was a "misaligned pointer dereference: address must be a multiple of 0x8
but is xxxx"

The edit has been tested with the Raspberry Pi, which works now.
2024-05-19 15:08:14 -07:00
Ryan Burleson
ab33fb8eda fix doc typo in application.rs (#3676) 2024-05-07 21:24:02 +02:00
linkmauve
b0b64a9a15 Reexport older versions of raw-window-handle
When the user decides to use an older version of raw-window-handle,
through the rwh_04 or rwh_05 features, it makes sense to reexport the
crate so they don’t have to depend on it manually and can instead use
winit::raw_window_handle.
2024-05-06 19:50:25 +04:00
Mads Marquart
d5d202d60e Reduce usage of direct msg_send! 2024-05-06 19:09:38 +04:00
Mads Marquart
cb39ab29f4 macOS: Move util::EMPTY_RANGE to usage spot (#3685) 2024-05-06 16:54:52 +02:00
Mads Marquart
0a3cacd577 Retain ApplicationDelegate in NSWindowDelegate and NSView
The delegate is only weakly referenced by NSApplication, so getting it
from there may fail if the event loop has been dropped.

Fixes #3668.
2024-05-06 18:29:07 +04:00
Mads Marquart
16fd2baba0 Use rustc-check-cfg (#3682) 2024-05-06 07:11:57 +02:00
daxpedda
7f8771a362 Web: fix Clippy v1.78 FPs (#3678) 2024-05-03 22:24:54 +02:00
Kirill Chibisov
337d50779c Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-04-27 20:02:40 +04:00
Joshua Pedrick
fd477986de Add UIGestureRecognizerDelegate and PanGestureRecogniser (#3597)
- Allow all gestures simultaneously recognized.
- Add PanGestureRecogniser with min/max number of touches.
- Fix sending delta values relative to Update instead to match macOS.
- Fix rotation gesture units from iOS to be in degrees instead of radians.

Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-04-27 15:55:04 +02:00
Mads Marquart
94664ff687 Don't set the background color when initializing with transparency (#3657)
Setting the background color changes how the window title bar appears,
which is something that the application should customize itself if it
wants this behaviour (and also, it wasn't set when calling
`set_transparent`, so the behaviour wasn't consistent).
2024-04-27 15:41:14 +02:00
growfrow
0fc8c37721 chore: fix some typos in comments (#3635)
Signed-off-by: growfrow <growfrow@outlook.com>
2024-04-27 15:29:11 +02:00
Kirill Chibisov
ec29c81ad2 chore: ensure that .cargo config is not published
Just in case, so the correct changelog will be rendered when pulling
the crate from the crates.io as archive and trying to build it.
2024-04-27 17:03:28 +04:00
Marijn Suijten
304a585493 android: bump to ndk 0.9.0 and android-activity 0.6.0 2024-04-27 13:35:11 +04:00
110 changed files with 2211 additions and 3964 deletions

8
.github/CODEOWNERS vendored
View File

@@ -1,6 +1,6 @@
# Android
/src/platform/android.rs @MarijnS95
/src/platform_impl/android @MarijnS95
/src/platform/android.rs @msiglreith @MarijnS95
/src/platform_impl/android @msiglreith @MarijnS95
# iOS
/src/platform/ios.rs @madsmtm
@@ -26,8 +26,8 @@
/src/platform_impl/web @daxpedda
# Windows
/src/platform/windows.rs @notgull
/src/platform_impl/windows @notgull
/src/platform/windows.rs @msiglreith
/src/platform_impl/windows @msiglreith
# Orbital (Redox OS)
/src/platform/orbital.rs @jackpot51

View File

@@ -10,7 +10,7 @@ jobs:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: taiki-e/checkout-action@v1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: taiki-e/checkout-action@v1
- uses: actions/checkout@v4
- uses: taiki-e/install-action@v2
with:
tool: typos-cli
@@ -88,7 +88,7 @@ jobs:
CMD: ${{ matrix.platform.cmd }}
steps:
- uses: taiki-e/checkout-action@v1
- uses: actions/checkout@v4
- name: Restore cache of cargo folder
# We use `restore` and later `save`, so that we can create the key after
@@ -107,12 +107,7 @@ jobs:
- name: Generate lockfile
# Also updates the crates.io index
run: |
cargo generate-lockfile
cargo update -p ahash --precise 0.8.7
cargo update -p bumpalo --precise 3.14.0
cargo update -p objc2-encode --precise 4.0.3
cargo update -p orbclient --precise 0.3.47
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0
- name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
@@ -225,24 +220,9 @@ jobs:
- { name: 'Windows', target: x86_64-pc-windows-gnu }
steps:
- uses: taiki-e/checkout-action@v1
- uses: EmbarkStudios/cargo-deny-action@v2
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check
log-level: error
arguments: --all-features --target ${{ matrix.platform.target }}
swc:
name: Minimize JavaScript
runs-on: ubuntu-latest
steps:
- uses: taiki-e/checkout-action@v1
- name: Install SWC
run: sudo npm i -g @swc/cli
- name: Run SWC
run: |
swc src/platform_impl/web/web_sys/worker.js -o src/platform_impl/web/web_sys/worker.min.js
- name: Check for diff
run: |
[[ -z $(git status -s) ]]

3
.gitignore vendored
View File

@@ -3,5 +3,8 @@ target/
rls/
.vscode/
*~
*.wasm
*.ts
*.js
#*#
.DS_Store

12
.swcrc
View File

@@ -1,12 +0,0 @@
{
"minify": true,
"jsc": {
"target": "es2022",
"minify": {
"compress": {
"unused": true
},
"mangle": true
}
}
}

View File

@@ -19,11 +19,6 @@ All patches have to be sent on Github as [pull requests][prs]. To simplify your
life during review it's recommended to check the "give contributors write access
to the branch" checkbox.
We use unstable Rustfmt options across the project, so please run
`cargo +nightly fmt` before submitting your work. If you are unable to do so,
the maintainers can do it for you before merging, just state so in your pull
request description.
#### Handling review
During the review process certain events could require an action from your side,

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.30.11"
version = "0.30.0"
authors = [
"The winit contributors",
"Pierre Krieger <pierre.krieger1708@gmail.com>",
@@ -14,17 +14,7 @@ rust-version.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
include = [
"/build.rs",
"/docs",
"/examples",
"/FEATURES.md",
"/LICENSE",
"/src",
"!/src/platform_impl/web/script",
"/src/platform_impl/web/script/**/*.min.js",
"/tests",
]
exclude = ["/.cargo"]
[package.metadata.docs.rs]
features = [
@@ -96,11 +86,11 @@ rwh_06 = { package = "raw-window-handle", version = "0.6", features = [
], optional = true }
serde = { workspace = true, optional = true }
smol_str = "0.2.0"
tracing = { version = "0.1.40", default-features = false }
tracing = { version = "0.1.40", default_features = false }
[dev-dependencies]
image = { version = "0.25.0", default-features = false, features = ["png"] }
tracing = { version = "0.1.40", default-features = false, features = ["log"] }
tracing = { version = "0.1.40", default_features = false, features = ["log"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
winit = { path = ".", features = ["rwh_05"] }
@@ -117,7 +107,6 @@ android-activity = "0.6.0"
ndk = { version = "0.9.0", default-features = false }
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
block2 = "0.5.1"
core-foundation = "0.9.3"
objc2 = "0.5.2"
@@ -127,7 +116,6 @@ core-graphics = "0.23.1"
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"block2",
"dispatch",
"NSArray",
"NSAttributedString",
@@ -135,7 +123,6 @@ features = [
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSPathUtilities",
@@ -153,7 +140,6 @@ features = [
"NSApplication",
"NSBitmapImageRep",
"NSButton",
"NSColor",
"NSControl",
"NSCursor",
"NSDragging",
@@ -180,13 +166,11 @@ features = [
[target.'cfg(target_os = "ios")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
"block2",
"dispatch",
"NSArray",
"NSEnumerator",
"NSGeometry",
"NSObjCRuntime",
"NSOperation",
"NSString",
"NSProcessInfo",
"NSThread",
@@ -201,8 +185,6 @@ features = [
"UIEvent",
"UIGeometry",
"UIGestureRecognizer",
"UITextInput",
"UITextInputTraits",
"UIOrientation",
"UIPanGestureRecognizer",
"UIPinchGestureRecognizer",
@@ -234,7 +216,6 @@ features = [
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_Security",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
@@ -254,7 +235,7 @@ features = [
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.13.0"
calloop = "0.12.3"
libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
@@ -264,18 +245,18 @@ rustix = { version = "0.38.4", default-features = false, features = [
"thread",
"process",
] }
sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = [
"calloop",
], optional = true }
sctk-adwaita = { version = "0.10.1", default-features = false, optional = true }
wayland-backend = { version = "0.3.10", default-features = false, features = [
sctk-adwaita = { version = "0.9.0", default_features = false, optional = true }
wayland-backend = { version = "0.3.0", default_features = false, features = [
"client_system",
], optional = true }
wayland-client = { version = "0.31.10", optional = true }
wayland-protocols = { version = "0.32.8", features = [
wayland-client = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.0", features = [
"staging",
], optional = true }
wayland-protocols-plasma = { version = "0.3.8", features = [
wayland-protocols-plasma = { version = "0.2.0", features = [
"client",
], optional = true }
x11-dl = { version = "2.19.1", optional = true }
@@ -293,63 +274,57 @@ xkbcommon-dl = "0.4.2"
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.4.1"
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
package = "web-sys"
version = "0.3.64"
features = [
'AbortController',
'AbortSignal',
'Blob',
'console',
'CssStyleDeclaration',
'Document',
'DomException',
'DomRect',
'DomRectReadOnly',
'Element',
'Event',
'EventTarget',
'FocusEvent',
'HtmlCanvasElement',
'HtmlElement',
'HtmlImageElement',
'ImageBitmap',
'ImageBitmapOptions',
'ImageBitmapRenderingContext',
'ImageData',
'IntersectionObserver',
'IntersectionObserverEntry',
'KeyboardEvent',
'MediaQueryList',
'MessageChannel',
'MessagePort',
'Node',
'PageTransitionEvent',
'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent',
'Url',
]
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.70"
js-sys = "0.3.64"
pin-project = "1"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-time = "1"
web_sys = { package = "web-sys", version = "0.3.70", features = [
"AbortController",
"AbortSignal",
"Blob",
"BlobPropertyBag",
"console",
"CssStyleDeclaration",
"Document",
"DomException",
"DomRect",
"DomRectReadOnly",
"Element",
"Event",
"EventTarget",
"FocusEvent",
"HtmlCanvasElement",
"HtmlElement",
"HtmlImageElement",
"ImageBitmap",
"ImageBitmapOptions",
"ImageBitmapRenderingContext",
"ImageData",
"IntersectionObserver",
"IntersectionObserverEntry",
"KeyboardEvent",
"MediaQueryList",
"MessageChannel",
"MessagePort",
"Navigator",
"Node",
"OrientationLockType",
"OrientationType",
"PageTransitionEvent",
"Permissions",
"PermissionState",
"PermissionStatus",
"PointerEvent",
"PremultiplyAlpha",
"ResizeObserver",
"ResizeObserverBoxOptions",
"ResizeObserverEntry",
"ResizeObserverOptions",
"ResizeObserverSize",
"Screen",
"ScreenOrientation",
"Url",
"VisibilityState",
"WheelEvent",
"Window",
"Worker",
] }
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
atomic-waker = "1"

View File

@@ -8,7 +8,7 @@
```toml
[dependencies]
winit = "0.30.11"
winit = "0.30.0"
```
## [Documentation](https://docs.rs/winit)
@@ -19,7 +19,7 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
## Contact Us
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room.
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit).
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
@@ -33,10 +33,6 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen
show something on the window you need to use the platform-specific getters provided by winit, or
another library.
## CONTRIBUTING
For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md).
## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to

View File

@@ -1,20 +1,15 @@
# https://embarkstudios.github.io/cargo-deny
# https://embarkstudios.github.io/cargo-deny/
# cargo install cargo-deny
# cargo update && cargo deny --target aarch64-apple-ios check
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
[graph]
all-features = true
exclude-dev = true
targets = [
{ triple = "aarch64-apple-ios" },
{ triple = "aarch64-linux-android" },
{ triple = "i686-pc-windows-gnu" },
{ triple = "i686-pc-windows-msvc" },
{ triple = "i686-unknown-linux-gnu" },
{ triple = "wasm32-unknown-unknown", features = [
"atomics",
] },
{ triple = "wasm32-unknown-unknown" },
{ triple = "x86_64-apple-darwin" },
{ triple = "x86_64-apple-ios" },
{ triple = "x86_64-pc-windows-gnu" },
@@ -23,56 +18,46 @@ targets = [
{ triple = "x86_64-unknown-redox" },
]
[licenses]
allow = [
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"ISC", # https://tldrlegal.com/license/isc-license
"MIT", # https://tldrlegal.com/license/mit-license
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
]
confidence-threshold = 1.0
private = { ignore = true }
[advisories]
vulnerability = "deny"
unmaintained = "warn"
yanked = "deny"
ignore = []
[bans]
multiple-versions = "deny"
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
deny = []
skip = [
{ crate = "raw-window-handle", reason = "we depend on multiple behind features" },
{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" },
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
{ name = "libloading" }, # x11rb uses a different version until the next update
]
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
skip-tree = []
[bans.build]
include-archives = true
interpreted = "deny"
[[bans.build.bypass]]
[licenses]
private = { ignore = true }
unlicensed = "deny"
allow-osi-fsf-free = "neither"
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
copyleft = "deny"
allow = [
{ path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" },
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
"ISC", # https://tldrlegal.com/license/-isc-license
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
"MIT-0", # https://choosealicense.com/licenses/mit-0/
"MIT", # https://tldrlegal.com/license/mit-license
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
]
crate = "android-activity"
[[bans.build.bypass]]
allow-globs = ["ci/*", "githooks/*"]
crate = "zerocopy"
[[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

@@ -3,50 +3,45 @@
fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap;
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize, Position};
use winit::event::{ElementState, Event, KeyEvent, WindowEvent};
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::Window;
use winit::window::{Window, WindowId};
#[path = "util/fill.rs"]
mod fill;
fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window {
let parent = parent.raw_window_handle().unwrap();
let mut window_attributes = Window::default_attributes()
.with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };
event_loop.create_window(window_attributes).unwrap()
#[derive(Default)]
struct Application {
parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, Window>,
}
let mut windows = HashMap::new();
impl ApplicationHandler for Application {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
let mut parent_window_id = None;
println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id());
event_loop.run(move |event: Event<()>, event_loop| {
match event {
Event::Resumed => {
let attributes = Window::default_attributes()
.with_title("parent window")
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_inner_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
self.windows.insert(window.id(), window);
}
parent_window_id = Some(window.id());
println!("Parent window id: {parent_window_id:?})");
windows.insert(window.id(), window);
},
Event::WindowEvent { window_id, event } => match event {
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
windows.clear();
self.windows.clear();
event_loop.exit();
},
WindowEvent::CursorEntered { device_id: _ } => {
@@ -61,22 +56,38 @@ fn main() -> Result<(), impl std::error::Error> {
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let parent_window = windows.get(&parent_window_id.unwrap()).unwrap();
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window(parent_window, event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
windows.insert(child_id, child_window);
self.windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = windows.get(&window_id) {
if let Some(window) = self.windows.get(&window_id) {
fill::fill_window(window);
}
},
_ => (),
},
_ => (),
}
}
})
}
fn spawn_child_window(parent: &Window, event_loop: &ActiveEventLoop) -> Window {
let parent = parent.raw_window_handle().unwrap();
let mut window_attributes = Window::default_attributes()
.with_title("child window")
.with_inner_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };
event_loop.create_window(window_attributes).unwrap()
}
let event_loop: EventLoop<()> = EventLoop::new().unwrap();
let mut app = Application::default();
event_loop.run_app(&mut app)
}
#[cfg(all(feature = "rwh_06", not(any(x11_platform, macos_platform, windows_platform))))]

View File

@@ -17,7 +17,9 @@ use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event::{
DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, StartCause, WindowEvent,
};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{Key, ModifiersState};
use winit::window::{
@@ -31,8 +33,6 @@ use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMac
use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
};
#[cfg(x11_platform)]
use winit::platform::x11::WindowAttributesExtX11;
#[path = "util/tracing.rs"]
mod tracing;
@@ -142,28 +142,6 @@ impl Application {
window_attributes = window_attributes.with_activation_token(token);
}
#[cfg(x11_platform)]
match std::env::var("X11_VISUAL_ID") {
Ok(visual_id_str) => {
info!("Using X11 visual id {visual_id_str}");
let visual_id = visual_id_str.parse()?;
window_attributes = window_attributes.with_x11_visual(visual_id);
},
Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"),
}
#[cfg(x11_platform)]
match std::env::var("X11_SCREEN_ID") {
Ok(screen_id_str) => {
info!("Placing the window on X11 screen {screen_id_str}");
let screen_id = screen_id_str.parse()?;
window_attributes = window_attributes.with_x11_screen(screen_id);
},
Err(_) => info!(
"Set the X11_SCREEN_ID env variable to place the window on non-default screen"
),
}
#[cfg(macos_platform)]
if let Some(tab_id) = _tab_id {
window_attributes = window_attributes.with_tabbing_identifier(&tab_id);
@@ -236,12 +214,6 @@ impl Application {
Action::PrintHelp => self.print_help(),
#[cfg(macos_platform)]
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
Action::SetTheme(theme) => {
window.window.set_theme(theme);
// Get the resulting current theme to draw with
let actual_theme = theme.or_else(|| window.window.theme()).unwrap_or(Theme::Dark);
window.set_draw_theme(actual_theme);
},
#[cfg(macos_platform)]
Action::CreateNewTab => {
let tab_id = window.window.tabbing_identifier();
@@ -333,6 +305,12 @@ impl Application {
}
impl ApplicationHandler<UserEvent> for Application {
fn new_events(&mut self, _event_loop: &ActiveEventLoop, start_cause: StartCause) {
if let StartCause::Init = start_cause {
info!("Started the event loop");
}
}
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
info!("User event: {event:?}");
}
@@ -350,6 +328,7 @@ impl ApplicationHandler<UserEvent> for Application {
match event {
WindowEvent::Resized(size) => {
info!("Resized({size:?})");
window.resize(size);
},
WindowEvent::Focused(focused) => {
@@ -364,7 +343,7 @@ impl ApplicationHandler<UserEvent> for Application {
},
WindowEvent::ThemeChanged(theme) => {
info!("Theme changed to {theme:?}");
window.set_draw_theme(theme);
window.set_theme(theme);
},
WindowEvent::RedrawRequested => {
if let Err(err) = window.draw() {
@@ -763,8 +742,8 @@ impl WindowState {
self.window.request_redraw();
}
/// Change the theme that things are drawn in.
fn set_draw_theme(&mut self, theme: Theme) {
/// Change the theme.
fn set_theme(&mut self, theme: Theme) {
self.theme = theme;
self.window.request_redraw();
}
@@ -914,7 +893,6 @@ enum Action {
ShowWindowMenu,
#[cfg(macos_platform)]
CycleOptionAsAlt,
SetTheme(Option<Theme>),
#[cfg(macos_platform)]
CreateNewTab,
RequestResize,
@@ -946,9 +924,6 @@ impl Action {
Action::ShowWindowMenu => "Show window menu",
#[cfg(macos_platform)]
Action::CycleOptionAsAlt => "Cycle option as alt mode",
Action::SetTheme(None) => "Change to the system theme",
Action::SetTheme(Some(Theme::Light)) => "Change to a light theme",
Action::SetTheme(Some(Theme::Dark)) => "Change to a dark theme",
#[cfg(macos_platform)]
Action::CreateNewTab => "Create new tab",
Action::RequestResize => "Request a resize",
@@ -1093,10 +1068,6 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Action::AnimationCustomCursor,
),
Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
// K.
Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)),
Binding::new("K", ModifiersState::SUPER, Action::SetTheme(Some(Theme::Light))),
Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))),
#[cfg(macos_platform)]
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
#[cfg(macos_platform)]

View File

@@ -12,6 +12,7 @@ on how to add them:
### Added
- Add `Window::turbo()`, implemented on X11, Wayland, and Web.
- Add traits `EventLoopExtWayland` and `EventLoopExtX11`, providing methods `is_wayland` and `is_x11` on `EventLoop`.
- On X11, add `Window::some_rare_api`.
- On X11, add `Window::even_more_rare_api`.
- On Wayland, add `Window::common_api`.
@@ -39,3 +40,19 @@ The migration guide could reference other migration examples in the current
changelog entry.
## Unreleased
### Added
- Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`.
- Implement `ApplicationHandler` for `&mut` references and heap allocations to something that implements `ApplicationHandler`.
### Removed
- Remove `EventLoop::run`.
- Remove `EventLoopExtRunOnDemand::run_on_demand`.
- Remove `EventLoopExtPumpEvents::pump_events`.
### Fixed
- On macOS, fix panic on exit when dropping windows outside the event loop.
- On macOS, fix window dragging glitches when dragging across a monitor boundary with different scale factor.

View File

@@ -137,7 +137,7 @@
- On X11, non-resizable windows now have maximize explicitly disabled.
- On Windows, support paths longer than MAX_PATH (260 characters) in `WindowEvent::DroppedFile`
and `WindowEvent::HoveredFile`.
and `WindowEvent::HoveredFile`.
- On Mac, implement `DeviceEvent::Button`.
- Change `Event::Suspended(true / false)` to `Event::Suspended` and `Event::Resumed`.
- On X11, fix sanity check which checks that a monitor's reported width and height (in millimeters) are non-zero when calculating the DPI factor.

View File

@@ -1,171 +1,3 @@
## 0.30.11
### Fixed
- On Windows, fixed crash in should_apps_use_dark_mode() for Windows versions < 17763.
- On Wayland, fixed `pump_events` driven loop deadlocking when loop was not drained before exit.
## 0.30.10
### Added
- On Windows, add `IconExtWindows::from_resource_name`.
- On Windows, add `CursorGrabMode::Locked`.
- On Wayland, add `WindowExtWayland::xdg_toplevel`.
### Changed
- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself).
- On iOS, remove custom application delegates. You are now allowed to override the
application delegate yourself.
- On iOS, no longer act as-if the application successfully open all URLs. Override
`application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself.
### Fixed
- On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw.
- On macOS, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game`
- On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode
- On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus.
- On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`.
- On Wayland, apply fractional scaling to custom cursors.
- On macOS, fixed `run_app_on_demand` returning without closing open windows.
- On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates.
- On macOS, store monitor handle to avoid panics after going in/out of sleep.
- On macOS, allow certain invalid monitor handles and return `None` instead of panicking.
- On Windows, fixed `Ime::Preedit` cursor offset calculation.
## 0.30.9
### Changed
- On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`.
### Fixed
- On X11, fix crash with uim.
- On X11, fix modifiers for keys that were sent by the same X11 request.
- On iOS, fix high CPU usage even when using `ControlFlow::Wait`.
## 0.30.8
### Added
- `ActivationToken::from_raw` and `ActivationToken::into_raw`.
- On X11, add a workaround for disabling IME on GNOME.
### Fixed
- On Windows, fixed the event loop not waking on accessibility requests.
- On X11, fixed cursor grab mode state tracking on error.
## 0.30.7
### Fixed
- On X11, fixed KeyboardInput delivered twice when IME enabled.
## 0.30.6
### Added
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
variables to test the respective modifiers of window creation.
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
### Fixed
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
default activation policy, unless explicitly provided during initialization.
- On macOS, fix crash when calling `drag_window()` without a left click present.
- On X11, key events forward to IME anyway, even when it's disabled.
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
- On macOS, fix crash when pressing Caps Lock in certain configurations.
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.
- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor.
## 0.30.5
### Added
- Add `ActiveEventLoop::system_theme()`, returning the current system theme.
- On Web, implement `Error` for `platform::web::CustomCursorError`.
- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop.
### Fixed
- On MacOS, fix building with `feature = "rwh_04"`.
- On Web, pen events are now routed through to `WindowEvent::Cursor*`.
- On macOS, fix panic when releasing not available monitor.
- On MacOS, return the system theme in `Window::theme()` if no theme override is set.
## 0.30.4
### Changed
- `DeviceId::dummy()` and `WindowId::dummy()` are no longer marked `unsafe`.
### Fixed
- On Wayland, avoid crashing when compositor is misbehaving.
- On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending
`WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested`
to not be de-duplicated.
- Account for different browser engine implementations of pointer movement coordinate space.
## 0.30.3
### Added
- On Web, add `EventLoopExtWebSys::(set_)poll_strategy()` to allow setting
control flow strategies before starting the event loop.
- On Web, add `WaitUntilStrategy`, which allows to set different strategies for
`ControlFlow::WaitUntil`. By default the Prioritized Task Scheduling API is
used, with a fallback to `setTimeout()` with a trick to circumvent throttling
to 4ms. But an option to use a Web worker to schedule the timer is available
as well, which commonly prevents any throttling when the window is not focused.
### Changed
- On macOS, set the window theme on the `NSWindow` instead of application-wide.
### Fixed
- On X11, build on arm platforms.
- On macOS, fixed `WindowBuilder::with_theme` not having any effect on the window.
## 0.30.2
### Fixed
- On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately
when not called from inside the event loop. Now queues a microtask instead.
- On Web, stop overwriting default cursor with `CursorIcon::Default`.
- On Web, prevent crash when using `InnerSizeWriter::request_inner_size()`.
- On macOS, fix not working opacity for entire window.
## 0.30.1
### Added
- Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`.
- Implement `ApplicationHandler` for `&mut` references and heap allocations to something that implements `ApplicationHandler`.
### Fixed
- On macOS, fix panic on exit when dropping windows outside the event loop.
- On macOS, fix window dragging glitches when dragging across a monitor boundary with different scale factor.
- On macOS, fix the range in `Ime::Preedit`.
- On macOS, use the system's internal mechanisms for queuing events.
- On macOS, handle events directly instead of queuing when possible.
## 0.30.0
### Added
@@ -233,7 +65,7 @@
`Fn`. The semantics are mostly the same, given that the capture list of the
closure is your new `State`. Consider the following code:
```rust,no_run
```rust,no_run,ignore
use winit::event::Event;
use winit::event_loop::EventLoop;
use winit::window::Window;

View File

@@ -3,20 +3,20 @@
- Added event `WindowEvent::HiDPIFactorChanged`.
- Added method `MonitorId::get_hidpi_factor`.
- Deprecated `get_inner_size_pixels` and `get_inner_size_points` methods of `Window` in favor of
`get_inner_size`.
`get_inner_size`.
- **Breaking:** `EventsLoop` is `!Send` and `!Sync` because of platform-dependant constraints,
but `Window`, `WindowId`, `DeviceId` and `MonitorId` guaranteed to be `Send`.
- `MonitorId::get_position` now returns `(i32, i32)` instead of `(u32, u32)`.
- Rewrite of the wayland backend to use wayland-client-0.11
- Support for dead keys on wayland for keyboard utf8 input
- Monitor enumeration on Windows is now implemented using `EnumDisplayMonitors` instead of
`EnumDisplayDevices`. This changes the value returned by `MonitorId::get_name()`.
`EnumDisplayDevices`. This changes the value returned by `MonitorId::get_name()`.
- On Windows added `MonitorIdExt::hmonitor` method
- Impl `Clone` for `EventsLoopProxy`
- `EventsLoop::get_primary_monitor()` on X11 will fallback to any available monitor if no primary is found
- Support for touch event on wayland
- `WindowEvent`s `MouseMoved`, `MouseEntered`, and `MouseLeft` have been renamed to
`CursorMoved`, `CursorEntered`, and `CursorLeft`.
`CursorMoved`, `CursorEntered`, and `CursorLeft`.
- New `DeviceEvent`s added, `MouseMotion` and `MouseWheel`.
- Send `CursorMoved` event after `CursorEntered` and `Focused` events.
- Add support for `ModifiersState`, `MouseMove`, `MouseInput`, `MouseMotion` for emscripten backend.

View File

@@ -389,8 +389,6 @@ pub enum WindowEvent {
/// Applications might wish to react to this to change the theme of the content of the window
/// when the system changes the window theme.
///
/// This only reports a change if the window theme was not overridden by [`Window::set_theme`].
///
/// ## Platform-specific
///
/// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported.
@@ -449,13 +447,16 @@ pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
/// Returns a dummy id, useful for unit testing.
///
/// # Notes
/// # Safety
///
/// The only guarantee made about the return value of this function is that
/// it will always be equal to itself and to future values returned by this function.
/// No other guarantees are made. This may be equal to a real `DeviceId`.
pub const fn dummy() -> Self {
DeviceId(platform_impl::DeviceId::dummy())
///
/// **Passing this into a winit function will result in undefined behavior.**
pub const unsafe fn dummy() -> Self {
#[allow(unused_unsafe)]
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
}
}
@@ -532,11 +533,11 @@ pub struct KeyEvent {
/// ## Caveats
///
/// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that
/// implements DVORAK in hardware (or firmware)
/// implements DVORAK in hardware (or firmware)
/// - Your application will likely have to handle keyboards which are missing keys that your
/// own keyboard has.
/// own keyboard has.
/// - Certain `KeyCode`s will move between a couple of different positions depending on what
/// layout the keyboard was manufactured to support.
/// layout the keyboard was manufactured to support.
///
/// **Because of these caveats, it is important that you provide users with a way to configure
/// most (if not all) keybinds in your application.**
@@ -558,7 +559,8 @@ pub struct KeyEvent {
///
/// This has two use cases:
/// - Allows querying whether the current input is a Dead key.
/// - Allows handling key-bindings on platforms which don't support [`key_without_modifiers`].
/// - Allows handling key-bindings on platforms which don't
/// support [`key_without_modifiers`].
///
/// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard
/// shortcuts, **it is important that you provide users with a way to configure your
@@ -566,8 +568,8 @@ pub struct KeyEvent {
/// incompatible keyboard layout.**
///
/// ## Platform-specific
/// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the
/// browser/OS.
/// - **Web:** Dead keys might be reported as the real key instead
/// of `Dead` depending on the browser/OS.
///
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
pub logical_key: keyboard::Key,
@@ -849,8 +851,8 @@ pub struct Touch {
///
/// - Only available on **iOS** 9.0+, **Windows** 8+, **Web**, and **Android**.
/// - **Android**: This will never be [None]. If the device doesn't support pressure
/// sensitivity, force will either be 0.0 or 1.0. Also see the
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
/// sensitivity, force will either be 0.0 or 1.0. Also see the
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).
pub force: Option<Force>,
/// Unique identifier of a finger.
pub id: u64,
@@ -1017,7 +1019,7 @@ mod tests {
($closure:expr) => {{
#[allow(unused_mut)]
let mut x = $closure;
let did = event::DeviceId::dummy();
let did = unsafe { event::DeviceId::dummy() };
#[allow(deprecated)]
{
@@ -1027,7 +1029,7 @@ mod tests {
use crate::window::WindowId;
// Mainline events.
let wid = WindowId::dummy();
let wid = unsafe { WindowId::dummy() };
x(UserEvent(()));
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
@@ -1152,11 +1154,11 @@ mod tests {
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event<()>| {
let _ = format!("{event:?}");
let _ = format!("{:?}", event);
});
let _ = event::StartCause::Init.clone();
let did = crate::event::DeviceId::dummy().clone();
let did = unsafe { crate::event::DeviceId::dummy() }.clone();
HashSet::new().insert(did);
let mut set = [did, did, did];
set.sort_unstable();

View File

@@ -20,10 +20,9 @@ use web_time::{Duration, Instant};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError};
use crate::event::Event;
use crate::monitor::MonitorHandle;
use crate::platform_impl;
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
/// Provides a way to retrieve events from the system and from the windows that were registered to
/// the events loop.
@@ -152,6 +151,7 @@ impl fmt::Debug for ActiveEventLoop {
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
/// [`Event::AboutToWait`]: crate::event::Event::AboutToWait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
@@ -209,6 +209,110 @@ impl EventLoop<()> {
}
}
#[cfg(debug_assertions)]
pub(crate) fn ensure_event_order<'a, T: 'static>(
handler: impl ApplicationHandler<T> + 'a,
) -> impl ApplicationHandler<T> + 'a {
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::window::WindowId;
#[derive(Default, Debug, PartialEq, Eq, Clone)]
enum State {
#[default]
NotRunning,
Suspended,
Running,
Waiting,
}
impl State {
#[track_caller]
fn expect(&self, expected: State) {
if *self != expected {
tracing::error!("expected state to be {expected:?}, found {self:?}");
}
}
#[track_caller]
fn transition(&mut self, from: State, to: State) {
if *self != from {
tracing::error!(
"invalid state transition to {to:?}. Expected {from:?}, found {self:?}"
);
}
*self = to;
}
}
struct EnsureEventOrder<A> {
inner: A,
state: State,
}
impl<A: ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for EnsureEventOrder<A> {
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
match cause {
StartCause::Init => self.state.transition(State::NotRunning, State::Suspended),
_ => self.state.transition(State::Waiting, State::Running),
}
self.inner.new_events(event_loop, cause);
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Suspended, State::Running);
self.inner.resumed(event_loop);
}
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Running, State::Suspended);
self.inner.suspended(event_loop);
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Running, State::Waiting);
self.inner.about_to_wait(event_loop);
}
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
self.state.transition(State::Suspended, State::NotRunning);
self.inner.exiting(event_loop);
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
self.state.expect(State::Running);
self.inner.user_event(event_loop, event);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
self.state.expect(State::Running);
self.inner.window_event(event_loop, window_id, event);
}
fn device_event(
&mut self,
event_loop: &ActiveEventLoop,
device_id: DeviceId,
event: DeviceEvent,
) {
self.state.expect(State::Running);
self.inner.device_event(event_loop, device_id, event);
}
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
// TODO: What states are allowed when receiving this?
self.inner.memory_warning(event_loop);
}
}
EnsureEventOrder { inner: handler, state: State::NotRunning }
}
impl<T> EventLoop<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
@@ -216,21 +320,6 @@ impl<T> EventLoop<T> {
EventLoopBuilder { platform_specific: Default::default(), _p: PhantomData }
}
/// See [`run_app`].
///
/// [`run_app`]: Self::run_app
#[inline]
#[deprecated = "use `EventLoop::run_app` instead"]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run<F>(self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &ActiveEventLoop),
{
let _span = tracing::debug_span!("winit::EventLoop::run").entered();
self.event_loop.run(event_handler)
}
/// Run the application with the event loop on the calling thread.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
@@ -262,7 +351,9 @@ impl<T> EventLoop<T> {
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
self.event_loop.run(|event, event_loop| dispatch_event_for_app(app, event_loop, event))
#[cfg(debug_assertions)]
let app = &mut ensure_event_order(app);
self.event_loop.run_app(app)
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
@@ -437,17 +528,6 @@ impl ActiveEventLoop {
self.p.listen_device_events(allowed);
}
/// Returns the current system theme.
///
/// Returns `None` if it cannot be determined on the current platform.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
pub fn system_theme(&self) -> Option<Theme> {
self.p.system_theme()
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.p.set_control_flow(control_flow)
@@ -460,7 +540,7 @@ impl ActiveEventLoop {
/// This exits the event loop.
///
/// See [`LoopExiting`][Event::LoopExiting].
/// See [`LoopExiting`][crate::event::Event::LoopExiting].
pub fn exit(&self) {
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
@@ -501,7 +581,7 @@ unsafe impl rwh_05::HasRawDisplayHandle for ActiveEventLoop {
/// A proxy for the underlying display handle.
///
/// The purpose of this type is to provide a cheaply cloneable handle to the underlying
/// The purpose of this type is to provide a cheaply clonable handle to the underlying
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`]
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
@@ -629,23 +709,3 @@ impl AsyncRequestSerial {
Self { serial }
}
}
/// Shim for various run APIs.
#[inline(always)]
pub(crate) fn dispatch_event_for_app<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
event_loop: &ActiveEventLoop,
event: Event<T>,
) {
match event {
Event::NewEvents(cause) => app.new_events(event_loop, cause),
Event::WindowEvent { window_id, event } => app.window_event(event_loop, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(event_loop, device_id, event),
Event::UserEvent(event) => app.user_event(event_loop, event),
Event::Suspended => app.suspended(event_loop),
Event::Resumed => app.resumed(event_loop),
Event::AboutToWait => app.about_to_wait(event_loop),
Event::LoopExiting => app.exiting(event_loop),
Event::MemoryWarning => app.memory_warning(event_loop),
}
}

View File

@@ -1232,7 +1232,7 @@ pub enum NamedKey {
Dimmer,
/// Swap video sources. (`VK_DISPLAY_SWAP`)
DisplaySwap,
/// Select Digital Video Recorder. (`KEYCODE_DVR`)
/// Select Digital Video Rrecorder. (`KEYCODE_DVR`)
DVR,
/// Exit the current application. (`VK_EXIT`)
Exit,

View File

@@ -7,12 +7,7 @@
//!
//! ```no_run
//! use winit::event_loop::EventLoop;
//!
//! # // Intentionally use `fn main` for clarity
//! fn main() {
//! let event_loop = EventLoop::new().unwrap();
//! // ...
//! }
//! let event_loop = EventLoop::new().unwrap();
//! ```
//!
//! Then you create a [`Window`] with [`create_window`].
@@ -89,22 +84,19 @@
//! }
//! }
//!
//! # // Intentionally use `fn main` for clarity
//! fn main() {
//! let event_loop = EventLoop::new().unwrap();
//! let event_loop = EventLoop::new().unwrap();
//!
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! event_loop.set_control_flow(ControlFlow::Poll);
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications.
//! event_loop.set_control_flow(ControlFlow::Poll);
//!
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait);
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
//! // This is ideal for non-game applications that only update in response to user
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait);
//!
//! let mut app = App::default();
//! event_loop.run_app(&mut app);
//! }
//! let mut app = App::default();
//! event_loop.run_app(&mut app);
//! ```
//!
//! [`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
@@ -165,6 +157,7 @@
//! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId
//! [`WindowAttributes`]: window::WindowAttributes
//! [window_new]: window::Window::new
//! [`create_window`]: event_loop::ActiveEventLoop::create_window
//! [`Window::id()`]: window::Window::id
//! [`WindowEvent`]: event::WindowEvent
@@ -184,9 +177,6 @@
// doc
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
#![allow(clippy::missing_safety_doc)]
#![warn(clippy::uninlined_format_args)]
// TODO: wasm-binding needs to be updated for that to be resolved, for now just silence it.
#![cfg_attr(web_platform, allow(unknown_lints, wasm_c_abi))]
#[cfg(feature = "rwh_04")]
pub use rwh_04 as raw_window_handle_04;

View File

@@ -62,7 +62,7 @@
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
//! with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.11",
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.0",
//! features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
@@ -76,22 +76,12 @@ use crate::window::{Window, WindowAttributes};
use self::activity::{AndroidApp, ConfigurationRef, Rect};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
pub trait EventLoopExtAndroid {}
impl<T> EventLoopExtAndroid for EventLoop<T> {
fn android_app(&self) -> &AndroidApp {
&self.event_loop.android_app
}
}
impl<T> EventLoopExtAndroid for EventLoop<T> {}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
pub trait ActiveEventLoopExtAndroid {}
/// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid {
@@ -110,11 +100,7 @@ impl WindowExtAndroid for Window {
}
}
impl ActiveEventLoopExtAndroid for ActiveEventLoop {
fn android_app(&self) -> &AndroidApp {
&self.p.app
}
}
impl ActiveEventLoopExtAndroid for ActiveEventLoop {}
/// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {}
@@ -122,9 +108,9 @@ pub trait WindowAttributesExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop
/// Associates the `AndroidApp` that was passed to `android_main()` with the event loop
///
/// This must be called on Android since the [`AndroidApp`] is not global state.
/// This must be called on Android since the `AndroidApp` is not global state.
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
/// Calling this will mark the volume keys to be manually handled by the application
@@ -161,7 +147,7 @@ impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
/// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit.
///
/// For compatibility applications should then import the [`AndroidApp`] type for
/// For compatibility applications should then import the `AndroidApp` type for
/// their `android_main(app: AndroidApp)` function like:
/// ```rust
/// #[cfg(target_os = "android")]

View File

@@ -3,14 +3,11 @@
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
//! iOS 9.3.
//!
//! ## Window initialization
//!
//! iOS's main `UIApplicationMain` does some init work that's required by all
//! UI-related code (see issue [#1705]). It is best to create your windows
//! inside [`ApplicationHandler::resumed`].
//! inside `Event::Resumed`.
//!
//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
//!
//! ## Building app
//!
@@ -66,16 +63,6 @@
//! opengl will result in segfault.
//!
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
//!
//! ## Custom `UIApplicationDelegate`
//!
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
//! though, you might want to access some of the more niche stuff that [the application
//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it
//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not
//! register an application delegate, so you can set up a custom one in a nib file instead.
//!
//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc
use std::os::raw::c_void;

View File

@@ -3,84 +3,16 @@
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
//! itself), and is regularly tested on macOS 10.14.
//!
//! ## Window initialization
//!
//! A lot of functionality expects the application to be ready before you
//! start doing anything; this includes creating windows, fetching monitors,
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
//!
//! If you encounter problems, you should try doing your initialization inside
//! [`ApplicationHandler::resumed`].
//! `Event::Resumed`.
//!
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
//!
//! ## Custom `NSApplicationDelegate`
//!
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
//! though, you might want to do more niche stuff, such as [handle when the user re-activates the
//! application][reopen]. Such functionality is not exposed directly in Winit, since it would
//! increase the API surface by quite a lot.
//!
//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc
//!
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is
//! to register your own application delegate, as outlined in the following example (see
//! `objc2-app-kit` for more detailed information).
#![cfg_attr(target_os = "macos", doc = "```")]
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
//! use objc2::rc::Retained;
//! use objc2::runtime::ProtocolObject;
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
//! use winit::event_loop::EventLoop;
//!
//! declare_class!(
//! struct AppDelegate;
//!
//! unsafe impl ClassType for AppDelegate {
//! type Super = NSObject;
//! type Mutability = mutability::MainThreadOnly;
//! const NAME: &'static str = "MyAppDelegate";
//! }
//!
//! impl DeclaredClass for AppDelegate {}
//!
//! unsafe impl NSObjectProtocol for AppDelegate {}
//!
//! unsafe impl NSApplicationDelegate for AppDelegate {
//! #[method(application:openURLs:)]
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
//! // Note: To specifically get `application:openURLs:` to work, you _might_
//! // have to bundle your application. This is not done in this example.
//! println!("open urls: {application:?}, {urls:?}");
//! }
//! }
//! );
//!
//! impl AppDelegate {
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
//! }
//! }
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let event_loop = EventLoop::new()?;
//!
//! let mtm = MainThreadMarker::new().unwrap();
//! let delegate = AppDelegate::new(mtm);
//! // Important: Call `sharedApplication` after `EventLoop::new`,
//! // doing it before is not yet supported.
//! let app = NSApplication::sharedApplication(mtm);
//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
//!
//! // event_loop.run_app(&mut my_app);
//! Ok(())
//! }
//! ```
use std::os::raw::c_void;
@@ -162,14 +94,6 @@ pub trait WindowExtMacOS {
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
fn option_as_alt(&self) -> OptionAsAlt;
/// Disable the Menu Bar and Dock in Simple or Borderless Fullscreen mode. Useful for games.
/// The effect is applied when [`WindowExtMacOS::set_simple_fullscreen`] or
/// [`Window::set_fullscreen`] is called.
fn set_borderless_game(&self, borderless_game: bool);
/// Getter for the [`WindowExtMacOS::set_borderless_game`].
fn is_borderless_game(&self) -> bool;
}
impl WindowExtMacOS for Window {
@@ -242,16 +166,6 @@ impl WindowExtMacOS for Window {
fn option_as_alt(&self) -> OptionAsAlt {
self.window.maybe_wait_on_main(|w| w.option_as_alt())
}
#[inline]
fn set_borderless_game(&self, borderless_game: bool) {
self.window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
}
#[inline]
fn is_borderless_game(&self) -> bool {
self.window.maybe_wait_on_main(|w| w.is_borderless_game())
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
@@ -302,8 +216,6 @@ pub trait WindowAttributesExtMacOS {
///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
/// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
fn with_borderless_game(self, borderless_game: bool) -> Self;
}
impl WindowAttributesExtMacOS for WindowAttributes {
@@ -372,21 +284,12 @@ impl WindowAttributesExtMacOS for WindowAttributes {
self.platform_specific.option_as_alt = option_as_alt;
self
}
#[inline]
fn with_borderless_game(mut self, borderless_game: bool) -> Self {
self.platform_specific.borderless_game = borderless_game;
self
}
}
pub trait EventLoopBuilderExtMacOS {
/// Sets the activation policy for the application. If used, this will override
/// any relevant settings provided in the package manifest.
/// For instance, `with_activation_policy(ActivationPolicy::Regular)` will prevent
/// the application from running as an "agent", even if LSUIElement is set to true.
/// Sets the activation policy for the application.
///
/// If unused, the Winit will honor the package manifest.
/// It is set to [`ActivationPolicy::Regular`] by default.
///
/// # Example
///
@@ -438,7 +341,7 @@ pub trait EventLoopBuilderExtMacOS {
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
#[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = Some(activation_policy);
self.platform_specific.activation_policy = activation_policy;
self
}

View File

@@ -1,12 +1,13 @@
use std::time::Duration;
use crate::application::ApplicationHandler;
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
use crate::event_loop::EventLoop;
/// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
///
/// [`Event::UserEvent`]: crate::event::Event::UserEvent
type UserEvent: 'static;
/// Pump the `EventLoop` to check for and dispatch pending events.
@@ -51,19 +52,19 @@ pub trait EventLoopExtPumpEvents {
/// buffered and handled outside of Winit include:
/// - `RedrawRequested` events, used to schedule rendering.
///
/// macOS for example uses a `drawRect` callback to drive rendering
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
/// macOS for example uses a `drawRect` callback to drive rendering
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
///
/// For portability it's strongly recommended that applications should
/// keep their rendering inside the closure provided to Winit.
/// For portability it's strongly recommended that applications should
/// keep their rendering inside the closure provided to Winit.
/// - Any lifecycle events, such as `Suspended` / `Resumed`.
///
/// The handling of these events needs to be synchronized with the
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
/// The handling of these events needs to be synchronized with the
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
///
/// ## Supported Platforms
///
@@ -74,13 +75,13 @@ pub trait EventLoopExtPumpEvents {
///
/// ## Unsupported Platforms
///
/// - **Web:** This API is fundamentally incompatible with the event-based way in which Web
/// browsers work because it's not possible to have a long-running external loop that would
/// block the browser and there is nothing that can be polled to ask for new new events.
/// Events are delivered via callbacks based on an event loop that is internal to the browser
/// itself.
/// - **Web:** This API is fundamentally incompatible with the event-based way in which
/// Web browsers work because it's not possible to have a long-running external
/// loop that would block the browser and there is nothing that can be
/// polled to ask for new new events. Events are delivered via callbacks based
/// on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so
/// there's no way to support the same approach to polling as on MacOS.
/// there's no way to support the same approach to polling as on MacOS.
///
/// ## Platform-specific
///
@@ -107,30 +108,18 @@ pub trait EventLoopExtPumpEvents {
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
#[allow(deprecated)]
self.pump_events(timeout, |event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
/// See [`pump_app_events`].
///
/// [`pump_app_events`]: Self::pump_app_events
#[deprecated = "use EventLoopExtPumpEvents::pump_app_events"]
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
) -> PumpStatus;
}
impl<T> EventLoopExtPumpEvents for EventLoop<T> {
type UserEvent = T;
fn pump_events<F>(&mut self, timeout: Option<Duration>, event_handler: F) -> PumpStatus
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.pump_events(timeout, event_handler)
fn pump_app_events<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
self.event_loop.pump_app_events(timeout, app)
}
}

View File

@@ -1,7 +1,6 @@
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
use crate::event_loop::{ActiveEventLoop, EventLoop};
#[cfg(doc)]
use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
@@ -9,15 +8,9 @@ use crate::{platform::pump_events::EventLoopExtPumpEvents, window::Window};
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand {
/// A type provided by the user that can be passed through [`Event::UserEvent`].
type UserEvent: 'static;
/// See [`run_app_on_demand`].
///
/// [`run_app_on_demand`]: Self::run_app_on_demand
#[deprecated = "use EventLoopExtRunOnDemand::run_app_on_demand"]
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// [`Event::UserEvent`]: crate::event::Event::UserEvent
type UserEvent: 'static;
/// Run the application with the event loop on the calling thread.
///
@@ -42,9 +35,7 @@ pub trait EventLoopExtRunOnDemand {
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// backend it is possible to use `EventLoopExtWebSys::spawn()`
#[cfg_attr(not(web_platform), doc = "[^1]")]
/// more than once instead).
/// backend it is possible to use `EventLoopExtWebSys::spawn()`[^1] more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop.
///
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
@@ -63,30 +54,25 @@ pub trait EventLoopExtRunOnDemand {
/// are delivered via callbacks based on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
#[cfg_attr(not(web_platform), doc = "[^1]: `spawn()` is only available on `wasm` platforms.")]
///
#[rustfmt::skip]
///
/// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
#[allow(deprecated)]
self.run_on_demand(|event, event_loop| {
event_loop::dispatch_event_for_app(app, event_loop, event)
})
}
) -> Result<(), EventLoopError>;
}
impl<T> EventLoopExtRunOnDemand for EventLoop<T> {
type UserEvent = T;
fn run_on_demand<F>(&mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
self.event_loop.window_target().clear_exit();
self.event_loop.run_on_demand(event_handler)
self.event_loop.run_app_on_demand(app)
}
}

View File

@@ -64,7 +64,7 @@ impl EventLoopExtStartupNotify for ActiveEventLoop {
crate::platform_impl::ActiveEventLoop::X(_) => env::var(X11_VAR),
}
.ok()
.map(ActivationToken::from_raw)
.map(ActivationToken::_new)
}
}
@@ -94,6 +94,6 @@ pub fn reset_activation_token_env() {
///
/// This could be used before running daemon processes.
pub fn set_activation_token_env(token: ActivationToken) {
env::set_var(X11_VAR, &token.token);
env::set_var(WAYLAND_VAR, token.token);
env::set_var(X11_VAR, &token._token);
env::set_var(WAYLAND_VAR, token._token);
}

View File

@@ -13,10 +13,6 @@
//! * `wayland-csd-adwaita` (default).
//! * `wayland-csd-adwaita-crossfont`.
//! * `wayland-csd-adwaita-notitle`.
use std::ffi::c_void;
use std::ptr::NonNull;
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::window::{Window, WindowAttributes};
@@ -76,25 +72,9 @@ impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
}
/// Additional methods on [`Window`] that are specific to Wayland.
///
/// [`Window`]: crate::window::Window
pub trait WindowExtWayland {
/// Returns `xdg_toplevel` of the window or [`None`] if the window is X11 window.
fn xdg_toplevel(&self) -> Option<NonNull<c_void>>;
}
pub trait WindowExtWayland {}
impl WindowExtWayland for Window {
#[inline]
fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
#[allow(clippy::single_match)]
match &self.window {
#[cfg(x11_platform)]
crate::platform_impl::Window::X(_) => None,
#[cfg(wayland_platform)]
crate::platform_impl::Window::Wayland(window) => window.xdg_toplevel(),
}
}
}
impl WindowExtWayland for Window {}
/// Additional methods on [`WindowAttributes`] that are specific to Wayland.
pub trait WindowAttributesExtWayland {

View File

@@ -55,8 +55,7 @@ use web_sys::HtmlCanvasElement;
use crate::application::ApplicationHandler;
use crate::cursor::CustomCursorSource;
use crate::event::Event;
use crate::event_loop::{self, ActiveEventLoop, EventLoop};
use crate::event_loop::{ActiveEventLoop, EventLoop};
#[cfg(web_platform)]
use crate::platform_impl::CustomCursorFuture as PlatformCustomCursorFuture;
use crate::platform_impl::PlatformCustomCursorSource;
@@ -156,7 +155,9 @@ impl WindowAttributesExtWebSys for WindowAttributes {
/// Additional methods on `EventLoop` that are specific to the web.
pub trait EventLoopExtWebSys {
/// A type provided by the user that can be passed through `Event::UserEvent`.
/// A type provided by the user that can be passed through [`Event::UserEvent`].
///
/// [`Event::UserEvent`]: crate::event::Event::UserEvent
type UserEvent: 'static;
/// Initializes the winit event loop.
@@ -182,74 +183,13 @@ pub trait EventLoopExtWebSys {
)]
/// [^1]: `run_app()` is _not_ available on WASM when the target supports `exception-handling`.
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A);
/// See [`spawn_app`].
///
/// [`spawn_app`]: Self::spawn_app
#[deprecated = "use EventLoopExtWebSys::spawn_app"]
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
/// Sets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].
///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn set_poll_strategy(&self, strategy: PollStrategy);
/// Gets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].
///
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn poll_strategy(&self) -> PollStrategy;
/// Sets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy);
/// Gets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn wait_until_strategy(&self) -> WaitUntilStrategy;
}
impl<T> EventLoopExtWebSys for EventLoop<T> {
type UserEvent = T;
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, mut app: A) {
self.event_loop.spawn(move |event, event_loop| {
event_loop::dispatch_event_for_app(&mut app, event_loop, event)
});
}
fn spawn<F>(self, event_handler: F)
where
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop),
{
self.event_loop.spawn(event_handler)
}
fn set_poll_strategy(&self, strategy: PollStrategy) {
self.event_loop.set_poll_strategy(strategy);
}
fn poll_strategy(&self) -> PollStrategy {
self.event_loop.poll_strategy()
}
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.event_loop.set_wait_until_strategy(strategy);
}
fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.event_loop.wait_until_strategy()
fn spawn_app<A: ApplicationHandler<Self::UserEvent> + 'static>(self, app: A) {
self.event_loop.spawn_app(app);
}
}
@@ -268,20 +208,6 @@ pub trait ActiveEventLoopExtWebSys {
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
fn poll_strategy(&self) -> PollStrategy;
/// Sets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy);
/// Gets the strategy for [`ControlFlow::WaitUntil`].
///
/// See [`WaitUntilStrategy`].
///
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
fn wait_until_strategy(&self) -> WaitUntilStrategy;
/// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the
/// cursor has completely finished loading.
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture;
@@ -302,16 +228,6 @@ impl ActiveEventLoopExtWebSys for ActiveEventLoop {
fn poll_strategy(&self) -> PollStrategy {
self.p.poll_strategy()
}
#[inline]
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.p.set_wait_until_strategy(strategy);
}
#[inline]
fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.p.wait_until_strategy()
}
}
/// Strategy used for [`ControlFlow::Poll`][crate::event_loop::ControlFlow::Poll].
@@ -340,29 +256,6 @@ pub enum PollStrategy {
Scheduler,
}
/// Strategy used for [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil].
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum WaitUntilStrategy {
/// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available
/// this will fallback to [`setTimeout()`].
///
/// This strategy is commonly not affected by browser throttling unless the window is not
/// focused.
///
/// This is the default strategy.
///
/// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API
/// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
#[default]
Scheduler,
/// Equal to [`Scheduler`][Self::Scheduler] but wakes up the event loop from a [worker].
///
/// This strategy is commonly not affected by browser throttling regardless of window focus.
///
/// [worker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
Worker,
}
pub trait CustomCursorExtWebSys {
/// Returns if this cursor is an animation.
fn is_animation(&self) -> bool;
@@ -422,7 +315,7 @@ impl fmt::Display for BadAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "No cursors supplied"),
Self::Animation => write!(f, "A supplied cursor is an animation"),
Self::Animation => write!(f, "A supplied cursor is an animtion"),
}
}
}
@@ -461,5 +354,3 @@ impl Display for CustomCursorError {
}
}
}
impl Error for CustomCursorError {}

View File

@@ -660,17 +660,6 @@ impl DeviceIdExtWindows for DeviceId {
}
/// Additional methods on `Icon` that are specific to Windows.
///
/// Windows icons can be created from files, or from the [`embedded resources`](https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files).
///
/// The `ICON` resource definition statement use the following syntax:
/// ```rc
/// nameID ICON filename
/// ```
/// `nameID` is a unique name or a 16-bit unsigned integer value identifying the resource,
/// `filename` is the name of the file that contains the resource.
///
/// More information about the `ICON` resource can be found at [`Microsoft Learn`](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) portal.
pub trait IconExtWindows: Sized {
/// Create an icon from a file path.
///
@@ -682,12 +671,7 @@ pub trait IconExtWindows: Sized {
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
-> Result<Self, BadIcon>;
/// Create an icon from a resource embedded in this executable or library by its ordinal id.
///
/// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an
/// invalid ordinal id, but it can be used with [`from_resource_name`] as `"0"`.
///
/// [`from_resource_name`]: IconExtWindows::from_resource_name
/// Create an icon from a resource embedded in this executable or library.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
@@ -695,55 +679,6 @@ pub trait IconExtWindows: Sized {
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
/// Create an icon from a resource embedded in this executable or library by its name.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
/// icon size from the file.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
///
/// # Notes
///
/// Consider the following resource definition statements:
/// ```rc
/// app ICON "app.ico"
/// 1 ICON "a.ico"
/// 0027 ICON "custom.ico"
/// 0 ICON "alt.ico"
/// ```
///
/// Due to some internal implementation details of the resource embedding/loading process on
/// Windows platform, strings that can be interpreted as 16-bit unsigned integers (`"1"`,
/// `"002"`, etc.) cannot be used as valid resource names, and instead should be passed into
/// [`from_resource`]:
///
/// [`from_resource`]: IconExtWindows::from_resource
///
/// ```rust,no_run
/// use winit::platform::windows::IconExtWindows;
/// use winit::window::Icon;
///
/// assert!(Icon::from_resource_name("app", None).is_ok());
/// assert!(Icon::from_resource(1, None).is_ok());
/// assert!(Icon::from_resource(27, None).is_ok());
/// assert!(Icon::from_resource_name("27", None).is_err());
/// assert!(Icon::from_resource_name("0027", None).is_err());
/// ```
///
/// While `0` cannot be used as an ordinal id (see [`from_resource`]), it can be used as a
/// name:
///
/// [`from_resource`]: IconExtWindows::from_resource
///
/// ```rust,no_run
/// # use winit::platform::windows::IconExtWindows;
/// # use winit::window::Icon;
/// assert!(Icon::from_resource_name("0", None).is_ok());
/// assert!(Icon::from_resource(0, None).is_err());
/// ```
fn from_resource_name(name: &str, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
}
impl IconExtWindows for Icon {
@@ -759,9 +694,4 @@ impl IconExtWindows for Icon {
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon })
}
fn from_resource_name(name: &str, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = crate::platform_impl::WinIcon::from_resource_name(name, size)?;
Ok(Icon { inner: win_icon })
}
}

View File

@@ -81,7 +81,9 @@ pub type XWindow = u32;
#[inline]
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
unsafe {
crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to X11.

View File

@@ -1,3 +1,5 @@
#![cfg(android_platform)]
use std::cell::Cell;
use std::collections::VecDeque;
use std::hash::Hash;
@@ -12,12 +14,13 @@ use android_activity::{
};
use tracing::{debug, trace, warn};
use crate::application::ApplicationHandler;
use crate::cursor::Cursor;
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error;
use crate::error::EventLoopError;
use crate::event::{self, Force, InnerSizeWriter, StartCause};
use crate::event_loop::{self, ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents};
use crate::event_loop::{self, ControlFlow, DeviceEvents};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Fullscreen;
use crate::window::{
@@ -132,7 +135,7 @@ impl RedrawRequester {
pub struct KeyEventExtra {}
pub struct EventLoop<T: 'static> {
pub(crate) android_app: AndroidApp,
android_app: AndroidApp,
window_target: event_loop::ActiveEventLoop,
redraw_flag: SharedFlag,
user_events_sender: mpsc::Sender<T>,
@@ -195,27 +198,28 @@ impl<T: 'static> EventLoop<T> {
})
}
fn single_iteration<F>(&mut self, main_event: Option<MainEvent<'_>>, callback: &mut F)
where
F: FnMut(event::Event<T>, &RootAEL),
{
fn single_iteration<A: ApplicationHandler<T>>(
&mut self,
main_event: Option<MainEvent<'_>>,
app: &mut A,
) {
trace!("Mainloop iteration");
let cause = self.cause;
let mut pending_redraw = self.pending_redraw;
let mut resized = false;
callback(event::Event::NewEvents(cause), self.window_target());
app.new_events(self.window_target(), cause);
if let Some(event) = main_event {
trace!("Handling main event {:?}", event);
match event {
MainEvent::InitWindow { .. } => {
callback(event::Event::Resumed, self.window_target());
app.resumed(self.window_target());
},
MainEvent::TerminateWindow { .. } => {
callback(event::Event::Suspended, self.window_target());
app.suspended(self.window_target());
},
MainEvent::WindowResized { .. } => resized = true,
MainEvent::RedrawNeeded { .. } => pending_redraw = true,
@@ -224,23 +228,15 @@ impl<T: 'static> EventLoop<T> {
},
MainEvent::GainedFocus => {
HAS_FOCUS.store(true, Ordering::Relaxed);
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(true),
},
self.window_target(),
);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(true);
app.window_event(self.window_target(), window_id, event);
},
MainEvent::LostFocus => {
HAS_FOCUS.store(false, Ordering::Relaxed);
callback(
event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Focused(false),
},
self.window_target(),
);
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Focused(false);
app.window_event(self.window_target(), window_id, event);
},
MainEvent::ConfigChanged { .. } => {
let monitor = MonitorHandle::new(self.android_app.clone());
@@ -250,20 +246,19 @@ impl<T: 'static> EventLoop<T> {
let new_inner_size = Arc::new(Mutex::new(
MonitorHandle::new(self.android_app.clone()).size(),
));
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
scale_factor,
},
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
scale_factor,
};
callback(event, self.window_target());
app.window_event(self.window_target(), window_id, event);
}
},
MainEvent::LowMemory => {
callback(event::Event::MemoryWarning, self.window_target());
app.memory_warning(self.window_target());
},
MainEvent::Start => {
// XXX: how to forward this state to applications?
@@ -311,7 +306,7 @@ impl<T: 'static> EventLoop<T> {
match android_app.input_events_iter() {
Ok(mut input_iter) => loop {
let read_event =
input_iter.next(|event| self.handle_input_event(&android_app, event, callback));
input_iter.next(|event| self.handle_input_event(&android_app, event, app));
if !read_event {
break;
@@ -325,7 +320,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_events_receiver.try_recv() {
callback(crate::event::Event::UserEvent(event), self.window_target());
app.user_event(self.window_target(), event);
}
}
@@ -338,39 +333,32 @@ impl<T: 'static> EventLoop<T> {
} else {
PhysicalSize::new(0, 0)
};
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::Resized(size),
};
callback(event, self.window_target());
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::Resized(size);
app.window_event(self.window_target(), window_id, event);
}
pending_redraw |= self.redraw_flag.get_and_reset();
if pending_redraw {
pending_redraw = false;
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::RedrawRequested,
};
callback(event, self.window_target());
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::RedrawRequested;
app.window_event(self.window_target(), window_id, event);
}
}
// This is always the last event we dispatch before poll again
callback(event::Event::AboutToWait, self.window_target());
app.about_to_wait(self.window_target());
self.pending_redraw = pending_redraw;
}
fn handle_input_event<F>(
fn handle_input_event<A: ApplicationHandler<T>>(
&mut self,
android_app: &AndroidApp,
event: &InputEvent<'_>,
callback: &mut F,
) -> InputStatus
where
F: FnMut(event::Event<T>, &RootAEL),
{
app: &mut A,
) -> InputStatus {
let mut input_status = InputStatus::Handled;
match event {
InputEvent::MotionEvent(motion_event) => {
@@ -408,17 +396,16 @@ impl<T: 'static> EventLoop<T> {
"Input event {device_id:?}, {phase:?}, loc={location:?}, \
pointer={pointer:?}"
);
let event = event::Event::WindowEvent {
window_id,
event: event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: Some(Force::Normalized(pointer.pressure() as f64)),
}),
};
callback(event, self.window_target());
let event = event::WindowEvent::Touch(event::Touch {
device_id,
phase,
location,
id: pointer.pointer_id() as u64,
force: Some(Force::Normalized(pointer.pressure() as f64)),
});
app.window_event(self.window_target(), window_id, event);
}
}
},
@@ -446,23 +433,22 @@ impl<T: 'static> EventLoop<T> {
&mut self.combining_accent,
);
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
let window_id = window::WindowId(WindowId);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId(key.device_id())),
event: event::KeyEvent {
state,
physical_key: keycodes::to_physical_key(keycode),
logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
};
callback(event, self.window_target());
app.window_event(self.window_target(), window_id, event);
},
}
},
@@ -474,19 +460,16 @@ impl<T: 'static> EventLoop<T> {
input_status
}
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
self.run_on_demand(event_handler)
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
loop {
match self.pump_events(None, &mut event_handler) {
match self.pump_app_events(None, app) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -500,10 +483,11 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(event::Event<T>, &RootAEL),
{
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
if !self.loop_running {
self.loop_running = true;
@@ -514,18 +498,18 @@ impl<T: 'static> EventLoop<T> {
self.cause = StartCause::Init;
// run the initial loop iteration
self.single_iteration(None, &mut callback);
self.single_iteration(None, app);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit
if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut callback);
self.poll_events_with_timeout(timeout, app);
}
if self.exiting() {
self.loop_running = false;
callback(event::Event::LoopExiting, self.window_target());
app.exiting(self.window_target());
PumpStatus::Exit(0)
} else {
@@ -533,10 +517,11 @@ impl<T: 'static> EventLoop<T> {
}
}
fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(event::Event<T>, &RootAEL),
{
fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
let start = Instant::now();
self.pending_redraw |= self.redraw_flag.get_and_reset();
@@ -557,8 +542,8 @@ impl<T: 'static> EventLoop<T> {
min_timeout(control_flow_timeout, timeout)
};
let app = self.android_app.clone(); // Don't borrow self as part of poll expression
app.poll_events(timeout, |poll_event| {
let android_app = self.android_app.clone(); // Don't borrow self as part of poll expression
android_app.poll_events(timeout, |poll_event| {
let mut main_event = None;
match poll_event {
@@ -599,7 +584,7 @@ impl<T: 'static> EventLoop<T> {
},
};
self.single_iteration(main_event, &mut callback);
self.single_iteration(main_event, app);
});
}
@@ -646,7 +631,7 @@ impl<T> EventLoopProxy<T> {
}
pub struct ActiveEventLoop {
pub(crate) app: AndroidApp,
app: AndroidApp,
control_flow: Cell<ControlFlow>,
exit: Cell<bool>,
redraw_requester: RedrawRequester,
@@ -677,11 +662,6 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
@@ -908,13 +888,7 @@ impl Window {
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
pub fn set_ime_allowed(&self, allowed: bool) {
if allowed {
self.app.show_soft_input(true);
} else {
self.app.hide_soft_input(true);
}
}
pub fn set_ime_allowed(&self, _allowed: bool) {}
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}

View File

@@ -0,0 +1,103 @@
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
use objc2_ui_kit::{UIApplication, UIWindow};
use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
use crate::event::{Event, WindowEvent};
use crate::window::WindowId as RootWindowId;
declare_class!(
pub struct AppDelegate;
unsafe impl ClassType for AppDelegate {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "WinitApplicationDelegate";
}
impl DeclaredClass for AppDelegate {}
// UIApplicationDelegate protocol
unsafe impl AppDelegate {
#[method(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
true
}
#[method(applicationDidBecomeActive:)]
fn did_become_active(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
}
#[method(applicationWillResignActive:)]
fn will_resign_active(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
}
#[method(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, false);
}
#[method(applicationDidEnterBackground:)]
fn did_enter_background(&self, application: &UIApplication) {
self.send_occluded_event_for_all_windows(application, true);
}
#[method(applicationWillTerminate:)]
fn will_terminate(&self, application: &UIApplication) {
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed,
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
app_state::terminated(mtm);
}
#[method(applicationDidReceiveMemoryWarning:)]
fn did_receive_memory_warning(&self, _application: &UIApplication) {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
}
}
);
impl AppDelegate {
fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) {
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Occluded(occluded),
}));
}
}
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, events);
}
}

View File

@@ -20,7 +20,7 @@ use objc2_foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
NSProcessInfo,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
use objc2_ui_kit::{UICoordinateSpace, UIView};
use super::window::WinitUIWindow;
use crate::dpi::PhysicalSize;
@@ -147,8 +147,6 @@ impl AppState {
// must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
#[allow(unknown_lints)] // New lint below
#[allow(static_mut_refs)] // TODO: Use `MainThreadBound` instead.
let mut guard = unsafe { APP_STATE.borrow_mut() };
if guard.is_none() {
#[inline(never)]
@@ -375,7 +373,6 @@ impl AppState {
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
self.waker.stop()
},
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant =>
@@ -665,28 +662,6 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
}
pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, occluded: bool) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Occluded(occluded),
}));
}
}
handle_nonuser_events(mtm, events);
}
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
if !this.has_launched() || this.has_terminated() {
@@ -721,27 +696,7 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
}
pub(crate) fn terminated(application: &UIApplication) {
let mtm = MainThreadMarker::from(application);
let mut events = Vec::new();
#[allow(deprecated)]
for window in application.windows().iter() {
if window.is_kind_of::<WinitUIWindow>() {
// SAFETY: We just checked that the window is a `winit` window
let window = unsafe {
let ptr: *const UIWindow = window;
let ptr: *const WinitUIWindow = ptr.cast();
&*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed,
}));
}
}
handle_nonuser_events(mtm, events);
pub fn terminated(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let mut handler = this.terminated_transition();
drop(this);
@@ -801,7 +756,7 @@ impl EventLoopWaker {
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
f64::MAX,
std::f64::MAX,
0.000_000_1,
0,
0,
@@ -815,11 +770,11 @@ impl EventLoopWaker {
}
fn stop(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
}
fn start(&mut self) {
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
}
fn start_at(&mut self, instant: Instant) {

View File

@@ -13,26 +13,21 @@ use core_foundation::runloop::{
};
use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain,
UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification,
UIApplicationWillTerminateNotification, UIDevice, UIScreen, UIUserInterfaceIdiom,
};
use objc2_foundation::{MainThreadMarker, NSString};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom};
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::platform::ios::Idiom;
use crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents};
use crate::window::{CustomCursor, CustomCursorSource, Theme};
use crate::platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents};
use crate::window::{CustomCursor, CustomCursorSource};
use super::app_state::{send_occluded_event_for_all_windows, AppState, EventWrapper};
use super::notification_center::create_observer;
use super::app_delegate::AppDelegate;
use super::app_state::AppState;
use super::{app_state, monitor, MonitorHandle};
#[derive(Debug)]
@@ -64,11 +59,6 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
@@ -119,17 +109,28 @@ impl OwnedDisplayHandle {
}
}
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootActiveEventLoop),
fn map_user_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
receiver: mpsc::Receiver<T>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + '_ {
move |event, window_target| match event {
Event::NewEvents(cause) => app.new_events(window_target, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
Event::UserEvent(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
app.user_event(window_target, event);
}
},
Event::Suspended => app.suspended(window_target),
Event::Resumed => app.resumed(window_target),
Event::AboutToWait => app.about_to_wait(window_target),
Event::LoopExiting => app.exiting(window_target),
Event::MemoryWarning => app.memory_warning(window_target),
}
}
@@ -138,18 +139,6 @@ pub struct EventLoop<T: 'static> {
sender: Sender<T>,
receiver: Receiver<T>,
window_target: RootActiveEventLoop,
// Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the
// system instead cleans it up next time it would have posted a notification to it.
//
// Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<NSObject>,
_did_become_active_observer: Retained<NSObject>,
_will_resign_active_observer: Retained<NSObject>,
_will_enter_foreground_observer: Retained<NSObject>,
_did_enter_background_observer: Retained<NSObject>,
_will_terminate_observer: Retained<NSObject>,
_did_receive_memory_warning_observer: Retained<NSObject>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -176,104 +165,15 @@ impl<T: 'static> EventLoop<T> {
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
let center = unsafe { NSNotificationCenter::defaultCenter() };
let _did_finish_launching_observer = create_observer(
&center,
// `application:didFinishLaunchingWithOptions:`
unsafe { UIApplicationDidFinishLaunchingNotification },
move |_| {
app_state::did_finish_launching(mtm);
},
);
let _did_become_active_observer = create_observer(
&center,
// `applicationDidBecomeActive:`
unsafe { UIApplicationDidBecomeActiveNotification },
move |_| {
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed));
},
);
let _will_resign_active_observer = create_observer(
&center,
// `applicationWillResignActive:`
unsafe { UIApplicationWillResignActiveNotification },
move |_| {
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended));
},
);
let _will_enter_foreground_observer = create_observer(
&center,
// `applicationWillEnterForeground:`
unsafe { UIApplicationWillEnterForegroundNotification },
move |notification| {
let app = unsafe { notification.object() }.expect(
"UIApplicationWillEnterForegroundNotification to have application object",
);
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, false);
},
);
let _did_enter_background_observer = create_observer(
&center,
// `applicationDidEnterBackground:`
unsafe { UIApplicationDidEnterBackgroundNotification },
move |notification| {
let app = unsafe { notification.object() }.expect(
"UIApplicationDidEnterBackgroundNotification to have application object",
);
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
// documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
send_occluded_event_for_all_windows(&app, true);
},
);
let _will_terminate_observer = create_observer(
&center,
// `applicationWillTerminate:`
unsafe { UIApplicationWillTerminateNotification },
move |notification| {
let app = unsafe { notification.object() }
.expect("UIApplicationWillTerminateNotification to have application object");
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
// (somewhat) documented to be `UIApplication`.
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
app_state::terminated(&app);
},
);
let _did_receive_memory_warning_observer = create_observer(
&center,
// `applicationDidReceiveMemoryWarning:`
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| {
app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::MemoryWarning),
);
},
);
Ok(EventLoop {
mtm,
sender,
receiver,
window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData },
_did_finish_launching_observer,
_did_become_active_observer,
_will_resign_active_observer,
_will_enter_foreground_observer,
_did_enter_background_observer,
_will_terminate_observer,
_did_receive_memory_warning_observer,
})
}
pub fn run<F>(self, handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> ! {
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
@@ -283,7 +183,7 @@ impl<T: 'static> EventLoop<T> {
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
);
let handler = map_user_event(handler, self.receiver);
let handler = map_user_event(app, self.receiver);
let handler = unsafe {
std::mem::transmute::<
@@ -296,6 +196,9 @@ impl<T: 'static> EventLoop<T> {
app_state::will_launch(self.mtm, handler);
// Ensure application delegate is initialized
let _ = AppDelegate::class();
extern "C" {
// These functions are in crt_externs.h.
fn _NSGetArgc() -> *mut c_int;
@@ -306,10 +209,8 @@ impl<T: 'static> EventLoop<T> {
UIApplicationMain(
*_NSGetArgc(),
NonNull::new(*_NSGetArgv()).unwrap(),
// We intentionally override neither the application nor the delegate, to allow the
// user to do so themselves!
None,
None,
Some(&NSString::from_str(AppDelegate::NAME)),
)
};
unreachable!()
@@ -382,7 +283,8 @@ impl<T> EventLoopProxy<T> {
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
@@ -465,7 +367,7 @@ fn setup_control_flow_observers() {
ptr::null_mut(),
kCFRunLoopAfterWaiting,
1, // repeat = true
CFIndex::MIN,
CFIndex::min_value(),
control_flow_begin_handler,
ptr::null_mut(),
);
@@ -485,7 +387,7 @@ fn setup_control_flow_observers() {
ptr::null_mut(),
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
1, // repeat = true
CFIndex::MAX,
CFIndex::max_value(),
control_flow_end_handler,
ptr::null_mut(),
);

View File

@@ -1,9 +1,10 @@
#![cfg(ios_platform)]
#![allow(clippy::let_unit_value)]
mod app_delegate;
mod app_state;
mod event_loop;
mod monitor;
mod notification_center;
mod view;
mod view_controller;
mod window;
@@ -32,7 +33,7 @@ pub(crate) use crate::platform_impl::Fullscreen;
pub struct DeviceId;
impl DeviceId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId
}
}

View File

@@ -102,20 +102,13 @@ impl Clone for MonitorHandle {
impl hash::Hash for MonitorHandle {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).hash(state);
(self as *const Self).hash(state);
}
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
// SAFETY: Only getting the pointer.
let mtm = unsafe { MainThreadMarker::new_unchecked() };
ptr::eq(
Retained::as_ptr(self.ui_screen.get(mtm)),
Retained::as_ptr(other.ui_screen.get(mtm)),
)
ptr::eq(self, other)
}
}
@@ -129,10 +122,8 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// SAFETY: Only getting the pointer.
// TODO: Make a better ordering
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm)))
(self as *const Self).cmp(&(other as *const Self))
}
}
@@ -251,27 +242,3 @@ pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
#[allow(deprecated)]
UIScreen::screens(mtm).into_iter().map(MonitorHandle::new).collect()
}
#[cfg(test)]
mod tests {
use objc2_foundation::NSSet;
use super::*;
// Test that UIScreen pointer comparisons are correct.
#[test]
#[allow(deprecated)]
fn screen_comparisons() {
// Test code, doesn't matter that it's not thread safe
let mtm = unsafe { MainThreadMarker::new_unchecked() };
assert!(ptr::eq(&*UIScreen::mainScreen(mtm), &*UIScreen::mainScreen(mtm)));
let main = UIScreen::mainScreen(mtm);
assert!(UIScreen::screens(mtm).iter().any(|screen| ptr::eq(screen, &*main)));
assert!(unsafe {
NSSet::setWithArray(&UIScreen::screens(mtm)).containsObject(&UIScreen::mainScreen(mtm))
});
}
}

View File

@@ -1,27 +0,0 @@
use std::ptr::NonNull;
use block2::RcBlock;
use objc2::rc::Retained;
use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject};
/// Observe the given notification.
///
/// This is used in Winit as an alternative to declaring an application delegate, as we want to
/// give the user full control over those.
pub fn create_observer(
center: &NSNotificationCenter,
name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static,
) -> Retained<NSObject> {
let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
handler(unsafe { notification.as_ref() });
});
unsafe {
center.addObserverForName_object_queue_usingBlock(
Some(name),
None, // No sender filter
None, // No queue, run on posting thread (i.e. main thread)
&block,
)
}
}

View File

@@ -4,21 +4,19 @@ use std::cell::{Cell, RefCell};
use objc2::rc::Retained;
use objc2::runtime::{NSObjectProtocol, ProtocolObject};
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet, NSString};
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet};
use objc2_ui_kit::{
UICoordinateSpace, UIEvent, UIForceTouchCapability, UIGestureRecognizer,
UIGestureRecognizerDelegate, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer,
UIGestureRecognizerDelegate, UIGestureRecognizerState, UIPanGestureRecognizer,
UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer,
UITextInputTraits, UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
};
use super::app_state::{self, EventWrapper};
use super::window::WinitUIWindow;
use crate::dpi::PhysicalPosition;
use crate::event::{ElementState, Event, Force, KeyEvent, Touch, TouchPhase, WindowEvent};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKeyCode, PhysicalKey};
use crate::event::{Event, Force, Touch, TouchPhase, WindowEvent};
use crate::platform_impl::platform::DEVICE_ID;
use crate::platform_impl::KeyEventExtra;
use crate::window::{WindowAttributes, WindowId as RootWindowId};
pub struct WinitViewState {
@@ -190,7 +188,7 @@ declare_class!(
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.scale())
}
state => panic!("unexpected recognizer state: {state:?}"),
state => panic!("unexpected recognizer state: {:?}", state),
};
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
@@ -249,7 +247,7 @@ declare_class!(
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -recognizer.rotation())
}
state => panic!("unexpected recognizer state: {state:?}"),
state => panic!("unexpected recognizer state: {:?}", state),
};
// Make delta negative to match macos, convert to degrees
@@ -300,7 +298,7 @@ declare_class!(
// Pass -delta so that action is reversed
(TouchPhase::Cancelled, -last_pan.x, -last_pan.y)
}
state => panic!("unexpected recognizer state: {state:?}"),
state => panic!("unexpected recognizer state: {:?}", state),
};
@@ -316,11 +314,6 @@ declare_class!(
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, gesture_event);
}
#[method(canBecomeFirstResponder)]
fn can_become_first_responder(&self) -> bool {
true
}
}
unsafe impl NSObjectProtocol for WinitView {}
@@ -331,26 +324,6 @@ declare_class!(
true
}
}
unsafe impl UITextInputTraits for WinitView {
}
unsafe impl UIKeyInput for WinitView {
#[method(hasText)]
fn has_text(&self) -> bool {
true
}
#[method(insertText:)]
fn insert_text(&self, text: &NSString) {
self.handle_insert_text(text)
}
#[method(deleteBackward)]
fn delete_backward(&self) {
self.handle_delete_backward()
}
}
);
impl WinitView {
@@ -539,69 +512,4 @@ impl WinitView {
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(mtm, touch_events);
}
fn handle_insert_text(&self, text: &NSString) {
let window = self.window().unwrap();
let window_id = RootWindowId(window.id());
let mtm = MainThreadMarker::new().unwrap();
// send individual events for each character
app_state::handle_nonuser_events(
mtm,
text.to_string().chars().flat_map(|c| {
let text = smol_str::SmolStr::from_iter([c]);
// Emit both press and release events
[ElementState::Pressed, ElementState::Released].map(|state| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
event: KeyEvent {
text: if state == ElementState::Pressed {
Some(text.clone())
} else {
None
},
state,
location: KeyLocation::Standard,
repeat: false,
logical_key: Key::Character(text.clone()),
physical_key: PhysicalKey::Unidentified(
NativeKeyCode::Unidentified,
),
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
device_id: DEVICE_ID,
},
})
})
}),
);
}
fn handle_delete_backward(&self) {
let window = self.window().unwrap();
let window_id = RootWindowId(window.id());
let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(
mtm,
[ElementState::Pressed, ElementState::Released].map(|state| {
EventWrapper::StaticEvent(Event::WindowEvent {
window_id,
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: KeyEvent {
state,
logical_key: Key::Named(NamedKey::Backspace),
physical_key: PhysicalKey::Code(KeyCode::Backspace),
platform_specific: KeyEventExtra {},
repeat: false,
location: KeyLocation::Standard,
text: None,
},
is_synthetic: false,
},
})
}),
);
}
}

View File

@@ -367,24 +367,12 @@ impl Inner {
warn!("`Window::set_ime_cursor_area` is ignored on iOS")
}
/// Show / hide the keyboard. To show the keyboard, we call `becomeFirstResponder`,
/// requesting focus for the [WinitView]. Since [WinitView] implements
/// [objc2_ui_kit::UIKeyInput], the keyboard will be shown.
/// <https://developer.apple.com/documentation/uikit/uiresponder/1621113-becomefirstresponder>
pub fn set_ime_allowed(&self, allowed: bool) {
if allowed {
unsafe {
self.view.becomeFirstResponder();
}
} else {
unsafe {
self.view.resignFirstResponder();
}
}
pub fn set_ime_allowed(&self, _allowed: bool) {
warn!("`Window::set_ime_allowed` is ignored on iOS")
}
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {
warn!("`Window::set_ime_purpose` is ignored on iOS")
warn!("`Window::set_ime_allowed` is ignored on iOS")
}
pub fn focus_window(&self) {
@@ -713,7 +701,7 @@ pub struct WindowId {
}
impl WindowId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
WindowId { window: std::ptr::null_mut() }
}
}

View File

@@ -184,7 +184,7 @@ pub struct KeyContext<'a> {
scratch_buffer: &'a mut Vec<u8>,
}
impl KeyContext<'_> {
impl<'a> KeyContext<'a> {
pub fn process_key_event(
&mut self,
keycode: u32,
@@ -320,7 +320,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
// The current behaviour makes it so composing a character overrides attempts to input a
// control character with the `Ctrl` key. We can potentially add a configuration option
// if someone specifically wants the opposite behaviour.
// if someone specifically wants the oppsite behaviour.
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
match self.composed_text() {
Ok(text) => text,

View File

@@ -11,6 +11,8 @@ use std::{env, fmt};
#[cfg(x11_platform)]
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Mutex};
use crate::application::ApplicationHandler;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::utils::Lazy;
use smol_str::SmolStr;
@@ -19,12 +21,9 @@ use smol_str::SmolStr;
use self::x11::{X11Error, XConnection, XError, XNotSupported};
use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{EventLoopError, ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event_loop::{
ActiveEventLoop as RootELW, AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::event_loop::{AsyncRequestSerial, ControlFlow, DeviceEvents, EventLoopClosed};
use crate::icon::Icon;
use crate::keyboard::Key;
use crate::platform::pump_events::PumpStatus;
#[cfg(x11_platform)]
use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
use crate::window::{
@@ -117,8 +116,6 @@ pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported
pub enum OsError {
Misc(&'static str),
#[cfg(x11_platform)]
XNotSupported(XNotSupported),
#[cfg(x11_platform)]
XError(Arc<X11Error>),
#[cfg(wayland_platform)]
WaylandError(Arc<wayland::WaylandError>),
@@ -129,8 +126,6 @@ impl fmt::Display for OsError {
match *self {
OsError::Misc(e) => _f.pad(e),
#[cfg(x11_platform)]
OsError::XNotSupported(ref e) => fmt::Display::fmt(e, _f),
#[cfg(x11_platform)]
OsError::XError(ref e) => fmt::Display::fmt(e, _f),
#[cfg(wayland_platform)]
OsError::WaylandError(ref e) => fmt::Display::fmt(e, _f),
@@ -161,7 +156,7 @@ impl From<u64> for WindowId {
}
impl WindowId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
Self(0)
}
}
@@ -175,11 +170,11 @@ pub enum DeviceId {
}
impl DeviceId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
#[cfg(wayland_platform)]
return DeviceId::Wayland(wayland::DeviceId::dummy());
return DeviceId::Wayland(unsafe { wayland::DeviceId::dummy() });
#[cfg(all(not(wayland_platform), x11_platform))]
return DeviceId::X(x11::DeviceId::dummy());
return DeviceId::X(unsafe { x11::DeviceId::dummy() });
}
}
@@ -647,18 +642,18 @@ pub(crate) enum PlatformCustomCursor {
/// Hooks for X11 errors.
#[cfg(x11_platform)]
pub(crate) static XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
pub(crate) static mut XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
#[cfg(x11_platform)]
unsafe extern "C" fn x_error_callback(
display: *mut x11::ffi::Display,
event: *mut x11::ffi::XErrorEvent,
) -> c_int {
let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner());
let xconn_lock = X11_BACKEND.lock().unwrap();
if let Ok(ref xconn) = *xconn_lock {
// Call all the hooks.
let mut error_handled = false;
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() {
error_handled |= hook(display as *mut _, event as *mut _);
}
@@ -696,7 +691,6 @@ unsafe extern "C" fn x_error_callback(
0
}
#[allow(clippy::large_enum_variant)]
pub enum EventLoop<T: 'static> {
#[cfg(wayland_platform)]
Wayland(Box<wayland::EventLoop<T>>),
@@ -769,9 +763,9 @@ impl<T: 'static> EventLoop<T> {
// Create the display based on the backend.
match backend {
#[cfg(wayland_platform)]
Backend::Wayland => EventLoop::new_wayland_any_thread(),
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
#[cfg(x11_platform)]
Backend::X => EventLoop::new_x11_any_thread(),
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
}
}
@@ -782,11 +776,9 @@ impl<T: 'static> EventLoop<T> {
#[cfg(x11_platform)]
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> {
let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
Ok(xconn) => xconn.clone(),
Err(err) => {
return Err(EventLoopError::Os(os_error!(OsError::XNotSupported(err.clone()))))
},
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
};
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
@@ -806,25 +798,23 @@ impl<T: 'static> EventLoop<T> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
}
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
self.run_on_demand(callback)
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
}
pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_on_demand(callback))
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app_on_demand(app))
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
where
F: FnMut(crate::event::Event<T>, &RootELW),
{
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_events(timeout, callback))
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_app_events(timeout, app))
}
pub fn window_target(&self) -> &crate::event_loop::ActiveEventLoop {
@@ -850,7 +840,6 @@ impl<T: 'static> EventLoopProxy<T> {
}
}
#[allow(clippy::large_enum_variant)]
pub enum ActiveEventLoop {
#[cfg(wayland_platform)]
Wayland(wayland::ActiveEventLoop),
@@ -905,11 +894,6 @@ impl ActiveEventLoop {
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(

View File

@@ -4,22 +4,17 @@ use std::cell::{Cell, RefCell};
use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::os::fd::OwnedFd;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Condvar, Mutex};
use std::thread::JoinHandle;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use calloop::ping::Ping;
use rustix::event::{PollFd, PollFlags};
use rustix::pipe::{self, PipeFlags};
use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::{globals, Connection, QueueHandle};
use tracing::warn;
use crate::application::ApplicationHandler;
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, OsError as RootOsError};
@@ -74,8 +69,6 @@ pub struct EventLoop<T: 'static> {
// XXX drop after everything else, just to be safe.
/// Calloop's event loop.
event_loop: calloop::EventLoop<'static, WinitState>,
pump_event_notifier: Option<PumpEventNotifier>,
}
impl<T: 'static> EventLoop<T> {
@@ -176,18 +169,21 @@ impl<T: 'static> EventLoop<T> {
p: PlatformActiveEventLoop::Wayland(window_target),
_marker: PhantomData,
},
pump_event_notifier: None,
};
Ok(event_loop)
}
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
let exit = loop {
match self.pump_events(None, &mut event_handler) {
match self.pump_app_events(None, app) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -209,58 +205,39 @@ impl<T: 'static> EventLoop<T> {
exit
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
if !self.loop_running {
self.loop_running = true;
// Run the initial loop iteration.
self.single_iteration(&mut callback, StartCause::Init);
self.single_iteration(app, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut callback);
self.poll_events_with_timeout(timeout, app);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
callback(Event::LoopExiting, self.window_target());
app.exiting(&self.window_target);
PumpStatus::Exit(code)
} else {
// NOTE: spawn a wake-up thread, thus if we have code reading the wayland connection
// in parallel to winit, we ensure that the loop itself is marked as having events.
if timeout.is_some() && self.pump_event_notifier.is_none() {
let awakener = match &self.window_target.p {
PlatformActiveEventLoop::Wayland(window_target) => {
window_target.event_loop_awakener.clone()
},
#[cfg(x11_platform)]
PlatformActiveEventLoop::X(_) => unreachable!(),
};
self.pump_event_notifier =
Some(PumpEventNotifier::spawn(self.connection.clone(), awakener));
}
if let Some(pump_event_notifier) = self.pump_event_notifier.as_ref() {
// Notify that we don't have to wait, since we're out of winit.
*pump_event_notifier.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor;
pump_event_notifier.control.1.notify_one();
}
PumpStatus::Continue
}
}
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
pub fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
let cause = loop {
let start = Instant::now();
@@ -315,23 +292,17 @@ impl<T: 'static> EventLoop<T> {
// Reduce spurious wake-ups.
let dispatched_events = self.with_state(|state| state.dispatched_events);
if matches!(cause, StartCause::WaitCancelled { .. })
&& !dispatched_events
&& timeout.is_none()
{
if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events {
continue;
}
break cause;
};
self.single_iteration(&mut callback, cause);
self.single_iteration(app, cause);
}
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
fn single_iteration<A: ApplicationHandler<T>>(&mut self, app: &mut A, cause: StartCause) {
// NOTE currently just indented to simplify the diff
// We retain these grow-only scratch buffers as part of the EventLoop
@@ -342,18 +313,18 @@ impl<T: 'static> EventLoop<T> {
let mut buffer_sink = std::mem::take(&mut self.buffer_sink);
let mut window_ids = std::mem::take(&mut self.window_ids);
callback(Event::NewEvents(cause), &self.window_target);
app.new_events(&self.window_target, cause);
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
callback(Event::Resumed, &self.window_target);
app.resumed(&self.window_target);
}
// Handle pending user events. We don't need back buffer, since we can't dispatch
// user events indirectly via callback to the user.
for user_event in self.pending_user_events.borrow_mut().drain(..) {
callback(Event::UserEvent(user_event), &self.window_target);
app.user_event(&self.window_target, user_event);
}
// Drain the pending compositor updates.
@@ -374,18 +345,13 @@ impl<T: 'static> EventLoop<T> {
let old_physical_size = physical_size;
let new_inner_size = Arc::new(Mutex::new(physical_size));
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
},
&self.window_target,
);
let root_window_id = crate::window::WindowId(window_id);
let event = WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
};
app.window_event(&self.window_target, root_window_id, event);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
@@ -428,23 +394,14 @@ impl<T: 'static> EventLoop<T> {
size
});
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::Resized(physical_size),
},
&self.window_target,
);
let window_id = crate::window::WindowId(window_id);
let event = WindowEvent::Resized(physical_size);
app.window_event(&self.window_target, window_id, event);
}
if compositor_update.close_window {
callback(
Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::CloseRequested,
},
&self.window_target,
);
let window_id = crate::window::WindowId(window_id);
app.window_event(&self.window_target, window_id, WindowEvent::CloseRequested);
}
}
@@ -453,8 +410,15 @@ impl<T: 'static> EventLoop<T> {
buffer_sink.append(&mut state.window_events_sink.lock().unwrap());
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
}
// Handle non-synthetic events.
@@ -462,8 +426,15 @@ impl<T: 'static> EventLoop<T> {
buffer_sink.append(&mut state.events_sink);
});
for event in buffer_sink.drain() {
let event = event.map_nonuser_event().unwrap();
callback(event, &self.window_target);
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(&self.window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(&self.window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
}
// Collect the window ids
@@ -499,10 +470,8 @@ impl<T: 'static> EventLoop<T> {
});
if let Some(event) = event {
callback(
Event::WindowEvent { window_id: crate::window::WindowId(*window_id), event },
&self.window_target,
);
let window_id = crate::window::WindowId(*window_id);
app.window_event(&self.window_target, window_id, event);
}
}
@@ -512,7 +481,7 @@ impl<T: 'static> EventLoop<T> {
});
// This is always the last event we dispatch before poll again
callback(Event::AboutToWait, &self.window_target);
app.about_to_wait(&self.window_target);
// Update the window frames and schedule redraws.
let mut wake_up = false;
@@ -633,7 +602,7 @@ impl<T> AsRawFd for EventLoop<T> {
pub struct ActiveEventLoop {
/// The event loop wakeup source.
pub event_loop_awakener: Ping,
pub event_loop_awakener: calloop::ping::Ping,
/// The main queue used by the event loop.
pub queue_handle: QueueHandle<WinitState>,
@@ -717,94 +686,3 @@ impl ActiveEventLoop {
.into())
}
}
#[derive(Debug)]
struct PumpEventNotifier {
/// Whether we're in winit or not.
control: Arc<(Mutex<PumpEventNotifierAction>, Condvar)>,
/// Waker handle for the working thread.
worker_waker: Option<OwnedFd>,
/// Thread handle.
handle: Option<JoinHandle<()>>,
}
impl Drop for PumpEventNotifier {
fn drop(&mut self) {
// Wake-up the thread.
if let Some(worker_waker) = self.worker_waker.as_ref() {
let _ = rustix::io::write(worker_waker.as_fd(), &[0u8]);
}
*self.control.0.lock().unwrap() = PumpEventNotifierAction::Shutdown;
self.control.1.notify_one();
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
impl PumpEventNotifier {
fn spawn(connection: Connection, awakener: Ping) -> Self {
// Start from the waiting state.
let control = Arc::new((Mutex::new(PumpEventNotifierAction::Pause), Condvar::new()));
let control_thread = Arc::clone(&control);
let (read, write) = match pipe::pipe_with(PipeFlags::CLOEXEC | PipeFlags::NONBLOCK) {
Ok((read, write)) => (read, write),
Err(_) => return Self { control, handle: None, worker_waker: None },
};
let handle =
std::thread::Builder::new().name(String::from("pump_events mon")).spawn(move || {
let (lock, cvar) = &*control_thread;
'outer: loop {
let mut wait = lock.lock().unwrap();
while *wait == PumpEventNotifierAction::Pause {
wait = cvar.wait(wait).unwrap();
}
// Exit the loop when we're asked to. Given that we poll
// only once we can take the `prepare_read`, but in some cases
// it could be not possible, we may block on `join`.
if *wait == PumpEventNotifierAction::Shutdown {
break 'outer;
}
// Wake-up the main loop and put this one back to sleep.
*wait = PumpEventNotifierAction::Pause;
drop(wait);
while let Some(read_guard) = connection.prepare_read() {
let _ = connection.flush();
let poll_fd = PollFd::from_borrowed_fd(connection.as_fd(), PollFlags::IN);
let pipe_poll_fd = PollFd::from_borrowed_fd(read.as_fd(), PollFlags::IN);
// Read from the `fd` before going back to poll.
if Ok(1) == rustix::io::read(read.as_fd(), &mut [0u8; 1]) {
break 'outer;
}
let _ = rustix::event::poll(&mut [poll_fd, pipe_poll_fd], -1);
// Non-blocking read the connection.
let _ = read_guard.read_without_dispatch();
}
awakener.ping();
}
});
if let Some(err) = handle.as_ref().err() {
warn!("failed to spawn pump_events wake-up thread: {err}");
}
PumpEventNotifier { control, handle: handle.ok(), worker_waker: Some(write) }
}
}
#[derive(Debug, PartialEq, Eq)]
enum PumpEventNotifierAction {
/// Monitor the wayland queue.
Monitor,
/// Pause monitoring.
Pause,
/// Shutdown the thread.
Shutdown,
}

View File

@@ -1,3 +1,5 @@
#![cfg(wayland_platform)]
//! Winit's Wayland backend.
use std::fmt::Display;
@@ -66,7 +68,7 @@ impl From<WaylandError> for OsError {
pub struct DeviceId;
impl DeviceId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId
}
}

View File

@@ -18,6 +18,7 @@ use crate::keyboard::ModifiersState;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::wayland::event_loop::sink::EventSink;
use crate::platform_impl::wayland::seat::WinitSeatState;
use crate::platform_impl::wayland::state::WinitState;
use crate::platform_impl::wayland::{self, DeviceId, WindowId};
@@ -32,17 +33,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
) {
let seat_state = match state.seats.get_mut(&data.seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received keyboard event {event:?} without seat");
return;
},
};
let keyboard_state = match seat_state.keyboard_state.as_mut() {
Some(keyboard_state) => keyboard_state,
None => {
warn!("Received keyboard event {event:?} without keyboard");
return;
},
None => return,
};
match event {
@@ -52,7 +43,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
warn!("non-xkb compatible keymap")
},
WlKeymapFormat::XkbV1 => {
let context = &mut keyboard_state.xkb_context;
let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
context.set_keymap_from_fd(fd, size as usize);
},
_ => unreachable!(),
@@ -76,6 +67,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
};
// Drop the repeat, if there were any.
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.current_repeat = None;
if let Some(token) = keyboard_state.repeat_token.take() {
keyboard_state.loop_handle.remove(token);
@@ -101,6 +93,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
// NOTE: we should drop the repeat regardless whethere it was for the present
// window of for the window which just went gone.
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.current_repeat = None;
if let Some(token) = keyboard_state.repeat_token.take() {
keyboard_state.loop_handle.remove(token);
@@ -135,7 +128,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let key = key + 8;
key_input(
keyboard_state,
seat_state,
&mut state.events_sink,
data,
key,
@@ -143,6 +136,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
false,
);
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
let delay = match keyboard_state.repeat_info {
RepeatInfo::Repeat { delay, .. } => delay,
RepeatInfo::Disable => return,
@@ -169,25 +163,18 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
state.dispatched_events = true;
let data = wl_keyboard.data::<KeyboardData>().unwrap();
let seat_state = match state.seats.get_mut(&data.seat.id()) {
Some(seat_state) => seat_state,
None => return TimeoutAction::Drop,
};
let seat_state = state.seats.get_mut(&data.seat.id()).unwrap();
let keyboard_state = match seat_state.keyboard_state.as_mut() {
Some(keyboard_state) => keyboard_state,
None => return TimeoutAction::Drop,
};
// NOTE: The removed on event source is batched, but key change to `None`
// is instant.
let repeat_keycode = match keyboard_state.current_repeat {
Some(repeat_keycode) => repeat_keycode,
None => return TimeoutAction::Drop,
};
// NOTE: The removed on event source is batched, but key change to
// `None` is instant.
let repeat_keycode =
match seat_state.keyboard_state.as_ref().unwrap().current_repeat {
Some(repeat_keycode) => repeat_keycode,
None => return TimeoutAction::Drop,
};
key_input(
keyboard_state,
seat_state,
&mut state.events_sink,
data,
repeat_keycode,
@@ -196,7 +183,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
);
// NOTE: the gap could change dynamically while repeat is going.
match keyboard_state.repeat_info {
match seat_state.keyboard_state.as_ref().unwrap().repeat_info {
RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap),
RepeatInfo::Disable => TimeoutAction::Drop,
}
@@ -207,7 +194,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
let key = key + 8;
key_input(
keyboard_state,
seat_state,
&mut state.events_sink,
data,
key,
@@ -215,6 +202,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
false,
);
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
if keyboard_state.repeat_info != RepeatInfo::Disable
&& keyboard_state.xkb_context.keymap_mut().unwrap().key_repeats(key)
&& Some(key) == keyboard_state.current_repeat
@@ -228,7 +216,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
WlKeyboardEvent::Modifiers {
mods_depressed, mods_latched, mods_locked, group, ..
} => {
let xkb_context = &mut keyboard_state.xkb_context;
let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
let xkb_state = match xkb_context.state_mut() {
Some(state) => state,
None => return,
@@ -252,6 +240,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
);
},
WlKeyboardEvent::RepeatInfo { rate, delay } => {
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
keyboard_state.repeat_info = if rate == 0 {
// Stop the repeat once we get a disable event.
keyboard_state.current_repeat = None;
@@ -359,7 +348,7 @@ impl KeyboardData {
}
fn key_input(
keyboard_state: &mut KeyboardState,
seat_state: &mut WinitSeatState,
event_sink: &mut EventSink,
data: &KeyboardData,
keycode: u32,
@@ -371,6 +360,8 @@ fn key_input(
None => return,
};
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
let event = key_context.process_key_event(keycode, state, repeat);

View File

@@ -3,7 +3,6 @@
use std::sync::Arc;
use ahash::AHashMap;
use tracing::warn;
use sctk::reexports::client::backend::ObjectId;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
@@ -77,13 +76,7 @@ impl SeatHandler for WinitState {
seat: WlSeat,
capability: SeatCapability,
) {
let seat_state = match self.seats.get_mut(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_seat::new_capability for unknown seat");
return;
},
};
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
match capability {
SeatCapability::Touch if seat_state.touch.is_none() => {
@@ -96,12 +89,8 @@ impl SeatHandler for WinitState {
},
SeatCapability::Pointer if seat_state.pointer.is_none() => {
let surface = self.compositor_state.create_surface(queue_handle);
let viewport = self
.viewporter_state
.as_ref()
.map(|state| state.get_viewport(&surface, queue_handle));
let surface_id = surface.id();
let pointer_data = WinitPointerData::new(seat.clone(), viewport);
let pointer_data = WinitPointerData::new(seat.clone());
let themed_pointer = self
.seat_state
.get_pointer_with_theme_and_data(
@@ -150,13 +139,7 @@ impl SeatHandler for WinitState {
seat: WlSeat,
capability: SeatCapability,
) {
let seat_state = match self.seats.get_mut(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_seat::remove_capability for unknown seat");
return;
},
};
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
if let Some(text_input) = seat_state.text_input.take() {
text_input.destroy();

View File

@@ -4,8 +4,6 @@ use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tracing::warn;
use sctk::reexports::client::delegate_dispatch;
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
@@ -18,7 +16,6 @@ use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_ma
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::csd_frame::FrameClick;
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
use sctk::compositor::SurfaceData;
use sctk::globals::GlobalData;
@@ -44,21 +41,7 @@ impl PointerHandler for WinitState {
events: &[PointerEvent],
) {
let seat = pointer.winit_data().seat();
let seat_state = match self.seats.get(&seat.id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received pointer event without seat");
return;
},
};
let themed_pointer = match seat_state.pointer.as_ref() {
Some(pointer) => pointer,
None => {
warn!("Received pointer event without pointer");
return;
},
};
let seat_state = self.seats.get(&seat.id()).unwrap();
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
@@ -95,7 +78,9 @@ impl PointerHandler for WinitState {
event.position.0,
event.position.1,
) {
let _ = themed_pointer.set_cursor(connection, icon);
if let Some(pointer) = seat_state.pointer.as_ref() {
let _ = pointer.set_cursor(connection, icon);
}
}
},
PointerEventKind::Leave { .. } if parent_surface != surface => {
@@ -128,7 +113,9 @@ impl PointerHandler for WinitState {
self.events_sink
.push_window_event(WindowEvent::CursorEntered { device_id }, window_id);
window.pointer_entered(Arc::downgrade(themed_pointer));
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
window.pointer_entered(pointer);
}
// Set the currently focused surface.
pointer.winit_data().inner.lock().unwrap().surface = Some(window_id);
@@ -139,7 +126,9 @@ impl PointerHandler for WinitState {
);
},
PointerEventKind::Leave { .. } => {
window.pointer_left(Arc::downgrade(themed_pointer));
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
window.pointer_left(pointer);
}
// Remove the active surface.
pointer.winit_data().inner.lock().unwrap().surface = None;
@@ -194,15 +183,15 @@ impl PointerHandler for WinitState {
pointer_data.phase = phase;
// Mice events have both pixel and discrete delta's at the same time. So prefer
// the discrete values if they are present.
// the descrite values if they are present.
let delta = if has_discrete_scroll {
// NOTE: Wayland sign convention is the inverse of winit.
// XXX Wayland sign convention is the inverse of winit.
MouseScrollDelta::LineDelta(
(-horizontal.discrete) as f32,
(-vertical.discrete) as f32,
)
} else {
// NOTE: Wayland sign convention is the inverse of winit.
// XXX Wayland sign convention is the inverse of winit.
MouseScrollDelta::PixelDelta(
LogicalPosition::new(-horizontal.absolute, -vertical.absolute)
.to_physical(scale_factor),
@@ -226,17 +215,13 @@ pub struct WinitPointerData {
/// The data required by the sctk.
sctk_data: PointerData,
/// Viewport for fractional cursor.
viewport: Option<WpViewport>,
}
impl WinitPointerData {
pub fn new(seat: WlSeat, viewport: Option<WpViewport>) -> Self {
pub fn new(seat: WlSeat) -> Self {
Self {
inner: Mutex::new(WinitPointerDataInner::default()),
sctk_data: PointerData::new(seat),
viewport,
}
}
@@ -317,18 +302,6 @@ impl WinitPointerData {
locked_pointer.set_cursor_position_hint(surface_x, surface_y);
}
}
pub fn viewport(&self) -> Option<&WpViewport> {
self.viewport.as_ref()
}
}
impl Drop for WinitPointerData {
fn drop(&mut self) {
if let Some(viewport) = self.viewport.take() {
viewport.destroy();
}
}
}
impl PointerDataExt for WinitPointerData {

View File

@@ -121,15 +121,11 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
None => return,
};
// Clear preedit, unless all we'll be doing next is sending a new preedit.
if text_input_data.pending_commit.is_some()
|| text_input_data.pending_preedit.is_none()
{
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
}
// Clear preedit at the start of `Done`.
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
// Send `Commit`.
if let Some(text) = text_input_data.pending_commit.take() {

View File

@@ -1,7 +1,5 @@
//! Touch handling.
use tracing::warn;
use sctk::reexports::client::protocol::wl_seat::WlSeat;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::protocol::wl_touch::WlTouch;
@@ -33,16 +31,11 @@ impl TouchHandler for WinitState {
None => return,
};
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::down without seat");
return;
},
};
let location = LogicalPosition::<f64>::from(position);
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
// Update the state of the point.
let location = LogicalPosition::<f64>::from(position);
seat_state.touch_map.insert(id, TouchPoint { surface, location });
self.events_sink.push_window_event(
@@ -68,13 +61,7 @@ impl TouchHandler for WinitState {
_: u32,
id: i32,
) {
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::up without seat");
return;
},
};
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
// Remove the touch point.
let touch_point = match seat_state.touch_map.remove(&id) {
@@ -111,13 +98,7 @@ impl TouchHandler for WinitState {
id: i32,
position: (f64, f64),
) {
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::motion without seat");
return;
},
};
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
// Remove the touch point.
let touch_point = match seat_state.touch_map.get_mut(&id) {
@@ -148,13 +129,7 @@ impl TouchHandler for WinitState {
}
fn cancel(&mut self, _: &Connection, _: &QueueHandle<Self>, touch: &WlTouch) {
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
Some(seat_state) => seat_state,
None => {
warn!("Received wl_touch::cancel without seat");
return;
},
};
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
for (id, touch_point) in seat_state.touch_map.drain() {
let window_id = wayland::make_wid(&touch_point.surface);

View File

@@ -339,30 +339,12 @@ impl CompositorHandler for WinitState {
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &wayland_client::protocol::wl_surface::WlSurface,
_: wayland_client::protocol::wl_output::Transform,
) {
// TODO(kchibisov) we need to expose it somehow in winit.
}
fn surface_enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &WlOutput,
) {
}
fn surface_leave(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: &WlOutput,
) {
}
fn scale_factor_changed(
&mut self,
_: &Connection,

View File

@@ -80,7 +80,7 @@ impl Dispatch<XdgActivationTokenV1, XdgActivationTokenData, WinitState> for XdgA
state.events_sink.push_window_event(
crate::event::WindowEvent::ActivationTokenDone {
serial: *serial,
token: ActivationToken::from_raw(token),
token: ActivationToken::_new(token),
},
*window_id,
);

View File

@@ -1,7 +1,5 @@
//! The Wayland window.
use std::ffi::c_void;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
@@ -170,7 +168,7 @@ impl Window {
if let (Some(xdg_activation), Some(token)) =
(xdg_activation.as_ref(), attributes.platform_specific.activation_token)
{
xdg_activation.activate(token.token, &surface);
xdg_activation.activate(token._token, &surface);
}
// XXX Do initial commit.
@@ -225,10 +223,6 @@ impl Window {
window_events_sink,
})
}
pub(crate) fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
NonNull::new(self.window.xdg_toplevel().id().as_ptr().cast())
}
}
impl Window {

View File

@@ -31,7 +31,7 @@ use sctk::subcompositor::SubcompositorState;
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
use crate::cursor::CustomCursor as RootCustomCursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Size};
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
use crate::error::{ExternalError, NotSupportedError};
use crate::platform_impl::wayland::logical_to_physical_rounded;
use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
@@ -222,9 +222,9 @@ impl WindowState {
}
/// Apply closure on the given pointer.
fn apply_on_pointer<F: FnMut(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
fn apply_on_pointer<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
&self,
mut callback: F,
callback: F,
) {
self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| {
let data = pointer.pointer().winit_data();
@@ -726,26 +726,17 @@ impl WindowState {
}
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
self.apply_on_pointer(|pointer, data| {
self.apply_on_pointer(|pointer, _| {
let surface = pointer.surface();
let scale = if let Some(viewport) = data.viewport() {
let scale = self.scale_factor();
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
viewport.set_destination(size.width, size.height);
scale
} else {
let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
surface.set_buffer_scale(scale);
scale as f64
};
let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
surface.set_buffer_scale(scale);
surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
if surface.version() >= 4 {
surface.damage_buffer(0, 0, cursor.w, cursor.h);
} else {
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
surface.damage(0, 0, size.width, size.height);
surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
}
surface.commit();
@@ -755,9 +746,12 @@ impl WindowState {
.and_then(|data| data.pointer_data().latest_enter_serial())
.unwrap();
let hotspot =
PhysicalPosition::new(cursor.hotspot_x, cursor.hotspot_y).to_logical(scale);
pointer.pointer().set_cursor(serial, Some(surface), hotspot.x, hotspot.y);
pointer.pointer().set_cursor(
serial,
Some(surface),
cursor.hotspot_x / scale,
cursor.hotspot_y / scale,
);
});
}
@@ -833,51 +827,34 @@ impl WindowState {
None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
};
let mut unset_old = false;
match self.cursor_grab_mode.current_grab_mode {
CursorGrabMode::None => unset_old = true,
// Replace the current mode.
let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
match old_mode {
CursorGrabMode::None => (),
CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
data.unconfine_pointer();
unset_old = true;
}),
CursorGrabMode::Locked => {
self.apply_on_pointer(|_, data| {
data.unlock_pointer();
unset_old = true;
});
self.apply_on_pointer(|_, data| data.unlock_pointer());
},
}
// In case we haven't unset the old mode, it means that we don't have a cursor above
// the window, thus just wait for it to re-appear.
if !unset_old {
return Ok(());
}
let mut set_mode = false;
let surface = self.window.wl_surface();
match mode {
CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
let pointer = pointer.pointer();
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
set_mode = true;
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
}),
CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
let pointer = pointer.pointer();
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
set_mode = true;
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
}),
CursorGrabMode::None => {
// Current lock/confine was already removed.
set_mode = true;
},
}
// Replace the current grab mode after we've ensure that it got updated.
if set_mode {
self.cursor_grab_mode.current_grab_mode = mode;
}
Ok(())
}

View File

@@ -165,14 +165,14 @@ fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
buffer: &'a mut Vec<u8>,
}
impl std::fmt::Write for Writer<'_> {
impl<'a> std::fmt::Write for Writer<'a> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buffer.extend_from_slice(s.as_bytes());
Ok(())
}
}
write!(Writer { buffer }, "{display}").unwrap();
write!(Writer { buffer }, "{}", display).unwrap();
}
#[cfg(test)]

View File

@@ -66,10 +66,7 @@ pub struct EventProcessor {
pub active_window: Option<xproto::Window>,
/// Latest modifiers we've sent for the user to trigger change in event.
pub modifiers: Cell<ModifiersState>,
// Track modifiers based on keycodes. NOTE: that serials generally don't work for tracking
// since they are not unique and could be duplicated in case of sequence of key events is
// delivered at near the same time.
pub xfiltered_modifiers: VecDeque<u8>,
pub xfiltered_modifiers: VecDeque<c_ulong>,
pub xmodmap: util::ModifierKeymap,
pub is_composing: bool,
}
@@ -132,7 +129,6 @@ impl EventProcessor {
/// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences,
/// along with an extra copy of the KeyRelease events. This also prevents backspace and
/// arrow keys from being detected twice.
#[must_use]
fn filter_event(&mut self, xev: &mut XEvent) -> bool {
let wt = Self::window_target(&self.target);
unsafe {
@@ -149,38 +145,20 @@ impl EventProcessor {
{
let event_type = xev.get_type();
// If we have IME disabled, don't try to `filter_event`, since only IME can consume them
// and forward back. This is not desired for e.g. games since some IMEs may delay the input
// and game can toggle IME back when e.g. typing into some field where latency won't really
// matter.
let filtered = if event_type == xlib::KeyPress || event_type == xlib::KeyRelease {
let wt = Self::window_target(&self.target);
let ime = wt.ime.as_ref();
let window = self.active_window.map(|window| window as XWindow);
let forward_to_ime = ime
.and_then(|ime| window.map(|window| ime.borrow().is_ime_allowed(window)))
.unwrap_or(false);
let filtered = forward_to_ime && self.filter_event(xev);
if filtered {
if self.filter_event(xev) {
if event_type == xlib::KeyPress || event_type == xlib::KeyRelease {
let xev: &XKeyEvent = xev.as_ref();
if self.xmodmap.is_modifier(xev.keycode as u8) {
// Don't grow the buffer past the `MAX_MOD_REPLAY_LEN`. This could happen
// when the modifiers are consumed entirely.
// when the modifiers are consumed entirely or serials are altered.
//
// Both cases shouldn't happen in well behaving clients.
if self.xfiltered_modifiers.len() == MAX_MOD_REPLAY_LEN {
self.xfiltered_modifiers.pop_back();
}
self.xfiltered_modifiers.push_front(xev.keycode as u8);
self.xfiltered_modifiers.push_front(xev.serial);
}
}
filtered
} else {
self.filter_event(xev)
};
// Don't process event if it was filtered.
if filtered {
return;
}
@@ -456,7 +434,7 @@ impl EventProcessor {
let flags = xev.data.get_long(1);
let version = flags >> 24;
self.dnd.version = Some(version);
let has_more_types = flags - (flags & (c_long::MAX - 1)) == 1;
let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1;
if !has_more_types {
let type_list = vec![
xev.data.get_long(2) as xproto::Atom,
@@ -951,7 +929,7 @@ impl EventProcessor {
// itself are out of sync due to XkbState being delivered before XKeyEvent, since it's
// being replayed by the XIM, thus we should replay ourselves.
let replay = if let Some(position) =
self.xfiltered_modifiers.iter().rev().position(|&s| s == xev.keycode as u8)
self.xfiltered_modifiers.iter().rev().position(|&s| s == xev.serial)
{
// We don't have to replay modifiers pressed before the current event if some events
// were not forwarded to us, since their state is irrelevant.

View File

@@ -123,15 +123,19 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
let is_allowed =
old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default();
// We can't use the style from the old context here, since it may change on reload, so
// pick style from the new XIM based on the old state.
let style = if is_allowed { new_im.preedit_style } else { new_im.none_style };
let new_context = {
let result = unsafe {
ImeContext::new(
xconn,
&new_im,
new_im.im,
style,
*window,
spot,
(*inner).event_sender.clone(),
is_allowed,
)
};
if result.is_err() {

View File

@@ -5,10 +5,11 @@ use std::{mem, ptr};
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
use super::{ffi, util, XConnection, XError};
use crate::platform_impl::platform::x11::ime::input_method::{InputMethod, Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
use super::{ffi, util, XConnection, XError};
/// IME creation error.
#[derive(Debug)]
pub enum ImeContextCreationError {
@@ -157,9 +158,7 @@ struct PreeditCallbacks {
impl PreeditCallbacks {
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
let start_callback = create_xim_callback(client_data, unsafe {
mem::transmute::<usize, unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer)>(
preedit_start_callback as usize,
)
mem::transmute(preedit_start_callback as usize)
});
let done_callback = create_xim_callback(client_data, preedit_done_callback);
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
@@ -183,7 +182,7 @@ struct ImeContextClientData {
pub struct ImeContext {
pub(crate) ic: ffi::XIC,
pub(crate) ic_spot: ffi::XPoint,
pub(crate) allowed: bool,
pub(crate) style: Style,
// Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free
// from there we keep the pointer to automatically deallocate it.
_client_data: Box<ImeContextClientData>,
@@ -192,11 +191,11 @@ pub struct ImeContext {
impl ImeContext {
pub(crate) unsafe fn new(
xconn: &Arc<XConnection>,
im: &InputMethod,
im: ffi::XIM,
style: Style,
window: ffi::Window,
ic_spot: Option<ffi::XPoint>,
event_sender: ImeEventSender,
allowed: bool,
) -> Result<Self, ImeContextCreationError> {
let client_data = Box::into_raw(Box::new(ImeContextClientData {
window,
@@ -205,24 +204,20 @@ impl ImeContext {
cursor_pos: 0,
}));
let style = if allowed { im.preedit_style } else { im.none_style };
let ic = match style as _ {
Style::Preedit(style) => unsafe {
ImeContext::create_preedit_ic(
xconn,
im.im,
im,
style,
window,
client_data as ffi::XPointer,
)
},
Style::Nothing(style) => unsafe {
ImeContext::create_nothing_ic(xconn, im.im, style, window)
},
Style::None(style) => unsafe {
ImeContext::create_none_ic(xconn, im.im, style, window)
ImeContext::create_nothing_ic(xconn, im, style, window)
},
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
}
.ok_or(ImeContextCreationError::Null)?;
@@ -231,7 +226,7 @@ impl ImeContext {
let mut context = ImeContext {
ic,
ic_spot: ffi::XPoint { x: 0, y: 0 },
allowed,
style,
_client_data: unsafe { Box::from_raw(client_data) },
};
@@ -338,7 +333,7 @@ impl ImeContext {
}
pub fn is_allowed(&self) -> bool {
self.allowed
!matches!(self.style, Style::None(_))
}
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks

View File

@@ -176,7 +176,7 @@ unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXi
)
.map_err(GetXimServersError::GetPropertyError)?
.into_iter()
.map(|atom| atom as _)
.map(ffi::Atom::from)
.collect::<Vec<_>>();
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());

View File

@@ -10,13 +10,15 @@ use std::sync::Arc;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use tracing::debug;
use super::{ffi, util, XConnection, XError};
use self::callbacks::*;
use self::context::ImeContext;
pub use self::context::ImeContextCreationError;
use self::inner::{close_im, ImeInner};
use self::input_method::PotentialInputMethods;
use super::{ffi, util, XConnection, XError};
use self::input_method::{PotentialInputMethods, Style};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -112,26 +114,39 @@ impl Ime {
pub fn create_context(
&mut self,
window: ffi::Window,
with_ime: bool,
with_preedit: bool,
) -> Result<bool, ImeContextCreationError> {
let context = if self.is_destroyed() {
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
None
} else {
let im = self.inner.im.as_ref().unwrap();
let style = if with_preedit { im.preedit_style } else { im.none_style };
let context = unsafe {
ImeContext::new(
&self.inner.xconn,
im,
im.im,
style,
window,
None,
self.inner.event_sender.clone(),
with_ime,
)?
};
let event = if context.is_allowed() { ImeEvent::Enabled } else { ImeEvent::Disabled };
// Check the state on the context, since it could fail to enable or disable preedit.
let event = if matches!(style, Style::None(_)) {
if with_preedit {
debug!("failed to create IME context with preedit support.")
}
ImeEvent::Disabled
} else {
if !with_preedit {
debug!("failed to create IME context without preedit support.")
}
ImeEvent::Enabled
};
self.inner.event_sender.send((window, event)).expect("Failed to send enabled event");
Some(context)
@@ -211,16 +226,6 @@ impl Ime {
// Create new context supporting IME input.
let _ = self.create_context(window, allowed);
}
pub fn is_ime_allowed(&self, window: ffi::Window) -> bool {
if self.is_destroyed() {
false
} else if let Some(Some(context)) = self.inner.contexts.get(&window) {
context.is_allowed()
} else {
false
}
}
}
impl Drop for Ime {

View File

@@ -1,3 +1,5 @@
#![cfg(x11_platform)]
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet, VecDeque};
use std::ffi::CStr;
@@ -25,6 +27,7 @@ use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::xcb_ffi::ReplyOrIdError;
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, OsError as RootOsError};
use crate::event::{Event, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents, EventLoopClosed};
@@ -377,12 +380,16 @@ impl<T: 'static> EventLoop<T> {
&self.event_processor.target
}
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootAEL),
{
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
let exit = loop {
match self.pump_events(None, &mut event_handler) {
match self.pump_app_events(None, app) {
PumpStatus::Exit(0) => {
break Ok(());
},
@@ -407,26 +414,27 @@ impl<T: 'static> EventLoop<T> {
exit
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut callback: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootAEL),
{
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
if !self.loop_running {
self.loop_running = true;
// run the initial loop iteration
self.single_iteration(&mut callback, StartCause::Init);
self.single_iteration(app, StartCause::Init);
}
// Consider the possibility that the `StartCause::Init` iteration could
// request to Exit.
if !self.exiting() {
self.poll_events_with_timeout(timeout, &mut callback);
self.poll_events_with_timeout(timeout, app);
}
if let Some(code) = self.exit_code() {
self.loop_running = false;
callback(Event::LoopExiting, self.window_target());
app.exiting(self.window_target());
PumpStatus::Exit(code)
} else {
@@ -440,10 +448,11 @@ impl<T: 'static> EventLoop<T> {
|| self.redraw_receiver.has_incoming()
}
pub fn poll_events_with_timeout<F>(&mut self, mut timeout: Option<Duration>, mut callback: F)
where
F: FnMut(Event<T>, &RootAEL),
{
pub fn poll_events_with_timeout<A: ApplicationHandler<T>>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
let start = Instant::now();
let has_pending = self.has_pending();
@@ -497,28 +506,24 @@ impl<T: 'static> EventLoop<T> {
// If we don't have any pending `_receiver`
if !self.has_pending()
&& !matches!(&cause, StartCause::ResumeTimeReached { .. } | StartCause::Poll)
&& timeout.is_none()
{
return;
}
self.single_iteration(&mut callback, cause);
self.single_iteration(app, cause);
}
fn single_iteration<F>(&mut self, callback: &mut F, cause: StartCause)
where
F: FnMut(Event<T>, &RootAEL),
{
callback(Event::NewEvents(cause), &self.event_processor.target);
fn single_iteration<A: ApplicationHandler<T>>(&mut self, app: &mut A, cause: StartCause) {
app.new_events(&self.event_processor.target, cause);
// NB: For consistency all platforms must emit a 'resumed' event even though X11
// applications don't themselves have a formal suspend/resume lifecycle.
if cause == StartCause::Init {
callback(Event::Resumed, &self.event_processor.target);
app.resumed(&self.event_processor.target)
}
// Process all pending events
self.drain_events(callback);
self.drain_events(app);
// Empty activation tokens.
while let Ok((window_id, serial)) = self.activation_receiver.try_recv() {
@@ -528,14 +533,12 @@ impl<T: 'static> EventLoop<T> {
match token {
Some(Ok(token)) => {
let event = Event::WindowEvent {
window_id: crate::window::WindowId(window_id),
event: WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::from_raw(token),
},
let window_id = crate::window::WindowId(window_id);
let event = WindowEvent::ActivationTokenDone {
serial,
token: crate::window::ActivationToken::_new(token),
};
callback(event, &self.event_processor.target)
app.window_event(&self.event_processor.target, window_id, event);
},
Some(Err(e)) => {
tracing::error!("Failed to get activation token: {}", e);
@@ -547,7 +550,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_receiver.try_recv() {
callback(Event::UserEvent(event), &self.event_processor.target);
app.user_event(&self.event_processor.target, event);
}
}
@@ -561,28 +564,24 @@ impl<T: 'static> EventLoop<T> {
for window_id in windows {
let window_id = crate::window::WindowId(window_id);
callback(
Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested },
app.window_event(
&self.event_processor.target,
window_id,
WindowEvent::RedrawRequested,
);
}
}
// This is always the last event we dispatch before poll again
{
callback(Event::AboutToWait, &self.event_processor.target);
}
app.about_to_wait(&self.event_processor.target);
}
fn drain_events<F>(&mut self, callback: &mut F)
where
F: FnMut(Event<T>, &RootAEL),
{
fn drain_events<A: ApplicationHandler<T>>(&mut self, app: &mut A) {
let mut xev = MaybeUninit::uninit();
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
let mut xev = unsafe { xev.assume_init() };
self.event_processor.process_event(&mut xev, |window_target, event| {
self.event_processor.process_event(&mut xev, |window_target, event: Event<T>| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
@@ -591,7 +590,15 @@ impl<T: 'static> EventLoop<T> {
let window_target = EventProcessor::window_target(window_target);
window_target.redraw_sender.send(wid).unwrap();
} else {
callback(event, window_target);
match event {
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
_ => unreachable!("event which is neither device nor window event."),
}
}
});
}
@@ -752,14 +759,14 @@ impl<'a> DeviceInfo<'a> {
}
}
impl Drop for DeviceInfo<'_> {
impl<'a> Drop for DeviceInfo<'a> {
fn drop(&mut self) {
assert!(!self.info.is_null());
unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
}
}
impl Deref for DeviceInfo<'_> {
impl<'a> Deref for DeviceInfo<'a> {
type Target = [ffi::XIDeviceInfo];
fn deref(&self) -> &Self::Target {
@@ -772,7 +779,7 @@ pub struct DeviceId(xinput::DeviceId);
impl DeviceId {
#[allow(unused)]
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId(0)
}
}
@@ -850,24 +857,24 @@ pub enum X11Error {
impl fmt::Display for X11Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X11Error::Xlib(e) => write!(f, "Xlib error: {e}"),
X11Error::Connect(e) => write!(f, "X11 connection error: {e}"),
X11Error::Connection(e) => write!(f, "X11 connection error: {e}"),
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {e}"),
X11Error::GetProperty(e) => write!(f, "Failed to get X property {e}"),
X11Error::X11(e) => write!(f, "X11 error: {e:?}"),
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {s}"),
X11Error::Xlib(e) => write!(f, "Xlib error: {}", e),
X11Error::Connect(e) => write!(f, "X11 connection error: {}", e),
X11Error::Connection(e) => write!(f, "X11 connection error: {}", e),
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e),
X11Error::GetProperty(e) => write!(f, "Failed to get X property {}", e),
X11Error::X11(e) => write!(f, "X11 error: {:?}", e),
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s),
X11Error::InvalidActivationToken(s) => write!(
f,
"Invalid activation token: {}",
std::str::from_utf8(s).unwrap_or("<invalid utf8>")
),
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {s}"),
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {}", s),
X11Error::NoSuchVisual(visualid) => {
write!(f, "Could not find a matching X11 visual for ID `{visualid:x}`")
write!(f, "Could not find a matching X11 visual for ID `{:x}`", visualid)
},
X11Error::XsettingsParse(err) => {
write!(f, "Failed to parse xsettings: {err:?}")
write!(f, "Failed to parse xsettings: {:?}", err)
},
}
}
@@ -958,7 +965,7 @@ trait CookieResultExt {
fn expect_then_ignore_error(self, msg: &str);
}
impl<E: fmt::Debug> CookieResultExt for Result<VoidCookie<'_>, E> {
impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
fn expect_then_ignore_error(self, msg: &str) {
self.expect(msg).ignore_error()
}

View File

@@ -301,7 +301,7 @@ impl XConnection {
let info = self
.xcb_connection()
.extension_information(randr::X11_EXTENSION_NAME)?
.ok_or(X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
.ok_or_else(|| X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
// Select input data.
let event_mask =

View File

@@ -19,7 +19,7 @@ impl<'a, T> XSmartPointer<'a, T> {
}
}
impl<T> Deref for XSmartPointer<'_, T> {
impl<'a, T> Deref for XSmartPointer<'a, T> {
type Target = T;
fn deref(&self) -> &T {
@@ -27,13 +27,13 @@ impl<T> Deref for XSmartPointer<'_, T> {
}
}
impl<T> DerefMut for XSmartPointer<'_, T> {
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr }
}
}
impl<T> Drop for XSmartPointer<'_, T> {
impl<'a, T> Drop for XSmartPointer<'a, T> {
fn drop(&mut self) {
unsafe {
(self.xconn.xlib.XFree)(self.ptr as *mut _);

View File

@@ -51,7 +51,7 @@ where
}
impl XConnection {
// This is important, so pay attention!
// This is impoartant, so pay attention!
// Xlib has an output buffer, and tries to hide the async nature of X from you.
// This buffer contains the requests you make, and is flushed under various circumstances:
// 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed"

View File

@@ -79,7 +79,7 @@ impl XConnection {
.iter()
// XRROutputInfo contains an array of mode ids that correspond to
// modes in the array in XRRScreenResources
.filter(|x| output_modes.contains(&x.id))
.filter(|x| output_modes.iter().any(|id| x.id == *id))
.map(|mode| {
VideoModeHandle {
size: (mode.width.into(), mode.height.into()),

View File

@@ -144,26 +144,12 @@ impl UnownedWindow {
) -> Result<UnownedWindow, RootOsError> {
let xconn = &event_loop.xconn;
let atoms = xconn.atoms();
let screen_id = match window_attrs.platform_specific.x11.screen_id {
Some(id) => id,
None => xconn.default_screen_index() as c_int,
};
let screen = {
let screen_id_usize = usize::try_from(screen_id)
.map_err(|_| os_error!(OsError::Misc("screen id must be non-negative")))?;
xconn.xcb_connection().setup().roots.get(screen_id_usize).ok_or(os_error!(
OsError::Misc("requested screen id not present in server's response")
))?
};
#[cfg(feature = "rwh_06")]
let root = match window_attrs.parent_window.as_ref().map(|handle| handle.0) {
Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window,
Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(),
Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"),
None => screen.root,
None => event_loop.root,
};
#[cfg(not(feature = "rwh_06"))]
let root = event_loop.root;
@@ -221,10 +207,18 @@ impl UnownedWindow {
dimensions
};
// An iterator over the visuals matching screen id combined with their depths.
let mut all_visuals = screen
.allowed_depths
let screen_id = match window_attrs.platform_specific.x11.screen_id {
Some(id) => id,
None => xconn.default_screen_index() as c_int,
};
// An iterator over all of the visuals combined with their depths.
let mut all_visuals = xconn
.xcb_connection()
.setup()
.roots
.iter()
.flat_map(|root| &root.allowed_depths)
.flat_map(|depth| depth.visuals.iter().map(move |visual| (visual, depth.depth)));
// creating
@@ -490,20 +484,6 @@ impl UnownedWindow {
);
leap!(result).ignore_error();
// Select XInput2 events
let mask = xinput::XIEventMask::MOTION
| xinput::XIEventMask::BUTTON_PRESS
| xinput::XIEventMask::BUTTON_RELEASE
| xinput::XIEventMask::ENTER
| xinput::XIEventMask::LEAVE
| xinput::XIEventMask::FOCUS_IN
| xinput::XIEventMask::FOCUS_OUT
| xinput::XIEventMask::TOUCH_BEGIN
| xinput::XIEventMask::TOUCH_UPDATE
| xinput::XIEventMask::TOUCH_END;
leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
.ignore_error();
// Set visibility (map window)
if window_attrs.visible {
leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error();
@@ -527,6 +507,20 @@ impl UnownedWindow {
}
}
// Select XInput2 events
let mask = xinput::XIEventMask::MOTION
| xinput::XIEventMask::BUTTON_PRESS
| xinput::XIEventMask::BUTTON_RELEASE
| xinput::XIEventMask::ENTER
| xinput::XIEventMask::LEAVE
| xinput::XIEventMask::FOCUS_IN
| xinput::XIEventMask::FOCUS_OUT
| xinput::XIEventMask::TOUCH_BEGIN
| xinput::XIEventMask::TOUCH_UPDATE
| xinput::XIEventMask::TOUCH_END;
leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
.ignore_error();
// Try to create input context for the window.
if let Some(ime) = event_loop.ime.as_ref() {
let result = ime.borrow_mut().create_context(window.xwindow as ffi::Window, false);
@@ -559,7 +553,7 @@ impl UnownedWindow {
// Remove the startup notification if we have one.
if let Some(startup) = window_attrs.platform_specific.activation_token.as_ref() {
leap!(xconn.remove_activation_token(xwindow, &startup.token));
leap!(xconn.remove_activation_token(xwindow, &startup._token));
}
// We never want to give the user a broken window, since by then, it's too late to handle.
@@ -976,8 +970,8 @@ impl UnownedWindow {
let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT];
match state {
Ok(atoms) => {
let horz_maximized = atoms.contains(&horz_atom);
let vert_maximized = atoms.contains(&vert_atom);
let horz_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == horz_atom);
let vert_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == vert_atom);
horz_maximized && vert_maximized
},
_ => false,
@@ -1492,11 +1486,6 @@ impl UnownedWindow {
#[inline]
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
// We don't support the locked cursor yet, so ignore it early on.
if mode == CursorGrabMode::Locked {
return Err(ExternalError::NotSupported(NotSupportedError::new()));
}
let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
if mode == *grabbed_lock {
return Ok(());
@@ -1508,40 +1497,40 @@ impl UnownedWindow {
.xcb_connection()
.ungrab_pointer(x11rb::CURRENT_TIME)
.expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`");
*grabbed_lock = CursorGrabMode::None;
let result = match mode {
CursorGrabMode::None => self.xconn.flush_requests().map_err(|err| {
ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into())))
}),
CursorGrabMode::Confined => {
let result = self
.xconn
.xcb_connection()
.grab_pointer(
true as _,
self.xwindow,
xproto::EventMask::BUTTON_PRESS
| xproto::EventMask::BUTTON_RELEASE
| xproto::EventMask::ENTER_WINDOW
| xproto::EventMask::LEAVE_WINDOW
| xproto::EventMask::POINTER_MOTION
| xproto::EventMask::POINTER_MOTION_HINT
| xproto::EventMask::BUTTON1_MOTION
| xproto::EventMask::BUTTON2_MOTION
| xproto::EventMask::BUTTON3_MOTION
| xproto::EventMask::BUTTON4_MOTION
| xproto::EventMask::BUTTON5_MOTION
| xproto::EventMask::KEYMAP_STATE,
xproto::GrabMode::ASYNC,
xproto::GrabMode::ASYNC,
self.xwindow,
0u32,
x11rb::CURRENT_TIME,
)
.expect("Failed to call `grab_pointer`")
.reply()
.expect("Failed to receive reply from `grab_pointer`");
let result = {
self.xconn
.xcb_connection()
.grab_pointer(
true as _,
self.xwindow,
xproto::EventMask::BUTTON_PRESS
| xproto::EventMask::BUTTON_RELEASE
| xproto::EventMask::ENTER_WINDOW
| xproto::EventMask::LEAVE_WINDOW
| xproto::EventMask::POINTER_MOTION
| xproto::EventMask::POINTER_MOTION_HINT
| xproto::EventMask::BUTTON1_MOTION
| xproto::EventMask::BUTTON2_MOTION
| xproto::EventMask::BUTTON3_MOTION
| xproto::EventMask::BUTTON4_MOTION
| xproto::EventMask::BUTTON5_MOTION
| xproto::EventMask::KEYMAP_STATE,
xproto::GrabMode::ASYNC,
xproto::GrabMode::ASYNC,
self.xwindow,
0u32,
x11rb::CURRENT_TIME,
)
.expect("Failed to call `grab_pointer`")
.reply()
.expect("Failed to receive reply from `grab_pointer`")
};
match result.status {
xproto::GrabStatus::SUCCESS => Ok(()),
@@ -1561,7 +1550,9 @@ impl UnownedWindow {
}
.map_err(|err| ExternalError::Os(os_error!(OsError::Misc(err))))
},
CursorGrabMode::Locked => return Ok(()),
CursorGrabMode::Locked => {
return Err(ExternalError::NotSupported(NotSupportedError::new()));
},
};
if result.is_ok() {

View File

@@ -145,7 +145,7 @@ impl XConnection {
fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option<xproto::Atom> {
// Fetch the _XSETTINGS_S[screen number] atom.
let xsettings_screen = xcb
.intern_atom(false, format!("_XSETTINGS_S{default_screen}").as_bytes())
.intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
.ok()?
.reply()
.ok()?

View File

@@ -1,104 +1,49 @@
#![allow(clippy::unnecessary_cast)]
#![allow(unknown_lints)] // New lint below
#![allow(static_mut_refs)] // Uses `MainThreadBound` in new version.
use std::cell::Cell;
use std::mem;
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2_foundation::{MainThreadMarker, NSObject};
use objc2::runtime::{Imp, Sel};
use objc2::sel;
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::MainThreadMarker;
use super::app_state::ApplicationDelegate;
use super::app_delegate::ApplicationDelegate;
use crate::event::{DeviceEvent, ElementState};
type SendEvent = extern "C" fn(&NSApplication, Sel, &NSEvent);
declare_class!(
pub(super) struct WinitApplication;
// NOTE: Only used on the main thread. Ideally, we'd use `MainThreadBound`, but that isn't
// constructible from `const` with this `objc2` version.
static mut ORIGINAL: Cell<Option<SendEvent>> = Cell::new(None);
unsafe impl ClassType for WinitApplication {
#[inherits(NSResponder, NSObject)]
type Super = NSApplication;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplication";
}
extern "C" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) {
let mtm = MainThreadMarker::from(app);
impl DeclaredClass for WinitApplication {}
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` fixes that. (https://stackoverflow.com/a/15294196)
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
//
// For posterity, there are some undocumented event types
// (https://github.com/servo/cocoa-rs/issues/155)
// but that doesn't really matter here.
let event_type = unsafe { event.r#type() };
let modifier_flags = unsafe { event.modifierFlags() };
if event_type == NSEventType::KeyUp
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
{
if let Some(key_window) = app.keyWindow() {
key_window.sendEvent(event);
unsafe impl WinitApplication {
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
#[method(sendEvent:)]
fn send_event(&self, event: &NSEvent) {
// For posterity, there are some undocumented event types
// (https://github.com/servo/cocoa-rs/issues/155)
// but that doesn't really matter here.
let event_type = unsafe { event.r#type() };
let modifier_flags = unsafe { event.modifierFlags() };
if event_type == NSEventType::KeyUp
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
{
if let Some(key_window) = self.keyWindow() {
key_window.sendEvent(event);
}
} else {
let delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
maybe_dispatch_device_event(&delegate, event);
unsafe { msg_send![super(self), sendEvent: event] }
}
}
return;
}
// Events are generally scoped to the window level, so the best way
// to get device events is to listen for them on NSApplication.
let delegate = ApplicationDelegate::get(mtm);
maybe_dispatch_device_event(&delegate, event);
let _ = mtm;
let original = unsafe { ORIGINAL.get().expect("no existing sendEvent: handler set") };
original(app, sel, event)
}
/// Override the [`sendEvent:`][NSApplication::sendEvent] method on the given application class.
///
/// The previous implementation created a subclass of [`NSApplication`], however we would like to
/// give the user full control over their `NSApplication`, so we override the method here using
/// method swizzling instead.
///
/// This _should_ also allow two versions of Winit to exist in the same application.
///
/// See the following links for more info on method swizzling:
/// - <https://nshipster.com/method-swizzling/>
/// - <https://spin.atomicobject.com/method-swizzling-objective-c/>
/// - <https://web.archive.org/web/20130308110627/http://cocoadev.com/wiki/MethodSwizzling>
///
/// NOTE: This function assumes that the passed in application object is the one returned from
/// [`NSApplication::sharedApplication`], i.e. the one and only global shared application object.
/// For testing though, we allow it to be a different object.
pub(crate) fn override_send_event(global_app: &NSApplication) {
let mtm = MainThreadMarker::from(global_app);
let class = global_app.class();
let method =
class.instance_method(sel!(sendEvent:)).expect("NSApplication must have sendEvent: method");
// SAFETY: Converting our `sendEvent:` implementation to an IMP.
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
// If we've already overridden the method, don't do anything.
// FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV.
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
if overridden == method.implementation() {
return;
}
// SAFETY: Our implementation has:
// 1. The same signature as `sendEvent:`.
// 2. Does not impose extra safety requirements on callers.
let original = unsafe { method.set_implementation(overridden) };
// SAFETY: This is the actual signature of `sendEvent:`.
let original = unsafe { mem::transmute::<Imp, SendEvent>(original) };
// NOTE: If NSApplication was safe to use from multiple threads, then this would potentially be
// a (checked) race-condition, since one could call `sendEvent:` before the original had been
// stored here.
//
// It is only usable from the main thread, however, so we're good!
let _ = mtm;
unsafe { ORIGINAL.set(Some(original)) };
}
);
fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) {
let event_type = unsafe { event.r#type() };
@@ -112,27 +57,25 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
let delta_y = unsafe { event.deltaY() } as f64;
if delta_x != 0.0 {
delegate.maybe_queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x });
delegate.queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x });
}
if delta_y != 0.0 {
delegate.maybe_queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y })
delegate.queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y })
}
if delta_x != 0.0 || delta_y != 0.0 {
delegate.maybe_queue_device_event(DeviceEvent::MouseMotion {
delta: (delta_x, delta_y),
});
delegate.queue_device_event(DeviceEvent::MouseMotion { delta: (delta_x, delta_y) });
}
},
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
delegate.maybe_queue_device_event(DeviceEvent::Button {
delegate.queue_device_event(DeviceEvent::Button {
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Pressed,
});
},
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
delegate.maybe_queue_device_event(DeviceEvent::Button {
delegate.queue_device_event(DeviceEvent::Button {
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Released,
});
@@ -140,56 +83,3 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
_ => (),
}
}
#[cfg(test)]
mod tests {
use objc2::rc::Retained;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use super::*;
#[test]
fn test_override() {
// FIXME(madsmtm): Ensure this always runs (maybe use cargo-nextest or `--test-threads=1`?)
let Some(mtm) = MainThreadMarker::new() else { return };
// Create a new application, without making it the shared application.
let app = unsafe { NSApplication::new(mtm) };
override_send_event(&app);
// Test calling twice works.
override_send_event(&app);
// FIXME(madsmtm): Can't test this yet, need some way to mock AppState.
// unsafe {
// let event = super::super::event::dummy_event().unwrap();
// app.sendEvent(&event)
// }
}
#[test]
fn test_custom_class() {
let Some(_mtm) = MainThreadMarker::new() else { return };
declare_class!(
struct TestApplication;
unsafe impl ClassType for TestApplication {
type Super = NSApplication;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "TestApplication";
}
impl DeclaredClass for TestApplication {}
unsafe impl TestApplication {
#[method(sendEvent:)]
fn send_event(&self, _event: &NSEvent) {
todo!()
}
}
);
let app: Retained<TestApplication> = unsafe { msg_send_id![TestApplication::class(), new] };
override_send_event(&app);
}
}

View File

@@ -1,29 +1,40 @@
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::mem;
use std::rc::Weak;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate, NSRunningApplication,
};
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol, NSSize};
use super::event_handler::EventHandler;
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::observer::{EventLoopWaker, RunLoop};
use super::window::WinitWindow;
use super::{menu, WindowId, DEVICE_ID};
use crate::event::{DeviceEvent, Event, StartCause, WindowEvent};
use crate::dpi::PhysicalSize;
use crate::event::{DeviceEvent, Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
#[derive(Debug)]
pub(super) struct AppState {
activation_policy: Option<NSApplicationActivationPolicy>,
struct Policy(NSApplicationActivationPolicy);
impl Default for Policy {
fn default() -> Self {
Self(NSApplicationActivationPolicy::Regular)
}
}
#[derive(Debug, Default)]
pub(super) struct State {
activation_policy: Policy,
default_menu: bool,
activate_ignoring_other_apps: bool,
run_loop: RunLoop,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
stop_before_wait: Cell<bool>,
@@ -39,6 +50,7 @@ pub(super) struct AppState {
waker: RefCell<EventLoopWaker>,
start_time: Cell<Option<Instant>>,
wait_timeout: Cell<Option<Instant>>,
pending_events: RefCell<VecDeque<QueuedEvent>>,
pending_redraw: RefCell<Vec<WindowId>>,
// 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.
@@ -55,20 +67,62 @@ declare_class!(
}
impl DeclaredClass for ApplicationDelegate {
type Ivars = AppState;
type Ivars = State;
}
unsafe impl NSObjectProtocol for ApplicationDelegate {}
unsafe impl NSApplicationDelegate for ApplicationDelegate {
// NOTE: This will, globally, only be run once, no matter how many
// `EventLoop`s the user creates.
#[method(applicationDidFinishLaunching:)]
fn app_did_finish_launching(&self, notification: &NSNotification) {
self.did_finish_launching(notification)
fn did_finish_launching(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationDidFinishLaunching:");
self.ivars().is_launched.set(true);
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
app.setActivationPolicy(self.ivars().activation_policy.0);
window_activation_hack(&app);
#[allow(deprecated)]
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
if self.ivars().default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
menu::initialize(&app);
}
self.ivars().waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
// If the application is being launched via `EventLoop::pump_app_events()` then we'll
// want to stop the app once it is launched (and return to the external loop)
//
// In this case we still want to consider Winit's `EventLoop` to be "running",
// so we call `start_running()` above.
if self.ivars().stop_on_launch.get() {
// NOTE: the original idea had been to only stop the underlying `RunLoop`
// for the app but that didn't work as expected (`-[NSApplication run]`
// effectively ignored the attempt to stop the RunLoop and re-started it).
//
// So we return from `pump_events` by stopping the application.
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
}
#[method(applicationWillTerminate:)]
fn app_will_terminate(&self, notification: &NSNotification) {
self.will_terminate(notification)
fn will_terminate(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationWillTerminate:");
// TODO: Notify every window that it will be destroyed, like done in iOS?
self.internal_exit();
}
}
);
@@ -76,101 +130,19 @@ declare_class!(
impl ApplicationDelegate {
pub(super) fn new(
mtm: MainThreadMarker,
activation_policy: Option<NSApplicationActivationPolicy>,
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Retained<Self> {
let this = mtm.alloc().set_ivars(AppState {
activation_policy,
let this = mtm.alloc().set_ivars(State {
activation_policy: Policy(activation_policy),
default_menu,
activate_ignoring_other_apps,
run_loop: RunLoop::main(mtm),
event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false),
stop_before_wait: Cell::new(false),
stop_after_wait: Cell::new(false),
stop_on_redraw: Cell::new(false),
is_launched: Cell::new(false),
is_running: Cell::new(false),
exit: Cell::new(false),
control_flow: Cell::new(ControlFlow::default()),
waker: RefCell::new(EventLoopWaker::new()),
start_time: Cell::new(None),
wait_timeout: Cell::new(None),
pending_redraw: RefCell::new(vec![]),
..Default::default()
});
unsafe { msg_send_id![super(this), init] }
}
// NOTE: This will, globally, only be run once, no matter how many
// `EventLoop`s the user creates.
fn did_finish_launching(&self, _notification: &NSNotification) {
trace_scope!("applicationDidFinishLaunching:");
self.ivars().is_launched.set(true);
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
// If no activation policy is explicitly provided, do not set it at all
// to allow the package manifest to define behavior via LSUIElement.
if let Some(activation_policy) = self.ivars().activation_policy {
app.setActivationPolicy(activation_policy);
} else {
// If no activation policy is explicitly provided, and the application
// is bundled, do not set the activation policy at all, to allow the
// package manifest to define the behavior via LSUIElement.
//
// See:
// - https://github.com/rust-windowing/winit/issues/261
// - https://github.com/rust-windowing/winit/issues/3958
let is_bundled =
unsafe { NSRunningApplication::currentApplication().bundleIdentifier().is_some() };
if !is_bundled {
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
}
}
window_activation_hack(&app);
#[allow(deprecated)]
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
if self.ivars().default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
menu::initialize(&app);
}
self.ivars().waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
// If the application is being launched via `EventLoop::pump_app_events()` then we'll
// want to stop the app once it is launched (and return to the external loop)
//
// In this case we still want to consider Winit's `EventLoop` to be "running",
// so we call `start_running()` above.
if self.ivars().stop_on_launch.get() {
// NOTE: the original idea had been to only stop the underlying `RunLoop`
// for the app but that didn't work as expected (`-[NSApplication run]`
// effectively ignored the attempt to stop the RunLoop and re-started it).
//
// So we return from `pump_events` by stopping the application.
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
}
fn will_terminate(&self, _notification: &NSNotification) {
trace_scope!("applicationWillTerminate:");
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
notify_windows_of_exit(&app);
self.internal_exit();
}
pub fn get(mtm: MainThreadMarker) -> Retained<Self> {
let app = NSApplication::sharedApplication(mtm);
let delegate =
@@ -221,6 +193,7 @@ impl ApplicationDelegate {
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
/// and we won't need to re-launch the app if subsequent EventLoops are run.
pub fn internal_exit(&self) {
self.handle_event(Event::Suspended);
self.handle_event(Event::LoopExiting);
self.set_is_running(false);
@@ -262,16 +235,28 @@ impl ApplicationDelegate {
self.ivars().control_flow.get()
}
pub fn maybe_queue_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.maybe_queue_event(Event::WindowEvent { window_id: RootWindowId(window_id), event });
pub fn queue_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.ivars()
.pending_events
.borrow_mut()
.push_back(QueuedEvent::WindowEvent(window_id, event));
}
pub fn handle_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.handle_event(Event::WindowEvent { window_id: RootWindowId(window_id), event });
pub fn queue_device_event(&self, event: DeviceEvent) {
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::DeviceEvent(event));
}
pub fn maybe_queue_device_event(&self, event: DeviceEvent) {
self.maybe_queue_event(Event::DeviceEvent { device_id: DEVICE_ID, event });
pub fn queue_static_scale_factor_changed_event(
&self,
window: Retained<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
) {
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::ScaleFactorChanged {
window,
suggested_size,
scale_factor,
});
}
pub fn handle_redraw(&self, window_id: WindowId) {
@@ -299,27 +284,9 @@ impl ApplicationDelegate {
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
self.ivars().run_loop.wakeup();
unsafe { RunLoop::get() }.wakeup();
}
#[track_caller]
fn maybe_queue_event(&self, event: Event<HandlePendingUserEvents>) {
// Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.)
// result in an event being queued, and applied at a later point.
//
// However, it is not documented which actions do this, and which ones are done immediately,
// so to make sure that we don't encounter re-entrancy issues, we first check if we're
// currently handling another event, and if we are, we queue the event instead.
if !self.ivars().event_handler.in_use() {
self.handle_event(event);
} else {
tracing::debug!(?event, "had to queue event since another is currently being handled");
let this = self.retain();
self.ivars().run_loop.queue_closure(move || this.handle_event(event));
}
}
#[track_caller]
fn handle_event(&self, event: Event<HandlePendingUserEvents>) {
self.ivars().event_handler.handle_event(event, &ActiveEventLoop::new_root(self.retain()))
}
@@ -381,6 +348,49 @@ impl ApplicationDelegate {
self.handle_event(Event::UserEvent(HandlePendingUserEvents));
let events = mem::take(&mut *self.ivars().pending_events.borrow_mut());
for event in events {
match event {
QueuedEvent::WindowEvent(window_id, event) => {
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event,
});
},
QueuedEvent::DeviceEvent(event) => {
self.handle_event(Event::DeviceEvent { device_id: DEVICE_ID, event });
},
QueuedEvent::ScaleFactorChanged { window, suggested_size, scale_factor } => {
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let scale_factor_changed_event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
};
self.handle_event(scale_factor_changed_event);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
if physical_size != suggested_size {
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
let resized_event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Resized(physical_size),
};
self.handle_event(resized_event);
},
}
}
let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut());
for window_id in redraw {
self.handle_event(Event::WindowEvent {
@@ -394,7 +404,6 @@ impl ApplicationDelegate {
if self.exiting() {
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
notify_windows_of_exit(&app);
}
if self.ivars().stop_before_wait.get() {
@@ -412,6 +421,17 @@ impl ApplicationDelegate {
}
}
#[derive(Debug)]
pub(crate) enum QueuedEvent {
WindowEvent(WindowId, WindowEvent),
DeviceEvent(DeviceEvent),
ScaleFactorChanged {
window: Retained<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
},
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;

View File

@@ -4,7 +4,7 @@ use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::{msg_send, msg_send_id, sel, ClassType};
use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
@@ -66,7 +66,7 @@ pub(crate) fn default_cursor() -> Retained<NSCursor> {
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
let cls = NSCursor::class();
if msg_send![cls, respondsToSelector: sel] {
if cls.responds_to(sel) {
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
Some(cursor)
} else {

View File

@@ -92,12 +92,17 @@ fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
/// Create `KeyEvent` for the given `NSEvent`.
///
/// This function shouldn't be called when the IME input is in process.
pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent {
pub(crate) fn create_key_event(
ns_event: &NSEvent,
is_press: bool,
is_repeat: bool,
key_override: Option<PhysicalKey>,
) -> KeyEvent {
use ElementState::{Pressed, Released};
let state = if is_press { Pressed } else { Released };
let scancode = unsafe { ns_event.keyCode() };
let mut physical_key = scancode_to_physicalkey(scancode as u32);
let mut physical_key = key_override.unwrap_or_else(|| scancode_to_physicalkey(scancode as u32));
// NOTE: The logical key should heed both SHIFT and ALT if possible.
// For instance:
@@ -106,15 +111,20 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
// * Pressing CTRL SHIFT A: logical key should also be "A"
// This is not easy to tease out of `NSEvent`, but we do our best.
let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
let text_with_all_modifiers = if characters.is_empty() {
let text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
None
} else {
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
let characters =
unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
if characters.is_empty() {
None
} else {
if matches!(physical_key, PhysicalKey::Unidentified(_)) {
// The key may be one of the funky function keys
physical_key = extra_function_key_to_code(scancode, &characters);
}
Some(SmolStr::new(characters))
}
Some(SmolStr::new(characters))
};
let key_from_code = code_to_key(physical_key, scancode);

View File

@@ -1,7 +1,7 @@
use std::cell::RefCell;
use std::{fmt, mem};
use super::app_state::HandlePendingUserEvents;
use super::app_delegate::HandlePendingUserEvents;
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
@@ -16,7 +16,7 @@ impl fmt::Debug for EventHandlerData {
}
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub(crate) struct EventHandler {
/// This can be in the following states:
/// - Not registered by the event loop (None).
@@ -26,10 +26,6 @@ pub(crate) struct EventHandler {
}
impl EventHandler {
pub(crate) const fn new() -> Self {
Self { inner: RefCell::new(None) }
}
/// Set the event loop handler for the duration of the given closure.
///
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates

View File

@@ -16,15 +16,16 @@ use core_foundation::runloop::{
};
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::ProtocolObject;
use objc2::sel;
use objc2::{msg_send_id, ClassType};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
use super::app::override_send_event;
use super::app_state::{ApplicationDelegate, HandlePendingUserEvents};
use super::app::WinitApplication;
use super::app_delegate::{ApplicationDelegate, HandlePendingUserEvents};
use super::event::dummy_event;
use super::monitor::{self, MonitorHandle};
use super::observer::setup_control_flow_observers;
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::{
@@ -33,7 +34,7 @@ use crate::event_loop::{
use crate::platform::macos::ActivationPolicy;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::cursor::CustomCursor;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource};
#[derive(Default)]
pub struct PanicInfo {
@@ -106,17 +107,6 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
@@ -166,17 +156,28 @@ impl ActiveEventLoop {
}
}
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootWindowTarget),
fn map_user_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
receiver: Rc<mpsc::Receiver<T>>,
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget) {
move |event, window_target| match event.map_nonuser_event() {
Ok(event) => (handler)(event, window_target),
Err(_) => {
) -> impl FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget) + '_ {
move |event, window_target| match event {
Event::NewEvents(cause) => app.new_events(window_target, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(window_target, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(window_target, device_id, event)
},
Event::UserEvent(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
app.user_event(window_target, event);
}
},
Event::Suspended => app.suspended(window_target),
Event::Resumed => app.resumed(window_target),
Event::AboutToWait => app.about_to_wait(window_target),
Event::LoopExiting => app.exiting(window_target),
Event::MemoryWarning => app.memory_warning(window_target),
}
}
@@ -202,14 +203,18 @@ pub struct EventLoop<T: 'static> {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) activation_policy: Option<ActivationPolicy>,
pub(crate) activation_policy: ActivationPolicy,
pub(crate) default_menu: bool,
pub(crate) activate_ignoring_other_apps: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
fn default() -> Self {
Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true }
Self {
activation_policy: Default::default(), // Regular
default_menu: true,
activate_ignoring_other_apps: true,
}
}
}
@@ -220,14 +225,20 @@ impl<T> EventLoop<T> {
let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!");
// Initialize the application (if it has not already been).
let app = NSApplication::sharedApplication(mtm);
let app: Retained<NSApplication> =
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
if !app.is_kind_of::<WinitApplication>() {
panic!(
"`winit` requires control over the principal class. You must create the event \
loop before other parts of your application initialize NSApplication"
);
}
let activation_policy = match attributes.activation_policy {
None => None,
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory),
Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited),
ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular,
ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory,
ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
};
let delegate = ApplicationDelegate::new(
mtm,
@@ -240,11 +251,8 @@ impl<T> EventLoop<T> {
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
});
// Override `sendEvent:` on the application to forward to our application state.
override_send_event(&app);
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
setup_control_flow_observers(Rc::downgrade(&panic_info));
let (sender, receiver) = mpsc::channel();
Ok(EventLoop {
@@ -264,22 +272,19 @@ impl<T> EventLoop<T> {
&self.window_target
}
pub fn run<F>(mut self, handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootWindowTarget),
{
self.run_on_demand(handler)
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
// time and so a layered implementation would end up using a lot of CPU due to
// redundant wake ups.
pub fn run_on_demand<F>(&mut self, handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootWindowTarget),
{
let handler = map_user_event(handler, self.receiver.clone());
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
let handler = map_user_event(app, self.receiver.clone());
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| {
@@ -314,11 +319,12 @@ impl<T> EventLoop<T> {
Ok(())
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, handler: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootWindowTarget),
{
let handler = map_user_event(handler, self.receiver.clone());
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
let handler = map_user_event(app, self.receiver.clone());
self.delegate.set_event_handler(handler, || {
autoreleasepool(|_| {
@@ -417,22 +423,6 @@ pub(super) fn stop_app_immediately(app: &NSApplication) {
});
}
/// Tell all windows to close.
///
/// This will synchronously trigger `WindowEvent::Destroyed` within
/// `windowWillClose:`, giving the application one last chance to handle
/// those events. It doesn't matter if the user also ends up closing the
/// windows in `Window`'s `Drop` impl, once a window has been closed once, it
/// stays closed.
///
/// This ensures that no windows linger on after the event loop has exited,
/// see <https://github.com/rust-windowing/winit/issues/4135>.
pub(super) fn notify_windows_of_exit(app: &NSApplication) {
for window in app.windows() {
window.close();
}
}
/// Catches panics that happen inside `f` and when a panic
/// happens, stops the `sharedApplication`
#[inline]
@@ -502,7 +492,8 @@ impl<T> EventLoopProxy<T> {
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);

View File

@@ -68,8 +68,6 @@ pub type CGDisplayModeRef = *mut c_void;
#[link(name = "ApplicationServices", kind = "framework")]
extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
pub fn CGDisplayGetDisplayIDFromUUID(uuid: CFUUIDRef) -> CGDirectDisplayID;
}
#[link(name = "CoreGraphics", kind = "framework")]

View File

@@ -2,7 +2,7 @@
mod util;
mod app;
mod app_state;
mod app_delegate;
mod cursor;
mod event;
mod event_handler;
@@ -37,7 +37,7 @@ pub(crate) use crate::platform_impl::Fullscreen;
pub struct DeviceId;
impl DeviceId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId
}
}

View File

@@ -6,7 +6,6 @@ use std::fmt;
use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex};
use core_foundation::base::{CFRelease, TCFType};
use core_foundation::string::CFString;
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUID};
use core_graphics::display::{
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
};
@@ -14,7 +13,6 @@ use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2_app_kit::NSScreen;
use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect};
use tracing::warn;
use super::ffi;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
@@ -99,71 +97,18 @@ impl VideoModeHandle {
}
}
/// `CGDirectDisplayID` is documented as:
/// > a framebuffer, a color correction (gamma) table, and possibly an attached monitor.
///
/// That is, it doesn't actually represent the monitor itself. Instead, we use the UUID of the
/// monitor, as retrieved from `CGDisplayCreateUUIDFromDisplayID` (this makes the monitor ID stable,
/// even across reboots and video mode changes).
///
/// NOTE: I'd be perfectly valid to store `[u8; 16]` in here instead, we only store `CFUUID` to
/// avoid having to re-create it when we want to fetch the display ID.
#[derive(Clone)]
pub struct MonitorHandle(CFUUID);
// SAFETY: CFUUID is immutable.
// FIXME(madsmtm): Upstream this into `objc2-core-foundation`.
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
type MonitorUuid = [u8; 16];
impl MonitorHandle {
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle.
fn uuid(&self) -> MonitorUuid {
let uuid = unsafe { CFUUIDGetUUIDBytes(self.0.as_concrete_TypeRef()) };
MonitorUuid::from([
uuid.byte0,
uuid.byte1,
uuid.byte2,
uuid.byte3,
uuid.byte4,
uuid.byte5,
uuid.byte6,
uuid.byte7,
uuid.byte8,
uuid.byte9,
uuid.byte10,
uuid.byte11,
uuid.byte12,
uuid.byte13,
uuid.byte14,
uuid.byte15,
])
}
fn display_id(&self) -> CGDirectDisplayID {
unsafe { ffi::CGDisplayGetDisplayIDFromUUID(self.0.as_concrete_TypeRef()) }
}
#[track_caller]
pub(crate) fn new(display_id: CGDirectDisplayID) -> Option<Self> {
// kCGNullDirectDisplay
if display_id == 0 {
// `CGDisplayCreateUUIDFromDisplayID` checks kCGNullDirectDisplay internally.
warn!("constructing monitor from invalid display ID 0; falling back to main monitor");
}
let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(display_id) };
if ptr.is_null() {
return None;
}
Some(Self(unsafe { CFUUID::wrap_under_create_rule(ptr) }))
}
}
pub struct MonitorHandle(CGDirectDisplayID);
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
// unique identifier that persists even across system reboots
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
self.uuid() == other.uuid()
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
}
}
}
@@ -177,13 +122,18 @@ impl PartialOrd for MonitorHandle {
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.uuid().cmp(&other.uuid())
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
}
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.uuid().hash(state);
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
}
}
}
@@ -191,8 +141,7 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
if let Ok(displays) = CGDisplay::active_displays() {
let mut monitors = VecDeque::with_capacity(displays.len());
for display in displays {
// Display ID just fetched from `CGGetActiveDisplayList`, should be fine to unwrap.
monitors.push_back(MonitorHandle::new(display).expect("invalid display ID"));
monitors.push_back(MonitorHandle(display));
}
monitors
} else {
@@ -201,8 +150,7 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
}
pub fn primary_monitor() -> MonitorHandle {
// Display ID just fetched from `CGMainDisplayID`, should be fine to unwrap.
MonitorHandle::new(CGDisplay::main().id).expect("invalid display ID")
MonitorHandle(CGDisplay::main().id)
}
impl fmt::Debug for MonitorHandle {
@@ -219,20 +167,26 @@ impl fmt::Debug for MonitorHandle {
}
impl MonitorHandle {
pub fn new(id: CGDirectDisplayID) -> Self {
MonitorHandle(id)
}
// TODO: Be smarter about this:
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
pub fn name(&self) -> Option<String> {
let screen_num = CGDisplay::new(self.display_id()).model_number();
let MonitorHandle(display_id) = *self;
let screen_num = CGDisplay::new(display_id).model_number();
Some(format!("Monitor #{screen_num}"))
}
#[inline]
pub fn native_identifier(&self) -> u32 {
self.display_id()
self.0
}
pub fn size(&self) -> PhysicalSize<u32> {
let display = CGDisplay::new(self.display_id());
let MonitorHandle(display_id) = *self;
let display = CGDisplay::new(display_id);
let height = display.pixels_high();
let width = display.pixels_wide();
PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.scale_factor())
@@ -259,15 +213,14 @@ impl MonitorHandle {
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
unsafe {
let current_display_mode =
NativeDisplayMode(CGDisplayCopyDisplayMode(self.display_id()) as _);
let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _);
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0);
if refresh_rate > 0.0 {
return Some((refresh_rate * 1000.0).round() as u32);
}
let mut display_link = std::ptr::null_mut();
if ffi::CVDisplayLinkCreateWithCGDisplay(self.display_id(), &mut display_link)
if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link)
!= ffi::kCVReturnSuccess
{
return None;
@@ -290,34 +243,27 @@ impl MonitorHandle {
unsafe {
let modes = {
let array = ffi::CGDisplayCopyAllDisplayModes(self.display_id(), std::ptr::null());
if array.is_null() {
// Occasionally, certain CalDigit Thunderbolt Hubs report a spurious monitor
// during sleep/wake/cycling monitors. It tends to have null
// or 1 video mode only. See <https://github.com/bevyengine/bevy/issues/17827>.
warn!(monitor = ?self, "failed to get a list of display modes");
Vec::new()
} else {
let array_count = CFArrayGetCount(array);
let modes: Vec<_> = (0..array_count)
.map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
})
.collect();
CFRelease(array as *const _);
modes
}
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
assert!(!array.is_null(), "failed to get list of display modes");
let array_count = CFArrayGetCount(array);
let modes: Vec<_> = (0..array_count)
.map(move |i| {
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
ffi::CGDisplayModeRetain(mode);
mode
})
.collect();
CFRelease(array as *const _);
modes
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode);
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0.0 {
(cg_refresh_rate_hertz * 1000.0).round() as u32
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 {
(cg_refresh_rate_hertz * 1000) as u32
} else {
refresh_rate_millihertz
};
@@ -350,17 +296,13 @@ impl MonitorHandle {
}
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
let uuid = self.uuid();
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
NSScreen::screens(mtm).into_iter().find(|screen| {
let other_native_id = get_display_id(screen);
if let Some(other) = MonitorHandle::new(other_native_id) {
uuid == other.uuid()
} else {
// Display ID was just fetched from live NSScreen, but can still result in `None`
// with certain Thunderbolt docked monitors.
warn!(other_native_id, "comparing against screen with invalid display ID");
false
}
let other_uuid = unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
};
uuid == other_uuid
})
}
}

View File

@@ -1,28 +1,21 @@
//! Utilities for working with `CFRunLoop`.
//!
//! See Apple's documentation on Run Loops for details:
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
use std::cell::Cell;
use std::ffi::c_void;
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::Weak;
use std::time::Instant;
use block2::Block;
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease};
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopExit,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
};
use objc2_foundation::MainThreadMarker;
use tracing::error;
use super::app_state::ApplicationDelegate;
use super::app_delegate::ApplicationDelegate;
use super::event_loop::{stop_app_on_panic, PanicInfo};
use super::ffi;
@@ -91,20 +84,10 @@ extern "C" fn control_flow_end_handler(
}
}
#[derive(Debug)]
pub struct RunLoop(CFRunLoopRef);
impl Default for RunLoop {
fn default() -> Self {
Self(ptr::null_mut())
}
}
impl RunLoop {
pub fn main(mtm: MainThreadMarker) -> Self {
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
let _ = mtm;
pub unsafe fn get() -> Self {
RunLoop(unsafe { CFRunLoopGetMain() })
}
@@ -131,79 +114,9 @@ impl RunLoop {
};
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
/// event sources are processed.
///
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
///
/// # Implementation
///
/// This queuing could be implemented in the following several ways with subtle differences in
/// timing. This list is sorted in rough order in which they are run:
///
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
///
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
///
/// a. `atStart = true`.
///
/// b. `atStart = false`.
///
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
/// respect the ordering that runloop events have.
///
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
/// want the event to be queued in a way that preserves the order the events originally arrived
/// in.
///
/// As an example, let's assume that we receive two events from the user, a mouse click which we
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
/// put the event at the very front of the queue, to be handled as soon as possible after
/// handling whatever event it's currently handling.
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
extern "C" {
fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block<dyn Fn()>);
}
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
if let Some(closure) = closure.take() {
closure()
} else {
error!("tried to execute queued closure on main thread twice");
}
});
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
// - `NSConnectionReplyMode`: TODO.
//
// We only want to run event handlers in the default mode, as we support running a blocking
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
// resizing such panel window enters the event tracking run loop mode, so we can't directly
// trigger events inside that mode either.
//
// Any events that are queued while running a modal or when live-resizing will instead wait,
// and be delivered to the application afterwards.
//
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef };
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
unsafe { CFRunLoopPerformBlock(self.0, mode, &block) }
}
}
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
let run_loop = RunLoop::main(mtm);
pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
unsafe {
let mut context = CFRunLoopObserverContext {
info: Weak::into_raw(panic_info) as *mut _,
@@ -212,15 +125,16 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<Pani
release: None,
copyDescription: None,
};
let run_loop = RunLoop::get();
run_loop.add_observer(
kCFRunLoopAfterWaiting,
CFIndex::MIN,
CFIndex::min_value(),
control_flow_begin_handler,
&mut context as *mut _,
);
run_loop.add_observer(
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
CFIndex::MAX,
CFIndex::max_value(),
control_flow_end_handler,
&mut context as *mut _,
);
@@ -251,8 +165,8 @@ impl Drop for EventLoopWaker {
}
}
impl EventLoopWaker {
pub(crate) fn new() -> Self {
impl Default for EventLoopWaker {
fn default() -> EventLoopWaker {
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
@@ -260,7 +174,7 @@ impl EventLoopWaker {
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
f64::MAX,
std::f64::MAX,
0.000_000_1,
0,
0,
@@ -268,21 +182,23 @@ impl EventLoopWaker {
ptr::null_mut(),
);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
Self { timer, start_instant: Instant::now(), next_fire_date: None }
EventLoopWaker { timer, start_instant: Instant::now(), next_fire_date: None }
}
}
}
impl EventLoopWaker {
pub fn stop(&mut self) {
if self.next_fire_date.is_some() {
self.next_fire_date = None;
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
}
}
pub fn start(&mut self) {
if self.next_fire_date != Some(self.start_instant) {
self.next_fire_date = Some(self.start_instant);
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
}
}

View File

@@ -16,17 +16,17 @@ use objc2_foundation::{
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use super::app_state::ApplicationDelegate;
use super::app_delegate::ApplicationDelegate;
use super::cursor::{default_cursor, invisible_cursor};
use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
scancode_to_physicalkey, KeyEventExtra,
scancode_to_physicalkey,
};
use super::window::WinitWindow;
use super::DEVICE_ID;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, TouchPhase,
DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase,
WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
@@ -270,20 +270,19 @@ declare_class!(
fn set_marked_text(
&self,
string: &NSObject,
selected_range: NSRange,
_selected_range: NSRange,
_replacement_range: NSRange,
) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
let (marked_text, preedit_string) = if string.is_kind_of::<NSAttributedString>() {
let string: *const NSObject = string;
let string: *const NSAttributedString = string.cast();
let string = unsafe { &*string };
(
NSMutableAttributedString::from_attributed_nsstring(string),
string.string(),
string.string().to_string(),
)
} else {
let string: *const NSObject = string;
@@ -291,7 +290,7 @@ declare_class!(
let string = unsafe { &*string };
(
NSMutableAttributedString::from_nsstring(string),
string.copy(),
string.to_string(),
)
};
@@ -311,21 +310,16 @@ declare_class!(
self.ivars().ime_state.set(ImeState::Ground);
}
let cursor_range = if string.is_empty() {
// An empty string basically means that there's no preedit, so indicate that by
// sending a `None` cursor range.
// Empty string basically means that there's no preedit, so indicate that by sending
// `None` cursor range.
let cursor_range = if preedit_string.is_empty() {
None
} else {
// Convert the selected range from UTF-16 indices to UTF-8 indices.
let sub_string_a = unsafe { string.substringToIndex(selected_range.location) };
let sub_string_b = unsafe { string.substringToIndex(selected_range.end()) };
let lowerbound_utf8 = sub_string_a.len();
let upperbound_utf8 = sub_string_b.len();
Some((lowerbound_utf8, upperbound_utf8))
Some((preedit_string.len(), preedit_string.len()))
};
// 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(preedit_string, cursor_range)));
}
#[method(unmarkText)]
@@ -385,7 +379,6 @@ declare_class!(
#[method(insertText:replacementRange:)]
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("insertText:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
@@ -399,7 +392,7 @@ declare_class!(
unsafe { &*string }.to_string()
};
let is_control = string.chars().next().is_some_and(|c| c.is_control());
let is_control = string.chars().next().map_or(false, |c| c.is_control());
// Commit only if we have marked text.
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control {
@@ -482,7 +475,7 @@ declare_class!(
};
if !had_ime_input || self.ivars().forward_key_to_app.get() {
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() });
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: key_event,
@@ -505,7 +498,7 @@ declare_class!(
) {
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: create_key_event(&event, false, false),
event: create_key_event(&event, false, false, None),
is_synthetic: false,
});
}
@@ -552,7 +545,7 @@ declare_class!(
.expect("could not find current event");
self.update_modifiers(&event, false);
let event = create_key_event(&event, true, unsafe { event.isARepeat() });
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
@@ -686,7 +679,7 @@ declare_class!(
self.update_modifiers(event, false);
self.ivars().app_delegate.maybe_queue_device_event(DeviceEvent::MouseWheel { delta });
self.queue_device_event(DeviceEvent::MouseWheel { delta });
self.queue_event(WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta,
@@ -830,7 +823,11 @@ impl WinitView {
}
fn queue_event(&self, event: WindowEvent) {
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
self.ivars().app_delegate.queue_window_event(self.window().id(), event);
}
fn queue_device_event(&self, event: DeviceEvent) {
self.ivars().app_delegate.queue_device_event(event);
}
fn scale_factor(&self) -> f64 {
@@ -933,36 +930,22 @@ impl WinitView {
let scancode = unsafe { ns_event.keyCode() };
let physical_key = scancode_to_physicalkey(scancode as u32);
let logical_key = code_to_key(physical_key, scancode);
// We'll correct the `is_press` later.
let mut event = create_key_event(ns_event, false, false, Some(physical_key));
let key = code_to_key(physical_key, scancode);
// Ignore processing of unknown modifiers because we can't determine whether
// it was pressed or release reliably.
//
// Furthermore, sometimes normal keys are reported inside flagsChanged:, such as
// when holding Caps Lock while pressing another key, see:
// https://github.com/alacritty/alacritty/issues/8268
let Some(event_modifier) = key_to_modifier(&logical_key) else {
let Some(event_modifier) = key_to_modifier(&key) else {
break 'send_event;
};
let mut event = KeyEvent {
location: code_to_location(physical_key),
logical_key: logical_key.clone(),
physical_key,
repeat: false,
// We'll correct this later.
state: Pressed,
text: None,
platform_specific: KeyEventExtra {
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
},
};
event.physical_key = physical_key;
event.logical_key = key.clone();
event.location = code_to_location(physical_key);
let location_mask = ModLocationMask::from_location(event.location);
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();
let phys_mod =
phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty());
let phys_mod = phys_mod_state.entry(key).or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);

View File

@@ -74,7 +74,7 @@ impl Window {
pub struct WindowId(pub usize);
impl WindowId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
Self(0)
}
}

View File

@@ -1,9 +1,6 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::ffi::c_void;
use std::ptr;
use std::sync::{Arc, Mutex};
use core_graphics::display::{CGDisplay, CGPoint};
use monitor::VideoModeHandle;
@@ -11,31 +8,28 @@ use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate,
NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode,
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility,
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication,
NSApplicationPresentationOptions, NSBackingStoreType, NSDraggingDestination,
NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSView,
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
NSWindowTabbingMode, NSWindowTitleVisibility,
};
use objc2_foundation::{
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey,
NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSObject,
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
NSRect, NSSize, NSString,
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDistributedNotificationCenter,
NSObject, NSObjectNSDelayedPerforming, NSObjectNSThreadPerformAdditions, NSObjectProtocol,
NSPoint, NSRect, NSSize, NSString,
};
use tracing::{trace, warn};
use super::app_state::ApplicationDelegate;
use super::app_delegate::ApplicationDelegate;
use super::cursor::cursor_from_icon;
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
use super::observer::RunLoop;
use super::view::WinitView;
use super::window::WinitWindow;
use super::{ffi, Fullscreen, MonitorHandle, OsError, WindowId};
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{InnerSizeWriter, WindowEvent};
use crate::event::WindowEvent;
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::window::{
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@@ -55,7 +49,6 @@ pub struct PlatformSpecificWindowAttributes {
pub accepts_first_mouse: bool,
pub tabbing_identifier: Option<String>,
pub option_as_alt: OptionAsAlt,
pub borderless_game: bool,
}
impl Default for PlatformSpecificWindowAttributes {
@@ -73,7 +66,6 @@ impl Default for PlatformSpecificWindowAttributes {
accepts_first_mouse: true,
tabbing_identifier: None,
option_as_alt: Default::default(),
borderless_game: false,
}
}
}
@@ -85,10 +77,12 @@ pub(crate) struct State {
window: Retained<WinitWindow>,
current_theme: Cell<Option<Theme>>,
// During `windowDidResize`, we use this to only send Moved if the position changed.
//
// This is expressed in desktop coordinates, and flipped to match Winit's coordinate system.
previous_position: Cell<NSPoint>,
// This is expressed in native screen coordinates.
previous_position: Cell<Option<NSPoint>>,
// Used to prevent redundant events.
previous_scale_factor: Cell<f64>,
@@ -122,7 +116,6 @@ pub(crate) struct State {
standard_frame: Cell<Option<NSRect>>,
is_simple_fullscreen: Cell<bool>,
saved_style: Cell<Option<NSWindowStyleMask>>,
is_borderless_game: Cell<bool>,
}
declare_class!(
@@ -191,17 +184,7 @@ declare_class!(
#[method(windowDidChangeBackingProperties:)]
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
trace_scope!("windowDidChangeBackingProperties:");
let scale_factor = self.scale_factor();
if scale_factor == self.ivars().previous_scale_factor.get() {
return;
};
self.ivars().previous_scale_factor.set(scale_factor);
let mtm = MainThreadMarker::from(self);
let this = self.retain();
RunLoop::main(mtm).queue_closure(move || {
this.handle_scale_factor_changed(scale_factor);
});
self.queue_static_scale_factor_changed_event();
}
#[method(windowDidBecomeKey:)]
@@ -424,66 +407,32 @@ declare_class!(
}
}
// Key-Value Observing
unsafe impl WindowDelegate {
#[method(observeValueForKeyPath:ofObject:change:context:)]
fn observe_value(
&self,
key_path: Option<&NSString>,
_object: Option<&AnyObject>,
change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
_context: *mut c_void,
) {
trace_scope!("observeValueForKeyPath:ofObject:change:context:");
// NOTE: We don't _really_ need to check the key path, as there should only be one, but
// in the future we might want to observe other key paths.
if key_path == Some(ns_string!("effectiveAppearance")) {
let change = change.expect("requested a change dictionary in `addObserver`, but none was provided");
let old = change.get(unsafe { NSKeyValueChangeOldKey }).expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
let new = change.get(unsafe { NSKeyValueChangeNewKey }).expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
// Observe theme change
#[method(effectiveAppearanceDidChange:)]
fn effective_appearance_did_change(&self, sender: Option<&AnyObject>) {
trace_scope!("effectiveAppearanceDidChange:");
unsafe {
self.performSelectorOnMainThread_withObject_waitUntilDone(
sel!(effectiveAppearanceDidChangedOnMainThread:),
sender,
false,
)
};
}
// SAFETY: The value of `effectiveAppearance` is `NSAppearance`
let old: *const AnyObject = old;
let old: *const NSAppearance = old.cast();
let old: &NSAppearance = unsafe { &*old };
let new: *const AnyObject = new;
let new: *const NSAppearance = new.cast();
let new: &NSAppearance = unsafe { &*new };
trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed");
// Ignore the change if the window's theme is customized by the user (since in that
// case the `effectiveAppearance` is only emitted upon said customization, and then
// it's triggered directly by a user action, and we don't want to emit the event).
if unsafe { self.window().appearance() }.is_some() {
return;
}
let old = appearance_to_theme(old);
let new = appearance_to_theme(new);
// Check that the theme changed in Winit's terms (the theme might have changed on
// other parameters, such as level of contrast, but the event should not be emitted
// in those cases).
if old == new {
return;
}
self.queue_event(WindowEvent::ThemeChanged(new));
} else {
panic!("unknown observed keypath {key_path:?}");
#[method(effectiveAppearanceDidChangedOnMainThread:)]
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self);
let theme = get_ns_theme(mtm);
let old_theme = self.ivars().current_theme.replace(Some(theme));
if old_theme != Some(theme) {
self.queue_event(WindowEvent::ThemeChanged(theme));
}
}
}
);
impl Drop for WindowDelegate {
fn drop(&mut self) {
unsafe {
self.window().removeObserver_forKeyPath(self, ns_string!("effectiveAppearance"));
}
}
}
fn new_window(
app_delegate: &ApplicationDelegate,
attrs: &WindowAttributes,
@@ -652,8 +601,6 @@ fn new_window(
if attrs.transparent {
window.setOpaque(false);
// See `set_transparent` for details on why we do this.
window.setBackgroundColor(unsafe { Some(&NSColor::clearColor()) });
}
// register for drag and drop operations.
@@ -707,14 +654,16 @@ impl WindowDelegate {
let scale_factor = window.backingScaleFactor() as _;
if let Some(appearance) = theme_to_appearance(attrs.preferred_theme) {
unsafe { window.setAppearance(Some(&appearance)) };
}
let current_theme = match attrs.preferred_theme {
Some(theme) => Some(theme),
None => Some(get_ns_theme(mtm)),
};
let delegate = mtm.alloc().set_ivars(State {
app_delegate: app_delegate.retain(),
window: window.retain(),
previous_position: Cell::new(flip_window_screen_coordinates(window.frame())),
current_theme: Cell::new(current_theme),
previous_position: Cell::new(None),
previous_scale_factor: Cell::new(scale_factor),
resize_increments: Cell::new(resize_increments),
decorations: Cell::new(attrs.decorations),
@@ -728,28 +677,22 @@ impl WindowDelegate {
standard_frame: Cell::new(None),
is_simple_fullscreen: Cell::new(false),
saved_style: Cell::new(None),
is_borderless_game: Cell::new(attrs.platform_specific.borderless_game),
});
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
if scale_factor != 1.0 {
let delegate = delegate.clone();
RunLoop::main(mtm).queue_closure(move || {
delegate.handle_scale_factor_changed(scale_factor);
});
delegate.queue_static_scale_factor_changed_event();
}
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
// Listen for theme change event.
//
// SAFETY: The observer is un-registered in the `Drop` of the delegate.
// Enable theme change event
let notification_center = unsafe { NSDistributedNotificationCenter::defaultCenter() };
unsafe {
window.addObserver_forKeyPath_options_context(
notification_center.addObserver_selector_name_object(
&delegate,
ns_string!("effectiveAppearance"),
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
| NSKeyValueObservingOptions::NSKeyValueObservingOptionOld,
ptr::null_mut(),
sel!(effectiveAppearanceDidChange:),
Some(ns_string!("AppleInterfaceThemeChangedNotification")),
None,
)
};
@@ -811,40 +754,34 @@ impl WindowDelegate {
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
self.ivars().app_delegate.queue_window_event(self.window().id(), event);
}
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
let app_delegate = &self.ivars().app_delegate;
let window = self.window();
fn queue_static_scale_factor_changed_event(&self) {
let scale_factor = self.scale_factor();
if scale_factor == self.ivars().previous_scale_factor.get() {
return;
};
let content_size = window.contentRectForFrameRect(window.frame()).size;
self.ivars().previous_scale_factor.set(scale_factor);
let content_size = self.window().contentRectForFrameRect(self.window().frame()).size;
let content_size = LogicalSize::new(content_size.width, content_size.height);
let suggested_size = content_size.to_physical(scale_factor);
let new_inner_size = Arc::new(Mutex::new(suggested_size));
app_delegate.handle_window_event(window.id(), WindowEvent::ScaleFactorChanged {
self.ivars().app_delegate.queue_static_scale_factor_changed_event(
self.window().retain(),
content_size.to_physical(scale_factor),
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
});
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
if physical_size != suggested_size {
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
app_delegate.handle_window_event(window.id(), WindowEvent::Resized(physical_size));
);
}
fn emit_move_event(&self) {
let position = flip_window_screen_coordinates(self.window().frame());
if self.ivars().previous_position.get() == position {
let frame = self.window().frame();
if self.ivars().previous_position.get() == Some(frame.origin) {
return;
}
self.ivars().previous_position.set(position);
self.ivars().previous_position.set(Some(frame.origin));
let position = flip_window_screen_coordinates(frame);
let position =
LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor());
self.queue_event(WindowEvent::Moved(position));
@@ -862,23 +799,7 @@ impl WindowDelegate {
}
pub fn set_transparent(&self, transparent: bool) {
// This is just a hint for Quartz, it doesn't actually speculate with window alpha.
// Providing a wrong value here could result in visual artifacts, when the window is
// transparent.
self.window().setOpaque(!transparent);
// AppKit draws the window with a background color by default, which is usually really
// nice, but gets in the way when we want to allow the contents of the window to be
// transparent, as in that case, the transparent contents will just be drawn on top of
// the background color. As such, to allow the window to be transparent, we must also set
// the background color to one with an empty alpha channel.
let color = if transparent {
unsafe { NSColor::clearColor() }
} else {
unsafe { NSColor::windowBackgroundColor() }
};
self.window().setBackgroundColor(Some(&color));
self.window().setOpaque(!transparent)
}
pub fn set_blur(&self, blur: bool) {
@@ -977,8 +898,8 @@ impl WindowDelegate {
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
let dimensions = dimensions.unwrap_or(Size::Logical(LogicalSize {
width: f32::MAX as f64,
height: f32::MAX as f64,
width: std::f32::MAX as f64,
height: std::f32::MAX as f64,
}));
let scale_factor = self.scale_factor();
let max_size = dimensions.to_logical::<CGFloat>(scale_factor);
@@ -1163,8 +1084,7 @@ impl WindowDelegate {
#[inline]
pub fn drag_window(&self) -> Result<(), ExternalError> {
let mtm = MainThreadMarker::from(self);
let event =
NSApplication::sharedApplication(mtm).currentEvent().ok_or(ExternalError::Ignored)?;
let event = NSApplication::sharedApplication(mtm).currentEvent().unwrap();
self.window().performWindowDragWithEvent(&event);
Ok(())
}
@@ -1413,7 +1333,7 @@ impl WindowDelegate {
}
match (old_fullscreen, fullscreen) {
(None, Some(fullscreen)) => {
(None, Some(_)) => {
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
// set a normal style temporarily. The previous state will be
// restored in `WindowDelegate::window_did_exit_fullscreen`.
@@ -1423,17 +1343,6 @@ impl WindowDelegate {
self.set_style_mask(required);
self.ivars().saved_style.set(Some(curr_mask));
}
// In borderless games, we want to disable the dock and menu bar
// by setting the presentation options. We do this here rather than in
// `window:willUseFullScreenPresentationOptions` because for some reason
// the menu bar remains interactable despite being hidden.
if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) {
let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
app.setPresentationOptions(presentation_options);
}
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Borderless(_)), None) => {
@@ -1441,7 +1350,13 @@ impl WindowDelegate {
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Exclusive(ref video_mode)), None) => {
restore_and_release_display(&video_mode.monitor());
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
ffi::kCGErrorSuccess
);
};
toggle_fullscreen(self.window());
},
(Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => {
@@ -1472,7 +1387,13 @@ impl WindowDelegate {
);
app.setPresentationOptions(presentation_options);
restore_and_release_display(&video_mode.monitor());
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
ffi::kCGErrorSuccess
);
};
// Restore the normal window level following the Borderless fullscreen
// `CGShieldingWindowLevel() + 1` hack.
@@ -1592,14 +1513,7 @@ impl WindowDelegate {
// Allow directly accessing the current monitor internally without unwrapping.
pub(crate) fn current_monitor_inner(&self) -> Option<MonitorHandle> {
let display_id = get_display_id(&*self.window().screen()?);
if let Some(monitor) = MonitorHandle::new(display_id) {
Some(monitor)
} else {
// NOTE: Display ID was just fetched from live NSScreen, but can still result in `None`
// with certain Thunderbolt docked monitors.
warn!(display_id, "got screen with invalid display ID");
None
}
Some(MonitorHandle::new(display_id))
}
#[inline]
@@ -1623,7 +1537,7 @@ impl WindowDelegate {
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
let mut window_handle = rwh_04::AppKitHandle::empty();
window_handle.ns_window = self.window() as *const WinitWindow as *mut _;
window_handle.ns_view = Retained::as_ptr(&self.view()) as *mut _;
window_handle.ns_view = Retained::as_ptr(&self.contentView().unwrap()) as *mut _;
rwh_04::RawWindowHandle::AppKit(window_handle)
}
@@ -1661,28 +1575,20 @@ impl WindowDelegate {
}
}
#[inline]
pub fn theme(&self) -> Option<Theme> {
self.ivars().current_theme.get()
}
#[inline]
pub fn has_focus(&self) -> bool {
self.window().isKeyWindow()
}
pub fn theme(&self) -> Option<Theme> {
unsafe { self.window().appearance() }
.map(|appearance| appearance_to_theme(&appearance))
.or_else(|| {
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
}
})
}
pub fn set_theme(&self, theme: Option<Theme>) {
unsafe { self.window().setAppearance(theme_to_appearance(theme).as_deref()) };
let mtm = MainThreadMarker::from(self);
set_ns_theme(theme, mtm);
self.ivars().current_theme.set(theme.or_else(|| Some(get_ns_theme(mtm))));
}
#[inline]
@@ -1703,21 +1609,6 @@ impl WindowDelegate {
}
}
fn restore_and_release_display(monitor: &MonitorHandle) {
let available_monitors = monitor::available_monitors();
if available_monitors.contains(monitor) {
unsafe {
ffi::CGRestorePermanentDisplayConfiguration();
assert_eq!(ffi::CGDisplayRelease(monitor.native_identifier()), ffi::kCGErrorSuccess);
};
} else {
warn!(
monitor = monitor.name(),
"Tried to restore exclusive fullscreen on a monitor that is no longer available"
);
}
}
impl WindowExtMacOS for WindowDelegate {
#[inline]
fn simple_fullscreen(&self) -> bool {
@@ -1753,13 +1644,9 @@ impl WindowExtMacOS for WindowDelegate {
self.ivars().is_simple_fullscreen.set(true);
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
let presentation_options = if self.is_borderless_game() {
NSApplicationPresentationOptions::NSApplicationPresentationHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar
} else {
let presentation_options =
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
};
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar;
app.setPresentationOptions(presentation_options);
// Hide the titlebar
@@ -1773,8 +1660,11 @@ impl WindowExtMacOS for WindowDelegate {
self.toggle_style_mask(NSWindowStyleMask::Miniaturizable, false);
self.toggle_style_mask(NSWindowStyleMask::Resizable, false);
self.window().setMovable(false);
true
} else {
let new_mask = self.saved_style();
self.set_style_mask(new_mask);
self.ivars().is_simple_fullscreen.set(false);
let save_presentation_opts = self.ivars().save_presentation_opts.get();
@@ -1786,10 +1676,9 @@ impl WindowExtMacOS for WindowDelegate {
self.window().setFrame_display(frame, true);
self.window().setMovable(true);
self.set_style_mask(new_mask);
}
true
true
}
}
#[inline]
@@ -1853,52 +1742,39 @@ impl WindowExtMacOS for WindowDelegate {
fn option_as_alt(&self) -> OptionAsAlt {
self.view().option_as_alt()
}
fn set_borderless_game(&self, borderless_game: bool) {
self.ivars().is_borderless_game.set(borderless_game);
}
fn is_borderless_game(&self) -> bool {
self.ivars().is_borderless_game.get()
}
}
const DEFAULT_STANDARD_FRAME: NSRect =
NSRect::new(NSPoint::new(50.0, 50.0), NSSize::new(800.0, 600.0));
fn dark_appearance_name() -> &'static NSString {
// Don't use the static `NSAppearanceNameDarkAqua` to allow linking on macOS < 10.14
ns_string!("NSAppearanceNameDarkAqua")
}
pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
unsafe { NSAppearanceNameAqua.copy() },
dark_appearance_name().copy(),
]));
if let Some(best_match) = best_match {
if *best_match == *dark_appearance_name() {
Theme::Dark
} else {
Theme::Light
}
} else {
warn!(?appearance, "failed to determine the theme of the appearance");
// Default to light in this case
Theme::Light
pub(super) fn get_ns_theme(mtm: MainThreadMarker) -> Theme {
let app = NSApplication::sharedApplication(mtm);
if !app.respondsToSelector(sel!(effectiveAppearance)) {
return Theme::Light;
}
let appearance = app.effectiveAppearance();
let name = appearance
.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
NSString::from_str("NSAppearanceNameAqua"),
NSString::from_str("NSAppearanceNameDarkAqua"),
]))
.unwrap();
match &*name.to_string() {
"NSAppearanceNameDarkAqua" => Theme::Dark,
_ => Theme::Light,
}
}
fn theme_to_appearance(theme: Option<Theme>) -> Option<Retained<NSAppearance>> {
let appearance = match theme? {
Theme::Light => unsafe { NSAppearance::appearanceNamed(NSAppearanceNameAqua) },
Theme::Dark => NSAppearance::appearanceNamed(dark_appearance_name()),
};
if let Some(appearance) = appearance {
Some(appearance)
} else {
warn!(?theme, "could not find appearance for theme");
// Assume system appearance in this case
None
fn set_ns_theme(theme: Option<Theme>, mtm: MainThreadMarker) {
let app = NSApplication::sharedApplication(mtm);
if app.respondsToSelector(sel!(effectiveAppearance)) {
let appearance = theme.map(|t| {
let name = match t {
Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"),
Theme::Light => NSString::from_str("NSAppearanceNameAqua"),
};
NSAppearance::appearanceNamed(&name).unwrap()
});
app.setAppearance(appearance.as_ref().map(|a| a.as_ref()));
}
}

View File

@@ -1,35 +1,27 @@
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoModeHandle as RootVideoModeHandle};
use crate::window::Fullscreen as RootFullscreen;
#[cfg(android_platform)]
mod android;
#[cfg(ios_platform)]
mod ios;
#[cfg(any(x11_platform, wayland_platform))]
mod linux;
#[cfg(macos_platform)]
mod macos;
#[cfg(orbital_platform)]
mod orbital;
#[cfg(web_platform)]
mod web;
#[cfg(windows_platform)]
mod windows;
#[cfg(android_platform)]
use android as platform;
#[cfg(ios_platform)]
use ios as platform;
#[path = "windows/mod.rs"]
mod platform;
#[cfg(any(x11_platform, wayland_platform))]
use linux as platform;
#[path = "linux/mod.rs"]
mod platform;
#[cfg(macos_platform)]
use macos as platform;
#[cfg(orbital_platform)]
use orbital as platform;
#[path = "macos/mod.rs"]
mod platform;
#[cfg(android_platform)]
#[path = "android/mod.rs"]
mod platform;
#[cfg(ios_platform)]
#[path = "ios/mod.rs"]
mod platform;
#[cfg(web_platform)]
use web as platform;
#[cfg(windows_platform)]
use windows as platform;
#[path = "web/mod.rs"]
mod platform;
#[cfg(orbital_platform)]
#[path = "orbital/mod.rs"]
mod platform;
pub use self::platform::*;

View File

@@ -12,6 +12,7 @@ use orbclient::{
};
use smol_str::SmolStr;
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::{self, Ime, Modifiers, StartCause};
use crate::event_loop::{self, ControlFlow, DeviceEvents};
@@ -20,7 +21,7 @@ use crate::keyboard::{
PhysicalKey,
};
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId,
CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId,
};
use super::{
@@ -322,14 +323,13 @@ impl<T: 'static> EventLoop<T> {
})
}
fn process_event<F>(
fn process_event<A: ApplicationHandler<T>>(
window_id: WindowId,
event_option: EventOption,
event_state: &mut EventState,
mut event_handler: F,
) where
F: FnMut(event::Event<T>),
{
window_target: &event_loop::ActiveEventLoop,
app: &mut A,
) {
match event_option {
EventOption::Key(KeyEvent { character, scancode, pressed }) => {
// Convert scancode
@@ -371,125 +371,128 @@ impl<T: 'static> EventLoop<T> {
key_without_modifiers = logical_key.clone();
}
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key,
physical_key,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,
text,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
},
let window_id = RootWindowId(window_id);
let event = event::WindowEvent::KeyboardInput {
device_id: event::DeviceId(DeviceId),
event: event::KeyEvent {
logical_key,
physical_key,
location: KeyLocation::Standard,
state: element_state(pressed),
repeat: false,
text,
platform_specific: KeyEventExtra {
key_without_modifiers,
text_with_all_modifiers,
},
is_synthetic: false,
},
});
is_synthetic: false,
};
app.window_event(window_target, window_id, event);
// If the state of the modifiers has changed, send the event.
if modifiers_before != event_state.keyboard {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::ModifiersChanged(event_state.modifiers()),
})
app.window_event(
window_target,
window_id,
event::WindowEvent::ModifiersChanged(event_state.modifiers()),
);
}
},
EventOption::TextInput(TextInputEvent { character }) => {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Ime(Ime::Preedit("".into(), None)),
});
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Ime(Ime::Commit(character.into())),
});
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Ime(Ime::Preedit("".into(), None)),
);
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Ime(Ime::Commit(character.into())),
);
},
EventOption::Mouse(MouseEvent { x, y }) => {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorMoved {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::CursorMoved {
device_id: event::DeviceId(DeviceId),
position: (x, y).into(),
},
});
);
},
EventOption::MouseRelative(MouseRelativeEvent { dx, dy }) => {
event_handler(event::Event::DeviceEvent {
device_id: event::DeviceId(DeviceId),
event: event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64) },
});
app.device_event(
window_target,
event::DeviceId(DeviceId),
event::DeviceEvent::MouseMotion { delta: (dx as f64, dy as f64) },
);
},
EventOption::Button(ButtonEvent { left, middle, right }) => {
while let Some((button, state)) = event_state.mouse(left, middle, right) {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::MouseInput {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::MouseInput {
device_id: event::DeviceId(DeviceId),
state,
button,
},
});
);
}
},
EventOption::Scroll(ScrollEvent { x, y }) => {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::MouseWheel {
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::MouseWheel {
device_id: event::DeviceId(DeviceId),
delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32),
phase: event::TouchPhase::Moved,
},
});
);
},
EventOption::Quit(QuitEvent {}) => {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CloseRequested,
});
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::CloseRequested,
);
},
EventOption::Focus(FocusEvent { focused }) => {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Focused(focused),
});
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Focused(focused),
);
},
EventOption::Move(MoveEvent { x, y }) => {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Moved((x, y).into()),
});
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Moved((x, y).into()),
);
},
EventOption::Resize(ResizeEvent { width, height }) => {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Resized((width, height).into()),
});
app.window_event(
window_target,
RootWindowId(window_id),
event::WindowEvent::Resized((width, height).into()),
);
// Acknowledge resize after event loop.
event_state.resize_opt = Some((width, height));
},
// TODO: Screen, Clipboard, Drop
EventOption::Hover(HoverEvent { entered }) => {
if entered {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorEntered {
device_id: event::DeviceId(DeviceId),
},
});
let event = if entered {
event::WindowEvent::CursorEntered { device_id: event::DeviceId(DeviceId) }
} else {
event_handler(event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::CursorLeft {
device_id: event::DeviceId(DeviceId),
},
});
}
event::WindowEvent::CursorLeft { device_id: event::DeviceId(DeviceId) }
};
app.window_event(window_target, RootWindowId(window_id), event);
},
other => {
tracing::warn!("unhandled event: {:?}", other);
@@ -497,22 +500,13 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn run<F>(mut self, mut event_handler_inner: F) -> Result<(), EventLoopError>
where
F: FnMut(event::Event<T>, &event_loop::ActiveEventLoop),
{
let mut event_handler =
move |event: event::Event<T>, window_target: &event_loop::ActiveEventLoop| {
event_handler_inner(event, window_target);
};
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
let mut start_cause = StartCause::Init;
loop {
event_handler(event::Event::NewEvents(start_cause), &self.window_target);
app.new_events(&self.window_target, start_cause);
if start_cause == StartCause::Init {
event_handler(event::Event::Resumed, &self.window_target);
app.resumed(&self.window_target);
}
// Handle window creates.
@@ -528,23 +522,15 @@ impl<T: 'static> EventLoop<T> {
self.windows.push((window, EventState::default()));
// Send resize event on create to indicate first size.
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Resized((properties.w, properties.h).into()),
},
&self.window_target,
);
let window_id = RootWindowId(window_id);
// Send resize event on create to indicate first position.
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::Moved((properties.x, properties.y).into()),
},
&self.window_target,
);
// Send resize event on create to indicate first size.
let event = event::WindowEvent::Resized((properties.w, properties.h).into());
app.window_event(&self.window_target, window_id, event);
// Send moved event on create to indicate first position.
let event = event::WindowEvent::Moved((properties.x, properties.y).into());
app.window_event(&self.window_target, window_id, event);
}
// Handle window destroys.
@@ -552,14 +538,8 @@ impl<T: 'static> EventLoop<T> {
let mut destroys = self.window_target.p.destroys.lock().unwrap();
destroys.pop_front()
} {
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(destroy_id),
event: event::WindowEvent::Destroyed,
},
&self.window_target,
);
let window_id = RootWindowId(destroy_id);
app.window_event(&self.window_target, window_id, event::WindowEvent::Destroyed);
self.windows.retain(|(window, _event_state)| window.fd as u64 != destroy_id.fd);
}
@@ -586,7 +566,8 @@ impl<T: 'static> EventLoop<T> {
window_id,
orbital_event.to_option(),
event_state,
|event| event_handler(event, &self.window_target),
&self.window_target,
app,
);
}
@@ -614,7 +595,7 @@ impl<T: 'static> EventLoop<T> {
}
while let Ok(event) = self.user_events_receiver.try_recv() {
event_handler(event::Event::UserEvent(event), &self.window_target);
app.user_event(&self.window_target, event);
}
// To avoid deadlocks the redraws lock is not held during event processing.
@@ -622,16 +603,14 @@ impl<T: 'static> EventLoop<T> {
let mut redraws = self.window_target.p.redraws.lock().unwrap();
redraws.pop_front()
} {
event_handler(
event::Event::WindowEvent {
window_id: RootWindowId(window_id),
event: event::WindowEvent::RedrawRequested,
},
app.window_event(
&self.window_target,
RootWindowId(window_id),
event::WindowEvent::RedrawRequested,
);
}
event_handler(event::Event::AboutToWait, &self.window_target);
app.about_to_wait(&self.window_target);
if self.window_target.p.exiting() {
break;
@@ -695,7 +674,7 @@ impl<T: 'static> EventLoop<T> {
}
}
event_handler(event::Event::LoopExiting, &self.window_target);
app.exiting(&self.window_target);
Ok(())
}
@@ -775,11 +754,6 @@ impl ActiveEventLoop {
rwh_05::RawDisplayHandle::Orbital(rwh_05::OrbitalDisplayHandle::empty())
}
#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}
#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(

View File

@@ -103,7 +103,7 @@ pub struct WindowId {
impl WindowId {
pub const fn dummy() -> Self {
WindowId { fd: u64::MAX }
WindowId { fd: u64::max_value() }
}
}
@@ -154,7 +154,7 @@ impl<'a> WindowProperties<'a> {
}
}
impl fmt::Display for WindowProperties<'_> {
impl<'a> fmt::Display for WindowProperties<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,

View File

@@ -420,7 +420,7 @@ impl Window {
window::ResizeDirection::West => "L",
};
self.window_socket
.write(format!("D,{arg}").as_bytes())
.write(format!("D,{}", arg).as_bytes())
.map_err(|err| error::ExternalError::Os(os_error!(OsError::new(err))))?;
Ok(())
}

View File

@@ -23,7 +23,7 @@ pub struct Sender<T>(Arc<SenderInner<T>>);
struct SenderInner<T> {
// We need to wrap it into a `Mutex` to make it `Sync`. So the sender can't
// be accessed on the main thread, as it could block. Additionally we need
// to wrap `Sender` in an `Arc` to make it cloneable on the main thread without
// to wrap `Sender` in an `Arc` to make it clonable on the main thread without
// having to block.
sender: Mutex<mpsc::Sender<T>>,
shared: Arc<Shared>,

View File

@@ -68,12 +68,7 @@ impl<T> Dispatcher<T> {
// SAFETY: The `transmute` is necessary because `Closure` requires `'static`. This is
// safe because this function won't return until `f` has finished executing. See
// `Self::new()`.
let closure = Closure(unsafe {
std::mem::transmute::<
Box<dyn FnOnce(&T) + Send>,
Box<dyn FnOnce(&T) + Send + 'static>,
>(closure)
});
let closure = Closure(unsafe { std::mem::transmute(closure) });
self.0.send(closure);

View File

@@ -1,5 +1,4 @@
use std::future;
use std::num::NonZeroUsize;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::Poll;
@@ -7,13 +6,13 @@ use std::task::Poll;
use super::super::main_thread::MainThreadMarker;
use super::{AtomicWaker, Wrapper};
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, usize>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, usize>);
struct Handler<T> {
value: T,
handler: fn(&T, NonZeroUsize, bool),
handler: fn(&T, usize),
}
#[derive(Clone)]
@@ -21,11 +20,7 @@ struct Sender(Arc<Inner>);
impl<T> WakerSpawner<T> {
#[track_caller]
pub fn new(
main_thread: MainThreadMarker,
value: T,
handler: fn(&T, NonZeroUsize, bool),
) -> Option<Self> {
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, usize)) -> Option<Self> {
let inner = Arc::new(Inner {
counter: AtomicUsize::new(0),
waker: AtomicWaker::new(),
@@ -42,7 +37,7 @@ impl<T> WakerSpawner<T> {
|handler, count| {
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, count, true);
(handler.handler)(&handler.value, count);
},
{
let inner = Arc::clone(&inner);
@@ -51,31 +46,29 @@ impl<T> WakerSpawner<T> {
while let Some(count) = future::poll_fn(|cx| {
let count = inner.counter.swap(0, Ordering::Relaxed);
match NonZeroUsize::new(count) {
Some(count) => Poll::Ready(Some(count)),
None => {
inner.waker.register(cx.waker());
if count > 0 {
Poll::Ready(Some(count))
} else {
inner.waker.register(cx.waker());
let count = inner.counter.swap(0, Ordering::Relaxed);
let count = inner.counter.swap(0, Ordering::Relaxed);
match NonZeroUsize::new(count) {
Some(count) => Poll::Ready(Some(count)),
None => {
if inner.closed.load(Ordering::Relaxed) {
return Poll::Ready(None);
}
Poll::Pending
},
if count > 0 {
Poll::Ready(Some(count))
} else {
if inner.closed.load(Ordering::Relaxed) {
return Poll::Ready(None);
}
},
Poll::Pending
}
}
})
.await
{
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, count, false);
(handler.handler)(&handler.value, count);
}
}
},
@@ -114,7 +107,7 @@ impl<T> Drop for WakerSpawner<T> {
impl<T> Waker<T> {
pub fn wake(&self) {
self.0.send(NonZeroUsize::MIN)
self.0.send(1)
}
}

View File

@@ -324,11 +324,7 @@ impl Inner {
match &self.cursor {
SelectedCursor::Icon(icon)
| SelectedCursor::Loading { previous: Previous::Icon(icon), .. } => {
if let CursorIcon::Default = icon {
self.style.remove("cursor")
} else {
self.style.set("cursor", icon.name())
}
self.style.set("cursor", icon.name())
},
SelectedCursor::Loading { previous: Previous::Image(cursor), .. }
| SelectedCursor::Image(cursor) => {
@@ -542,8 +538,8 @@ fn from_rgba(
//
// We call `createImageBitmap()` before spawning the future,
// to not have to clone the image buffer.
let options = ImageBitmapOptions::new();
options.set_premultiply_alpha(PremultiplyAlpha::None);
let mut options = ImageBitmapOptions::new();
options.premultiply_alpha(PremultiplyAlpha::None);
let bitmap = JsFuture::from(
window
.create_image_bitmap_with_image_data_and_image_bitmap_options(&image_data, &options)

View File

@@ -2,7 +2,7 @@
pub struct DeviceId(pub i32);
impl DeviceId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
Self(0)
}
}

View File

@@ -1,10 +1,10 @@
use std::marker::PhantomData;
use std::sync::mpsc::{self, Receiver, Sender};
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event::Event;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
use crate::platform::web::{ActiveEventLoopExtWebSys, PollStrategy, WaitUntilStrategy};
use super::{backend, device, window};
@@ -32,33 +32,17 @@ impl<T> EventLoop<T> {
Ok(EventLoop { elw, user_event_sender, user_event_receiver })
}
pub fn run<F>(self, mut event_handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
pub fn run_app<A: ApplicationHandler<T>>(self, app: &mut A) -> ! {
let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData };
// SAFETY: Don't use `move` to make sure we leak the `event_handler` and `target`.
let handler: Box<dyn FnMut(Event<()>)> = Box::new(|event| {
let event = match event.map_nonuser_event() {
Ok(event) => event,
Err(Event::UserEvent(())) => Event::UserEvent(
self.user_event_receiver
.try_recv()
.expect("handler woken up without user event"),
),
Err(_) => unreachable!(),
};
event_handler(event, &target)
});
let handler: Box<dyn FnMut(Event<()>)> =
Box::new(|event| handle_event(app, &target, &self.user_event_receiver, event));
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
// because this function will never return and all resources not cleaned up by the point we
// `throw` will leak, making this actually `'static`.
let handler = unsafe {
std::mem::transmute::<Box<dyn FnMut(Event<()>)>, Box<dyn FnMut(Event<()>) + 'static>>(
handler,
)
};
let handler = unsafe { std::mem::transmute(handler) };
self.elw.p.run(handler, false);
// Throw an exception to break out of Rust execution and use unreachable to tell the
@@ -70,24 +54,12 @@ impl<T> EventLoop<T> {
unreachable!();
}
pub fn spawn<F>(self, mut event_handler: F)
where
F: 'static + FnMut(Event<T>, &RootActiveEventLoop),
{
pub fn spawn_app<A: ApplicationHandler<T> + 'static>(self, mut app: A) {
let target = RootActiveEventLoop { p: self.elw.p.clone(), _marker: PhantomData };
self.elw.p.run(
Box::new(move |event| {
let event = match event.map_nonuser_event() {
Ok(event) => event,
Err(Event::UserEvent(())) => Event::UserEvent(
self.user_event_receiver
.try_recv()
.expect("handler woken up without user event"),
),
Err(_) => unreachable!(),
};
event_handler(event, &target)
handle_event(&mut app, &target, &self.user_event_receiver, event)
}),
true,
);
@@ -100,20 +72,27 @@ impl<T> EventLoop<T> {
pub fn window_target(&self) -> &RootActiveEventLoop {
&self.elw
}
}
pub fn set_poll_strategy(&self, strategy: PollStrategy) {
self.elw.set_poll_strategy(strategy);
}
pub fn poll_strategy(&self) -> PollStrategy {
self.elw.poll_strategy()
}
pub fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.elw.set_wait_until_strategy(strategy);
}
pub fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.elw.wait_until_strategy()
fn handle_event<T: 'static, A: ApplicationHandler<T>>(
app: &mut A,
target: &RootActiveEventLoop,
user_event_receiver: &Receiver<T>,
event: Event<()>,
) {
match event {
Event::NewEvents(cause) => app.new_events(target, cause),
Event::WindowEvent { window_id, event } => app.window_event(target, window_id, event),
Event::DeviceEvent { device_id, event } => app.device_event(target, device_id, event),
Event::UserEvent(_) => {
let event =
user_event_receiver.try_recv().expect("user event signaled but not received");
app.user_event(target, event);
},
Event::Suspended => app.suspended(target),
Event::Resumed => app.resumed(target),
Event::AboutToWait => app.about_to_wait(target),
Event::LoopExiting => app.exiting(target),
Event::MemoryWarning => app.memory_warning(target),
}
}

View File

@@ -1,15 +1,3 @@
use std::cell::{Cell, RefCell};
use std::collections::{HashSet, VecDeque};
use std::iter;
use std::num::NonZeroUsize;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast;
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
use web_time::{Duration, Instant};
use super::super::main_thread::MainThreadMarker;
use super::super::DeviceId;
use super::backend;
@@ -20,12 +8,21 @@ use crate::event::{
WindowEvent,
};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
use crate::platform::web::PollStrategy;
use crate::platform_impl::platform::backend::EventListenerHandle;
use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner};
use crate::platform_impl::platform::window::Inner;
use crate::window::WindowId;
use std::cell::{Cell, RefCell};
use std::collections::{HashSet, VecDeque};
use std::iter;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use wasm_bindgen::prelude::Closure;
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
use web_time::{Duration, Instant};
pub struct Shared(Rc<Execution>);
pub(super) type EventHandler = dyn FnMut(Event<()>);
@@ -43,7 +40,6 @@ pub struct Execution {
proxy_spawner: WakerSpawner<Weak<Self>>,
control_flow: Cell<ControlFlow>,
poll_strategy: Cell<PollStrategy>,
wait_until_strategy: Cell<WaitUntilStrategy>,
exit: Cell<bool>,
runner: RefCell<RunnerEnum>,
suspended: Cell<bool>,
@@ -137,20 +133,18 @@ impl Shared {
let document = window.document().expect("Failed to obtain document");
Shared(Rc::<Execution>::new_cyclic(|weak| {
let proxy_spawner =
WakerSpawner::new(main_thread, weak.clone(), |runner, count, local| {
if let Some(runner) = runner.upgrade() {
Shared(runner).send_user_events(count, local)
}
})
.expect("`EventLoop` has to be created in the main thread");
let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, count| {
if let Some(runner) = runner.upgrade() {
Shared(runner).send_events(iter::repeat(Event::UserEvent(())).take(count))
}
})
.expect("`EventLoop` has to be created in the main thread");
Execution {
main_thread,
proxy_spawner,
control_flow: Cell::new(ControlFlow::default()),
poll_strategy: Cell::new(PollStrategy::default()),
wait_until_strategy: Cell::new(WaitUntilStrategy::default()),
exit: Cell::new(false),
runner: RefCell::new(RunnerEnum::Pending),
suspended: Cell::new(false),
@@ -245,10 +239,21 @@ impl Shared {
return;
}
let pointer_type = event.pointer_type();
if pointer_type != "mouse" {
return;
}
// chorded button event
let device_id = RootDeviceId(DeviceId(event.pointer_id()));
if let Some(button) = backend::event::mouse_button(&event) {
debug_assert_eq!(
pointer_type, "mouse",
"expect pointer type of a chorded button event to be a mouse"
);
let state = if backend::event::mouse_buttons(&event).contains(button.into()) {
ElementState::Pressed
} else {
@@ -312,6 +317,10 @@ impl Shared {
return;
}
if event.pointer_type() != "mouse" {
return;
}
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId(event.pointer_id())),
@@ -331,6 +340,10 @@ impl Shared {
return;
}
if event.pointer_type() != "mouse" {
return;
}
let button = backend::event::mouse_button(&event).expect("no mouse button pressed");
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId(event.pointer_id())),
@@ -351,7 +364,7 @@ impl Shared {
}
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId::dummy()),
device_id: RootDeviceId(unsafe { DeviceId::dummy() }),
event: DeviceEvent::Key(RawKeyEvent {
physical_key: backend::event::key_code(&event),
state: ElementState::Pressed,
@@ -369,7 +382,7 @@ impl Shared {
}
runner.send_event(Event::DeviceEvent {
device_id: RootDeviceId(DeviceId::dummy()),
device_id: RootDeviceId(unsafe { DeviceId::dummy() }),
event: DeviceEvent::Key(RawKeyEvent {
physical_key: backend::event::key_code(&event),
state: ElementState::Released,
@@ -447,42 +460,6 @@ impl Shared {
self.send_events(iter::once(event));
}
// Add a series of user events to the event loop runner
//
// This will schedule the event loop to wake up instead of waking it up immediately if its not
// running.
pub(crate) fn send_user_events(&self, count: NonZeroUsize, local: bool) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
}
if local {
// If the loop is not running and triggered locally, queue on next microtick.
if let Ok(RunnerEnum::Running(_)) =
self.0.runner.try_borrow().as_ref().map(Deref::deref)
{
self.window().queue_microtask(
&Closure::once_into_js({
let this = Rc::downgrade(&self.0);
move || {
if let Some(shared) = this.upgrade() {
Shared(shared).send_events(
iter::repeat(Event::UserEvent(())).take(count.get()),
)
}
}
})
.unchecked_into(),
);
return;
}
}
self.send_events(iter::repeat(Event::UserEvent(())).take(count.get()))
}
// Add a series of events to the event loop runner
//
// It will determine if the event should be immediately sent to the user or buffered for later
@@ -496,7 +473,7 @@ impl Shared {
match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
Ok(RunnerEnum::Running(ref runner)) => {
// If we're currently polling, queue this and wait for the poll() method to be
// called.
// called
if let State::Poll { .. } = runner.state {
process_immediately = false;
}
@@ -670,7 +647,6 @@ impl Shared {
start,
end,
_timeout: backend::Schedule::new_with_duration(
self.wait_until_strategy(),
self.window(),
move || cloned.resume_time_reached(start, end),
delay,
@@ -783,14 +759,6 @@ impl Shared {
self.0.poll_strategy.get()
}
pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.0.wait_until_strategy.set(strategy)
}
pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.0.wait_until_strategy.get()
}
pub(crate) fn waker(&self) -> Waker<Weak<Execution>> {
self.0.proxy_spawner.waker()
}

View File

@@ -18,7 +18,7 @@ use crate::event::{
};
use crate::event_loop::{ControlFlow, DeviceEvents};
use crate::keyboard::ModifiersState;
use crate::platform::web::{CustomCursorFuture, PollStrategy, WaitUntilStrategy};
use crate::platform::web::{CustomCursorFuture, PollStrategy};
use crate::platform_impl::platform::cursor::CustomCursor;
use crate::platform_impl::platform::r#async::Waker;
use crate::window::{
@@ -142,7 +142,7 @@ impl ActiveEventLoop {
}
});
let device_id = RootDeviceId(DeviceId::dummy());
let device_id = RootDeviceId(unsafe { DeviceId::dummy() });
runner.send_events(
iter::once(Event::WindowEvent {
@@ -178,7 +178,7 @@ impl ActiveEventLoop {
}
});
let device_id = RootDeviceId(DeviceId::dummy());
let device_id = RootDeviceId(unsafe { DeviceId::dummy() });
runner.send_events(
iter::once(Event::WindowEvent {
@@ -258,6 +258,21 @@ impl ActiveEventLoop {
});
canvas.on_cursor_move(
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers| {
if has_focus.get() && modifiers.get() != active_modifiers {
modifiers.set(active_modifiers);
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
})
}
}
},
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
@@ -357,6 +372,20 @@ impl ActiveEventLoop {
);
canvas.on_mouse_press(
{
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers| {
if modifiers.get() != active_modifiers {
modifiers.set(active_modifiers);
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
})
}
}
},
{
let runner = self.runner.clone();
let modifiers = self.modifiers.clone();
@@ -421,6 +450,21 @@ impl ActiveEventLoop {
);
canvas.on_mouse_release(
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
let modifiers = self.modifiers.clone();
move |active_modifiers| {
if has_focus.get() && modifiers.get() != active_modifiers {
modifiers.set(active_modifiers);
runner.send_event(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ModifiersChanged(active_modifiers.into()),
});
}
}
},
{
let runner = self.runner.clone();
let has_focus = has_focus.clone();
@@ -561,7 +605,7 @@ impl ActiveEventLoop {
window_id: RootWindowId(id),
event: WindowEvent::Resized(new_size),
});
canvas.request_animation_frame();
runner.request_redraw(RootWindowId(id));
}
}
},
@@ -614,16 +658,6 @@ impl ActiveEventLoop {
self.runner.listen_device_events(allowed)
}
pub fn system_theme(&self) -> Option<Theme> {
backend::is_dark_mode(self.runner.window()).map(|is_dark_mode| {
if is_dark_mode {
Theme::Dark
} else {
Theme::Light
}
})
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.runner.set_control_flow(control_flow)
}
@@ -648,14 +682,6 @@ impl ActiveEventLoop {
self.runner.poll_strategy()
}
pub(crate) fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.runner.set_wait_until_strategy(strategy)
}
pub(crate) fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.runner.wait_until_strategy()
}
pub(crate) fn waker(&self) -> Waker<Weak<Execution>> {
self.runner.waker()
}

View File

@@ -28,9 +28,11 @@ mod event_loop;
mod keyboard;
mod main_thread;
mod monitor;
mod web_sys;
mod window;
#[path = "web_sys/mod.rs"]
mod backend;
pub use self::device::DeviceId;
pub use self::error::OsError;
pub(crate) use self::event_loop::{
@@ -41,7 +43,6 @@ pub use self::monitor::{MonitorHandle, VideoModeHandle};
pub use self::window::{PlatformSpecificWindowAttributes, Window, WindowId};
pub(crate) use self::keyboard::KeyEventExtra;
use self::web_sys as backend;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
pub(crate) use cursor::{

View File

@@ -329,29 +329,51 @@ impl Canvas {
self.pointer_handler.on_cursor_enter(&self.common, handler)
}
pub fn on_mouse_release<M, T>(&mut self, mouse_handler: M, touch_handler: T)
where
pub fn on_mouse_release<MOD, M, T>(
&mut self,
modifier_handler: MOD,
mouse_handler: M,
touch_handler: T,
) where
MOD: 'static + FnMut(ModifiersState),
M: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, MouseButton),
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
self.pointer_handler.on_mouse_release(&self.common, mouse_handler, touch_handler)
self.pointer_handler.on_mouse_release(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
)
}
pub fn on_mouse_press<M, T>(&mut self, mouse_handler: M, touch_handler: T)
where
pub fn on_mouse_press<MOD, M, T>(
&mut self,
modifier_handler: MOD,
mouse_handler: M,
touch_handler: T,
) where
MOD: 'static + FnMut(ModifiersState),
M: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, MouseButton),
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
self.pointer_handler.on_mouse_press(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
Rc::clone(&self.prevent_default),
)
}
pub fn on_cursor_move<M, T, B>(&mut self, mouse_handler: M, touch_handler: T, button_handler: B)
where
pub fn on_cursor_move<MOD, M, T, B>(
&mut self,
modifier_handler: MOD,
mouse_handler: M,
touch_handler: T,
button_handler: B,
) where
MOD: 'static + FnMut(ModifiersState),
M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator<Item = PhysicalPosition<f64>>),
T: 'static
+ FnMut(ModifiersState, i32, &mut dyn Iterator<Item = (PhysicalPosition<f64>, Force)>),
@@ -359,6 +381,7 @@ impl Canvas {
{
self.pointer_handler.on_cursor_move(
&self.common,
modifier_handler,
mouse_handler,
touch_handler,
button_handler,
@@ -404,8 +427,8 @@ impl Canvas {
pub(crate) fn on_resize_scale<S, R>(&mut self, scale_handler: S, size_handler: R)
where
S: 'static + Fn(PhysicalSize<u32>, f64),
R: 'static + Fn(PhysicalSize<u32>),
S: 'static + FnMut(PhysicalSize<u32>, f64),
R: 'static + FnMut(PhysicalSize<u32>),
{
self.on_resize_scale = Some(ResizeScaleHandle::new(
self.window().clone(),

View File

@@ -1,15 +1,13 @@
use crate::dpi::LogicalPosition;
use crate::event::{MouseButton, MouseScrollDelta};
use crate::keyboard::{Key, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
use dpi::{LogicalPosition, PhysicalPosition, Position};
use smol_str::SmolStr;
use std::cell::OnceCell;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{KeyboardEvent, MouseEvent, PointerEvent, WheelEvent};
use super::Engine;
bitflags::bitflags! {
// https://www.w3.org/TR/pointerevents3/#the-buttons-property
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -97,48 +95,42 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition<f64> {
LogicalPosition { x: event.offset_x(), y: event.offset_y() }
}
// TODO: Remove this when Firefox supports correct movement values in coalesced events and browsers
// have agreed on what coordinate space `movementX/Y` is using.
// TODO: Remove this when Firefox supports correct movement values in coalesced events.
// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1753724>.
// See <https://github.com/w3c/pointerlock/issues/42>.
pub enum MouseDelta {
Chromium,
Gecko { old_position: LogicalPosition<f64>, old_delta: LogicalPosition<f64> },
Other,
pub struct MouseDelta(Option<MouseDeltaInner>);
pub struct MouseDeltaInner {
old_position: LogicalPosition<f64>,
old_delta: LogicalPosition<f64>,
}
impl MouseDelta {
pub fn init(window: &web_sys::Window, event: &PointerEvent) -> Self {
match super::engine(window) {
Some(Engine::Chromium) => Self::Chromium,
// Firefox has wrong movement values in coalesced events.
Some(Engine::Gecko) if has_coalesced_events_support(event) => Self::Gecko {
// Firefox has wrong movement values in coalesced events, we will detect that by checking
// for `pointerrawupdate` support. Presumably an implementation of `pointerrawupdate`
// should require correct movement values, otherwise uncoalesced events might be broken as
// well.
Self((!has_pointer_raw_support(window) && has_coalesced_events_support(event)).then(|| {
MouseDeltaInner {
old_position: mouse_position(event),
old_delta: LogicalPosition::new(
event.movement_x() as f64,
event.movement_y() as f64,
),
},
_ => Self::Other,
}
old_delta: LogicalPosition {
x: event.movement_x() as f64,
y: event.movement_y() as f64,
},
}
}))
}
pub fn delta(&mut self, event: &MouseEvent) -> Position {
match self {
MouseDelta::Chromium => {
PhysicalPosition::new(event.movement_x(), event.movement_y()).into()
},
MouseDelta::Gecko { old_position, old_delta } => {
let new_position = mouse_position(event);
let x = new_position.x - old_position.x + old_delta.x;
let y = new_position.y - old_position.y + old_delta.y;
*old_position = new_position;
*old_delta = LogicalPosition::new(0., 0.);
LogicalPosition::new(x, y).into()
},
MouseDelta::Other => {
LogicalPosition::new(event.movement_x(), event.movement_y()).into()
},
pub fn delta(&mut self, event: &MouseEvent) -> LogicalPosition<f64> {
if let Some(inner) = &mut self.0 {
let new_position = mouse_position(event);
let x = new_position.x - inner.old_position.x + inner.old_delta.x;
let y = new_position.y - inner.old_position.y + inner.old_delta.y;
inner.old_position = new_position;
inner.old_delta = LogicalPosition::new(0., 0.);
LogicalPosition::new(x, y)
} else {
LogicalPosition { x: event.movement_x() as f64, y: event.movement_y() as f64 }
}
}
}
@@ -246,6 +238,29 @@ pub fn pointer_move_event(event: PointerEvent) -> impl Iterator<Item = PointerEv
}
}
// TODO: Remove when all browsers implement it correctly.
// See <https://github.com/rust-windowing/winit/issues/2875>.
pub fn has_pointer_raw_support(window: &web_sys::Window) -> bool {
thread_local! {
static POINTER_RAW_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
}
POINTER_RAW_SUPPORT.with(|support| {
*support.get_or_init(|| {
#[wasm_bindgen]
extern "C" {
type PointerRawSupport;
#[wasm_bindgen(method, getter, js_name = onpointerrawupdate)]
fn has_on_pointerrawupdate(this: &PointerRawSupport) -> JsValue;
}
let support: &PointerRawSupport = window.unchecked_ref();
!support.has_on_pointerrawupdate().is_undefined()
})
})
}
// TODO: Remove when Safari supports `getCoalescedEvents`.
// See <https://bugs.webkit.org/show_bug.cgi?id=210454>.
pub fn has_coalesced_events_support(event: &PointerEvent) -> bool {

View File

@@ -9,8 +9,6 @@ mod pointer;
mod resize_scaling;
mod schedule;
use std::sync::OnceLock;
pub use self::canvas::{Canvas, Style};
pub use self::event::ButtonsState;
pub use self::event_handle::EventListenerHandle;
@@ -18,13 +16,8 @@ pub use self::resize_scaling::ResizeScaleHandle;
pub use self::schedule::Schedule;
use crate::dpi::{LogicalPosition, LogicalSize};
use js_sys::Array;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsCast;
use web_sys::{
Document, HtmlCanvasElement, Navigator, PageTransitionEvent, VisibilityState, Window,
};
use web_sys::{Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState};
pub fn throw(msg: &str) {
wasm_bindgen::throw_str(msg);
@@ -165,69 +158,3 @@ pub fn is_visible(document: &Document) -> bool {
}
pub type RawCanvasType = HtmlCanvasElement;
#[derive(Clone, Copy)]
pub enum Engine {
Chromium,
Gecko,
WebKit,
}
pub fn engine(window: &Window) -> Option<Engine> {
static ENGINE: OnceLock<Option<Engine>> = OnceLock::new();
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Navigator)]
type NavigatorExt;
#[wasm_bindgen(method, getter, js_name = userAgentData)]
fn user_agent_data(this: &NavigatorExt) -> Option<NavigatorUaData>;
type NavigatorUaData;
#[wasm_bindgen(method, getter)]
fn brands(this: &NavigatorUaData) -> Array;
type NavigatorUaBrandVersion;
#[wasm_bindgen(method, getter)]
fn brand(this: &NavigatorUaBrandVersion) -> String;
}
*ENGINE.get_or_init(|| {
let navigator: NavigatorExt = window.navigator().unchecked_into();
if let Some(data) = navigator.user_agent_data() {
for brand in data
.brands()
.iter()
.map(NavigatorUaBrandVersion::unchecked_from_js)
.map(|brand| brand.brand())
{
match brand.as_str() {
"Chromium" => return Some(Engine::Chromium),
// TODO: verify when Firefox actually implements it.
"Gecko" => return Some(Engine::Gecko),
// TODO: verify when Safari actually implements it.
"WebKit" => return Some(Engine::WebKit),
_ => (),
}
}
None
} else {
let data = navigator.user_agent().ok()?;
if data.contains("Chrome/") {
Some(Engine::Chromium)
} else if data.contains("Gecko/") {
Some(Engine::Gecko)
} else if data.contains("AppleWebKit/") {
Some(Engine::WebKit)
} else {
None
}
}
})
}

View File

@@ -44,7 +44,7 @@ impl PointerHandler {
// touch events are handled separately
// handling them here would produce duplicate mouse events, inconsistent with
// other platforms.
let pointer_id = (event.pointer_type() != "touch").then(|| event.pointer_id());
let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id());
handler(modifiers, pointer_id);
}));
@@ -61,18 +61,20 @@ impl PointerHandler {
// touch events are handled separately
// handling them here would produce duplicate mouse events, inconsistent with
// other platforms.
let pointer_id = (event.pointer_type() != "touch").then(|| event.pointer_id());
let pointer_id = (event.pointer_type() == "mouse").then(|| event.pointer_id());
handler(modifiers, pointer_id);
}));
}
pub fn on_mouse_release<M, T>(
pub fn on_mouse_release<MOD, M, T>(
&mut self,
canvas_common: &Common,
mut modifier_handler: MOD,
mut mouse_handler: M,
mut touch_handler: T,
) where
MOD: 'static + FnMut(ModifiersState),
M: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, MouseButton),
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
@@ -88,23 +90,26 @@ impl PointerHandler {
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
Force::Normalized(event.pressure() as f64),
),
_ => mouse_handler(
"mouse" => mouse_handler(
modifiers,
event.pointer_id(),
event::mouse_position(&event).to_physical(super::scale_factor(&window)),
event::mouse_button(&event).expect("no mouse button released"),
),
_ => modifier_handler(modifiers),
}
}));
}
pub fn on_mouse_press<M, T>(
pub fn on_mouse_press<MOD, M, T>(
&mut self,
canvas_common: &Common,
mut modifier_handler: MOD,
mut mouse_handler: M,
mut touch_handler: T,
prevent_default: Rc<Cell<bool>>,
) where
MOD: 'static + FnMut(ModifiersState),
M: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, MouseButton),
T: 'static + FnMut(ModifiersState, i32, PhysicalPosition<f64>, Force),
{
@@ -120,9 +125,8 @@ impl PointerHandler {
}
let modifiers = event::mouse_modifiers(&event);
let pointer_type = &event.pointer_type();
match pointer_type.as_str() {
match event.pointer_type().as_str() {
"touch" => {
touch_handler(
modifiers,
@@ -131,7 +135,7 @@ impl PointerHandler {
Force::Normalized(event.pressure() as f64),
);
},
_ => {
"mouse" => {
mouse_handler(
modifiers,
event.pointer_id(),
@@ -139,27 +143,27 @@ impl PointerHandler {
event::mouse_button(&event).expect("no mouse button pressed"),
);
if pointer_type == "mouse" {
// Error is swallowed here since the error would occur every time the
// mouse is clicked when the cursor is
// grabbed, and there is probably not a
// situation where this could fail, that we
// care if it fails.
let _e = canvas.set_pointer_capture(event.pointer_id());
}
// Error is swallowed here since the error would occur every time the mouse
// is clicked when the cursor is grabbed, and there
// is probably not a situation where this could
// fail, that we care if it fails.
let _e = canvas.set_pointer_capture(event.pointer_id());
},
_ => modifier_handler(modifiers),
}
}));
}
pub fn on_cursor_move<M, T, B>(
pub fn on_cursor_move<MOD, M, T, B>(
&mut self,
canvas_common: &Common,
mut modifier_handler: MOD,
mut mouse_handler: M,
mut touch_handler: T,
mut button_handler: B,
prevent_default: Rc<Cell<bool>>,
) where
MOD: 'static + FnMut(ModifiersState),
M: 'static + FnMut(ModifiersState, i32, &mut dyn Iterator<Item = PhysicalPosition<f64>>),
T: 'static
+ FnMut(ModifiersState, i32, &mut dyn Iterator<Item = (PhysicalPosition<f64>, Force)>),
@@ -171,10 +175,23 @@ impl PointerHandler {
Some(canvas_common.add_event("pointermove", move |event: PointerEvent| {
let modifiers = event::mouse_modifiers(&event);
let pointer_type = event.pointer_type();
if let "touch" | "mouse" = pointer_type.as_str() {
} else {
modifier_handler(modifiers);
return;
}
let id = event.pointer_id();
// chorded button event
if let Some(button) = event::mouse_button(&event) {
debug_assert_eq!(
pointer_type, "mouse",
"expect pointer type of a chorded button event to be a mouse"
);
if prevent_default.get() {
// prevent text selection
event.prevent_default();
@@ -195,7 +212,13 @@ impl PointerHandler {
// pointer move event
let scale = super::scale_factor(&window);
match event.pointer_type().as_str() {
match pointer_type.as_str() {
"mouse" => mouse_handler(
modifiers,
id,
&mut event::pointer_move_event(event)
.map(|event| event::mouse_position(&event).to_physical(scale)),
),
"touch" => touch_handler(
modifiers,
id,
@@ -206,12 +229,7 @@ impl PointerHandler {
)
}),
),
_ => mouse_handler(
modifiers,
id,
&mut event::pointer_move_event(event)
.map(|event| event::mouse_position(&event).to_physical(scale)),
),
_ => unreachable!("didn't return early before"),
};
}));
}

View File

@@ -16,7 +16,7 @@ use super::media_query_handle::MediaQueryListHandle;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
pub struct ResizeScaleHandle(Rc<ResizeScaleInternal>);
pub struct ResizeScaleHandle(Rc<RefCell<ResizeScaleInternal>>);
impl ResizeScaleHandle {
pub(crate) fn new<S, R>(
@@ -28,8 +28,8 @@ impl ResizeScaleHandle {
resize_handler: R,
) -> Self
where
S: 'static + Fn(PhysicalSize<u32>, f64),
R: 'static + Fn(PhysicalSize<u32>),
S: 'static + FnMut(PhysicalSize<u32>, f64),
R: 'static + FnMut(PhysicalSize<u32>),
{
Self(ResizeScaleInternal::new(
window,
@@ -42,7 +42,7 @@ impl ResizeScaleHandle {
}
pub(crate) fn notify_resize(&self) {
self.0.notify()
self.0.borrow_mut().notify()
}
}
@@ -53,11 +53,11 @@ struct ResizeScaleInternal {
document: Document,
canvas: HtmlCanvasElement,
style: Style,
mql: RefCell<MediaQueryListHandle>,
mql: MediaQueryListHandle,
observer: ResizeObserver,
_observer_closure: Closure<dyn FnMut(Array, ResizeObserver)>,
scale_handler: Box<dyn Fn(PhysicalSize<u32>, f64)>,
resize_handler: Box<dyn Fn(PhysicalSize<u32>)>,
scale_handler: Box<dyn FnMut(PhysicalSize<u32>, f64)>,
resize_handler: Box<dyn FnMut(PhysicalSize<u32>)>,
notify_scale: Cell<bool>,
}
@@ -69,12 +69,12 @@ impl ResizeScaleInternal {
style: Style,
scale_handler: S,
resize_handler: R,
) -> Rc<Self>
) -> Rc<RefCell<Self>>
where
S: 'static + Fn(PhysicalSize<u32>, f64),
R: 'static + Fn(PhysicalSize<u32>),
S: 'static + FnMut(PhysicalSize<u32>, f64),
R: 'static + FnMut(PhysicalSize<u32>),
{
Rc::<ResizeScaleInternal>::new_cyclic(|weak_self| {
Rc::<RefCell<ResizeScaleInternal>>::new_cyclic(|weak_self| {
let mql = Self::create_mql(&window, {
let weak_self = weak_self.clone();
move |mql| {
@@ -86,7 +86,9 @@ impl ResizeScaleInternal {
let weak_self = weak_self.clone();
let observer_closure = Closure::new(move |entries: Array, _| {
if let Some(this) = weak_self.upgrade() {
if let Some(rc_self) = weak_self.upgrade() {
let mut this = rc_self.borrow_mut();
let size = this.process_entry(entries);
if this.notify_scale.replace(false) {
@@ -99,18 +101,18 @@ impl ResizeScaleInternal {
});
let observer = Self::create_observer(&canvas, observer_closure.as_ref());
Self {
RefCell::new(Self {
window,
document,
canvas,
style,
mql: RefCell::new(mql),
mql,
observer,
_observer_closure: observer_closure,
scale_handler: Box::new(scale_handler),
resize_handler: Box::new(resize_handler),
notify_scale: Cell::new(false),
}
})
})
}
@@ -139,9 +141,10 @@ impl ResizeScaleInternal {
// Safari doesn't support `devicePixelContentBoxSize`
if has_device_pixel_support() {
let options = ResizeObserverOptions::new();
options.set_box(ResizeObserverBoxOptions::DevicePixelContentBox);
observer.observe_with_options(canvas, &options);
observer.observe_with_options(
canvas,
ResizeObserverOptions::new().box_(ResizeObserverBoxOptions::DevicePixelContentBox),
);
} else {
observer.observe(canvas);
}
@@ -149,7 +152,7 @@ impl ResizeScaleInternal {
observer
}
fn notify(&self) {
fn notify(&mut self) {
if !self.document.contains(Some(&self.canvas)) || self.style.get("display") == "none" {
let size = PhysicalSize::new(0, 0);
@@ -197,9 +200,10 @@ impl ResizeScaleInternal {
}
}
fn handle_scale(self: Rc<Self>, mql: &MediaQueryList) {
let weak_self = Rc::downgrade(&self);
let scale = super::scale_factor(&self.window);
fn handle_scale(this: Rc<RefCell<Self>>, mql: &MediaQueryList) {
let weak_self = Rc::downgrade(&this);
let mut this = this.borrow_mut();
let scale = super::scale_factor(&this.window);
// TODO: confirm/reproduce this problem, see:
// <https://github.com/rust-windowing/winit/issues/2597>.
@@ -213,15 +217,15 @@ impl ResizeScaleInternal {
return;
}
let new_mql = Self::create_mql(&self.window, move |mql| {
let new_mql = Self::create_mql(&this.window, move |mql| {
if let Some(rc_self) = weak_self.upgrade() {
Self::handle_scale(rc_self, mql);
}
});
self.mql.replace(new_mql);
this.mql = new_mql;
self.notify_scale.set(true);
self.notify();
this.notify_scale.set(true);
this.notify();
}
fn process_entry(&self, entries: Array) -> PhysicalSize<u32> {

View File

@@ -1,14 +1,12 @@
use js_sys::{Array, Function, Object, Promise, Reflect};
use js_sys::{Function, Object, Promise, Reflect};
use std::cell::OnceCell;
use std::time::Duration;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{
AbortController, AbortSignal, Blob, BlobPropertyBag, MessageChannel, MessagePort, Url, Worker,
};
use web_sys::{AbortController, AbortSignal, MessageChannel, MessagePort};
use crate::platform::web::{PollStrategy, WaitUntilStrategy};
use crate::platform::web::PollStrategy;
#[derive(Debug)]
pub struct Schedule {
@@ -31,7 +29,6 @@ enum Inner {
port: MessagePort,
_timeout_closure: Closure<dyn FnMut()>,
},
Worker(MessagePort),
}
impl Schedule {
@@ -48,24 +45,14 @@ impl Schedule {
}
}
pub fn new_with_duration<F>(
strategy: WaitUntilStrategy,
window: &web_sys::Window,
f: F,
duration: Duration,
) -> Schedule
pub fn new_with_duration<F>(window: &web_sys::Window, f: F, duration: Duration) -> Schedule
where
F: 'static + FnMut(),
{
match strategy {
WaitUntilStrategy::Scheduler => {
if has_scheduler_support(window) {
Self::new_scheduler(window, f, Some(duration))
} else {
Self::new_timeout(window.clone(), f, Some(duration))
}
},
WaitUntilStrategy::Worker => Self::new_worker(f, duration),
if has_scheduler_support(window) {
Self::new_scheduler(window, f, Some(duration))
} else {
Self::new_timeout(window.clone(), f, Some(duration))
}
}
@@ -166,44 +153,6 @@ impl Schedule {
},
}
}
fn new_worker<F>(f: F, duration: Duration) -> Schedule
where
F: 'static + FnMut(),
{
thread_local! {
static URL: ScriptUrl = ScriptUrl::new(include_str!("worker.min.js"));
static WORKER: Worker = URL.with(|url| Worker::new(&url.0)).expect("`new Worker()` is not expected to fail with a local script");
}
let channel = MessageChannel::new().unwrap();
let closure = Closure::new(f);
let port_1 = channel.port1();
port_1.set_onmessage(Some(closure.as_ref().unchecked_ref()));
port_1.start();
// `Duration::as_millis()` always rounds down (because of truncation), we want to round
// up instead. This makes sure that the we never wake up **before** the given time.
let duration = duration
.as_secs()
.try_into()
.ok()
.and_then(|secs: u32| secs.checked_mul(1000))
.and_then(|secs| secs.checked_add(duration_millis_ceil(duration)))
.unwrap_or(u32::MAX);
WORKER
.with(|worker| {
let port_2 = channel.port2();
worker.post_message_with_transfer(
&Array::of2(&port_2, &duration.into()),
&Array::of1(&port_2).into(),
)
})
.expect("`Worker.postMessage()` is not expected to fail");
Schedule { _closure: closure, inner: Inner::Worker(port_1) }
}
}
impl Drop for Schedule {
@@ -216,10 +165,6 @@ impl Drop for Schedule {
port.close();
port.set_onmessage(None);
},
Inner::Worker(port) => {
port.close();
port.set_onmessage(None);
},
}
}
}
@@ -281,29 +226,6 @@ fn has_idle_callback_support(window: &web_sys::Window) -> bool {
})
}
struct ScriptUrl(String);
impl ScriptUrl {
fn new(script: &str) -> Self {
let sequence = Array::of1(&script.into());
let property = BlobPropertyBag::new();
property.set_type("text/javascript");
let blob = Blob::new_with_str_sequence_and_options(&sequence, &property)
.expect("`new Blob()` should never throw");
let url = Url::create_object_url_with_blob(&blob)
.expect("`URL.createObjectURL()` should never throw");
Self(url)
}
}
impl Drop for ScriptUrl {
fn drop(&mut self) {
Url::revoke_object_url(&self.0).expect("`URL.revokeObjectURL()` should never throw");
}
}
#[wasm_bindgen]
extern "C" {
type WindowSupportExt;

View File

@@ -1,10 +0,0 @@
onmessage = event => {
const [port, timeout] = event.data
const f = () => port.postMessage(undefined)
if ('scheduler' in this) {
scheduler.postTask(f, { delay: timeout })
} else {
setTimeout(f, timeout)
}
}

View File

@@ -1 +0,0 @@
onmessage=e=>{let[s,t]=e.data,a=()=>s.postMessage(void 0);"scheduler"in this?scheduler.postTask(a,{delay:t}):setTimeout(a,t)};

View File

@@ -431,7 +431,7 @@ impl Drop for Inner {
pub struct WindowId(pub(crate) u32);
impl WindowId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
Self(0)
}
}

View File

@@ -123,7 +123,7 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {
}
}
pub fn should_use_dark_mode() -> bool {
fn should_use_dark_mode() -> bool {
should_apps_use_dark_mode() && !is_high_contrast()
}
@@ -132,13 +132,7 @@ fn should_apps_use_dark_mode() -> bool {
static SHOULD_APPS_USE_DARK_MODE: Lazy<Option<ShouldAppsUseDarkMode>> = Lazy::new(|| unsafe {
const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: PCSTR = 132 as PCSTR;
// We won't try to do anything for windows versions < 17763
// (Windows 10 October 2018 update)
if !*DARK_MODE_SUPPORTED {
return None;
}
let module = LoadLibraryA("uxtheme.dll\0".as_ptr().cast());
let module = LoadLibraryA("uxtheme.dll\0".as_ptr());
if module == 0 {
return None;

View File

@@ -6,7 +6,6 @@ use std::cell::Cell;
use std::collections::VecDeque;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::os::windows::io::{AsRawHandle as _, FromRawHandle as _, OwnedHandle, RawHandle};
use std::rc::Rc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::mpsc::{self, Receiver, Sender};
@@ -17,18 +16,13 @@ use std::{mem, panic, ptr};
use crate::utils::Lazy;
use windows_sys::Win32::Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE;
use windows_sys::Win32::Foundation::{
GetLastError, FALSE, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WAIT_FAILED, WPARAM,
};
use windows_sys::Win32::Foundation::{HWND, LPARAM, LRESULT, POINT, RECT, WPARAM};
use windows_sys::Win32::Graphics::Gdi::{
GetMonitorInfoW, MonitorFromRect, MonitorFromWindow, RedrawWindow, ScreenToClient,
ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, SC_SCREENSAVE,
};
use windows_sys::Win32::System::Ole::RevokeDragDrop;
use windows_sys::Win32::System::Threading::{
CreateWaitableTimerExW, GetCurrentThreadId, SetWaitableTimer,
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, INFINITE, TIMER_ALL_ACCESS,
};
use windows_sys::Win32::System::Threading::{GetCurrentThreadId, INFINITE};
use windows_sys::Win32::UI::Controls::{HOVER_DEFAULT, WM_MOUSELEAVE};
use windows_sys::Win32::UI::Input::Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW};
use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
@@ -44,15 +38,15 @@ use windows_sys::Win32::UI::Input::Touch::{
use windows_sys::Win32::UI::Input::{RAWINPUT, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE};
use windows_sys::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetClientRect, GetCursorPos,
GetMenu, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW,
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, TranslateMessage,
CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT,
MINMAXINFO, MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN,
PT_TOUCH, QS_ALLINPUT, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE,
SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS,
WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT,
WMSZ_TOPRIGHT, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED,
WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION,
GetMenu, GetMessageW, KillTimer, LoadCursorW, PeekMessageW, PostMessageW, RegisterClassExW,
RegisterWindowMessageA, SetCursor, SetTimer, SetWindowPos, TranslateMessage, CREATESTRUCTW,
GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO,
MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN, PT_TOUCH, RI_MOUSE_HWHEEL,
RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE,
SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT,
WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT,
WM_CAPTURECHANGED, WM_CLOSE, 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_INPUT, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN,
WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP,
WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE,
@@ -63,6 +57,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
};
use crate::application::ApplicationHandler;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::EventLoopError;
use crate::event::{
@@ -87,7 +82,7 @@ use crate::platform_impl::platform::{
raw_input, util, wrap_device_id, Fullscreen, WindowId, DEVICE_ID,
};
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId,
CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId,
};
use runner::{EventLoopRunner, EventLoopRunnerShared};
@@ -156,10 +151,6 @@ pub struct EventLoop<T: 'static> {
user_event_receiver: Receiver<T>,
window_target: RootAEL,
msg_hook: Option<Box<dyn FnMut(*const c_void) -> bool + 'static>>,
// It is a timer used on timed waits.
// It is created lazily in case if we have `ControlFlow::WaitUntil`.
// Keep it as a field to avoid recreating it on every `ControlFlow::WaitUntil`.
high_resolution_timer: Option<OwnedHandle>,
}
pub(crate) struct PlatformSpecificEventLoopAttributes {
@@ -218,7 +209,6 @@ impl<T: 'static> EventLoop<T> {
_marker: PhantomData,
},
msg_hook: attributes.msg_hook.take(),
high_resolution_timer: None,
})
}
@@ -226,17 +216,14 @@ impl<T: 'static> EventLoop<T> {
&self.window_target
}
pub fn run<F>(mut self, event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootAEL),
{
self.run_on_demand(event_handler)
pub fn run_app<A: ApplicationHandler<T>>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_on_demand<F>(&mut self, mut event_handler: F) -> Result<(), EventLoopError>
where
F: FnMut(Event<T>, &RootAEL),
{
pub fn run_app_on_demand<A: ApplicationHandler<T>>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
{
let runner = &self.window_target.p.runner_shared;
@@ -247,29 +234,39 @@ impl<T: 'static> EventLoop<T> {
// returning
unsafe {
runner.set_event_handler(move |event| {
// the shared `EventLoopRunner` is not parameterized
// `EventLoopProxy::send_event()` calls `PostMessage`
// to wakeup and dispatch a placeholder `UserEvent`,
// when we received the placeholder event here, the
// real UserEvent(T) should already be put in the
// mpsc channel and ready to be pulled
let event = match event.map_nonuser_event() {
Ok(non_user_event) => non_user_event,
Err(_user_event_placeholder) => Event::UserEvent(
user_event_receiver
match event {
Event::NewEvents(cause) => app.new_events(event_loop_windows_ref, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(event_loop_windows_ref, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(event_loop_windows_ref, device_id, event)
},
// The shared `EventLoopRunner` is not parameterized
// `EventLoopProxy::send_event()` calls `PostMessage`
// to wakeup and dispatch a placeholder `UserEvent`,
// when we received the placeholder event here, the
// real UserEvent(T) should already be put in the
// mpsc channel and ready to be pulled
Event::UserEvent(_) => {
let event = user_event_receiver
.try_recv()
.expect("user event signaled but not received"),
),
};
event_handler(event, event_loop_windows_ref)
.expect("user event signaled but not received");
app.user_event(event_loop_windows_ref, event);
},
Event::Suspended => app.suspended(event_loop_windows_ref),
Event::Resumed => app.resumed(event_loop_windows_ref),
Event::AboutToWait => app.about_to_wait(event_loop_windows_ref),
Event::LoopExiting => app.exiting(event_loop_windows_ref),
Event::MemoryWarning => app.memory_warning(event_loop_windows_ref),
}
});
}
}
let exit_code = loop {
self.wait_for_messages(None);
// wait_for_messages calls user application before and after waiting
// so it may have decided to exit.
self.wait_and_dispatch_message(None);
if let Some(code) = self.exit_code() {
break code;
}
@@ -296,10 +293,11 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, mut event_handler: F) -> PumpStatus
where
F: FnMut(Event<T>, &RootAEL),
{
pub fn pump_app_events<A: ApplicationHandler<T>>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
{
let runner = &self.window_target.p.runner_shared;
let event_loop_windows_ref = &self.window_target;
@@ -314,25 +312,40 @@ impl<T: 'static> EventLoop<T> {
// event handler.
unsafe {
runner.set_event_handler(move |event| {
let event = match event.map_nonuser_event() {
Ok(non_user_event) => non_user_event,
Err(_user_event_placeholder) => Event::UserEvent(
user_event_receiver
.recv()
.expect("user event signaled but not received"),
),
};
event_handler(event, event_loop_windows_ref)
match event {
Event::NewEvents(cause) => app.new_events(event_loop_windows_ref, cause),
Event::WindowEvent { window_id, event } => {
app.window_event(event_loop_windows_ref, window_id, event)
},
Event::DeviceEvent { device_id, event } => {
app.device_event(event_loop_windows_ref, device_id, event)
},
// The shared `EventLoopRunner` is not parameterized
// `EventLoopProxy::send_event()` calls `PostMessage`
// to wakeup and dispatch a placeholder `UserEvent`,
// when we received the placeholder event here, the
// real UserEvent(T) should already be put in the
// mpsc channel and ready to be pulled
Event::UserEvent(_) => {
let event = user_event_receiver
.try_recv()
.expect("user event signaled but not received");
app.user_event(event_loop_windows_ref, event);
},
Event::Suspended => app.suspended(event_loop_windows_ref),
Event::Resumed => app.resumed(event_loop_windows_ref),
Event::AboutToWait => app.about_to_wait(event_loop_windows_ref),
Event::LoopExiting => app.exiting(event_loop_windows_ref),
Event::MemoryWarning => app.memory_warning(event_loop_windows_ref),
}
});
runner.wakeup();
}
}
if self.exit_code().is_none() {
self.wait_for_messages(timeout);
}
// wait_for_messages calls user application before and after waiting
// so it may have decided to exit.
self.wait_and_dispatch_message(timeout);
if self.exit_code().is_none() {
self.dispatch_peeked_messages();
}
@@ -362,27 +375,101 @@ impl<T: 'static> EventLoop<T> {
status
}
/// Waits until new event messages arrive to be peeked.
/// Doesn't peek messages itself.
///
/// Parameter timeout is optional. This method would wait for the smaller timeout
/// between the argument and a timeout from control flow.
fn wait_for_messages(&mut self, timeout: Option<Duration>) {
/// Wait for one message and dispatch it, optionally with a timeout
fn wait_and_dispatch_message(&mut self, timeout: Option<Duration>) {
fn get_msg_with_timeout(msg: &mut MSG, timeout: Option<Duration>) -> PumpStatus {
unsafe {
// A timeout of None means wait indefinitely (so we don't need to call SetTimer)
let timer_id = timeout.map(|timeout| SetTimer(0, 0, dur2timeout(timeout), None));
let get_status = GetMessageW(msg, 0, 0, 0);
if let Some(timer_id) = timer_id {
KillTimer(0, timer_id);
}
// A return value of 0 implies `WM_QUIT`
if get_status == 0 {
PumpStatus::Exit(0)
} else {
PumpStatus::Continue
}
}
}
/// Fetch the next MSG either via PeekMessage or GetMessage depending on whether the
/// requested timeout is `ZERO` (and so we don't want to block)
///
/// Returns `None` if no MSG was read, else a `Continue` or `Exit` status
fn wait_for_msg(msg: &mut MSG, timeout: Option<Duration>) -> Option<PumpStatus> {
if timeout == Some(Duration::ZERO) {
unsafe {
if PeekMessageW(msg, 0, 0, 0, PM_REMOVE) != 0 {
Some(PumpStatus::Continue)
} else {
None
}
}
} else {
Some(get_msg_with_timeout(msg, timeout))
}
}
let runner = &self.window_target.p.runner_shared;
// We aim to be consistent with the MacOS backend which has a RunLoop
// observer that will dispatch AboutToWait when about to wait for
// events, and NewEvents after the RunLoop wakes up.
//
// We emulate similar behaviour by treating `MsgWaitForMultipleObjectsEx` as our wait
// We emulate similar behaviour by treating `GetMessage` as our wait
// point and wake up point (when it returns) and we drain all other
// pending messages via `PeekMessage` until we come back to "wait" via
// `MsgWaitForMultipleObjectsEx`.
// `GetMessage`
//
runner.prepare_wait();
wait_for_messages_impl(&mut self.high_resolution_timer, runner.control_flow(), timeout);
let control_flow_timeout = match runner.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
let start = Instant::now();
Some(wait_deadline.saturating_duration_since(start))
},
};
let timeout = min_timeout(control_flow_timeout, timeout);
// # Safety
// The Windows API has no documented requirement for bitwise
// initializing a `MSG` struct (it can be uninitialized memory for the C
// API) and there's no API to construct or initialize a `MSG`. This
// is the simplest way avoid uninitialized memory in Rust
let mut msg = unsafe { mem::zeroed() };
let msg_status = wait_for_msg(&mut msg, timeout);
// Before we potentially exit, make sure to consistently emit an event for the wake up
runner.wakeup();
match msg_status {
None => {}, // No MSG to dispatch
Some(PumpStatus::Exit(code)) => {
runner.set_exit_code(code);
},
Some(PumpStatus::Continue) => {
unsafe {
let handled = if let Some(callback) = self.msg_hook.as_deref_mut() {
callback(&mut msg as *mut _ as *mut _)
} else {
false
};
if !handled {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
if let Err(payload) = runner.take_panic_error() {
runner.reset_runner();
panic::resume_unwind(payload);
}
},
}
}
/// Dispatch all queued messages via `PeekMessageW`
@@ -401,7 +488,7 @@ impl<T: 'static> EventLoop<T> {
// initializing a `MSG` struct (it can be uninitialized memory for the C
// API) and there's no API to construct or initialize a `MSG`. This
// is the simplest way avoid uninitialized memory in Rust
let mut msg: MSG = unsafe { mem::zeroed() };
let mut msg = unsafe { mem::zeroed() };
loop {
unsafe {
@@ -491,10 +578,6 @@ impl ActiveEventLoop {
raw_input::register_all_mice_and_keyboards_for_raw_input(self.thread_msg_target, allowed);
}
pub fn system_theme(&self) -> Option<Theme> {
Some(if super::dark_mode::should_use_dark_mode() { Theme::Dark } else { Theme::Light })
}
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.runner_shared.set_control_flow(control_flow)
}
@@ -564,7 +647,7 @@ impl OwnedDisplayHandle {
fn main_thread_id() -> u32 {
static mut MAIN_THREAD_ID: u32 = 0;
// Function pointer used in CRT initialization section to set the above static field's value.
/// Function pointer used in CRT initialization section to set the above static field's value.
// Mark as used so this is not removable.
#[used]
@@ -623,144 +706,6 @@ impl<T> Drop for EventLoop<T> {
}
}
/// Set upper limit for waiting time to avoid overflows.
/// I chose 50 days as a limit because it is used in dur2timeout.
const FIFTY_DAYS: Duration = Duration::from_secs(50_u64 * 24 * 60 * 60);
/// Waitable timers use 100 ns intervals to indicate due time.
/// <https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-setwaitabletimer#parameters>
/// And there is no point waiting using other ways for such small timings
/// because they are even less precise (can overshoot by few ms).
const MIN_WAIT: Duration = Duration::from_nanos(100);
fn create_high_resolution_timer() -> Option<OwnedHandle> {
unsafe {
let handle: HANDLE = CreateWaitableTimerExW(
ptr::null(),
ptr::null(),
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
TIMER_ALL_ACCESS,
);
// CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is supported only after
// Win10 1803 but it is already default option for rustc
// (std uses it to implement `std::thread::sleep`).
if handle == 0 {
None
} else {
Some(OwnedHandle::from_raw_handle(handle as *mut c_void))
}
}
}
/// This function should not return error if parameters are valid
/// but there is no guarantee about that at MSDN docs
/// so we return result of GetLastError if fail.
///
/// ## Safety
///
/// timer must be a valid timer handle created by [create_high_resolution_timer].
/// timeout divided by 100 nanoseconds must be more than 0 and less than i64::MAX.
unsafe fn set_high_resolution_timer(timer: RawHandle, timeout: Duration) -> Result<(), u32> {
const INTERVAL_NS: u32 = MIN_WAIT.subsec_nanos();
const INTERVALS_IN_SEC: u64 = (Duration::from_secs(1).as_nanos() / INTERVAL_NS as u128) as u64;
let intervals_to_wait: u64 =
timeout.as_secs() * INTERVALS_IN_SEC + u64::from(timeout.subsec_nanos() / INTERVAL_NS);
debug_assert!(intervals_to_wait < i64::MAX as u64, "Must be called with smaller duration",);
// Use negative time to indicate relative time.
let due_time: i64 = -(intervals_to_wait as i64);
unsafe {
let set_result = SetWaitableTimer(timer as HANDLE, &due_time, 0, None, ptr::null(), FALSE);
if set_result != FALSE {
Ok(())
} else {
Err(GetLastError())
}
}
}
/// Implementation detail of [EventLoop::wait_for_messages].
///
/// Does actual system-level waiting and doesn't process any messages itself,
/// including winits internal notifications about waiting and new messages arrival.
fn wait_for_messages_impl(
high_resolution_timer: &mut Option<OwnedHandle>,
control_flow: ControlFlow,
timeout: Option<Duration>,
) {
let timeout = {
let control_flow_timeout = match control_flow {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
let start = Instant::now();
Some(wait_deadline.saturating_duration_since(start))
},
};
let timeout = min_timeout(timeout, control_flow_timeout);
if timeout == Some(Duration::ZERO) {
// Do not wait if we don't have time.
return;
}
// Now we decided to wait so need to do some clamping
// to avoid problems with overflow and calling WinAPI with invalid parameters.
timeout
.map(|t| t.min(FIFTY_DAYS))
// If timeout is less than minimally supported by Windows,
// increase it to that minimum. Who want less than microsecond delays anyway?
.map(|t| t.max(MIN_WAIT))
};
if timeout.is_some() && high_resolution_timer.is_none() {
*high_resolution_timer = create_high_resolution_timer();
}
let high_resolution_timer: Option<RawHandle> =
high_resolution_timer.as_ref().map(OwnedHandle::as_raw_handle);
let use_timer: bool;
if let (Some(handle), Some(timeout)) = (high_resolution_timer, timeout) {
let res = unsafe {
// Safety: handle can be Some only if we succeeded in creating high resolution
// timer. We properly clamped timeout so it can be used as argument
// to timer.
set_high_resolution_timer(handle, timeout)
};
if let Err(error_code) = res {
// We successfully got timer but failed to set it?
// Should be some bug in our code.
tracing::trace!("Failed to set high resolution timer: last error {}", error_code);
use_timer = false;
} else {
use_timer = true;
}
} else {
use_timer = false;
}
unsafe {
// Either:
// 1. User wants to wait indefinely if timeout is not set.
// 2. We failed to get and set high resolution timer and we need something instead of it.
let wait_duration_ms = timeout.map(dur2timeout).unwrap_or(INFINITE);
let (num_handles, raw_handles) =
if use_timer { (1, [high_resolution_timer.unwrap()]) } else { (0, [ptr::null_mut()]) };
// We must use `QS_ALLINPUT` to wake on accessibility messages.
let result = MsgWaitForMultipleObjectsEx(
num_handles,
raw_handles.as_ptr() as *const _,
wait_duration_ms,
QS_ALLINPUT,
MWMO_INPUTAVAILABLE,
);
if result == WAIT_FAILED {
// Well, nothing smart to do in such case.
// Treat it as spurious wake up.
tracing::warn!("Failed to MsgWaitForMultipleObjectsEx: error code {}", GetLastError(),);
}
}
}
pub(crate) struct EventLoopThreadExecutor {
thread_id: u32,
target_window: HWND,
@@ -1216,31 +1161,6 @@ unsafe fn public_window_callback_inner(
WM_NCLBUTTONDOWN => {
if wparam == HTCAPTION as _ {
// Prevent the user event loop from pausing when left clicking the title bar.
//
// When the user interacts with the title bar, Windows enters the modal event
// loop. Currently, a left click causes a pause for about 500ms. Sending a dummy
// mouse-move event seems to cancel the modal loop early, preventing the pause.
// The application will never see this dummy event.
//
// The mouse coordinates are encoded into the lparam value, however the WM_MOUSEMOVE
// event is not using the same coordinate system of the WM_NCLBUTTONDOWN event.
// One uses client-area coordinates and the other is screen-coordinates. In any
// case, passing the lparam as-is with the dummy event does not seem the cancel
// the modal loop.
//
// However, passing in a value of 0 has been observed to always cancel the pause.
//
// Other notes:
//
// For some unknown reason, the cursor will blink when clicking the title bar.
// Cancelling the modal loop early causes the blink to happen *immediately*.
// Otherwise, the blank happens *after* the pause.
//
// When right-click the title bar, the system window menu is presented to the user,
// and the modal event loop begins. This dummy event does *not* prevent the freeze
// in the main event loop caused by that popup menu.
let lparam = 0;
unsafe { PostMessageW(window, WM_MOUSEMOVE, 0, lparam) };
}
result = ProcResult::DefWindowProc(wparam);
@@ -2549,7 +2469,7 @@ unsafe extern "system" fn thread_event_target_callback(
if userdata_removed {
drop(userdata);
} else {
Box::leak(userdata);
Box::into_raw(userdata);
}
result
}

Some files were not shown because too many files have changed in this diff Show More