mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 22:53:15 -04:00
Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6893a4390 | ||
|
|
c0a8bedee2 | ||
|
|
b248ecba31 | ||
|
|
b49d34ebf0 | ||
|
|
cc43ea13d9 | ||
|
|
911fad0af0 | ||
|
|
2191eacfc8 | ||
|
|
f7ac8127e3 | ||
|
|
bd2b5cda8d | ||
|
|
3930a6334f | ||
|
|
17b5737972 | ||
|
|
f49a2a1827 | ||
|
|
2385410366 | ||
|
|
6db1343c0b | ||
|
|
a26899a75d | ||
|
|
80bddda641 | ||
|
|
5f1e9f6cc1 | ||
|
|
57baf72741 | ||
|
|
da7a09658a | ||
|
|
53321dc6f5 | ||
|
|
6556cde246 | ||
|
|
7672fd5657 | ||
|
|
847511672a | ||
|
|
53bbe6c273 | ||
|
|
a224b3de06 | ||
|
|
114599c2da | ||
|
|
aaecc92b62 | ||
|
|
c6cfa048b0 | ||
|
|
c591089ece | ||
|
|
ec7677d692 | ||
|
|
b9b2f1643e | ||
|
|
1db15b6875 | ||
|
|
37a4394a3e | ||
|
|
1ae4f5cdea | ||
|
|
501d9b4a44 | ||
|
|
487137b867 | ||
|
|
b77ea7d218 | ||
|
|
3154c60ef4 | ||
|
|
abfe90bddb | ||
|
|
090498a4a6 | ||
|
|
58402b58cf | ||
|
|
d7710f7264 | ||
|
|
61314cd50a | ||
|
|
6b5cc165dd | ||
|
|
43c323ccc0 | ||
|
|
9cbce055d3 | ||
|
|
727583ffbf | ||
|
|
82f0949aea | ||
|
|
5d2151e893 | ||
|
|
6f22822025 | ||
|
|
cf829a44e7 | ||
|
|
f2fb7dee3d | ||
|
|
836e717021 | ||
|
|
1e44c354fe | ||
|
|
1fb118031c | ||
|
|
d6ab3dcb86 | ||
|
|
d828523b39 | ||
|
|
fccfd359db | ||
|
|
5c1cc652c4 | ||
|
|
1d18a6ca57 | ||
|
|
bab2d82fd3 | ||
|
|
bbab3d8e35 | ||
|
|
b02a70e6bb | ||
|
|
9ceb8d4144 | ||
|
|
485ae90aae | ||
|
|
107bf91a2d | ||
|
|
1bc405e526 | ||
|
|
685090021b | ||
|
|
cfe7eb8954 | ||
|
|
838b2459a7 | ||
|
|
f4a5ad9c22 | ||
|
|
00ecd1533e | ||
|
|
114512bb32 | ||
|
|
8c07319ced | ||
|
|
fa3ec006e2 | ||
|
|
f3d1f922f9 | ||
|
|
77e8fe6094 | ||
|
|
5fce4a09de | ||
|
|
5735786f42 | ||
|
|
9ca8c09208 | ||
|
|
4b1aa51094 | ||
|
|
1dec9b4b33 | ||
|
|
04d8a284a0 | ||
|
|
949cb0f203 | ||
|
|
bf68ac0b14 | ||
|
|
ba188376d1 | ||
|
|
6509f8a18b | ||
|
|
71dea4637d | ||
|
|
ec24c3efd3 | ||
|
|
feca480b4c | ||
|
|
655fdc896f | ||
|
|
faa641e57f | ||
|
|
7c81364e1c | ||
|
|
6abfef1220 | ||
|
|
d2d4d20108 | ||
|
|
d8f4d8f1b7 | ||
|
|
a974640a66 | ||
|
|
3d7d766182 | ||
|
|
c73d8cff20 | ||
|
|
79aa95b212 | ||
|
|
ecd14688dc | ||
|
|
b512ed1e63 | ||
|
|
96388f4f6b | ||
|
|
1745b01502 | ||
|
|
54e974c090 | ||
|
|
b14d5c0c99 | ||
|
|
437747b966 | ||
|
|
21e266f3b7 | ||
|
|
3a0928af45 | ||
|
|
ad92b4f89d | ||
|
|
bf4445bb62 | ||
|
|
391a22217d | ||
|
|
fb4a674ee5 | ||
|
|
43f296b2b3 | ||
|
|
042667c5eb | ||
|
|
3206d105fe | ||
|
|
1afec3ca0d | ||
|
|
c4a8e9321d | ||
|
|
dee7a405fc | ||
|
|
a298b4d00e | ||
|
|
aebd5edc9e | ||
|
|
c801b69d3e | ||
|
|
ebd6454f8f | ||
|
|
4f2f0bc08f |
8
.github/CODEOWNERS
vendored
8
.github/CODEOWNERS
vendored
@@ -1,6 +1,6 @@
|
||||
# Android
|
||||
/src/platform/android.rs @msiglreith @MarijnS95
|
||||
/src/platform_impl/android @msiglreith @MarijnS95
|
||||
/src/platform/android.rs @MarijnS95
|
||||
/src/platform_impl/android @MarijnS95
|
||||
|
||||
# iOS
|
||||
/src/platform/ios.rs @madsmtm
|
||||
@@ -26,8 +26,8 @@
|
||||
/src/platform_impl/web @daxpedda
|
||||
|
||||
# Windows
|
||||
/src/platform/windows.rs @msiglreith
|
||||
/src/platform_impl/windows @msiglreith
|
||||
/src/platform/windows.rs @notgull
|
||||
/src/platform_impl/windows @notgull
|
||||
|
||||
# Orbital (Redox OS)
|
||||
/src/platform/orbital.rs @jackpot51
|
||||
|
||||
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: taiki-e/checkout-action@v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rustfmt
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: taiki-e/checkout-action@v1
|
||||
- uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: typos-cli
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
CMD: ${{ matrix.platform.cmd }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: taiki-e/checkout-action@v1
|
||||
|
||||
- name: Restore cache of cargo folder
|
||||
# We use `restore` and later `save`, so that we can create the key after
|
||||
@@ -107,7 +107,12 @@ jobs:
|
||||
|
||||
- name: Generate lockfile
|
||||
# Also updates the crates.io index
|
||||
run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0
|
||||
run: |
|
||||
cargo generate-lockfile
|
||||
cargo update -p ahash --precise 0.8.7
|
||||
cargo update -p bumpalo --precise 3.14.0
|
||||
cargo update -p objc2-encode --precise 4.0.3
|
||||
cargo update -p orbclient --precise 0.3.47
|
||||
|
||||
- name: Install GCC Multilib
|
||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||
@@ -220,9 +225,24 @@ jobs:
|
||||
- { name: 'Windows', target: x86_64-pc-windows-gnu }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
- uses: taiki-e/checkout-action@v1
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
with:
|
||||
command: check
|
||||
log-level: error
|
||||
arguments: --all-features --target ${{ matrix.platform.target }}
|
||||
|
||||
swc:
|
||||
name: Minimize JavaScript
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: taiki-e/checkout-action@v1
|
||||
- name: Install SWC
|
||||
run: sudo npm i -g @swc/cli
|
||||
- name: Run SWC
|
||||
run: |
|
||||
swc src/platform_impl/web/web_sys/worker.js -o src/platform_impl/web/web_sys/worker.min.js
|
||||
- name: Check for diff
|
||||
run: |
|
||||
[[ -z $(git status -s) ]]
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,8 +3,5 @@ target/
|
||||
rls/
|
||||
.vscode/
|
||||
*~
|
||||
*.wasm
|
||||
*.ts
|
||||
*.js
|
||||
#*#
|
||||
.DS_Store
|
||||
|
||||
12
.swcrc
Normal file
12
.swcrc
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"minify": true,
|
||||
"jsc": {
|
||||
"target": "es2022",
|
||||
"minify": {
|
||||
"compress": {
|
||||
"unused": true
|
||||
},
|
||||
"mangle": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,11 @@ All patches have to be sent on Github as [pull requests][prs]. To simplify your
|
||||
life during review it's recommended to check the "give contributors write access
|
||||
to the branch" checkbox.
|
||||
|
||||
We use unstable Rustfmt options across the project, so please run
|
||||
`cargo +nightly fmt` before submitting your work. If you are unable to do so,
|
||||
the maintainers can do it for you before merging, just state so in your pull
|
||||
request description.
|
||||
|
||||
#### Handling review
|
||||
|
||||
During the review process certain events could require an action from your side,
|
||||
|
||||
181
Cargo.toml
181
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "winit"
|
||||
version = "0.30.0"
|
||||
version = "0.30.12"
|
||||
authors = [
|
||||
"The winit contributors",
|
||||
"Pierre Krieger <pierre.krieger1708@gmail.com>",
|
||||
@@ -14,7 +14,17 @@ rust-version.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
exclude = ["/.cargo"]
|
||||
include = [
|
||||
"/build.rs",
|
||||
"/docs",
|
||||
"/examples",
|
||||
"/FEATURES.md",
|
||||
"/LICENSE",
|
||||
"/src",
|
||||
"!/src/platform_impl/web/script",
|
||||
"/src/platform_impl/web/script/**/*.min.js",
|
||||
"/tests",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
@@ -71,7 +81,7 @@ rwh_05 = ["dep:rwh_05", "ndk/rwh_05"]
|
||||
rwh_06 = ["dep:rwh_06", "ndk/rwh_06"]
|
||||
|
||||
[build-dependencies]
|
||||
cfg_aliases = "0.2.0"
|
||||
cfg_aliases = "0.2.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2"
|
||||
@@ -86,11 +96,11 @@ rwh_06 = { package = "raw-window-handle", version = "0.6", features = [
|
||||
], optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
smol_str = "0.2.0"
|
||||
tracing = { version = "0.1.40", default_features = false }
|
||||
tracing = { version = "0.1.40", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
image = { version = "0.25.0", default-features = false, features = ["png"] }
|
||||
tracing = { version = "0.1.40", default_features = false, features = ["log"] }
|
||||
tracing = { version = "0.1.40", default-features = false, features = ["log"] }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
winit = { path = ".", features = ["rwh_05"] }
|
||||
|
||||
@@ -107,36 +117,43 @@ android-activity = "0.6.0"
|
||||
ndk = { version = "0.9.0", default-features = false }
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
||||
block2 = "0.5.1"
|
||||
core-foundation = "0.9.3"
|
||||
objc2 = "0.5.1"
|
||||
objc2 = { version = "0.5.2", features = ["relax-sign-encoding"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-graphics = "0.23.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
|
||||
version = "0.2.0"
|
||||
version = "0.2.2"
|
||||
features = [
|
||||
"block2",
|
||||
"dispatch",
|
||||
"NSArray",
|
||||
"NSAttributedString",
|
||||
"NSData",
|
||||
"NSDictionary",
|
||||
"NSDistributedNotificationCenter",
|
||||
"NSEnumerator",
|
||||
"NSKeyValueObserving",
|
||||
"NSNotification",
|
||||
"NSObjCRuntime",
|
||||
"NSString",
|
||||
"NSPathUtilities",
|
||||
"NSProcessInfo",
|
||||
"NSRunLoop",
|
||||
"NSString",
|
||||
"NSThread",
|
||||
"NSValue",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.objc2-app-kit]
|
||||
version = "0.2.0"
|
||||
version = "0.2.2"
|
||||
features = [
|
||||
"NSAppearance",
|
||||
"NSApplication",
|
||||
"NSBitmapImageRep",
|
||||
"NSButton",
|
||||
"NSColor",
|
||||
"NSControl",
|
||||
"NSCursor",
|
||||
"NSDragging",
|
||||
@@ -161,19 +178,46 @@ features = [
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies.objc2-foundation]
|
||||
version = "0.2.0"
|
||||
version = "0.2.2"
|
||||
features = [
|
||||
"block2",
|
||||
"dispatch",
|
||||
"NSArray",
|
||||
"NSEnumerator",
|
||||
"NSGeometry",
|
||||
"NSObjCRuntime",
|
||||
"NSOperation",
|
||||
"NSString",
|
||||
"NSProcessInfo",
|
||||
"NSThread",
|
||||
"NSSet",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies.objc2-ui-kit]
|
||||
version = "0.2.2"
|
||||
features = [
|
||||
"UIApplication",
|
||||
"UIDevice",
|
||||
"UIEvent",
|
||||
"UIGeometry",
|
||||
"UIGestureRecognizer",
|
||||
"UITextInput",
|
||||
"UITextInputTraits",
|
||||
"UIOrientation",
|
||||
"UIPanGestureRecognizer",
|
||||
"UIPinchGestureRecognizer",
|
||||
"UIResponder",
|
||||
"UIRotationGestureRecognizer",
|
||||
"UIScreen",
|
||||
"UIScreenMode",
|
||||
"UITapGestureRecognizer",
|
||||
"UITouch",
|
||||
"UITraitCollection",
|
||||
"UIView",
|
||||
"UIViewController",
|
||||
"UIWindow",
|
||||
]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
unicode-segmentation = "1.7.1"
|
||||
|
||||
@@ -190,6 +234,7 @@ features = [
|
||||
"Win32_System_Com",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Ole",
|
||||
"Win32_Security",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading",
|
||||
@@ -209,7 +254,7 @@ features = [
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies]
|
||||
ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
|
||||
bytemuck = { version = "1.13.1", default-features = false, optional = true }
|
||||
calloop = "0.12.3"
|
||||
calloop = "0.13.0"
|
||||
libc = "0.2.64"
|
||||
memmap2 = { version = "0.9.0", optional = true }
|
||||
percent-encoding = { version = "2.0", optional = true }
|
||||
@@ -219,18 +264,18 @@ rustix = { version = "0.38.4", default-features = false, features = [
|
||||
"thread",
|
||||
"process",
|
||||
] }
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.18.0", default-features = false, features = [
|
||||
sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [
|
||||
"calloop",
|
||||
], optional = true }
|
||||
sctk-adwaita = { version = "0.9.0", default_features = false, optional = true }
|
||||
wayland-backend = { version = "0.3.0", default_features = false, features = [
|
||||
sctk-adwaita = { version = "0.10.1", default-features = false, optional = true }
|
||||
wayland-backend = { version = "0.3.10", default-features = false, features = [
|
||||
"client_system",
|
||||
], optional = true }
|
||||
wayland-client = { version = "0.31.1", optional = true }
|
||||
wayland-protocols = { version = "0.31.0", features = [
|
||||
wayland-client = { version = "0.31.10", optional = true }
|
||||
wayland-protocols = { version = "0.32.8", features = [
|
||||
"staging",
|
||||
], optional = true }
|
||||
wayland-protocols-plasma = { version = "0.2.0", features = [
|
||||
wayland-protocols-plasma = { version = "0.3.8", features = [
|
||||
"client",
|
||||
], optional = true }
|
||||
x11-dl = { version = "2.19.1", optional = true }
|
||||
@@ -248,57 +293,63 @@ xkbcommon-dl = "0.4.2"
|
||||
orbclient = { version = "0.3.47", default-features = false }
|
||||
redox_syscall = "0.4.1"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies.web_sys]
|
||||
package = "web-sys"
|
||||
version = "0.3.64"
|
||||
features = [
|
||||
'AbortController',
|
||||
'AbortSignal',
|
||||
'Blob',
|
||||
'console',
|
||||
'CssStyleDeclaration',
|
||||
'Document',
|
||||
'DomException',
|
||||
'DomRect',
|
||||
'DomRectReadOnly',
|
||||
'Element',
|
||||
'Event',
|
||||
'EventTarget',
|
||||
'FocusEvent',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'HtmlImageElement',
|
||||
'ImageBitmap',
|
||||
'ImageBitmapOptions',
|
||||
'ImageBitmapRenderingContext',
|
||||
'ImageData',
|
||||
'IntersectionObserver',
|
||||
'IntersectionObserverEntry',
|
||||
'KeyboardEvent',
|
||||
'MediaQueryList',
|
||||
'MessageChannel',
|
||||
'MessagePort',
|
||||
'Node',
|
||||
'PageTransitionEvent',
|
||||
'PointerEvent',
|
||||
'PremultiplyAlpha',
|
||||
'ResizeObserver',
|
||||
'ResizeObserverBoxOptions',
|
||||
'ResizeObserverEntry',
|
||||
'ResizeObserverOptions',
|
||||
'ResizeObserverSize',
|
||||
'VisibilityState',
|
||||
'Window',
|
||||
'WheelEvent',
|
||||
'Url',
|
||||
]
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
js-sys = "0.3.64"
|
||||
js-sys = "0.3.70"
|
||||
pin-project = "1"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
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",
|
||||
] }
|
||||
|
||||
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
|
||||
atomic-waker = "1"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
winit = "0.30.0"
|
||||
winit = "0.30.12"
|
||||
```
|
||||
|
||||
## [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 [](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [](https://web.libera.chat/#winit).
|
||||
Join us in our [](https://matrix.to/#/#rust-windowing:matrix.org) room.
|
||||
|
||||
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
|
||||
|
||||
@@ -33,6 +33,10 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen
|
||||
show something on the window you need to use the platform-specific getters provided by winit, or
|
||||
another library.
|
||||
|
||||
## CONTRIBUTING
|
||||
|
||||
For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
## MSRV Policy
|
||||
|
||||
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
|
||||
|
||||
7
build.rs
7
build.rs
@@ -1,10 +1,10 @@
|
||||
use cfg_aliases::cfg_aliases;
|
||||
|
||||
fn main() {
|
||||
// The script doesn't depend on our code
|
||||
// The script doesn't depend on our code.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// Setup cfg aliases
|
||||
// Setup cfg aliases.
|
||||
cfg_aliases! {
|
||||
// Systems.
|
||||
android_platform: { target_os = "android" },
|
||||
@@ -21,4 +21,7 @@ fn main() {
|
||||
wayland_platform: { all(feature = "wayland", free_unix, not(redox)) },
|
||||
orbital_platform: { redox },
|
||||
}
|
||||
|
||||
// Winit defined cfgs.
|
||||
println!("cargo:rustc-check-cfg=cfg(unreleased_changelogs)");
|
||||
}
|
||||
|
||||
91
deny.toml
91
deny.toml
@@ -1,15 +1,20 @@
|
||||
# https://embarkstudios.github.io/cargo-deny/
|
||||
# https://embarkstudios.github.io/cargo-deny
|
||||
# cargo install cargo-deny
|
||||
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
|
||||
# cargo update && cargo deny --target aarch64-apple-ios check
|
||||
# Note: running just `cargo deny check` without a `--target` will result in
|
||||
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
|
||||
[graph]
|
||||
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" },
|
||||
{ triple = "wasm32-unknown-unknown", features = [
|
||||
"atomics",
|
||||
] },
|
||||
{ triple = "x86_64-apple-darwin" },
|
||||
{ triple = "x86_64-apple-ios" },
|
||||
{ triple = "x86_64-pc-windows-gnu" },
|
||||
@@ -18,46 +23,58 @@ targets = [
|
||||
{ triple = "x86_64-unknown-redox" },
|
||||
]
|
||||
|
||||
|
||||
[advisories]
|
||||
vulnerability = "deny"
|
||||
unmaintained = "warn"
|
||||
yanked = "deny"
|
||||
ignore = []
|
||||
|
||||
[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 }
|
||||
|
||||
[bans]
|
||||
multiple-versions = "deny"
|
||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||
deny = []
|
||||
skip = [
|
||||
{ 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
|
||||
{ crate = "raw-window-handle", reason = "we depend on multiple behind features" },
|
||||
{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" },
|
||||
{ crate = "rustix@0.38", reason = "the ecosystem is in the process of migrating" },
|
||||
{ crate = "linux-raw-sys@0.4", reason = "the ecosystem is in the process of migrating" },
|
||||
]
|
||||
skip-tree = []
|
||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||
|
||||
[bans.build]
|
||||
include-archives = true
|
||||
interpreted = "deny"
|
||||
|
||||
[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"
|
||||
[[bans.build.bypass]]
|
||||
allow = [
|
||||
"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)
|
||||
{ path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" },
|
||||
]
|
||||
crate = "android-activity"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["ci/*", "githooks/*"]
|
||||
crate = "zerocopy"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["freetype2/*"]
|
||||
crate = "freetype-sys"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["lib/*.a"]
|
||||
crate = "windows_i686_gnu"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["lib/*.lib"]
|
||||
crate = "windows_i686_msvc"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["lib/*.a"]
|
||||
crate = "windows_x86_64_gnu"
|
||||
|
||||
[[bans.build.bypass]]
|
||||
allow-globs = ["lib/*.lib"]
|
||||
crate = "windows_x86_64_msvc"
|
||||
|
||||
@@ -31,6 +31,8 @@ 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;
|
||||
@@ -140,6 +142,28 @@ 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);
|
||||
@@ -212,6 +236,12 @@ impl Application {
|
||||
Action::PrintHelp => self.print_help(),
|
||||
#[cfg(macos_platform)]
|
||||
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
|
||||
Action::SetTheme(theme) => {
|
||||
window.window.set_theme(theme);
|
||||
// Get the resulting current theme to draw with
|
||||
let actual_theme = theme.or_else(|| window.window.theme()).unwrap_or(Theme::Dark);
|
||||
window.set_draw_theme(actual_theme);
|
||||
},
|
||||
#[cfg(macos_platform)]
|
||||
Action::CreateNewTab => {
|
||||
let tab_id = window.window.tabbing_identifier();
|
||||
@@ -334,7 +364,7 @@ impl ApplicationHandler<UserEvent> for Application {
|
||||
},
|
||||
WindowEvent::ThemeChanged(theme) => {
|
||||
info!("Theme changed to {theme:?}");
|
||||
window.set_theme(theme);
|
||||
window.set_draw_theme(theme);
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Err(err) = window.draw() {
|
||||
@@ -733,8 +763,8 @@ impl WindowState {
|
||||
self.window.request_redraw();
|
||||
}
|
||||
|
||||
/// Change the theme.
|
||||
fn set_theme(&mut self, theme: Theme) {
|
||||
/// Change the theme that things are drawn in.
|
||||
fn set_draw_theme(&mut self, theme: Theme) {
|
||||
self.theme = theme;
|
||||
self.window.request_redraw();
|
||||
}
|
||||
@@ -884,6 +914,7 @@ enum Action {
|
||||
ShowWindowMenu,
|
||||
#[cfg(macos_platform)]
|
||||
CycleOptionAsAlt,
|
||||
SetTheme(Option<Theme>),
|
||||
#[cfg(macos_platform)]
|
||||
CreateNewTab,
|
||||
RequestResize,
|
||||
@@ -915,6 +946,9 @@ impl Action {
|
||||
Action::ShowWindowMenu => "Show window menu",
|
||||
#[cfg(macos_platform)]
|
||||
Action::CycleOptionAsAlt => "Cycle option as alt mode",
|
||||
Action::SetTheme(None) => "Change to the system theme",
|
||||
Action::SetTheme(Some(Theme::Light)) => "Change to a light theme",
|
||||
Action::SetTheme(Some(Theme::Dark)) => "Change to a dark theme",
|
||||
#[cfg(macos_platform)]
|
||||
Action::CreateNewTab => "Create new tab",
|
||||
Action::RequestResize => "Request a resize",
|
||||
@@ -1059,6 +1093,10 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
|
||||
Action::AnimationCustomCursor,
|
||||
),
|
||||
Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
|
||||
// K.
|
||||
Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)),
|
||||
Binding::new("K", ModifiersState::SUPER, Action::SetTheme(Some(Theme::Light))),
|
||||
Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))),
|
||||
#[cfg(macos_platform)]
|
||||
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
|
||||
#[cfg(macos_platform)]
|
||||
|
||||
@@ -190,7 +190,7 @@ pub trait ApplicationHandler<T: 'static = ()> {
|
||||
/// Emitted when the event loop is being shut down.
|
||||
///
|
||||
/// This is irreversible - if this method is called, it is guaranteed that the event loop
|
||||
/// will exist right after.
|
||||
/// will exit right after.
|
||||
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
|
||||
let _ = event_loop;
|
||||
}
|
||||
@@ -223,3 +223,117 @@ pub trait ApplicationHandler<T: 'static = ()> {
|
||||
let _ = event_loop;
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for &mut A {
|
||||
#[inline]
|
||||
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
|
||||
(**self).new_events(event_loop, cause);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
(**self).resumed(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
|
||||
(**self).user_event(event_loop, event);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
(**self).window_event(event_loop, window_id, event);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn device_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
device_id: DeviceId,
|
||||
event: DeviceEvent,
|
||||
) {
|
||||
(**self).device_event(event_loop, device_id, event);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||||
(**self).about_to_wait(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
|
||||
(**self).suspended(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
|
||||
(**self).exiting(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
|
||||
(**self).memory_warning(event_loop);
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: ?Sized + ApplicationHandler<T>, T: 'static> ApplicationHandler<T> for Box<A> {
|
||||
#[inline]
|
||||
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
|
||||
(**self).new_events(event_loop, cause);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||
(**self).resumed(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: T) {
|
||||
(**self).user_event(event_loop, event);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
window_id: WindowId,
|
||||
event: WindowEvent,
|
||||
) {
|
||||
(**self).window_event(event_loop, window_id, event);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn device_event(
|
||||
&mut self,
|
||||
event_loop: &ActiveEventLoop,
|
||||
device_id: DeviceId,
|
||||
event: DeviceEvent,
|
||||
) {
|
||||
(**self).device_event(event_loop, device_id, event);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||||
(**self).about_to_wait(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
|
||||
(**self).suspended(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
|
||||
(**self).exiting(event_loop);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn memory_warning(&mut self, event_loop: &ActiveEventLoop) {
|
||||
(**self).memory_warning(event_loop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current
|
||||
changelog entry.
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Fixed
|
||||
|
||||
- On macOS, fix crash on macOS 26 by using objc2's `relax-sign-encoding` feature.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
- On Web, fix some `WindowBuilder` methods doing nothing.
|
||||
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
|
||||
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
|
||||
- On Web, fix touch input not gaining or loosing focus.
|
||||
- On Web, fix touch input not gaining or losing focus.
|
||||
- On Web, fix touch location to be as accurate as mouse position.
|
||||
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
|
||||
- On Web, implement `Window::focus_window()`.
|
||||
|
||||
@@ -1,3 +1,177 @@
|
||||
## 0.30.12
|
||||
|
||||
### Fixed
|
||||
|
||||
- On macOS, fix crash on macOS 26 by using objc2's `relax-sign-encoding` feature.
|
||||
|
||||
## 0.30.11
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Windows, fixed crash in should_apps_use_dark_mode() for Windows versions < 17763.
|
||||
- On Wayland, fixed `pump_events` driven loop deadlocking when loop was not drained before exit.
|
||||
|
||||
## 0.30.10
|
||||
|
||||
### Added
|
||||
|
||||
- On Windows, add `IconExtWindows::from_resource_name`.
|
||||
- On Windows, add `CursorGrabMode::Locked`.
|
||||
- On Wayland, add `WindowExtWayland::xdg_toplevel`.
|
||||
|
||||
### Changed
|
||||
|
||||
- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself).
|
||||
- On iOS, remove custom application delegates. You are now allowed to override the
|
||||
application delegate yourself.
|
||||
- On iOS, no longer act as-if the application successfully open all URLs. Override
|
||||
`application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Windows, fixed ~500 ms pause when clicking the title bar during continuous redraw.
|
||||
- On macOS, `WindowExtMacOS::set_simple_fullscreen` now honors `WindowExtMacOS::set_borderless_game`
|
||||
- On X11 and Wayland, fixed pump_events with `Some(Duration::Zero)` blocking with `Wait` polling mode
|
||||
- On Wayland, fixed a crash when consequently calling `set_cursor_grab` without pointer focus.
|
||||
- On Wayland, ensure that external event loop is woken-up when using pump_events and integrating via `FD`.
|
||||
- On Wayland, apply fractional scaling to custom cursors.
|
||||
- On macOS, fixed `run_app_on_demand` returning without closing open windows.
|
||||
- On macOS, fixed `VideoMode::refresh_rate_millihertz` for fractional refresh rates.
|
||||
- On macOS, store monitor handle to avoid panics after going in/out of sleep.
|
||||
- On macOS, allow certain invalid monitor handles and return `None` instead of panicking.
|
||||
- On Windows, fixed `Ime::Preedit` cursor offset calculation.
|
||||
|
||||
## 0.30.9
|
||||
|
||||
### Changed
|
||||
|
||||
- On Wayland, no longer send an explicit clearing `Ime::Preedit` just prior to a new `Ime::Preedit`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On X11, fix crash with uim.
|
||||
- On X11, fix modifiers for keys that were sent by the same X11 request.
|
||||
- On iOS, fix high CPU usage even when using `ControlFlow::Wait`.
|
||||
|
||||
## 0.30.8
|
||||
|
||||
### Added
|
||||
|
||||
- `ActivationToken::from_raw` and `ActivationToken::into_raw`.
|
||||
- On X11, add a workaround for disabling IME on GNOME.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Windows, fixed the event loop not waking on accessibility requests.
|
||||
- On X11, fixed cursor grab mode state tracking on error.
|
||||
|
||||
## 0.30.7
|
||||
|
||||
### Fixed
|
||||
|
||||
- On X11, fixed KeyboardInput delivered twice when IME enabled.
|
||||
|
||||
## 0.30.6
|
||||
|
||||
### Added
|
||||
|
||||
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
|
||||
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
|
||||
- On X11, the `window` example now understands the `X11_VISUAL_ID` and `X11_SCREEN_ID` env
|
||||
variables to test the respective modifiers of window creation.
|
||||
- On Android, the soft keyboard can now be shown using `Window::set_ime_allowed`.
|
||||
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On macOS, fix `WindowEvent::Moved` sometimes being triggered unnecessarily on resize.
|
||||
- On macOS, package manifest definitions of `LSUIElement` will no longer be overridden with the
|
||||
default activation policy, unless explicitly provided during initialization.
|
||||
- On macOS, fix crash when calling `drag_window()` without a left click present.
|
||||
- On X11, key events forward to IME anyway, even when it's disabled.
|
||||
- On Windows, make `ControlFlow::WaitUntil` work more precisely using `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`.
|
||||
- On X11, creating windows on screen that is not the first one (e.g. `DISPLAY=:0.1`) works again.
|
||||
- On X11, creating windows while passing `with_x11_screen(non_default_screen)` works again.
|
||||
- On X11, fix XInput handling that prevented a new window from getting the focus in some cases.
|
||||
- On macOS, fix crash when pressing Caps Lock in certain configurations.
|
||||
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.
|
||||
- On macOS, fixed undocumented cursors (e.g. zoom, resize, help) always appearing to be invalid and falling back to the default cursor.
|
||||
|
||||
## 0.30.5
|
||||
|
||||
### Added
|
||||
|
||||
- Add `ActiveEventLoop::system_theme()`, returning the current system theme.
|
||||
- On Web, implement `Error` for `platform::web::CustomCursorError`.
|
||||
- On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On MacOS, fix building with `feature = "rwh_04"`.
|
||||
- On Web, pen events are now routed through to `WindowEvent::Cursor*`.
|
||||
- On macOS, fix panic when releasing not available monitor.
|
||||
- On MacOS, return the system theme in `Window::theme()` if no theme override is set.
|
||||
|
||||
## 0.30.4
|
||||
|
||||
### Changed
|
||||
|
||||
- `DeviceId::dummy()` and `WindowId::dummy()` are no longer marked `unsafe`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Wayland, avoid crashing when compositor is misbehaving.
|
||||
- On Web, fix `WindowEvent::Resized` not using `requestAnimationFrame` when sending
|
||||
`WindowEvent::RedrawRequested` and also potentially causing `WindowEvent::RedrawRequested`
|
||||
to not be de-duplicated.
|
||||
- Account for different browser engine implementations of pointer movement coordinate space.
|
||||
|
||||
## 0.30.3
|
||||
|
||||
### Added
|
||||
|
||||
- On Web, add `EventLoopExtWebSys::(set_)poll_strategy()` to allow setting
|
||||
control flow strategies before starting the event loop.
|
||||
- On Web, add `WaitUntilStrategy`, which allows to set different strategies for
|
||||
`ControlFlow::WaitUntil`. By default the Prioritized Task Scheduling API is
|
||||
used, with a fallback to `setTimeout()` with a trick to circumvent throttling
|
||||
to 4ms. But an option to use a Web worker to schedule the timer is available
|
||||
as well, which commonly prevents any throttling when the window is not focused.
|
||||
|
||||
### Changed
|
||||
|
||||
- On macOS, set the window theme on the `NSWindow` instead of application-wide.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On X11, build on arm platforms.
|
||||
- On macOS, fixed `WindowBuilder::with_theme` not having any effect on the window.
|
||||
|
||||
## 0.30.2
|
||||
|
||||
### Fixed
|
||||
|
||||
- On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately
|
||||
when not called from inside the event loop. Now queues a microtask instead.
|
||||
- On Web, stop overwriting default cursor with `CursorIcon::Default`.
|
||||
- On Web, prevent crash when using `InnerSizeWriter::request_inner_size()`.
|
||||
- On macOS, fix not working opacity for entire window.
|
||||
|
||||
## 0.30.1
|
||||
|
||||
### Added
|
||||
|
||||
- Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`.
|
||||
- Implement `ApplicationHandler` for `&mut` references and heap allocations to something that implements `ApplicationHandler`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- On macOS, fix panic on exit when dropping windows outside the event loop.
|
||||
- On macOS, fix window dragging glitches when dragging across a monitor boundary with different scale factor.
|
||||
- On macOS, fix the range in `Ime::Preedit`.
|
||||
- On macOS, use the system's internal mechanisms for queuing events.
|
||||
- On macOS, handle events directly instead of queuing when possible.
|
||||
|
||||
## 0.30.0
|
||||
|
||||
### Added
|
||||
|
||||
@@ -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.
|
||||
|
||||
36
src/event.rs
36
src/event.rs
@@ -389,6 +389,8 @@ pub enum WindowEvent {
|
||||
/// Applications might wish to react to this to change the theme of the content of the window
|
||||
/// when the system changes the window theme.
|
||||
///
|
||||
/// This only reports a change if the window theme was not overridden by [`Window::set_theme`].
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **iOS / Android / X11 / Wayland / Orbital:** Unsupported.
|
||||
@@ -447,16 +449,13 @@ pub struct DeviceId(pub(crate) platform_impl::DeviceId);
|
||||
impl DeviceId {
|
||||
/// Returns a dummy id, useful for unit testing.
|
||||
///
|
||||
/// # Safety
|
||||
/// # Notes
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// **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() })
|
||||
pub const fn dummy() -> Self {
|
||||
DeviceId(platform_impl::DeviceId::dummy())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,11 +532,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.**
|
||||
@@ -559,8 +558,7 @@ 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
|
||||
@@ -568,8 +566,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,
|
||||
@@ -851,8 +849,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,
|
||||
@@ -1019,7 +1017,7 @@ mod tests {
|
||||
($closure:expr) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut x = $closure;
|
||||
let did = unsafe { event::DeviceId::dummy() };
|
||||
let did = event::DeviceId::dummy();
|
||||
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
@@ -1029,7 +1027,7 @@ mod tests {
|
||||
use crate::window::WindowId;
|
||||
|
||||
// Mainline events.
|
||||
let wid = unsafe { WindowId::dummy() };
|
||||
let wid = WindowId::dummy();
|
||||
x(UserEvent(()));
|
||||
x(NewEvents(event::StartCause::Init));
|
||||
x(AboutToWait);
|
||||
@@ -1154,11 +1152,11 @@ mod tests {
|
||||
#[test]
|
||||
fn ensure_attrs_do_not_panic() {
|
||||
foreach_event!(|event: event::Event<()>| {
|
||||
let _ = format!("{:?}", event);
|
||||
let _ = format!("{event:?}");
|
||||
});
|
||||
let _ = event::StartCause::Init.clone();
|
||||
|
||||
let did = unsafe { crate::event::DeviceId::dummy() }.clone();
|
||||
let did = crate::event::DeviceId::dummy().clone();
|
||||
HashSet::new().insert(did);
|
||||
let mut set = [did, did, did];
|
||||
set.sort_unstable();
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::error::{EventLoopError, OsError};
|
||||
use crate::event::Event;
|
||||
use crate::monitor::MonitorHandle;
|
||||
use crate::platform_impl;
|
||||
use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes};
|
||||
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes};
|
||||
|
||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
||||
/// the events loop.
|
||||
@@ -103,11 +103,14 @@ impl<T> EventLoopBuilder<T> {
|
||||
///
|
||||
/// [`platform`]: crate::platform
|
||||
#[cfg_attr(
|
||||
android,
|
||||
android_platform,
|
||||
doc = "[`.with_android_app(app)`]: \
|
||||
crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
|
||||
)]
|
||||
#[cfg_attr(not(android), doc = "[`.with_android_app(app)`]: #only-available-on-android")]
|
||||
#[cfg_attr(
|
||||
not(android_platform),
|
||||
doc = "[`.with_android_app(app)`]: #only-available-on-android"
|
||||
)]
|
||||
#[inline]
|
||||
pub fn build(&mut self) -> Result<EventLoop<T>, EventLoopError> {
|
||||
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
|
||||
@@ -434,6 +437,17 @@ 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)
|
||||
@@ -487,7 +501,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 clonable handle to the underlying
|
||||
/// The purpose of this type is to provide a cheaply cloneable 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
|
||||
|
||||
@@ -1232,7 +1232,7 @@ pub enum NamedKey {
|
||||
Dimmer,
|
||||
/// Swap video sources. (`VK_DISPLAY_SWAP`)
|
||||
DisplaySwap,
|
||||
/// Select Digital Video Rrecorder. (`KEYCODE_DVR`)
|
||||
/// Select Digital Video Recorder. (`KEYCODE_DVR`)
|
||||
DVR,
|
||||
/// Exit the current application. (`VK_EXIT`)
|
||||
Exit,
|
||||
|
||||
38
src/lib.rs
38
src/lib.rs
@@ -7,7 +7,12 @@
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use winit::event_loop::EventLoop;
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//!
|
||||
//! # // Intentionally use `fn main` for clarity
|
||||
//! fn main() {
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! // ...
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Then you create a [`Window`] with [`create_window`].
|
||||
@@ -84,19 +89,22 @@
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! let event_loop = EventLoop::new().unwrap();
|
||||
//! # // Intentionally use `fn main` for clarity
|
||||
//! fn main() {
|
||||
//! 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
|
||||
@@ -157,7 +165,6 @@
|
||||
//! [`Window`]: window::Window
|
||||
//! [`WindowId`]: window::WindowId
|
||||
//! [`WindowAttributes`]: window::WindowAttributes
|
||||
//! [window_new]: window::Window::new
|
||||
//! [`create_window`]: event_loop::ActiveEventLoop::create_window
|
||||
//! [`Window::id()`]: window::Window::id
|
||||
//! [`WindowEvent`]: event::WindowEvent
|
||||
@@ -177,7 +184,14 @@
|
||||
// doc
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#![warn(clippy::uninlined_format_args)]
|
||||
// TODO: wasm-binding needs to be updated for that to be resolved, for now just silence it.
|
||||
#![cfg_attr(web_platform, allow(unknown_lints, renamed_and_removed_lints, wasm_c_abi))]
|
||||
|
||||
#[cfg(feature = "rwh_04")]
|
||||
pub use rwh_04 as raw_window_handle_04;
|
||||
#[cfg(feature = "rwh_05")]
|
||||
pub use rwh_05 as raw_window_handle_05;
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub use rwh_06 as raw_window_handle;
|
||||
|
||||
|
||||
@@ -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.0",
|
||||
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.12",
|
||||
//! 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,12 +76,22 @@ use crate::window::{Window, WindowAttributes};
|
||||
use self::activity::{AndroidApp, ConfigurationRef, Rect};
|
||||
|
||||
/// Additional methods on [`EventLoop`] that are specific to Android.
|
||||
pub trait EventLoopExtAndroid {}
|
||||
pub trait EventLoopExtAndroid {
|
||||
/// Get the [`AndroidApp`] which was used to create this event loop.
|
||||
fn android_app(&self) -> &AndroidApp;
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtAndroid for EventLoop<T> {}
|
||||
impl<T> EventLoopExtAndroid for EventLoop<T> {
|
||||
fn android_app(&self) -> &AndroidApp {
|
||||
&self.event_loop.android_app
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
|
||||
pub trait ActiveEventLoopExtAndroid {}
|
||||
pub trait ActiveEventLoopExtAndroid {
|
||||
/// Get the [`AndroidApp`] which was used to create this event loop.
|
||||
fn android_app(&self) -> &AndroidApp;
|
||||
}
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to Android.
|
||||
pub trait WindowExtAndroid {
|
||||
@@ -100,7 +110,11 @@ impl WindowExtAndroid for Window {
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveEventLoopExtAndroid for ActiveEventLoop {}
|
||||
impl ActiveEventLoopExtAndroid for ActiveEventLoop {
|
||||
fn android_app(&self) -> &AndroidApp {
|
||||
&self.p.app
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`WindowAttributes`] that are specific to Android.
|
||||
pub trait WindowAttributesExtAndroid {}
|
||||
@@ -108,9 +122,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
|
||||
@@ -147,7 +161,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")]
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
|
||||
//! iOS 9.3.
|
||||
//!
|
||||
//! ## Window initialization
|
||||
//!
|
||||
//! iOS's main `UIApplicationMain` does some init work that's required by all
|
||||
//! UI-related code (see issue [#1705]). It is best to create your windows
|
||||
//! inside `Event::Resumed`.
|
||||
//! inside [`ApplicationHandler::resumed`].
|
||||
//!
|
||||
//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
|
||||
//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
|
||||
//!
|
||||
//! ## Building app
|
||||
//!
|
||||
@@ -63,6 +66,16 @@
|
||||
//! opengl will result in segfault.
|
||||
//!
|
||||
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
|
||||
//!
|
||||
//! ## Custom `UIApplicationDelegate`
|
||||
//!
|
||||
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
|
||||
//! though, you might want to access some of the more niche stuff that [the application
|
||||
//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it
|
||||
//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not
|
||||
//! register an application delegate, so you can set up a custom one in a nib file instead.
|
||||
//!
|
||||
//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
@@ -357,7 +370,7 @@ impl MonitorHandleExtIOS for MonitorHandle {
|
||||
fn ui_screen(&self) -> *mut c_void {
|
||||
// SAFETY: The marker is only used to get the pointer of the screen
|
||||
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
|
||||
objc2::rc::Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
|
||||
objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -3,16 +3,84 @@
|
||||
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
|
||||
//! itself), and is regularly tested on macOS 10.14.
|
||||
//!
|
||||
//! ## Window initialization
|
||||
//!
|
||||
//! A lot of functionality expects the application to be ready before you
|
||||
//! start doing anything; this includes creating windows, fetching monitors,
|
||||
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
|
||||
//!
|
||||
//! If you encounter problems, you should try doing your initialization inside
|
||||
//! `Event::Resumed`.
|
||||
//! [`ApplicationHandler::resumed`].
|
||||
//!
|
||||
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
|
||||
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
|
||||
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
|
||||
//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
|
||||
//!
|
||||
//! ## Custom `NSApplicationDelegate`
|
||||
//!
|
||||
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
|
||||
//! though, you might want to do more niche stuff, such as [handle when the user re-activates the
|
||||
//! application][reopen]. Such functionality is not exposed directly in Winit, since it would
|
||||
//! increase the API surface by quite a lot.
|
||||
//!
|
||||
//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc
|
||||
//!
|
||||
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is
|
||||
//! to register your own application delegate, as outlined in the following example (see
|
||||
//! `objc2-app-kit` for more detailed information).
|
||||
#![cfg_attr(target_os = "macos", doc = "```")]
|
||||
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
|
||||
//! use objc2::rc::Retained;
|
||||
//! use objc2::runtime::ProtocolObject;
|
||||
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
|
||||
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
|
||||
//! use winit::event_loop::EventLoop;
|
||||
//!
|
||||
//! declare_class!(
|
||||
//! struct AppDelegate;
|
||||
//!
|
||||
//! unsafe impl ClassType for AppDelegate {
|
||||
//! type Super = NSObject;
|
||||
//! type Mutability = mutability::MainThreadOnly;
|
||||
//! const NAME: &'static str = "MyAppDelegate";
|
||||
//! }
|
||||
//!
|
||||
//! impl DeclaredClass for AppDelegate {}
|
||||
//!
|
||||
//! unsafe impl NSObjectProtocol for AppDelegate {}
|
||||
//!
|
||||
//! unsafe impl NSApplicationDelegate for AppDelegate {
|
||||
//! #[method(application:openURLs:)]
|
||||
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
|
||||
//! // Note: To specifically get `application:openURLs:` to work, you _might_
|
||||
//! // have to bundle your application. This is not done in this example.
|
||||
//! println!("open urls: {application:?}, {urls:?}");
|
||||
//! }
|
||||
//! }
|
||||
//! );
|
||||
//!
|
||||
//! impl AppDelegate {
|
||||
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
|
||||
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! let event_loop = EventLoop::new()?;
|
||||
//!
|
||||
//! let mtm = MainThreadMarker::new().unwrap();
|
||||
//! let delegate = AppDelegate::new(mtm);
|
||||
//! // Important: Call `sharedApplication` after `EventLoop::new`,
|
||||
//! // doing it before is not yet supported.
|
||||
//! let app = NSApplication::sharedApplication(mtm);
|
||||
//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||
//!
|
||||
//! // event_loop.run_app(&mut my_app);
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
@@ -94,6 +162,14 @@ pub trait WindowExtMacOS {
|
||||
|
||||
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
|
||||
fn option_as_alt(&self) -> OptionAsAlt;
|
||||
|
||||
/// Disable the Menu Bar and Dock in Simple or Borderless Fullscreen mode. Useful for games.
|
||||
/// The effect is applied when [`WindowExtMacOS::set_simple_fullscreen`] or
|
||||
/// [`Window::set_fullscreen`] is called.
|
||||
fn set_borderless_game(&self, borderless_game: bool);
|
||||
|
||||
/// Getter for the [`WindowExtMacOS::set_borderless_game`].
|
||||
fn is_borderless_game(&self) -> bool;
|
||||
}
|
||||
|
||||
impl WindowExtMacOS for Window {
|
||||
@@ -166,6 +242,16 @@ 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`.
|
||||
@@ -216,6 +302,8 @@ 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 {
|
||||
@@ -284,12 +372,21 @@ 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.
|
||||
/// 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.
|
||||
///
|
||||
/// It is set to [`ActivationPolicy::Regular`] by default.
|
||||
/// If unused, the Winit will honor the package manifest.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -341,7 +438,7 @@ pub trait EventLoopBuilderExtMacOS {
|
||||
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
|
||||
#[inline]
|
||||
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
|
||||
self.platform_specific.activation_policy = activation_policy;
|
||||
self.platform_specific.activation_policy = Some(activation_policy);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -375,7 +472,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
|
||||
fn ns_screen(&self) -> Option<*mut c_void> {
|
||||
// SAFETY: We only use the marker to get a pointer
|
||||
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
|
||||
self.inner.ns_screen(mtm).map(|s| objc2::rc::Id::as_ptr(&s) as _)
|
||||
self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,19 +51,19 @@ pub trait EventLoopExtPumpEvents {
|
||||
/// buffered and handled outside of Winit include:
|
||||
/// - `RedrawRequested` events, used to schedule rendering.
|
||||
///
|
||||
/// macOS for example uses a `drawRect` callback to drive rendering
|
||||
/// within applications and expects rendering to be finished before
|
||||
/// the `drawRect` callback returns.
|
||||
/// macOS for example uses a `drawRect` callback to drive rendering
|
||||
/// within applications and expects rendering to be finished before
|
||||
/// the `drawRect` callback returns.
|
||||
///
|
||||
/// For portability it's strongly recommended that applications should
|
||||
/// keep their rendering inside the closure provided to Winit.
|
||||
/// For portability it's strongly recommended that applications should
|
||||
/// keep their rendering inside the closure provided to Winit.
|
||||
/// - Any lifecycle events, such as `Suspended` / `Resumed`.
|
||||
///
|
||||
/// The handling of these events needs to be synchronized with the
|
||||
/// operating system and it would never be appropriate to buffer a
|
||||
/// notification that your application has been suspended or resumed and
|
||||
/// then handled that later since there would always be a chance that
|
||||
/// other lifecycle events occur while the event is buffered.
|
||||
/// The handling of these events needs to be synchronized with the
|
||||
/// operating system and it would never be appropriate to buffer a
|
||||
/// notification that your application has been suspended or resumed and
|
||||
/// then handled that later since there would always be a chance that
|
||||
/// other lifecycle events occur while the event is buffered.
|
||||
///
|
||||
/// ## Supported Platforms
|
||||
///
|
||||
@@ -74,13 +74,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
|
||||
///
|
||||
|
||||
@@ -42,7 +42,9 @@ 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()`[^1] more than once instead).
|
||||
/// backend it is possible to use `EventLoopExtWebSys::spawn()`
|
||||
#[cfg_attr(not(web_platform), doc = "[^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
|
||||
@@ -61,8 +63,8 @@ pub trait EventLoopExtRunOnDemand {
|
||||
/// are delivered via callbacks based on an event loop that is internal to the browser itself.
|
||||
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
|
||||
#[cfg_attr(not(web_platform), doc = "[^1]: `spawn()` is only available on `wasm` platforms.")]
|
||||
#[rustfmt::skip]
|
||||
///
|
||||
#[rustfmt::skip]
|
||||
/// [`exit()`]: ActiveEventLoop::exit()
|
||||
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
|
||||
fn run_app_on_demand<A: ApplicationHandler<Self::UserEvent>>(
|
||||
|
||||
@@ -64,7 +64,7 @@ impl EventLoopExtStartupNotify for ActiveEventLoop {
|
||||
crate::platform_impl::ActiveEventLoop::X(_) => env::var(X11_VAR),
|
||||
}
|
||||
.ok()
|
||||
.map(ActivationToken::_new)
|
||||
.map(ActivationToken::from_raw)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,11 @@
|
||||
//! * `wayland-csd-adwaita` (default).
|
||||
//! * `wayland-csd-adwaita-crossfont`.
|
||||
//! * `wayland-csd-adwaita-notitle`.
|
||||
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
|
||||
use crate::monitor::MonitorHandle;
|
||||
use crate::window::{Window, WindowAttributes};
|
||||
|
||||
@@ -32,6 +36,19 @@ impl ActiveEventLoopExtWayland for ActiveEventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`EventLoop`] that are specific to Wayland.
|
||||
pub trait EventLoopExtWayland {
|
||||
/// True if the [`EventLoop`] uses Wayland.
|
||||
fn is_wayland(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopExtWayland for EventLoop<T> {
|
||||
#[inline]
|
||||
fn is_wayland(&self) -> bool {
|
||||
self.event_loop.is_wayland()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`EventLoopBuilder`] that are specific to Wayland.
|
||||
pub trait EventLoopBuilderExtWayland {
|
||||
/// Force using Wayland.
|
||||
@@ -59,9 +76,25 @@ impl<T> EventLoopBuilderExtWayland for EventLoopBuilder<T> {
|
||||
}
|
||||
|
||||
/// Additional methods on [`Window`] that are specific to Wayland.
|
||||
pub trait WindowExtWayland {}
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
pub trait WindowExtWayland {
|
||||
/// Returns `xdg_toplevel` of the window or [`None`] if the window is X11 window.
|
||||
fn xdg_toplevel(&self) -> Option<NonNull<c_void>>;
|
||||
}
|
||||
|
||||
impl WindowExtWayland for Window {}
|
||||
impl WindowExtWayland for Window {
|
||||
#[inline]
|
||||
fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
|
||||
#[allow(clippy::single_match)]
|
||||
match &self.window {
|
||||
#[cfg(x11_platform)]
|
||||
crate::platform_impl::Window::X(_) => None,
|
||||
#[cfg(wayland_platform)]
|
||||
crate::platform_impl::Window::Wayland(window) => window.xdg_toplevel(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`WindowAttributes`] that are specific to Wayland.
|
||||
pub trait WindowAttributesExtWayland {
|
||||
|
||||
@@ -190,6 +190,34 @@ pub trait EventLoopExtWebSys {
|
||||
fn spawn<F>(self, event_handler: F)
|
||||
where
|
||||
F: 'static + FnMut(Event<Self::UserEvent>, &ActiveEventLoop);
|
||||
|
||||
/// Sets the strategy for [`ControlFlow::Poll`].
|
||||
///
|
||||
/// See [`PollStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
|
||||
fn set_poll_strategy(&self, strategy: PollStrategy);
|
||||
|
||||
/// Gets the strategy for [`ControlFlow::Poll`].
|
||||
///
|
||||
/// See [`PollStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
|
||||
fn poll_strategy(&self) -> PollStrategy;
|
||||
|
||||
/// Sets the strategy for [`ControlFlow::WaitUntil`].
|
||||
///
|
||||
/// See [`WaitUntilStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy);
|
||||
|
||||
/// Gets the strategy for [`ControlFlow::WaitUntil`].
|
||||
///
|
||||
/// See [`WaitUntilStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
fn wait_until_strategy(&self) -> WaitUntilStrategy;
|
||||
}
|
||||
|
||||
impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
@@ -207,6 +235,22 @@ impl<T> EventLoopExtWebSys for EventLoop<T> {
|
||||
{
|
||||
self.event_loop.spawn(event_handler)
|
||||
}
|
||||
|
||||
fn set_poll_strategy(&self, strategy: PollStrategy) {
|
||||
self.event_loop.set_poll_strategy(strategy);
|
||||
}
|
||||
|
||||
fn poll_strategy(&self) -> PollStrategy {
|
||||
self.event_loop.poll_strategy()
|
||||
}
|
||||
|
||||
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.event_loop.set_wait_until_strategy(strategy);
|
||||
}
|
||||
|
||||
fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.event_loop.wait_until_strategy()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ActiveEventLoopExtWebSys {
|
||||
@@ -224,6 +268,20 @@ pub trait ActiveEventLoopExtWebSys {
|
||||
/// [`ControlFlow::Poll`]: crate::event_loop::ControlFlow::Poll
|
||||
fn poll_strategy(&self) -> PollStrategy;
|
||||
|
||||
/// Sets the strategy for [`ControlFlow::WaitUntil`].
|
||||
///
|
||||
/// See [`WaitUntilStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy);
|
||||
|
||||
/// Gets the strategy for [`ControlFlow::WaitUntil`].
|
||||
///
|
||||
/// See [`WaitUntilStrategy`].
|
||||
///
|
||||
/// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
|
||||
fn wait_until_strategy(&self) -> WaitUntilStrategy;
|
||||
|
||||
/// Async version of [`ActiveEventLoop::create_custom_cursor()`] which waits until the
|
||||
/// cursor has completely finished loading.
|
||||
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture;
|
||||
@@ -244,6 +302,16 @@ impl ActiveEventLoopExtWebSys for ActiveEventLoop {
|
||||
fn poll_strategy(&self) -> PollStrategy {
|
||||
self.p.poll_strategy()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
|
||||
self.p.set_wait_until_strategy(strategy);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wait_until_strategy(&self) -> WaitUntilStrategy {
|
||||
self.p.wait_until_strategy()
|
||||
}
|
||||
}
|
||||
|
||||
/// Strategy used for [`ControlFlow::Poll`][crate::event_loop::ControlFlow::Poll].
|
||||
@@ -272,6 +340,29 @@ pub enum PollStrategy {
|
||||
Scheduler,
|
||||
}
|
||||
|
||||
/// Strategy used for [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil].
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum WaitUntilStrategy {
|
||||
/// Uses the [Prioritized Task Scheduling API] to queue the next event loop. If not available
|
||||
/// this will fallback to [`setTimeout()`].
|
||||
///
|
||||
/// This strategy is commonly not affected by browser throttling unless the window is not
|
||||
/// focused.
|
||||
///
|
||||
/// This is the default strategy.
|
||||
///
|
||||
/// [Prioritized Task Scheduling API]: https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API
|
||||
/// [`setTimeout()`]: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
|
||||
#[default]
|
||||
Scheduler,
|
||||
/// Equal to [`Scheduler`][Self::Scheduler] but wakes up the event loop from a [worker].
|
||||
///
|
||||
/// This strategy is commonly not affected by browser throttling regardless of window focus.
|
||||
///
|
||||
/// [worker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
|
||||
Worker,
|
||||
}
|
||||
|
||||
pub trait CustomCursorExtWebSys {
|
||||
/// Returns if this cursor is an animation.
|
||||
fn is_animation(&self) -> bool;
|
||||
@@ -331,7 +422,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 animtion"),
|
||||
Self::Animation => write!(f, "A supplied cursor is an animation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,3 +461,5 @@ impl Display for CustomCursorError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CustomCursorError {}
|
||||
|
||||
@@ -476,10 +476,10 @@ pub trait WindowAttributesExtWindows {
|
||||
/// the menus look. If you use this, it is recommended that you combine it with
|
||||
/// `with_theme(Some(Theme::Light))` to avoid a jarring effect.
|
||||
#[cfg_attr(
|
||||
platform_windows,
|
||||
windows_platform,
|
||||
doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
|
||||
)]
|
||||
#[cfg_attr(not(platform_windows), doc = "[`CreateMenu`]: #only-available-on-windows")]
|
||||
#[cfg_attr(not(windows_platform), doc = "[`CreateMenu`]: #only-available-on-windows")]
|
||||
fn with_menu(self, menu: HMENU) -> Self;
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
@@ -660,6 +660,17 @@ impl DeviceIdExtWindows for DeviceId {
|
||||
}
|
||||
|
||||
/// Additional methods on `Icon` that are specific to Windows.
|
||||
///
|
||||
/// Windows icons can be created from files, or from the [`embedded resources`](https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files).
|
||||
///
|
||||
/// The `ICON` resource definition statement use the following syntax:
|
||||
/// ```rc
|
||||
/// nameID ICON filename
|
||||
/// ```
|
||||
/// `nameID` is a unique name or a 16-bit unsigned integer value identifying the resource,
|
||||
/// `filename` is the name of the file that contains the resource.
|
||||
///
|
||||
/// More information about the `ICON` resource can be found at [`Microsoft Learn`](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) portal.
|
||||
pub trait IconExtWindows: Sized {
|
||||
/// Create an icon from a file path.
|
||||
///
|
||||
@@ -671,7 +682,12 @@ pub trait IconExtWindows: Sized {
|
||||
fn from_path<P: AsRef<Path>>(path: P, size: Option<PhysicalSize<u32>>)
|
||||
-> Result<Self, BadIcon>;
|
||||
|
||||
/// Create an icon from a resource embedded in this executable or library.
|
||||
/// Create an icon from a resource embedded in this executable or library by its ordinal id.
|
||||
///
|
||||
/// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an
|
||||
/// invalid ordinal id, but it can be used with [`from_resource_name`] as `"0"`.
|
||||
///
|
||||
/// [`from_resource_name`]: IconExtWindows::from_resource_name
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
@@ -679,6 +695,55 @@ pub trait IconExtWindows: Sized {
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
|
||||
|
||||
/// Create an icon from a resource embedded in this executable or library by its name.
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// Consider the following resource definition statements:
|
||||
/// ```rc
|
||||
/// app ICON "app.ico"
|
||||
/// 1 ICON "a.ico"
|
||||
/// 0027 ICON "custom.ico"
|
||||
/// 0 ICON "alt.ico"
|
||||
/// ```
|
||||
///
|
||||
/// Due to some internal implementation details of the resource embedding/loading process on
|
||||
/// Windows platform, strings that can be interpreted as 16-bit unsigned integers (`"1"`,
|
||||
/// `"002"`, etc.) cannot be used as valid resource names, and instead should be passed into
|
||||
/// [`from_resource`]:
|
||||
///
|
||||
/// [`from_resource`]: IconExtWindows::from_resource
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use winit::platform::windows::IconExtWindows;
|
||||
/// use winit::window::Icon;
|
||||
///
|
||||
/// assert!(Icon::from_resource_name("app", None).is_ok());
|
||||
/// assert!(Icon::from_resource(1, None).is_ok());
|
||||
/// assert!(Icon::from_resource(27, None).is_ok());
|
||||
/// assert!(Icon::from_resource_name("27", None).is_err());
|
||||
/// assert!(Icon::from_resource_name("0027", None).is_err());
|
||||
/// ```
|
||||
///
|
||||
/// While `0` cannot be used as an ordinal id (see [`from_resource`]), it can be used as a
|
||||
/// name:
|
||||
///
|
||||
/// [`from_resource`]: IconExtWindows::from_resource
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use winit::platform::windows::IconExtWindows;
|
||||
/// # use winit::window::Icon;
|
||||
/// assert!(Icon::from_resource_name("0", None).is_ok());
|
||||
/// assert!(Icon::from_resource(0, None).is_err());
|
||||
/// ```
|
||||
fn from_resource_name(name: &str, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
|
||||
}
|
||||
|
||||
impl IconExtWindows for Icon {
|
||||
@@ -694,4 +759,9 @@ impl IconExtWindows for Icon {
|
||||
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
|
||||
fn from_resource_name(name: &str, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
|
||||
let win_icon = crate::platform_impl::WinIcon::from_resource_name(name, size)?;
|
||||
Ok(Icon { inner: win_icon })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
|
||||
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
|
||||
use crate::monitor::MonitorHandle;
|
||||
use crate::window::{Window, WindowAttributes};
|
||||
|
||||
@@ -81,9 +81,7 @@ pub type XWindow = u32;
|
||||
#[inline]
|
||||
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
|
||||
// Append new hook.
|
||||
unsafe {
|
||||
crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
|
||||
}
|
||||
crate::platform_impl::XLIB_ERROR_HOOKS.lock().unwrap().push(hook);
|
||||
}
|
||||
|
||||
/// Additional methods on [`ActiveEventLoop`] that are specific to X11.
|
||||
@@ -99,6 +97,19 @@ impl ActiveEventLoopExtX11 for ActiveEventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`EventLoop`] that are specific to X11.
|
||||
pub trait EventLoopExtX11 {
|
||||
/// True if the [`EventLoop`] uses X11.
|
||||
fn is_x11(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoopExtX11 for EventLoop<T> {
|
||||
#[inline]
|
||||
fn is_x11(&self) -> bool {
|
||||
!self.event_loop.is_wayland()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`EventLoopBuilder`] that are specific to X11.
|
||||
pub trait EventLoopBuilderExtX11 {
|
||||
/// Force using X11.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(android_platform)]
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::VecDeque;
|
||||
use std::hash::Hash;
|
||||
@@ -134,7 +132,7 @@ impl RedrawRequester {
|
||||
pub struct KeyEventExtra {}
|
||||
|
||||
pub struct EventLoop<T: 'static> {
|
||||
android_app: AndroidApp,
|
||||
pub(crate) android_app: AndroidApp,
|
||||
window_target: event_loop::ActiveEventLoop,
|
||||
redraw_flag: SharedFlag,
|
||||
user_events_sender: mpsc::Sender<T>,
|
||||
@@ -648,7 +646,7 @@ impl<T> EventLoopProxy<T> {
|
||||
}
|
||||
|
||||
pub struct ActiveEventLoop {
|
||||
app: AndroidApp,
|
||||
pub(crate) app: AndroidApp,
|
||||
control_flow: Cell<ControlFlow>,
|
||||
exit: Cell<bool>,
|
||||
redraw_requester: RedrawRequester,
|
||||
@@ -679,6 +677,11 @@ 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(
|
||||
@@ -905,7 +908,13 @@ impl Window {
|
||||
|
||||
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
|
||||
|
||||
pub fn set_ime_allowed(&self, _allowed: bool) {}
|
||||
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_purpose(&self, _purpose: ImePurpose) {}
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
|
||||
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
|
||||
|
||||
use super::app_state::{self, EventWrapper};
|
||||
use super::uikit::{UIApplication, UIWindow};
|
||||
use super::window::WinitUIWindow;
|
||||
use crate::event::{Event, WindowEvent};
|
||||
use crate::window::WindowId as RootWindowId;
|
||||
|
||||
declare_class!(
|
||||
pub struct AppDelegate;
|
||||
|
||||
unsafe impl ClassType for AppDelegate {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
const NAME: &'static str = "WinitApplicationDelegate";
|
||||
}
|
||||
|
||||
impl DeclaredClass for AppDelegate {}
|
||||
|
||||
// UIApplicationDelegate protocol
|
||||
unsafe impl AppDelegate {
|
||||
#[method(application:didFinishLaunchingWithOptions:)]
|
||||
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
|
||||
app_state::did_finish_launching(MainThreadMarker::new().unwrap());
|
||||
true
|
||||
}
|
||||
|
||||
#[method(applicationDidBecomeActive:)]
|
||||
fn did_become_active(&self, _application: &UIApplication) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
|
||||
}
|
||||
|
||||
#[method(applicationWillResignActive:)]
|
||||
fn will_resign_active(&self, _application: &UIApplication) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
|
||||
}
|
||||
|
||||
#[method(applicationWillEnterForeground:)]
|
||||
fn will_enter_foreground(&self, application: &UIApplication) {
|
||||
self.send_occluded_event_for_all_windows(application, false);
|
||||
}
|
||||
|
||||
#[method(applicationDidEnterBackground:)]
|
||||
fn did_enter_background(&self, application: &UIApplication) {
|
||||
self.send_occluded_event_for_all_windows(application, true);
|
||||
}
|
||||
|
||||
#[method(applicationWillTerminate:)]
|
||||
fn will_terminate(&self, application: &UIApplication) {
|
||||
let mut events = Vec::new();
|
||||
for window in application.windows().iter() {
|
||||
if window.is_kind_of::<WinitUIWindow>() {
|
||||
// SAFETY: We just checked that the window is a `winit` window
|
||||
let window = unsafe {
|
||||
let ptr: *const UIWindow = window;
|
||||
let ptr: *const WinitUIWindow = ptr.cast();
|
||||
&*ptr
|
||||
};
|
||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Destroyed,
|
||||
}));
|
||||
}
|
||||
}
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_events(mtm, events);
|
||||
app_state::terminated(mtm);
|
||||
}
|
||||
|
||||
#[method(applicationDidReceiveMemoryWarning:)]
|
||||
fn did_receive_memory_warning(&self, _application: &UIApplication) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning))
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl AppDelegate {
|
||||
fn send_occluded_event_for_all_windows(&self, application: &UIApplication, occluded: bool) {
|
||||
let mut events = Vec::new();
|
||||
for window in application.windows().iter() {
|
||||
if window.is_kind_of::<WinitUIWindow>() {
|
||||
// SAFETY: We just checked that the window is a `winit` window
|
||||
let window = unsafe {
|
||||
let ptr: *const UIWindow = window;
|
||||
let ptr: *const WinitUIWindow = ptr.cast();
|
||||
&*ptr
|
||||
};
|
||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Occluded(occluded),
|
||||
}));
|
||||
}
|
||||
}
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
app_state::handle_nonuser_events(mtm, events);
|
||||
}
|
||||
}
|
||||
@@ -13,14 +13,15 @@ use core_foundation::runloop::{
|
||||
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
|
||||
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
|
||||
};
|
||||
use objc2::rc::Id;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::{msg_send, sel};
|
||||
use objc2_foundation::{
|
||||
CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo,
|
||||
CGRect, CGSize, MainThreadMarker, NSInteger, NSObjectProtocol, NSOperatingSystemVersion,
|
||||
NSProcessInfo,
|
||||
};
|
||||
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView, UIWindow};
|
||||
|
||||
use super::uikit::UIView;
|
||||
use super::window::WinitUIWindow;
|
||||
use crate::dpi::PhysicalSize;
|
||||
use crate::event::{Event, InnerSizeWriter, StartCause, WindowEvent};
|
||||
@@ -71,7 +72,7 @@ pub(crate) enum EventWrapper {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScaleFactorChanged {
|
||||
pub(super) window: Id<WinitUIWindow>,
|
||||
pub(super) window: Retained<WinitUIWindow>,
|
||||
pub(super) suggested_size: PhysicalSize<u32>,
|
||||
pub(super) scale_factor: f64,
|
||||
}
|
||||
@@ -98,25 +99,25 @@ impl Event<HandlePendingUserEvents> {
|
||||
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
|
||||
enum AppStateImpl {
|
||||
NotLaunched {
|
||||
queued_windows: Vec<Id<WinitUIWindow>>,
|
||||
queued_windows: Vec<Retained<WinitUIWindow>>,
|
||||
queued_events: Vec<EventWrapper>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
||||
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
|
||||
},
|
||||
Launching {
|
||||
queued_windows: Vec<Id<WinitUIWindow>>,
|
||||
queued_windows: Vec<Retained<WinitUIWindow>>,
|
||||
queued_events: Vec<EventWrapper>,
|
||||
queued_handler: EventLoopHandler,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
||||
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
|
||||
},
|
||||
ProcessingEvents {
|
||||
handler: EventLoopHandler,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
||||
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
|
||||
active_control_flow: ControlFlow,
|
||||
},
|
||||
// special state to deal with reentrancy and prevent mutable aliasing.
|
||||
InUserCallback {
|
||||
queued_events: Vec<EventWrapper>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow>>,
|
||||
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
|
||||
},
|
||||
ProcessingRedraws {
|
||||
handler: EventLoopHandler,
|
||||
@@ -146,6 +147,8 @@ 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)]
|
||||
@@ -227,7 +230,9 @@ impl AppState {
|
||||
});
|
||||
}
|
||||
|
||||
fn did_finish_launching_transition(&mut self) -> (Vec<Id<WinitUIWindow>>, Vec<EventWrapper>) {
|
||||
fn did_finish_launching_transition(
|
||||
&mut self,
|
||||
) -> (Vec<Retained<WinitUIWindow>>, Vec<EventWrapper>) {
|
||||
let (windows, events, handler, queued_gpu_redraws) = match self.take_state() {
|
||||
AppStateImpl::Launching {
|
||||
queued_windows,
|
||||
@@ -343,7 +348,7 @@ impl AppState {
|
||||
UserCallbackTransitionResult::Success { handler, active_control_flow, processing_redraws }
|
||||
}
|
||||
|
||||
fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow>> {
|
||||
fn main_events_cleared_transition(&mut self) -> HashSet<Retained<WinitUIWindow>> {
|
||||
let (handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
|
||||
AppStateImpl::ProcessingEvents { handler, queued_gpu_redraws, active_control_flow } => {
|
||||
(handler, queued_gpu_redraws, active_control_flow)
|
||||
@@ -370,6 +375,7 @@ impl AppState {
|
||||
(ControlFlow::Wait, ControlFlow::Wait) => {
|
||||
let start = Instant::now();
|
||||
self.set_state(AppStateImpl::Waiting { waiting_handler, start });
|
||||
self.waker.stop()
|
||||
},
|
||||
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
|
||||
if old_instant == new_instant =>
|
||||
@@ -411,7 +417,7 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>) {
|
||||
pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Retained<WinitUIWindow>) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
match this.state_mut() {
|
||||
&mut AppStateImpl::NotLaunched { ref mut queued_windows, .. } => {
|
||||
@@ -431,7 +437,7 @@ pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>)
|
||||
window.makeKeyAndVisible();
|
||||
}
|
||||
|
||||
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUIWindow>) {
|
||||
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<WinitUIWindow>) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
match this.state_mut() {
|
||||
&mut AppStateImpl::NotLaunched { ref mut queued_gpu_redraws, .. }
|
||||
@@ -659,6 +665,28 @@ fn handle_user_events(mtm: MainThreadMarker) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, occluded: bool) {
|
||||
let mtm = MainThreadMarker::from(application);
|
||||
|
||||
let mut events = Vec::new();
|
||||
#[allow(deprecated)]
|
||||
for window in application.windows().iter() {
|
||||
if window.is_kind_of::<WinitUIWindow>() {
|
||||
// SAFETY: We just checked that the window is a `winit` window
|
||||
let window = unsafe {
|
||||
let ptr: *const UIWindow = window;
|
||||
let ptr: *const WinitUIWindow = ptr.cast();
|
||||
&*ptr
|
||||
};
|
||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Occluded(occluded),
|
||||
}));
|
||||
}
|
||||
}
|
||||
handle_nonuser_events(mtm, events);
|
||||
}
|
||||
|
||||
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
if !this.has_launched() || this.has_terminated() {
|
||||
@@ -693,7 +721,27 @@ pub fn handle_events_cleared(mtm: MainThreadMarker) {
|
||||
AppState::get_mut(mtm).events_cleared_transition();
|
||||
}
|
||||
|
||||
pub fn terminated(mtm: MainThreadMarker) {
|
||||
pub(crate) fn terminated(application: &UIApplication) {
|
||||
let mtm = MainThreadMarker::from(application);
|
||||
|
||||
let mut events = Vec::new();
|
||||
#[allow(deprecated)]
|
||||
for window in application.windows().iter() {
|
||||
if window.is_kind_of::<WinitUIWindow>() {
|
||||
// SAFETY: We just checked that the window is a `winit` window
|
||||
let window = unsafe {
|
||||
let ptr: *const UIWindow = window;
|
||||
let ptr: *const WinitUIWindow = ptr.cast();
|
||||
&*ptr
|
||||
};
|
||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Destroyed,
|
||||
}));
|
||||
}
|
||||
}
|
||||
handle_nonuser_events(mtm, events);
|
||||
|
||||
let mut this = AppState::get_mut(mtm);
|
||||
let mut handler = this.terminated_transition();
|
||||
drop(this);
|
||||
@@ -721,7 +769,7 @@ fn handle_hidpi_proxy(handler: &mut EventLoopHandler, event: ScaleFactorChanged)
|
||||
view.setFrame(new_frame);
|
||||
}
|
||||
|
||||
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Id<UIView>, CGRect) {
|
||||
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRect) {
|
||||
let view_controller = window.rootViewController().unwrap();
|
||||
let view = view_controller.view().unwrap();
|
||||
let bounds = window.bounds();
|
||||
@@ -753,7 +801,7 @@ impl EventLoopWaker {
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
std::f64::MAX,
|
||||
f64::MAX,
|
||||
0.000_000_1,
|
||||
0,
|
||||
0,
|
||||
@@ -767,11 +815,11 @@ impl EventLoopWaker {
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
|
||||
}
|
||||
|
||||
fn start_at(&mut self, instant: Instant) {
|
||||
@@ -857,23 +905,17 @@ fn meets_requirements(
|
||||
}
|
||||
|
||||
fn get_version() -> NSOperatingSystemVersion {
|
||||
unsafe {
|
||||
let process_info = NSProcessInfo::processInfo();
|
||||
let atleast_ios_8: bool = msg_send![
|
||||
&process_info,
|
||||
respondsToSelector: sel!(operatingSystemVersion)
|
||||
];
|
||||
// winit requires atleast iOS 8 because no one has put the time into supporting earlier os
|
||||
// versions. Older iOS versions are increasingly difficult to test. For example,
|
||||
// Xcode 11 does not support debugging on devices with an iOS version of less than
|
||||
// 8. Another example, in order to use an iOS simulator older than iOS 8, you must
|
||||
// download an older version of Xcode (<9), and at least Xcode 7 has been tested to
|
||||
// not even run on macOS 10.15 - Xcode 8 might?
|
||||
//
|
||||
// The minimum required iOS version is likely to grow in the future.
|
||||
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
|
||||
process_info.operatingSystemVersion()
|
||||
}
|
||||
let process_info = NSProcessInfo::processInfo();
|
||||
let atleast_ios_8 = process_info.respondsToSelector(sel!(operatingSystemVersion));
|
||||
// Winit requires atleast iOS 8 because no one has put the time into supporting earlier os
|
||||
// versions. Older iOS versions are increasingly difficult to test. For example, Xcode 11 does
|
||||
// not support debugging on devices with an iOS version of less than 8. Another example, in
|
||||
// order to use an iOS simulator older than iOS 8, you must download an older version of Xcode
|
||||
// (<9), and at least Xcode 7 has been tested to not even run on macOS 10.15 - Xcode 8 might?
|
||||
//
|
||||
// The minimum required iOS version is likely to grow in the future.
|
||||
assert!(atleast_ios_8, "`winit` requires iOS version 8 or greater");
|
||||
process_info.operatingSystemVersion()
|
||||
}
|
||||
|
||||
pub fn os_capabilities() -> OSCapabilities {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::c_void;
|
||||
use std::ffi::{c_char, c_int, c_void};
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::sync::mpsc::{self, Receiver, Sender};
|
||||
|
||||
use core_foundation::base::{CFIndex, CFRelease};
|
||||
@@ -11,8 +11,16 @@ use core_foundation::runloop::{
|
||||
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
|
||||
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2::ClassType;
|
||||
use objc2_foundation::{MainThreadMarker, NSString};
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{msg_send_id, ClassType};
|
||||
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
|
||||
use objc2_ui_kit::{
|
||||
UIApplication, UIApplicationDidBecomeActiveNotification,
|
||||
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
|
||||
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain,
|
||||
UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification,
|
||||
UIApplicationWillTerminateNotification, UIDevice, UIScreen, UIUserInterfaceIdiom,
|
||||
};
|
||||
|
||||
use crate::error::EventLoopError;
|
||||
use crate::event::Event;
|
||||
@@ -20,12 +28,11 @@ use crate::event_loop::{
|
||||
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed,
|
||||
};
|
||||
use crate::platform::ios::Idiom;
|
||||
use crate::platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents};
|
||||
use crate::window::{CustomCursor, CustomCursorSource};
|
||||
use crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents};
|
||||
use crate::window::{CustomCursor, CustomCursorSource, Theme};
|
||||
|
||||
use super::app_delegate::AppDelegate;
|
||||
use super::app_state::AppState;
|
||||
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom};
|
||||
use super::app_state::{send_occluded_event_for_all_windows, AppState, EventWrapper};
|
||||
use super::notification_center::create_observer;
|
||||
use super::{app_state, monitor, MonitorHandle};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -44,7 +51,8 @@ impl ActiveEventLoop {
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
Some(MonitorHandle::new(UIScreen::main(self.mtm)))
|
||||
#[allow(deprecated)]
|
||||
Some(MonitorHandle::new(UIScreen::mainScreen(self.mtm)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -56,6 +64,11 @@ 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(
|
||||
@@ -125,6 +138,18 @@ pub struct EventLoop<T: 'static> {
|
||||
sender: Sender<T>,
|
||||
receiver: Receiver<T>,
|
||||
window_target: RootActiveEventLoop,
|
||||
|
||||
// Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the
|
||||
// system instead cleans it up next time it would have posted a notification to it.
|
||||
//
|
||||
// Though we do still need to keep the observers around to prevent them from being deallocated.
|
||||
_did_finish_launching_observer: Retained<NSObject>,
|
||||
_did_become_active_observer: Retained<NSObject>,
|
||||
_will_resign_active_observer: Retained<NSObject>,
|
||||
_will_enter_foreground_observer: Retained<NSObject>,
|
||||
_did_enter_background_observer: Retained<NSObject>,
|
||||
_will_terminate_observer: Retained<NSObject>,
|
||||
_did_receive_memory_warning_observer: Retained<NSObject>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -151,11 +176,97 @@ impl<T: 'static> EventLoop<T> {
|
||||
// this line sets up the main run loop before `UIApplicationMain`
|
||||
setup_control_flow_observers();
|
||||
|
||||
let center = unsafe { NSNotificationCenter::defaultCenter() };
|
||||
|
||||
let _did_finish_launching_observer = create_observer(
|
||||
¢er,
|
||||
// `application:didFinishLaunchingWithOptions:`
|
||||
unsafe { UIApplicationDidFinishLaunchingNotification },
|
||||
move |_| {
|
||||
app_state::did_finish_launching(mtm);
|
||||
},
|
||||
);
|
||||
let _did_become_active_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationDidBecomeActive:`
|
||||
unsafe { UIApplicationDidBecomeActiveNotification },
|
||||
move |_| {
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed));
|
||||
},
|
||||
);
|
||||
let _will_resign_active_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationWillResignActive:`
|
||||
unsafe { UIApplicationWillResignActiveNotification },
|
||||
move |_| {
|
||||
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended));
|
||||
},
|
||||
);
|
||||
let _will_enter_foreground_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationWillEnterForeground:`
|
||||
unsafe { UIApplicationWillEnterForegroundNotification },
|
||||
move |notification| {
|
||||
let app = unsafe { notification.object() }.expect(
|
||||
"UIApplicationWillEnterForegroundNotification to have application object",
|
||||
);
|
||||
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
|
||||
// documented to be `UIApplication`.
|
||||
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
||||
send_occluded_event_for_all_windows(&app, false);
|
||||
},
|
||||
);
|
||||
let _did_enter_background_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationDidEnterBackground:`
|
||||
unsafe { UIApplicationDidEnterBackgroundNotification },
|
||||
move |notification| {
|
||||
let app = unsafe { notification.object() }.expect(
|
||||
"UIApplicationDidEnterBackgroundNotification to have application object",
|
||||
);
|
||||
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
|
||||
// documented to be `UIApplication`.
|
||||
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
||||
send_occluded_event_for_all_windows(&app, true);
|
||||
},
|
||||
);
|
||||
let _will_terminate_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationWillTerminate:`
|
||||
unsafe { UIApplicationWillTerminateNotification },
|
||||
move |notification| {
|
||||
let app = unsafe { notification.object() }
|
||||
.expect("UIApplicationWillTerminateNotification to have application object");
|
||||
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
|
||||
// (somewhat) documented to be `UIApplication`.
|
||||
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
|
||||
app_state::terminated(&app);
|
||||
},
|
||||
);
|
||||
let _did_receive_memory_warning_observer = create_observer(
|
||||
¢er,
|
||||
// `applicationDidReceiveMemoryWarning:`
|
||||
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
|
||||
move |_| {
|
||||
app_state::handle_nonuser_event(
|
||||
mtm,
|
||||
EventWrapper::StaticEvent(Event::MemoryWarning),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Ok(EventLoop {
|
||||
mtm,
|
||||
sender,
|
||||
receiver,
|
||||
window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData },
|
||||
_did_finish_launching_observer,
|
||||
_did_become_active_observer,
|
||||
_will_resign_active_observer,
|
||||
_will_enter_foreground_observer,
|
||||
_did_enter_background_observer,
|
||||
_will_terminate_observer,
|
||||
_did_receive_memory_warning_observer,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -163,7 +274,8 @@ impl<T: 'static> EventLoop<T> {
|
||||
where
|
||||
F: FnMut(Event<T>, &RootActiveEventLoop),
|
||||
{
|
||||
let application = UIApplication::shared(self.mtm);
|
||||
let application: Option<Retained<UIApplication>> =
|
||||
unsafe { msg_send_id![UIApplication::class(), sharedApplication] };
|
||||
assert!(
|
||||
application.is_none(),
|
||||
"\
|
||||
@@ -184,11 +296,21 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
app_state::will_launch(self.mtm, handler);
|
||||
|
||||
// Ensure application delegate is initialized
|
||||
let _ = AppDelegate::class();
|
||||
extern "C" {
|
||||
// These functions are in crt_externs.h.
|
||||
fn _NSGetArgc() -> *mut c_int;
|
||||
fn _NSGetArgv() -> *mut *mut *mut c_char;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
UIApplicationMain(0, ptr::null(), None, Some(&NSString::from_str(AppDelegate::NAME)))
|
||||
UIApplicationMain(
|
||||
*_NSGetArgc(),
|
||||
NonNull::new(*_NSGetArgv()).unwrap(),
|
||||
// We intentionally override neither the application nor the delegate, to allow the
|
||||
// user to do so themselves!
|
||||
None,
|
||||
None,
|
||||
)
|
||||
};
|
||||
unreachable!()
|
||||
}
|
||||
@@ -205,7 +327,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// EventLoopExtIOS
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn idiom(&self) -> Idiom {
|
||||
match UIDevice::current(self.mtm).userInterfaceIdiom() {
|
||||
match UIDevice::currentDevice(self.mtm).userInterfaceIdiom() {
|
||||
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
|
||||
UIUserInterfaceIdiom::Phone => Idiom::Phone,
|
||||
UIUserInterfaceIdiom::Pad => Idiom::Pad,
|
||||
@@ -260,8 +382,7 @@ impl<T> EventLoopProxy<T> {
|
||||
cancel: None,
|
||||
perform: event_loop_proxy_handler,
|
||||
};
|
||||
let source =
|
||||
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
||||
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
|
||||
@@ -344,7 +465,7 @@ fn setup_control_flow_observers() {
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopAfterWaiting,
|
||||
1, // repeat = true
|
||||
CFIndex::min_value(),
|
||||
CFIndex::MIN,
|
||||
control_flow_begin_handler,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
@@ -364,7 +485,7 @@ fn setup_control_flow_observers() {
|
||||
ptr::null_mut(),
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
1, // repeat = true
|
||||
CFIndex::max_value(),
|
||||
CFIndex::MAX,
|
||||
control_flow_end_handler,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#![cfg(ios_platform)]
|
||||
#![allow(clippy::let_unit_value)]
|
||||
|
||||
mod app_delegate;
|
||||
mod app_state;
|
||||
mod event_loop;
|
||||
mod monitor;
|
||||
mod uikit;
|
||||
mod notification_center;
|
||||
mod view;
|
||||
mod view_controller;
|
||||
mod window;
|
||||
@@ -34,7 +32,7 @@ pub(crate) use crate::platform_impl::Fullscreen;
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
pub const fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,22 @@ use std::collections::{BTreeSet, VecDeque};
|
||||
use std::{fmt, hash, ptr};
|
||||
|
||||
use objc2::mutability::IsRetainable;
|
||||
use objc2::rc::Id;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::Message;
|
||||
use objc2_foundation::{run_on_main, MainThreadBound, MainThreadMarker, NSInteger};
|
||||
use objc2_ui_kit::{UIScreen, UIScreenMode};
|
||||
|
||||
use super::uikit::{UIScreen, UIScreenMode};
|
||||
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)]
|
||||
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
|
||||
struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Retained<T>>);
|
||||
|
||||
impl<T: IsRetainable + Message> Clone for MainThreadBoundDelegateImpls<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(run_on_main(|mtm| MainThreadBound::new(Id::clone(self.0.get(mtm)), mtm)))
|
||||
Self(run_on_main(|mtm| MainThreadBound::new(Retained::clone(self.0.get(mtm)), mtm)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ impl<T: IsRetainable + Message> hash::Hash for MainThreadBoundDelegateImpls<T> {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
// SAFETY: Marker only used to get the pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Id::as_ptr(self.0.get(mtm)).hash(state);
|
||||
Retained::as_ptr(self.0.get(mtm)).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ impl<T: IsRetainable + Message> PartialEq for MainThreadBoundDelegateImpls<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// SAFETY: Marker only used to get the pointer
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Id::as_ptr(self.0.get(mtm)) == Id::as_ptr(other.0.get(mtm))
|
||||
Retained::as_ptr(self.0.get(mtm)) == Retained::as_ptr(other.0.get(mtm))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ pub struct VideoModeHandle {
|
||||
|
||||
impl VideoModeHandle {
|
||||
fn new(
|
||||
uiscreen: Id<UIScreen>,
|
||||
screen_mode: Id<UIScreenMode>,
|
||||
uiscreen: Retained<UIScreen>,
|
||||
screen_mode: Retained<UIScreenMode>,
|
||||
mtm: MainThreadMarker,
|
||||
) -> VideoModeHandle {
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
|
||||
@@ -83,13 +83,13 @@ impl VideoModeHandle {
|
||||
self.monitor.clone()
|
||||
}
|
||||
|
||||
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Id<UIScreenMode> {
|
||||
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Retained<UIScreenMode> {
|
||||
self.screen_mode.0.get(mtm)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MonitorHandle {
|
||||
ui_screen: MainThreadBound<Id<UIScreen>>,
|
||||
ui_screen: MainThreadBound<Retained<UIScreen>>,
|
||||
}
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
@@ -102,13 +102,20 @@ impl Clone for MonitorHandle {
|
||||
|
||||
impl hash::Hash for MonitorHandle {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
(self as *const Self).hash(state);
|
||||
// SAFETY: Only getting the pointer.
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Retained::as_ptr(self.ui_screen.get(mtm)).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
ptr::eq(self, other)
|
||||
// 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)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,8 +129,10 @@ 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
|
||||
(self as *const Self).cmp(&(other as *const Self))
|
||||
let mtm = unsafe { MainThreadMarker::new_unchecked() };
|
||||
Retained::as_ptr(self.ui_screen.get(mtm)).cmp(&Retained::as_ptr(other.ui_screen.get(mtm)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,20 +149,22 @@ impl fmt::Debug for MonitorHandle {
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
pub(crate) fn new(ui_screen: Id<UIScreen>) -> Self {
|
||||
// Holding `Id<UIScreen>` implies we're on the main thread.
|
||||
pub(crate) fn new(ui_screen: Retained<UIScreen>) -> Self {
|
||||
// Holding `Retained<UIScreen>` implies we're on the main thread.
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
Self { ui_screen: MainThreadBound::new(ui_screen, mtm) }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<String> {
|
||||
run_on_main(|mtm| {
|
||||
let main = UIScreen::main(mtm);
|
||||
#[allow(deprecated)]
|
||||
let main = UIScreen::mainScreen(mtm);
|
||||
if *self.ui_screen(mtm) == main {
|
||||
Some("Primary".to_string())
|
||||
} else if *self.ui_screen(mtm) == main.mirroredScreen() {
|
||||
} else if Some(self.ui_screen(mtm)) == main.mirroredScreen().as_ref() {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
UIScreen::screens(mtm)
|
||||
.iter()
|
||||
.position(|rhs| rhs == &**self.ui_screen(mtm))
|
||||
@@ -197,7 +208,7 @@ impl MonitorHandle {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Id<UIScreen> {
|
||||
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Retained<UIScreen> {
|
||||
self.ui_screen.get(mtm)
|
||||
}
|
||||
|
||||
@@ -237,5 +248,30 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
|
||||
}
|
||||
|
||||
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))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
27
src/platform_impl/ios/notification_center.rs
Normal file
27
src/platform_impl/ios/notification_center.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use block2::RcBlock;
|
||||
use objc2::rc::Retained;
|
||||
use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject};
|
||||
|
||||
/// Observe the given notification.
|
||||
///
|
||||
/// This is used in Winit as an alternative to declaring an application delegate, as we want to
|
||||
/// give the user full control over those.
|
||||
pub fn create_observer(
|
||||
center: &NSNotificationCenter,
|
||||
name: &NSNotificationName,
|
||||
handler: impl Fn(&NSNotification) + 'static,
|
||||
) -> Retained<NSObject> {
|
||||
let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
|
||||
handler(unsafe { notification.as_ref() });
|
||||
});
|
||||
unsafe {
|
||||
center.addObserverForName_object_queue_usingBlock(
|
||||
Some(name),
|
||||
None, // No sender filter
|
||||
None, // No queue, run on posting thread (i.e. main thread)
|
||||
&block,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
use objc2_foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
|
||||
|
||||
use super::{UIResponder, UIWindow};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIApplication;
|
||||
|
||||
unsafe impl ClassType for UIApplication {
|
||||
#[inherits(NSObject)]
|
||||
type Super = UIResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIApplication {
|
||||
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self>> {
|
||||
unsafe { msg_send_id![Self::class(), sharedApplication] }
|
||||
}
|
||||
|
||||
pub fn windows(&self) -> Id<NSArray<UIWindow>> {
|
||||
unsafe { msg_send_id![self, windows] }
|
||||
}
|
||||
|
||||
#[method(statusBarFrame)]
|
||||
pub fn statusBarFrame(&self) -> CGRect;
|
||||
}
|
||||
);
|
||||
@@ -1,12 +0,0 @@
|
||||
use objc2::{extern_class, mutability, ClassType};
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UICoordinateSpace;
|
||||
|
||||
unsafe impl ClassType for UICoordinateSpace {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
@@ -1,41 +0,0 @@
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
use objc2_foundation::{MainThreadMarker, NSInteger, NSObject};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIDevice;
|
||||
|
||||
unsafe impl ClassType for UIDevice {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIDevice {
|
||||
pub fn current(_mtm: MainThreadMarker) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::class(), currentDevice] }
|
||||
}
|
||||
|
||||
#[method(userInterfaceIdiom)]
|
||||
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
|
||||
}
|
||||
);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIUserInterfaceIdiom(NSInteger);
|
||||
|
||||
unsafe impl Encode for UIUserInterfaceIdiom {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
impl UIUserInterfaceIdiom {
|
||||
pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1);
|
||||
pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0);
|
||||
pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1);
|
||||
pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2);
|
||||
pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
use objc2::{extern_class, mutability, ClassType};
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIEvent;
|
||||
|
||||
unsafe impl ClassType for UIEvent {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
@@ -1,14 +0,0 @@
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2_foundation::NSUInteger;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIRectEdge(pub NSUInteger);
|
||||
|
||||
impl UIRectEdge {
|
||||
pub const NONE: Self = Self(0);
|
||||
}
|
||||
|
||||
unsafe impl Encode for UIRectEdge {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{extern_class, extern_methods, extern_protocol, mutability, ClassType, ProtocolType};
|
||||
use objc2_foundation::{CGFloat, CGPoint, NSInteger, NSObject, NSObjectProtocol, NSUInteger};
|
||||
|
||||
use super::UIView;
|
||||
|
||||
extern_class!(
|
||||
/// [`UIGestureRecognizer`](https://developer.apple.com/documentation/uikit/uigesturerecognizer)
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIGestureRecognizer;
|
||||
|
||||
unsafe impl ClassType for UIGestureRecognizer {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIGestureRecognizer {
|
||||
#[method(state)]
|
||||
pub fn state(&self) -> UIGestureRecognizerState;
|
||||
|
||||
/// [`delegate`](https://developer.apple.com/documentation/uikit/uigesturerecognizer/1624207-delegate?language=objc)
|
||||
/// @property(nullable, nonatomic, weak) id<UIGestureRecognizerDelegate> delegate;
|
||||
#[method(setDelegate:)]
|
||||
pub fn setDelegate(&self, delegate: &ProtocolObject<dyn UIGestureRecognizerDelegate>);
|
||||
|
||||
#[method_id(delegate)]
|
||||
pub fn delegate(&self) -> Id<ProtocolObject<dyn UIGestureRecognizerDelegate>>;
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl Encode for UIGestureRecognizer {
|
||||
const ENCODING: Encoding = Encoding::Object;
|
||||
}
|
||||
|
||||
// [`UIGestureRecognizerState`](https://developer.apple.com/documentation/uikit/uigesturerecognizer/state)
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIGestureRecognizerState(NSInteger);
|
||||
|
||||
unsafe impl Encode for UIGestureRecognizerState {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl UIGestureRecognizerState {
|
||||
pub const Possible: Self = Self(0);
|
||||
pub const Began: Self = Self(1);
|
||||
pub const Changed: Self = Self(2);
|
||||
pub const Ended: Self = Self(3);
|
||||
pub const Cancelled: Self = Self(4);
|
||||
pub const Failed: Self = Self(5);
|
||||
}
|
||||
|
||||
// [`UIPinchGestureRecognizer`](https://developer.apple.com/documentation/uikit/uipinchgesturerecognizer)
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIPinchGestureRecognizer;
|
||||
|
||||
unsafe impl ClassType for UIPinchGestureRecognizer {
|
||||
type Super = UIGestureRecognizer;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIPinchGestureRecognizer {
|
||||
#[method(scale)]
|
||||
pub fn scale(&self) -> CGFloat;
|
||||
|
||||
#[method(velocity)]
|
||||
pub fn velocity(&self) -> CGFloat;
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl Encode for UIPinchGestureRecognizer {
|
||||
const ENCODING: Encoding = Encoding::Object;
|
||||
}
|
||||
|
||||
extern_class!(
|
||||
/// [`UIRotationGestureRecognizer`](https://developer.apple.com/documentation/uikit/uirotationgesturerecognizer)
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIRotationGestureRecognizer;
|
||||
|
||||
unsafe impl ClassType for UIRotationGestureRecognizer {
|
||||
type Super = UIGestureRecognizer;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIRotationGestureRecognizer {
|
||||
#[method(rotation)]
|
||||
pub fn rotation(&self) -> CGFloat;
|
||||
|
||||
#[method(velocity)]
|
||||
pub fn velocity(&self) -> CGFloat;
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl Encode for UIRotationGestureRecognizer {
|
||||
const ENCODING: Encoding = Encoding::Object;
|
||||
}
|
||||
|
||||
extern_class!(
|
||||
/// [`UITapGestureRecognizer`](https://developer.apple.com/documentation/uikit/uitapgesturerecognizer)
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UITapGestureRecognizer;
|
||||
|
||||
unsafe impl ClassType for UITapGestureRecognizer {
|
||||
type Super = UIGestureRecognizer;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UITapGestureRecognizer {
|
||||
#[method(setNumberOfTapsRequired:)]
|
||||
pub fn setNumberOfTapsRequired(&self, number_of_taps_required: NSUInteger);
|
||||
|
||||
#[method(setNumberOfTouchesRequired:)]
|
||||
pub fn setNumberOfTouchesRequired(&self, number_of_touches_required: NSUInteger);
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl Encode for UITapGestureRecognizer {
|
||||
const ENCODING: Encoding = Encoding::Object;
|
||||
}
|
||||
|
||||
extern_class!(
|
||||
/// [`UIPanGestureRecognizer`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer)
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIPanGestureRecognizer;
|
||||
|
||||
unsafe impl ClassType for UIPanGestureRecognizer {
|
||||
type Super = UIGestureRecognizer;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIPanGestureRecognizer {
|
||||
#[method(translationInView:)]
|
||||
pub fn translationInView(&self, view: &UIView) -> CGPoint;
|
||||
|
||||
#[method(setTranslation:inView:)]
|
||||
pub fn setTranslationInView(&self, translation: CGPoint, view: &UIView);
|
||||
|
||||
#[method(velocityInView:)]
|
||||
pub fn velocityInView(&self, view: &UIView) -> CGPoint;
|
||||
|
||||
#[method(setMinimumNumberOfTouches:)]
|
||||
pub fn setMinimumNumberOfTouches(&self, minimum_number_of_touches: NSUInteger);
|
||||
|
||||
#[method(minimumNumberOfTouches)]
|
||||
pub fn minimumNumberOfTouches(&self) -> NSUInteger;
|
||||
|
||||
#[method(setMaximumNumberOfTouches:)]
|
||||
pub fn setMaximumNumberOfTouches(&self, maximum_number_of_touches: NSUInteger);
|
||||
|
||||
#[method(maximumNumberOfTouches)]
|
||||
pub fn maximumNumberOfTouches(&self) -> NSUInteger;
|
||||
}
|
||||
);
|
||||
|
||||
extern_protocol!(
|
||||
/// (@protocol UIGestureRecognizerDelegate)[https://developer.apple.com/documentation/uikit/uigesturerecognizerdelegate?language=objc]
|
||||
pub(crate) unsafe trait UIGestureRecognizerDelegate: NSObjectProtocol {}
|
||||
|
||||
unsafe impl ProtocolType for dyn UIGestureRecognizerDelegate {
|
||||
const NAME: &'static str = "UIGestureRecognizerDelegate";
|
||||
}
|
||||
);
|
||||
@@ -1,53 +0,0 @@
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use objc2_foundation::NSString;
|
||||
|
||||
mod application;
|
||||
mod coordinate_space;
|
||||
mod device;
|
||||
mod event;
|
||||
mod geometry;
|
||||
mod gesture_recognizer;
|
||||
mod responder;
|
||||
mod screen;
|
||||
mod screen_mode;
|
||||
mod status_bar_style;
|
||||
mod touch;
|
||||
mod trait_collection;
|
||||
mod view;
|
||||
mod view_controller;
|
||||
mod window;
|
||||
|
||||
pub(crate) use self::application::UIApplication;
|
||||
pub(crate) use self::coordinate_space::UICoordinateSpace;
|
||||
pub(crate) use self::device::{UIDevice, UIUserInterfaceIdiom};
|
||||
pub(crate) use self::event::UIEvent;
|
||||
pub(crate) use self::geometry::UIRectEdge;
|
||||
pub(crate) use self::gesture_recognizer::{
|
||||
UIGestureRecognizer, UIGestureRecognizerDelegate, UIGestureRecognizerState,
|
||||
UIPanGestureRecognizer, UIPinchGestureRecognizer, UIRotationGestureRecognizer,
|
||||
UITapGestureRecognizer,
|
||||
};
|
||||
pub(crate) use self::responder::UIResponder;
|
||||
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
|
||||
pub(crate) use self::screen_mode::UIScreenMode;
|
||||
pub(crate) use self::status_bar_style::UIStatusBarStyle;
|
||||
pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType};
|
||||
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use self::view::{UIEdgeInsets, UIView};
|
||||
pub(crate) use self::view_controller::{UIInterfaceOrientationMask, UIViewController};
|
||||
pub(crate) use self::window::UIWindow;
|
||||
|
||||
#[link(name = "UIKit", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn UIApplicationMain(
|
||||
argc: c_int,
|
||||
argv: *const c_char,
|
||||
principalClassName: Option<&NSString>,
|
||||
delegateClassName: Option<&NSString>,
|
||||
) -> c_int;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
use objc2::{extern_class, mutability, ClassType};
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIResponder;
|
||||
|
||||
unsafe impl ClassType for UIResponder {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
@@ -1,80 +0,0 @@
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
use objc2_foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
|
||||
|
||||
use super::{UICoordinateSpace, UIScreenMode};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIScreen;
|
||||
|
||||
unsafe impl ClassType for UIScreen {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIScreen {
|
||||
pub fn main(_mtm: MainThreadMarker) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::class(), mainScreen] }
|
||||
}
|
||||
|
||||
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self>> {
|
||||
unsafe { msg_send_id![Self::class(), screens] }
|
||||
}
|
||||
|
||||
#[method(bounds)]
|
||||
pub fn bounds(&self) -> CGRect;
|
||||
|
||||
#[method(scale)]
|
||||
pub fn scale(&self) -> CGFloat;
|
||||
|
||||
#[method(nativeBounds)]
|
||||
pub fn nativeBounds(&self) -> CGRect;
|
||||
|
||||
#[method(nativeScale)]
|
||||
pub fn nativeScale(&self) -> CGFloat;
|
||||
|
||||
#[method(maximumFramesPerSecond)]
|
||||
pub fn maximumFramesPerSecond(&self) -> NSInteger;
|
||||
|
||||
pub fn mirroredScreen(&self) -> Id<Self> {
|
||||
unsafe { msg_send_id![Self::class(), mirroredScreen] }
|
||||
}
|
||||
|
||||
pub fn preferredMode(&self) -> Option<Id<UIScreenMode>> {
|
||||
unsafe { msg_send_id![self, preferredMode] }
|
||||
}
|
||||
|
||||
#[method(setCurrentMode:)]
|
||||
pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>);
|
||||
|
||||
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode>> {
|
||||
unsafe { msg_send_id![self, availableModes] }
|
||||
}
|
||||
|
||||
#[method(setOverscanCompensation:)]
|
||||
pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation);
|
||||
|
||||
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace> {
|
||||
unsafe { msg_send_id![self, coordinateSpace] }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct UIScreenOverscanCompensation(NSInteger);
|
||||
|
||||
unsafe impl Encode for UIScreenOverscanCompensation {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl UIScreenOverscanCompensation {
|
||||
pub const Scale: Self = Self(0);
|
||||
pub const InsetBounds: Self = Self(1);
|
||||
pub const None: Self = Self(2);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
use objc2_foundation::{CGSize, NSObject};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIScreenMode;
|
||||
|
||||
unsafe impl ClassType for UIScreenMode {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIScreenMode {
|
||||
#[method(size)]
|
||||
pub fn size(&self) -> CGSize;
|
||||
}
|
||||
);
|
||||
@@ -1,16 +0,0 @@
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2_foundation::NSInteger;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UIStatusBarStyle {
|
||||
#[default]
|
||||
Default = 0,
|
||||
LightContent = 1,
|
||||
DarkContent = 3,
|
||||
}
|
||||
|
||||
unsafe impl Encode for UIStatusBarStyle {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
use objc2_foundation::{CGFloat, CGPoint, NSInteger, NSObject};
|
||||
|
||||
use super::UIView;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UITouch;
|
||||
|
||||
unsafe impl ClassType for UITouch {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UITouch {
|
||||
#[method(locationInView:)]
|
||||
pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint;
|
||||
|
||||
#[method(type)]
|
||||
pub fn type_(&self) -> UITouchType;
|
||||
|
||||
#[method(force)]
|
||||
pub fn force(&self) -> CGFloat;
|
||||
|
||||
#[method(maximumPossibleForce)]
|
||||
pub fn maximumPossibleForce(&self) -> CGFloat;
|
||||
|
||||
#[method(altitudeAngle)]
|
||||
pub fn altitudeAngle(&self) -> CGFloat;
|
||||
|
||||
#[method(phase)]
|
||||
pub fn phase(&self) -> UITouchPhase;
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UITouchType {
|
||||
Direct = 0,
|
||||
Indirect,
|
||||
Pencil,
|
||||
}
|
||||
|
||||
unsafe impl Encode for UITouchType {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UITouchPhase {
|
||||
Began = 0,
|
||||
Moved,
|
||||
Stationary,
|
||||
Ended,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
unsafe impl Encode for UITouchPhase {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::{extern_class, extern_methods, mutability, ClassType};
|
||||
use objc2_foundation::{NSInteger, NSObject};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UITraitCollection;
|
||||
|
||||
unsafe impl ClassType for UITraitCollection {
|
||||
type Super = NSObject;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UITraitCollection {
|
||||
#[method(forceTouchCapability)]
|
||||
pub fn forceTouchCapability(&self) -> UIForceTouchCapability;
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)]
|
||||
pub enum UIForceTouchCapability {
|
||||
Unknown = 0,
|
||||
Unavailable,
|
||||
Available,
|
||||
}
|
||||
|
||||
unsafe impl Encode for UIForceTouchCapability {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
use objc2_foundation::{CGFloat, CGRect, NSObject};
|
||||
|
||||
use super::{UICoordinateSpace, UIGestureRecognizer, UIResponder, UIViewController};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIView;
|
||||
|
||||
unsafe impl ClassType for UIView {
|
||||
#[inherits(NSObject)]
|
||||
type Super = UIResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIView {
|
||||
#[method(bounds)]
|
||||
pub fn bounds(&self) -> CGRect;
|
||||
|
||||
#[method(setBounds:)]
|
||||
pub fn setBounds(&self, value: CGRect);
|
||||
|
||||
#[method(frame)]
|
||||
pub fn frame(&self) -> CGRect;
|
||||
|
||||
#[method(setFrame:)]
|
||||
pub fn setFrame(&self, value: CGRect);
|
||||
|
||||
#[method(contentScaleFactor)]
|
||||
pub fn contentScaleFactor(&self) -> CGFloat;
|
||||
|
||||
#[method(setContentScaleFactor:)]
|
||||
pub fn setContentScaleFactor(&self, val: CGFloat);
|
||||
|
||||
#[method(setMultipleTouchEnabled:)]
|
||||
pub fn setMultipleTouchEnabled(&self, val: bool);
|
||||
|
||||
pub fn rootViewController(&self) -> Option<Id<UIViewController>> {
|
||||
unsafe { msg_send_id![self, rootViewController] }
|
||||
}
|
||||
|
||||
#[method(setRootViewController:)]
|
||||
pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>);
|
||||
|
||||
#[method(convertRect:toCoordinateSpace:)]
|
||||
pub fn convertRect_toCoordinateSpace(
|
||||
&self,
|
||||
rect: CGRect,
|
||||
coordinateSpace: &UICoordinateSpace,
|
||||
) -> CGRect;
|
||||
|
||||
#[method(convertRect:fromCoordinateSpace:)]
|
||||
pub fn convertRect_fromCoordinateSpace(
|
||||
&self,
|
||||
rect: CGRect,
|
||||
coordinateSpace: &UICoordinateSpace,
|
||||
) -> CGRect;
|
||||
|
||||
#[method(safeAreaInsets)]
|
||||
pub fn safeAreaInsets(&self) -> UIEdgeInsets;
|
||||
|
||||
#[method(setNeedsDisplay)]
|
||||
pub fn setNeedsDisplay(&self);
|
||||
|
||||
#[method(addGestureRecognizer:)]
|
||||
pub fn addGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
|
||||
|
||||
#[method(removeGestureRecognizer:)]
|
||||
pub fn removeGestureRecognizer(&self, gestureRecognizer: &UIGestureRecognizer);
|
||||
}
|
||||
);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UIEdgeInsets {
|
||||
pub top: CGFloat,
|
||||
pub left: CGFloat,
|
||||
pub bottom: CGFloat,
|
||||
pub right: CGFloat,
|
||||
}
|
||||
|
||||
unsafe impl Encode for UIEdgeInsets {
|
||||
const ENCODING: Encoding = Encoding::Struct("UIEdgeInsets", &[
|
||||
CGFloat::ENCODING,
|
||||
CGFloat::ENCODING,
|
||||
CGFloat::ENCODING,
|
||||
CGFloat::ENCODING,
|
||||
]);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
use objc2_foundation::{NSObject, NSUInteger};
|
||||
|
||||
use super::{UIResponder, UIView};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIViewController;
|
||||
|
||||
unsafe impl ClassType for UIViewController {
|
||||
#[inherits(NSObject)]
|
||||
type Super = UIResponder;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIViewController {
|
||||
#[method(attemptRotationToDeviceOrientation)]
|
||||
pub fn attemptRotationToDeviceOrientation();
|
||||
|
||||
#[method(setNeedsStatusBarAppearanceUpdate)]
|
||||
pub fn setNeedsStatusBarAppearanceUpdate(&self);
|
||||
|
||||
#[method(setNeedsUpdateOfHomeIndicatorAutoHidden)]
|
||||
pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self);
|
||||
|
||||
#[method(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
|
||||
pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self);
|
||||
|
||||
pub fn view(&self) -> Option<Id<UIView>> {
|
||||
unsafe { msg_send_id![self, view] }
|
||||
}
|
||||
|
||||
#[method(setView:)]
|
||||
pub fn setView(&self, view: Option<&UIView>);
|
||||
}
|
||||
);
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UIInterfaceOrientationMask: NSUInteger {
|
||||
const Portrait = 1 << 1;
|
||||
const PortraitUpsideDown = 1 << 2;
|
||||
const LandscapeRight = 1 << 3;
|
||||
const LandscapeLeft = 1 << 4;
|
||||
const Landscape = Self::LandscapeLeft.bits() | Self::LandscapeRight.bits();
|
||||
const AllButUpsideDown = Self::Landscape.bits() | Self::Portrait.bits();
|
||||
const All = Self::AllButUpsideDown.bits() | Self::PortraitUpsideDown.bits();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for UIInterfaceOrientationMask {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
use objc2::rc::Id;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, mutability, ClassType};
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
use super::{UIResponder, UIScreen, UIView};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIWindow;
|
||||
|
||||
unsafe impl ClassType for UIWindow {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIView;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl UIWindow {
|
||||
pub fn screen(&self) -> Id<UIScreen> {
|
||||
unsafe { msg_send_id![self, screen] }
|
||||
}
|
||||
|
||||
#[method(setScreen:)]
|
||||
pub fn setScreen(&self, screen: &UIScreen);
|
||||
|
||||
#[method(setHidden:)]
|
||||
pub fn setHidden(&self, flag: bool);
|
||||
|
||||
#[method(makeKeyAndVisible)]
|
||||
pub fn makeKeyAndVisible(&self);
|
||||
|
||||
#[method(isKeyWindow)]
|
||||
pub fn isKeyWindow(&self) -> bool;
|
||||
}
|
||||
);
|
||||
@@ -1,31 +1,31 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::{AnyClass, NSObjectProtocol, ProtocolObject};
|
||||
use objc2::{
|
||||
declare_class, extern_methods, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
|
||||
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_ui_kit::{
|
||||
UICoordinateSpace, UIEvent, UIForceTouchCapability, UIGestureRecognizer,
|
||||
UIGestureRecognizerDelegate, UIGestureRecognizerState, UIKeyInput, UIPanGestureRecognizer,
|
||||
UIPinchGestureRecognizer, UIResponder, UIRotationGestureRecognizer, UITapGestureRecognizer,
|
||||
UITextInputTraits, UITouch, UITouchPhase, UITouchType, UITraitEnvironment, UIView,
|
||||
};
|
||||
use objc2_foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject, NSSet};
|
||||
|
||||
use super::app_state::{self, EventWrapper};
|
||||
use super::uikit::{
|
||||
UIEvent, UIForceTouchCapability, UIGestureRecognizer, UIGestureRecognizerDelegate,
|
||||
UIGestureRecognizerState, UIPanGestureRecognizer, UIPinchGestureRecognizer, UIResponder,
|
||||
UIRotationGestureRecognizer, UITapGestureRecognizer, UITouch, UITouchPhase, UITouchType,
|
||||
UITraitCollection, UIView,
|
||||
};
|
||||
use super::window::WinitUIWindow;
|
||||
use crate::dpi::PhysicalPosition;
|
||||
use crate::event::{Event, Force, Touch, TouchPhase, WindowEvent};
|
||||
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::window::{WindowAttributes, WindowId as RootWindowId};
|
||||
|
||||
pub struct WinitViewState {
|
||||
pinch_gesture_recognizer: RefCell<Option<Id<UIPinchGestureRecognizer>>>,
|
||||
doubletap_gesture_recognizer: RefCell<Option<Id<UITapGestureRecognizer>>>,
|
||||
rotation_gesture_recognizer: RefCell<Option<Id<UIRotationGestureRecognizer>>>,
|
||||
pan_gesture_recognizer: RefCell<Option<Id<UIPanGestureRecognizer>>>,
|
||||
pinch_gesture_recognizer: RefCell<Option<Retained<UIPinchGestureRecognizer>>>,
|
||||
doubletap_gesture_recognizer: RefCell<Option<Retained<UITapGestureRecognizer>>>,
|
||||
rotation_gesture_recognizer: RefCell<Option<Retained<UIRotationGestureRecognizer>>>,
|
||||
pan_gesture_recognizer: RefCell<Option<Retained<UIPanGestureRecognizer>>>,
|
||||
|
||||
// for iOS delta references the start of the Gesture
|
||||
rotation_last_delta: Cell<CGFloat>,
|
||||
@@ -39,7 +39,7 @@ declare_class!(
|
||||
unsafe impl ClassType for WinitView {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIView;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitUIView";
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ declare_class!(
|
||||
// Pass -delta so that action is reversed
|
||||
(TouchPhase::Cancelled, -recognizer.scale())
|
||||
}
|
||||
state => panic!("unexpected recognizer state: {:?}", state),
|
||||
state => panic!("unexpected recognizer state: {state:?}"),
|
||||
};
|
||||
|
||||
let gesture_event = EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
@@ -249,7 +249,7 @@ declare_class!(
|
||||
// Pass -delta so that action is reversed
|
||||
(TouchPhase::Cancelled, -recognizer.rotation())
|
||||
}
|
||||
state => panic!("unexpected recognizer state: {:?}", state),
|
||||
state => panic!("unexpected recognizer state: {state:?}"),
|
||||
};
|
||||
|
||||
// Make delta negative to match macos, convert to degrees
|
||||
@@ -270,7 +270,7 @@ declare_class!(
|
||||
fn pan_gesture(&self, recognizer: &UIPanGestureRecognizer) {
|
||||
let window = self.window().unwrap();
|
||||
|
||||
let translation = recognizer.translationInView(self);
|
||||
let translation = recognizer.translationInView(Some(self));
|
||||
|
||||
let (phase, dx, dy) = match recognizer.state() {
|
||||
UIGestureRecognizerState::Began => {
|
||||
@@ -300,7 +300,7 @@ declare_class!(
|
||||
// Pass -delta so that action is reversed
|
||||
(TouchPhase::Cancelled, -last_pan.x, -last_pan.y)
|
||||
}
|
||||
state => panic!("unexpected recognizer state: {:?}", state),
|
||||
state => panic!("unexpected recognizer state: {state:?}"),
|
||||
};
|
||||
|
||||
|
||||
@@ -316,6 +316,11 @@ 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 {}
|
||||
@@ -326,32 +331,35 @@ declare_class!(
|
||||
true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
#[allow(non_snake_case)]
|
||||
unsafe impl WinitView {
|
||||
fn window(&self) -> Option<Id<WinitUIWindow>> {
|
||||
unsafe { msg_send_id![self, window] }
|
||||
unsafe impl UITextInputTraits for WinitView {
|
||||
}
|
||||
|
||||
unsafe impl UIKeyInput for WinitView {
|
||||
#[method(hasText)]
|
||||
fn has_text(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
unsafe fn traitCollection(&self) -> Id<UITraitCollection> {
|
||||
msg_send_id![self, traitCollection]
|
||||
#[method(insertText:)]
|
||||
fn insert_text(&self, text: &NSString) {
|
||||
self.handle_insert_text(text)
|
||||
}
|
||||
|
||||
// TODO: Allow the user to customize this
|
||||
#[method(layerClass)]
|
||||
pub(crate) fn layerClass() -> &'static AnyClass;
|
||||
#[method(deleteBackward)]
|
||||
fn delete_backward(&self) {
|
||||
self.handle_delete_backward()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitView {
|
||||
pub(crate) fn new(
|
||||
_mtm: MainThreadMarker,
|
||||
mtm: MainThreadMarker,
|
||||
window_attributes: &WindowAttributes,
|
||||
frame: CGRect,
|
||||
) -> Id<Self> {
|
||||
let this = Self::alloc().set_ivars(WinitViewState {
|
||||
) -> Retained<Self> {
|
||||
let this = mtm.alloc().set_ivars(WinitViewState {
|
||||
pinch_gesture_recognizer: RefCell::new(None),
|
||||
doubletap_gesture_recognizer: RefCell::new(None),
|
||||
rotation_gesture_recognizer: RefCell::new(None),
|
||||
@@ -361,7 +369,7 @@ impl WinitView {
|
||||
pinch_last_delta: Cell::new(0.0),
|
||||
pan_last_delta: Cell::new(CGPoint { x: 0.0, y: 0.0 }),
|
||||
});
|
||||
let this: Id<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
|
||||
let this: Retained<Self> = unsafe { msg_send_id![super(this), initWithFrame: frame] };
|
||||
|
||||
this.setMultipleTouchEnabled(true);
|
||||
|
||||
@@ -372,13 +380,23 @@ impl WinitView {
|
||||
this
|
||||
}
|
||||
|
||||
fn window(&self) -> Option<Retained<WinitUIWindow>> {
|
||||
// SAFETY: `WinitView`s are always installed in a `WinitUIWindow`
|
||||
(**self).window().map(|window| unsafe { Retained::cast(window) })
|
||||
}
|
||||
|
||||
pub(crate) fn recognize_pinch_gesture(&self, should_recognize: bool) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
if should_recognize {
|
||||
if self.ivars().pinch_gesture_recognizer.borrow().is_none() {
|
||||
let pinch: Id<UIPinchGestureRecognizer> = unsafe {
|
||||
msg_send_id![UIPinchGestureRecognizer::alloc(), initWithTarget: self, action: sel!(pinchGesture:)]
|
||||
let pinch = unsafe {
|
||||
UIPinchGestureRecognizer::initWithTarget_action(
|
||||
mtm.alloc(),
|
||||
Some(self),
|
||||
Some(sel!(pinchGesture:)),
|
||||
)
|
||||
};
|
||||
pinch.setDelegate(ProtocolObject::from_ref(self));
|
||||
pinch.setDelegate(Some(ProtocolObject::from_ref(self)));
|
||||
self.addGestureRecognizer(&pinch);
|
||||
self.ivars().pinch_gesture_recognizer.replace(Some(pinch));
|
||||
}
|
||||
@@ -393,12 +411,17 @@ impl WinitView {
|
||||
minimum_number_of_touches: u8,
|
||||
maximum_number_of_touches: u8,
|
||||
) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
if should_recognize {
|
||||
if self.ivars().pan_gesture_recognizer.borrow().is_none() {
|
||||
let pan: Id<UIPanGestureRecognizer> = unsafe {
|
||||
msg_send_id![UIPanGestureRecognizer::alloc(), initWithTarget: self, action: sel!(panGesture:)]
|
||||
let pan = unsafe {
|
||||
UIPanGestureRecognizer::initWithTarget_action(
|
||||
mtm.alloc(),
|
||||
Some(self),
|
||||
Some(sel!(panGesture:)),
|
||||
)
|
||||
};
|
||||
pan.setDelegate(ProtocolObject::from_ref(self));
|
||||
pan.setDelegate(Some(ProtocolObject::from_ref(self)));
|
||||
pan.setMinimumNumberOfTouches(minimum_number_of_touches as _);
|
||||
pan.setMaximumNumberOfTouches(maximum_number_of_touches as _);
|
||||
self.addGestureRecognizer(&pan);
|
||||
@@ -410,12 +433,17 @@ impl WinitView {
|
||||
}
|
||||
|
||||
pub(crate) fn recognize_doubletap_gesture(&self, should_recognize: bool) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
if should_recognize {
|
||||
if self.ivars().doubletap_gesture_recognizer.borrow().is_none() {
|
||||
let tap: Id<UITapGestureRecognizer> = unsafe {
|
||||
msg_send_id![UITapGestureRecognizer::alloc(), initWithTarget: self, action: sel!(doubleTapGesture:)]
|
||||
let tap = unsafe {
|
||||
UITapGestureRecognizer::initWithTarget_action(
|
||||
mtm.alloc(),
|
||||
Some(self),
|
||||
Some(sel!(doubleTapGesture:)),
|
||||
)
|
||||
};
|
||||
tap.setDelegate(ProtocolObject::from_ref(self));
|
||||
tap.setDelegate(Some(ProtocolObject::from_ref(self)));
|
||||
tap.setNumberOfTapsRequired(2);
|
||||
tap.setNumberOfTouchesRequired(1);
|
||||
self.addGestureRecognizer(&tap);
|
||||
@@ -427,12 +455,17 @@ impl WinitView {
|
||||
}
|
||||
|
||||
pub(crate) fn recognize_rotation_gesture(&self, should_recognize: bool) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
if should_recognize {
|
||||
if self.ivars().rotation_gesture_recognizer.borrow().is_none() {
|
||||
let rotation: Id<UIRotationGestureRecognizer> = unsafe {
|
||||
msg_send_id![UIRotationGestureRecognizer::alloc(), initWithTarget: self, action: sel!(rotationGesture:)]
|
||||
let rotation = unsafe {
|
||||
UIRotationGestureRecognizer::initWithTarget_action(
|
||||
mtm.alloc(),
|
||||
Some(self),
|
||||
Some(sel!(rotationGesture:)),
|
||||
)
|
||||
};
|
||||
rotation.setDelegate(ProtocolObject::from_ref(self));
|
||||
rotation.setDelegate(Some(ProtocolObject::from_ref(self)));
|
||||
self.addGestureRecognizer(&rotation);
|
||||
self.ivars().rotation_gesture_recognizer.replace(Some(rotation));
|
||||
}
|
||||
@@ -447,9 +480,9 @@ impl WinitView {
|
||||
let os_supports_force = app_state::os_capabilities().force_touch;
|
||||
for touch in touches {
|
||||
let logical_location = touch.locationInView(None);
|
||||
let touch_type = touch.type_();
|
||||
let touch_type = touch.r#type();
|
||||
let force = if os_supports_force {
|
||||
let trait_collection = unsafe { self.traitCollection() };
|
||||
let trait_collection = self.traitCollection();
|
||||
let touch_capability = trait_collection.forceTouchCapability();
|
||||
// Both the OS _and_ the device need to be checked for force touch support.
|
||||
if touch_capability == UIForceTouchCapability::Available
|
||||
@@ -482,7 +515,7 @@ impl WinitView {
|
||||
// 2 is UITouchPhase::Stationary and is not expected here
|
||||
UITouchPhase::Ended => TouchPhase::Ended,
|
||||
UITouchPhase::Cancelled => TouchPhase::Cancelled,
|
||||
_ => panic!("unexpected touch phase: {:?}", phase as i32),
|
||||
_ => panic!("unexpected touch phase: {phase:?}"),
|
||||
};
|
||||
|
||||
let physical_location = {
|
||||
@@ -506,4 +539,69 @@ 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,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use objc2::rc::Id;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2_foundation::{MainThreadMarker, NSObject};
|
||||
|
||||
use super::app_state::{self};
|
||||
use super::uikit::{
|
||||
use objc2_ui_kit::{
|
||||
UIDevice, UIInterfaceOrientationMask, UIRectEdge, UIResponder, UIStatusBarStyle,
|
||||
UIUserInterfaceIdiom, UIView, UIViewController,
|
||||
};
|
||||
|
||||
use super::app_state::{self};
|
||||
use crate::platform::ios::{ScreenEdge, StatusBarStyle, ValidOrientations};
|
||||
use crate::window::WindowAttributes;
|
||||
|
||||
@@ -26,7 +26,7 @@ declare_class!(
|
||||
unsafe impl ClassType for WinitViewController {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIViewController;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitUIViewController";
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ impl WinitViewController {
|
||||
mtm: MainThreadMarker,
|
||||
valid_orientations: ValidOrientations,
|
||||
) {
|
||||
let mask = match (valid_orientations, UIDevice::current(mtm).userInterfaceIdiom()) {
|
||||
let mask = match (valid_orientations, UIDevice::currentDevice(mtm).userInterfaceIdiom()) {
|
||||
(ValidOrientations::LandscapeAndPortrait, UIUserInterfaceIdiom::Phone) => {
|
||||
UIInterfaceOrientationMask::AllButUpsideDown
|
||||
},
|
||||
@@ -129,23 +129,24 @@ impl WinitViewController {
|
||||
},
|
||||
};
|
||||
self.ivars().supported_orientations.set(mask);
|
||||
UIViewController::attemptRotationToDeviceOrientation();
|
||||
#[allow(deprecated)]
|
||||
UIViewController::attemptRotationToDeviceOrientation(mtm);
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
mtm: MainThreadMarker,
|
||||
window_attributes: &WindowAttributes,
|
||||
view: &UIView,
|
||||
) -> Id<Self> {
|
||||
) -> Retained<Self> {
|
||||
// These are set properly below, we just to set them to something in the meantime.
|
||||
let this = Self::alloc().set_ivars(ViewControllerState {
|
||||
let this = mtm.alloc().set_ivars(ViewControllerState {
|
||||
prefers_status_bar_hidden: Cell::new(false),
|
||||
preferred_status_bar_style: Cell::new(UIStatusBarStyle::Default),
|
||||
prefers_home_indicator_auto_hidden: Cell::new(false),
|
||||
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::NONE),
|
||||
preferred_screen_edges_deferring_system_gestures: Cell::new(UIRectEdge::empty()),
|
||||
});
|
||||
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
|
||||
this.set_prefers_status_bar_hidden(
|
||||
window_attributes.platform_specific.prefers_status_bar_hidden,
|
||||
|
||||
@@ -2,16 +2,19 @@
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use objc2::rc::Id;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::{AnyObject, NSObject};
|
||||
use objc2::{class, declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2_foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker};
|
||||
use objc2_foundation::{
|
||||
CGFloat, CGPoint, CGRect, CGSize, MainThreadBound, MainThreadMarker, NSObjectProtocol,
|
||||
};
|
||||
use objc2_ui_kit::{
|
||||
UIApplication, UICoordinateSpace, UIResponder, UIScreen, UIScreenOverscanCompensation,
|
||||
UIViewController, UIWindow,
|
||||
};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use super::app_state::EventWrapper;
|
||||
use super::uikit::{
|
||||
UIApplication, UIResponder, UIScreen, UIScreenOverscanCompensation, UIViewController, UIWindow,
|
||||
};
|
||||
use super::view::WinitView;
|
||||
use super::view_controller::WinitViewController;
|
||||
use crate::cursor::Cursor;
|
||||
@@ -35,7 +38,7 @@ declare_class!(
|
||||
unsafe impl ClassType for WinitUIWindow {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
type Super = UIWindow;
|
||||
type Mutability = mutability::InteriorMutable;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitUIWindow";
|
||||
}
|
||||
|
||||
@@ -76,8 +79,8 @@ impl WinitUIWindow {
|
||||
window_attributes: &WindowAttributes,
|
||||
frame: CGRect,
|
||||
view_controller: &UIViewController,
|
||||
) -> Id<Self> {
|
||||
let this: Id<Self> = unsafe { msg_send_id![Self::alloc(), initWithFrame: frame] };
|
||||
) -> Retained<Self> {
|
||||
let this: Retained<Self> = unsafe { msg_send_id![mtm.alloc(), initWithFrame: frame] };
|
||||
|
||||
this.setRootViewController(Some(view_controller));
|
||||
|
||||
@@ -104,9 +107,9 @@ impl WinitUIWindow {
|
||||
}
|
||||
|
||||
pub struct Inner {
|
||||
window: Id<WinitUIWindow>,
|
||||
view_controller: Id<WinitViewController>,
|
||||
view: Id<WinitView>,
|
||||
window: Retained<WinitUIWindow>,
|
||||
view_controller: Retained<WinitViewController>,
|
||||
view: Retained<WinitView>,
|
||||
gl_or_metal_backed: bool,
|
||||
}
|
||||
|
||||
@@ -364,12 +367,24 @@ impl Inner {
|
||||
warn!("`Window::set_ime_cursor_area` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn set_ime_allowed(&self, _allowed: bool) {
|
||||
warn!("`Window::set_ime_allowed` 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_purpose(&self, _purpose: ImePurpose) {
|
||||
warn!("`Window::set_ime_allowed` is ignored on iOS")
|
||||
warn!("`Window::set_ime_purpose` is ignored on iOS")
|
||||
}
|
||||
|
||||
pub fn focus_window(&self) {
|
||||
@@ -394,7 +409,8 @@ impl Inner {
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
Some(MonitorHandle::new(UIScreen::main(MainThreadMarker::new().unwrap())))
|
||||
#[allow(deprecated)]
|
||||
Some(MonitorHandle::new(UIScreen::mainScreen(MainThreadMarker::new().unwrap())))
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WindowId {
|
||||
@@ -404,18 +420,18 @@ impl Inner {
|
||||
#[cfg(feature = "rwh_04")]
|
||||
pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
|
||||
let mut window_handle = rwh_04::UiKitHandle::empty();
|
||||
window_handle.ui_window = Id::as_ptr(&self.window) as _;
|
||||
window_handle.ui_view = Id::as_ptr(&self.view) as _;
|
||||
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
|
||||
window_handle.ui_window = Retained::as_ptr(&self.window) as _;
|
||||
window_handle.ui_view = Retained::as_ptr(&self.view) as _;
|
||||
window_handle.ui_view_controller = Retained::as_ptr(&self.view_controller) as _;
|
||||
rwh_04::RawWindowHandle::UiKit(window_handle)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
||||
let mut window_handle = rwh_05::UiKitWindowHandle::empty();
|
||||
window_handle.ui_window = Id::as_ptr(&self.window) as _;
|
||||
window_handle.ui_view = Id::as_ptr(&self.view) as _;
|
||||
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
|
||||
window_handle.ui_window = Retained::as_ptr(&self.window) as _;
|
||||
window_handle.ui_view = Retained::as_ptr(&self.view) as _;
|
||||
window_handle.ui_view_controller = Retained::as_ptr(&self.view_controller) as _;
|
||||
rwh_05::RawWindowHandle::UiKit(window_handle)
|
||||
}
|
||||
|
||||
@@ -427,11 +443,11 @@ impl Inner {
|
||||
#[cfg(feature = "rwh_06")]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
|
||||
let mut window_handle = rwh_06::UiKitWindowHandle::new({
|
||||
let ui_view = Id::as_ptr(&self.view) as _;
|
||||
std::ptr::NonNull::new(ui_view).expect("Id<T> should never be null")
|
||||
let ui_view = Retained::as_ptr(&self.view) as _;
|
||||
std::ptr::NonNull::new(ui_view).expect("Retained<T> should never be null")
|
||||
});
|
||||
window_handle.ui_view_controller =
|
||||
std::ptr::NonNull::new(Id::as_ptr(&self.view_controller) as _);
|
||||
std::ptr::NonNull::new(Retained::as_ptr(&self.view_controller) as _);
|
||||
rwh_06::RawWindowHandle::UiKit(window_handle)
|
||||
}
|
||||
|
||||
@@ -481,7 +497,8 @@ impl Window {
|
||||
|
||||
// TODO: transparency, visible
|
||||
|
||||
let main_screen = UIScreen::main(mtm);
|
||||
#[allow(deprecated)]
|
||||
let main_screen = UIScreen::mainScreen(mtm);
|
||||
let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
|
||||
let screen = match fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
|
||||
@@ -505,12 +522,8 @@ impl Window {
|
||||
|
||||
let view = WinitView::new(mtm, &window_attributes, frame);
|
||||
|
||||
let gl_or_metal_backed = unsafe {
|
||||
let layer_class = WinitView::layerClass();
|
||||
let is_metal = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
|
||||
let is_gl = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
|
||||
is_metal || is_gl
|
||||
};
|
||||
let gl_or_metal_backed =
|
||||
view.isKindOfClass(class!(CAMetalLayer)) || view.isKindOfClass(class!(CAEAGLLayer));
|
||||
|
||||
let view_controller = WinitViewController::new(mtm, &window_attributes, &view);
|
||||
let window = WinitUIWindow::new(mtm, &window_attributes, frame, &view_controller);
|
||||
@@ -674,10 +687,8 @@ impl Inner {
|
||||
} else {
|
||||
let screen_frame = self.rect_to_screen_space(bounds);
|
||||
let status_bar_frame = {
|
||||
let app = UIApplication::shared(MainThreadMarker::new().unwrap()).expect(
|
||||
"`Window::get_inner_position` cannot be called before `EventLoop::run_app` on \
|
||||
iOS",
|
||||
);
|
||||
let app = UIApplication::sharedApplication(MainThreadMarker::new().unwrap());
|
||||
#[allow(deprecated)]
|
||||
app.statusBarFrame()
|
||||
};
|
||||
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
|
||||
@@ -702,7 +713,7 @@ pub struct WindowId {
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
pub const fn dummy() -> Self {
|
||||
WindowId { window: std::ptr::null_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ pub struct KeyContext<'a> {
|
||||
scratch_buffer: &'a mut Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> KeyContext<'a> {
|
||||
impl KeyContext<'_> {
|
||||
pub fn process_key_event(
|
||||
&mut self,
|
||||
keycode: u32,
|
||||
@@ -320,7 +320,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
|
||||
|
||||
// The current behaviour makes it so composing a character overrides attempts to input a
|
||||
// control character with the `Ctrl` key. We can potentially add a configuration option
|
||||
// if someone specifically wants the oppsite behaviour.
|
||||
// if someone specifically wants the opposite behaviour.
|
||||
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
|
||||
match self.composed_text() {
|
||||
Ok(text) => text,
|
||||
|
||||
@@ -117,6 +117,8 @@ 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>),
|
||||
@@ -127,6 +129,8 @@ 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),
|
||||
@@ -157,7 +161,7 @@ impl From<u64> for WindowId {
|
||||
}
|
||||
|
||||
impl WindowId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
pub const fn dummy() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
@@ -171,11 +175,11 @@ pub enum DeviceId {
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
pub const fn dummy() -> Self {
|
||||
#[cfg(wayland_platform)]
|
||||
return DeviceId::Wayland(unsafe { wayland::DeviceId::dummy() });
|
||||
return DeviceId::Wayland(wayland::DeviceId::dummy());
|
||||
#[cfg(all(not(wayland_platform), x11_platform))]
|
||||
return DeviceId::X(unsafe { x11::DeviceId::dummy() });
|
||||
return DeviceId::X(x11::DeviceId::dummy());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,18 +647,18 @@ pub(crate) enum PlatformCustomCursor {
|
||||
|
||||
/// Hooks for X11 errors.
|
||||
#[cfg(x11_platform)]
|
||||
pub(crate) static mut XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
|
||||
pub(crate) static 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();
|
||||
let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if let Ok(ref xconn) = *xconn_lock {
|
||||
// Call all the hooks.
|
||||
let mut error_handled = false;
|
||||
for hook in unsafe { XLIB_ERROR_HOOKS.lock() }.unwrap().iter() {
|
||||
for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
|
||||
error_handled |= hook(display as *mut _, event as *mut _);
|
||||
}
|
||||
|
||||
@@ -692,6 +696,7 @@ unsafe extern "C" fn x_error_callback(
|
||||
0
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum EventLoop<T: 'static> {
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(Box<wayland::EventLoop<T>>),
|
||||
@@ -764,9 +769,9 @@ impl<T: 'static> EventLoop<T> {
|
||||
// Create the display based on the backend.
|
||||
match backend {
|
||||
#[cfg(wayland_platform)]
|
||||
Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
|
||||
Backend::Wayland => EventLoop::new_wayland_any_thread(),
|
||||
#[cfg(x11_platform)]
|
||||
Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
|
||||
Backend::X => EventLoop::new_x11_any_thread(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -777,14 +782,26 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
fn new_x11_any_thread() -> Result<EventLoop<T>, EventLoopError> {
|
||||
let xconn = match X11_BACKEND.lock().unwrap().as_ref() {
|
||||
let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
|
||||
Ok(xconn) => xconn.clone(),
|
||||
Err(_) => return Err(EventLoopError::NotSupported(NotSupportedError::new())),
|
||||
Err(err) => {
|
||||
return Err(EventLoopError::Os(os_error!(OsError::XNotSupported(err.clone()))))
|
||||
},
|
||||
};
|
||||
|
||||
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_wayland(&self) -> bool {
|
||||
match *self {
|
||||
#[cfg(wayland_platform)]
|
||||
EventLoop::Wayland(_) => true,
|
||||
#[cfg(x11_platform)]
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
|
||||
}
|
||||
@@ -833,6 +850,7 @@ impl<T: 'static> EventLoopProxy<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum ActiveEventLoop {
|
||||
#[cfg(wayland_platform)]
|
||||
Wayland(wayland::ActiveEventLoop),
|
||||
@@ -887,6 +905,11 @@ 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(
|
||||
|
||||
@@ -4,15 +4,21 @@ use std::cell::{Cell, RefCell};
|
||||
use std::io::Result as IOResult;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use calloop::ping::Ping;
|
||||
use rustix::event::{PollFd, PollFlags};
|
||||
use rustix::pipe::{self, PipeFlags};
|
||||
use sctk::reexports::calloop::Error as CalloopError;
|
||||
use sctk::reexports::calloop_wayland_source::WaylandSource;
|
||||
use sctk::reexports::client::{globals, Connection, QueueHandle};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::cursor::OnlyCursorImage;
|
||||
use crate::dpi::LogicalSize;
|
||||
@@ -68,6 +74,8 @@ pub struct EventLoop<T: 'static> {
|
||||
// XXX drop after everything else, just to be safe.
|
||||
/// Calloop's event loop.
|
||||
event_loop: calloop::EventLoop<'static, WinitState>,
|
||||
|
||||
pump_event_notifier: Option<PumpEventNotifier>,
|
||||
}
|
||||
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
@@ -168,6 +176,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
p: PlatformActiveEventLoop::Wayland(window_target),
|
||||
_marker: PhantomData,
|
||||
},
|
||||
pump_event_notifier: None,
|
||||
};
|
||||
|
||||
Ok(event_loop)
|
||||
@@ -223,6 +232,27 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
PumpStatus::Exit(code)
|
||||
} else {
|
||||
// NOTE: spawn a wake-up thread, thus if we have code reading the wayland connection
|
||||
// in parallel to winit, we ensure that the loop itself is marked as having events.
|
||||
if timeout.is_some() && self.pump_event_notifier.is_none() {
|
||||
let awakener = match &self.window_target.p {
|
||||
PlatformActiveEventLoop::Wayland(window_target) => {
|
||||
window_target.event_loop_awakener.clone()
|
||||
},
|
||||
#[cfg(x11_platform)]
|
||||
PlatformActiveEventLoop::X(_) => unreachable!(),
|
||||
};
|
||||
|
||||
self.pump_event_notifier =
|
||||
Some(PumpEventNotifier::spawn(self.connection.clone(), awakener));
|
||||
}
|
||||
|
||||
if let Some(pump_event_notifier) = self.pump_event_notifier.as_ref() {
|
||||
// Notify that we don't have to wait, since we're out of winit.
|
||||
*pump_event_notifier.control.0.lock().unwrap() = PumpEventNotifierAction::Monitor;
|
||||
pump_event_notifier.control.1.notify_one();
|
||||
}
|
||||
|
||||
PumpStatus::Continue
|
||||
}
|
||||
}
|
||||
@@ -285,7 +315,10 @@ impl<T: 'static> EventLoop<T> {
|
||||
|
||||
// Reduce spurious wake-ups.
|
||||
let dispatched_events = self.with_state(|state| state.dispatched_events);
|
||||
if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events {
|
||||
if matches!(cause, StartCause::WaitCancelled { .. })
|
||||
&& !dispatched_events
|
||||
&& timeout.is_none()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -600,7 +633,7 @@ impl<T> AsRawFd for EventLoop<T> {
|
||||
|
||||
pub struct ActiveEventLoop {
|
||||
/// The event loop wakeup source.
|
||||
pub event_loop_awakener: calloop::ping::Ping,
|
||||
pub event_loop_awakener: Ping,
|
||||
|
||||
/// The main queue used by the event loop.
|
||||
pub queue_handle: QueueHandle<WinitState>,
|
||||
@@ -684,3 +717,94 @@ impl ActiveEventLoop {
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PumpEventNotifier {
|
||||
/// Whether we're in winit or not.
|
||||
control: Arc<(Mutex<PumpEventNotifierAction>, Condvar)>,
|
||||
/// Waker handle for the working thread.
|
||||
worker_waker: Option<OwnedFd>,
|
||||
/// Thread handle.
|
||||
handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Drop for PumpEventNotifier {
|
||||
fn drop(&mut self) {
|
||||
// Wake-up the thread.
|
||||
if let Some(worker_waker) = self.worker_waker.as_ref() {
|
||||
let _ = rustix::io::write(worker_waker.as_fd(), &[0u8]);
|
||||
}
|
||||
*self.control.0.lock().unwrap() = PumpEventNotifierAction::Shutdown;
|
||||
self.control.1.notify_one();
|
||||
|
||||
if let Some(handle) = self.handle.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PumpEventNotifier {
|
||||
fn spawn(connection: Connection, awakener: Ping) -> Self {
|
||||
// Start from the waiting state.
|
||||
let control = Arc::new((Mutex::new(PumpEventNotifierAction::Pause), Condvar::new()));
|
||||
let control_thread = Arc::clone(&control);
|
||||
|
||||
let (read, write) = match pipe::pipe_with(PipeFlags::CLOEXEC | PipeFlags::NONBLOCK) {
|
||||
Ok((read, write)) => (read, write),
|
||||
Err(_) => return Self { control, handle: None, worker_waker: None },
|
||||
};
|
||||
|
||||
let handle =
|
||||
std::thread::Builder::new().name(String::from("pump_events mon")).spawn(move || {
|
||||
let (lock, cvar) = &*control_thread;
|
||||
'outer: loop {
|
||||
let mut wait = lock.lock().unwrap();
|
||||
while *wait == PumpEventNotifierAction::Pause {
|
||||
wait = cvar.wait(wait).unwrap();
|
||||
}
|
||||
|
||||
// Exit the loop when we're asked to. Given that we poll
|
||||
// only once we can take the `prepare_read`, but in some cases
|
||||
// it could be not possible, we may block on `join`.
|
||||
if *wait == PumpEventNotifierAction::Shutdown {
|
||||
break 'outer;
|
||||
}
|
||||
|
||||
// Wake-up the main loop and put this one back to sleep.
|
||||
*wait = PumpEventNotifierAction::Pause;
|
||||
drop(wait);
|
||||
|
||||
while let Some(read_guard) = connection.prepare_read() {
|
||||
let _ = connection.flush();
|
||||
let poll_fd = PollFd::from_borrowed_fd(connection.as_fd(), PollFlags::IN);
|
||||
let pipe_poll_fd = PollFd::from_borrowed_fd(read.as_fd(), PollFlags::IN);
|
||||
// Read from the `fd` before going back to poll.
|
||||
if Ok(1) == rustix::io::read(read.as_fd(), &mut [0u8; 1]) {
|
||||
break 'outer;
|
||||
}
|
||||
let _ = rustix::event::poll(&mut [poll_fd, pipe_poll_fd], -1);
|
||||
// Non-blocking read the connection.
|
||||
let _ = read_guard.read_without_dispatch();
|
||||
}
|
||||
|
||||
awakener.ping();
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(err) = handle.as_ref().err() {
|
||||
warn!("failed to spawn pump_events wake-up thread: {err}");
|
||||
}
|
||||
|
||||
PumpEventNotifier { control, handle: handle.ok(), worker_waker: Some(write) }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum PumpEventNotifierAction {
|
||||
/// Monitor the wayland queue.
|
||||
Monitor,
|
||||
/// Pause monitoring.
|
||||
Pause,
|
||||
/// Shutdown the thread.
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(wayland_platform)]
|
||||
|
||||
//! Winit's Wayland backend.
|
||||
|
||||
use std::fmt::Display;
|
||||
@@ -68,7 +66,7 @@ impl From<WaylandError> for OsError {
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
pub const fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ 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};
|
||||
|
||||
@@ -33,7 +32,17 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
) {
|
||||
let seat_state = match state.seats.get_mut(&data.seat.id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => return,
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
match event {
|
||||
@@ -43,7 +52,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
warn!("non-xkb compatible keymap")
|
||||
},
|
||||
WlKeymapFormat::XkbV1 => {
|
||||
let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
|
||||
let context = &mut keyboard_state.xkb_context;
|
||||
context.set_keymap_from_fd(fd, size as usize);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
@@ -67,7 +76,6 @@ 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);
|
||||
@@ -91,9 +99,8 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
WlKeyboardEvent::Leave { surface, .. } => {
|
||||
let window_id = wayland::make_wid(&surface);
|
||||
|
||||
// NOTE: we should drop the repeat regardless whethere it was for the present
|
||||
// NOTE: we should drop the repeat regardless whether 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);
|
||||
@@ -128,7 +135,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
let key = key + 8;
|
||||
|
||||
key_input(
|
||||
seat_state,
|
||||
keyboard_state,
|
||||
&mut state.events_sink,
|
||||
data,
|
||||
key,
|
||||
@@ -136,7 +143,6 @@ 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,
|
||||
@@ -163,18 +169,25 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
state.dispatched_events = true;
|
||||
|
||||
let data = wl_keyboard.data::<KeyboardData>().unwrap();
|
||||
let seat_state = state.seats.get_mut(&data.seat.id()).unwrap();
|
||||
let seat_state = match state.seats.get_mut(&data.seat.id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
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,
|
||||
};
|
||||
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,
|
||||
};
|
||||
|
||||
key_input(
|
||||
seat_state,
|
||||
keyboard_state,
|
||||
&mut state.events_sink,
|
||||
data,
|
||||
repeat_keycode,
|
||||
@@ -183,7 +196,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
);
|
||||
|
||||
// NOTE: the gap could change dynamically while repeat is going.
|
||||
match seat_state.keyboard_state.as_ref().unwrap().repeat_info {
|
||||
match keyboard_state.repeat_info {
|
||||
RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap),
|
||||
RepeatInfo::Disable => TimeoutAction::Drop,
|
||||
}
|
||||
@@ -194,7 +207,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
let key = key + 8;
|
||||
|
||||
key_input(
|
||||
seat_state,
|
||||
keyboard_state,
|
||||
&mut state.events_sink,
|
||||
data,
|
||||
key,
|
||||
@@ -202,7 +215,6 @@ 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
|
||||
@@ -216,7 +228,7 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
WlKeyboardEvent::Modifiers {
|
||||
mods_depressed, mods_latched, mods_locked, group, ..
|
||||
} => {
|
||||
let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
|
||||
let xkb_context = &mut keyboard_state.xkb_context;
|
||||
let xkb_state = match xkb_context.state_mut() {
|
||||
Some(state) => state,
|
||||
None => return,
|
||||
@@ -240,7 +252,6 @@ 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;
|
||||
@@ -348,7 +359,7 @@ impl KeyboardData {
|
||||
}
|
||||
|
||||
fn key_input(
|
||||
seat_state: &mut WinitSeatState,
|
||||
keyboard_state: &mut KeyboardState,
|
||||
event_sink: &mut EventSink,
|
||||
data: &KeyboardData,
|
||||
keycode: u32,
|
||||
@@ -360,8 +371,6 @@ 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);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use ahash::AHashMap;
|
||||
use tracing::warn;
|
||||
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
@@ -76,7 +77,13 @@ impl SeatHandler for WinitState {
|
||||
seat: WlSeat,
|
||||
capability: SeatCapability,
|
||||
) {
|
||||
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
match capability {
|
||||
SeatCapability::Touch if seat_state.touch.is_none() => {
|
||||
@@ -89,8 +96,12 @@ impl SeatHandler for WinitState {
|
||||
},
|
||||
SeatCapability::Pointer if seat_state.pointer.is_none() => {
|
||||
let surface = self.compositor_state.create_surface(queue_handle);
|
||||
let viewport = self
|
||||
.viewporter_state
|
||||
.as_ref()
|
||||
.map(|state| state.get_viewport(&surface, queue_handle));
|
||||
let surface_id = surface.id();
|
||||
let pointer_data = WinitPointerData::new(seat.clone());
|
||||
let pointer_data = WinitPointerData::new(seat.clone(), viewport);
|
||||
let themed_pointer = self
|
||||
.seat_state
|
||||
.get_pointer_with_theme_and_data(
|
||||
@@ -139,7 +150,13 @@ impl SeatHandler for WinitState {
|
||||
seat: WlSeat,
|
||||
capability: SeatCapability,
|
||||
) {
|
||||
let seat_state = self.seats.get_mut(&seat.id()).unwrap();
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(text_input) = seat_state.text_input.take() {
|
||||
text_input.destroy();
|
||||
|
||||
@@ -4,6 +4,8 @@ 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;
|
||||
@@ -16,6 +18,7 @@ use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_ma
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::csd_frame::FrameClick;
|
||||
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
|
||||
|
||||
use sctk::compositor::SurfaceData;
|
||||
use sctk::globals::GlobalData;
|
||||
@@ -41,7 +44,21 @@ impl PointerHandler for WinitState {
|
||||
events: &[PointerEvent],
|
||||
) {
|
||||
let seat = pointer.winit_data().seat();
|
||||
let seat_state = self.seats.get(&seat.id()).unwrap();
|
||||
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 device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
|
||||
|
||||
@@ -78,9 +95,7 @@ impl PointerHandler for WinitState {
|
||||
event.position.0,
|
||||
event.position.1,
|
||||
) {
|
||||
if let Some(pointer) = seat_state.pointer.as_ref() {
|
||||
let _ = pointer.set_cursor(connection, icon);
|
||||
}
|
||||
let _ = themed_pointer.set_cursor(connection, icon);
|
||||
}
|
||||
},
|
||||
PointerEventKind::Leave { .. } if parent_surface != surface => {
|
||||
@@ -113,9 +128,7 @@ impl PointerHandler for WinitState {
|
||||
self.events_sink
|
||||
.push_window_event(WindowEvent::CursorEntered { device_id }, window_id);
|
||||
|
||||
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
|
||||
window.pointer_entered(pointer);
|
||||
}
|
||||
window.pointer_entered(Arc::downgrade(themed_pointer));
|
||||
|
||||
// Set the currently focused surface.
|
||||
pointer.winit_data().inner.lock().unwrap().surface = Some(window_id);
|
||||
@@ -126,9 +139,7 @@ impl PointerHandler for WinitState {
|
||||
);
|
||||
},
|
||||
PointerEventKind::Leave { .. } => {
|
||||
if let Some(pointer) = seat_state.pointer.as_ref().map(Arc::downgrade) {
|
||||
window.pointer_left(pointer);
|
||||
}
|
||||
window.pointer_left(Arc::downgrade(themed_pointer));
|
||||
|
||||
// Remove the active surface.
|
||||
pointer.winit_data().inner.lock().unwrap().surface = None;
|
||||
@@ -183,15 +194,15 @@ impl PointerHandler for WinitState {
|
||||
pointer_data.phase = phase;
|
||||
|
||||
// Mice events have both pixel and discrete delta's at the same time. So prefer
|
||||
// the descrite values if they are present.
|
||||
// the discrete values if they are present.
|
||||
let delta = if has_discrete_scroll {
|
||||
// XXX Wayland sign convention is the inverse of winit.
|
||||
// NOTE: Wayland sign convention is the inverse of winit.
|
||||
MouseScrollDelta::LineDelta(
|
||||
(-horizontal.discrete) as f32,
|
||||
(-vertical.discrete) as f32,
|
||||
)
|
||||
} else {
|
||||
// XXX Wayland sign convention is the inverse of winit.
|
||||
// NOTE: Wayland sign convention is the inverse of winit.
|
||||
MouseScrollDelta::PixelDelta(
|
||||
LogicalPosition::new(-horizontal.absolute, -vertical.absolute)
|
||||
.to_physical(scale_factor),
|
||||
@@ -215,13 +226,17 @@ pub struct WinitPointerData {
|
||||
|
||||
/// The data required by the sctk.
|
||||
sctk_data: PointerData,
|
||||
|
||||
/// Viewport for fractional cursor.
|
||||
viewport: Option<WpViewport>,
|
||||
}
|
||||
|
||||
impl WinitPointerData {
|
||||
pub fn new(seat: WlSeat) -> Self {
|
||||
pub fn new(seat: WlSeat, viewport: Option<WpViewport>) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(WinitPointerDataInner::default()),
|
||||
sctk_data: PointerData::new(seat),
|
||||
viewport,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +317,18 @@ impl WinitPointerData {
|
||||
locked_pointer.set_cursor_position_hint(surface_x, surface_y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn viewport(&self) -> Option<&WpViewport> {
|
||||
self.viewport.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WinitPointerData {
|
||||
fn drop(&mut self) {
|
||||
if let Some(viewport) = self.viewport.take() {
|
||||
viewport.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerDataExt for WinitPointerData {
|
||||
|
||||
@@ -121,11 +121,15 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Clear preedit at the start of `Done`.
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
window_id,
|
||||
);
|
||||
// Clear preedit, unless all we'll be doing next is sending a new preedit.
|
||||
if text_input_data.pending_commit.is_some()
|
||||
|| text_input_data.pending_preedit.is_none()
|
||||
{
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
// Send `Commit`.
|
||||
if let Some(text) = text_input_data.pending_commit.take() {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! 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;
|
||||
@@ -31,11 +33,16 @@ impl TouchHandler for WinitState {
|
||||
None => return,
|
||||
};
|
||||
|
||||
let location = LogicalPosition::<f64>::from(position);
|
||||
|
||||
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
// 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(
|
||||
@@ -61,7 +68,13 @@ impl TouchHandler for WinitState {
|
||||
_: u32,
|
||||
id: i32,
|
||||
) {
|
||||
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
// Remove the touch point.
|
||||
let touch_point = match seat_state.touch_map.remove(&id) {
|
||||
@@ -98,7 +111,13 @@ impl TouchHandler for WinitState {
|
||||
id: i32,
|
||||
position: (f64, f64),
|
||||
) {
|
||||
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
// Remove the touch point.
|
||||
let touch_point = match seat_state.touch_map.get_mut(&id) {
|
||||
@@ -129,7 +148,13 @@ impl TouchHandler for WinitState {
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &Connection, _: &QueueHandle<Self>, touch: &WlTouch) {
|
||||
let seat_state = self.seats.get_mut(&touch.seat().id()).unwrap();
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
for (id, touch_point) in seat_state.touch_map.drain() {
|
||||
let window_id = wayland::make_wid(&touch_point.surface);
|
||||
|
||||
@@ -339,12 +339,30 @@ impl CompositorHandler for WinitState {
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_surface::WlSurface,
|
||||
_: &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,
|
||||
|
||||
@@ -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::_new(token),
|
||||
token: ActivationToken::from_raw(token),
|
||||
},
|
||||
*window_id,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! The Wayland window.
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -168,7 +170,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.
|
||||
@@ -223,6 +225,10 @@ impl Window {
|
||||
window_events_sink,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
|
||||
NonNull::new(self.window.xdg_toplevel().id().as_ptr().cast())
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
|
||||
@@ -31,7 +31,7 @@ use sctk::subcompositor::SubcompositorState;
|
||||
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur::OrgKdeKwinBlur;
|
||||
|
||||
use crate::cursor::CustomCursor as RootCustomCursor;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize, Size};
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError};
|
||||
use crate::platform_impl::wayland::logical_to_physical_rounded;
|
||||
use crate::platform_impl::wayland::types::cursor::{CustomCursor, SelectedCursor};
|
||||
@@ -222,9 +222,9 @@ impl WindowState {
|
||||
}
|
||||
|
||||
/// Apply closure on the given pointer.
|
||||
fn apply_on_pointer<F: Fn(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
|
||||
fn apply_on_pointer<F: FnMut(&ThemedPointer<WinitPointerData>, &WinitPointerData)>(
|
||||
&self,
|
||||
callback: F,
|
||||
mut callback: F,
|
||||
) {
|
||||
self.pointers.iter().filter_map(Weak::upgrade).for_each(|pointer| {
|
||||
let data = pointer.pointer().winit_data();
|
||||
@@ -726,17 +726,26 @@ impl WindowState {
|
||||
}
|
||||
|
||||
fn apply_custom_cursor(&self, cursor: &CustomCursor) {
|
||||
self.apply_on_pointer(|pointer, _| {
|
||||
self.apply_on_pointer(|pointer, data| {
|
||||
let surface = pointer.surface();
|
||||
|
||||
let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
|
||||
let scale = if let Some(viewport) = data.viewport() {
|
||||
let scale = self.scale_factor();
|
||||
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
|
||||
viewport.set_destination(size.width, size.height);
|
||||
scale
|
||||
} else {
|
||||
let scale = surface.data::<SurfaceData>().unwrap().surface_data().scale_factor();
|
||||
surface.set_buffer_scale(scale);
|
||||
scale as f64
|
||||
};
|
||||
|
||||
surface.set_buffer_scale(scale);
|
||||
surface.attach(Some(cursor.buffer.wl_buffer()), 0, 0);
|
||||
if surface.version() >= 4 {
|
||||
surface.damage_buffer(0, 0, cursor.w, cursor.h);
|
||||
} else {
|
||||
surface.damage(0, 0, cursor.w / scale, cursor.h / scale);
|
||||
let size = PhysicalSize::new(cursor.w, cursor.h).to_logical(scale);
|
||||
surface.damage(0, 0, size.width, size.height);
|
||||
}
|
||||
surface.commit();
|
||||
|
||||
@@ -746,12 +755,9 @@ impl WindowState {
|
||||
.and_then(|data| data.pointer_data().latest_enter_serial())
|
||||
.unwrap();
|
||||
|
||||
pointer.pointer().set_cursor(
|
||||
serial,
|
||||
Some(surface),
|
||||
cursor.hotspot_x / scale,
|
||||
cursor.hotspot_y / scale,
|
||||
);
|
||||
let hotspot =
|
||||
PhysicalPosition::new(cursor.hotspot_x, cursor.hotspot_y).to_logical(scale);
|
||||
pointer.pointer().set_cursor(serial, Some(surface), hotspot.x, hotspot.y);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -827,34 +833,51 @@ impl WindowState {
|
||||
None => return Err(ExternalError::NotSupported(NotSupportedError::new())),
|
||||
};
|
||||
|
||||
// Replace the current mode.
|
||||
let old_mode = std::mem::replace(&mut self.cursor_grab_mode.current_grab_mode, mode);
|
||||
|
||||
match old_mode {
|
||||
CursorGrabMode::None => (),
|
||||
let mut unset_old = false;
|
||||
match self.cursor_grab_mode.current_grab_mode {
|
||||
CursorGrabMode::None => unset_old = true,
|
||||
CursorGrabMode::Confined => self.apply_on_pointer(|_, data| {
|
||||
data.unconfine_pointer();
|
||||
unset_old = true;
|
||||
}),
|
||||
CursorGrabMode::Locked => {
|
||||
self.apply_on_pointer(|_, data| data.unlock_pointer());
|
||||
self.apply_on_pointer(|_, data| {
|
||||
data.unlock_pointer();
|
||||
unset_old = true;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// In case we haven't unset the old mode, it means that we don't have a cursor above
|
||||
// the window, thus just wait for it to re-appear.
|
||||
if !unset_old {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut set_mode = false;
|
||||
let surface = self.window.wl_surface();
|
||||
match mode {
|
||||
CursorGrabMode::Locked => self.apply_on_pointer(|pointer, data| {
|
||||
let pointer = pointer.pointer();
|
||||
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
|
||||
data.lock_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
|
||||
set_mode = true;
|
||||
}),
|
||||
CursorGrabMode::Confined => self.apply_on_pointer(|pointer, data| {
|
||||
let pointer = pointer.pointer();
|
||||
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle)
|
||||
data.confine_pointer(pointer_constraints, surface, pointer, &self.queue_handle);
|
||||
set_mode = true;
|
||||
}),
|
||||
CursorGrabMode::None => {
|
||||
// Current lock/confine was already removed.
|
||||
set_mode = true;
|
||||
},
|
||||
}
|
||||
|
||||
// Replace the current grab mode after we've ensure that it got updated.
|
||||
if set_mode {
|
||||
self.cursor_grab_mode.current_grab_mode = mode;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -165,14 +165,14 @@ fn push_display(buffer: &mut Vec<u8>, display: &impl std::fmt::Display) {
|
||||
buffer: &'a mut Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Write for Writer<'a> {
|
||||
impl std::fmt::Write for Writer<'_> {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
self.buffer.extend_from_slice(s.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
write!(Writer { buffer }, "{}", display).unwrap();
|
||||
write!(Writer { buffer }, "{display}").unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -66,7 +66,10 @@ pub struct EventProcessor {
|
||||
pub active_window: Option<xproto::Window>,
|
||||
/// Latest modifiers we've sent for the user to trigger change in event.
|
||||
pub modifiers: Cell<ModifiersState>,
|
||||
pub xfiltered_modifiers: VecDeque<c_ulong>,
|
||||
// Track modifiers based on keycodes. NOTE: that serials generally don't work for tracking
|
||||
// since they are not unique and could be duplicated in case of sequence of key events is
|
||||
// delivered at near the same time.
|
||||
pub xfiltered_modifiers: VecDeque<u8>,
|
||||
pub xmodmap: util::ModifierKeymap,
|
||||
pub is_composing: bool,
|
||||
}
|
||||
@@ -129,6 +132,7 @@ 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 {
|
||||
@@ -145,20 +149,38 @@ impl EventProcessor {
|
||||
{
|
||||
let event_type = xev.get_type();
|
||||
|
||||
if self.filter_event(xev) {
|
||||
if event_type == xlib::KeyPress || event_type == xlib::KeyRelease {
|
||||
// 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 {
|
||||
let xev: &XKeyEvent = xev.as_ref();
|
||||
if self.xmodmap.is_modifier(xev.keycode as u8) {
|
||||
// Don't grow the buffer past the `MAX_MOD_REPLAY_LEN`. This could happen
|
||||
// when the modifiers are consumed entirely or serials are altered.
|
||||
//
|
||||
// Both cases shouldn't happen in well behaving clients.
|
||||
// when the modifiers are consumed entirely.
|
||||
if self.xfiltered_modifiers.len() == MAX_MOD_REPLAY_LEN {
|
||||
self.xfiltered_modifiers.pop_back();
|
||||
}
|
||||
self.xfiltered_modifiers.push_front(xev.serial);
|
||||
self.xfiltered_modifiers.push_front(xev.keycode as u8);
|
||||
}
|
||||
}
|
||||
|
||||
filtered
|
||||
} else {
|
||||
self.filter_event(xev)
|
||||
};
|
||||
|
||||
// Don't process event if it was filtered.
|
||||
if filtered {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -434,7 +456,7 @@ impl EventProcessor {
|
||||
let flags = xev.data.get_long(1);
|
||||
let version = flags >> 24;
|
||||
self.dnd.version = Some(version);
|
||||
let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1;
|
||||
let has_more_types = flags - (flags & (c_long::MAX - 1)) == 1;
|
||||
if !has_more_types {
|
||||
let type_list = vec![
|
||||
xev.data.get_long(2) as xproto::Atom,
|
||||
@@ -929,7 +951,7 @@ impl EventProcessor {
|
||||
// itself are out of sync due to XkbState being delivered before XKeyEvent, since it's
|
||||
// being replayed by the XIM, thus we should replay ourselves.
|
||||
let replay = if let Some(position) =
|
||||
self.xfiltered_modifiers.iter().rev().position(|&s| s == xev.serial)
|
||||
self.xfiltered_modifiers.iter().rev().position(|&s| s == xev.keycode as u8)
|
||||
{
|
||||
// We don't have to replay modifiers pressed before the current event if some events
|
||||
// were not forwarded to us, since their state is irrelevant.
|
||||
@@ -1424,7 +1446,7 @@ impl EventProcessor {
|
||||
if !xinput2::XIMaskIsSet(mask, i) {
|
||||
continue;
|
||||
}
|
||||
let x = unsafe { *value };
|
||||
let x = unsafe { value.read_unaligned() };
|
||||
|
||||
// We assume that every XInput2 device with analog axes is a pointing device emitting
|
||||
// relative coordinates.
|
||||
|
||||
@@ -123,19 +123,15 @@ unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
||||
let is_allowed =
|
||||
old_context.as_ref().map(|old_context| old_context.is_allowed()).unwrap_or_default();
|
||||
|
||||
// We can't use the style from the old context here, since it may change on reload, so
|
||||
// pick style from the new XIM based on the old state.
|
||||
let style = if is_allowed { new_im.preedit_style } else { new_im.none_style };
|
||||
|
||||
let new_context = {
|
||||
let result = unsafe {
|
||||
ImeContext::new(
|
||||
xconn,
|
||||
new_im.im,
|
||||
style,
|
||||
&new_im,
|
||||
*window,
|
||||
spot,
|
||||
(*inner).event_sender.clone(),
|
||||
is_allowed,
|
||||
)
|
||||
};
|
||||
if result.is_err() {
|
||||
|
||||
@@ -5,10 +5,9 @@ use std::{mem, ptr};
|
||||
|
||||
use x11_dl::xlib::{XIMCallback, XIMPreeditCaretCallbackStruct, XIMPreeditDrawCallbackStruct};
|
||||
|
||||
use crate::platform_impl::platform::x11::ime::input_method::{Style, XIMStyle};
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
use crate::platform_impl::platform::x11::ime::input_method::{InputMethod, Style, XIMStyle};
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventSender};
|
||||
|
||||
/// IME creation error.
|
||||
#[derive(Debug)]
|
||||
@@ -158,7 +157,9 @@ struct PreeditCallbacks {
|
||||
impl PreeditCallbacks {
|
||||
pub fn new(client_data: ffi::XPointer) -> PreeditCallbacks {
|
||||
let start_callback = create_xim_callback(client_data, unsafe {
|
||||
mem::transmute(preedit_start_callback as usize)
|
||||
mem::transmute::<usize, unsafe extern "C" fn(ffi::XIM, ffi::XPointer, ffi::XPointer)>(
|
||||
preedit_start_callback as usize,
|
||||
)
|
||||
});
|
||||
let done_callback = create_xim_callback(client_data, preedit_done_callback);
|
||||
let caret_callback = create_xim_callback(client_data, preedit_caret_callback);
|
||||
@@ -182,7 +183,7 @@ struct ImeContextClientData {
|
||||
pub struct ImeContext {
|
||||
pub(crate) ic: ffi::XIC,
|
||||
pub(crate) ic_spot: ffi::XPoint,
|
||||
pub(crate) style: Style,
|
||||
pub(crate) allowed: bool,
|
||||
// Since the data is passed shared between X11 XIM callbacks, but couldn't be directly free
|
||||
// from there we keep the pointer to automatically deallocate it.
|
||||
_client_data: Box<ImeContextClientData>,
|
||||
@@ -191,11 +192,11 @@ pub struct ImeContext {
|
||||
impl ImeContext {
|
||||
pub(crate) unsafe fn new(
|
||||
xconn: &Arc<XConnection>,
|
||||
im: ffi::XIM,
|
||||
style: Style,
|
||||
im: &InputMethod,
|
||||
window: ffi::Window,
|
||||
ic_spot: Option<ffi::XPoint>,
|
||||
event_sender: ImeEventSender,
|
||||
allowed: bool,
|
||||
) -> Result<Self, ImeContextCreationError> {
|
||||
let client_data = Box::into_raw(Box::new(ImeContextClientData {
|
||||
window,
|
||||
@@ -204,20 +205,24 @@ impl ImeContext {
|
||||
cursor_pos: 0,
|
||||
}));
|
||||
|
||||
let style = if allowed { im.preedit_style } else { im.none_style };
|
||||
|
||||
let ic = match style as _ {
|
||||
Style::Preedit(style) => unsafe {
|
||||
ImeContext::create_preedit_ic(
|
||||
xconn,
|
||||
im,
|
||||
im.im,
|
||||
style,
|
||||
window,
|
||||
client_data as ffi::XPointer,
|
||||
)
|
||||
},
|
||||
Style::Nothing(style) => unsafe {
|
||||
ImeContext::create_nothing_ic(xconn, im, style, window)
|
||||
ImeContext::create_nothing_ic(xconn, im.im, style, window)
|
||||
},
|
||||
Style::None(style) => unsafe {
|
||||
ImeContext::create_none_ic(xconn, im.im, style, window)
|
||||
},
|
||||
Style::None(style) => unsafe { ImeContext::create_none_ic(xconn, im, style, window) },
|
||||
}
|
||||
.ok_or(ImeContextCreationError::Null)?;
|
||||
|
||||
@@ -226,7 +231,7 @@ impl ImeContext {
|
||||
let mut context = ImeContext {
|
||||
ic,
|
||||
ic_spot: ffi::XPoint { x: 0, y: 0 },
|
||||
style,
|
||||
allowed,
|
||||
_client_data: unsafe { Box::from_raw(client_data) },
|
||||
};
|
||||
|
||||
@@ -333,7 +338,7 @@ impl ImeContext {
|
||||
}
|
||||
|
||||
pub fn is_allowed(&self) -> bool {
|
||||
!matches!(self.style, Style::None(_))
|
||||
self.allowed
|
||||
}
|
||||
|
||||
// Set the spot for preedit text. Setting spot isn't working with libX11 when preedit callbacks
|
||||
|
||||
@@ -176,7 +176,7 @@ unsafe fn get_xim_servers(xconn: &Arc<XConnection>) -> Result<Vec<String>, GetXi
|
||||
)
|
||||
.map_err(GetXimServersError::GetPropertyError)?
|
||||
.into_iter()
|
||||
.map(ffi::Atom::from)
|
||||
.map(|atom| atom as _)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len());
|
||||
|
||||
@@ -10,15 +10,13 @@ use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::debug;
|
||||
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
use self::callbacks::*;
|
||||
use self::context::ImeContext;
|
||||
pub use self::context::ImeContextCreationError;
|
||||
use self::inner::{close_im, ImeInner};
|
||||
use self::input_method::{PotentialInputMethods, Style};
|
||||
use self::input_method::PotentialInputMethods;
|
||||
use super::{ffi, util, XConnection, XError};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
@@ -114,39 +112,26 @@ impl Ime {
|
||||
pub fn create_context(
|
||||
&mut self,
|
||||
window: ffi::Window,
|
||||
with_preedit: bool,
|
||||
with_ime: bool,
|
||||
) -> Result<bool, ImeContextCreationError> {
|
||||
let context = if self.is_destroyed() {
|
||||
// Create empty entry in map, so that when IME is rebuilt, this window has a context.
|
||||
None
|
||||
} else {
|
||||
let im = self.inner.im.as_ref().unwrap();
|
||||
let style = if with_preedit { im.preedit_style } else { im.none_style };
|
||||
|
||||
let context = unsafe {
|
||||
ImeContext::new(
|
||||
&self.inner.xconn,
|
||||
im.im,
|
||||
style,
|
||||
im,
|
||||
window,
|
||||
None,
|
||||
self.inner.event_sender.clone(),
|
||||
with_ime,
|
||||
)?
|
||||
};
|
||||
|
||||
// Check the state on the context, since it could fail to enable or disable preedit.
|
||||
let event = if matches!(style, Style::None(_)) {
|
||||
if with_preedit {
|
||||
debug!("failed to create IME context with preedit support.")
|
||||
}
|
||||
ImeEvent::Disabled
|
||||
} else {
|
||||
if !with_preedit {
|
||||
debug!("failed to create IME context without preedit support.")
|
||||
}
|
||||
ImeEvent::Enabled
|
||||
};
|
||||
|
||||
let event = if context.is_allowed() { ImeEvent::Enabled } else { ImeEvent::Disabled };
|
||||
self.inner.event_sender.send((window, event)).expect("Failed to send enabled event");
|
||||
|
||||
Some(context)
|
||||
@@ -226,6 +211,16 @@ 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 {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(x11_platform)]
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::ffi::CStr;
|
||||
@@ -499,6 +497,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
// If we don't have any pending `_receiver`
|
||||
if !self.has_pending()
|
||||
&& !matches!(&cause, StartCause::ResumeTimeReached { .. } | StartCause::Poll)
|
||||
&& timeout.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -533,7 +532,7 @@ impl<T: 'static> EventLoop<T> {
|
||||
window_id: crate::window::WindowId(window_id),
|
||||
event: WindowEvent::ActivationTokenDone {
|
||||
serial,
|
||||
token: crate::window::ActivationToken::_new(token),
|
||||
token: crate::window::ActivationToken::from_raw(token),
|
||||
},
|
||||
};
|
||||
callback(event, &self.event_processor.target)
|
||||
@@ -753,14 +752,14 @@ impl<'a> DeviceInfo<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for DeviceInfo<'a> {
|
||||
impl Drop for DeviceInfo<'_> {
|
||||
fn drop(&mut self) {
|
||||
assert!(!self.info.is_null());
|
||||
unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for DeviceInfo<'a> {
|
||||
impl Deref for DeviceInfo<'_> {
|
||||
type Target = [ffi::XIDeviceInfo];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -773,7 +772,7 @@ pub struct DeviceId(xinput::DeviceId);
|
||||
|
||||
impl DeviceId {
|
||||
#[allow(unused)]
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
pub const fn dummy() -> Self {
|
||||
DeviceId(0)
|
||||
}
|
||||
}
|
||||
@@ -851,24 +850,24 @@ pub enum X11Error {
|
||||
impl fmt::Display for X11Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
X11Error::Xlib(e) => write!(f, "Xlib error: {}", e),
|
||||
X11Error::Connect(e) => write!(f, "X11 connection error: {}", e),
|
||||
X11Error::Connection(e) => write!(f, "X11 connection error: {}", e),
|
||||
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {}", e),
|
||||
X11Error::GetProperty(e) => write!(f, "Failed to get X property {}", e),
|
||||
X11Error::X11(e) => write!(f, "X11 error: {:?}", e),
|
||||
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {}", s),
|
||||
X11Error::Xlib(e) => write!(f, "Xlib error: {e}"),
|
||||
X11Error::Connect(e) => write!(f, "X11 connection error: {e}"),
|
||||
X11Error::Connection(e) => write!(f, "X11 connection error: {e}"),
|
||||
X11Error::XidsExhausted(e) => write!(f, "XID range exhausted: {e}"),
|
||||
X11Error::GetProperty(e) => write!(f, "Failed to get X property {e}"),
|
||||
X11Error::X11(e) => write!(f, "X11 error: {e:?}"),
|
||||
X11Error::UnexpectedNull(s) => write!(f, "Xlib function returned null: {s}"),
|
||||
X11Error::InvalidActivationToken(s) => write!(
|
||||
f,
|
||||
"Invalid activation token: {}",
|
||||
std::str::from_utf8(s).unwrap_or("<invalid utf8>")
|
||||
),
|
||||
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {}", s),
|
||||
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {s}"),
|
||||
X11Error::NoSuchVisual(visualid) => {
|
||||
write!(f, "Could not find a matching X11 visual for ID `{:x}`", visualid)
|
||||
write!(f, "Could not find a matching X11 visual for ID `{visualid:x}`")
|
||||
},
|
||||
X11Error::XsettingsParse(err) => {
|
||||
write!(f, "Failed to parse xsettings: {:?}", err)
|
||||
write!(f, "Failed to parse xsettings: {err:?}")
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -959,7 +958,7 @@ trait CookieResultExt {
|
||||
fn expect_then_ignore_error(self, msg: &str);
|
||||
}
|
||||
|
||||
impl<'a, E: fmt::Debug> CookieResultExt for Result<VoidCookie<'a>, E> {
|
||||
impl<E: fmt::Debug> CookieResultExt for Result<VoidCookie<'_>, E> {
|
||||
fn expect_then_ignore_error(self, msg: &str) {
|
||||
self.expect(msg).ignore_error()
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ impl XConnection {
|
||||
let info = self
|
||||
.xcb_connection()
|
||||
.extension_information(randr::X11_EXTENSION_NAME)?
|
||||
.ok_or_else(|| X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
|
||||
.ok_or(X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
|
||||
|
||||
// Select input data.
|
||||
let event_mask =
|
||||
|
||||
@@ -19,7 +19,7 @@ impl<'a, T> XSmartPointer<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for XSmartPointer<'a, T> {
|
||||
impl<T> Deref for XSmartPointer<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
@@ -27,13 +27,13 @@ impl<'a, T> Deref for XSmartPointer<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for XSmartPointer<'a, T> {
|
||||
impl<T> DerefMut for XSmartPointer<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for XSmartPointer<'a, T> {
|
||||
impl<T> Drop for XSmartPointer<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFree)(self.ptr as *mut _);
|
||||
|
||||
@@ -51,7 +51,7 @@ where
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
// This is impoartant, so pay attention!
|
||||
// This is important, so pay attention!
|
||||
// Xlib has an output buffer, and tries to hide the async nature of X from you.
|
||||
// This buffer contains the requests you make, and is flushed under various circumstances:
|
||||
// 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed"
|
||||
|
||||
@@ -79,7 +79,7 @@ impl XConnection {
|
||||
.iter()
|
||||
// XRROutputInfo contains an array of mode ids that correspond to
|
||||
// modes in the array in XRRScreenResources
|
||||
.filter(|x| output_modes.iter().any(|id| x.id == *id))
|
||||
.filter(|x| output_modes.contains(&x.id))
|
||||
.map(|mode| {
|
||||
VideoModeHandle {
|
||||
size: (mode.width.into(), mode.height.into()),
|
||||
|
||||
@@ -144,12 +144,26 @@ 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 => event_loop.root,
|
||||
None => screen.root,
|
||||
};
|
||||
#[cfg(not(feature = "rwh_06"))]
|
||||
let root = event_loop.root;
|
||||
@@ -207,18 +221,10 @@ impl UnownedWindow {
|
||||
dimensions
|
||||
};
|
||||
|
||||
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
|
||||
// An iterator over the visuals matching screen id combined with their depths.
|
||||
let mut all_visuals = screen
|
||||
.allowed_depths
|
||||
.iter()
|
||||
.flat_map(|root| &root.allowed_depths)
|
||||
.flat_map(|depth| depth.visuals.iter().map(move |visual| (visual, depth.depth)));
|
||||
|
||||
// creating
|
||||
@@ -484,6 +490,20 @@ 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();
|
||||
@@ -507,20 +527,6 @@ 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);
|
||||
@@ -553,7 +559,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.
|
||||
@@ -970,8 +976,8 @@ impl UnownedWindow {
|
||||
let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT];
|
||||
match state {
|
||||
Ok(atoms) => {
|
||||
let horz_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == horz_atom);
|
||||
let vert_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == vert_atom);
|
||||
let horz_maximized = atoms.contains(&horz_atom);
|
||||
let vert_maximized = atoms.contains(&vert_atom);
|
||||
horz_maximized && vert_maximized
|
||||
},
|
||||
_ => false,
|
||||
@@ -1486,6 +1492,11 @@ 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(());
|
||||
@@ -1497,40 +1508,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(()),
|
||||
@@ -1550,9 +1561,7 @@ impl UnownedWindow {
|
||||
}
|
||||
.map_err(|err| ExternalError::Os(os_error!(OsError::Misc(err))))
|
||||
},
|
||||
CursorGrabMode::Locked => {
|
||||
return Err(ExternalError::NotSupported(NotSupportedError::new()));
|
||||
},
|
||||
CursorGrabMode::Locked => return Ok(()),
|
||||
};
|
||||
|
||||
if result.is_ok() {
|
||||
|
||||
@@ -145,7 +145,7 @@ impl XConnection {
|
||||
fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option<xproto::Atom> {
|
||||
// Fetch the _XSETTINGS_S[screen number] atom.
|
||||
let xsettings_screen = xcb
|
||||
.intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
|
||||
.intern_atom(false, format!("_XSETTINGS_S{default_screen}").as_bytes())
|
||||
.ok()?
|
||||
.reply()
|
||||
.ok()?
|
||||
|
||||
@@ -1,50 +1,104 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
#![allow(unknown_lints)] // New lint below
|
||||
#![allow(static_mut_refs)] // Uses `MainThreadBound` in new version.
|
||||
|
||||
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
|
||||
use objc2_foundation::{MainThreadMarker, NSObject};
|
||||
use std::cell::Cell;
|
||||
use std::mem;
|
||||
|
||||
use super::app_delegate::ApplicationDelegate;
|
||||
use super::event::flags_contains;
|
||||
use objc2::runtime::{Imp, Sel};
|
||||
use objc2::sel;
|
||||
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
|
||||
use objc2_foundation::MainThreadMarker;
|
||||
|
||||
use super::app_state::ApplicationDelegate;
|
||||
use crate::event::{DeviceEvent, ElementState};
|
||||
|
||||
declare_class!(
|
||||
pub(super) struct WinitApplication;
|
||||
type SendEvent = extern "C" fn(&NSApplication, Sel, &NSEvent);
|
||||
|
||||
unsafe impl ClassType for WinitApplication {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSApplication;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitApplication";
|
||||
}
|
||||
// NOTE: Only used on the main thread. Ideally, we'd use `MainThreadBound`, but that isn't
|
||||
// constructible from `const` with this `objc2` version.
|
||||
static mut ORIGINAL: Cell<Option<SendEvent>> = Cell::new(None);
|
||||
|
||||
impl DeclaredClass for WinitApplication {}
|
||||
extern "C" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) {
|
||||
let mtm = MainThreadMarker::from(app);
|
||||
|
||||
unsafe impl WinitApplication {
|
||||
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
|
||||
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
|
||||
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
|
||||
#[method(sendEvent:)]
|
||||
fn send_event(&self, event: &NSEvent) {
|
||||
// For posterity, there are some undocumented event types
|
||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
||||
// but that doesn't really matter here.
|
||||
let event_type = unsafe { event.r#type() };
|
||||
let modifier_flags = unsafe { event.modifierFlags() };
|
||||
if event_type == NSEventType::KeyUp
|
||||
&& flags_contains(modifier_flags, NSEventModifierFlags::NSEventModifierFlagCommand)
|
||||
{
|
||||
if let Some(key_window) = self.keyWindow() {
|
||||
key_window.sendEvent(event);
|
||||
}
|
||||
} else {
|
||||
let delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
|
||||
maybe_dispatch_device_event(&delegate, event);
|
||||
unsafe { msg_send![super(self), sendEvent: event] }
|
||||
}
|
||||
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
|
||||
// Overriding `sendEvent:` fixes that. (https://stackoverflow.com/a/15294196)
|
||||
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
|
||||
//
|
||||
// For posterity, there are some undocumented event types
|
||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
||||
// but that doesn't really matter here.
|
||||
let event_type = unsafe { event.r#type() };
|
||||
let modifier_flags = unsafe { event.modifierFlags() };
|
||||
if event_type == NSEventType::KeyUp
|
||||
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
|
||||
{
|
||||
if let Some(key_window) = app.keyWindow() {
|
||||
key_window.sendEvent(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
// Events are generally scoped to the window level, so the best way
|
||||
// to get device events is to listen for them on NSApplication.
|
||||
let delegate = ApplicationDelegate::get(mtm);
|
||||
maybe_dispatch_device_event(&delegate, event);
|
||||
|
||||
let _ = mtm;
|
||||
let original = unsafe { ORIGINAL.get().expect("no existing sendEvent: handler set") };
|
||||
original(app, sel, event)
|
||||
}
|
||||
|
||||
/// Override the [`sendEvent:`][NSApplication::sendEvent] method on the given application class.
|
||||
///
|
||||
/// The previous implementation created a subclass of [`NSApplication`], however we would like to
|
||||
/// give the user full control over their `NSApplication`, so we override the method here using
|
||||
/// method swizzling instead.
|
||||
///
|
||||
/// This _should_ also allow two versions of Winit to exist in the same application.
|
||||
///
|
||||
/// See the following links for more info on method swizzling:
|
||||
/// - <https://nshipster.com/method-swizzling/>
|
||||
/// - <https://spin.atomicobject.com/method-swizzling-objective-c/>
|
||||
/// - <https://web.archive.org/web/20130308110627/http://cocoadev.com/wiki/MethodSwizzling>
|
||||
///
|
||||
/// NOTE: This function assumes that the passed in application object is the one returned from
|
||||
/// [`NSApplication::sharedApplication`], i.e. the one and only global shared application object.
|
||||
/// For testing though, we allow it to be a different object.
|
||||
pub(crate) fn override_send_event(global_app: &NSApplication) {
|
||||
let mtm = MainThreadMarker::from(global_app);
|
||||
let class = global_app.class();
|
||||
|
||||
let method =
|
||||
class.instance_method(sel!(sendEvent:)).expect("NSApplication must have sendEvent: method");
|
||||
|
||||
// SAFETY: Converting our `sendEvent:` implementation to an IMP.
|
||||
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
|
||||
|
||||
// If we've already overridden the method, don't do anything.
|
||||
// FIXME(madsmtm): Use `std::ptr::fn_addr_eq` (Rust 1.85) once available in MSRV.
|
||||
#[allow(unknown_lints, unpredictable_function_pointer_comparisons)]
|
||||
if overridden == method.implementation() {
|
||||
return;
|
||||
}
|
||||
|
||||
// SAFETY: Our implementation has:
|
||||
// 1. The same signature as `sendEvent:`.
|
||||
// 2. Does not impose extra safety requirements on callers.
|
||||
let original = unsafe { method.set_implementation(overridden) };
|
||||
|
||||
// SAFETY: This is the actual signature of `sendEvent:`.
|
||||
let original = unsafe { mem::transmute::<Imp, SendEvent>(original) };
|
||||
|
||||
// NOTE: If NSApplication was safe to use from multiple threads, then this would potentially be
|
||||
// a (checked) race-condition, since one could call `sendEvent:` before the original had been
|
||||
// stored here.
|
||||
//
|
||||
// It is only usable from the main thread, however, so we're good!
|
||||
let _ = mtm;
|
||||
unsafe { ORIGINAL.set(Some(original)) };
|
||||
}
|
||||
|
||||
fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent) {
|
||||
let event_type = unsafe { event.r#type() };
|
||||
@@ -58,25 +112,27 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
|
||||
let delta_y = unsafe { event.deltaY() } as f64;
|
||||
|
||||
if delta_x != 0.0 {
|
||||
delegate.queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x });
|
||||
delegate.maybe_queue_device_event(DeviceEvent::Motion { axis: 0, value: delta_x });
|
||||
}
|
||||
|
||||
if delta_y != 0.0 {
|
||||
delegate.queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y })
|
||||
delegate.maybe_queue_device_event(DeviceEvent::Motion { axis: 1, value: delta_y })
|
||||
}
|
||||
|
||||
if delta_x != 0.0 || delta_y != 0.0 {
|
||||
delegate.queue_device_event(DeviceEvent::MouseMotion { delta: (delta_x, delta_y) });
|
||||
delegate.maybe_queue_device_event(DeviceEvent::MouseMotion {
|
||||
delta: (delta_x, delta_y),
|
||||
});
|
||||
}
|
||||
},
|
||||
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
|
||||
delegate.queue_device_event(DeviceEvent::Button {
|
||||
delegate.maybe_queue_device_event(DeviceEvent::Button {
|
||||
button: unsafe { event.buttonNumber() } as u32,
|
||||
state: ElementState::Pressed,
|
||||
});
|
||||
},
|
||||
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
|
||||
delegate.queue_device_event(DeviceEvent::Button {
|
||||
delegate.maybe_queue_device_event(DeviceEvent::Button {
|
||||
button: unsafe { event.buttonNumber() } as u32,
|
||||
state: ElementState::Released,
|
||||
});
|
||||
@@ -84,3 +140,56 @@ fn maybe_dispatch_device_event(delegate: &ApplicationDelegate, event: &NSEvent)
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_override() {
|
||||
// FIXME(madsmtm): Ensure this always runs (maybe use cargo-nextest or `--test-threads=1`?)
|
||||
let Some(mtm) = MainThreadMarker::new() else { return };
|
||||
|
||||
// Create a new application, without making it the shared application.
|
||||
let app = unsafe { NSApplication::new(mtm) };
|
||||
override_send_event(&app);
|
||||
// Test calling twice works.
|
||||
override_send_event(&app);
|
||||
|
||||
// FIXME(madsmtm): Can't test this yet, need some way to mock AppState.
|
||||
// unsafe {
|
||||
// let event = super::super::event::dummy_event().unwrap();
|
||||
// app.sendEvent(&event)
|
||||
// }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_class() {
|
||||
let Some(_mtm) = MainThreadMarker::new() else { return };
|
||||
|
||||
declare_class!(
|
||||
struct TestApplication;
|
||||
|
||||
unsafe impl ClassType for TestApplication {
|
||||
type Super = NSApplication;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "TestApplication";
|
||||
}
|
||||
|
||||
impl DeclaredClass for TestApplication {}
|
||||
|
||||
unsafe impl TestApplication {
|
||||
#[method(sendEvent:)]
|
||||
fn send_event(&self, _event: &NSEvent) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let app: Retained<TestApplication> = unsafe { msg_send_id![TestApplication::class(), new] };
|
||||
override_send_event(&app);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,29 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::VecDeque;
|
||||
use std::mem;
|
||||
use std::rc::Weak;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
|
||||
use objc2::rc::Id;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
|
||||
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol, NSSize};
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate, NSRunningApplication,
|
||||
};
|
||||
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
|
||||
|
||||
use super::event_handler::EventHandler;
|
||||
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
|
||||
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop, PanicInfo};
|
||||
use super::observer::{EventLoopWaker, RunLoop};
|
||||
use super::window::WinitWindow;
|
||||
use super::{menu, WindowId, DEVICE_ID};
|
||||
use crate::dpi::PhysicalSize;
|
||||
use crate::event::{DeviceEvent, Event, InnerSizeWriter, StartCause, WindowEvent};
|
||||
use crate::event::{DeviceEvent, Event, StartCause, WindowEvent};
|
||||
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
|
||||
use crate::window::WindowId as RootWindowId;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Policy(NSApplicationActivationPolicy);
|
||||
|
||||
impl Default for Policy {
|
||||
fn default() -> Self {
|
||||
Self(NSApplicationActivationPolicy::Regular)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct State {
|
||||
activation_policy: Policy,
|
||||
pub(super) struct AppState {
|
||||
activation_policy: Option<NSApplicationActivationPolicy>,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
run_loop: RunLoop,
|
||||
event_handler: EventHandler,
|
||||
stop_on_launch: Cell<bool>,
|
||||
stop_before_wait: Cell<bool>,
|
||||
@@ -50,8 +39,9 @@ pub(super) struct State {
|
||||
waker: RefCell<EventLoopWaker>,
|
||||
start_time: Cell<Option<Instant>>,
|
||||
wait_timeout: Cell<Option<Instant>>,
|
||||
pending_events: RefCell<VecDeque<QueuedEvent>>,
|
||||
pending_redraw: RefCell<Vec<WindowId>>,
|
||||
// NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and
|
||||
// as such should be careful to not add fields that, in turn, strongly reference those.
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
@@ -65,62 +55,20 @@ declare_class!(
|
||||
}
|
||||
|
||||
impl DeclaredClass for ApplicationDelegate {
|
||||
type Ivars = State;
|
||||
type Ivars = AppState;
|
||||
}
|
||||
|
||||
unsafe impl NSObjectProtocol for ApplicationDelegate {}
|
||||
|
||||
unsafe impl NSApplicationDelegate for ApplicationDelegate {
|
||||
// Note: This will, globally, only be run once, no matter how many
|
||||
// `EventLoop`s the user creates.
|
||||
#[method(applicationDidFinishLaunching:)]
|
||||
fn did_finish_launching(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("applicationDidFinishLaunching:");
|
||||
self.ivars().is_launched.set(true);
|
||||
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
// We need to delay setting the activation policy and activating the app
|
||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
||||
// menu bar is initially unresponsive on macOS 10.15.
|
||||
app.setActivationPolicy(self.ivars().activation_policy.0);
|
||||
|
||||
window_activation_hack(&app);
|
||||
#[allow(deprecated)]
|
||||
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
|
||||
|
||||
if self.ivars().default_menu {
|
||||
// The menubar initialization should be before the `NewEvents` event, to allow
|
||||
// overriding of the default menu even if it's created
|
||||
menu::initialize(&app);
|
||||
}
|
||||
|
||||
self.ivars().waker.borrow_mut().start();
|
||||
|
||||
self.set_is_running(true);
|
||||
self.dispatch_init_events();
|
||||
|
||||
// If the application is being launched via `EventLoop::pump_app_events()` then we'll
|
||||
// want to stop the app once it is launched (and return to the external loop)
|
||||
//
|
||||
// In this case we still want to consider Winit's `EventLoop` to be "running",
|
||||
// so we call `start_running()` above.
|
||||
if self.ivars().stop_on_launch.get() {
|
||||
// Note: the original idea had been to only stop the underlying `RunLoop`
|
||||
// for the app but that didn't work as expected (`-[NSApplication run]`
|
||||
// effectively ignored the attempt to stop the RunLoop and re-started it).
|
||||
//
|
||||
// So we return from `pump_events` by stopping the application.
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
fn app_did_finish_launching(&self, notification: &NSNotification) {
|
||||
self.did_finish_launching(notification)
|
||||
}
|
||||
|
||||
#[method(applicationWillTerminate:)]
|
||||
fn will_terminate(&self, _sender: Option<&AnyObject>) {
|
||||
trace_scope!("applicationWillTerminate:");
|
||||
// TODO: Notify every window that it will be destroyed, like done in iOS?
|
||||
self.internal_exit();
|
||||
fn app_will_terminate(&self, notification: &NSNotification) {
|
||||
self.will_terminate(notification)
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -128,26 +76,108 @@ declare_class!(
|
||||
impl ApplicationDelegate {
|
||||
pub(super) fn new(
|
||||
mtm: MainThreadMarker,
|
||||
activation_policy: NSApplicationActivationPolicy,
|
||||
activation_policy: Option<NSApplicationActivationPolicy>,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
) -> Id<Self> {
|
||||
let this = mtm.alloc().set_ivars(State {
|
||||
activation_policy: Policy(activation_policy),
|
||||
) -> Retained<Self> {
|
||||
let this = mtm.alloc().set_ivars(AppState {
|
||||
activation_policy,
|
||||
default_menu,
|
||||
activate_ignoring_other_apps,
|
||||
..Default::default()
|
||||
run_loop: RunLoop::main(mtm),
|
||||
event_handler: EventHandler::new(),
|
||||
stop_on_launch: Cell::new(false),
|
||||
stop_before_wait: Cell::new(false),
|
||||
stop_after_wait: Cell::new(false),
|
||||
stop_on_redraw: Cell::new(false),
|
||||
is_launched: Cell::new(false),
|
||||
is_running: Cell::new(false),
|
||||
exit: Cell::new(false),
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
waker: RefCell::new(EventLoopWaker::new()),
|
||||
start_time: Cell::new(None),
|
||||
wait_timeout: Cell::new(None),
|
||||
pending_redraw: RefCell::new(vec![]),
|
||||
});
|
||||
unsafe { msg_send_id![super(this), init] }
|
||||
}
|
||||
|
||||
pub fn get(mtm: MainThreadMarker) -> Id<Self> {
|
||||
// NOTE: This will, globally, only be run once, no matter how many
|
||||
// `EventLoop`s the user creates.
|
||||
fn did_finish_launching(&self, _notification: &NSNotification) {
|
||||
trace_scope!("applicationDidFinishLaunching:");
|
||||
self.ivars().is_launched.set(true);
|
||||
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
// We need to delay setting the activation policy and activating the app
|
||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
||||
// menu bar is initially unresponsive on macOS 10.15.
|
||||
// If no activation policy is explicitly provided, do not set it at all
|
||||
// to allow the package manifest to define behavior via LSUIElement.
|
||||
if let Some(activation_policy) = self.ivars().activation_policy {
|
||||
app.setActivationPolicy(activation_policy);
|
||||
} else {
|
||||
// If no activation policy is explicitly provided, and the application
|
||||
// is bundled, do not set the activation policy at all, to allow the
|
||||
// package manifest to define the behavior via LSUIElement.
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/rust-windowing/winit/issues/261
|
||||
// - https://github.com/rust-windowing/winit/issues/3958
|
||||
let is_bundled =
|
||||
unsafe { NSRunningApplication::currentApplication().bundleIdentifier().is_some() };
|
||||
if !is_bundled {
|
||||
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
|
||||
}
|
||||
}
|
||||
|
||||
window_activation_hack(&app);
|
||||
#[allow(deprecated)]
|
||||
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
|
||||
|
||||
if self.ivars().default_menu {
|
||||
// The menubar initialization should be before the `NewEvents` event, to allow
|
||||
// overriding of the default menu even if it's created
|
||||
menu::initialize(&app);
|
||||
}
|
||||
|
||||
self.ivars().waker.borrow_mut().start();
|
||||
|
||||
self.set_is_running(true);
|
||||
self.dispatch_init_events();
|
||||
|
||||
// If the application is being launched via `EventLoop::pump_app_events()` then we'll
|
||||
// want to stop the app once it is launched (and return to the external loop)
|
||||
//
|
||||
// In this case we still want to consider Winit's `EventLoop` to be "running",
|
||||
// so we call `start_running()` above.
|
||||
if self.ivars().stop_on_launch.get() {
|
||||
// NOTE: the original idea had been to only stop the underlying `RunLoop`
|
||||
// for the app but that didn't work as expected (`-[NSApplication run]`
|
||||
// effectively ignored the attempt to stop the RunLoop and re-started it).
|
||||
//
|
||||
// So we return from `pump_events` by stopping the application.
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
stop_app_immediately(&app);
|
||||
}
|
||||
}
|
||||
|
||||
fn will_terminate(&self, _notification: &NSNotification) {
|
||||
trace_scope!("applicationWillTerminate:");
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
notify_windows_of_exit(&app);
|
||||
self.internal_exit();
|
||||
}
|
||||
|
||||
pub fn get(mtm: MainThreadMarker) -> Retained<Self> {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let delegate =
|
||||
unsafe { app.delegate() }.expect("a delegate was not configured on the application");
|
||||
if delegate.is_kind_of::<Self>() {
|
||||
// SAFETY: Just checked that the delegate is an instance of `ApplicationDelegate`
|
||||
unsafe { Id::cast(delegate) }
|
||||
unsafe { Retained::cast(delegate) }
|
||||
} else {
|
||||
panic!("tried to get a delegate that was not the one Winit has registered")
|
||||
}
|
||||
@@ -188,7 +218,7 @@ impl ApplicationDelegate {
|
||||
|
||||
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits.
|
||||
///
|
||||
/// Note: that if the `NSApplication` has been launched then that state is preserved,
|
||||
/// 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);
|
||||
@@ -232,28 +262,16 @@ impl ApplicationDelegate {
|
||||
self.ivars().control_flow.get()
|
||||
}
|
||||
|
||||
pub fn queue_window_event(&self, window_id: WindowId, event: WindowEvent) {
|
||||
self.ivars()
|
||||
.pending_events
|
||||
.borrow_mut()
|
||||
.push_back(QueuedEvent::WindowEvent(window_id, event));
|
||||
pub fn maybe_queue_window_event(&self, window_id: WindowId, event: WindowEvent) {
|
||||
self.maybe_queue_event(Event::WindowEvent { window_id: RootWindowId(window_id), event });
|
||||
}
|
||||
|
||||
pub fn queue_device_event(&self, event: DeviceEvent) {
|
||||
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::DeviceEvent(event));
|
||||
pub fn handle_window_event(&self, window_id: WindowId, event: WindowEvent) {
|
||||
self.handle_event(Event::WindowEvent { window_id: RootWindowId(window_id), event });
|
||||
}
|
||||
|
||||
pub fn queue_static_scale_factor_changed_event(
|
||||
&self,
|
||||
window: Id<WinitWindow>,
|
||||
suggested_size: PhysicalSize<u32>,
|
||||
scale_factor: f64,
|
||||
) {
|
||||
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::ScaleFactorChanged {
|
||||
window,
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
});
|
||||
pub fn 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) {
|
||||
@@ -281,9 +299,27 @@ impl ApplicationDelegate {
|
||||
if !pending_redraw.contains(&window_id) {
|
||||
pending_redraw.push(window_id);
|
||||
}
|
||||
unsafe { RunLoop::get() }.wakeup();
|
||||
self.ivars().run_loop.wakeup();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn maybe_queue_event(&self, event: Event<HandlePendingUserEvents>) {
|
||||
// Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.)
|
||||
// result in an event being queued, and applied at a later point.
|
||||
//
|
||||
// However, it is not documented which actions do this, and which ones are done immediately,
|
||||
// so to make sure that we don't encounter re-entrancy issues, we first check if we're
|
||||
// currently handling another event, and if we are, we queue the event instead.
|
||||
if !self.ivars().event_handler.in_use() {
|
||||
self.handle_event(event);
|
||||
} else {
|
||||
tracing::debug!(?event, "had to queue event since another is currently being handled");
|
||||
let this = self.retain();
|
||||
self.ivars().run_loop.queue_closure(move || this.handle_event(event));
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn handle_event(&self, event: Event<HandlePendingUserEvents>) {
|
||||
self.ivars().event_handler.handle_event(event, &ActiveEventLoop::new_root(self.retain()))
|
||||
}
|
||||
@@ -345,47 +381,6 @@ impl ApplicationDelegate {
|
||||
|
||||
self.handle_event(Event::UserEvent(HandlePendingUserEvents));
|
||||
|
||||
let events = mem::take(&mut *self.ivars().pending_events.borrow_mut());
|
||||
for event in events {
|
||||
match event {
|
||||
QueuedEvent::WindowEvent(window_id, event) => {
|
||||
self.handle_event(Event::WindowEvent {
|
||||
window_id: RootWindowId(window_id),
|
||||
event,
|
||||
});
|
||||
},
|
||||
QueuedEvent::DeviceEvent(event) => {
|
||||
self.handle_event(Event::DeviceEvent { device_id: DEVICE_ID, event });
|
||||
},
|
||||
QueuedEvent::ScaleFactorChanged { window, suggested_size, scale_factor } => {
|
||||
let new_inner_size = Arc::new(Mutex::new(suggested_size));
|
||||
let scale_factor_changed_event = Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
|
||||
&new_inner_size,
|
||||
)),
|
||||
},
|
||||
};
|
||||
|
||||
self.handle_event(scale_factor_changed_event);
|
||||
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
let logical_size = physical_size.to_logical(scale_factor);
|
||||
let size = NSSize::new(logical_size.width, logical_size.height);
|
||||
window.setContentSize(size);
|
||||
|
||||
let resized_event = Event::WindowEvent {
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Resized(physical_size),
|
||||
};
|
||||
self.handle_event(resized_event);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut());
|
||||
for window_id in redraw {
|
||||
self.handle_event(Event::WindowEvent {
|
||||
@@ -399,6 +394,7 @@ impl ApplicationDelegate {
|
||||
if self.exiting() {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
stop_app_immediately(&app);
|
||||
notify_windows_of_exit(&app);
|
||||
}
|
||||
|
||||
if self.ivars().stop_before_wait.get() {
|
||||
@@ -416,17 +412,6 @@ impl ApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum QueuedEvent {
|
||||
WindowEvent(WindowId, WindowEvent),
|
||||
DeviceEvent(DeviceEvent),
|
||||
ScaleFactorChanged {
|
||||
window: Id<WinitWindow>,
|
||||
suggested_size: PhysicalSize<u32>,
|
||||
scale_factor: f64,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct HandlePendingUserEvents;
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::ffi::c_uchar;
|
||||
use std::slice;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use objc2::rc::Id;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{msg_send_id, sel, ClassType};
|
||||
use objc2::{msg_send, 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,
|
||||
@@ -15,7 +15,7 @@ use crate::cursor::{CursorImage, OnlyCursorImageSource};
|
||||
use crate::window::CursorIcon;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct CustomCursor(pub(crate) Id<NSCursor>);
|
||||
pub struct CustomCursor(pub(crate) Retained<NSCursor>);
|
||||
|
||||
// SAFETY: NSCursor is immutable and thread-safe
|
||||
// TODO(madsmtm): Put this logic in objc2-app-kit itself
|
||||
@@ -28,7 +28,7 @@ impl CustomCursor {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Id<NSCursor> {
|
||||
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Retained<NSCursor> {
|
||||
let width = cursor.width;
|
||||
let height = cursor.height;
|
||||
|
||||
@@ -60,14 +60,14 @@ pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Id<NSCursor> {
|
||||
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
|
||||
}
|
||||
|
||||
pub(crate) fn default_cursor() -> Id<NSCursor> {
|
||||
pub(crate) fn default_cursor() -> Retained<NSCursor> {
|
||||
NSCursor::arrowCursor()
|
||||
}
|
||||
|
||||
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Id<NSCursor>> {
|
||||
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
|
||||
let cls = NSCursor::class();
|
||||
if cls.responds_to(sel) {
|
||||
let cursor: Id<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
|
||||
if msg_send![cls, respondsToSelector: sel] {
|
||||
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
|
||||
Some(cursor)
|
||||
} else {
|
||||
tracing::warn!("cursor `{sel}` appears to be invalid");
|
||||
@@ -82,7 +82,7 @@ macro_rules! def_undocumented_cursor {
|
||||
)*} => {$(
|
||||
$(#[$($m)*])*
|
||||
#[allow(non_snake_case)]
|
||||
fn $name() -> Id<NSCursor> {
|
||||
fn $name() -> Retained<NSCursor> {
|
||||
unsafe { try_cursor_from_selector(sel!($name)).unwrap_or_else(|| default_cursor()) }
|
||||
}
|
||||
)*};
|
||||
@@ -112,7 +112,7 @@ def_undocumented_cursor!(
|
||||
|
||||
// Note that loading `busybutclickable` with this code won't animate
|
||||
// the frames; instead you'll just get them all in a column.
|
||||
unsafe fn load_webkit_cursor(name: &NSString) -> Id<NSCursor> {
|
||||
unsafe fn load_webkit_cursor(name: &NSString) -> Retained<NSCursor> {
|
||||
// Snatch a cursor from WebKit; They fit the style of the native
|
||||
// cursors, and will seem completely standard to macOS users.
|
||||
//
|
||||
@@ -128,7 +128,7 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Id<NSCursor> {
|
||||
|
||||
// TODO: Handle PLists better
|
||||
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
|
||||
let info: Id<NSDictionary<NSObject, NSObject>> = unsafe {
|
||||
let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe {
|
||||
msg_send_id![
|
||||
<NSDictionary<NSObject, NSObject>>::class(),
|
||||
dictionaryWithContentsOfFile: &*info_path,
|
||||
@@ -155,15 +155,15 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Id<NSCursor> {
|
||||
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
|
||||
}
|
||||
|
||||
fn webkit_move() -> Id<NSCursor> {
|
||||
fn webkit_move() -> Retained<NSCursor> {
|
||||
unsafe { load_webkit_cursor(ns_string!("move")) }
|
||||
}
|
||||
|
||||
fn webkit_cell() -> Id<NSCursor> {
|
||||
fn webkit_cell() -> Retained<NSCursor> {
|
||||
unsafe { load_webkit_cursor(ns_string!("cell")) }
|
||||
}
|
||||
|
||||
pub(crate) fn invisible_cursor() -> Id<NSCursor> {
|
||||
pub(crate) fn invisible_cursor() -> Retained<NSCursor> {
|
||||
// 16x16 GIF data for invisible cursor
|
||||
// You can reproduce this via ImageMagick.
|
||||
// $ convert -size 16x16 xc:none cursor.gif
|
||||
@@ -174,7 +174,7 @@ pub(crate) fn invisible_cursor() -> Id<NSCursor> {
|
||||
0xa3, 0x9c, 0xb4, 0xda, 0x8b, 0xb3, 0x3e, 0x05, 0x00, 0x3b,
|
||||
];
|
||||
|
||||
fn new_invisible() -> Id<NSCursor> {
|
||||
fn new_invisible() -> Retained<NSCursor> {
|
||||
// TODO: Consider using `dataWithBytesNoCopy:`
|
||||
let data = NSData::with_bytes(CURSOR_BYTES);
|
||||
let image = NSImage::initWithData(NSImage::alloc(), &data).unwrap();
|
||||
@@ -187,7 +187,7 @@ pub(crate) fn invisible_cursor() -> Id<NSCursor> {
|
||||
CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Id<NSCursor> {
|
||||
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
|
||||
match icon {
|
||||
CursorIcon::Default => default_cursor(),
|
||||
CursorIcon::Pointer => NSCursor::pointingHandCursor(),
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::ffi::c_void;
|
||||
|
||||
use core_foundation::base::CFRelease;
|
||||
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
|
||||
use objc2::rc::Id;
|
||||
use objc2::rc::Retained;
|
||||
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
|
||||
use objc2_foundation::{run_on_main, NSPoint};
|
||||
use smol_str::SmolStr;
|
||||
@@ -92,17 +92,12 @@ 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,
|
||||
key_override: Option<PhysicalKey>,
|
||||
) -> KeyEvent {
|
||||
pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bool) -> KeyEvent {
|
||||
use ElementState::{Pressed, Released};
|
||||
let state = if is_press { Pressed } else { Released };
|
||||
|
||||
let scancode = unsafe { ns_event.keyCode() };
|
||||
let mut physical_key = key_override.unwrap_or_else(|| scancode_to_physicalkey(scancode as u32));
|
||||
let mut physical_key = scancode_to_physicalkey(scancode as u32);
|
||||
|
||||
// NOTE: The logical key should heed both SHIFT and ALT if possible.
|
||||
// For instance:
|
||||
@@ -111,20 +106,15 @@ pub(crate) fn create_key_event(
|
||||
// * 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 text_with_all_modifiers: Option<SmolStr> = if key_override.is_some() {
|
||||
let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
|
||||
let text_with_all_modifiers = if characters.is_empty() {
|
||||
None
|
||||
} else {
|
||||
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))
|
||||
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))
|
||||
};
|
||||
|
||||
let key_from_code = code_to_key(physical_key, scancode);
|
||||
@@ -133,8 +123,8 @@ pub(crate) fn create_key_event(
|
||||
let key_without_modifiers = get_modifierless_char(scancode);
|
||||
|
||||
let modifiers = unsafe { ns_event.modifierFlags() };
|
||||
let has_ctrl = flags_contains(modifiers, NSEventModifierFlags::NSEventModifierFlagControl);
|
||||
let has_cmd = flags_contains(modifiers, NSEventModifierFlags::NSEventModifierFlagCommand);
|
||||
let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl);
|
||||
let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand);
|
||||
|
||||
let logical_key = match text_with_all_modifiers.as_ref() {
|
||||
// Only checking for ctrl and cmd here, not checking for alt because we DO want to
|
||||
@@ -305,16 +295,12 @@ const NX_DEVICELALTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x000000
|
||||
const NX_DEVICERALTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00000040);
|
||||
const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00002000);
|
||||
|
||||
pub(super) fn flags_contains(flags: NSEventModifierFlags, value: NSEventModifierFlags) -> bool {
|
||||
flags.0 & value.0 == value.0
|
||||
}
|
||||
|
||||
pub(super) fn lalt_pressed(event: &NSEvent) -> bool {
|
||||
flags_contains(unsafe { event.modifierFlags() }, NX_DEVICELALTKEYMASK)
|
||||
unsafe { event.modifierFlags() }.contains(NX_DEVICELALTKEYMASK)
|
||||
}
|
||||
|
||||
pub(super) fn ralt_pressed(event: &NSEvent) -> bool {
|
||||
flags_contains(unsafe { event.modifierFlags() }, NX_DEVICERALTKEYMASK)
|
||||
unsafe { event.modifierFlags() }.contains(NX_DEVICERALTKEYMASK)
|
||||
}
|
||||
|
||||
pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
|
||||
@@ -322,38 +308,33 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
|
||||
let mut state = ModifiersState::empty();
|
||||
let mut pressed_mods = ModifiersKeys::empty();
|
||||
|
||||
state.set(
|
||||
ModifiersState::SHIFT,
|
||||
flags_contains(flags, NSEventModifierFlags::NSEventModifierFlagShift),
|
||||
);
|
||||
pressed_mods.set(ModifiersKeys::LSHIFT, flags_contains(flags, NX_DEVICELSHIFTKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RSHIFT, flags_contains(flags, NX_DEVICERSHIFTKEYMASK));
|
||||
state
|
||||
.set(ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSEventModifierFlagShift));
|
||||
pressed_mods.set(ModifiersKeys::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK));
|
||||
|
||||
state.set(
|
||||
ModifiersState::CONTROL,
|
||||
flags_contains(flags, NSEventModifierFlags::NSEventModifierFlagControl),
|
||||
flags.contains(NSEventModifierFlags::NSEventModifierFlagControl),
|
||||
);
|
||||
pressed_mods.set(ModifiersKeys::LCONTROL, flags_contains(flags, NX_DEVICELCTLKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RCONTROL, flags_contains(flags, NX_DEVICERCTLKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK));
|
||||
|
||||
state.set(
|
||||
ModifiersState::ALT,
|
||||
flags_contains(flags, NSEventModifierFlags::NSEventModifierFlagOption),
|
||||
);
|
||||
pressed_mods.set(ModifiersKeys::LALT, flags_contains(flags, NX_DEVICELALTKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RALT, flags_contains(flags, NX_DEVICERALTKEYMASK));
|
||||
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSEventModifierFlagOption));
|
||||
pressed_mods.set(ModifiersKeys::LALT, flags.contains(NX_DEVICELALTKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK));
|
||||
|
||||
state.set(
|
||||
ModifiersState::SUPER,
|
||||
flags_contains(flags, NSEventModifierFlags::NSEventModifierFlagCommand),
|
||||
flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand),
|
||||
);
|
||||
pressed_mods.set(ModifiersKeys::LSUPER, flags_contains(flags, NX_DEVICELCMDKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RSUPER, flags_contains(flags, NX_DEVICERCMDKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK));
|
||||
pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK));
|
||||
|
||||
Modifiers { state, pressed_mods }
|
||||
}
|
||||
|
||||
pub(super) fn dummy_event() -> Option<Id<NSEvent>> {
|
||||
pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
|
||||
unsafe {
|
||||
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
|
||||
NSEventType::ApplicationDefined,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::cell::RefCell;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use super::app_delegate::HandlePendingUserEvents;
|
||||
use super::app_state::HandlePendingUserEvents;
|
||||
use crate::event::Event;
|
||||
use crate::event_loop::ActiveEventLoop as RootActiveEventLoop;
|
||||
|
||||
@@ -16,7 +16,7 @@ impl fmt::Debug for EventHandlerData {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct EventHandler {
|
||||
/// This can be in the following states:
|
||||
/// - Not registered by the event loop (None).
|
||||
@@ -26,6 +26,10 @@ pub(crate) struct EventHandler {
|
||||
}
|
||||
|
||||
impl EventHandler {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Self { inner: RefCell::new(None) }
|
||||
}
|
||||
|
||||
/// Set the event loop handler for the duration of the given closure.
|
||||
///
|
||||
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates
|
||||
|
||||
@@ -14,14 +14,14 @@ use core_foundation::runloop::{
|
||||
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
|
||||
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::rc::{autoreleasepool, Retained};
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{msg_send_id, ClassType};
|
||||
use objc2::sel;
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow};
|
||||
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
|
||||
|
||||
use super::app::WinitApplication;
|
||||
use super::app_delegate::{ApplicationDelegate, HandlePendingUserEvents};
|
||||
use super::app::override_send_event;
|
||||
use super::app_state::{ApplicationDelegate, HandlePendingUserEvents};
|
||||
use super::event::dummy_event;
|
||||
use super::monitor::{self, MonitorHandle};
|
||||
use super::observer::setup_control_flow_observers;
|
||||
@@ -33,7 +33,7 @@ use crate::event_loop::{
|
||||
use crate::platform::macos::ActivationPolicy;
|
||||
use crate::platform::pump_events::PumpStatus;
|
||||
use crate::platform_impl::platform::cursor::CustomCursor;
|
||||
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource};
|
||||
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PanicInfo {
|
||||
@@ -67,17 +67,21 @@ impl PanicInfo {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActiveEventLoop {
|
||||
delegate: Id<ApplicationDelegate>,
|
||||
delegate: Retained<ApplicationDelegate>,
|
||||
pub(super) mtm: MainThreadMarker,
|
||||
}
|
||||
|
||||
impl ActiveEventLoop {
|
||||
pub(super) fn new_root(delegate: Id<ApplicationDelegate>) -> RootWindowTarget {
|
||||
pub(super) fn new_root(delegate: Retained<ApplicationDelegate>) -> RootWindowTarget {
|
||||
let mtm = MainThreadMarker::from(&*delegate);
|
||||
let p = Self { delegate, mtm };
|
||||
RootWindowTarget { p, _marker: PhantomData }
|
||||
}
|
||||
|
||||
pub(super) fn app_delegate(&self) -> &ApplicationDelegate {
|
||||
&self.delegate
|
||||
}
|
||||
|
||||
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor {
|
||||
RootCustomCursor { inner: CustomCursor::new(source.inner) }
|
||||
}
|
||||
@@ -102,6 +106,17 @@ 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(
|
||||
@@ -133,9 +148,7 @@ impl ActiveEventLoop {
|
||||
pub(crate) fn owned_display_handle(&self) -> OwnedDisplayHandle {
|
||||
OwnedDisplayHandle
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveEventLoop {
|
||||
pub(crate) fn hide_application(&self) {
|
||||
NSApplication::sharedApplication(self.mtm).hide(None)
|
||||
}
|
||||
@@ -172,12 +185,12 @@ pub struct EventLoop<T: 'static> {
|
||||
///
|
||||
/// We intentionally don't store `WinitApplication` since we want to have
|
||||
/// the possibility of swapping that out at some point.
|
||||
app: Id<NSApplication>,
|
||||
app: Retained<NSApplication>,
|
||||
/// The application delegate that we've registered.
|
||||
///
|
||||
/// The delegate is only weakly referenced by NSApplication, so we must
|
||||
/// keep it around here as well.
|
||||
delegate: Id<ApplicationDelegate>,
|
||||
delegate: Retained<ApplicationDelegate>,
|
||||
|
||||
// Event sender and receiver, used for EventLoopProxy.
|
||||
sender: mpsc::Sender<T>,
|
||||
@@ -189,18 +202,14 @@ pub struct EventLoop<T: 'static> {
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct PlatformSpecificEventLoopAttributes {
|
||||
pub(crate) activation_policy: ActivationPolicy,
|
||||
pub(crate) activation_policy: Option<ActivationPolicy>,
|
||||
pub(crate) default_menu: bool,
|
||||
pub(crate) activate_ignoring_other_apps: bool,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificEventLoopAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
activation_policy: Default::default(), // Regular
|
||||
default_menu: true,
|
||||
activate_ignoring_other_apps: true,
|
||||
}
|
||||
Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,20 +220,14 @@ impl<T> EventLoop<T> {
|
||||
let mtm = MainThreadMarker::new()
|
||||
.expect("on macOS, `EventLoop` must be created on the main thread!");
|
||||
|
||||
let app: Id<NSApplication> =
|
||||
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
|
||||
|
||||
if !app.is_kind_of::<WinitApplication>() {
|
||||
panic!(
|
||||
"`winit` requires control over the principal class. You must create the event \
|
||||
loop before other parts of your application initialize NSApplication"
|
||||
);
|
||||
}
|
||||
// Initialize the application (if it has not already been).
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
|
||||
let activation_policy = match attributes.activation_policy {
|
||||
ActivationPolicy::Regular => NSApplicationActivationPolicy::Regular,
|
||||
ActivationPolicy::Accessory => NSApplicationActivationPolicy::Accessory,
|
||||
ActivationPolicy::Prohibited => NSApplicationActivationPolicy::Prohibited,
|
||||
None => None,
|
||||
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
|
||||
Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory),
|
||||
Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited),
|
||||
};
|
||||
let delegate = ApplicationDelegate::new(
|
||||
mtm,
|
||||
@@ -237,8 +240,11 @@ impl<T> EventLoop<T> {
|
||||
app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||
});
|
||||
|
||||
// Override `sendEvent:` on the application to forward to our application state.
|
||||
override_send_event(&app);
|
||||
|
||||
let panic_info: Rc<PanicInfo> = Default::default();
|
||||
setup_control_flow_observers(Rc::downgrade(&panic_info));
|
||||
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
|
||||
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
Ok(EventLoop {
|
||||
@@ -411,6 +417,22 @@ pub(super) fn stop_app_immediately(app: &NSApplication) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Tell all windows to close.
|
||||
///
|
||||
/// This will synchronously trigger `WindowEvent::Destroyed` within
|
||||
/// `windowWillClose:`, giving the application one last chance to handle
|
||||
/// those events. It doesn't matter if the user also ends up closing the
|
||||
/// windows in `Window`'s `Drop` impl, once a window has been closed once, it
|
||||
/// stays closed.
|
||||
///
|
||||
/// This ensures that no windows linger on after the event loop has exited,
|
||||
/// see <https://github.com/rust-windowing/winit/issues/4135>.
|
||||
pub(super) fn notify_windows_of_exit(app: &NSApplication) {
|
||||
for window in app.windows() {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
/// Catches panics that happen inside `f` and when a panic
|
||||
/// happens, stops the `sharedApplication`
|
||||
#[inline]
|
||||
@@ -480,8 +502,7 @@ impl<T> EventLoopProxy<T> {
|
||||
cancel: None,
|
||||
perform: event_loop_proxy_handler,
|
||||
};
|
||||
let source =
|
||||
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
||||
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
|
||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
||||
CFRunLoopWakeUp(rl);
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ pub type CGDisplayModeRef = *mut c_void;
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
|
||||
pub fn CGDisplayGetDisplayIDFromUUID(uuid: CFUUIDRef) -> CGDirectDisplayID;
|
||||
}
|
||||
|
||||
#[link(name = "CoreGraphics", kind = "framework")]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use objc2::rc::Id;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::sel;
|
||||
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
|
||||
@@ -48,10 +48,10 @@ pub fn initialize(app: &NSApplication) {
|
||||
Some(sel!(hideOtherApplications:)),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: Some(NSEventModifierFlags(
|
||||
NSEventModifierFlags::NSEventModifierFlagOption.0
|
||||
| NSEventModifierFlags::NSEventModifierFlagCommand.0,
|
||||
)),
|
||||
masks: Some(
|
||||
NSEventModifierFlags::NSEventModifierFlagOption
|
||||
| NSEventModifierFlags::NSEventModifierFlagCommand,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -91,7 +91,7 @@ fn menu_item(
|
||||
title: &NSString,
|
||||
selector: Option<Sel>,
|
||||
key_equivalent: Option<KeyEquivalent<'_>>,
|
||||
) -> Id<NSMenuItem> {
|
||||
) -> Retained<NSMenuItem> {
|
||||
let (key, masks) = match key_equivalent {
|
||||
Some(ke) => (ke.key, ke.masks),
|
||||
None => (ns_string!(""), None),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
mod util;
|
||||
|
||||
mod app;
|
||||
mod app_delegate;
|
||||
mod app_state;
|
||||
mod cursor;
|
||||
mod event;
|
||||
mod event_handler;
|
||||
@@ -37,7 +37,7 @@ pub(crate) use crate::platform_impl::Fullscreen;
|
||||
pub struct DeviceId;
|
||||
|
||||
impl DeviceId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
pub const fn dummy() -> Self {
|
||||
DeviceId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,15 @@ use std::fmt;
|
||||
use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex};
|
||||
use core_foundation::base::{CFRelease, TCFType};
|
||||
use core_foundation::string::CFString;
|
||||
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUID};
|
||||
use core_graphics::display::{
|
||||
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
|
||||
};
|
||||
use objc2::rc::Id;
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2_app_kit::NSScreen;
|
||||
use objc2_foundation::{ns_string, run_on_main, MainThreadMarker, NSNumber, NSPoint, NSRect};
|
||||
use tracing::warn;
|
||||
|
||||
use super::ffi;
|
||||
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
|
||||
@@ -97,18 +99,71 @@ impl VideoModeHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// `CGDirectDisplayID` is documented as:
|
||||
/// > a framebuffer, a color correction (gamma) table, and possibly an attached monitor.
|
||||
///
|
||||
/// That is, it doesn't actually represent the monitor itself. Instead, we use the UUID of the
|
||||
/// monitor, as retrieved from `CGDisplayCreateUUIDFromDisplayID` (this makes the monitor ID stable,
|
||||
/// even across reboots and video mode changes).
|
||||
///
|
||||
/// NOTE: I'd be perfectly valid to store `[u8; 16]` in here instead, we only store `CFUUID` to
|
||||
/// avoid having to re-create it when we want to fetch the display ID.
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorHandle(CGDirectDisplayID);
|
||||
pub struct MonitorHandle(CFUUID);
|
||||
|
||||
// SAFETY: CFUUID is immutable.
|
||||
// FIXME(madsmtm): Upstream this into `objc2-core-foundation`.
|
||||
unsafe impl Send for MonitorHandle {}
|
||||
unsafe impl Sync for MonitorHandle {}
|
||||
|
||||
type MonitorUuid = [u8; 16];
|
||||
|
||||
impl MonitorHandle {
|
||||
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle.
|
||||
fn uuid(&self) -> MonitorUuid {
|
||||
let uuid = unsafe { CFUUIDGetUUIDBytes(self.0.as_concrete_TypeRef()) };
|
||||
MonitorUuid::from([
|
||||
uuid.byte0,
|
||||
uuid.byte1,
|
||||
uuid.byte2,
|
||||
uuid.byte3,
|
||||
uuid.byte4,
|
||||
uuid.byte5,
|
||||
uuid.byte6,
|
||||
uuid.byte7,
|
||||
uuid.byte8,
|
||||
uuid.byte9,
|
||||
uuid.byte10,
|
||||
uuid.byte11,
|
||||
uuid.byte12,
|
||||
uuid.byte13,
|
||||
uuid.byte14,
|
||||
uuid.byte15,
|
||||
])
|
||||
}
|
||||
|
||||
fn display_id(&self) -> CGDirectDisplayID {
|
||||
unsafe { ffi::CGDisplayGetDisplayIDFromUUID(self.0.as_concrete_TypeRef()) }
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn new(display_id: CGDirectDisplayID) -> Option<Self> {
|
||||
// kCGNullDirectDisplay
|
||||
if display_id == 0 {
|
||||
// `CGDisplayCreateUUIDFromDisplayID` checks kCGNullDirectDisplay internally.
|
||||
warn!("constructing monitor from invalid display ID 0; falling back to main monitor");
|
||||
}
|
||||
let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(display_id) };
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(Self(unsafe { CFUUID::wrap_under_create_rule(ptr) }))
|
||||
}
|
||||
}
|
||||
|
||||
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
|
||||
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
|
||||
// unique identifier that persists even across system reboots
|
||||
impl PartialEq for MonitorHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
||||
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
|
||||
}
|
||||
self.uuid() == other.uuid()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,18 +177,13 @@ impl PartialOrd for MonitorHandle {
|
||||
|
||||
impl Ord for MonitorHandle {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
||||
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
|
||||
}
|
||||
self.uuid().cmp(&other.uuid())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for MonitorHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
|
||||
}
|
||||
self.uuid().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +191,8 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||
if let Ok(displays) = CGDisplay::active_displays() {
|
||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||
for display in displays {
|
||||
monitors.push_back(MonitorHandle(display));
|
||||
// Display ID just fetched from `CGGetActiveDisplayList`, should be fine to unwrap.
|
||||
monitors.push_back(MonitorHandle::new(display).expect("invalid display ID"));
|
||||
}
|
||||
monitors
|
||||
} else {
|
||||
@@ -150,7 +201,8 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||
}
|
||||
|
||||
pub fn primary_monitor() -> MonitorHandle {
|
||||
MonitorHandle(CGDisplay::main().id)
|
||||
// Display ID just fetched from `CGMainDisplayID`, should be fine to unwrap.
|
||||
MonitorHandle::new(CGDisplay::main().id).expect("invalid display ID")
|
||||
}
|
||||
|
||||
impl fmt::Debug for MonitorHandle {
|
||||
@@ -167,26 +219,20 @@ impl fmt::Debug for MonitorHandle {
|
||||
}
|
||||
|
||||
impl MonitorHandle {
|
||||
pub fn new(id: CGDirectDisplayID) -> Self {
|
||||
MonitorHandle(id)
|
||||
}
|
||||
|
||||
// TODO: Be smarter about this:
|
||||
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
|
||||
pub fn name(&self) -> Option<String> {
|
||||
let MonitorHandle(display_id) = *self;
|
||||
let screen_num = CGDisplay::new(display_id).model_number();
|
||||
let screen_num = CGDisplay::new(self.display_id()).model_number();
|
||||
Some(format!("Monitor #{screen_num}"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn native_identifier(&self) -> u32 {
|
||||
self.0
|
||||
self.display_id()
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
let MonitorHandle(display_id) = *self;
|
||||
let display = CGDisplay::new(display_id);
|
||||
let display = CGDisplay::new(self.display_id());
|
||||
let height = display.pixels_high();
|
||||
let width = display.pixels_wide();
|
||||
PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.scale_factor())
|
||||
@@ -213,14 +259,15 @@ impl MonitorHandle {
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
unsafe {
|
||||
let current_display_mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.0) as _);
|
||||
let current_display_mode =
|
||||
NativeDisplayMode(CGDisplayCopyDisplayMode(self.display_id()) as _);
|
||||
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(current_display_mode.0);
|
||||
if refresh_rate > 0.0 {
|
||||
return Some((refresh_rate * 1000.0).round() as u32);
|
||||
}
|
||||
|
||||
let mut display_link = std::ptr::null_mut();
|
||||
if ffi::CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link)
|
||||
if ffi::CVDisplayLinkCreateWithCGDisplay(self.display_id(), &mut display_link)
|
||||
!= ffi::kCVReturnSuccess
|
||||
{
|
||||
return None;
|
||||
@@ -243,27 +290,34 @@ impl MonitorHandle {
|
||||
|
||||
unsafe {
|
||||
let modes = {
|
||||
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
|
||||
assert!(!array.is_null(), "failed to get list of display modes");
|
||||
let array_count = CFArrayGetCount(array);
|
||||
let modes: Vec<_> = (0..array_count)
|
||||
.map(move |i| {
|
||||
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
|
||||
ffi::CGDisplayModeRetain(mode);
|
||||
mode
|
||||
})
|
||||
.collect();
|
||||
CFRelease(array as *const _);
|
||||
modes
|
||||
let array = ffi::CGDisplayCopyAllDisplayModes(self.display_id(), std::ptr::null());
|
||||
if array.is_null() {
|
||||
// Occasionally, certain CalDigit Thunderbolt Hubs report a spurious monitor
|
||||
// during sleep/wake/cycling monitors. It tends to have null
|
||||
// or 1 video mode only. See <https://github.com/bevyengine/bevy/issues/17827>.
|
||||
warn!(monitor = ?self, "failed to get a list of display modes");
|
||||
Vec::new()
|
||||
} else {
|
||||
let array_count = CFArrayGetCount(array);
|
||||
let modes: Vec<_> = (0..array_count)
|
||||
.map(move |i| {
|
||||
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
|
||||
ffi::CGDisplayModeRetain(mode);
|
||||
mode
|
||||
})
|
||||
.collect();
|
||||
CFRelease(array as *const _);
|
||||
modes
|
||||
}
|
||||
};
|
||||
|
||||
modes.into_iter().map(move |mode| {
|
||||
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
|
||||
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode);
|
||||
|
||||
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
|
||||
// isn't a CRT
|
||||
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 {
|
||||
(cg_refresh_rate_hertz * 1000) as u32
|
||||
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0.0 {
|
||||
(cg_refresh_rate_hertz * 1000.0).round() as u32
|
||||
} else {
|
||||
refresh_rate_millihertz
|
||||
};
|
||||
@@ -295,14 +349,18 @@ impl MonitorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Id<NSScreen>> {
|
||||
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
|
||||
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
|
||||
let uuid = self.uuid();
|
||||
NSScreen::screens(mtm).into_iter().find(|screen| {
|
||||
let other_native_id = get_display_id(screen);
|
||||
let other_uuid = unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
|
||||
};
|
||||
uuid == other_uuid
|
||||
if let Some(other) = MonitorHandle::new(other_native_id) {
|
||||
uuid == other.uuid()
|
||||
} else {
|
||||
// Display ID was just fetched from live NSScreen, but can still result in `None`
|
||||
// with certain Thunderbolt docked monitors.
|
||||
warn!(other_native_id, "comparing against screen with invalid display ID");
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
//! Utilities for working with `CFRunLoop`.
|
||||
//!
|
||||
//! See Apple's documentation on Run Loops for details:
|
||||
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
|
||||
use std::cell::Cell;
|
||||
use std::ffi::c_void;
|
||||
use std::panic::{AssertUnwindSafe, UnwindSafe};
|
||||
use std::ptr;
|
||||
use std::rc::Weak;
|
||||
use std::time::Instant;
|
||||
|
||||
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease};
|
||||
use block2::Block;
|
||||
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
|
||||
use core_foundation::date::CFAbsoluteTimeGetCurrent;
|
||||
use core_foundation::runloop::{
|
||||
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopExit,
|
||||
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
|
||||
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
|
||||
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
|
||||
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
|
||||
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
|
||||
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2_foundation::MainThreadMarker;
|
||||
use tracing::error;
|
||||
|
||||
use super::app_delegate::ApplicationDelegate;
|
||||
use super::app_state::ApplicationDelegate;
|
||||
use super::event_loop::{stop_app_on_panic, PanicInfo};
|
||||
use super::ffi;
|
||||
|
||||
@@ -84,10 +91,20 @@ extern "C" fn control_flow_end_handler(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RunLoop(CFRunLoopRef);
|
||||
|
||||
impl Default for RunLoop {
|
||||
fn default() -> Self {
|
||||
Self(ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl RunLoop {
|
||||
pub unsafe fn get() -> Self {
|
||||
pub fn main(mtm: MainThreadMarker) -> Self {
|
||||
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
|
||||
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
|
||||
let _ = mtm;
|
||||
RunLoop(unsafe { CFRunLoopGetMain() })
|
||||
}
|
||||
|
||||
@@ -114,9 +131,79 @@ impl RunLoop {
|
||||
};
|
||||
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
|
||||
}
|
||||
|
||||
/// Submit a closure to run on the main thread as the next step in the run loop, before other
|
||||
/// event sources are processed.
|
||||
///
|
||||
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// This queuing could be implemented in the following several ways with subtle differences in
|
||||
/// timing. This list is sorted in rough order in which they are run:
|
||||
///
|
||||
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
|
||||
///
|
||||
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
|
||||
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
|
||||
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
|
||||
///
|
||||
/// a. `atStart = true`.
|
||||
///
|
||||
/// b. `atStart = false`.
|
||||
///
|
||||
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
|
||||
/// respect the ordering that runloop events have.
|
||||
///
|
||||
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
|
||||
/// want the event to be queued in a way that preserves the order the events originally arrived
|
||||
/// in.
|
||||
///
|
||||
/// As an example, let's assume that we receive two events from the user, a mouse click which we
|
||||
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
|
||||
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
|
||||
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
|
||||
/// put the event at the very front of the queue, to be handled as soon as possible after
|
||||
/// handling whatever event it's currently handling.
|
||||
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
|
||||
extern "C" {
|
||||
fn CFRunLoopPerformBlock(rl: CFRunLoopRef, mode: CFTypeRef, block: &Block<dyn Fn()>);
|
||||
}
|
||||
|
||||
// Convert `FnOnce()` to `Block<dyn Fn()>`.
|
||||
let closure = Cell::new(Some(closure));
|
||||
let block = block2::RcBlock::new(move || {
|
||||
if let Some(closure) = closure.take() {
|
||||
closure()
|
||||
} else {
|
||||
error!("tried to execute queued closure on main thread twice");
|
||||
}
|
||||
});
|
||||
|
||||
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
|
||||
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
|
||||
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
|
||||
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
|
||||
// - `NSConnectionReplyMode`: TODO.
|
||||
//
|
||||
// We only want to run event handlers in the default mode, as we support running a blocking
|
||||
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
|
||||
// resizing such panel window enters the event tracking run loop mode, so we can't directly
|
||||
// trigger events inside that mode either.
|
||||
//
|
||||
// Any events that are queued while running a modal or when live-resizing will instead wait,
|
||||
// and be delivered to the application afterwards.
|
||||
//
|
||||
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
|
||||
let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef };
|
||||
|
||||
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
|
||||
unsafe { CFRunLoopPerformBlock(self.0, mode, &block) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
|
||||
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
|
||||
let run_loop = RunLoop::main(mtm);
|
||||
unsafe {
|
||||
let mut context = CFRunLoopObserverContext {
|
||||
info: Weak::into_raw(panic_info) as *mut _,
|
||||
@@ -125,16 +212,15 @@ pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
};
|
||||
let run_loop = RunLoop::get();
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopAfterWaiting,
|
||||
CFIndex::min_value(),
|
||||
CFIndex::MIN,
|
||||
control_flow_begin_handler,
|
||||
&mut context as *mut _,
|
||||
);
|
||||
run_loop.add_observer(
|
||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
||||
CFIndex::max_value(),
|
||||
CFIndex::MAX,
|
||||
control_flow_end_handler,
|
||||
&mut context as *mut _,
|
||||
);
|
||||
@@ -165,8 +251,8 @@ impl Drop for EventLoopWaker {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EventLoopWaker {
|
||||
fn default() -> EventLoopWaker {
|
||||
impl EventLoopWaker {
|
||||
pub(crate) fn new() -> Self {
|
||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
||||
unsafe {
|
||||
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
|
||||
@@ -174,7 +260,7 @@ impl Default for EventLoopWaker {
|
||||
// future, but that gets changed to fire immediately in did_finish_launching
|
||||
let timer = CFRunLoopTimerCreate(
|
||||
ptr::null_mut(),
|
||||
std::f64::MAX,
|
||||
f64::MAX,
|
||||
0.000_000_1,
|
||||
0,
|
||||
0,
|
||||
@@ -182,23 +268,21 @@ impl Default for EventLoopWaker {
|
||||
ptr::null_mut(),
|
||||
);
|
||||
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
|
||||
EventLoopWaker { timer, start_instant: Instant::now(), next_fire_date: None }
|
||||
Self { timer, start_instant: Instant::now(), next_fire_date: None }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker {
|
||||
pub fn stop(&mut self) {
|
||||
if self.next_fire_date.is_some() {
|
||||
self.next_fire_date = None;
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MAX) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
if self.next_fire_date != Some(self.start_instant) {
|
||||
self.next_fire_date = Some(self.start_instant);
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
|
||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use objc2_foundation::{NSNotFound, NSRange, NSUInteger};
|
||||
use tracing::trace;
|
||||
|
||||
pub static EMPTY_RANGE: NSRange = NSRange { location: NSNotFound as NSUInteger, length: 0 };
|
||||
|
||||
macro_rules! trace_scope {
|
||||
($s:literal) => {
|
||||
let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s);
|
||||
|
||||
@@ -3,32 +3,30 @@ use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::ptr;
|
||||
|
||||
use objc2::rc::{Id, WeakId};
|
||||
use objc2::rc::{Retained, WeakId};
|
||||
use objc2::runtime::{AnyObject, Sel};
|
||||
use objc2::{
|
||||
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
|
||||
};
|
||||
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
|
||||
NSTrackingRectTag, NSView,
|
||||
NSTrackingRectTag, NSView, NSViewFrameDidChangeNotification,
|
||||
};
|
||||
use objc2_foundation::{
|
||||
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
|
||||
NSMutableAttributedString, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize,
|
||||
NSString, NSUInteger,
|
||||
NSMutableAttributedString, NSNotFound, NSNotificationCenter, NSObject, NSObjectProtocol,
|
||||
NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
|
||||
};
|
||||
|
||||
use super::app_delegate::ApplicationDelegate;
|
||||
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,
|
||||
scancode_to_physicalkey, KeyEventExtra,
|
||||
};
|
||||
use super::window::WinitWindow;
|
||||
use super::{util, DEVICE_ID};
|
||||
use super::DEVICE_ID;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, Ime, Modifiers, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, TouchPhase,
|
||||
WindowEvent,
|
||||
};
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
|
||||
@@ -37,7 +35,7 @@ use crate::platform::macos::OptionAsAlt;
|
||||
#[derive(Debug)]
|
||||
struct CursorState {
|
||||
visible: bool,
|
||||
cursor: Id<NSCursor>,
|
||||
cursor: Retained<NSCursor>,
|
||||
}
|
||||
|
||||
impl Default for CursorState {
|
||||
@@ -110,8 +108,11 @@ fn get_left_modifier_code(key: &Key) -> KeyCode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct ViewState {
|
||||
/// Strong reference to the global application state.
|
||||
app_delegate: Retained<ApplicationDelegate>,
|
||||
|
||||
cursor_state: RefCell<CursorState>,
|
||||
ime_position: Cell<NSPoint>,
|
||||
ime_size: Cell<NSSize>,
|
||||
@@ -130,7 +131,7 @@ pub struct ViewState {
|
||||
/// to the application, even during IME
|
||||
forward_key_to_app: Cell<bool>,
|
||||
|
||||
marked_text: RefCell<Id<NSMutableAttributedString>>,
|
||||
marked_text: RefCell<Retained<NSMutableAttributedString>>,
|
||||
accepts_first_mouse: bool,
|
||||
|
||||
// Weak reference because the window keeps a strong reference to the view
|
||||
@@ -199,19 +200,15 @@ declare_class!(
|
||||
}
|
||||
|
||||
#[method(drawRect:)]
|
||||
fn draw_rect(&self, rect: NSRect) {
|
||||
fn draw_rect(&self, _rect: NSRect) {
|
||||
trace_scope!("drawRect:");
|
||||
|
||||
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
|
||||
if let Some(window) = self.ivars()._ns_window.load() {
|
||||
let app_delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
|
||||
app_delegate.handle_redraw(window.id());
|
||||
self.ivars().app_delegate.handle_redraw(window.id());
|
||||
}
|
||||
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
let _: () = msg_send![super(self), drawRect: rect];
|
||||
}
|
||||
// This is a direct subclass of NSView, no need to call superclass' drawRect:
|
||||
}
|
||||
|
||||
#[method(acceptsFirstResponder)]
|
||||
@@ -224,7 +221,7 @@ declare_class!(
|
||||
// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem
|
||||
// TODO: Add an API extension for using `NSTouchBar`
|
||||
#[method_id(touchBar)]
|
||||
fn touch_bar(&self) -> Option<Id<NSObject>> {
|
||||
fn touch_bar(&self) -> Option<Retained<NSObject>> {
|
||||
trace_scope!("touchBar");
|
||||
None
|
||||
}
|
||||
@@ -257,33 +254,36 @@ declare_class!(
|
||||
if length > 0 {
|
||||
NSRange::new(0, length)
|
||||
} else {
|
||||
util::EMPTY_RANGE
|
||||
// Documented to return `{NSNotFound, 0}` if there is no marked range.
|
||||
NSRange::new(NSNotFound as NSUInteger, 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[method(selectedRange)]
|
||||
fn selected_range(&self) -> NSRange {
|
||||
trace_scope!("selectedRange");
|
||||
util::EMPTY_RANGE
|
||||
// Documented to return `{NSNotFound, 0}` if there is no selection.
|
||||
NSRange::new(NSNotFound as NSUInteger, 0)
|
||||
}
|
||||
|
||||
#[method(setMarkedText:selectedRange:replacementRange:)]
|
||||
fn set_marked_text(
|
||||
&self,
|
||||
string: &NSObject,
|
||||
_selected_range: NSRange,
|
||||
selected_range: NSRange,
|
||||
_replacement_range: NSRange,
|
||||
) {
|
||||
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
|
||||
trace_scope!("setMarkedText:selectedRange:replacementRange:");
|
||||
|
||||
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
let (marked_text, preedit_string) = if string.is_kind_of::<NSAttributedString>() {
|
||||
let (marked_text, string) = if string.is_kind_of::<NSAttributedString>() {
|
||||
let string: *const NSObject = string;
|
||||
let string: *const NSAttributedString = string.cast();
|
||||
let string = unsafe { &*string };
|
||||
(
|
||||
NSMutableAttributedString::from_attributed_nsstring(string),
|
||||
string.string().to_string(),
|
||||
string.string(),
|
||||
)
|
||||
} else {
|
||||
let string: *const NSObject = string;
|
||||
@@ -291,7 +291,7 @@ declare_class!(
|
||||
let string = unsafe { &*string };
|
||||
(
|
||||
NSMutableAttributedString::from_nsstring(string),
|
||||
string.to_string(),
|
||||
string.copy(),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -311,16 +311,21 @@ declare_class!(
|
||||
self.ivars().ime_state.set(ImeState::Ground);
|
||||
}
|
||||
|
||||
// Empty string basically means that there's no preedit, so indicate that by sending
|
||||
// `None` cursor range.
|
||||
let cursor_range = if preedit_string.is_empty() {
|
||||
let cursor_range = if string.is_empty() {
|
||||
// An empty string basically means that there's no preedit, so indicate that by
|
||||
// sending a `None` cursor range.
|
||||
None
|
||||
} else {
|
||||
Some((preedit_string.len(), preedit_string.len()))
|
||||
// Convert the selected range from UTF-16 indices to UTF-8 indices.
|
||||
let sub_string_a = unsafe { string.substringToIndex(selected_range.location) };
|
||||
let sub_string_b = unsafe { string.substringToIndex(selected_range.end()) };
|
||||
let lowerbound_utf8 = sub_string_a.len();
|
||||
let upperbound_utf8 = sub_string_b.len();
|
||||
Some((lowerbound_utf8, upperbound_utf8))
|
||||
};
|
||||
|
||||
// Send WindowEvent for updating marked text
|
||||
self.queue_event(WindowEvent::Ime(Ime::Preedit(preedit_string, cursor_range)));
|
||||
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
|
||||
}
|
||||
|
||||
#[method(unmarkText)]
|
||||
@@ -341,7 +346,7 @@ declare_class!(
|
||||
}
|
||||
|
||||
#[method_id(validAttributesForMarkedText)]
|
||||
fn valid_attributes_for_marked_text(&self) -> Id<NSArray<NSAttributedStringKey>> {
|
||||
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
|
||||
trace_scope!("validAttributesForMarkedText");
|
||||
NSArray::new()
|
||||
}
|
||||
@@ -351,7 +356,7 @@ declare_class!(
|
||||
&self,
|
||||
_range: NSRange,
|
||||
_actual_range: *mut NSRange,
|
||||
) -> Option<Id<NSAttributedString>> {
|
||||
) -> Option<Retained<NSAttributedString>> {
|
||||
trace_scope!("attributedSubstringForProposedRange:actualRange:");
|
||||
None
|
||||
}
|
||||
@@ -380,6 +385,7 @@ declare_class!(
|
||||
|
||||
#[method(insertText:replacementRange:)]
|
||||
fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
|
||||
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
|
||||
trace_scope!("insertText:replacementRange:");
|
||||
|
||||
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
|
||||
@@ -393,7 +399,7 @@ declare_class!(
|
||||
unsafe { &*string }.to_string()
|
||||
};
|
||||
|
||||
let is_control = string.chars().next().map_or(false, |c| c.is_control());
|
||||
let is_control = string.chars().next().is_some_and(|c| c.is_control());
|
||||
|
||||
// Commit only if we have marked text.
|
||||
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control {
|
||||
@@ -476,7 +482,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() }, None);
|
||||
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() });
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event: key_event,
|
||||
@@ -499,7 +505,7 @@ declare_class!(
|
||||
) {
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
event: create_key_event(&event, false, false, None),
|
||||
event: create_key_event(&event, false, false),
|
||||
is_synthetic: false,
|
||||
});
|
||||
}
|
||||
@@ -546,7 +552,7 @@ declare_class!(
|
||||
.expect("could not find current event");
|
||||
|
||||
self.update_modifiers(&event, false);
|
||||
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
|
||||
let event = create_key_event(&event, true, unsafe { event.isARepeat() });
|
||||
|
||||
self.queue_event(WindowEvent::KeyboardInput {
|
||||
device_id: DEVICE_ID,
|
||||
@@ -680,7 +686,7 @@ declare_class!(
|
||||
|
||||
self.update_modifiers(event, false);
|
||||
|
||||
self.queue_device_event(DeviceEvent::MouseWheel { delta });
|
||||
self.ivars().app_delegate.maybe_queue_device_event(DeviceEvent::MouseWheel { delta });
|
||||
self.queue_event(WindowEvent::MouseWheel {
|
||||
device_id: DEVICE_ID,
|
||||
delta,
|
||||
@@ -773,34 +779,40 @@ declare_class!(
|
||||
|
||||
impl WinitView {
|
||||
pub(super) fn new(
|
||||
app_delegate: &ApplicationDelegate,
|
||||
window: &WinitWindow,
|
||||
accepts_first_mouse: bool,
|
||||
option_as_alt: OptionAsAlt,
|
||||
) -> Id<Self> {
|
||||
) -> Retained<Self> {
|
||||
let mtm = MainThreadMarker::from(window);
|
||||
let this = mtm.alloc().set_ivars(ViewState {
|
||||
app_delegate: app_delegate.retain(),
|
||||
cursor_state: Default::default(),
|
||||
ime_position: Default::default(),
|
||||
ime_size: Default::default(),
|
||||
modifiers: Default::default(),
|
||||
phys_modifiers: Default::default(),
|
||||
tracking_rect: Default::default(),
|
||||
ime_state: Default::default(),
|
||||
input_source: Default::default(),
|
||||
ime_allowed: Default::default(),
|
||||
forward_key_to_app: Default::default(),
|
||||
marked_text: Default::default(),
|
||||
accepts_first_mouse,
|
||||
_ns_window: WeakId::new(&window.retain()),
|
||||
option_as_alt: Cell::new(option_as_alt),
|
||||
..Default::default()
|
||||
});
|
||||
let this: Id<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
|
||||
|
||||
this.setPostsFrameChangedNotifications(true);
|
||||
let notification_center: &AnyObject =
|
||||
unsafe { msg_send![class!(NSNotificationCenter), defaultCenter] };
|
||||
// About frame change
|
||||
let frame_did_change_notification_name =
|
||||
NSString::from_str("NSViewFrameDidChangeNotification");
|
||||
#[allow(clippy::let_unit_value)]
|
||||
let notification_center = unsafe { NSNotificationCenter::defaultCenter() };
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
notification_center,
|
||||
addObserver: &*this,
|
||||
selector: sel!(frameDidChange:),
|
||||
name: &*frame_did_change_notification_name,
|
||||
object: &*this,
|
||||
];
|
||||
notification_center.addObserver_selector_name_object(
|
||||
&this,
|
||||
sel!(frameDidChange:),
|
||||
Some(NSViewFrameDidChangeNotification),
|
||||
Some(&this),
|
||||
)
|
||||
}
|
||||
|
||||
*this.ivars().input_source.borrow_mut() = this.current_input_source();
|
||||
@@ -808,7 +820,7 @@ impl WinitView {
|
||||
this
|
||||
}
|
||||
|
||||
fn window(&self) -> Id<WinitWindow> {
|
||||
fn window(&self) -> Retained<WinitWindow> {
|
||||
// TODO: Simply use `window` property on `NSView`.
|
||||
// That only returns a window _after_ the view has been attached though!
|
||||
// (which is incompatible with `frameDidChange:`)
|
||||
@@ -818,13 +830,7 @@ impl WinitView {
|
||||
}
|
||||
|
||||
fn queue_event(&self, event: WindowEvent) {
|
||||
let app_delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
|
||||
app_delegate.queue_window_event(self.window().id(), event);
|
||||
}
|
||||
|
||||
fn queue_device_event(&self, event: DeviceEvent) {
|
||||
let app_delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
|
||||
app_delegate.queue_device_event(event);
|
||||
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f64 {
|
||||
@@ -843,11 +849,11 @@ impl WinitView {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(super) fn cursor_icon(&self) -> Id<NSCursor> {
|
||||
pub(super) fn cursor_icon(&self) -> Retained<NSCursor> {
|
||||
self.ivars().cursor_state.borrow().cursor.clone()
|
||||
}
|
||||
|
||||
pub(super) fn set_cursor_icon(&self, icon: Id<NSCursor>) {
|
||||
pub(super) fn set_cursor_icon(&self, icon: Retained<NSCursor>) {
|
||||
let mut cursor_state = self.ivars().cursor_state.borrow_mut();
|
||||
cursor_state.cursor = icon;
|
||||
}
|
||||
@@ -927,22 +933,36 @@ impl WinitView {
|
||||
let scancode = unsafe { ns_event.keyCode() };
|
||||
let physical_key = scancode_to_physicalkey(scancode as u32);
|
||||
|
||||
// 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);
|
||||
let logical_key = code_to_key(physical_key, scancode);
|
||||
// Ignore processing of unknown modifiers because we can't determine whether
|
||||
// it was pressed or release reliably.
|
||||
let Some(event_modifier) = key_to_modifier(&key) else {
|
||||
//
|
||||
// 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 {
|
||||
break 'send_event;
|
||||
};
|
||||
event.physical_key = physical_key;
|
||||
event.logical_key = key.clone();
|
||||
event.location = code_to_location(physical_key);
|
||||
|
||||
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(),
|
||||
},
|
||||
};
|
||||
|
||||
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(key).or_insert(ModLocationMask::empty());
|
||||
let phys_mod =
|
||||
phys_mod_state.entry(logical_key).or_insert(ModLocationMask::empty());
|
||||
|
||||
let is_active = current_modifiers.state().contains(event_modifier);
|
||||
let mut events = VecDeque::with_capacity(2);
|
||||
@@ -1080,7 +1100,7 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
|
||||
// NOTE: to get option as alt working we need to rewrite events
|
||||
// we're getting from the operating system, which makes it
|
||||
// impossible to provide such events as extra in `KeyEvent`.
|
||||
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Id<NSEvent> {
|
||||
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEvent> {
|
||||
let ev_mods = event_mods(event).state;
|
||||
let ignore_alt_characters = match option_as_alt {
|
||||
OptionAsAlt::OnlyLeft if lalt_pressed(event) => true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::rc::{autoreleasepool, Retained};
|
||||
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{NSResponder, NSWindow};
|
||||
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
|
||||
@@ -11,9 +11,9 @@ use crate::error::OsError as RootOsError;
|
||||
use crate::window::WindowAttributes;
|
||||
|
||||
pub(crate) struct Window {
|
||||
window: MainThreadBound<Id<WinitWindow>>,
|
||||
window: MainThreadBound<Retained<WinitWindow>>,
|
||||
/// The window only keeps a weak reference to this, so we must keep it around here.
|
||||
delegate: MainThreadBound<Id<WindowDelegate>>,
|
||||
delegate: MainThreadBound<Retained<WindowDelegate>>,
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
@@ -28,7 +28,9 @@ impl Window {
|
||||
attributes: WindowAttributes,
|
||||
) -> Result<Self, RootOsError> {
|
||||
let mtm = window_target.mtm;
|
||||
let delegate = autoreleasepool(|_| WindowDelegate::new(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),
|
||||
@@ -72,7 +74,7 @@ impl Window {
|
||||
pub struct WindowId(pub usize);
|
||||
|
||||
impl WindowId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
pub const fn dummy() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,41 @@
|
||||
#![allow(clippy::unnecessary_cast)]
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use core_graphics::display::{CGDisplay, CGPoint};
|
||||
use monitor::VideoModeHandle;
|
||||
use objc2::rc::{autoreleasepool, Id};
|
||||
use objc2::rc::{autoreleasepool, Retained};
|
||||
use objc2::runtime::{AnyObject, ProtocolObject};
|
||||
use objc2::{
|
||||
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
|
||||
};
|
||||
use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{
|
||||
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSApplication,
|
||||
NSApplicationPresentationOptions, NSBackingStoreType, NSDraggingDestination,
|
||||
NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSView,
|
||||
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
|
||||
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
|
||||
NSWindowTabbingMode, NSWindowTitleVisibility,
|
||||
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
|
||||
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
|
||||
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
|
||||
NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate,
|
||||
NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode,
|
||||
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility,
|
||||
};
|
||||
use objc2_foundation::{
|
||||
CGFloat, MainThreadMarker, NSArray, NSCopying, NSObject, NSObjectProtocol, NSPoint, NSRect,
|
||||
NSSize, NSString,
|
||||
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey,
|
||||
NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, NSKeyValueObservingOptions, NSObject,
|
||||
NSObjectNSDelayedPerforming, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSPoint,
|
||||
NSRect, NSSize, NSString,
|
||||
};
|
||||
use tracing::{trace, warn};
|
||||
|
||||
use super::app_delegate::ApplicationDelegate;
|
||||
use super::app_state::ApplicationDelegate;
|
||||
use super::cursor::cursor_from_icon;
|
||||
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
|
||||
use super::observer::RunLoop;
|
||||
use super::view::WinitView;
|
||||
use super::window::WinitWindow;
|
||||
use super::{ffi, Fullscreen, MonitorHandle, OsError, WindowId};
|
||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
||||
use crate::event::WindowEvent;
|
||||
use crate::event::{InnerSizeWriter, WindowEvent};
|
||||
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
|
||||
use crate::window::{
|
||||
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
|
||||
@@ -51,6 +55,7 @@ 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 {
|
||||
@@ -68,20 +73,22 @@ impl Default for PlatformSpecificWindowAttributes {
|
||||
accepts_first_mouse: true,
|
||||
tabbing_identifier: None,
|
||||
option_as_alt: Default::default(),
|
||||
borderless_game: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct State {
|
||||
window: Id<WinitWindow>,
|
||||
/// Strong reference to the global application state.
|
||||
app_delegate: Retained<ApplicationDelegate>,
|
||||
|
||||
current_theme: Cell<Option<Theme>>,
|
||||
window: Retained<WinitWindow>,
|
||||
|
||||
// During `windowDidResize`, we use this to only send Moved if the position changed.
|
||||
//
|
||||
// This is expressed in native screen coordinates.
|
||||
previous_position: Cell<Option<NSPoint>>,
|
||||
// This is expressed in desktop coordinates, and flipped to match Winit's coordinate system.
|
||||
previous_position: Cell<NSPoint>,
|
||||
|
||||
// Used to prevent redundant events.
|
||||
previous_scale_factor: Cell<f64>,
|
||||
@@ -115,6 +122,7 @@ 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!(
|
||||
@@ -183,7 +191,17 @@ declare_class!(
|
||||
#[method(windowDidChangeBackingProperties:)]
|
||||
fn window_did_change_backing_properties(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeBackingProperties:");
|
||||
self.queue_static_scale_factor_changed_event();
|
||||
let scale_factor = self.scale_factor();
|
||||
if scale_factor == self.ivars().previous_scale_factor.get() {
|
||||
return;
|
||||
};
|
||||
self.ivars().previous_scale_factor.set(scale_factor);
|
||||
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let this = self.retain();
|
||||
RunLoop::main(mtm).queue_closure(move || {
|
||||
this.handle_scale_factor_changed(scale_factor);
|
||||
});
|
||||
}
|
||||
|
||||
#[method(windowDidBecomeKey:)]
|
||||
@@ -230,7 +248,7 @@ declare_class!(
|
||||
None => {
|
||||
let current_monitor = self.current_monitor_inner();
|
||||
*fullscreen = Some(Fullscreen::Borderless(current_monitor));
|
||||
}
|
||||
},
|
||||
}
|
||||
self.ivars().in_fullscreen_transition.set(true);
|
||||
}
|
||||
@@ -261,11 +279,9 @@ declare_class!(
|
||||
let mut options = proposed_options;
|
||||
let fullscreen = self.ivars().fullscreen.borrow();
|
||||
if let Some(Fullscreen::Exclusive(_)) = &*fullscreen {
|
||||
options = NSApplicationPresentationOptions(
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen.0
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock.0
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar.0,
|
||||
);
|
||||
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
}
|
||||
|
||||
options
|
||||
@@ -316,14 +332,12 @@ declare_class!(
|
||||
self.ivars().in_fullscreen_transition.set(false);
|
||||
self.ivars().target_fullscreen.replace(None);
|
||||
if self.ivars().initial_fullscreen.get() {
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
self.window(),
|
||||
performSelector: sel!(toggleFullScreen:),
|
||||
withObject: ptr::null::<AnyObject>(),
|
||||
afterDelay: 0.5,
|
||||
];
|
||||
self.window().performSelector_withObject_afterDelay(
|
||||
sel!(toggleFullScreen:),
|
||||
None,
|
||||
0.5,
|
||||
)
|
||||
};
|
||||
} else {
|
||||
self.restore_state_from_fullscreen();
|
||||
@@ -334,8 +348,7 @@ declare_class!(
|
||||
#[method(windowDidChangeOcclusionState:)]
|
||||
fn window_did_change_occlusion_state(&self, _: Option<&AnyObject>) {
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
let visible = self.window().occlusionState().0 & NSWindowOcclusionState::Visible.0
|
||||
== NSWindowOcclusionState::Visible.0;
|
||||
let visible = self.window().occlusionState().contains(NSWindowOcclusionState::Visible);
|
||||
self.queue_event(WindowEvent::Occluded(!visible));
|
||||
}
|
||||
|
||||
@@ -359,11 +372,9 @@ declare_class!(
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap();
|
||||
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
|
||||
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
|
||||
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
let path = PathBuf::from(file.to_string());
|
||||
@@ -387,11 +398,9 @@ declare_class!(
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: Id<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb
|
||||
.propertyListForType(unsafe { NSFilenamesPboardType })
|
||||
.unwrap();
|
||||
let filenames: Id<NSArray<NSString>> = unsafe { Id::cast(filenames) };
|
||||
let pb: Retained<NSPasteboard> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType }).unwrap();
|
||||
let filenames: Retained<NSArray<NSString>> = unsafe { Retained::cast(filenames) };
|
||||
|
||||
filenames.into_iter().for_each(|file| {
|
||||
let path = PathBuf::from(file.to_string());
|
||||
@@ -415,34 +424,71 @@ declare_class!(
|
||||
}
|
||||
}
|
||||
|
||||
// Key-Value Observing
|
||||
unsafe impl WindowDelegate {
|
||||
// Observe theme change
|
||||
#[method(effectiveAppearanceDidChange:)]
|
||||
fn effective_appearance_did_change(this: *mut Self, sender: Option<&AnyObject>) {
|
||||
trace_scope!("effectiveAppearanceDidChange:");
|
||||
unsafe {
|
||||
msg_send![
|
||||
this,
|
||||
performSelectorOnMainThread: sel!(effectiveAppearanceDidChangedOnMainThread:),
|
||||
withObject: sender,
|
||||
waitUntilDone: false,
|
||||
]
|
||||
}
|
||||
}
|
||||
#[method(observeValueForKeyPath:ofObject:change:context:)]
|
||||
fn observe_value(
|
||||
&self,
|
||||
key_path: Option<&NSString>,
|
||||
_object: Option<&AnyObject>,
|
||||
change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
|
||||
_context: *mut c_void,
|
||||
) {
|
||||
trace_scope!("observeValueForKeyPath:ofObject:change:context:");
|
||||
// NOTE: We don't _really_ need to check the key path, as there should only be one, but
|
||||
// in the future we might want to observe other key paths.
|
||||
if key_path == Some(ns_string!("effectiveAppearance")) {
|
||||
let change = change.expect("requested a change dictionary in `addObserver`, but none was provided");
|
||||
let old = change.get(unsafe { NSKeyValueChangeOldKey }).expect("requested change dictionary did not contain `NSKeyValueChangeOldKey`");
|
||||
let new = change.get(unsafe { NSKeyValueChangeNewKey }).expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");
|
||||
|
||||
#[method(effectiveAppearanceDidChangedOnMainThread:)]
|
||||
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&AnyObject>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let theme = get_ns_theme(mtm);
|
||||
let old_theme = self.ivars().current_theme.replace(Some(theme));
|
||||
if old_theme != Some(theme) {
|
||||
self.queue_event(WindowEvent::ThemeChanged(theme));
|
||||
// SAFETY: The value of `effectiveAppearance` is `NSAppearance`
|
||||
let old: *const AnyObject = old;
|
||||
let old: *const NSAppearance = old.cast();
|
||||
let old: &NSAppearance = unsafe { &*old };
|
||||
let new: *const AnyObject = new;
|
||||
let new: *const NSAppearance = new.cast();
|
||||
let new: &NSAppearance = unsafe { &*new };
|
||||
|
||||
trace!(old = %unsafe { old.name() }, new = %unsafe { new.name() }, "effectiveAppearance changed");
|
||||
|
||||
// Ignore the change if the window's theme is customized by the user (since in that
|
||||
// case the `effectiveAppearance` is only emitted upon said customization, and then
|
||||
// it's triggered directly by a user action, and we don't want to emit the event).
|
||||
if unsafe { self.window().appearance() }.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let old = appearance_to_theme(old);
|
||||
let new = appearance_to_theme(new);
|
||||
// Check that the theme changed in Winit's terms (the theme might have changed on
|
||||
// other parameters, such as level of contrast, but the event should not be emitted
|
||||
// in those cases).
|
||||
if old == new {
|
||||
return;
|
||||
}
|
||||
|
||||
self.queue_event(WindowEvent::ThemeChanged(new));
|
||||
} else {
|
||||
panic!("unknown observed keypath {key_path:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
fn new_window(attrs: &WindowAttributes, mtm: MainThreadMarker) -> Option<Id<WinitWindow>> {
|
||||
impl Drop for WindowDelegate {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.window().removeObserver_forKeyPath(self, ns_string!("effectiveAppearance"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_window(
|
||||
app_delegate: &ApplicationDelegate,
|
||||
attrs: &WindowAttributes,
|
||||
mtm: MainThreadMarker,
|
||||
) -> Option<Retained<WinitWindow>> {
|
||||
autoreleasepool(|_| {
|
||||
let screen = match attrs.fullscreen.clone().map(Into::into) {
|
||||
Some(Fullscreen::Borderless(Some(monitor)))
|
||||
@@ -487,38 +533,38 @@ fn new_window(attrs: &WindowAttributes, mtm: MainThreadMarker) -> Option<Id<Wini
|
||||
// if decorations is set to false, ignore pl_attrs
|
||||
//
|
||||
// if the titlebar is hidden, ignore other pl_attrs
|
||||
NSWindowStyleMask::Borderless.0
|
||||
| NSWindowStyleMask::Resizable.0
|
||||
| NSWindowStyleMask::Miniaturizable.0
|
||||
NSWindowStyleMask::Borderless
|
||||
| NSWindowStyleMask::Resizable
|
||||
| NSWindowStyleMask::Miniaturizable
|
||||
} else {
|
||||
// default case, resizable window with titlebar and titlebar buttons
|
||||
NSWindowStyleMask::Closable.0
|
||||
| NSWindowStyleMask::Miniaturizable.0
|
||||
| NSWindowStyleMask::Resizable.0
|
||||
| NSWindowStyleMask::Titled.0
|
||||
NSWindowStyleMask::Closable
|
||||
| NSWindowStyleMask::Miniaturizable
|
||||
| NSWindowStyleMask::Resizable
|
||||
| NSWindowStyleMask::Titled
|
||||
};
|
||||
|
||||
if !attrs.resizable {
|
||||
masks &= !NSWindowStyleMask::Resizable.0;
|
||||
masks &= !NSWindowStyleMask::Resizable;
|
||||
}
|
||||
|
||||
if !attrs.enabled_buttons.contains(WindowButtons::MINIMIZE) {
|
||||
masks &= !NSWindowStyleMask::Miniaturizable.0;
|
||||
masks &= !NSWindowStyleMask::Miniaturizable;
|
||||
}
|
||||
|
||||
if !attrs.enabled_buttons.contains(WindowButtons::CLOSE) {
|
||||
masks &= !NSWindowStyleMask::Closable.0;
|
||||
masks &= !NSWindowStyleMask::Closable;
|
||||
}
|
||||
|
||||
if attrs.platform_specific.fullsize_content_view {
|
||||
masks |= NSWindowStyleMask::FullSizeContentView.0;
|
||||
masks |= NSWindowStyleMask::FullSizeContentView;
|
||||
}
|
||||
|
||||
let window: Option<Id<WinitWindow>> = unsafe {
|
||||
let window: Option<Retained<WinitWindow>> = unsafe {
|
||||
msg_send_id![
|
||||
super(mtm.alloc().set_ivars(())),
|
||||
initWithContentRect: frame,
|
||||
styleMask: NSWindowStyleMask(masks),
|
||||
styleMask: masks,
|
||||
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
||||
defer: false,
|
||||
]
|
||||
@@ -579,6 +625,7 @@ fn new_window(attrs: &WindowAttributes, mtm: MainThreadMarker) -> Option<Id<Wini
|
||||
}
|
||||
|
||||
let view = WinitView::new(
|
||||
app_delegate,
|
||||
&window,
|
||||
attrs.platform_specific.accepts_first_mouse,
|
||||
attrs.platform_specific.option_as_alt,
|
||||
@@ -605,6 +652,8 @@ fn new_window(attrs: &WindowAttributes, mtm: MainThreadMarker) -> Option<Id<Wini
|
||||
|
||||
if attrs.transparent {
|
||||
window.setOpaque(false);
|
||||
// See `set_transparent` for details on why we do this.
|
||||
window.setBackgroundColor(unsafe { Some(&NSColor::clearColor()) });
|
||||
}
|
||||
|
||||
// register for drag and drop operations.
|
||||
@@ -618,8 +667,12 @@ fn new_window(attrs: &WindowAttributes, mtm: MainThreadMarker) -> Option<Id<Wini
|
||||
}
|
||||
|
||||
impl WindowDelegate {
|
||||
pub fn new(attrs: WindowAttributes, mtm: MainThreadMarker) -> Result<Id<Self>, RootOsError> {
|
||||
let window = new_window(&attrs, mtm)
|
||||
pub(super) fn new(
|
||||
app_delegate: &ApplicationDelegate,
|
||||
attrs: WindowAttributes,
|
||||
mtm: MainThreadMarker,
|
||||
) -> Result<Retained<Self>, RootOsError> {
|
||||
let window = new_window(app_delegate, &attrs, mtm)
|
||||
.ok_or_else(|| os_error!(OsError::CreationError("couldn't create `NSWindow`")))?;
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
@@ -627,8 +680,8 @@ impl WindowDelegate {
|
||||
Some(rwh_06::RawWindowHandle::AppKit(handle)) => {
|
||||
// SAFETY: Caller ensures the pointer is valid or NULL
|
||||
// Unwrap is fine, since the pointer comes from `NonNull`.
|
||||
let parent_view: Id<NSView> =
|
||||
unsafe { Id::retain(handle.ns_view.as_ptr().cast()) }.unwrap();
|
||||
let parent_view: Retained<NSView> =
|
||||
unsafe { Retained::retain(handle.ns_view.as_ptr().cast()) }.unwrap();
|
||||
let parent = parent_view.window().ok_or_else(|| {
|
||||
os_error!(OsError::CreationError("parent view should be installed in a window"))
|
||||
})?;
|
||||
@@ -654,15 +707,14 @@ impl WindowDelegate {
|
||||
|
||||
let scale_factor = window.backingScaleFactor() as _;
|
||||
|
||||
let current_theme = match attrs.preferred_theme {
|
||||
Some(theme) => Some(theme),
|
||||
None => Some(get_ns_theme(mtm)),
|
||||
};
|
||||
if let Some(appearance) = theme_to_appearance(attrs.preferred_theme) {
|
||||
unsafe { window.setAppearance(Some(&appearance)) };
|
||||
}
|
||||
|
||||
let delegate = mtm.alloc().set_ivars(State {
|
||||
app_delegate: app_delegate.retain(),
|
||||
window: window.retain(),
|
||||
current_theme: Cell::new(current_theme),
|
||||
previous_position: Cell::new(None),
|
||||
previous_position: Cell::new(flip_window_screen_coordinates(window.frame())),
|
||||
previous_scale_factor: Cell::new(scale_factor),
|
||||
resize_increments: Cell::new(resize_increments),
|
||||
decorations: Cell::new(attrs.decorations),
|
||||
@@ -676,26 +728,29 @@ 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: Id<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
|
||||
let delegate: Retained<WindowDelegate> = unsafe { msg_send_id![super(delegate), init] };
|
||||
|
||||
if scale_factor != 1.0 {
|
||||
delegate.queue_static_scale_factor_changed_event();
|
||||
let delegate = delegate.clone();
|
||||
RunLoop::main(mtm).queue_closure(move || {
|
||||
delegate.handle_scale_factor_changed(scale_factor);
|
||||
});
|
||||
}
|
||||
window.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
|
||||
|
||||
// Enable theme change event
|
||||
let notification_center: Id<AnyObject> =
|
||||
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
|
||||
let notification_name = NSString::from_str("AppleInterfaceThemeChangedNotification");
|
||||
let _: () = unsafe {
|
||||
msg_send![
|
||||
¬ification_center,
|
||||
addObserver: &*delegate,
|
||||
selector: sel!(effectiveAppearanceDidChange:),
|
||||
name: &*notification_name,
|
||||
object: ptr::null::<AnyObject>(),
|
||||
]
|
||||
// Listen for theme change event.
|
||||
//
|
||||
// SAFETY: The observer is un-registered in the `Drop` of the delegate.
|
||||
unsafe {
|
||||
window.addObserver_forKeyPath_options_context(
|
||||
&delegate,
|
||||
ns_string!("effectiveAppearance"),
|
||||
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
|
||||
| NSKeyValueObservingOptions::NSKeyValueObservingOptionOld,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if attrs.blur {
|
||||
@@ -740,9 +795,9 @@ impl WindowDelegate {
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(super) fn view(&self) -> Id<WinitView> {
|
||||
pub(super) fn view(&self) -> Retained<WinitView> {
|
||||
// SAFETY: The view inside WinitWindow is always `WinitView`
|
||||
unsafe { Id::cast(self.window().contentView().unwrap()) }
|
||||
unsafe { Retained::cast(self.window().contentView().unwrap()) }
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -756,36 +811,40 @@ impl WindowDelegate {
|
||||
}
|
||||
|
||||
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
||||
let app_delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
|
||||
app_delegate.queue_window_event(self.window().id(), event);
|
||||
self.ivars().app_delegate.maybe_queue_window_event(self.window().id(), event);
|
||||
}
|
||||
|
||||
fn queue_static_scale_factor_changed_event(&self) {
|
||||
let scale_factor = self.scale_factor();
|
||||
if scale_factor == self.ivars().previous_scale_factor.get() {
|
||||
return;
|
||||
};
|
||||
fn handle_scale_factor_changed(&self, scale_factor: CGFloat) {
|
||||
let app_delegate = &self.ivars().app_delegate;
|
||||
let window = self.window();
|
||||
|
||||
self.ivars().previous_scale_factor.set(scale_factor);
|
||||
let content_size = self.window().contentRectForFrameRect(self.window().frame()).size;
|
||||
let content_size = window.contentRectForFrameRect(window.frame()).size;
|
||||
let content_size = LogicalSize::new(content_size.width, content_size.height);
|
||||
|
||||
let app_delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
|
||||
app_delegate.queue_static_scale_factor_changed_event(
|
||||
self.window().retain(),
|
||||
content_size.to_physical(scale_factor),
|
||||
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 {
|
||||
scale_factor,
|
||||
);
|
||||
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
|
||||
});
|
||||
let physical_size = *new_inner_size.lock().unwrap();
|
||||
drop(new_inner_size);
|
||||
|
||||
if physical_size != suggested_size {
|
||||
let logical_size = physical_size.to_logical(scale_factor);
|
||||
let size = NSSize::new(logical_size.width, logical_size.height);
|
||||
window.setContentSize(size);
|
||||
}
|
||||
app_delegate.handle_window_event(window.id(), WindowEvent::Resized(physical_size));
|
||||
}
|
||||
|
||||
fn emit_move_event(&self) {
|
||||
let frame = self.window().frame();
|
||||
if self.ivars().previous_position.get() == Some(frame.origin) {
|
||||
let position = flip_window_screen_coordinates(self.window().frame());
|
||||
if self.ivars().previous_position.get() == position {
|
||||
return;
|
||||
}
|
||||
self.ivars().previous_position.set(Some(frame.origin));
|
||||
self.ivars().previous_position.set(position);
|
||||
|
||||
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));
|
||||
@@ -803,7 +862,23 @@ impl WindowDelegate {
|
||||
}
|
||||
|
||||
pub fn set_transparent(&self, transparent: bool) {
|
||||
self.window().setOpaque(!transparent)
|
||||
// This is just a hint for Quartz, it doesn't actually speculate with window alpha.
|
||||
// Providing a wrong value here could result in visual artifacts, when the window is
|
||||
// transparent.
|
||||
self.window().setOpaque(!transparent);
|
||||
|
||||
// AppKit draws the window with a background color by default, which is usually really
|
||||
// nice, but gets in the way when we want to allow the contents of the window to be
|
||||
// transparent, as in that case, the transparent contents will just be drawn on top of
|
||||
// the background color. As such, to allow the window to be transparent, we must also set
|
||||
// the background color to one with an empty alpha channel.
|
||||
let color = if transparent {
|
||||
unsafe { NSColor::clearColor() }
|
||||
} else {
|
||||
unsafe { NSColor::windowBackgroundColor() }
|
||||
};
|
||||
|
||||
self.window().setBackgroundColor(Some(&color));
|
||||
}
|
||||
|
||||
pub fn set_blur(&self, blur: bool) {
|
||||
@@ -833,8 +908,7 @@ impl WindowDelegate {
|
||||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
let app_delegate = ApplicationDelegate::get(MainThreadMarker::from(self));
|
||||
app_delegate.queue_redraw(self.window().id());
|
||||
self.ivars().app_delegate.queue_redraw(self.window().id());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -903,8 +977,8 @@ impl WindowDelegate {
|
||||
|
||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
||||
let dimensions = dimensions.unwrap_or(Size::Logical(LogicalSize {
|
||||
width: std::f32::MAX as f64,
|
||||
height: std::f32::MAX as f64,
|
||||
width: f32::MAX as f64,
|
||||
height: f32::MAX as f64,
|
||||
}));
|
||||
let scale_factor = self.scale_factor();
|
||||
let max_size = dimensions.to_logical::<CGFloat>(scale_factor);
|
||||
@@ -958,13 +1032,13 @@ impl WindowDelegate {
|
||||
self.ivars().resizable.set(resizable);
|
||||
let fullscreen = self.ivars().fullscreen.borrow().is_some();
|
||||
if !fullscreen {
|
||||
let mut mask = self.window().styleMask().0;
|
||||
let mut mask = self.window().styleMask();
|
||||
if resizable {
|
||||
mask |= NSWindowStyleMask::Resizable.0;
|
||||
mask |= NSWindowStyleMask::Resizable;
|
||||
} else {
|
||||
mask &= !NSWindowStyleMask::Resizable.0;
|
||||
mask &= !NSWindowStyleMask::Resizable;
|
||||
}
|
||||
self.set_style_mask(NSWindowStyleMask(mask));
|
||||
self.set_style_mask(mask);
|
||||
}
|
||||
// Otherwise, we don't change the mask until we exit fullscreen.
|
||||
}
|
||||
@@ -976,23 +1050,23 @@ impl WindowDelegate {
|
||||
|
||||
#[inline]
|
||||
pub fn set_enabled_buttons(&self, buttons: WindowButtons) {
|
||||
let mut mask = self.window().styleMask().0;
|
||||
let mut mask = self.window().styleMask();
|
||||
|
||||
if buttons.contains(WindowButtons::CLOSE) {
|
||||
mask |= NSWindowStyleMask::Closable.0;
|
||||
mask |= NSWindowStyleMask::Closable;
|
||||
} else {
|
||||
mask &= !NSWindowStyleMask::Closable.0;
|
||||
mask &= !NSWindowStyleMask::Closable;
|
||||
}
|
||||
|
||||
if buttons.contains(WindowButtons::MINIMIZE) {
|
||||
mask |= NSWindowStyleMask::Miniaturizable.0;
|
||||
mask |= NSWindowStyleMask::Miniaturizable;
|
||||
} else {
|
||||
mask &= !NSWindowStyleMask::Miniaturizable.0;
|
||||
mask &= !NSWindowStyleMask::Miniaturizable;
|
||||
}
|
||||
|
||||
// This must happen before the button's "enabled" status has been set,
|
||||
// hence we do it synchronously.
|
||||
self.set_style_mask(NSWindowStyleMask(mask));
|
||||
self.set_style_mask(mask);
|
||||
|
||||
// We edit the button directly instead of using `NSResizableWindowMask`,
|
||||
// since that mask also affect the resizability of the window (which is
|
||||
@@ -1089,7 +1163,8 @@ impl WindowDelegate {
|
||||
#[inline]
|
||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let event = NSApplication::sharedApplication(mtm).currentEvent().unwrap();
|
||||
let event =
|
||||
NSApplication::sharedApplication(mtm).currentEvent().ok_or(ExternalError::Ignored)?;
|
||||
self.window().performWindowDragWithEvent(&event);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1113,9 +1188,8 @@ impl WindowDelegate {
|
||||
// we make it resizable temporarily.
|
||||
let curr_mask = self.window().styleMask();
|
||||
|
||||
let required =
|
||||
NSWindowStyleMask(NSWindowStyleMask::Titled.0 | NSWindowStyleMask::Resizable.0);
|
||||
let needs_temp_mask = !mask_contains(curr_mask, required);
|
||||
let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable;
|
||||
let needs_temp_mask = !curr_mask.contains(required);
|
||||
if needs_temp_mask {
|
||||
self.set_style_mask(required);
|
||||
}
|
||||
@@ -1132,12 +1206,12 @@ impl WindowDelegate {
|
||||
|
||||
fn saved_style(&self) -> NSWindowStyleMask {
|
||||
let base_mask =
|
||||
self.ivars().saved_style.take().unwrap_or_else(|| self.window().styleMask()).0;
|
||||
NSWindowStyleMask(if self.ivars().resizable.get() {
|
||||
base_mask | NSWindowStyleMask::Resizable.0
|
||||
self.ivars().saved_style.take().unwrap_or_else(|| self.window().styleMask());
|
||||
if self.ivars().resizable.get() {
|
||||
base_mask | NSWindowStyleMask::Resizable
|
||||
} else {
|
||||
base_mask & !NSWindowStyleMask::Resizable.0
|
||||
})
|
||||
base_mask & !NSWindowStyleMask::Resizable
|
||||
}
|
||||
}
|
||||
|
||||
/// This is called when the window is exiting fullscreen, whether by the
|
||||
@@ -1192,7 +1266,7 @@ impl WindowDelegate {
|
||||
return;
|
||||
}
|
||||
|
||||
if mask_contains(self.window().styleMask(), NSWindowStyleMask::Resizable) {
|
||||
if self.window().styleMask().contains(NSWindowStyleMask::Resizable) {
|
||||
// Just use the native zoom if resizable
|
||||
self.window().zoom(None);
|
||||
} else {
|
||||
@@ -1339,17 +1413,27 @@ impl WindowDelegate {
|
||||
}
|
||||
|
||||
match (old_fullscreen, fullscreen) {
|
||||
(None, Some(_)) => {
|
||||
(None, Some(fullscreen)) => {
|
||||
// `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`.
|
||||
let curr_mask = self.window().styleMask();
|
||||
let required =
|
||||
NSWindowStyleMask(NSWindowStyleMask::Titled.0 | NSWindowStyleMask::Resizable.0);
|
||||
if !mask_contains(curr_mask, required) {
|
||||
let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable;
|
||||
if !curr_mask.contains(required) {
|
||||
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) => {
|
||||
@@ -1357,13 +1441,7 @@ impl WindowDelegate {
|
||||
toggle_fullscreen(self.window());
|
||||
},
|
||||
(Some(Fullscreen::Exclusive(ref video_mode)), None) => {
|
||||
unsafe {
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
assert_eq!(
|
||||
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
|
||||
ffi::kCGErrorSuccess
|
||||
);
|
||||
};
|
||||
restore_and_release_display(&video_mode.monitor());
|
||||
toggle_fullscreen(self.window());
|
||||
},
|
||||
(Some(Fullscreen::Borderless(_)), Some(Fullscreen::Exclusive(_))) => {
|
||||
@@ -1377,11 +1455,10 @@ impl WindowDelegate {
|
||||
// delegate in `window:willUseFullScreenPresentationOptions:`.
|
||||
self.ivars().save_presentation_opts.set(Some(app.presentationOptions()));
|
||||
|
||||
let presentation_options = NSApplicationPresentationOptions(
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen.0
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock.0
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar.0,
|
||||
);
|
||||
let presentation_options =
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
let window_level = unsafe { ffi::CGShieldingWindowLevel() } as NSWindowLevel + 1;
|
||||
@@ -1389,19 +1466,13 @@ impl WindowDelegate {
|
||||
},
|
||||
(Some(Fullscreen::Exclusive(ref video_mode)), Some(Fullscreen::Borderless(_))) => {
|
||||
let presentation_options = self.ivars().save_presentation_opts.get().unwrap_or(
|
||||
NSApplicationPresentationOptions(NSApplicationPresentationOptions::NSApplicationPresentationFullScreen.0
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock.0
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar.0),
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
|
||||
);
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
unsafe {
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
assert_eq!(
|
||||
ffi::CGDisplayRelease(video_mode.monitor().native_identifier()),
|
||||
ffi::kCGErrorSuccess
|
||||
);
|
||||
};
|
||||
restore_and_release_display(&video_mode.monitor());
|
||||
|
||||
// Restore the normal window level following the Borderless fullscreen
|
||||
// `CGShieldingWindowLevel() + 1` hack.
|
||||
@@ -1430,19 +1501,19 @@ impl WindowDelegate {
|
||||
|
||||
let new_mask = {
|
||||
let mut new_mask = if decorations {
|
||||
NSWindowStyleMask::Closable.0
|
||||
| NSWindowStyleMask::Miniaturizable.0
|
||||
| NSWindowStyleMask::Resizable.0
|
||||
| NSWindowStyleMask::Titled.0
|
||||
NSWindowStyleMask::Closable
|
||||
| NSWindowStyleMask::Miniaturizable
|
||||
| NSWindowStyleMask::Resizable
|
||||
| NSWindowStyleMask::Titled
|
||||
} else {
|
||||
NSWindowStyleMask::Borderless.0 | NSWindowStyleMask::Resizable.0
|
||||
NSWindowStyleMask::Borderless | NSWindowStyleMask::Resizable
|
||||
};
|
||||
if !resizable {
|
||||
new_mask &= !NSWindowStyleMask::Resizable.0;
|
||||
new_mask &= !NSWindowStyleMask::Resizable;
|
||||
}
|
||||
new_mask
|
||||
};
|
||||
self.set_style_mask(NSWindowStyleMask(new_mask));
|
||||
self.set_style_mask(new_mask);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1521,7 +1592,14 @@ impl WindowDelegate {
|
||||
// Allow directly accessing the current monitor internally without unwrapping.
|
||||
pub(crate) fn current_monitor_inner(&self) -> Option<MonitorHandle> {
|
||||
let display_id = get_display_id(&*self.window().screen()?);
|
||||
Some(MonitorHandle::new(display_id))
|
||||
if let Some(monitor) = MonitorHandle::new(display_id) {
|
||||
Some(monitor)
|
||||
} else {
|
||||
// NOTE: Display ID was just fetched from live NSScreen, but can still result in `None`
|
||||
// with certain Thunderbolt docked monitors.
|
||||
warn!(display_id, "got screen with invalid display ID");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1545,7 +1623,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 = Id::as_ptr(&self.contentView().unwrap()) as *mut _;
|
||||
window_handle.ns_view = Retained::as_ptr(&self.view()) as *mut _;
|
||||
rwh_04::RawWindowHandle::AppKit(window_handle)
|
||||
}
|
||||
|
||||
@@ -1554,7 +1632,7 @@ impl WindowDelegate {
|
||||
pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
|
||||
let mut window_handle = rwh_05::AppKitWindowHandle::empty();
|
||||
window_handle.ns_window = self.window() as *const WinitWindow as *mut _;
|
||||
window_handle.ns_view = Id::as_ptr(&self.view()) as *mut _;
|
||||
window_handle.ns_view = Retained::as_ptr(&self.view()) as *mut _;
|
||||
rwh_05::RawWindowHandle::AppKit(window_handle)
|
||||
}
|
||||
|
||||
@@ -1568,8 +1646,8 @@ impl WindowDelegate {
|
||||
#[inline]
|
||||
pub fn raw_window_handle_rwh_06(&self) -> rwh_06::RawWindowHandle {
|
||||
let window_handle = rwh_06::AppKitWindowHandle::new({
|
||||
let ptr = Id::as_ptr(&self.view()) as *mut _;
|
||||
std::ptr::NonNull::new(ptr).expect("Id<T> should never be null")
|
||||
let ptr = Retained::as_ptr(&self.view()) as *mut _;
|
||||
std::ptr::NonNull::new(ptr).expect("Retained<T> should never be null")
|
||||
});
|
||||
rwh_06::RawWindowHandle::AppKit(window_handle)
|
||||
}
|
||||
@@ -1577,26 +1655,34 @@ impl WindowDelegate {
|
||||
fn toggle_style_mask(&self, mask: NSWindowStyleMask, on: bool) {
|
||||
let current_style_mask = self.window().styleMask();
|
||||
if on {
|
||||
self.set_style_mask(NSWindowStyleMask(current_style_mask.0 | mask.0));
|
||||
self.set_style_mask(current_style_mask | mask);
|
||||
} else {
|
||||
self.set_style_mask(NSWindowStyleMask(current_style_mask.0 & (!mask.0)));
|
||||
self.set_style_mask(current_style_mask & !mask);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn theme(&self) -> Option<Theme> {
|
||||
self.ivars().current_theme.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_focus(&self) -> bool {
|
||||
self.window().isKeyWindow()
|
||||
}
|
||||
|
||||
pub fn theme(&self) -> Option<Theme> {
|
||||
unsafe { self.window().appearance() }
|
||||
.map(|appearance| appearance_to_theme(&appearance))
|
||||
.or_else(|| {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
|
||||
if app.respondsToSelector(sel!(effectiveAppearance)) {
|
||||
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
|
||||
} else {
|
||||
Some(Theme::Light)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_theme(&self, theme: Option<Theme>) {
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
set_ns_theme(theme, mtm);
|
||||
self.ivars().current_theme.set(theme.or_else(|| Some(get_ns_theme(mtm))));
|
||||
unsafe { self.window().setAppearance(theme_to_appearance(theme).as_deref()) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1617,6 +1703,21 @@ 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 {
|
||||
@@ -1652,10 +1753,13 @@ impl WindowExtMacOS for WindowDelegate {
|
||||
self.ivars().is_simple_fullscreen.set(true);
|
||||
|
||||
// Simulate pre-Lion fullscreen by hiding the dock and menu bar
|
||||
let presentation_options = NSApplicationPresentationOptions(
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock.0
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar.0,
|
||||
);
|
||||
let presentation_options = if self.is_borderless_game() {
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar
|
||||
} else {
|
||||
NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar
|
||||
};
|
||||
app.setPresentationOptions(presentation_options);
|
||||
|
||||
// Hide the titlebar
|
||||
@@ -1669,11 +1773,8 @@ impl WindowExtMacOS for WindowDelegate {
|
||||
self.toggle_style_mask(NSWindowStyleMask::Miniaturizable, false);
|
||||
self.toggle_style_mask(NSWindowStyleMask::Resizable, false);
|
||||
self.window().setMovable(false);
|
||||
|
||||
true
|
||||
} else {
|
||||
let new_mask = self.saved_style();
|
||||
self.set_style_mask(new_mask);
|
||||
self.ivars().is_simple_fullscreen.set(false);
|
||||
|
||||
let save_presentation_opts = self.ivars().save_presentation_opts.get();
|
||||
@@ -1685,9 +1786,10 @@ impl WindowExtMacOS for WindowDelegate {
|
||||
|
||||
self.window().setFrame_display(frame, true);
|
||||
self.window().setMovable(true);
|
||||
|
||||
true
|
||||
self.set_style_mask(new_mask);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1751,45 +1853,52 @@ impl WindowExtMacOS for WindowDelegate {
|
||||
fn option_as_alt(&self) -> OptionAsAlt {
|
||||
self.view().option_as_alt()
|
||||
}
|
||||
}
|
||||
|
||||
fn mask_contains(mask: NSWindowStyleMask, value: NSWindowStyleMask) -> bool {
|
||||
mask.0 & value.0 == value.0
|
||||
fn set_borderless_game(&self, borderless_game: bool) {
|
||||
self.ivars().is_borderless_game.set(borderless_game);
|
||||
}
|
||||
|
||||
fn is_borderless_game(&self) -> bool {
|
||||
self.ivars().is_borderless_game.get()
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_STANDARD_FRAME: NSRect =
|
||||
NSRect::new(NSPoint::new(50.0, 50.0), NSSize::new(800.0, 600.0));
|
||||
|
||||
pub(super) fn get_ns_theme(mtm: MainThreadMarker) -> Theme {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] };
|
||||
if !has_theme {
|
||||
return Theme::Light;
|
||||
}
|
||||
let appearance = app.effectiveAppearance();
|
||||
let name = appearance
|
||||
.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
||||
NSString::from_str("NSAppearanceNameAqua"),
|
||||
NSString::from_str("NSAppearanceNameDarkAqua"),
|
||||
]))
|
||||
.unwrap();
|
||||
match &*name.to_string() {
|
||||
"NSAppearanceNameDarkAqua" => Theme::Dark,
|
||||
_ => Theme::Light,
|
||||
fn dark_appearance_name() -> &'static NSString {
|
||||
// Don't use the static `NSAppearanceNameDarkAqua` to allow linking on macOS < 10.14
|
||||
ns_string!("NSAppearanceNameDarkAqua")
|
||||
}
|
||||
|
||||
pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme {
|
||||
let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
|
||||
unsafe { NSAppearanceNameAqua.copy() },
|
||||
dark_appearance_name().copy(),
|
||||
]));
|
||||
if let Some(best_match) = best_match {
|
||||
if *best_match == *dark_appearance_name() {
|
||||
Theme::Dark
|
||||
} else {
|
||||
Theme::Light
|
||||
}
|
||||
} else {
|
||||
warn!(?appearance, "failed to determine the theme of the appearance");
|
||||
// Default to light in this case
|
||||
Theme::Light
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ns_theme(theme: Option<Theme>, mtm: MainThreadMarker) {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] };
|
||||
if has_theme {
|
||||
let appearance = theme.map(|t| {
|
||||
let name = match t {
|
||||
Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"),
|
||||
Theme::Light => NSString::from_str("NSAppearanceNameAqua"),
|
||||
};
|
||||
NSAppearance::appearanceNamed(&name).unwrap()
|
||||
});
|
||||
app.setAppearance(appearance.as_ref().map(|a| a.as_ref()));
|
||||
fn theme_to_appearance(theme: Option<Theme>) -> Option<Retained<NSAppearance>> {
|
||||
let appearance = match theme? {
|
||||
Theme::Light => unsafe { NSAppearance::appearanceNamed(NSAppearanceNameAqua) },
|
||||
Theme::Dark => NSAppearance::appearanceNamed(dark_appearance_name()),
|
||||
};
|
||||
if let Some(appearance) = appearance {
|
||||
Some(appearance)
|
||||
} else {
|
||||
warn!(?theme, "could not find appearance for theme");
|
||||
// Assume system appearance in this case
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoModeHandle as RootVideoModeHandle};
|
||||
use crate::window::Fullscreen as RootFullscreen;
|
||||
|
||||
#[cfg(windows_platform)]
|
||||
#[path = "windows/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
#[path = "linux/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(macos_platform)]
|
||||
#[path = "macos/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(android_platform)]
|
||||
#[path = "android/mod.rs"]
|
||||
mod platform;
|
||||
mod android;
|
||||
#[cfg(ios_platform)]
|
||||
#[path = "ios/mod.rs"]
|
||||
mod platform;
|
||||
#[cfg(web_platform)]
|
||||
#[path = "web/mod.rs"]
|
||||
mod platform;
|
||||
mod ios;
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
mod linux;
|
||||
#[cfg(macos_platform)]
|
||||
mod macos;
|
||||
#[cfg(orbital_platform)]
|
||||
#[path = "orbital/mod.rs"]
|
||||
mod platform;
|
||||
mod orbital;
|
||||
#[cfg(web_platform)]
|
||||
mod web;
|
||||
#[cfg(windows_platform)]
|
||||
mod windows;
|
||||
|
||||
#[cfg(android_platform)]
|
||||
use android as platform;
|
||||
#[cfg(ios_platform)]
|
||||
use ios as platform;
|
||||
#[cfg(any(x11_platform, wayland_platform))]
|
||||
use linux as platform;
|
||||
#[cfg(macos_platform)]
|
||||
use macos as platform;
|
||||
#[cfg(orbital_platform)]
|
||||
use orbital as platform;
|
||||
#[cfg(web_platform)]
|
||||
use web as platform;
|
||||
#[cfg(windows_platform)]
|
||||
use windows as platform;
|
||||
|
||||
pub use self::platform::*;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::keyboard::{
|
||||
PhysicalKey,
|
||||
};
|
||||
use crate::window::{
|
||||
CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId,
|
||||
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -775,6 +775,11 @@ 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(
|
||||
|
||||
@@ -103,7 +103,7 @@ pub struct WindowId {
|
||||
|
||||
impl WindowId {
|
||||
pub const fn dummy() -> Self {
|
||||
WindowId { fd: u64::max_value() }
|
||||
WindowId { fd: u64::MAX }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ impl<'a> WindowProperties<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for WindowProperties<'a> {
|
||||
impl fmt::Display for WindowProperties<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user