Compare commits

..

48 Commits

Author SHA1 Message Date
Mads Marquart
bf73a978df Only run observers in the default run loop mode 2024-06-29 00:27:31 +02:00
Mads Marquart
82d9bbe559 Remove EventLoopExtIOS::idiom and ios::Idiom (#2924)
Introduced in 3a7350c with unclear motivation.

Nowadays, this feature is incomplete and unsound, and the equivalent
functionality can be trivially achieved outside of `winit` using
`objc2-ui-kit`.
2024-06-24 22:56:20 +02:00
Mads Marquart
8bdd4d620e Use self:: when re-importing local module (#3757)
Follow-up to https://github.com/rust-windowing/winit/pull/3755.
2024-06-24 22:40:49 +02:00
Mads Marquart
9d5412ffe1 Move iOS and macOS implementations into new apple module (#3756)
Move iOS and macOS implementations to a shared folder called `apple`, to allow
us to reduce the code-duplication between these platforms in the future.

The folder structure is now:
- `src/platform_impl/apple/`
  - `appkit/`
  - `uikit/`
  - `example_shared_file.rs`
  - `mod.rs`

* Add preliminary support for tvOS, watchOS and visionOS
* Reduce duplication in Cargo.toml when specifying dependencies
2024-06-24 13:26:49 +02:00
Kirill Chibisov
ecb887e5c3 event_loop: remove generic user event
Let the users wake up the event loop and then they could poll their
user sources.

Co-authored-by: Mads Marquart <mads@marquart.dk>
Co-authored-by: daxpedda <daxpedda@gmail.com>
2024-06-24 13:04:55 +03:00
Mads Marquart
7d1287958f Avoid path when importing modules (#3755)
Rust tooling generally works better this way. This includes
rust-analyzer, but more noticeably the output from `tracing` typically
prints the module path, which did not correspond to the actual file
system before.

Concretely, tracing output from the macOS backend changes from printing:
`winit::platform_impl::platform::util`
To printing:
`winit::platform_impl::macos::util`
2024-06-24 03:57:48 +02:00
Mads Marquart
c0c14aaf00 macOS: Call ApplicationHandler directly instead of using Event (#3753)
Additionally, always queue events in `handle_scale_factor_changed`.

These events were intentionally not queued before, since they are
executed inside `handle_scale_factor_changed`, which is itself queued.
Though that's a bit too subtle, so let's just take the minuscule perf
hit of redundantly checking whether we need to queue again.

Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
2024-06-23 23:14:22 +02:00
John Nunley
10dc0674bb ci: Use taiki-e/checkout-action
taiki-e/checkout-action has a few advantages over actions/checkout,
such as:

- It is written in Bash rather than Node.js
- It does not have frequent breaking changes, reducing churn

Signed-off-by: John Nunley <dev@notgull.net>
2024-06-23 12:28:58 -07:00
msiglreith
4f59796e8a Add notgull as Windows maintainer 2024-06-22 08:41:37 -07:00
msiglreith
32097d75c7 Update codeowner list 2024-06-22 08:41:37 -07:00
Bruce Mitchener
c6c4395c3b Use default-features, not default_features (#3746)
The latter syntax is deprecated and will be removed in Rust
2024 edition. This also generates a warning with current
versions of Rust.
2024-06-22 11:38:42 +02:00
Kirill Chibisov
38e6f9ad84 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-06-21 21:40:16 +03:00
daxpedda
3e6092b8ed Web: implement WaitUntilStrategy (#3739) 2024-06-20 23:07:42 +02:00
daxpedda
b4e83a5966 Web: set control flow strategies on EventLoop (#3740) 2024-06-20 22:56:08 +02:00
Mads Marquart
db2c97a995 macOS: set the theme on the NSWindow, instead of application-wide
This new implementation uses:
- The NSAppearanceCustomization protocol for retrieving the appearance
  of the window, instead of using the application-wide
  `-[NSApplication effectiveAppearance]`.
- Key-Value observing for observing the `effectiveAppearance` to compute
  the `ThemeChanged` event, instead of using the undocumented
  `AppleInterfaceThemeChangedNotification` notification.

This also fixes `WindowBuilder::with_theme` not having any effect, and
the conversion between `Theme` and `NSAppearance` is made a bit more
robust.
2024-06-20 17:05:34 +03:00
daxpedda
1552eb21f7 Bump MSRV to v1.73 (#3743) 2024-06-20 11:09:15 +02:00
Kirill Chibisov
d8ffd4bb26 x11: fix build on arm
The c_char type, which was used under the hood is different depending
on arch, thus use it directly instead of i8.

Fixes #3735.
2024-06-17 13:51:08 +03:00
Kirill Chibisov
34c15608e0 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-06-15 23:50:40 +03:00
Kirill Chibisov
eef6977f45 macOS: fix opacity handling
Not using `NSColor::clearColor()` results in Quartz thinking that the
window is not transparent at all, which results in artifacts.

However, not setting the `windowBackgroundColor` in
`Window::set_transparent` results in border not properly rendered.

Fixes: 94664ff687 (Don't set the background color)
2024-06-15 15:41:34 +03:00
Kirill Chibisov
078b46720b chore: address 1.79 clippy lints 2024-06-15 15:26:26 +03:00
daxpedda
3b4e064a07 Web: fix crash InnerSizeWriter::request_inner_size() (#3727) 2024-06-12 00:22:03 +02:00
daxpedda
39bc139500 Web: don't overwrite cursor with CursorIcon::Default (#3729) 2024-06-12 00:12:14 +02:00
daxpedda
9522670081 Web: queue EventLoopProxy::send_event() to microtask 2024-06-12 00:02:26 +02:00
Kirill Chibisov
9a1ef49dc3 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2024-06-10 17:13:33 -07:00
Mads Marquart
3a624e0f52 macOS/iOS: Various refactorings in application state (#3720)
I'm preparing to get rid of our application delegate in favour of registering
notification observers, to do so I'm renaming `app_delegate.rs` to
`app_state.rs`, and moving the functionality out of the Objective-C method
into a normal method.

Additionally, `AppState` previously implemented `Default`, but really, this
was a hack done because someone (probably myself) was too lazy to write out
the full initialization in `AppDelegate::new`.
2024-06-06 14:39:31 +02:00
Mads Marquart
279e3edc54 macOS: Improve event queuing (#3708)
* Use AppKit's internal queuing mechanisms

This allows events to be queued in a more consistent order, they're now
interleaved with events that we handle immediately (like redraw events),
instead of being handled afterwards.

* Only queue events if necessary

This makes the call stack / backtraces easier to understand whenever
possible, and generally improves upon the order in which events are
delivered.
2024-06-06 12:32:02 +02:00
Philippe Renon
0e74d37ff5 doc: clarify Window::pre_present_notify availability
Fixes #3703.
2024-06-06 12:11:06 +03:00
ShikiSuen
2d1382f7d6 Handle _selected_range sent to NSTextInputClient.setMarkedText(). (#3619)
Co-authored-by: Mads Marquart <mads@marquart.dk>
2024-05-31 08:54:20 +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
105 changed files with 2100 additions and 3409 deletions

9
.github/CODEOWNERS vendored
View File

@@ -2,9 +2,10 @@
/src/platform/android.rs @MarijnS95
/src/platform_impl/android @MarijnS95
# iOS
# Apple (AppKit + UIKit)
/src/platform/ios.rs @madsmtm
/src/platform_impl/ios @madsmtm
/src/platform/macos.rs @madsmtm
/src/platform_impl/apple @madsmtm
# Unix
/src/platform_impl/linux/mod.rs @kchibisov
@@ -17,10 +18,6 @@
/src/platform/x11.rs @kchibisov @notgull
/src/platform_impl/linux/x11 @kchibisov @notgull
# macOS
/src/platform/macos.rs @madsmtm
/src/platform_impl/macos @madsmtm
# Web
/src/platform/web.rs @daxpedda
/src/platform_impl/web @daxpedda

View File

@@ -42,7 +42,7 @@ jobs:
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly, '1.70.0']
toolchain: [stable, nightly, '1.73']
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -61,10 +61,10 @@ jobs:
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude:
# Android is tested on stable-3
- toolchain: '1.70.0'
- toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
include:
- toolchain: '1.70.0'
- toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'nightly'
platform: {
@@ -149,13 +149,13 @@ jobs:
- name: Test dpi crate
if: >
contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.70.0'
matrix.toolchain != '1.73'
run: cargo test -p dpi
- name: Build tests
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0'
matrix.toolchain != '1.73'
run: cargo $CMD test --no-run $OPTIONS
- name: Run tests
@@ -164,7 +164,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0'
matrix.toolchain != '1.73'
run: cargo $CMD test $OPTIONS
- name: Lint with clippy
@@ -174,7 +174,7 @@ jobs:
- name: Build tests with serde enabled
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0'
matrix.toolchain != '1.73'
run: cargo $CMD test --no-run $OPTIONS --features serde
- name: Run tests with serde enabled
@@ -183,7 +183,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
!contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.70.0'
matrix.toolchain != '1.73'
run: cargo $CMD test $OPTIONS --features serde
- name: Check docs.rs documentation
@@ -221,7 +221,7 @@ jobs:
steps:
- uses: taiki-e/checkout-action@v1
- uses: EmbarkStudios/cargo-deny-action@v2
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check
log-level: error

View File

@@ -1,6 +1,6 @@
[package]
name = "winit"
version = "0.30.8"
version = "0.30.3"
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 = [
@@ -112,21 +102,21 @@ softbuffer = { version = "0.4.0", default-features = false, features = [
"wayland-dlopen",
] }
# Android
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0"
ndk = { version = "0.9.0", default-features = false }
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
core-foundation = "0.9.3"
objc2 = "0.5.2"
# AppKit
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
block2 = "0.5.1"
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
objc2-foundation = { version = "0.2.2", features = [
"block2",
"dispatch",
"NSArray",
@@ -144,11 +134,8 @@ features = [
"NSString",
"NSThread",
"NSValue",
]
[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit]
version = "0.2.2"
features = [
] }
objc2-app-kit = { version = "0.2.2", features = [
"NSAppearance",
"NSApplication",
"NSBitmapImageRep",
@@ -175,11 +162,11 @@ features = [
"NSWindow",
"NSWindowScripting",
"NSWindowTabGroup",
]
] }
[target.'cfg(target_os = "ios")'.dependencies.objc2-foundation]
version = "0.2.2"
features = [
# UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2-foundation = { version = "0.2.2", features = [
"dispatch",
"NSArray",
"NSEnumerator",
@@ -189,18 +176,13 @@ features = [
"NSProcessInfo",
"NSThread",
"NSSet",
]
[target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit]
version = "0.2.2"
features = [
] }
objc2-ui-kit = { version = "0.2.2", features = [
"UIApplication",
"UIDevice",
"UIEvent",
"UIGeometry",
"UIGestureRecognizer",
"UITextInput",
"UITextInputTraits",
"UIOrientation",
"UIPanGestureRecognizer",
"UIPinchGestureRecognizer",
@@ -214,14 +196,12 @@ features = [
"UIView",
"UIViewController",
"UIWindow",
]
] }
# Windows
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.52.0"
features = [
windows-sys = { version = "0.52.0", features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
@@ -232,7 +212,6 @@ features = [
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_Security",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
@@ -247,12 +226,13 @@ features = [
"Win32_UI_Shell",
"Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging",
]
] }
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
# Linux
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.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 }
@@ -262,18 +242,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.5", 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.4", optional = true }
wayland-protocols = { version = "0.32.2", 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.2", features = [
wayland-protocols-plasma = { version = "0.2.0", features = [
"client",
], optional = true }
x11-dl = { version = "2.19.1", optional = true }
@@ -287,67 +267,61 @@ x11rb = { version = "0.13.0", default-features = false, features = [
], optional = true }
xkbcommon-dl = "0.4.2"
# Orbital
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.4.1"
# Web
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.70"
pin-project = "1"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
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",
web_sys = { package = "web-sys", version = "0.3.64", 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',
'Node',
'PageTransitionEvent',
'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'VisibilityState',
'Window',
'WheelEvent',
'Worker',
'Url',
] }
js-sys = "0.3.64"
pin-project = "1"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-time = "1"
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
atomic-waker = "1"
@@ -366,7 +340,7 @@ resolver = "2"
members = ["dpi"]
[workspace.package]
rust-version = "1.70.0"
rust-version = "1.73"
repository = "https://github.com/rust-windowing/winit"
license = "Apache-2.0"
edition = "2021"

View File

@@ -154,7 +154,6 @@ If your PR makes notable changes to Winit's features, please update this section
* Home indicator visibility
* Status bar visibility and style
* Deferring system gestures
* Getting the device idiom
* Getting the preferred video mode
### Web

View File

@@ -8,7 +8,7 @@
```toml
[dependencies]
winit = "0.30.8"
winit = "0.30.3"
```
## [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).
@@ -35,7 +35,7 @@ another library.
## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
This crate's Minimum Supported Rust Version (MSRV) is **1.73**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following

View File

@@ -10,10 +10,9 @@ fn main() {
android_platform: { target_os = "android" },
web_platform: { all(target_family = "wasm", target_os = "unknown") },
macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" },
ios_platform: { all(target_vendor = "apple", not(target_os = "macos")) },
windows_platform: { target_os = "windows" },
apple: { any(target_os = "ios", target_os = "macos") },
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
free_unix: { all(unix, not(target_vendor = "apple"), not(android_platform), not(target_os = "emscripten")) },
redox: { target_os = "redox" },
// Native displays.

View File

@@ -12,4 +12,6 @@ disallowed-methods = [
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
{ path = "core_foundation::runloop::kCFRunLoopCommonModes", reason = "Using the common modes includes the modal panel mode, which we usually want to avoid" },
{ path = "objc2_foundation::NSRunLoopCommonModes", reason = "Using the common modes includes the modal panel mode, which we usually want to avoid" }
]

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,52 +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 = ["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::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

@@ -31,8 +31,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;
@@ -46,7 +44,7 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing::init();
let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
let event_loop = EventLoop::new()?;
let _event_loop_proxy = event_loop.create_proxy();
// Wire the user event from another thread.
@@ -56,7 +54,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// from a different thread.
info!("Starting to send user event every second");
loop {
let _ = _event_loop_proxy.send_event(UserEvent::WakeUp);
_event_loop_proxy.wake_up();
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
@@ -66,12 +64,6 @@ fn main() -> Result<(), Box<dyn Error>> {
event_loop.run_app(&mut state).map_err(Into::into)
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
enum UserEvent {
WakeUp,
}
/// Application state and event handling.
struct Application {
/// Custom cursors assets.
@@ -87,7 +79,7 @@ struct Application {
}
impl Application {
fn new<T>(event_loop: &EventLoop<T>) -> Self {
fn new(event_loop: &EventLoop) -> Self {
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
#[cfg(not(any(android_platform, ios_platform)))]
let context = Some(
@@ -142,28 +134,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);
@@ -332,9 +302,9 @@ impl Application {
}
}
impl ApplicationHandler<UserEvent> for Application {
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
info!("User event: {event:?}");
impl ApplicationHandler for Application {
fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
info!("User wake up");
}
fn window_event(

View File

@@ -5,7 +5,7 @@ use crate::event_loop::ActiveEventLoop;
use crate::window::WindowId;
/// The handler of the application events.
pub trait ApplicationHandler<T: 'static = ()> {
pub trait ApplicationHandler {
/// Emitted when new events arrive from the OS to be processed.
///
/// This is a useful place to put code that should be done before you start processing
@@ -82,11 +82,95 @@ pub trait ApplicationHandler<T: 'static = ()> {
/// [`Suspended`]: Self::suspended
fn resumed(&mut self, event_loop: &ActiveEventLoop);
/// Emitted when an event is sent from [`EventLoopProxy::send_event`].
/// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
///
/// [`EventLoopProxy::send_event`]: crate::event_loop::EventLoopProxy::send_event
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
let _ = (event_loop, event);
/// Multiple calls to the aforementioned method will be merged, and will only wake the event
/// loop once; however, due to the nature of multi-threading some wake ups may appear
/// spuriously. For these reasons, you should not rely on the number of times that this was
/// called.
///
/// The order in which this is emitted in relation to other events is not guaranteed. The time
/// at which this will be emitted is not guaranteed, only that it will happen "soon". That is,
/// there may be several executions of the event loop, including multiple redraws to windows,
/// between [`EventLoopProxy::wake_up()`] being called and the event being delivered.
///
/// [`EventLoopProxy::wake_up()`]: crate::event_loop::EventLoopProxy::wake_up
///
/// # Example
///
/// Use a [`std::sync::mpsc`] channel to handle events from a different thread.
///
/// ```no_run
/// use std::sync::mpsc;
/// use std::thread;
/// use std::time::Duration;
///
/// use winit::application::ApplicationHandler;
/// use winit::event_loop::{ActiveEventLoop, EventLoop};
///
/// struct MyApp {
/// receiver: mpsc::Receiver<u64>,
/// }
///
/// impl ApplicationHandler for MyApp {
/// # fn window_event(
/// # &mut self,
/// # _event_loop: &ActiveEventLoop,
/// # _window_id: winit::window::WindowId,
/// # _event: winit::event::WindowEvent,
/// # ) {
/// # }
/// #
/// # fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}
/// #
/// fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
/// // Iterate current events, since wake-ups may have been merged.
/// //
/// // Note: We take care not to use `recv` or `iter` here, as those are blocking,
/// // and that would be bad for performance and might lead to a deadlock.
/// for i in self.receiver.try_iter() {
/// println!("received: {i}");
/// }
/// }
///
/// // Rest of `ApplicationHandler`
/// }
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let event_loop = EventLoop::new()?;
///
/// let (sender, receiver) = mpsc::channel();
///
/// let mut app = MyApp { receiver };
///
/// // Send an event in a loop
/// let proxy = event_loop.create_proxy();
/// let background_thread = thread::spawn(move || {
/// let mut i = 0;
/// loop {
/// println!("sending: {i}");
/// if sender.send(i).is_err() {
/// // Stop sending once `MyApp` is dropped
/// break;
/// }
/// // Trigger the wake-up _after_ we placed the event in the channel.
/// // Otherwise, `proxy_wake_up` might be triggered prematurely.
/// proxy.wake_up();
/// i += 1;
/// thread::sleep(Duration::from_secs(1));
/// }
/// });
///
/// event_loop.run_app(&mut app)?;
///
/// drop(app);
/// background_thread.join().unwrap();
///
/// Ok(())
/// }
/// ```
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted when the OS sends an event to a winit window.
@@ -224,7 +308,7 @@ pub trait ApplicationHandler<T: 'static = ()> {
}
}
impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for &mut A {
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
#[inline]
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
@@ -236,8 +320,8 @@ impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for &m
}
#[inline]
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
(**self).user_event(event_loop, event);
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
}
#[inline]
@@ -281,7 +365,7 @@ impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for &m
}
}
impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for Box<A> {
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
#[inline]
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
@@ -293,8 +377,8 @@ impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for Bo
}
#[inline]
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
(**self).user_event(event_loop, event);
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
}
#[inline]

View File

@@ -39,3 +39,27 @@ The migration guide could reference other migration examples in the current
changelog entry.
## Unreleased
### Changed
- On Web, let events wake up event loop immediately when using `ControlFlow::Poll`.
- Bump MSRV from `1.70` to `1.73`.
- Changed `ApplicationHandler::user_event` to `user_wake_up`, removing the
generic user event.
Winit will now only indicate that wake up happened, you will have to pair
this with an external mechanism like `std::sync::mpsc::channel` if you want
to send specific data to be processed on the main thread.
- Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now
only wakes up the loop.
### Removed
- Remove `EventLoop::run`.
- Remove `EventLoopExtRunOnDemand::run_on_demand`.
- Remove `EventLoopExtPumpEvents::pump_events`.
- Remove `Event`.
- On iOS, remove `platform::ios::EventLoopExtIOS` and related `platform::ios::Idiom` type.
This feature was incomplete, and the equivalent functionality can be trivially achieved outside
of `winit` using `objc2-ui-kit` and calling `UIDevice::currentDevice().userInterfaceIdiom()`.

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,76 +1,3 @@
## 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
@@ -108,6 +35,7 @@
- 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`.
- Add traits `EventLoopExtWayland` and `EventLoopExtX11`, providing methods `is_wayland` and `is_x11` on `EventLoop`.
### Fixed
@@ -184,7 +112,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;
@@ -220,7 +148,7 @@
we move particular `match event` arms into methods on `ApplicationHandler`,
for example:
```rust,no_run
```rust,no_run,ignore
use winit::application::ApplicationHandler;
use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId};
use winit::event_loop::{EventLoop, ActiveEventLoop};
@@ -283,7 +211,7 @@
Using the migration example from above, you can change your code as follows:
```rust,no_run
```rust,no_run,ignore
use winit::application::ApplicationHandler;
use winit::event::{Event, WindowEvent, DeviceEvent, DeviceId};
use winit::event_loop::{EventLoop, ActiveEventLoop};

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

@@ -1,4 +1,4 @@
//! The [`Event`] enum and assorted supporting types.
//! The event enums and assorted supporting types.
//!
//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get
//! processed and used to modify the program state. For more details, see the root-level
@@ -54,11 +54,15 @@ use crate::platform_impl;
use crate::window::Window;
use crate::window::{ActivationToken, Theme, WindowId};
// TODO: Remove once the backends can call `ApplicationHandler` methods directly. For now backends
// like Windows and Web require `Event` to wire user events, otherwise each backend will have to
// wrap `Event` in some other structure.
/// Describes a generic event.
///
/// See the module-level docs for more information on the event loop manages each event.
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum Event<T: 'static> {
pub(crate) enum Event {
/// See [`ApplicationHandler::new_events`] for details.
///
/// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events
@@ -67,18 +71,15 @@ pub enum Event<T: 'static> {
/// See [`ApplicationHandler::window_event`] for details.
///
/// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event
#[allow(clippy::enum_variant_names)]
WindowEvent { window_id: WindowId, event: WindowEvent },
/// See [`ApplicationHandler::device_event`] for details.
///
/// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event
#[allow(clippy::enum_variant_names)]
DeviceEvent { device_id: DeviceId, event: DeviceEvent },
/// See [`ApplicationHandler::user_event`] for details.
///
/// [`ApplicationHandler::user_event`]: crate::application::ApplicationHandler::user_event
UserEvent(T),
/// See [`ApplicationHandler::suspended`] for details.
///
/// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended
@@ -103,24 +104,9 @@ pub enum Event<T: 'static> {
///
/// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning
MemoryWarning,
}
impl<T> Event<T> {
#[allow(clippy::result_large_err)]
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
use self::Event::*;
match self {
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
AboutToWait => Ok(AboutToWait),
LoopExiting => Ok(LoopExiting),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
MemoryWarning => Ok(MemoryWarning),
}
}
/// User requested a wake up.
UserWakeUp,
}
/// Describes the reason the event loop is resuming.
@@ -449,13 +435,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 +521,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 +547,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 +556,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 +839,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 +1007,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,8 +1017,7 @@ mod tests {
use crate::window::WindowId;
// Mainline events.
let wid = WindowId::dummy();
x(UserEvent(()));
let wid = unsafe { WindowId::dummy() };
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
x(LoopExiting);
@@ -1112,25 +1101,12 @@ mod tests {
#[allow(clippy::redundant_clone)]
#[test]
fn test_event_clone() {
foreach_event!(|event: event::Event<()>| {
foreach_event!(|event: event::Event| {
let event2 = event.clone();
assert_eq!(event, event2);
})
}
#[test]
fn test_map_nonuser_event() {
foreach_event!(|event: event::Event<()>| {
let is_user = matches!(event, event::Event::UserEvent(()));
let event2 = event.map_nonuser_event::<()>();
if is_user {
assert_eq!(event2, Err(event::Event::UserEvent(())));
} else {
assert!(event2.is_ok());
}
})
}
#[test]
fn test_force_normalize() {
let force = event::Force::Normalized(0.0);
@@ -1151,12 +1127,12 @@ mod tests {
#[allow(clippy::clone_on_copy)]
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event<()>| {
foreach_event!(|event: event::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

@@ -3,15 +3,16 @@
//!
//! If you want to send custom events to the event loop, use
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
//! [`send_event`][EventLoopProxy::send_event] method.
//! [`wake_up`][EventLoopProxy::wake_up] method. Then during handling the wake up
//! you can poll your event sources.
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
use std::fmt;
use std::marker::PhantomData;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::{error, fmt};
#[cfg(not(web_platform))]
use std::time::{Duration, Instant};
@@ -20,10 +21,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.
@@ -40,8 +40,8 @@ use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttri
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
///
/// [`Window`]: crate::window::Window
pub struct EventLoop<T: 'static> {
pub(crate) event_loop: platform_impl::EventLoop<T>,
pub struct EventLoop {
pub(crate) event_loop: platform_impl::EventLoop,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}
@@ -59,16 +59,15 @@ pub struct ActiveEventLoop {
/// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported.
///
/// This can be created using [`EventLoop::new`] or [`EventLoop::with_user_event`].
/// This can be created using [`EventLoop::builder`].
#[derive(Default)]
pub struct EventLoopBuilder<T: 'static> {
pub struct EventLoopBuilder {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
_p: PhantomData<T>,
}
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl EventLoopBuilder<()> {
impl EventLoopBuilder {
/// Start building a new event loop.
#[inline]
#[deprecated = "use `EventLoop::builder` instead"]
@@ -77,7 +76,7 @@ impl EventLoopBuilder<()> {
}
}
impl<T> EventLoopBuilder<T> {
impl EventLoopBuilder {
/// Builds a new event loop.
///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
@@ -112,7 +111,7 @@ impl<T> EventLoopBuilder<T> {
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
pub fn build(&mut self) -> Result<EventLoop<T>, EventLoopError> {
pub fn build(&mut self) -> Result<EventLoop, EventLoopError> {
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
@@ -133,7 +132,7 @@ impl<T> EventLoopBuilder<T> {
}
}
impl<T> fmt::Debug for EventLoop<T> {
impl fmt::Debug for EventLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoop { .. }")
}
@@ -147,11 +146,12 @@ impl fmt::Debug for ActiveEventLoop {
/// Set through [`ActiveEventLoop::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
///
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
/// [`about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
@@ -189,12 +189,12 @@ impl ControlFlow {
}
}
impl EventLoop<()> {
impl EventLoop {
/// Create the event loop.
///
/// This is an alias of `EventLoop::builder().build()`.
#[inline]
pub fn new() -> Result<EventLoop<()>, EventLoopError> {
pub fn new() -> Result<EventLoop, EventLoopError> {
Self::builder().build()
}
@@ -204,33 +204,12 @@ impl EventLoop<()> {
///
/// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that.
#[inline]
pub fn builder() -> EventLoopBuilder<()> {
Self::with_user_event()
pub fn builder() -> EventLoopBuilder {
EventLoopBuilder { platform_specific: Default::default() }
}
}
impl<T> EventLoop<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
pub fn with_user_event() -> EventLoopBuilder<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)
}
impl EventLoop {
/// 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.
@@ -261,13 +240,13 @@ impl<T> EventLoop<T> {
/// [^1]: `EventLoopExtWebSys::spawn_app()` is only available on Web.
#[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))
pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> Result<(), EventLoopError> {
self.event_loop.run_app(app)
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
pub fn create_proxy(&self) -> EventLoopProxy<T> {
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy { event_loop_proxy: self.event_loop.create_proxy() }
}
@@ -323,14 +302,14 @@ impl<T> EventLoop<T> {
}
#[cfg(feature = "rwh_06")]
impl<T> rwh_06::HasDisplayHandle for EventLoop<T> {
impl rwh_06::HasDisplayHandle for EventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
rwh_06::HasDisplayHandle::display_handle(self.event_loop.window_target())
}
}
#[cfg(feature = "rwh_05")]
unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
unsafe impl rwh_05::HasRawDisplayHandle for EventLoop {
/// Returns a [`rwh_05::RawDisplayHandle`] for the event loop.
fn raw_display_handle(&self) -> rwh_05::RawDisplayHandle {
rwh_05::HasRawDisplayHandle::raw_display_handle(self.event_loop.window_target())
@@ -338,7 +317,7 @@ unsafe impl<T> rwh_05::HasRawDisplayHandle for EventLoop<T> {
}
#[cfg(any(x11_platform, wayland_platform))]
impl<T> AsFd for EventLoop<T> {
impl AsFd for EventLoop {
/// Get the underlying [EventLoop]'s `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API.
@@ -352,7 +331,7 @@ impl<T> AsFd for EventLoop<T> {
}
#[cfg(any(x11_platform, wayland_platform))]
impl<T> AsRawFd for EventLoop<T> {
impl AsRawFd for EventLoop {
/// Get the underlying [EventLoop]'s raw `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API.
@@ -437,17 +416,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 +428,7 @@ impl ActiveEventLoop {
/// This exits the event loop.
///
/// See [`LoopExiting`][Event::LoopExiting].
/// See [`exiting`][crate::application::ApplicationHandler::exiting].
pub fn exit(&self) {
let _span = tracing::debug_span!("winit::ActiveEventLoop::exit",).entered();
@@ -501,7 +469,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
@@ -545,53 +513,39 @@ unsafe impl rwh_05::HasRawDisplayHandle for OwnedDisplayHandle {
}
}
/// Used to send custom events to [`EventLoop`].
pub struct EventLoopProxy<T: 'static> {
event_loop_proxy: platform_impl::EventLoopProxy<T>,
/// Control the [`EventLoop`], possibly from a different thread, without referencing it directly.
#[derive(Clone)]
pub struct EventLoopProxy {
event_loop_proxy: platform_impl::EventLoopProxy,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self { event_loop_proxy: self.event_loop_proxy.clone() }
impl EventLoopProxy {
/// Wake up the [`EventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being
/// called.
///
/// Calls to this method are coalesced into a single call to [`proxy_wake_up`], see the
/// documentation on that for details.
///
/// If the event loop is no longer running, this is a no-op.
///
/// [`proxy_wake_up`]: ApplicationHandler::proxy_wake_up
///
/// # Platform-specific
///
/// - **Windows**: The wake-up may be ignored under high contention, see [#3687].
///
/// [#3687]: https://github.com/rust-windowing/winit/pull/3687
pub fn wake_up(&self) {
self.event_loop_proxy.wake_up();
}
}
impl<T: 'static> EventLoopProxy<T> {
/// Send an event to the [`EventLoop`] from which this proxy was created. This emits a
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
/// function.
///
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
///
/// [`UserEvent(event)`]: Event::UserEvent
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
let _span = tracing::debug_span!("winit::EventLoopProxy::send_event",).entered();
self.event_loop_proxy.send_event(event)
}
}
impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
impl fmt::Debug for EventLoopProxy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("EventLoopProxy { .. }")
}
}
/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
/// no longer exists.
///
/// Contains the original event given to [`EventLoopProxy::send_event`].
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventLoopClosed<T>(pub T);
impl<T> fmt::Display for EventLoopClosed<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Tried to wake up a closed `EventLoop`")
}
}
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
/// Control when device events are captured.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub enum DeviceEvents {
@@ -629,23 +583,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

@@ -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`].
@@ -24,12 +19,11 @@
//! window or a key getting pressed while the window is focused. Devices can generate
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. You can also create and handle your own custom [`Event::UserEvent`]s, if
//! desired.
//! [`DeviceEvent`].
//!
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`Event::LoopExiting`].
//! will run until [`exit()`] is used, at which point [`exiting()`] is called.
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works
@@ -89,22 +83,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
@@ -171,7 +162,7 @@
//! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent
//! [`Event::UserEvent`]: event::Event::UserEvent
//! [`Event::LoopExiting`]: event::Event::LoopExiting
//! [`exiting()`]: crate::application::ApplicationHandler::exiting
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.

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.8",
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.3",
//! 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 EventLoopExtAndroid for EventLoop {}
/// 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
@@ -133,7 +119,7 @@ pub trait EventLoopBuilderExtAndroid {
fn handle_volume_keys(&mut self) -> &mut Self;
}
impl<T> EventLoopBuilderExtAndroid for EventLoopBuilder<T> {
impl EventLoopBuilderExtAndroid for EventLoopBuilder {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
@@ -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

@@ -66,22 +66,9 @@
use std::os::raw::c_void;
use crate::event_loop::EventLoop;
use crate::monitor::{MonitorHandle, VideoModeHandle};
use crate::window::{Window, WindowAttributes};
/// Additional methods on [`EventLoop`] that are specific to iOS.
pub trait EventLoopExtIOS {
/// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
fn idiom(&self) -> Idiom;
}
impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
fn idiom(&self) -> Idiom {
self.event_loop.idiom()
}
}
/// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
@@ -379,24 +366,6 @@ pub enum ValidOrientations {
Portrait,
}
/// The device [idiom].
///
/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Idiom {
Unspecified,
/// iPhone and iPod touch.
Phone,
/// iPad.
Pad,
/// tvOS and Apple TV.
TV,
CarPlay,
}
bitflags::bitflags! {
/// The [edges] of a screen.
///

View File

@@ -94,12 +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 Borderless Fullscreen mode. Useful for games.
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 {
@@ -172,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`.
@@ -232,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 {
@@ -302,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
///
@@ -365,10 +338,10 @@ pub trait EventLoopBuilderExtMacOS {
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
impl EventLoopBuilderExtMacOS for EventLoopBuilder {
#[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,14 +1,10 @@
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`].
type UserEvent: 'static;
/// Pump the `EventLoop` to check for and dispatch pending events.
///
/// This API is designed to enable applications to integrate Winit into an
@@ -52,18 +48,18 @@ pub trait EventLoopExtPumpEvents {
/// - `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.
/// 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.
/// 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.
/// 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 +70,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
///
@@ -103,34 +99,20 @@ pub trait EventLoopExtPumpEvents {
/// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect`
/// callback.
fn pump_app_events<A: ApplicationHandler<Self::UserEvent>>(
fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus;
}
impl EventLoopExtPumpEvents for EventLoop {
fn pump_app_events<A: ApplicationHandler>(
&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);
}
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)
self.event_loop.pump_app_events(timeout, app)
}
}

View File

@@ -1,24 +1,12 @@
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};
/// 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);
/// Run the application with the event loop on the calling thread.
///
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
@@ -42,9 +30,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
@@ -67,26 +53,19 @@ pub trait EventLoopExtRunOnDemand {
///
/// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError>;
}
impl EventLoopExtRunOnDemand for EventLoop {
fn run_app_on_demand<A: ApplicationHandler>(
&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)
})
}
}
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),
{
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

@@ -38,7 +38,7 @@ pub trait EventLoopExtWayland {
fn is_wayland(&self) -> bool;
}
impl<T: 'static> EventLoopExtWayland for EventLoop<T> {
impl EventLoopExtWayland for EventLoop {
#[inline]
fn is_wayland(&self) -> bool {
self.event_loop.is_wayland()
@@ -57,7 +57,7 @@ pub trait EventLoopBuilderExtWayland {
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
impl EventLoopBuilderExtWayland for EventLoopBuilder {
#[inline]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);

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,9 +155,6 @@ 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`.
type UserEvent: 'static;
/// Initializes the winit event loop.
///
/// Unlike
@@ -181,15 +177,7 @@ pub trait EventLoopExtWebSys {
doc = "[`run_app()`]: EventLoop::run_app()"
)]
/// [^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);
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
/// Sets the strategy for [`ControlFlow::Poll`].
///
@@ -220,20 +208,9 @@ pub trait EventLoopExtWebSys {
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)
impl EventLoopExtWebSys for EventLoop {
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
self.event_loop.spawn_app(app);
}
fn set_poll_strategy(&self, strategy: PollStrategy) {
@@ -422,7 +399,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 +438,3 @@ impl Display for CustomCursorError {
}
}
}
impl Error for CustomCursorError {}

View File

@@ -227,7 +227,7 @@ pub trait EventLoopBuilderExtWindows {
F: FnMut(*const c_void) -> bool + 'static;
}
impl<T> EventLoopBuilderExtWindows for EventLoopBuilder<T> {
impl EventLoopBuilderExtWindows for EventLoopBuilder {
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;

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.
@@ -103,7 +105,7 @@ pub trait EventLoopExtX11 {
fn is_x11(&self) -> bool;
}
impl<T: 'static> EventLoopExtX11 for EventLoop<T> {
impl EventLoopExtX11 for EventLoop {
#[inline]
fn is_x11(&self) -> bool {
!self.event_loop.is_wayland()
@@ -122,7 +124,7 @@ pub trait EventLoopBuilderExtX11 {
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl<T> EventLoopBuilderExtX11 for EventLoopBuilder<T> {
impl EventLoopBuilderExtX11 for EventLoopBuilder {
#[inline]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X);

View File

@@ -3,7 +3,7 @@ use std::collections::VecDeque;
use std::hash::Hash;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc, Mutex};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
@@ -12,12 +12,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::{
@@ -41,41 +42,6 @@ fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}
struct PeekableReceiver<T> {
recv: mpsc::Receiver<T>,
first: Option<T>,
}
impl<T> PeekableReceiver<T> {
pub fn from_recv(recv: mpsc::Receiver<T>) -> Self {
Self { recv, first: None }
}
pub fn has_incoming(&mut self) -> bool {
if self.first.is_some() {
return true;
}
match self.recv.try_recv() {
Ok(v) => {
self.first = Some(v);
true
},
Err(mpsc::TryRecvError::Empty) => false,
Err(mpsc::TryRecvError::Disconnected) => {
warn!("Channel was disconnected when checking incoming");
false
},
}
}
pub fn try_recv(&mut self) -> Result<T, mpsc::TryRecvError> {
if let Some(first) = self.first.take() {
return Ok(first);
}
self.recv.try_recv()
}
}
#[derive(Clone)]
struct SharedFlagSetter {
flag: Arc<AtomicBool>,
@@ -131,13 +97,12 @@ impl RedrawRequester {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {}
pub struct EventLoop<T: 'static> {
pub(crate) android_app: AndroidApp,
pub struct EventLoop {
android_app: AndroidApp,
window_target: event_loop::ActiveEventLoop,
redraw_flag: SharedFlag,
user_events_sender: mpsc::Sender<T>,
user_events_receiver: PeekableReceiver<T>, // must wake looper whenever something gets sent
loop_running: bool, // Dispatched `NewEvents<Init>`
proxy_wake_up: Arc<AtomicBool>,
loop_running: bool, // Dispatched `NewEvents<Init>`
running: bool,
pending_redraw: bool,
cause: StartCause,
@@ -157,11 +122,11 @@ impl Default for PlatformSpecificEventLoopAttributes {
}
}
impl<T: 'static> EventLoop<T> {
impl EventLoop {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let (user_events_sender, user_events_receiver) = mpsc::channel();
let proxy_wake_up = Arc::new(AtomicBool::new(false));
let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
@@ -184,8 +149,7 @@ impl<T: 'static> EventLoop<T> {
_marker: PhantomData,
},
redraw_flag,
user_events_sender,
user_events_receiver: PeekableReceiver::from_recv(user_events_receiver),
proxy_wake_up,
loop_running: false,
running: false,
pending_redraw: false,
@@ -195,27 +159,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>(
&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 +189,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 +207,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 +267,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;
@@ -322,11 +278,8 @@ 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());
}
if self.proxy_wake_up.swap(false, Ordering::Relaxed) {
app.proxy_wake_up(self.window_target());
}
if self.running {
@@ -338,39 +291,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>(
&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 +354,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 +391,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 +418,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>(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>(
&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 +441,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>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
if !self.loop_running {
self.loop_running = true;
@@ -514,18 +456,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,32 +475,34 @@ 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>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
let start = Instant::now();
self.pending_redraw |= self.redraw_flag.get_and_reset();
timeout =
if self.running && (self.pending_redraw || self.user_events_receiver.has_incoming()) {
// If we already have work to do then we don't want to block on the next poll
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
},
};
min_timeout(control_flow_timeout, timeout)
timeout = if self.running
&& (self.pending_redraw || self.proxy_wake_up.load(Ordering::Relaxed))
{
// If we already have work to do then we don't want to block on the next poll
Some(Duration::ZERO)
} else {
let control_flow_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Duration::ZERO),
ControlFlow::WaitUntil(wait_deadline) => {
Some(wait_deadline.saturating_duration_since(start))
},
};
let app = self.android_app.clone(); // Don't borrow self as part of poll expression
app.poll_events(timeout, |poll_event| {
min_timeout(control_flow_timeout, timeout)
};
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 {
@@ -573,7 +517,7 @@ impl<T: 'static> EventLoop<T> {
// We also ignore wake ups while suspended.
self.pending_redraw |= self.redraw_flag.get_and_reset();
if !self.running
|| (!self.pending_redraw && !self.user_events_receiver.has_incoming())
|| (!self.pending_redraw && !self.proxy_wake_up.load(Ordering::Relaxed))
{
return;
}
@@ -599,7 +543,7 @@ impl<T: 'static> EventLoop<T> {
},
};
self.single_iteration(main_event, &mut callback);
self.single_iteration(main_event, app);
});
}
@@ -607,9 +551,9 @@ impl<T: 'static> EventLoop<T> {
&self.window_target
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
proxy_wake_up: self.proxy_wake_up.clone(),
waker: self.android_app.create_waker(),
}
}
@@ -623,30 +567,21 @@ impl<T: 'static> EventLoop<T> {
}
}
pub struct EventLoopProxy<T: 'static> {
user_events_sender: mpsc::Sender<T>,
#[derive(Clone)]
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
waker: AndroidAppWaker,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
waker: self.waker.clone(),
}
}
}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
self.user_events_sender.send(event).map_err(|err| event_loop::EventLoopClosed(err.0))?;
impl EventLoopProxy {
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, Ordering::Relaxed);
self.waker.wake();
Ok(())
}
}
pub struct ActiveEventLoop {
pub(crate) app: AndroidApp,
app: AndroidApp,
control_flow: Cell<ControlFlow>,
exit: Cell<bool>,
redraw_requester: RedrawRequester,
@@ -677,11 +612,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 +838,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

@@ -5,6 +5,7 @@ use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, N
use objc2_foundation::{MainThreadMarker, NSObject};
use super::app_state::ApplicationDelegate;
use super::DEVICE_ID;
use crate::event::{DeviceEvent, ElementState};
declare_class!(
@@ -57,29 +58,47 @@ 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.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, 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.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, 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.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseMotion {
delta: (delta_x, delta_y),
});
});
}
},
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
delegate.maybe_queue_device_event(DeviceEvent::Button {
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Pressed,
let button = unsafe { event.buttonNumber() } as u32;
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
button,
state: ElementState::Pressed,
});
});
},
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
delegate.maybe_queue_device_event(DeviceEvent::Button {
button: unsafe { event.buttonNumber() } as u32,
state: ElementState::Released,
let button = unsafe { event.buttonNumber() } as u32;
delegate.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, DEVICE_ID, DeviceEvent::Button {
button,
state: ElementState::Released,
});
});
},
_ => (),

View File

@@ -1,29 +1,32 @@
use std::cell::{Cell, RefCell};
use std::mem;
use std::rc::Weak;
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use std::time::Instant;
use objc2::rc::Retained;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate, NSRunningApplication,
};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
use crate::application::ApplicationHandler;
use crate::event::{StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
use super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::observer::{EventLoopWaker, RunLoop};
use super::{menu, WindowId, DEVICE_ID};
use crate::event::{DeviceEvent, Event, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
use super::{menu, WindowId};
#[derive(Debug)]
pub(super) struct AppState {
activation_policy: Option<NSApplicationActivationPolicy>,
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
activate_ignoring_other_apps: bool,
run_loop: RunLoop,
proxy_wake_up: Arc<AtomicBool>,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
stop_before_wait: Cell<bool>,
@@ -76,12 +79,14 @@ declare_class!(
impl ApplicationDelegate {
pub(super) fn new(
mtm: MainThreadMarker,
activation_policy: Option<NSApplicationActivationPolicy>,
activation_policy: NSApplicationActivationPolicy,
proxy_wake_up: Arc<AtomicBool>,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Retained<Self> {
let this = mtm.alloc().set_ivars(AppState {
activation_policy,
proxy_wake_up,
default_menu,
activate_ignoring_other_apps,
run_loop: RunLoop::main(mtm),
@@ -113,24 +118,7 @@ impl ApplicationDelegate {
// 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);
}
}
app.setActivationPolicy(self.ivars().activation_policy);
window_activation_hack(&app);
#[allow(deprecated)]
@@ -185,7 +173,7 @@ impl ApplicationDelegate {
/// of the given closure.
pub fn set_event_handler<R>(
&self,
handler: impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop),
handler: &mut dyn ApplicationHandler,
closure: impl FnOnce() -> R,
) -> R {
self.ivars().event_handler.set(handler, closure)
@@ -219,7 +207,9 @@ 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::LoopExiting);
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
self.set_is_running(false);
self.set_stop_on_redraw(false);
@@ -260,26 +250,13 @@ 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 handle_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.handle_event(Event::WindowEvent { window_id: RootWindowId(window_id), event });
}
pub fn maybe_queue_device_event(&self, event: DeviceEvent) {
self.maybe_queue_event(Event::DeviceEvent { device_id: DEVICE_ID, event });
}
pub fn handle_redraw(&self, window_id: WindowId) {
let mtm = MainThreadMarker::from(self);
// Redraw request might come out of order from the OS.
// -> Don't go back into the event handler when our callstack originates from there
if !self.ivars().event_handler.in_use() {
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event: WindowEvent::RedrawRequested,
self.with_handler(|app, event_loop| {
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
});
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
@@ -301,7 +278,10 @@ impl ApplicationDelegate {
}
#[track_caller]
fn maybe_queue_event(&self, event: Event<HandlePendingUserEvents>) {
pub fn maybe_queue_with_handler(
&self,
callback: impl FnOnce(&mut dyn ApplicationHandler, &RootActiveEventLoop) + 'static,
) {
// 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.
//
@@ -309,25 +289,31 @@ impl ApplicationDelegate {
// 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);
self.with_handler(callback);
} else {
tracing::debug!(?event, "had to queue event since another is currently being handled");
tracing::debug!("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));
self.ivars().run_loop.queue_closure(move || {
this.with_handler(callback);
});
}
}
#[track_caller]
fn handle_event(&self, event: Event<HandlePendingUserEvents>) {
self.ivars().event_handler.handle_event(event, &ActiveEventLoop::new_root(self.retain()))
fn with_handler(
&self,
callback: impl FnOnce(&mut dyn ApplicationHandler, &RootActiveEventLoop),
) {
let event_loop = ActiveEventLoop::new_root(self.retain());
self.ivars().event_handler.handle(callback, &event_loop);
}
/// dispatch `NewEvents(Init)` + `Resumed`
pub fn dispatch_init_events(&self) {
self.handle_event(Event::NewEvents(StartCause::Init));
self.with_handler(|app, event_loop| app.new_events(event_loop, StartCause::Init));
// NB: For consistency all platforms must emit a 'resumed' event even though macOS
// applications don't themselves have a formal suspend/resume lifecycle.
self.handle_event(Event::Resumed);
self.with_handler(|app, event_loop| app.resumed(event_loop));
}
// Called by RunLoopObserver after finishing waiting for new events
@@ -337,8 +323,7 @@ impl ApplicationDelegate {
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
if panic_info.is_panicking() || !self.is_running() {
return;
}
@@ -360,7 +345,7 @@ impl ApplicationDelegate {
},
};
self.handle_event(Event::NewEvents(cause));
self.with_handler(|app, event_loop| app.new_events(event_loop, cause));
}
// Called by RunLoopObserver before waiting for new events
@@ -370,24 +355,23 @@ impl ApplicationDelegate {
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
// we're about to return to the `CFRunLoop` to poll for new events?
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
if panic_info.is_panicking() || !self.is_running() {
return;
}
self.handle_event(Event::UserEvent(HandlePendingUserEvents));
if self.ivars().proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}
let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut());
for window_id in redraw {
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event: WindowEvent::RedrawRequested,
self.with_handler(|app, event_loop| {
app.window_event(event_loop, RootWindowId(window_id), WindowEvent::RedrawRequested);
});
}
self.handle_event(Event::AboutToWait);
self.with_handler(|app, event_loop| {
app.about_to_wait(event_loop);
});
if self.exiting() {
let app = NSApplication::sharedApplication(mtm);
@@ -409,9 +393,6 @@ impl ApplicationDelegate {
}
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
/// Returns the minimum `Option<Instant>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)

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

@@ -7,12 +7,12 @@ use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_foundation::{run_on_main, NSPoint};
use smol_str::SmolStr;
use super::ffi;
use crate::event::{ElementState, KeyEvent, Modifiers};
use crate::keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode,
PhysicalKey,
};
use crate::platform_impl::platform::ffi;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {
@@ -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,32 +1,31 @@
use std::cell::RefCell;
use std::{fmt, mem};
use super::app_state::HandlePendingUserEvents;
use crate::event::Event;
use crate::application::ApplicationHandler;
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
struct EventHandlerData {
#[allow(clippy::type_complexity)]
handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'static>,
}
impl fmt::Debug for EventHandlerData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventHandlerData").finish_non_exhaustive()
}
}
#[derive(Debug)]
#[derive(Default)]
pub(crate) struct EventHandler {
/// This can be in the following states:
/// - Not registered by the event loop (None).
/// - Present (Some(handler)).
/// - Currently executing the handler / in use (RefCell borrowed).
inner: RefCell<Option<EventHandlerData>>,
inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
}
impl fmt::Debug for EventHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = match self.inner.try_borrow().as_deref() {
Ok(Some(_)) => "<available>",
Ok(None) => "<not set>",
Err(_) => "<in use>",
};
f.debug_struct("EventHandler").field("state", &state).finish_non_exhaustive()
}
}
impl EventHandler {
pub(crate) const fn new() -> Self {
pub(crate) fn new() -> Self {
Self { inner: RefCell::new(None) }
}
@@ -37,7 +36,7 @@ impl EventHandler {
/// from within the closure.
pub(crate) fn set<'handler, R>(
&self,
handler: impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'handler,
app: &'handler mut dyn ApplicationHandler,
closure: impl FnOnce() -> R,
) -> R {
// SAFETY: We extend the lifetime of the handler here so that we can
@@ -48,9 +47,9 @@ impl EventHandler {
// extended beyond `'handler`.
let handler = unsafe {
mem::transmute::<
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'handler>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop) + 'static>,
>(Box::new(handler))
&'handler mut dyn ApplicationHandler,
&'static mut dyn ApplicationHandler,
>(app)
};
match self.inner.try_borrow_mut().as_deref_mut() {
@@ -58,7 +57,7 @@ impl EventHandler {
unreachable!("tried to set handler while another was already set");
},
Ok(data @ None) => {
*data = Some(EventHandlerData { handler });
*data = Some(handler);
},
Err(_) => {
unreachable!("tried to set handler that is currently in use");
@@ -105,24 +104,20 @@ impl EventHandler {
self.inner.try_borrow().is_err()
}
pub(crate) fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}
pub(crate) fn handle_event(
pub(crate) fn handle(
&self,
event: Event<HandlePendingUserEvents>,
callback: impl FnOnce(&mut dyn ApplicationHandler, &RootActiveEventLoop),
event_loop: &RootActiveEventLoop,
) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(EventHandlerData { handler })) => {
Ok(Some(user_app)) => {
// It is important that we keep the reference borrowed here,
// so that `in_use` can properly detect that the handler is
// still in use.
//
// If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed.
(handler)(event, event_loop);
callback(*user_app, event_loop);
},
Ok(None) => {
// `NSApplication`, our app delegate and this handler are all

View File

@@ -6,34 +6,33 @@ use std::os::raw::c_void;
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::{Rc, Weak};
use std::sync::mpsc;
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
kCFRunLoopDefaultMode, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::ProtocolObject;
use objc2::{msg_send_id, sel, ClassType};
use objc2::{msg_send_id, ClassType};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
use super::app::WinitApplication;
use super::app_state::{ApplicationDelegate, HandlePendingUserEvents};
use super::app_state::ApplicationDelegate;
use super::cursor::CustomCursor;
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::{
ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents, EventLoopClosed,
};
use crate::event_loop::{ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents};
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 +105,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,21 +154,7 @@ impl ActiveEventLoop {
}
}
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootWindowTarget),
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(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
}
},
}
}
pub struct EventLoop<T: 'static> {
pub struct EventLoop {
/// Store a reference to the application for convenience.
///
/// We intentionally don't store `WinitApplication` since we want to have
@@ -192,9 +166,7 @@ pub struct EventLoop<T: 'static> {
/// keep it around here as well.
delegate: Retained<ApplicationDelegate>,
// Event sender and receiver, used for EventLoopProxy.
sender: mpsc::Sender<T>,
receiver: Rc<mpsc::Receiver<T>>,
proxy_wake_up: Arc<AtomicBool>,
window_target: RootWindowTarget,
panic_info: Rc<PanicInfo>,
@@ -202,18 +174,22 @@ 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,
}
}
}
impl<T> EventLoop<T> {
impl EventLoop {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
@@ -231,14 +207,17 @@ impl<T> EventLoop<T> {
}
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 proxy_wake_up = Arc::new(AtomicBool::new(false));
let delegate = ApplicationDelegate::new(
mtm,
activation_policy,
proxy_wake_up.clone(),
attributes.default_menu,
attributes.activate_ignoring_other_apps,
);
@@ -250,16 +229,14 @@ impl<T> EventLoop<T> {
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
let (sender, receiver) = mpsc::channel();
Ok(EventLoop {
app,
delegate: delegate.clone(),
sender,
receiver: Rc::new(receiver),
window_target: RootWindowTarget {
p: ActiveEventLoop { delegate, mtm },
_marker: PhantomData,
},
proxy_wake_up,
panic_info,
})
}
@@ -268,24 +245,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>(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());
self.delegate.set_event_handler(handler, || {
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
self.delegate.set_event_handler(app, || {
autoreleasepool(|_| {
// clear / normalize pump_events state
self.delegate.set_wait_timeout(None);
@@ -318,13 +290,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());
self.delegate.set_event_handler(handler, || {
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: &mut A,
) -> PumpStatus {
self.delegate.set_event_handler(app, || {
autoreleasepool(|_| {
// As a special case, if the application hasn't been launched yet then we at least
// run the loop until it has fully launched.
@@ -388,8 +359,8 @@ impl<T> EventLoop<T> {
})
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.proxy_wake_up.clone())
}
}
@@ -447,15 +418,15 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
}
}
pub struct EventLoopProxy<T> {
sender: mpsc::Sender<T>,
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
source: CFRunLoopSourceRef,
}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
unsafe impl<T: Send> Sync for EventLoopProxy<T> {}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl<T> Drop for EventLoopProxy<T> {
impl Drop for EventLoopProxy {
fn drop(&mut self) {
unsafe {
CFRelease(self.source as _);
@@ -463,14 +434,14 @@ impl<T> Drop for EventLoopProxy<T> {
}
}
impl<T> Clone for EventLoopProxy<T> {
impl Clone for EventLoopProxy {
fn clone(&self) -> Self {
EventLoopProxy::new(self.sender.clone())
EventLoopProxy::new(self.proxy_wake_up.clone())
}
}
impl<T> EventLoopProxy<T> {
fn new(sender: mpsc::Sender<T>) -> Self {
impl EventLoopProxy {
fn new(proxy_wake_up: Arc<AtomicBool>) -> Self {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
@@ -491,21 +462,20 @@ impl<T> EventLoopProxy<T> {
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopAddSource(rl, source, kCFRunLoopDefaultMode);
CFRunLoopWakeUp(rl);
EventLoopProxy { sender, source }
EventLoopProxy { proxy_wake_up, source }
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender.send(event).map_err(|mpsc::SendError(x)| EventLoopClosed(x))?;
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
Ok(())
}
}

View File

@@ -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

@@ -13,8 +13,8 @@ use block2::Block;
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopDefaultMode, kCFRunLoopExit,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
@@ -129,7 +129,7 @@ impl RunLoop {
context,
)
};
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode) };
}
/// Submit a closure to run on the main thread as the next step in the run loop, before other
@@ -267,7 +267,7 @@ impl EventLoopWaker {
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopDefaultMode);
Self { timer, start_instant: Instant::now(), next_fire_date: None }
}
}

View File

@@ -2,7 +2,8 @@ use tracing::trace;
macro_rules! trace_scope {
($s:literal) => {
let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s);
let _crate =
$crate::platform_impl::platform::appkit::util::TraceGuard::new(module_path!(), $s);
};
}

View File

@@ -20,17 +20,18 @@ use super::app_state::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};
use crate::platform::macos::OptionAsAlt;
use crate::window::WindowId as RootWindowId;
#[derive(Debug)]
struct CursorState {
@@ -399,7 +400,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 +483,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 +506,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 +553,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 +687,9 @@ declare_class!(
self.update_modifiers(event, false);
self.ivars().app_delegate.maybe_queue_device_event(DeviceEvent::MouseWheel { delta });
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop|
app.device_event(event_loop, DEVICE_ID, DeviceEvent::MouseWheel { delta })
);
self.queue_event(WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta,
@@ -830,7 +833,10 @@ impl WinitView {
}
fn queue_event(&self, event: WindowEvent) {
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
let window_id = RootWindowId(self.window().id());
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
}
fn scale_factor(&self) -> f64 {
@@ -933,36 +939,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

@@ -1,6 +1,5 @@
#![allow(clippy::unnecessary_cast)]
use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained};
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSResponder, NSWindow};
@@ -8,12 +7,8 @@ use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
use crate::error::RequestError;
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::window::{
Cursor, Fullscreen, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
use crate::error::OsError as RootOsError;
use crate::window::WindowAttributes;
pub(crate) struct Window {
window: MainThreadBound<Retained<WinitWindow>>,
@@ -21,20 +16,32 @@ pub(crate) struct Window {
delegate: MainThreadBound<Retained<WindowDelegate>>,
}
impl Drop for Window {
fn drop(&mut self) {
self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
}
}
impl Window {
pub(crate) fn new(
window_target: &ActiveEventLoop,
attributes: WindowAttributes,
) -> Result<Self, RequestError> {
) -> Result<Self, RootOsError> {
let mtm = window_target.mtm;
let delegate =
autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?;
let delegate = autoreleasepool(|_| {
WindowDelegate::new(window_target.app_delegate(), attributes, mtm)
})?;
Ok(Window {
window: MainThreadBound::new(delegate.window().retain(), mtm),
delegate: MainThreadBound::new(delegate, mtm),
})
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&WindowDelegate) + Send + 'static) {
// For now, don't actually do queuing, since it may be less predictable
self.maybe_wait_on_main(f)
}
pub(crate) fn maybe_wait_on_main<R: Send>(
&self,
f: impl FnOnce(&WindowDelegate) -> R + Send,
@@ -63,274 +70,24 @@ impl Window {
}
}
impl Drop for Window {
fn drop(&mut self) {
// Restore the video mode.
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_))) {
self.set_fullscreen(None);
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(pub usize);
self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
impl WindowId {
pub const unsafe fn dummy() -> Self {
Self(0)
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasDisplayHandle for Window {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_display_handle_rwh_06()?;
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.0 as u64
}
}
#[cfg(feature = "rwh_06")]
impl rwh_06::HasWindowHandle for Window {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_window_handle_rwh_06()?;
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
}
}
impl CoreWindow for Window {
fn id(&self) -> crate::window::WindowId {
self.maybe_wait_on_main(|delegate| delegate.id())
}
fn scale_factor(&self) -> f64 {
self.maybe_wait_on_main(|delegate| delegate.scale_factor())
}
fn request_redraw(&self) {
self.maybe_wait_on_main(|delegate| delegate.request_redraw());
}
fn pre_present_notify(&self) {
self.maybe_wait_on_main(|delegate| delegate.pre_present_notify());
}
fn reset_dead_keys(&self) {
self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys());
}
fn inner_position(&self) -> Result<dpi::PhysicalPosition<i32>, RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.inner_position()))
}
fn outer_position(&self) -> Result<dpi::PhysicalPosition<i32>, RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.outer_position()))
}
fn set_outer_position(&self, position: Position) {
self.maybe_wait_on_main(|delegate| delegate.set_outer_position(position));
}
fn surface_size(&self) -> dpi::PhysicalSize<u32> {
self.maybe_wait_on_main(|delegate| delegate.surface_size())
}
fn request_surface_size(&self, size: Size) -> Option<dpi::PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.request_surface_size(size))
}
fn outer_size(&self) -> dpi::PhysicalSize<u32> {
self.maybe_wait_on_main(|delegate| delegate.outer_size())
}
fn set_min_surface_size(&self, min_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size))
}
fn set_max_surface_size(&self, max_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_max_surface_size(max_size));
}
fn surface_resize_increments(&self) -> Option<dpi::PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.surface_resize_increments())
}
fn set_surface_resize_increments(&self, increments: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_surface_resize_increments(increments));
}
fn set_title(&self, title: &str) {
self.maybe_wait_on_main(|delegate| delegate.set_title(title));
}
fn set_transparent(&self, transparent: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_transparent(transparent));
}
fn set_blur(&self, blur: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_blur(blur));
}
fn set_visible(&self, visible: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_visible(visible));
}
fn is_visible(&self) -> Option<bool> {
self.maybe_wait_on_main(|delegate| delegate.is_visible())
}
fn set_resizable(&self, resizable: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_resizable(resizable))
}
fn is_resizable(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_resizable())
}
fn set_enabled_buttons(&self, buttons: WindowButtons) {
self.maybe_wait_on_main(|delegate| delegate.set_enabled_buttons(buttons))
}
fn enabled_buttons(&self) -> WindowButtons {
self.maybe_wait_on_main(|delegate| delegate.enabled_buttons())
}
fn set_minimized(&self, minimized: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_minimized(minimized));
}
fn is_minimized(&self) -> Option<bool> {
self.maybe_wait_on_main(|delegate| delegate.is_minimized())
}
fn set_maximized(&self, maximized: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_maximized(maximized));
}
fn is_maximized(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_maximized())
}
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen.map(Into::into)))
}
fn fullscreen(&self) -> Option<Fullscreen> {
self.maybe_wait_on_main(|delegate| delegate.fullscreen().map(Into::into))
}
fn set_decorations(&self, decorations: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_decorations(decorations));
}
fn is_decorated(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_decorated())
}
fn set_window_level(&self, level: WindowLevel) {
self.maybe_wait_on_main(|delegate| delegate.set_window_level(level));
}
fn set_window_icon(&self, window_icon: Option<Icon>) {
self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon));
}
fn set_ime_cursor_area(&self, position: Position, size: Size) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size));
}
fn set_ime_allowed(&self, allowed: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed));
}
fn set_ime_purpose(&self, purpose: ImePurpose) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose));
}
fn focus_window(&self) {
self.maybe_wait_on_main(|delegate| delegate.focus_window());
}
fn has_focus(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.has_focus())
}
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
self.maybe_wait_on_main(|delegate| delegate.request_user_attention(request_type));
}
fn set_theme(&self, theme: Option<Theme>) {
self.maybe_wait_on_main(|delegate| delegate.set_theme(theme));
}
fn theme(&self) -> Option<Theme> {
self.maybe_wait_on_main(|delegate| delegate.theme())
}
fn set_content_protected(&self, protected: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_content_protected(protected));
}
fn title(&self) -> String {
self.maybe_wait_on_main(|delegate| delegate.title())
}
fn set_cursor(&self, cursor: Cursor) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor(cursor));
}
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position))
}
fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode))
}
fn set_cursor_visible(&self, visible: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_visible(visible))
}
fn drag_window(&self) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.drag_window())
}
fn drag_resize_window(
&self,
direction: crate::window::ResizeDirection,
) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?)
}
fn show_window_menu(&self, position: Position) {
self.maybe_wait_on_main(|delegate| delegate.show_window_menu(position))
}
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_hittest(hittest));
Ok(())
}
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.current_monitor().map(|inner| CoreMonitorHandle { inner })
})
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
self.maybe_wait_on_main(|delegate| {
Box::new(
delegate.available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner }),
)
})
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.primary_monitor().map(|inner| CoreMonitorHandle { inner })
})
}
#[cfg(feature = "rwh_06")]
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
#[cfg(feature = "rwh_06")]
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
self
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self(raw_id as usize)
}
}
@@ -364,6 +121,6 @@ declare_class!(
impl WinitWindow {
pub(super) fn id(&self) -> WindowId {
WindowId::from_raw(self as *const Self as usize)
WindowId(self as *const Self as usize)
}
}

View File

@@ -39,7 +39,7 @@ use crate::event::{InnerSizeWriter, WindowEvent};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::window::{
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowLevel,
WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
};
#[derive(Clone, Debug)]
@@ -55,7 +55,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 +72,6 @@ impl Default for PlatformSpecificWindowAttributes {
accepts_first_mouse: true,
tabbing_identifier: None,
option_as_alt: Default::default(),
borderless_game: false,
}
}
}
@@ -87,8 +85,8 @@ pub(crate) struct State {
// 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 +120,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!(
@@ -714,7 +711,7 @@ impl WindowDelegate {
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())),
previous_position: Cell::new(None),
previous_scale_factor: Cell::new(scale_factor),
resize_increments: Cell::new(resize_increments),
decorations: Cell::new(attrs.decorations),
@@ -728,7 +725,6 @@ 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] };
@@ -811,11 +807,13 @@ impl WindowDelegate {
}
pub(crate) fn queue_event(&self, event: WindowEvent) {
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
let window_id = RootWindowId(self.window().id());
self.ivars().app_delegate.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
}
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
let app_delegate = &self.ivars().app_delegate;
let window = self.window();
let content_size = window.contentRectForFrameRect(window.frame()).size;
@@ -823,7 +821,7 @@ impl WindowDelegate {
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.queue_event(WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
});
@@ -835,16 +833,17 @@ impl WindowDelegate {
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
app_delegate.handle_window_event(window.id(), WindowEvent::Resized(physical_size));
self.queue_event(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));
@@ -1163,8 +1162,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 +1411,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 +1421,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 +1428,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 +1465,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.
@@ -1616,7 +1615,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)
}
@@ -1660,18 +1659,14 @@ impl WindowDelegate {
}
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)
}
})
// Note: We could choose between returning the value of `effectiveAppearance` or
// `appearance`, depending on what the user is asking about:
// - "how should I render on this particular frame".
// - "what is the configuration for this window".
//
// We choose the latter for consistency with the `set_theme` call, though it might also be
// useful to expose the former.
Some(appearance_to_theme(unsafe { &*self.window().appearance()? }))
}
pub fn set_theme(&self, theme: Option<Theme>) {
@@ -1696,21 +1691,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 {
@@ -1844,14 +1824,6 @@ 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 =
@@ -1862,7 +1834,7 @@ fn dark_appearance_name() -> &'static NSString {
ns_string!("NSAppearanceNameDarkAqua")
}
pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
unsafe { NSAppearanceNameAqua.copy() },
dark_appearance_name().copy(),

View File

@@ -0,0 +1,11 @@
//! Apple/Darwin-specific implementations
#[cfg(target_os = "macos")]
mod appkit;
#[cfg(not(target_os = "macos"))]
mod uikit;
#[cfg(target_os = "macos")]
pub use self::appkit::*;
#[cfg(not(target_os = "macos"))]
pub use self::uikit::*;

View File

@@ -10,7 +10,7 @@ use std::{fmt, mem, ptr};
use core_foundation::base::CFRelease;
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
kCFRunLoopDefaultMode, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
};
use objc2::rc::Retained;
@@ -40,12 +40,9 @@ macro_rules! bug_assert {
};
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
pub(crate) struct EventLoopHandler {
#[allow(clippy::type_complexity)]
pub(crate) handler: Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
pub(crate) handler: Box<dyn FnMut(Event, &RootActiveEventLoop)>,
pub(crate) event_loop: RootActiveEventLoop,
}
@@ -59,14 +56,14 @@ impl fmt::Debug for EventLoopHandler {
}
impl EventLoopHandler {
fn handle_event(&mut self, event: Event<HandlePendingUserEvents>) {
fn handle_event(&mut self, event: Event) {
(self.handler)(event, &self.event_loop)
}
}
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<HandlePendingUserEvents>),
StaticEvent(Event),
ScaleFactorChanged(ScaleFactorChanged),
}
@@ -88,7 +85,7 @@ enum UserCallbackTransitionResult<'a> {
},
}
impl Event<HandlePendingUserEvents> {
impl Event {
fn is_redraw(&self) -> bool {
matches!(self, Event::WindowEvent { event: WindowEvent::RedrawRequested, .. })
}
@@ -147,8 +144,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)]
@@ -627,7 +622,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
drop(this);
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
handler.handle_event(Event::UserWakeUp);
loop {
let mut this = AppState::get_mut(mtm);
@@ -660,7 +655,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
}
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
handler.handle_event(Event::UserWakeUp);
}
}
@@ -807,7 +802,7 @@ impl EventLoopWaker {
wakeup_main_loop,
ptr::null_mut(),
);
CFRunLoopAddTimer(rl, timer, kCFRunLoopCommonModes);
CFRunLoopAddTimer(rl, timer, kCFRunLoopDefaultMode);
EventLoopWaker { timer }
}

View File

@@ -2,28 +2,27 @@ use std::collections::VecDeque;
use std::ffi::{c_char, c_int, c_void};
use std::marker::PhantomData;
use std::ptr::{self, NonNull};
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopDefaultMode, kCFRunLoopExit,
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::Retained;
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSString};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom};
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIScreen};
use super::app_state::EventLoopHandler;
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::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents};
use crate::window::{CustomCursor, CustomCursorSource};
use super::app_delegate::AppDelegate;
use super::app_state::AppState;
@@ -58,11 +57,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(
@@ -113,34 +107,44 @@ impl OwnedDisplayHandle {
}
}
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootActiveEventLoop),
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(_) => {
for event in receiver.try_iter() {
(handler)(Event::UserEvent(event), window_target);
fn map_user_event<A: ApplicationHandler>(
app: &mut A,
proxy_wake_up: Arc<AtomicBool>,
) -> impl FnMut(Event, &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::UserWakeUp => {
if proxy_wake_up.swap(false, AtomicOrdering::Relaxed) {
app.proxy_wake_up(window_target);
}
},
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),
}
}
pub struct EventLoop<T: 'static> {
pub struct EventLoop {
mtm: MainThreadMarker,
sender: Sender<T>,
receiver: Receiver<T>,
proxy_wake_up: Arc<AtomicBool>,
window_target: RootActiveEventLoop,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T: 'static> EventLoop<T> {
impl EventLoop {
pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop<T>, EventLoopError> {
) -> Result<EventLoop, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
@@ -153,23 +157,19 @@ impl<T: 'static> EventLoop<T> {
SINGLETON_INIT = true;
}
let (sender, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers();
let proxy_wake_up = Arc::new(AtomicBool::new(false));
Ok(EventLoop {
mtm,
sender,
receiver,
proxy_wake_up,
window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData },
})
}
pub fn run<F>(self, handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
pub fn run_app<A: ApplicationHandler>(self, app: &mut A) -> ! {
let application: Option<Retained<UIApplication>> =
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
assert!(
@@ -179,12 +179,12 @@ 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.proxy_wake_up.clone());
let handler = unsafe {
std::mem::transmute::<
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
Box<dyn FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop)>,
Box<dyn FnMut(Event, &RootActiveEventLoop)>,
Box<dyn FnMut(Event, &RootActiveEventLoop)>,
>(Box::new(handler))
};
@@ -212,8 +212,8 @@ impl<T: 'static> EventLoop<T> {
unreachable!()
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.proxy_wake_up.clone())
}
pub fn window_target(&self) -> &RootActiveEventLoop {
@@ -221,35 +221,21 @@ impl<T: 'static> EventLoop<T> {
}
}
// EventLoopExtIOS
impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom {
match UIDevice::currentDevice(self.mtm).userInterfaceIdiom() {
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
UIUserInterfaceIdiom::Phone => Idiom::Phone,
UIUserInterfaceIdiom::Pad => Idiom::Pad,
UIUserInterfaceIdiom::TV => Idiom::TV,
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
_ => Idiom::Unspecified,
}
}
}
pub struct EventLoopProxy<T> {
sender: Sender<T>,
pub struct EventLoopProxy {
proxy_wake_up: Arc<AtomicBool>,
source: CFRunLoopSourceRef,
}
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
unsafe impl<T: Send> Sync for EventLoopProxy<T> {}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
impl Clone for EventLoopProxy {
fn clone(&self) -> EventLoopProxy {
EventLoopProxy::new(self.proxy_wake_up.clone())
}
}
impl<T> Drop for EventLoopProxy<T> {
impl Drop for EventLoopProxy {
fn drop(&mut self) {
unsafe {
CFRunLoopSourceInvalidate(self.source);
@@ -258,8 +244,8 @@ impl<T> Drop for EventLoopProxy<T> {
}
}
impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
impl EventLoopProxy {
fn new(proxy_wake_up: Arc<AtomicBool>) -> EventLoopProxy {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
@@ -280,22 +266,21 @@ impl<T> EventLoopProxy<T> {
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopAddSource(rl, source, kCFRunLoopDefaultMode);
CFRunLoopWakeUp(rl);
EventLoopProxy { sender, source }
EventLoopProxy { proxy_wake_up, source }
}
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender.send(event).map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
pub fn wake_up(&self) {
self.proxy_wake_up.store(true, AtomicOrdering::Relaxed);
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
Ok(())
}
}

View File

@@ -32,7 +32,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

@@ -9,9 +9,9 @@ use objc2::Message;
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
use objc2_ui_kit::{UIScreen, UIScreenMode};
use super::app_state;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::monitor::VideoModeHandle as RootVideoModeHandle;
use crate::platform_impl::platform::app_state;
// Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug)]
@@ -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

@@ -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 super::DEVICE_ID;
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::platform_impl::platform::DEVICE_ID;
use crate::platform_impl::KeyEventExtra;
use crate::event::{Event, Force, Touch, TouchPhase, WindowEvent};
use crate::window::{WindowAttributes, WindowId as RootWindowId};
pub struct WinitViewState {
@@ -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

@@ -17,15 +17,13 @@ use tracing::{debug, warn};
use super::app_state::EventWrapper;
use super::view::WinitView;
use super::view_controller::WinitViewController;
use super::{app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle};
use crate::cursor::Cursor;
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
use crate::event::{Event, WindowEvent};
use crate::icon::Icon;
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
use crate::platform_impl::platform::{
app_state, monitor, ActiveEventLoop, Fullscreen, MonitorHandle,
};
use crate::window::{
CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
WindowButtons, WindowId as RootWindowId, WindowLevel,
@@ -367,24 +365,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 +699,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,

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};
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,27 +691,22 @@ unsafe extern "C" fn x_error_callback(
0
}
pub enum EventLoop<T: 'static> {
pub enum EventLoop {
#[cfg(wayland_platform)]
Wayland(Box<wayland::EventLoop<T>>),
Wayland(Box<wayland::EventLoop>),
#[cfg(x11_platform)]
X(x11::EventLoop<T>),
X(x11::EventLoop),
}
pub enum EventLoopProxy<T: 'static> {
#[derive(Clone)]
pub enum EventLoopProxy {
#[cfg(x11_platform)]
X(x11::EventLoopProxy<T>),
X(x11::EventLoopProxy),
#[cfg(wayland_platform)]
Wayland(wayland::EventLoopProxy<T>),
Wayland(wayland::EventLoopProxy),
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy)
}
}
impl<T: 'static> EventLoop<T> {
impl EventLoop {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
@@ -775,17 +765,15 @@ impl<T: 'static> EventLoop<T> {
}
#[cfg(wayland_platform)]
fn new_wayland_any_thread() -> Result<EventLoop<T>, EventLoopError> {
fn new_wayland_any_thread() -> Result<EventLoop, EventLoopError> {
wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
}
#[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() {
fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> {
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)))
@@ -801,29 +789,27 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
pub fn create_proxy(&self) -> EventLoopProxy {
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>(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>(
&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>(
&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 {
@@ -831,21 +817,21 @@ impl<T: 'static> EventLoop<T> {
}
}
impl<T> AsFd for EventLoop<T> {
impl AsFd for EventLoop {
fn as_fd(&self) -> BorrowedFd<'_> {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
}
}
impl<T> AsRawFd for EventLoop<T> {
impl AsRawFd for EventLoop {
fn as_raw_fd(&self) -> RawFd {
x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event))
impl EventLoopProxy {
pub fn wake_up(&self) {
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.wake_up())
}
}
@@ -903,11 +889,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

@@ -5,7 +5,6 @@ use std::io::Result as IOResult;
use std::marker::PhantomData;
use std::mem;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
@@ -14,6 +13,7 @@ use sctk::reexports::calloop::Error as CalloopError;
use sctk::reexports::calloop_wayland_source::WaylandSource;
use sctk::reexports::client::{globals, Connection, QueueHandle};
use crate::application::ApplicationHandler;
use crate::cursor::OnlyCursorImage;
use crate::dpi::LogicalSize;
use crate::error::{EventLoopError, OsError as RootOsError};
@@ -39,7 +39,7 @@ use super::{logical_to_physical_rounded, DeviceId, WaylandError, WindowId};
type WaylandDispatcher = calloop::Dispatcher<'static, WaylandSource<WinitState>, WinitState>;
/// The Wayland event loop.
pub struct EventLoop<T: 'static> {
pub struct EventLoop {
/// Has `run` or `run_on_demand` been called or a call to `pump_events` that starts the loop
loop_running: bool,
@@ -47,13 +47,8 @@ pub struct EventLoop<T: 'static> {
compositor_updates: Vec<WindowCompositorUpdate>,
window_ids: Vec<WindowId>,
/// Sender of user events.
user_events_sender: calloop::channel::Sender<T>,
// XXX can't remove RefCell out of here, unless we can plumb generics into the `Window`, which
// we don't really want, since it'll break public API by a lot.
/// Pending events from the user.
pending_user_events: Rc<RefCell<Vec<T>>>,
/// Event loop proxy
event_loop_proxy: EventLoopProxy,
/// The Wayland dispatcher to has raw access to the queue when needed, such as
/// when creating a new window.
@@ -70,8 +65,8 @@ pub struct EventLoop<T: 'static> {
event_loop: calloop::EventLoop<'static, WinitState>,
}
impl<T: 'static> EventLoop<T> {
pub fn new() -> Result<EventLoop<T>, EventLoopError> {
impl EventLoop {
pub fn new() -> Result<EventLoop, EventLoopError> {
macro_rules! map_err {
($e:expr, $err:expr) => {
$e.map_err(|error| os_error!($err(error).into()))
@@ -114,16 +109,12 @@ impl<T: 'static> EventLoop<T> {
)?;
// Setup the user proxy.
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel();
let (ping, ping_source) = calloop::ping::make_ping().unwrap();
let result = event_loop
.handle()
.insert_source(user_events_channel, move |event, _, winit_state: &mut WinitState| {
if let calloop::channel::Event::Msg(msg) = event {
winit_state.dispatched_events = true;
pending_user_events_clone.borrow_mut().push(msg);
}
.insert_source(ping_source, move |_, _, winit_state: &mut WinitState| {
winit_state.dispatched_events = true;
winit_state.proxy_wake_up = true;
})
.map_err(|error| error.error);
map_err!(result, WaylandError::Calloop)?;
@@ -161,8 +152,7 @@ impl<T: 'static> EventLoop<T> {
window_ids: Vec::new(),
connection,
wayland_dispatcher,
user_events_sender,
pending_user_events,
event_loop_proxy: EventLoopProxy::new(ping),
event_loop,
window_target: RootActiveEventLoop {
p: PlatformActiveEventLoop::Wayland(window_target),
@@ -173,12 +163,16 @@ impl<T: 'static> EventLoop<T> {
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>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&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(());
},
@@ -200,26 +194,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>, &RootActiveEventLoop),
{
pub fn pump_app_events<A: ApplicationHandler>(
&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 {
@@ -227,10 +222,11 @@ impl<T: 'static> EventLoop<T> {
}
}
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>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
let cause = loop {
let start = Instant::now();
@@ -292,13 +288,10 @@ impl<T: 'static> EventLoop<T> {
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>(&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
@@ -309,18 +302,17 @@ 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);
// Indicate user wake up.
if self.with_state(|state| mem::take(&mut state.proxy_wake_up)) {
app.proxy_wake_up(&self.window_target);
}
// Drain the pending compositor updates.
@@ -341,18 +333,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);
@@ -395,23 +382,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);
}
}
@@ -420,8 +398,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.
@@ -429,8 +414,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
@@ -466,10 +458,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);
}
}
@@ -479,7 +469,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;
@@ -523,8 +513,8 @@ impl<T: 'static> EventLoop<T> {
}
#[inline]
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.user_events_sender.clone())
pub fn create_proxy(&self) -> EventLoopProxy {
self.event_loop_proxy.clone()
}
#[inline]
@@ -586,13 +576,13 @@ impl<T: 'static> EventLoop<T> {
}
}
impl<T> AsFd for EventLoop<T> {
impl AsFd for EventLoop {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl<T> AsRawFd for EventLoop<T> {
impl AsRawFd for EventLoop {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}

View File

@@ -1,28 +1,19 @@
//! An event loop proxy.
use std::sync::mpsc::SendError;
use sctk::reexports::calloop::channel::Sender;
use crate::event_loop::EventLoopClosed;
use sctk::reexports::calloop::ping::Ping;
/// A handle that can be sent across the threads and used to wake up the `EventLoop`.
pub struct EventLoopProxy<T: 'static> {
user_events_sender: Sender<T>,
#[derive(Clone)]
pub struct EventLoopProxy {
ping: Ping,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy { user_events_sender: self.user_events_sender.clone() }
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn new(user_events_sender: Sender<T>) -> Self {
Self { user_events_sender }
impl EventLoopProxy {
pub fn new(ping: Ping) -> Self {
Self { ping }
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_events_sender.send(event).map_err(|SendError(error)| EventLoopClosed(error))
pub fn wake_up(&self) {
self.ping.ping();
}
}

View File

@@ -12,7 +12,7 @@ use super::{DeviceId, WindowId};
/// to the winit's user.
#[derive(Default)]
pub struct EventSink {
pub window_events: Vec<Event<()>>,
pub(crate) window_events: Vec<Event>,
}
impl EventSink {
@@ -47,7 +47,7 @@ impl EventSink {
}
#[inline]
pub fn drain(&mut self) -> Drain<'_, Event<()>> {
pub(crate) fn drain(&mut self) -> Drain<'_, Event> {
self.window_events.drain(..)
}
}

View File

@@ -66,7 +66,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() => {
@@ -146,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;
@@ -43,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));
@@ -94,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 => {
@@ -127,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);
@@ -138,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;
@@ -195,13 +185,13 @@ impl PointerHandler for WinitState {
// Mice events have both pixel and discrete delta's at the same time. So prefer
// 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),

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

@@ -115,6 +115,9 @@ pub struct WinitState {
/// Whether we have dispatched events to the user thus we want to
/// send `AboutToWait` and normally wakeup the user.
pub dispatched_events: bool,
/// Whether the user initiated a wake up.
pub proxy_wake_up: bool,
}
impl WinitState {
@@ -192,6 +195,7 @@ impl WinitState {
loop_handle,
// Make it true by default.
dispatched_events: true,
proxy_wake_up: false,
})
}
@@ -339,30 +343,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

@@ -168,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.

View File

@@ -165,7 +165,7 @@ 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(())

View File

@@ -72,9 +72,9 @@ pub struct EventProcessor {
}
impl EventProcessor {
pub fn process_event<T: 'static, F>(&mut self, xev: &mut XEvent, mut callback: F)
pub(crate) fn process_event<F>(&mut self, xev: &mut XEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
self.process_xevent(xev, &mut callback);
@@ -129,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 {
@@ -140,26 +139,14 @@ impl EventProcessor {
}
}
fn process_xevent<T: 'static, F>(&mut self, xev: &mut XEvent, mut callback: F)
fn process_xevent<F>(&mut self, xev: &mut XEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
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
@@ -172,14 +159,6 @@ impl EventProcessor {
self.xfiltered_modifiers.push_front(xev.serial);
}
}
filtered
} else {
self.filter_event(xev)
};
// Don't process event if it was filtered.
if filtered {
return;
}
@@ -408,9 +387,9 @@ impl EventProcessor {
}
}
fn client_message<T: 'static, F>(&mut self, xev: &XClientMessageEvent, mut callback: F)
fn client_message<F>(&mut self, xev: &XClientMessageEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
let atoms = wt.xconn.atoms();
@@ -573,9 +552,9 @@ impl EventProcessor {
}
}
fn selection_notify<T: 'static, F>(&mut self, xev: &XSelectionEvent, mut callback: F)
fn selection_notify<F>(&mut self, xev: &XSelectionEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
let atoms = wt.xconn.atoms();
@@ -607,9 +586,9 @@ impl EventProcessor {
}
}
fn configure_notify<T: 'static, F>(&self, xev: &XConfigureEvent, mut callback: F)
fn configure_notify<F>(&self, xev: &XConfigureEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -802,9 +781,9 @@ impl EventProcessor {
});
}
fn map_notify<T: 'static, F>(&self, xev: &XMapEvent, mut callback: F)
fn map_notify<F>(&self, xev: &XMapEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let window = xev.window as xproto::Window;
let window_id = mkwid(window);
@@ -820,9 +799,9 @@ impl EventProcessor {
callback(&self.target, event);
}
fn destroy_notify<T: 'static, F>(&self, xev: &XDestroyWindowEvent, mut callback: F)
fn destroy_notify<F>(&self, xev: &XDestroyWindowEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -844,9 +823,9 @@ impl EventProcessor {
callback(&self.target, Event::WindowEvent { window_id, event: WindowEvent::Destroyed });
}
fn property_notify<T: 'static, F>(&mut self, xev: &XPropertyEvent, mut callback: F)
fn property_notify<F>(&mut self, xev: &XPropertyEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
let atoms = wt.x_connection().atoms();
@@ -859,9 +838,9 @@ impl EventProcessor {
}
}
fn visibility_notify<T: 'static, F>(&self, xev: &XVisibilityEvent, mut callback: F)
fn visibility_notify<F>(&self, xev: &XVisibilityEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let xwindow = xev.window as xproto::Window;
@@ -876,9 +855,9 @@ impl EventProcessor {
});
}
fn expose<T: 'static, F>(&self, xev: &XExposeEvent, mut callback: F)
fn expose<F>(&self, xev: &XExposeEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
// Multiple Expose events may be received for subareas of a window.
// We issue `RedrawRequested` only for the last event of such a series.
@@ -892,13 +871,9 @@ impl EventProcessor {
}
}
fn xinput_key_input<T: 'static, F>(
&mut self,
xev: &mut XKeyEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
fn xinput_key_input<F>(&mut self, xev: &mut XKeyEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -1010,13 +985,13 @@ impl EventProcessor {
}
}
fn send_synthic_modifier_from_core<T: 'static, F>(
fn send_synthic_modifier_from_core<F>(
&mut self,
window_id: crate::window::WindowId,
state: u16,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let keymap = match self.xkb_context.keymap_mut() {
Some(keymap) => keymap,
@@ -1043,13 +1018,9 @@ impl EventProcessor {
callback(&self.target, event);
}
fn xinput2_button_input<T: 'static, F>(
&self,
event: &XIDeviceEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
fn xinput2_button_input<F>(&self, event: &XIDeviceEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
let window_id = mkwid(event.event as xproto::Window);
@@ -1100,9 +1071,9 @@ impl EventProcessor {
callback(&self.target, event);
}
fn xinput2_mouse_motion<T: 'static, F>(&self, event: &XIDeviceEvent, mut callback: F)
fn xinput2_mouse_motion<F>(&self, event: &XIDeviceEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -1178,9 +1149,9 @@ impl EventProcessor {
}
}
fn xinput2_mouse_enter<T: 'static, F>(&self, event: &XIEnterEvent, mut callback: F)
fn xinput2_mouse_enter<F>(&self, event: &XIEnterEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -1223,9 +1194,9 @@ impl EventProcessor {
}
}
fn xinput2_mouse_left<T: 'static, F>(&self, event: &XILeaveEvent, mut callback: F)
fn xinput2_mouse_left<F>(&self, event: &XILeaveEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
let window = event.event as xproto::Window;
@@ -1246,9 +1217,9 @@ impl EventProcessor {
}
}
fn xinput2_focused<T: 'static, F>(&mut self, xev: &XIFocusInEvent, mut callback: F)
fn xinput2_focused<F>(&mut self, xev: &XIFocusInEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
let window = xev.event as xproto::Window;
@@ -1305,9 +1276,9 @@ impl EventProcessor {
callback(&self.target, event);
}
fn xinput2_unfocused<T: 'static, F>(&mut self, xev: &XIFocusOutEvent, mut callback: F)
fn xinput2_unfocused<F>(&mut self, xev: &XIFocusOutEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
let window = xev.event as xproto::Window;
@@ -1357,13 +1328,9 @@ impl EventProcessor {
}
}
fn xinput2_touch<T: 'static, F>(
&mut self,
xev: &XIDeviceEvent,
phase: TouchPhase,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
fn xinput2_touch<F>(&mut self, xev: &XIDeviceEvent, phase: TouchPhase, mut callback: F)
where
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -1403,13 +1370,9 @@ impl EventProcessor {
}
}
fn xinput2_raw_button_input<T: 'static, F>(
&self,
xev: &XIRawEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
fn xinput2_raw_button_input<F>(&self, xev: &XIRawEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -1425,9 +1388,9 @@ impl EventProcessor {
}
}
fn xinput2_raw_mouse_motion<T: 'static, F>(&self, xev: &XIRawEvent, mut callback: F)
fn xinput2_raw_mouse_motion<F>(&self, xev: &XIRawEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -1485,13 +1448,9 @@ impl EventProcessor {
}
}
fn xinput2_raw_key_input<T: 'static, F>(
&mut self,
xev: &XIRawEvent,
state: ElementState,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
fn xinput2_raw_key_input<F>(&mut self, xev: &XIRawEvent, state: ElementState, mut callback: F)
where
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -1511,9 +1470,9 @@ impl EventProcessor {
});
}
fn xinput2_hierarchy_changed<T: 'static, F>(&mut self, xev: &XIHierarchyEvent, mut callback: F)
fn xinput2_hierarchy_changed<F>(&mut self, xev: &XIHierarchyEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -1538,9 +1497,9 @@ impl EventProcessor {
}
}
fn xkb_event<T: 'static, F>(&mut self, xev: &XkbAnyEvent, mut callback: F)
fn xkb_event<F>(&mut self, xev: &XkbAnyEvent, mut callback: F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
match xev.xkb_type {
@@ -1617,14 +1576,14 @@ impl EventProcessor {
}
}
pub fn update_mods_from_xinput2_event<T: 'static, F>(
pub(crate) fn update_mods_from_xinput2_event<F>(
&mut self,
mods: &XIModifierState,
group: &XIModifierState,
force: bool,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
if let Some(state) = self.xkb_context.state_mut() {
state.update_modifiers(
@@ -1648,12 +1607,9 @@ impl EventProcessor {
}
}
fn update_mods_from_query<T: 'static, F>(
&mut self,
window_id: crate::window::WindowId,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
fn update_mods_from_query<F>(&mut self, window_id: crate::window::WindowId, mut callback: F)
where
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
@@ -1682,13 +1638,13 @@ impl EventProcessor {
self.send_modifiers(window_id, mods.into(), true, &mut callback)
}
pub fn update_mods_from_core_event<T: 'static, F>(
pub(crate) fn update_mods_from_core_event<F>(
&mut self,
window_id: crate::window::WindowId,
state: u16,
mut callback: F,
) where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let xkb_mask = self.xkb_mod_mask_from_core(state);
let xkb_state = match self.xkb_context.state_mut() {
@@ -1761,7 +1717,7 @@ impl EventProcessor {
///
/// The event won't be sent when the `modifiers` match the previously `sent` modifiers value,
/// unless `force` is passed. The `force` should be passed when the active window changes.
fn send_modifiers<T: 'static, F: FnMut(&RootAEL, Event<T>)>(
fn send_modifiers<F: FnMut(&RootAEL, Event)>(
&self,
window_id: crate::window::WindowId,
modifiers: ModifiersState,
@@ -1779,14 +1735,14 @@ impl EventProcessor {
}
}
fn handle_pressed_keys<T: 'static, F>(
fn handle_pressed_keys<F>(
target: &RootAEL,
window_id: crate::window::WindowId,
state: ElementState,
xkb_context: &mut Context,
callback: &mut F,
) where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
@@ -1821,9 +1777,9 @@ impl EventProcessor {
}
}
fn process_dpi_change<T: 'static, F>(&self, callback: &mut F)
fn process_dpi_change<F>(&self, callback: &mut F)
where
F: FnMut(&RootAEL, Event<T>),
F: FnMut(&RootAEL, Event),
{
let wt = Self::window_target(&self.target);
wt.xconn.reload_database().expect("failed to reload Xft database");

View File

@@ -81,9 +81,7 @@ impl InputMethod {
}
let preedit_style = preedit_style.unwrap_or_else(|| none_style.unwrap());
// Always initialize none style even when it's not advertised, since it seems to work
// regardless...
let none_style = none_style.unwrap_or(Style::None(XIM_NONE_STYLE));
let none_style = none_style.unwrap_or(preedit_style);
Some(InputMethod { im, _name: name, preedit_style, none_style })
}

View File

@@ -226,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

@@ -9,7 +9,7 @@ use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::{Arc, Weak};
use std::time::{Duration, Instant};
use std::{fmt, ptr, slice, str};
use std::{fmt, mem, ptr, slice, str};
use calloop::generic::Generic;
use calloop::ping::Ping;
@@ -25,9 +25,10 @@ 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};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents};
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::common::xkb::Context;
use crate::platform_impl::platform::{min_timeout, WindowId};
@@ -80,12 +81,11 @@ impl<T> Clone for WakeSender<T> {
}
impl<T> WakeSender<T> {
pub fn send(&self, t: T) -> Result<(), EventLoopClosed<T>> {
let res = self.sender.send(t).map_err(|e| EventLoopClosed(e.0));
pub fn send(&self, t: T) {
let res = self.sender.send(t);
if res.is_ok() {
self.waker.ping();
}
res
}
}
@@ -140,15 +140,13 @@ pub struct ActiveEventLoop {
device_events: Cell<DeviceEvents>,
}
pub struct EventLoop<T: 'static> {
pub struct EventLoop {
loop_running: bool,
event_loop: Loop<'static, EventLoopState>,
waker: calloop::ping::Ping,
event_processor: EventProcessor,
redraw_receiver: PeekableReceiver<WindowId>,
user_receiver: PeekableReceiver<T>,
activation_receiver: PeekableReceiver<ActivationToken>,
user_sender: Sender<T>,
event_loop_proxy: EventLoopProxy,
/// The current state of the event loop.
state: EventLoopState,
@@ -159,20 +157,13 @@ type ActivationToken = (WindowId, crate::event_loop::AsyncRequestSerial);
struct EventLoopState {
/// The latest readiness state for the x11 file descriptor
x11_readiness: Readiness,
/// User requested a wake up.
proxy_wake_up: bool,
}
pub struct EventLoopProxy<T: 'static> {
user_sender: WakeSender<T>,
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
EventLoopProxy { user_sender: self.user_sender.clone() }
}
}
impl<T: 'static> EventLoop<T> {
pub(crate) fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
impl EventLoop {
pub(crate) fn new(xconn: Arc<XConnection>) -> EventLoop {
let root = xconn.default_root().root;
let atoms = xconn.atoms();
@@ -276,7 +267,16 @@ impl<T: 'static> EventLoop<T> {
let (activation_token_sender, activation_token_channel) = mpsc::channel();
// Create a channel for sending user events.
let (user_sender, user_channel) = mpsc::channel();
let (user_waker, user_waker_source) =
calloop::ping::make_ping().expect("Failed to create user event loop waker.");
event_loop
.handle()
.insert_source(user_waker_source, move |_, _, state| {
// No extra handling is required, we just need to wake-up.
state.proxy_wake_up = true;
})
.expect("Failed to register the event loop waker source");
let event_loop_proxy = EventLoopProxy::new(user_waker);
let xkb_context =
Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
@@ -357,32 +357,32 @@ impl<T: 'static> EventLoop<T> {
EventLoop {
loop_running: false,
event_loop,
waker,
event_processor,
redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
activation_receiver: PeekableReceiver::from_recv(activation_token_channel),
user_receiver: PeekableReceiver::from_recv(user_channel),
user_sender,
state: EventLoopState { x11_readiness: Readiness::EMPTY },
event_loop_proxy,
state: EventLoopState { x11_readiness: Readiness::EMPTY, proxy_wake_up: false },
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
user_sender: WakeSender { sender: self.user_sender.clone(), waker: self.waker.clone() },
}
pub fn create_proxy(&self) -> EventLoopProxy {
self.event_loop_proxy.clone()
}
pub(crate) fn window_target(&self) -> &RootAEL {
&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>(mut self, app: &mut A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>(
&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 +407,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>(
&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 {
@@ -436,14 +437,15 @@ impl<T: 'static> EventLoop<T> {
fn has_pending(&mut self) -> bool {
self.event_processor.poll()
|| self.user_receiver.has_incoming()
|| self.state.proxy_wake_up
|| 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>(
&mut self,
mut timeout: Option<Duration>,
app: &mut A,
) {
let start = Instant::now();
let has_pending = self.has_pending();
@@ -501,23 +503,20 @@ impl<T: 'static> EventLoop<T> {
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>(&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() {
@@ -527,14 +526,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);
@@ -544,10 +541,8 @@ 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);
}
if mem::take(&mut self.state.proxy_wake_up) {
app.proxy_wake_up(&self.event_processor.target);
}
// Empty the redraw requests
@@ -560,37 +555,41 @@ 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>(&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| {
if let Event::WindowEvent {
window_id: crate::window::WindowId(wid),
event: WindowEvent::RedrawRequested,
} = event
{
let window_target = EventProcessor::window_target(window_target);
window_target.redraw_sender.send(wid).unwrap();
window_target.redraw_sender.send(wid);
} 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."),
}
}
});
}
@@ -617,13 +616,13 @@ impl<T: 'static> EventLoop<T> {
}
}
impl<T> AsFd for EventLoop<T> {
impl AsFd for EventLoop {
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
impl<T> AsRawFd for EventLoop<T> {
impl AsRawFd for EventLoop {
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
@@ -723,9 +722,9 @@ impl ActiveEventLoop {
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.user_sender.send(event).map_err(|e| EventLoopClosed(e.0))
impl EventLoopProxy {
pub fn wake_up(&self) {
self.ping.ping();
}
}
@@ -751,14 +750,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 {
@@ -771,7 +770,7 @@ pub struct DeviceId(xinput::DeviceId);
impl DeviceId {
#[allow(unused)]
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId(0)
}
}
@@ -809,6 +808,17 @@ impl Drop for Window {
}
}
#[derive(Clone)]
pub struct EventLoopProxy {
ping: Ping,
}
impl EventLoopProxy {
fn new(ping: Ping) -> Self {
Self { ping }
}
}
/// Generic sum error type for X11 errors.
#[derive(Debug)]
pub enum X11Error {
@@ -957,7 +967,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

@@ -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.
@@ -882,11 +876,11 @@ impl UnownedWindow {
/// Refresh the API for the given monitor.
#[inline]
pub(super) fn refresh_dpi_for_monitor<T: 'static>(
pub(super) fn refresh_dpi_for_monitor(
&self,
new_monitor: &X11MonitorHandle,
maybe_prev_scale_factor: Option<f64>,
mut callback: impl FnMut(Event<T>),
mut callback: impl FnMut(Event),
) {
// Check if the self is on this monitor
let monitor = self.shared_state_lock().last_monitor.clone();
@@ -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() {
@@ -1814,9 +1805,7 @@ impl UnownedWindow {
#[inline]
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
let serial = AsyncRequestSerial::get();
self.activation_sender
.send((self.id(), serial))
.expect("activation token channel should never be closed");
self.activation_sender.send((self.id(), serial));
Ok(serial)
}
@@ -1827,7 +1816,7 @@ impl UnownedWindow {
#[inline]
pub fn request_redraw(&self) {
self.redraw_sender.send(WindowId(self.xwindow as _)).unwrap();
self.redraw_sender.send(WindowId(self.xwindow as _));
}
#[inline]

View File

@@ -1,126 +0,0 @@
#![allow(clippy::unnecessary_cast)]
use objc2::rc::{autoreleasepool, Retained};
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSResponder, NSWindow};
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
use crate::error::OsError as RootOsError;
use crate::window::WindowAttributes;
pub(crate) struct Window {
window: MainThreadBound<Retained<WinitWindow>>,
/// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Retained<WindowDelegate>>,
}
impl Drop for Window {
fn drop(&mut self) {
self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
}
}
impl Window {
pub(crate) fn new(
window_target: &ActiveEventLoop,
attributes: WindowAttributes,
) -> Result<Self, RootOsError> {
let mtm = window_target.mtm;
let delegate = autoreleasepool(|_| {
WindowDelegate::new(window_target.app_delegate(), attributes, mtm)
})?;
Ok(Window {
window: MainThreadBound::new(delegate.window().retain(), mtm),
delegate: MainThreadBound::new(delegate, mtm),
})
}
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&WindowDelegate) + Send + 'static) {
// For now, don't actually do queuing, since it may be less predictable
self.maybe_wait_on_main(f)
}
pub(crate) fn maybe_wait_on_main<R: Send>(
&self,
f: impl FnOnce(&WindowDelegate) -> R + Send,
) -> R {
self.delegate.get_on_main(|delegate| f(delegate))
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_window_handle_rwh_06(
&self,
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
if let Some(mtm) = MainThreadMarker::new() {
Ok(self.delegate.get(mtm).raw_window_handle_rwh_06())
} else {
Err(rwh_06::HandleError::Unavailable)
}
}
#[cfg(feature = "rwh_06")]
#[inline]
pub(crate) fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new()))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(pub usize);
impl WindowId {
pub const fn dummy() -> Self {
Self(0)
}
}
impl From<WindowId> for u64 {
fn from(window_id: WindowId) -> Self {
window_id.0 as u64
}
}
impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self {
Self(raw_id as usize)
}
}
declare_class!(
#[derive(Debug)]
pub struct WinitWindow;
unsafe impl ClassType for WinitWindow {
#[inherits(NSResponder, NSObject)]
type Super = NSWindow;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindow";
}
impl DeclaredClass for WinitWindow {}
unsafe impl WinitWindow {
#[method(canBecomeMainWindow)]
fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow");
true
}
#[method(canBecomeKeyWindow)]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
true
}
}
);
impl WinitWindow {
pub(super) fn id(&self) -> WindowId {
WindowId(self as *const Self as usize)
}
}

View File

@@ -3,12 +3,10 @@ use crate::window::Fullscreen as RootFullscreen;
#[cfg(android_platform)]
mod android;
#[cfg(ios_platform)]
mod ios;
#[cfg(target_vendor = "apple")]
mod apple;
#[cfg(any(x11_platform, wayland_platform))]
mod linux;
#[cfg(macos_platform)]
mod macos;
#[cfg(orbital_platform)]
mod orbital;
#[cfg(web_platform)]
@@ -17,19 +15,17 @@ mod web;
mod windows;
#[cfg(android_platform)]
use android as platform;
#[cfg(ios_platform)]
use ios as platform;
use self::android as platform;
#[cfg(target_vendor = "apple")]
use self::apple as platform;
#[cfg(any(x11_platform, wayland_platform))]
use linux as platform;
#[cfg(macos_platform)]
use macos as platform;
use self::linux as platform;
#[cfg(orbital_platform)]
use orbital as platform;
use self::orbital as platform;
#[cfg(web_platform)]
use web as platform;
use self::web as platform;
#[cfg(windows_platform)]
use windows as platform;
use self::windows as 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::{
@@ -271,16 +272,18 @@ impl EventState {
}
}
pub struct EventLoop<T> {
pub struct EventLoop {
windows: Vec<(Arc<RedoxSocket>, EventState)>,
window_target: event_loop::ActiveEventLoop,
user_events_sender: mpsc::Sender<T>,
user_events_receiver: mpsc::Receiver<T>,
user_events_sender: mpsc::SyncSender<()>,
user_events_receiver: mpsc::Receiver<()>,
}
impl<T: 'static> EventLoop<T> {
impl EventLoop {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
let (user_events_sender, user_events_receiver) = mpsc::channel();
// NOTE: Create a channel which can hold only one event to automatically _squash_ user
// events.
let (user_events_sender, user_events_receiver) = mpsc::sync_channel(1);
let event_socket = Arc::new(
RedoxSocket::event()
@@ -322,14 +325,13 @@ impl<T: 'static> EventLoop<T> {
})
}
fn process_event<F>(
fn process_event<A: ApplicationHandler>(
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 +373,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 +502,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>(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 +524,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 +540,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 +568,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,
);
}
@@ -613,8 +596,8 @@ impl<T: 'static> EventLoop<T> {
i += 1;
}
while let Ok(event) = self.user_events_receiver.try_recv() {
event_handler(event::Event::UserEvent(event), &self.window_target);
while self.user_events_receiver.try_recv().is_ok() {
app.proxy_wake_up(&self.window_target);
}
// To avoid deadlocks the redraws lock is not held during event processing.
@@ -622,16 +605,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 +676,7 @@ impl<T: 'static> EventLoop<T> {
}
}
event_handler(event::Event::LoopExiting, &self.window_target);
app.exiting(&self.window_target);
Ok(())
}
@@ -704,7 +685,7 @@ impl<T: 'static> EventLoop<T> {
&self.window_target
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy {
user_events_sender: self.user_events_sender.clone(),
wake_socket: self.window_target.p.wake_socket.clone(),
@@ -712,24 +693,22 @@ impl<T: 'static> EventLoop<T> {
}
}
pub struct EventLoopProxy<T: 'static> {
user_events_sender: mpsc::Sender<T>,
pub struct EventLoopProxy {
user_events_sender: mpsc::SyncSender<()>,
wake_socket: Arc<TimeSocket>,
}
impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
self.user_events_sender
.send(event)
.map_err(|mpsc::SendError(x)| event_loop::EventLoopClosed(x))?;
self.wake_socket.wake().unwrap();
Ok(())
impl EventLoopProxy {
pub fn wake_up(&self) {
// When we fail to send the event it means that we haven't woken up to read the previous
// event.
if self.user_events_sender.try_send(()).is_ok() {
self.wake_socket.wake().unwrap();
}
}
}
impl<T> Clone for EventLoopProxy<T> {
impl Clone for EventLoopProxy {
fn clone(&self) -> Self {
Self {
user_events_sender: self.user_events_sender.clone(),
@@ -738,7 +717,7 @@ impl<T> Clone for EventLoopProxy<T> {
}
}
impl<T> Unpin for EventLoopProxy<T> {}
impl Unpin for EventLoopProxy {}
pub struct ActiveEventLoop {
control_flow: Cell<ControlFlow>,
@@ -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

@@ -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

@@ -1,8 +1,7 @@
use std::future;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, RecvError, SendError, TryRecvError};
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use std::task::Poll;
use super::AtomicWaker;
@@ -11,54 +10,35 @@ pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
let (sender, receiver) = mpsc::channel();
let shared = Arc::new(Shared { closed: AtomicBool::new(false), waker: AtomicWaker::new() });
let sender =
Sender(Arc::new(SenderInner { sender: Mutex::new(sender), shared: Arc::clone(&shared) }));
let receiver = Receiver { receiver: Rc::new(receiver), shared };
let sender = Sender { sender, shared: Arc::clone(&shared) };
let receiver = Receiver { receiver, shared };
(sender, receiver)
}
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
// having to block.
sender: Mutex<mpsc::Sender<T>>,
pub struct Sender<T> {
sender: mpsc::Sender<T>,
shared: Arc<Shared>,
}
impl<T> Sender<T> {
pub fn send(&self, event: T) -> Result<(), SendError<T>> {
self.0.sender.lock().unwrap().send(event)?;
self.0.shared.waker.wake();
self.sender.send(event)?;
self.shared.waker.wake();
Ok(())
}
}
impl<T> SenderInner<T> {
fn close(&self) {
impl<T> Drop for Sender<T> {
fn drop(&mut self) {
self.shared.closed.store(true, Ordering::Relaxed);
self.shared.waker.wake();
}
}
impl<T> Clone for Sender<T> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<T> Drop for SenderInner<T> {
fn drop(&mut self) {
self.close();
}
}
pub struct Receiver<T> {
receiver: Rc<mpsc::Receiver<T>>,
receiver: mpsc::Receiver<T>,
shared: Arc<Shared>,
}
@@ -95,18 +75,6 @@ impl<T> Receiver<T> {
}
}
impl<T> Clone for Receiver<T> {
fn clone(&self) -> Self {
Self { receiver: Rc::clone(&self.receiver), shared: Arc::clone(&self.shared) }
}
}
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
self.shared.closed.store(true, Ordering::Relaxed);
}
}
struct Shared {
closed: AtomicBool,
waker: AtomicWaker,

View File

@@ -1,9 +1,10 @@
use super::super::main_thread::MainThreadMarker;
use super::{channel, Receiver, Sender, Wrapper};
use std::cell::Ref;
use std::rc::Rc;
use std::sync::{Arc, Condvar, Mutex};
pub struct Dispatcher<T: 'static>(Wrapper<T, Sender<Closure<T>>, Closure<T>>);
pub struct Dispatcher<T: 'static>(Wrapper<T, Arc<Sender<Closure<T>>>, Closure<T>>);
struct Closure<T>(Box<dyn FnOnce(&T) + Send>);
@@ -11,6 +12,8 @@ impl<T> Dispatcher<T> {
#[track_caller]
pub fn new(main_thread: MainThreadMarker, value: T) -> Option<(Self, DispatchRunner<T>)> {
let (sender, receiver) = channel::<Closure<T>>();
let sender = Arc::new(sender);
let receiver = Rc::new(receiver);
Wrapper::new(
main_thread,
@@ -21,7 +24,7 @@ impl<T> Dispatcher<T> {
closure(value.borrow().as_ref().unwrap())
},
{
let receiver = receiver.clone();
let receiver = Rc::clone(&receiver);
move |value| async move {
while let Ok(Closure(closure)) = receiver.next().await {
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't
@@ -89,8 +92,8 @@ impl<T> Dispatcher<T> {
}
pub struct DispatchRunner<T: 'static> {
wrapper: Wrapper<T, Sender<Closure<T>>, Closure<T>>,
receiver: Receiver<Closure<T>>,
wrapper: Wrapper<T, Arc<Sender<Closure<T>>>, Closure<T>>,
receiver: Rc<Receiver<Closure<T>>>,
}
impl<T> DispatchRunner<T> {

View File

@@ -1,19 +1,18 @@
use std::future;
use std::num::NonZeroUsize;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
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, ()>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, ()>);
struct Handler<T> {
value: T,
handler: fn(&T, NonZeroUsize, bool),
handler: fn(&T, bool),
}
#[derive(Clone)]
@@ -21,13 +20,9 @@ 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, bool)) -> Option<Self> {
let inner = Arc::new(Inner {
counter: AtomicUsize::new(0),
awoken: AtomicBool::new(false),
waker: AtomicWaker::new(),
closed: AtomicBool::new(false),
});
@@ -39,49 +34,43 @@ impl<T> WakerSpawner<T> {
let wrapper = Wrapper::new(
main_thread,
handler,
|handler, count| {
|handler, _| {
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, count, true);
(handler.handler)(&handler.value, true);
},
{
let inner = Arc::clone(&inner);
move |handler| async move {
while let Some(count) = future::poll_fn(|cx| {
let count = inner.counter.swap(0, Ordering::Relaxed);
while future::poll_fn(|cx| {
if inner.awoken.swap(false, Ordering::Relaxed) {
Poll::Ready(true)
} else {
inner.waker.register(cx.waker());
match NonZeroUsize::new(count) {
Some(count) => Poll::Ready(Some(count)),
None => {
inner.waker.register(cx.waker());
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 inner.awoken.swap(false, Ordering::Relaxed) {
Poll::Ready(true)
} else {
if inner.closed.load(Ordering::Relaxed) {
return Poll::Ready(false);
}
},
Poll::Pending
}
}
})
.await
{
let handler = handler.borrow();
let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, count, false);
(handler.handler)(&handler.value, false);
}
}
},
sender,
|inner, _| {
inner.0.counter.fetch_add(1, Ordering::Relaxed);
inner.0.awoken.store(true, Ordering::Relaxed);
inner.0.waker.wake();
},
)?;
@@ -93,13 +82,13 @@ impl<T> WakerSpawner<T> {
Waker(self.0.clone())
}
pub fn fetch(&self) -> usize {
pub fn take(&self) -> bool {
debug_assert!(
MainThreadMarker::new().is_some(),
"this should only be called from the main thread"
);
self.0.with_sender_data(|inner| inner.0.counter.swap(0, Ordering::Relaxed))
self.0.with_sender_data(|inner| inner.0.awoken.swap(false, Ordering::Relaxed))
}
}
@@ -114,7 +103,7 @@ impl<T> Drop for WakerSpawner<T> {
impl<T> Waker<T> {
pub fn wake(&self) {
self.0.send(NonZeroUsize::MIN)
self.0.send(())
}
}
@@ -125,7 +114,7 @@ impl<T> Clone for Waker<T> {
}
struct Inner {
counter: AtomicUsize,
awoken: AtomicBool,
waker: AtomicWaker,
closed: AtomicBool,
}

View File

@@ -542,8 +542,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,6 +1,6 @@
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;
@@ -16,48 +16,30 @@ mod window_target;
pub(crate) use proxy::EventLoopProxy;
pub(crate) use window_target::{ActiveEventLoop, OwnedDisplayHandle};
pub struct EventLoop<T: 'static> {
pub struct EventLoop {
elw: RootActiveEventLoop,
user_event_sender: Sender<T>,
user_event_receiver: Receiver<T>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
impl<T> EventLoop<T> {
impl EventLoop {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
let (user_event_sender, user_event_receiver) = mpsc::channel();
let elw = RootActiveEventLoop { p: ActiveEventLoop::new(), _marker: PhantomData };
Ok(EventLoop { elw, user_event_sender, user_event_receiver })
Ok(EventLoop { elw })
}
pub fn run<F>(self, mut event_handler: F) -> !
where
F: FnMut(Event<T>, &RootActiveEventLoop),
{
pub fn run_app<A: ApplicationHandler>(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, 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,
)
std::mem::transmute::<Box<dyn FnMut(Event)>, Box<dyn FnMut(Event) + 'static>>(handler)
};
self.elw.p.run(handler, false);
@@ -70,31 +52,14 @@ 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 + '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)
}),
true,
);
self.elw.p.run(Box::new(move |event| handle_event(&mut app, &target, event)), true);
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.elw.p.waker(), self.user_event_sender.clone())
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy::new(self.elw.p.waker())
}
pub fn window_target(&self) -> &RootActiveEventLoop {
@@ -117,3 +82,17 @@ impl<T> EventLoop<T> {
self.elw.wait_until_strategy()
}
}
fn handle_event<A: ApplicationHandler>(app: &mut A, target: &RootActiveEventLoop, 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::UserWakeUp => app.proxy_wake_up(target),
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,29 +1,19 @@
use std::rc::Weak;
use std::sync::mpsc::{SendError, Sender};
use super::runner::Execution;
use crate::event_loop::EventLoopClosed;
use crate::platform_impl::platform::r#async::Waker;
pub struct EventLoopProxy<T: 'static> {
#[derive(Clone)]
pub struct EventLoopProxy {
runner: Waker<Weak<Execution>>,
sender: Sender<T>,
}
impl<T: 'static> EventLoopProxy<T> {
pub fn new(runner: Waker<Weak<Execution>>, sender: Sender<T>) -> Self {
Self { runner, sender }
impl EventLoopProxy {
pub fn new(runner: Waker<Weak<Execution>>) -> Self {
Self { runner }
}
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender.send(event).map_err(|SendError(event)| EventLoopClosed(event))?;
pub fn wake_up(&self) {
self.runner.wake();
Ok(())
}
}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self { runner: self.runner.clone(), sender: self.sender.clone() }
}
}

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;
@@ -26,9 +14,20 @@ use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawne
use crate::platform_impl::platform::window::Inner;
use crate::window::WindowId;
use js_sys::Function;
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::{wasm_bindgen, Closure};
use wasm_bindgen::JsCast;
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<()>);
pub(super) type EventHandler = dyn FnMut(Event);
impl Clone for Shared {
fn clone(&self) -> Self {
@@ -137,13 +136,12 @@ 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, local| {
if let Some(runner) = runner.upgrade() {
Shared(runner).send_proxy_wake_up(local);
}
})
.expect("`EventLoop` has to be created in the main thread");
Execution {
main_thread,
@@ -203,7 +201,7 @@ impl Shared {
// Set the event callback to use for the event loop runner
// This the event callback is a fairly thin layer over the user-provided callback that closes
// over a RootActiveEventLoop reference
pub fn set_listener(&self, event_handler: Box<EventHandler>) {
pub(crate) fn set_listener(&self, event_handler: Box<EventHandler>) {
{
let mut runner = self.0.runner.borrow_mut();
assert!(matches!(*runner, RunnerEnum::Pending));
@@ -245,10 +243,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 +321,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 +344,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 +368,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 +386,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,11 +464,11 @@ impl Shared {
self.send_events(iter::once(event));
}
// Add a series of user events to the event loop runner
// Add a user event 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) {
pub(crate) fn send_proxy_wake_up(&self, local: bool) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
@@ -462,14 +479,18 @@ impl Shared {
if let Ok(RunnerEnum::Running(_)) =
self.0.runner.try_borrow().as_ref().map(Deref::deref)
{
self.window().queue_microtask(
&Closure::once_into_js({
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = queueMicrotask)]
fn queue_microtask(task: Function);
}
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()),
)
Shared(shared).send_event(Event::UserWakeUp)
}
}
})
@@ -480,7 +501,7 @@ impl Shared {
}
}
self.send_events(iter::repeat(Event::UserEvent(())).take(count.get()))
self.send_event(Event::UserWakeUp);
}
// Add a series of events to the event loop runner
@@ -494,13 +515,8 @@ impl Shared {
// If we can run the event processing right now, or need to queue this and wait for later
let mut process_immediately = true;
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.
if let State::Poll { .. } = runner.state {
process_immediately = false;
}
},
// If the runner is attached but not running, we always wake it up.
Ok(RunnerEnum::Running(_)) => (),
Ok(RunnerEnum::Pending) => {
// The runner still hasn't been attached: queue this event and wait for it to be
process_immediately = false;
@@ -628,8 +644,10 @@ impl Shared {
// Pre-fetch `UserEvent`s to avoid having to wait until the next event loop cycle.
events.extend(
iter::repeat(Event::UserEvent(()))
.take(self.0.proxy_spawner.fetch())
self.0
.proxy_spawner
.take()
.then_some(Event::UserWakeUp)
.map(EventWrapper::from),
);
@@ -717,7 +735,7 @@ impl Shared {
// * The `register_redraw_request` closure.
// * The `destroy_fn` closure.
if self.0.event_loop_recreation.get() {
crate::event_loop::EventLoopBuilder::<()>::allow_event_loop_recreation();
crate::event_loop::EventLoopBuilder::allow_event_loop_recreation();
}
}
@@ -797,12 +815,12 @@ impl Shared {
}
pub(crate) enum EventWrapper {
Event(Event<()>),
Event(Event),
ScaleChange { canvas: Weak<RefCell<backend::Canvas>>, size: PhysicalSize<u32>, scale: f64 },
}
impl From<Event<()>> for EventWrapper {
fn from(value: Event<()>) -> Self {
impl From<Event> for EventWrapper {
fn from(value: Event) -> Self {
Self::Event(value)
}
}

View File

@@ -55,7 +55,11 @@ impl ActiveEventLoop {
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
}
pub fn run(&self, event_handler: Box<runner::EventHandler>, event_loop_recreation: bool) {
pub(crate) fn run(
&self,
event_handler: Box<runner::EventHandler>,
event_loop_recreation: bool,
) {
self.runner.event_loop_recreation(event_loop_recreation);
self.runner.set_listener(event_handler);
}
@@ -142,7 +146,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 +182,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 +262,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 +376,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 +454,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 +609,7 @@ impl ActiveEventLoop {
window_id: RootWindowId(id),
event: WindowEvent::Resized(new_size),
});
canvas.request_animation_frame();
runner.request_redraw(RootWindowId(id));
}
}
},
@@ -614,16 +662,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)
}

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,
@@ -460,7 +483,7 @@ impl Canvas {
pub(crate) fn handle_scale_change(
&self,
runner: &super::super::event_loop::runner::Shared,
event_handler: impl FnOnce(crate::event::Event<()>),
event_handler: impl FnOnce(crate::event::Event),
current_size: PhysicalSize<u32>,
scale: f64,
) {

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

@@ -139,9 +139,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);
}

View File

@@ -87,7 +87,7 @@ impl Schedule {
let duration = duration
.as_secs()
.checked_mul(1000)
.and_then(|secs| secs.checked_add(duration_millis_ceil(duration).into()))
.and_then(|secs| secs.checked_add(duration.subsec_micros().div_ceil(1000).into()))
.unwrap_or(u64::MAX);
options.delay(duration as f64);
@@ -140,7 +140,9 @@ impl Schedule {
.ok()
.and_then(|secs: i32| secs.checked_mul(1000))
.and_then(|secs: i32| {
let millis: i32 = duration_millis_ceil(duration)
let millis: i32 = duration
.subsec_micros()
.div_ceil(1000)
.try_into()
.expect("millis are somehow bigger then 1K");
secs.checked_add(millis)
@@ -189,7 +191,7 @@ impl Schedule {
.try_into()
.ok()
.and_then(|secs: u32| secs.checked_mul(1000))
.and_then(|secs| secs.checked_add(duration_millis_ceil(duration)))
.and_then(|secs| secs.checked_add(duration.subsec_micros().div_ceil(1000)))
.unwrap_or(u32::MAX);
WORKER
@@ -224,20 +226,6 @@ impl Drop for Schedule {
}
}
// TODO: Replace with `u32::div_ceil()` when we hit Rust v1.73.
fn duration_millis_ceil(duration: Duration) -> u32 {
let micros = duration.subsec_micros();
// From <https://doc.rust-lang.org/1.73.0/src/core/num/uint_macros.rs.html#2086-2094>.
let d = micros / 1000;
let r = micros % 1000;
if r > 0 && 1000 > 0 {
d + 1
} else {
d
}
}
fn has_scheduler_support(window: &web_sys::Window) -> bool {
thread_local! {
static SCHEDULER_SUPPORT: OnceCell<bool> = const { OnceCell::new() };
@@ -286,8 +274,8 @@ 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 mut property = BlobPropertyBag::new();
property.type_("text/javascript");
let blob = Blob::new_with_str_sequence_and_options(&sequence, &property)
.expect("`new Blob()` should never throw");

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()
}

View File

@@ -25,7 +25,7 @@ pub struct FileDropHandlerData {
pub interface: IDropTarget,
refcount: AtomicUsize,
window: HWND,
send_event: Box<dyn Fn(Event<()>)>,
send_event: Box<dyn Fn(Event)>,
cursor_effect: u32,
hovered_is_valid: bool, /* If the currently hovered item is not valid there must not be any
* `HoveredFileCancelled` emitted */
@@ -37,7 +37,7 @@ pub struct FileDropHandler {
#[allow(non_snake_case)]
impl FileDropHandler {
pub fn new(window: HWND, send_event: Box<dyn Fn(Event<()>)>) -> FileDropHandler {
pub(crate) fn new(window: HWND, send_event: Box<dyn Fn(Event)>) -> FileDropHandler {
let data = Box::new(FileDropHandlerData {
interface: IDropTarget { lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl },
refcount: AtomicUsize::new(1),
@@ -211,7 +211,7 @@ impl FileDropHandler {
}
impl FileDropHandlerData {
fn send_event(&self, event: Event<()>) {
fn send_event(&self, event: Event) {
(self.send_event)(event);
}
}

View File

@@ -6,10 +6,8 @@ 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};
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::{Duration, Instant};
use std::{mem, panic, ptr};
@@ -17,18 +15,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 +37,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,12 +56,13 @@ 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::{
DeviceEvent, Event, Force, Ime, InnerSizeWriter, RawKeyEvent, Touch, TouchPhase, WindowEvent,
};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents, EventLoopClosed};
use crate::event_loop::{ActiveEventLoop as RootAEL, ControlFlow, DeviceEvents};
use crate::keyboard::ModifiersState;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::platform::dark_mode::try_theme;
@@ -87,37 +81,16 @@ 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};
use runner::EventLoopRunner;
use super::window::set_skip_taskbar;
use super::SelectedCursor;
/// some backends like macos uses an uninhabited `Never` type,
/// on windows, `UserEvent`s are also dispatched through the
/// WNDPROC callback, and due to the re-entrant nature of the
/// callback, recursively delivered events must be queued in a
/// buffer, the current implementation put this queue in
/// `EventLoopRunner`, which is shared between the event pumping
/// loop and the callback. because it's hard to decide from the
/// outside whether a event needs to be buffered, I decided not
/// use `Event<Never>` for the shared runner state, but use unit
/// as a placeholder so user events can be buffered as usual,
/// the real `UserEvent` is pulled from the mpsc channel directly
/// when the placeholder event is delivered to the event handler
pub(crate) struct UserEventPlaceholder;
// here below, the generic `EventLoopRunnerShared<T>` is replaced with
// `EventLoopRunnerShared<UserEventPlaceholder>` so we can get rid
// of the generic parameter T in types which don't depend on T.
// this is the approach which requires minimum changes to current
// backend implementation. it should be considered transitional
// and should be refactored and cleaned up eventually, I hope.
pub(crate) struct WindowData {
pub window_state: Arc<Mutex<WindowState>>,
pub event_loop_runner: EventLoopRunnerShared<UserEventPlaceholder>,
pub event_loop_runner: Rc<EventLoopRunner>,
pub key_event_builder: KeyEventBuilder,
pub _file_drop_handler: Option<FileDropHandler>,
pub userdata_removed: Cell<bool>,
@@ -125,7 +98,7 @@ pub(crate) struct WindowData {
}
impl WindowData {
fn send_event(&self, event: Event<UserEventPlaceholder>) {
fn send_event(&self, event: Event) {
self.event_loop_runner.send_event(event);
}
@@ -135,11 +108,11 @@ impl WindowData {
}
struct ThreadMsgTargetData {
event_loop_runner: EventLoopRunnerShared<UserEventPlaceholder>,
event_loop_runner: Rc<EventLoopRunner>,
}
impl ThreadMsgTargetData {
fn send_event(&self, event: Event<UserEventPlaceholder>) {
fn send_event(&self, event: Event) {
self.event_loop_runner.send_event(event);
}
}
@@ -151,15 +124,9 @@ pub(crate) enum ProcResult {
Value(isize),
}
pub struct EventLoop<T: 'static> {
user_event_sender: Sender<T>,
user_event_receiver: Receiver<T>,
pub struct EventLoop {
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 {
@@ -177,10 +144,10 @@ impl Default for PlatformSpecificEventLoopAttributes {
pub struct ActiveEventLoop {
thread_id: u32,
thread_msg_target: HWND,
pub(crate) runner_shared: EventLoopRunnerShared<UserEventPlaceholder>,
pub(crate) runner_shared: Rc<EventLoopRunner>,
}
impl<T: 'static> EventLoop<T> {
impl EventLoop {
pub(crate) fn new(
attributes: &mut PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
@@ -203,7 +170,6 @@ impl<T: 'static> EventLoop<T> {
let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target));
let (user_event_sender, user_event_receiver) = mpsc::channel();
insert_event_target_window_data(thread_msg_target, runner_shared.clone());
raw_input::register_all_mice_and_keyboards_for_raw_input(
thread_msg_target,
@@ -211,14 +177,11 @@ impl<T: 'static> EventLoop<T> {
);
Ok(EventLoop {
user_event_sender,
user_event_receiver,
window_target: RootAEL {
p: ActiveEventLoop { thread_id, thread_msg_target, runner_shared },
_marker: PhantomData,
},
msg_hook: attributes.msg_hook.take(),
high_resolution_timer: None,
})
}
@@ -226,50 +189,43 @@ 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>(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>(
&mut self,
app: &mut A,
) -> Result<(), EventLoopError> {
{
let runner = &self.window_target.p.runner_shared;
let event_loop_windows_ref = &self.window_target;
let user_event_receiver = &self.user_event_receiver;
// # Safety
// We make sure to call runner.clear_event_handler() before
// 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
.try_recv()
.expect("user event signaled but not received"),
),
};
event_handler(event, event_loop_windows_ref)
runner.set_event_handler(move |event| 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)
},
Event::UserWakeUp => app.proxy_wake_up(event_loop_windows_ref),
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,14 +252,15 @@ 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>(
&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;
let user_event_receiver = &self.user_event_receiver;
// let user_event_receiver = &self.user_event_receiver;
// # Safety
// We make sure to call runner.clear_event_handler() before
@@ -313,26 +270,28 @@ impl<T: 'static> EventLoop<T> {
// to leave the runner in an unsound state with an associated
// 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)
runner.set_event_handler(move |event| 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)
},
Event::UserWakeUp => app.proxy_wake_up(event_loop_windows_ref),
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 +321,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 +434,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 {
@@ -435,11 +468,8 @@ impl<T: 'static> EventLoop<T> {
}
}
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
target_window: self.window_target.p.thread_msg_target,
event_send: self.user_event_sender.clone(),
}
pub fn create_proxy(&self) -> EventLoopProxy {
EventLoopProxy { target_window: self.window_target.p.thread_msg_target }
}
fn exit_code(&self) -> Option<i32> {
@@ -491,10 +521,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 +590,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]
@@ -615,7 +641,7 @@ fn dur2timeout(dur: Duration) -> u32 {
.unwrap_or(INFINITE)
}
impl<T> Drop for EventLoop<T> {
impl Drop for EventLoop {
fn drop(&mut self) {
unsafe {
DestroyWindow(self.window_target.p.thread_msg_target);
@@ -623,144 +649,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,
@@ -811,27 +699,16 @@ impl EventLoopThreadExecutor {
type ThreadExecFn = Box<Box<dyn FnMut()>>;
pub struct EventLoopProxy<T: 'static> {
#[derive(Clone)]
pub struct EventLoopProxy {
target_window: HWND,
event_send: Sender<T>,
}
unsafe impl<T: Send + 'static> Send for EventLoopProxy<T> {}
impl<T: 'static> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self { target_window: self.target_window, event_send: self.event_send.clone() }
}
}
impl<T: 'static> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.event_send
.send(event)
.map(|result| {
unsafe { PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) };
result
})
.map_err(|e| EventLoopClosed(e.0))
unsafe impl Send for EventLoopProxy {}
impl EventLoopProxy {
pub fn wake_up(&self) {
unsafe { PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) };
}
}
@@ -963,7 +840,7 @@ fn create_event_target_window() -> HWND {
fn insert_event_target_window_data(
thread_msg_target: HWND,
event_loop_runner: EventLoopRunnerShared<UserEventPlaceholder>,
event_loop_runner: Rc<EventLoopRunner>,
) {
let userdata = ThreadMsgTargetData { event_loop_runner };
let input_ptr = Box::into_raw(Box::new(userdata));
@@ -2509,7 +2386,7 @@ unsafe extern "system" fn thread_event_target_callback(
// user event is still in the mpsc channel and will be pulled
// once the placeholder event is delivered to the wrapper
// `event_handler`
userdata.send_event(Event::UserEvent(UserEventPlaceholder));
userdata.send_event(Event::UserWakeUp);
0
},
_ if msg == EXEC_MSG_ID.get() => {
@@ -2524,7 +2401,7 @@ unsafe extern "system" fn thread_event_target_callback(
if userdata_removed {
drop(userdata);
} else {
Box::leak(userdata);
Box::into_raw(userdata);
}
result
}

View File

@@ -1,7 +1,6 @@
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::{mem, panic};
@@ -16,11 +15,9 @@ use crate::window::WindowId;
use super::ControlFlow;
pub(crate) type EventLoopRunnerShared<T> = Rc<EventLoopRunner<T>>;
type EventHandler = Cell<Option<Box<dyn FnMut(Event)>>>;
type EventHandler<T> = Cell<Option<Box<dyn FnMut(Event<T>)>>>;
pub(crate) struct EventLoopRunner<T: 'static> {
pub(crate) struct EventLoopRunner {
// The event loop's win32 handles
pub(super) thread_msg_target: HWND,
@@ -33,8 +30,8 @@ pub(crate) struct EventLoopRunner<T: 'static> {
exit: Cell<Option<i32>>,
runner_state: Cell<RunnerState>,
last_events_cleared: Cell<Instant>,
event_handler: EventHandler<T>,
event_buffer: RefCell<VecDeque<BufferedEvent<T>>>,
event_handler: EventHandler,
event_buffer: RefCell<VecDeque<BufferedEvent>>,
panic_error: Cell<Option<PanicError>>,
}
@@ -55,13 +52,13 @@ pub(crate) enum RunnerState {
Destroyed,
}
enum BufferedEvent<T: 'static> {
Event(Event<T>),
enum BufferedEvent {
Event(Event),
ScaleFactorChanged(WindowId, f64, PhysicalSize<u32>),
}
impl<T> EventLoopRunner<T> {
pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner<T> {
impl EventLoopRunner {
pub(crate) fn new(thread_msg_target: HWND) -> EventLoopRunner {
EventLoopRunner {
thread_msg_target,
interrupt_msg_dispatch: Cell::new(false),
@@ -88,13 +85,12 @@ impl<T> EventLoopRunner<T> {
/// undefined behaviour.
pub(crate) unsafe fn set_event_handler<F>(&self, f: F)
where
F: FnMut(Event<T>),
F: FnMut(Event),
{
// Erase closure lifetime.
// SAFETY: Caller upholds that the lifetime of the closure is upheld.
let f = unsafe {
mem::transmute::<Box<dyn FnMut(Event<T>)>, Box<dyn FnMut(Event<T>)>>(Box::new(f))
};
let f =
unsafe { mem::transmute::<Box<dyn FnMut(Event)>, Box<dyn FnMut(Event)>>(Box::new(f)) };
let old_event_handler = self.event_handler.replace(Some(f));
assert!(old_event_handler.is_none());
}
@@ -124,7 +120,7 @@ impl<T> EventLoopRunner<T> {
}
/// State retrieval functions.
impl<T> EventLoopRunner<T> {
impl EventLoopRunner {
#[allow(unused)]
pub fn thread_msg_target(&self) -> HWND {
self.thread_msg_target
@@ -166,7 +162,7 @@ impl<T> EventLoopRunner<T> {
}
/// Misc. functions
impl<T> EventLoopRunner<T> {
impl EventLoopRunner {
pub fn catch_unwind<R>(&self, f: impl FnOnce() -> R) -> Option<R> {
let panic_error = self.panic_error.take();
if panic_error.is_none() {
@@ -196,7 +192,7 @@ impl<T> EventLoopRunner<T> {
}
/// Event dispatch functions.
impl<T> EventLoopRunner<T> {
impl EventLoopRunner {
pub(crate) fn prepare_wait(&self) {
self.move_state_to(RunnerState::Idle);
}
@@ -205,7 +201,7 @@ impl<T> EventLoopRunner<T> {
self.move_state_to(RunnerState::HandlingMainEvents);
}
pub(crate) fn send_event(&self, event: Event<T>) {
pub(crate) fn send_event(&self, event: Event) {
if let Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } = event {
self.call_event_handler(event);
// As a rule, to ensure that `pump_events` can't block an external event loop
@@ -226,7 +222,7 @@ impl<T> EventLoopRunner<T> {
self.move_state_to(RunnerState::Destroyed);
}
fn call_event_handler(&self, event: Event<T>) {
fn call_event_handler(&self, event: Event) {
self.catch_unwind(|| {
let mut event_handler = self.event_handler.take().expect(
"either event handler is re-entrant (likely), or no event handler is registered \
@@ -358,8 +354,8 @@ impl<T> EventLoopRunner<T> {
}
}
impl<T> BufferedEvent<T> {
pub fn from_event(event: Event<T>) -> BufferedEvent<T> {
impl BufferedEvent {
pub fn from_event(event: Event) -> BufferedEvent {
match event {
Event::WindowEvent {
event: WindowEvent::ScaleFactorChanged { scale_factor, inner_size_writer },
@@ -373,23 +369,23 @@ impl<T> BufferedEvent<T> {
}
}
pub fn dispatch_event(self, dispatch: impl FnOnce(Event<T>)) {
pub fn dispatch_event(self, dispatch: impl FnOnce(Event)) {
match self {
Self::Event(event) => dispatch(event),
Self::ScaleFactorChanged(window_id, scale_factor, new_inner_size) => {
let user_new_inner_size = Arc::new(Mutex::new(new_inner_size));
let user_new_innner_size = Arc::new(Mutex::new(new_inner_size));
dispatch(Event::WindowEvent {
window_id,
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&user_new_inner_size,
&user_new_innner_size,
)),
},
});
let inner_size = *user_new_inner_size.lock().unwrap();
let inner_size = *user_new_innner_size.lock().unwrap();
drop(user_new_inner_size);
drop(user_new_innner_size);
if inner_size != new_inner_size {
let window_flags = unsafe {

View File

@@ -98,7 +98,7 @@ impl KeyEventBuilder {
MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events))
},
WM_KILLFOCUS => {
// synthesize keyup events
// sythesize keyup events
let kbd_state = get_kbd_state();
let key_events = Self::synthesize_kbd_state(ElementState::Released, &kbd_state);
MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events))
@@ -334,11 +334,11 @@ impl KeyEventBuilder {
// We are synthesizing the press event for caps-lock first for the following reasons:
// 1. If caps-lock is *not* held down but *is* active, then we have to synthesize all
// printable keys, respecting the caps-lock state.
// 2. If caps-lock is held down, we could choose to synthesize its keypress after every
// other key, in which case all other keys *must* be sythesized as if the caps-lock state
// was be the opposite of what it currently is.
// 2. If caps-lock is held down, we could choose to sythesize its keypress after every other
// key, in which case all other keys *must* be sythesized as if the caps-lock state was
// be the opposite of what it currently is.
// --
// For the sake of simplicity we are choosing to always synthesize
// For the sake of simplicity we are choosing to always sythesize
// caps-lock first, and always use the current caps-lock state
// to determine the produced text
if is_key_pressed!(VK_CAPITAL) {
@@ -556,7 +556,7 @@ impl PartialKeyEventInfo {
// We convert dead keys into their character.
// The reason for this is that `key_without_modifiers` is designed for key-bindings,
// but the US International layout treats `'` (apostrophe) as a dead key and the
// regular US layout treats it a character. In order for a single binding
// reguar US layout treats it a character. In order for a single binding
// configuration to work with both layouts, we forward each dead key as a character.
Key::Dead(k) => {
if let Some(ch) = k {

View File

@@ -67,7 +67,7 @@ unsafe impl Sync for PlatformSpecificWindowAttributes {}
pub struct DeviceId(u32);
impl DeviceId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
DeviceId(0)
}
}
@@ -103,7 +103,7 @@ unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
impl WindowId {
pub const fn dummy() -> Self {
pub const unsafe fn dummy() -> Self {
WindowId(0)
}
}

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