Compare commits

..

1 Commits

Author SHA1 Message Date
John Nunley
19477d3f03 doc: Add installation instructions to docs
This commit adds installation instructions to the top-level
documentation for lib.rs. The main goal is to give the dependencies that
X11/Wayland use in a formal place, since it's not written down anywhere
to my knowledge.

Signed-off-by: John Nunley <dev@notgull.net>
2024-12-08 08:30:31 -08:00
296 changed files with 14298 additions and 14856 deletions

31
.github/CODEOWNERS vendored
View File

@@ -1,26 +1,31 @@
# Android
/winit-android @MarijnS95
/src/platform/android.rs @MarijnS95
/src/platform_impl/android @MarijnS95
# Apple (AppKit + UIKit)
/winit-appkit @madsmtm
/winit-uikit @madsmtm
/winit-common/src/core_foundation @madsmtm
/winit-common/src/event_handler.rs @madsmtm
/src/platform/ios.rs @madsmtm
/src/platform/macos.rs @madsmtm
/src/platform_impl/apple @madsmtm
# XKB
/winit-common/src/xkb @kchibisov
# Unix
/src/platform_impl/linux/mod.rs @kchibisov
# Wayland
/winit-wayland @kchibisov
/src/platform/wayland.rs @kchibisov
/src/platform_impl/linux/wayland @kchibisov
# X11
/winit-x11 @kchibisov
/src/platform/x11.rs @kchibisov @notgull
/src/platform_impl/linux/x11 @kchibisov @notgull
# Web
/winit-web @daxpedda
/src/platform/web.rs @daxpedda
/src/platform_impl/web @daxpedda
# Windows (Win32) (UNOWNED)
#/winit-win32
# Windows
/src/platform/windows.rs @notgull
/src/platform_impl/windows @notgull
# Orbital (Redox OS)
/winit-orbital @jackpot51
/src/platform/orbital.rs @jackpot51
/src/platform_impl/orbital @jackpot51

View File

@@ -55,7 +55,7 @@ jobs:
strategy:
fail-fast: false
matrix:
toolchain: [stable, nightly, '1.80']
toolchain: [stable, nightly, '1.73']
platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
@@ -66,7 +66,7 @@ jobs:
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package winit --features=android-native-activity', cmd: 'apk -- ' }
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS x86_64', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'macOS Aarch64', target: aarch64-apple-darwin, os: macos-latest, }
@@ -74,22 +74,18 @@ jobs:
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
- { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude:
# Web on nightly needs extra arguments
# Web on nightly needs extra arguments
- toolchain: nightly
platform: { name: 'Web' }
# Rustup is broken.
- toolchain: nightly
platform: { name: 'Windows 32bit GNU' }
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest }
# Android is tested on stable-3
- toolchain: '1.80'
platform: { name: 'Android' }
- toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
# Redox OS doesn't follow MSRV
- toolchain: '1.80'
platform: { name: 'Redox OS' }
- toolchain: '1.73'
platform: { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest }
include:
- toolchain: '1.80'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package winit --features=android-native-activity', cmd: 'apk -- ' }
- toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'nightly'
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, test-options: -Zdoctest-xcompile }
- toolchain: 'nightly'
@@ -179,70 +175,17 @@ jobs:
- name: Build crate
run: cargo $CMD build $OPTIONS
- name: Test winit core
run: cargo test -p winit-core
- name: Test winit Android
if: contains(matrix.platform.target, 'android')
run: cargo $CMD test -p winit-android --features native-activity --no-run
- name: Test winit Common (EventHandler)
run: cargo $CMD test -p winit-common --features event-handler --no-run
- name: Test winit Common (CF)
if: contains(matrix.platform.target, 'apple')
run: cargo $CMD test -p winit-common --features core-foundation --no-run
- name: Test winit Common (XKB)
if: contains(matrix.platform.target, 'linux-gnu')
run: cargo $CMD test -p winit-common --features xkb,x11,wayland --no-run
- name: Test winit AppKit
if: contains(matrix.platform.target, 'macos')
run: cargo $CMD test -p winit-appkit $OPTIONS
- name: Test winit Orbital
if: contains(matrix.platform.target, 'redox')
run: cargo test -p winit-orbital
- name: Test winit UIKit
if: contains(matrix.platform.target, 'ios')
# TODO: Run on Simulator
run: cargo $CMD test -p winit-uikit $OPTIONS --no-run
- name: Test winit Web
if: contains(matrix.platform.target, 'wasm')
run: cargo $CMD test -p winit-web $OPTIONS --no-run
- name: Test winit Win32
if: contains(matrix.platform.target, 'windows')
run: cargo $CMD test -p winit-win32 $OPTIONS
- name: Test winit X11
if: contains(matrix.platform.target, 'linux-gnu')
run: cargo $CMD test -p winit-x11 --target=${{ matrix.platform.target }}
- name: Test winit Wayland
if: contains(matrix.platform.target, 'linux-gnu')
run: cargo $CMD test -p winit-wayland --target=${{ matrix.platform.target }}
# Test only on Linux x86_64, so we avoid spending unnecessary CI hours.
- name: Test dpi crate
if: >
contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.80'
matrix.toolchain != '1.73'
run: cargo test -p dpi
- name: Check dpi crate (no_std)
if: >
contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.80'
run: cargo check -p dpi --no-default-features
- name: Build tests
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.80'
matrix.toolchain != '1.73'
run: cargo $CMD test --no-run $OPTIONS
- name: Run tests
@@ -251,7 +194,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.80'
matrix.toolchain != '1.73'
run: cargo $CMD test $OPTIONS
- name: Lint with clippy
@@ -261,7 +204,7 @@ jobs:
- name: Build tests with serde enabled
if: >
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.80'
matrix.toolchain != '1.73'
run: cargo $CMD test --no-run $OPTIONS $TEST_OPTIONS --features serde
- name: Run tests with serde enabled
@@ -270,7 +213,7 @@ jobs:
!contains(matrix.platform.target, 'ios') &&
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') &&
!contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.80'
matrix.toolchain != '1.73'
run: cargo $CMD test $OPTIONS $TEST_OPTIONS --features serde
- name: Check docs.rs documentation
@@ -313,7 +256,6 @@ jobs:
with:
command: check
log-level: error
manifest-path: winit/Cargo.toml
arguments: --all-features --target ${{ matrix.platform.target }}
eslint:
@@ -322,7 +264,7 @@ jobs:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./winit-web/src/script
working-directory: ./src/platform_impl/web/script
steps:
- uses: taiki-e/checkout-action@v1
@@ -337,7 +279,7 @@ jobs:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./winit-web/src/script
working-directory: ./src/platform_impl/web/script
steps:
- uses: taiki-e/checkout-action@v1

View File

@@ -19,7 +19,7 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
@@ -41,7 +41,7 @@ jobs:
done
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
uses: actions/upload-pages-artifact@v3
with:
path: target/doc

View File

@@ -3,6 +3,6 @@ Changelog entries should be put in the [`changelog::unreleased`].
The changelog can also be viewed [on docs.rs][docs_rs] or [on the current
master docs][master_docs].
[`changelog::unreleased`]: winit/src/changelog/unreleased.md
[`changelog::unreleased`]: src/changelog/unreleased.md
[docs_rs]: https://docs.rs/winit/latest/winit/changelog/index.html
[master_docs]: https://rust-windowing.github.io/winit/winit/changelog/index.html

View File

@@ -13,29 +13,12 @@ To save your time it's wise to check already opened [pull requests][prs] and
accepted, however larger new API proposals should go into the issue first. When
in doubt contact us on [Matrix][matrix] or via opening an issue.
### Windows
To run the examples on Windows, you must have symlinks enabled, see
[this][git-windows-symlinks].
[git-windows-symlinks]: https://gitforwindows.org/symbolic-links.html
### Submitting your work and handling review
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. For details on how to use nightly, consult [the
documentation][toolchains].
When editing markdown files (`.md`) they must be wrapped at 80 characters.
[toolchains]: https://rust-lang.github.io/rustup/concepts/toolchains.html
#### Handling review
During the review process certain events could require an action from your side,

View File

@@ -1,104 +1,364 @@
[workspace]
default-members = ["winit"]
members = ["dpi", "winit*"]
resolver = "2"
[package]
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
categories = ["gui"]
description = "Cross-platform window creation library."
documentation = "https://docs.rs/winit"
edition.workspace = true
include = [
"/build.rs",
"/docs",
"/examples",
"/FEATURES.md",
"/LICENSE",
"/src",
"!/src/platform_impl/web/script",
"/src/platform_impl/web/script/**/*.min.js",
"/tests",
]
keywords = ["windowing"]
license.workspace = true
name = "winit"
readme = "README.md"
repository.workspace = true
rust-version.workspace = true
version = "0.30.5"
[workspace.package]
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/rust-windowing/winit"
rust-version = "1.80"
version = "0.30.12"
[package.metadata.docs.rs]
features = [
"serde",
"mint",
# Enabled to get docs to compile
"android-native-activity",
]
# These are all tested in CI
rustdoc-args = ["--cfg", "docsrs"]
targets = [
# Windows
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
# macOS
"aarch64-apple-darwin",
"x86_64-apple-darwin",
# Unix (X11 & Wayland)
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# iOS
"aarch64-apple-ios",
# Android
"aarch64-linux-android",
# Web
"wasm32-unknown-unknown",
]
[workspace.dependencies]
# Workspace dependencies.
# `winit` has no version here to allow using it in dev deps for docs.
winit = { path = "winit" }
winit-android = { version = "0.30.12", path = "winit-android" }
winit-appkit = { version = "0.30.12", path = "winit-appkit" }
winit-common = { version = "0.30.12", path = "winit-common" }
winit-core = { version = "0.30.12", path = "winit-core" }
winit-orbital = { version = "0.30.12", path = "winit-orbital" }
winit-uikit = { version = "0.30.12", path = "winit-uikit" }
winit-wayland = { version = "0.30.12", path = "winit-wayland", default-features = false }
winit-web = { version = "0.30.12", path = "winit-web" }
winit-win32 = { version = "0.30.12", path = "winit-win32" }
winit-x11 = { version = "0.30.12", path = "winit-x11" }
# Features are documented in either `lib.rs` or under `winit::platform`.
[features]
android-game-activity = ["android-activity/game-activity"]
android-native-activity = ["android-activity/native-activity"]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
mint = ["dpi/mint"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde", "bitflags/serde"]
wayland = [
"wayland-client",
"wayland-backend",
"wayland-protocols",
"wayland-protocols-plasma",
"sctk",
"ahash",
"memmap2",
]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
wayland-dlopen = ["wayland-backend/dlopen"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
# Core dependencies.
bitflags = "2"
[build-dependencies]
cfg_aliases = "0.2.1"
[dependencies]
bitflags = "2"
cursor-icon = "1.1.0"
dpi = { version = "0.1.2", path = "dpi" }
keyboard-types = "0.8.0"
mint = "0.5.6"
dpi = { version = "0.1.1", path = "dpi" }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
serde = { version = "1", features = ["serde_derive"] }
serde = { workspace = true, optional = true }
smol_str = "0.3"
tracing = { version = "0.1.40", default-features = false }
# Dev dependencies.
image = { version = "0.25.0", 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-subscriber = { version = "0.3.18", features = ["env-filter"] }
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
softbuffer = { version = "0.4.6", default-features = false, features = [
"x11",
"x11-dlopen",
"wayland",
"wayland-dlopen",
] }
tracing-subscriber = "0.3.18"
# Android dependencies.
# Android
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0"
ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
# Apple dependencies.
block2 = "0.6.1"
dispatch2 = { version = "0.3.0", default-features = false, features = ["std", "objc2"] }
objc2 = { version = "0.6.1", features = ["relax-sign-encoding"] }
objc2-app-kit = { version = "0.3.1", default-features = false }
objc2-core-foundation = { version = "0.3.1", default-features = false }
objc2-core-graphics = { version = "0.3.1", default-features = false }
objc2-core-video = { version = "0.3.1", default-features = false }
objc2-foundation = { version = "0.3.1", default-features = false }
objc2-ui-kit = { version = "0.3.1", default-features = false }
# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
block2 = "0.5.1"
core-foundation = "0.9.3"
objc2 = "0.5.2"
# Windows dependencies.
# AppKit
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.23.1"
objc2-app-kit = { version = "0.2.2", features = [
"NSAppearance",
"NSApplication",
"NSBitmapImageRep",
"NSButton",
"NSColor",
"NSControl",
"NSCursor",
"NSDragging",
"NSEvent",
"NSGraphics",
"NSGraphicsContext",
"NSImage",
"NSImageRep",
"NSMenu",
"NSMenuItem",
"NSOpenGLView",
"NSPasteboard",
"NSResponder",
"NSRunningApplication",
"NSScreen",
"NSTextInputClient",
"NSTextInputContext",
"NSToolbar",
"NSView",
"NSWindow",
"NSWindowScripting",
"NSWindowTabGroup",
] }
objc2-foundation = { version = "0.2.2", features = [
"block2",
"dispatch",
"NSArray",
"NSAttributedString",
"NSData",
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSGeometry",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSOperation",
"NSPathUtilities",
"NSProcessInfo",
"NSRunLoop",
"NSString",
"NSThread",
"NSValue",
] }
# UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2-foundation = { version = "0.2.2", features = [
"block2",
"dispatch",
"NSArray",
"NSEnumerator",
"NSGeometry",
"NSObjCRuntime",
"NSOperation",
"NSString",
"NSProcessInfo",
"NSThread",
"NSSet",
] }
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",
] }
# Windows
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
windows-sys = "0.59.0"
windows-sys = { version = "0.52.0", features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Media",
"Win32_System_Com_StructuredStorage",
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_Security",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_System_WindowsProgramming",
"Win32_UI_Accessibility",
"Win32_UI_Controls",
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Input_Pointer",
"Win32_UI_Input_Touch",
"Win32_UI_Shell",
"Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging",
] }
# Linux dependencies.
ahash = { version = "0.8.7", features = ["no-rng"] }
bytemuck = { version = "1.13.1", default-features = false }
# Linux
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.13.0"
libc = "0.2.64"
memmap2 = "0.9.0"
percent-encoding = "2.0"
rustix = { version = "1.0.7", default-features = false }
memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
rustix = { version = "0.38.4", default-features = false, features = [
"std",
"system",
"thread",
"process",
] }
sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [
"calloop",
] }
sctk-adwaita = { version = "0.10.1", default-features = false }
wayland-backend = { version = "0.3.10", default-features = false, features = ["client_system"] }
wayland-client = "0.31.10"
wayland-protocols = { version = "0.32.8", features = ["staging"] }
wayland-protocols-plasma = { version = "0.3.8", features = ["client"] }
x11-dl = "2.19.1"
x11rb = { version = "0.13.0", default-features = false }
], optional = true }
sctk-adwaita = { version = "0.10.1", default-features = false, optional = true }
wayland-backend = { version = "0.3.5", default-features = false, features = [
"client_system",
], optional = true }
wayland-client = { version = "0.31.4", optional = true }
wayland-protocols = { version = "0.32.2", features = ["staging"], optional = true }
wayland-protocols-plasma = { version = "0.3.2", features = ["client"], optional = true }
x11-dl = { version = "2.19.1", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = [
"allow-unsafe-code",
"cursor",
"dl-libxcb",
"randr",
"resource_manager",
"sync",
"xinput",
"xkb",
], optional = true }
xkbcommon-dl = "0.4.2"
# Orbital dependencies.
# Orbital
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.5.7"
# Web dependencies.
atomic-waker = "1"
concurrent-queue = { version = "2", default-features = false }
console_error_panic_hook = "0.1"
# Web
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.70"
pin-project = "1"
tracing-web = "0.1"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
wasm-bindgen-test = "0.3"
web-time = "1"
web_sys = { package = "web-sys", version = "0.3.70" }
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",
"HtmlHtmlElement",
"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"
concurrent-queue = { version = "2", default-features = false }
[target.'cfg(target_family = "wasm")'.dev-dependencies]
console_error_panic_hook = "0.1"
tracing-web = "0.1"
wasm-bindgen-test = "0.3"
[[example]]
doc-scrape-examples = true
name = "window"
[[example]]
name = "child_window"
[workspace]
members = ["dpi"]
resolver = "2"
[workspace.package]
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/rust-windowing/winit"
rust-version = "1.73"
[workspace.dependencies]
mint = "0.5.6"
serde = { version = "1", features = ["serde_derive"] }

View File

@@ -8,7 +8,7 @@
```toml
[dependencies]
winit = "0.30.12"
winit = "0.30.5"
```
## [Documentation](https://docs.rs/winit)
@@ -33,13 +33,9 @@ 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.80**. Changes to
This crate's Minimum Supported Rust Version (MSRV) is **1.73**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following
@@ -71,9 +67,3 @@ same MSRV policy.
### Platform-specific usage
Check out the [`winit::platform`](https://docs.rs/winit/latest/winit/platform/index.html) module for platform-specific usage.
### Repository License
Note that the license in `LICENSE` doesn't apply in full to the DPI package [./dpi](./dpi).
Full details can be found in that folder's README.
<!-- This doesn't apply to users of the Winit crate, but this is also the repository level README -->

View File

@@ -1,17 +1,16 @@
# Using allow-invalid because this is platform-specific code
disallowed-methods = [
{ allow-invalid = true, path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ allow-invalid = true, path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
{ allow-invalid = true, path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ allow-invalid = true, path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ allow-invalid = true, path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::window", reason = "is not available in every context" },
{ path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
{ path = "objc2_app_kit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::window", reason = "is not available in every context" },
]

View File

@@ -38,12 +38,8 @@ private = { ignore = true }
[bans]
multiple-versions = "deny"
skip = [
{ 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" },
]
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
skip = [{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" }]
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
[bans.build]
include-archives = true
@@ -55,10 +51,6 @@ allow = [
]
crate = "android-activity"
[[bans.build.bypass]]
allow-globs = ["ci/*", "githooks/*"]
crate = "zerocopy"
[[bans.build.bypass]]
allow-globs = ["freetype2/*"]
crate = "freetype-sys"

18
docs/ATTRIBUTION.md Normal file
View File

@@ -0,0 +1,18 @@
# Image Attribution
These images are used in the documentation of `winit`.
## keyboard_*.svg
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
which have been released under the same license as a derivative work.
## `coordinate-systems*`
These files are created by [Mads Marquart](https://github.com/madsmtm) using
[draw.io](https://draw.io/), and compressed using [svgomg.net](https://svgomg.net/).
They are licensed under the [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -11,10 +11,7 @@ Unreleased` header.
## Unreleased
## 0.1.2
- Added `Insets`, `LogicalInsets` and `PhysicalInsets` types.
- Make `no_std` compatible. If you use this functionality, DPI's license has changed.
## 0.1.1

View File

@@ -3,22 +3,16 @@ categories = ["gui"]
description = "Types for handling UI scaling"
edition.workspace = true
keywords = ["DPI", "HiDPI", "scale-factor"]
# N.B. This is "AND", because of the imported libm code.
license = "Apache-2.0 AND MIT"
license.workspace = true
name = "dpi"
repository.workspace = true
rust-version.workspace = true
version = "0.1.2"
version = "0.1.1"
[features]
default = ["std"]
mint = ["dep:mint"]
serde = ["dep:serde"]
# Access mathematical functions using the standard library implementations
std = []
[dependencies]
mint = { workspace = true, optional = true }
serde = { workspace = true, optional = true }

View File

@@ -1,51 +0,0 @@
rust-lang/libm as a whole is available for use under the MIT license:
------------------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
This Rust library contains the following copyrights:
Copyright (c) 2018 Jorge Aparicio
Portions of this software are derived from third-party works licensed under
terms compatible with the above MIT license:
* musl libc https://www.musl-libc.org/. This library contains the following
copyright:
Copyright © 2005-2020 Rich Felker, et al.
* The CORE-MATH project https://core-math.gitlabpages.inria.fr/. CORE-MATH
routines are available under the MIT license on a per-file basis.
The musl libc COPYRIGHT file also includes the following notice relevant to
math portions of the library:
------------------------------------------------------------------------------
Much of the math library code (src/math/* and src/complex/*) is
Copyright © 1993,2004 Sun Microsystems or
Copyright © 2003-2011 David Schultz or
Copyright © 2003-2009 Steven G. Kargl or
Copyright © 2003-2009 Bruce D. Evans or
Copyright © 2008 Stephen L. Moshier or
Copyright © 2017-2018 Arm Limited
and labelled as such in comments in the individual source files. All
have been licensed under extremely permissive terms.
------------------------------------------------------------------------------

View File

@@ -1,18 +0,0 @@
# DPI
Full docs can be found on docs.rs.
## License
Most of DPI is licensed under the Apache License, Version 2.0 ([LICENSE](LICENSE)).
All files except for `src/libm.rs` (and `LICENSE-LIBM-MIT`) are available solely under that license.
For its `no_std` support, DPI uses code from the [libm](https://crates.io/crates/libm) crate.
This is in the `libm.rs` file, and is licensed solely under the MIT Licence ([LICENSE-LIBM-MIT](LICENSE-LIBM-MIT)).
That file contains details of all potentially applicable copyright notices.
This is feature gated to only be included if you disable the `std` feature, otherwise it will not be compiled into your final binary
(and so these license terms will not apply).
Overall, this means that the license for this crate depends on what features you have enabled.
If you enable the `std` feature, then DPI uses only code available under the Apache-2.0 license, and so can be used under the terms of that license.
However, if you disable the `std` feature, then both these licenses must be followed to use the crate as a whole.

View File

@@ -54,25 +54,13 @@
//!
//! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
//! * `mint`: Enables mint (math interoperability standard types) conversions.
//! * `std` (enabled by default): Uses the standard library mathematical functions (normally through
//! your target platform's libm). This feature also changes the library's license from `Apache-2.0
//! AND MIT` to `APACHE-2.0` (only). For full details, see the package README.
//!
//! To use this library on a target without the standard library available, you should disable
//! default features (thus disabling the `std` feature, with the license consequences thereof).
//!
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
#![cfg_attr(feature = "std", forbid(unsafe_code))]
#![no_std]
#[cfg(not(feature = "std"))]
mod libm;
#[cfg(any(feature = "std", test))]
extern crate std;
#![forbid(unsafe_code)]
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@@ -86,32 +74,32 @@ pub trait Pixel: Copy + Into<f64> {
impl Pixel for u8 {
fn from_f64(f: f64) -> Self {
round(f) as u8
f.round() as u8
}
}
impl Pixel for u16 {
fn from_f64(f: f64) -> Self {
round(f) as u16
f.round() as u16
}
}
impl Pixel for u32 {
fn from_f64(f: f64) -> Self {
round(f) as u32
f.round() as u32
}
}
impl Pixel for i8 {
fn from_f64(f: f64) -> Self {
round(f) as i8
f.round() as i8
}
}
impl Pixel for i16 {
fn from_f64(f: f64) -> Self {
round(f) as i16
f.round() as i16
}
}
impl Pixel for i32 {
fn from_f64(f: f64) -> Self {
round(f) as i32
f.round() as i32
}
}
impl Pixel for f32 {
@@ -125,15 +113,6 @@ impl Pixel for f64 {
}
}
/// Round f to the closest integer, rounding away from `0.0`
#[inline]
fn round(f: f64) -> f64 {
#[cfg(feature = "std")]
return f.round();
#[cfg(not(feature = "std"))]
return libm::round(f);
}
/// Checks that the scale factor is a normal positive `f64`.
///
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing
@@ -1291,20 +1270,20 @@ mod tests {
// Eat coverage for the Debug impls et al
#[test]
fn ensure_attrs_do_not_panic() {
let _ = std::format!("{:?}", LogicalPosition::<u32>::default().clone());
let _ = format!("{:?}", LogicalPosition::<u32>::default().clone());
HashSet::new().insert(LogicalPosition::<u32>::default());
let _ = std::format!("{:?}", PhysicalPosition::<u32>::default().clone());
let _ = format!("{:?}", PhysicalPosition::<u32>::default().clone());
HashSet::new().insert(PhysicalPosition::<u32>::default());
let _ = std::format!("{:?}", LogicalSize::<u32>::default().clone());
let _ = format!("{:?}", LogicalSize::<u32>::default().clone());
HashSet::new().insert(LogicalSize::<u32>::default());
let _ = std::format!("{:?}", PhysicalSize::<u32>::default().clone());
let _ = format!("{:?}", PhysicalSize::<u32>::default().clone());
HashSet::new().insert(PhysicalSize::<u32>::default());
let _ = std::format!("{:?}", Size::Physical((1, 2).into()).clone());
let _ = std::format!("{:?}", Position::Physical((1, 2).into()).clone());
let _ = format!("{:?}", Size::Physical((1, 2).into()).clone());
let _ = format!("{:?}", Position::Physical((1, 2).into()).clone());
}
#[test]

View File

@@ -1,56 +0,0 @@
// Copyright (c) 2018 Jorge Aparicio
// Copyright © 2005-2020 Rich Felker, et al.
// Copyright © 1993,2004 Sun Microsystems or
// Copyright © 2003-2011 David Schultz or
// Copyright © 2003-2009 Steven G. Kargl or
// Copyright © 2003-2009 Bruce D. Evans or
// Copyright © 2008 Stephen L. Moshier or
// Copyright © 2017-2018 Arm Limited
// SPDX-License-Identifier: MIT
// This file is licensed solely under the terms discussed in LICENSE-LIBM-MIT at the crate root.
// See the package-level README for full details.
// Taken from https://github.com/rust-lang/libm/blob/master/src/math/mod.rs#L1
macro_rules! force_eval {
($e:expr) => {
unsafe { ::core::ptr::read_volatile(&$e) }
};
}
// Taken from https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/round.rs
pub(crate) fn round(x: f64) -> f64 {
trunc(x + copysign(0.5 - 0.25 * f64::EPSILON, x))
}
// Adapted from: https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/trunc.rs#L8-L12
#[allow(clippy::needless_late_init /*, reason = "The original libm code uses this style" */)]
fn trunc(x: f64) -> f64 {
let x1p120 = f64::from_bits(0x4770000000000000); // 0x1p120f === 2 ^ 120
let mut i: u64 = x.to_bits();
let mut e: i64 = ((i >> 52) & 0x7ff) as i64 - 0x3ff + 12;
let m: u64;
if e >= 52 + 12 {
return x;
}
if e < 12 {
e = 1;
}
m = -1i64 as u64 >> e;
if (i & m) == 0 {
return x;
}
force_eval!(x + x1p120);
i &= !m;
f64::from_bits(i)
}
// Taken from https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/copysign.rs
fn copysign(x: f64, y: f64) -> f64 {
let mut ux = x.to_bits();
let uy = y.to_bits();
ux &= (!0) >> 1;
ux |= uy & (1 << 63);
f64::from_bits(ux)
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,22 +13,10 @@ fn main() -> Result<(), impl std::error::Error> {
#[path = "util/fill.rs"]
mod fill;
#[derive(Debug)]
struct WindowData {
window: Box<dyn Window>,
color: u32,
}
impl WindowData {
fn new(window: Box<dyn Window>, color: u32) -> Self {
Self { window, color }
}
}
#[derive(Default, Debug)]
#[derive(Default)]
struct Application {
parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, WindowData>,
windows: HashMap<WindowId, Box<dyn Window>>,
}
impl ApplicationHandler for Application {
@@ -38,10 +26,11 @@ fn main() -> Result<(), impl std::error::Error> {
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap();
println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), WindowData::new(window, 0xffbbbbbb));
self.windows.insert(window.id(), window);
}
fn window_event(
@@ -67,24 +56,15 @@ fn main() -> Result<(), impl std::error::Error> {
event: KeyEvent { state: ElementState::Pressed, .. },
..
} => {
let child_index = self.windows.len() - 1;
let child_color =
0xff000000 + 3_u32.pow((child_index + 2).rem_euclid(16) as u32);
let parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window =
spawn_child_window(parent_window.window.as_ref(), event_loop, child_index);
let child_window = spawn_child_window(parent_window.as_ref(), event_loop);
let child_id = child_window.id();
println!("Child window created with id: {child_id:?}");
self.windows.insert(child_id, WindowData::new(child_window, child_color));
self.windows.insert(child_id, child_window);
},
WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get(&window_id) {
if window_id == self.parent_window_id.unwrap() {
fill::fill_window(window.window.as_ref());
} else {
fill::fill_window_with_color(window.window.as_ref(), window.color);
}
fill::fill_window(window.as_ref());
}
},
_ => (),
@@ -95,20 +75,12 @@ fn main() -> Result<(), impl std::error::Error> {
fn spawn_child_window(
parent: &dyn Window,
event_loop: &dyn ActiveEventLoop,
child_count: usize,
) -> Box<dyn Window> {
let parent = parent.raw_window_handle().unwrap();
// As child count increases, x goes from 0*128 to 5*128 and then repeats
let x: f64 = child_count.rem_euclid(5) as f64 * 128.0;
// After 5 windows have been put side by side horizontally, a new row starts
let y: f64 = (child_count / 5) as f64 * 96.0;
let mut window_attributes = WindowAttributes::default()
.with_title("child window")
.with_surface_size(LogicalSize::new(128.0f32, 96.0))
.with_position(Position::Logical(LogicalPosition::new(x, y)))
.with_surface_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window.
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };

View File

@@ -46,7 +46,7 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.run_app(ControlFlowDemo::default())
}
#[derive(Default, Debug)]
#[derive(Default)]
struct ControlFlowDemo {
mode: Mode,
request_redraw: bool,

View File

@@ -1,65 +0,0 @@
use std::error::Error;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
#[path = "util/tracing.rs"]
mod tracing;
fn main() -> Result<(), Box<dyn Error>> {
tracing::init();
let event_loop = EventLoop::new()?;
let app = Application::new();
Ok(event_loop.run_app(app)?)
}
/// Application state and event handling.
#[derive(Debug)]
struct Application {
window: Option<Box<dyn Window>>,
}
impl Application {
fn new() -> Self {
Self { window: None }
}
}
impl ApplicationHandler for Application {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes =
WindowAttributes::default().with_title("Drag and drop files on me!");
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::DragLeft { .. }
| WindowEvent::DragEntered { .. }
| WindowEvent::DragMoved { .. }
| WindowEvent::DragDropped { .. } => {
println!("{event:?}");
},
WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap();
window.pre_present_notify();
fill::fill_window(window.as_ref());
},
WindowEvent::CloseRequested => {
event_loop.exit();
},
_ => {},
}
}
}

View File

@@ -9,14 +9,14 @@ fn main() -> std::process::ExitCode {
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::pump_events::{EventLoopExtPumpEvents, PumpStatus};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
#[derive(Default, Debug)]
#[derive(Default)]
struct PumpDemo {
window: Option<Box<dyn Window>>,
}

View File

@@ -7,14 +7,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
#[derive(Default, Debug)]
#[derive(Default)]
struct App {
idx: usize,
window_id: Option<WindowId>,

View File

@@ -9,12 +9,7 @@
#[allow(unused_imports)]
pub use platform::cleanup_window;
#[allow(unused_imports)]
pub use platform::fill_window;
#[allow(unused_imports)]
pub use platform::fill_window_with_animated_color;
#[allow(unused_imports)]
pub use platform::fill_window_with_color;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod platform {
@@ -23,12 +18,8 @@ mod platform {
use std::mem;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
#[cfg(all(not(android_platform), not(web_platform)))]
use std::time::Instant;
use softbuffer::{Context, Surface};
#[cfg(all(web_platform, not(android_platform)))]
use web_time::Instant;
use winit::window::{Window, WindowId};
thread_local! {
@@ -79,7 +70,7 @@ mod platform {
}
}
pub fn fill_window_with_color(window: &dyn Window, color: u32) {
pub fn fill_window(window: &dyn Window) {
GC.with(|gc| {
let size = window.surface_size();
let (Some(width), Some(height)) =
@@ -93,31 +84,17 @@ mod platform {
let surface =
gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
// Fill a buffer with a solid color
// Fill a buffer with a solid color.
const DARK_GRAY: u32 = 0xff181818;
surface.resize(width, height).expect("Failed to resize the softbuffer surface");
let mut buffer = surface.buffer_mut().expect("Failed to get the softbuffer buffer");
buffer.fill(color);
buffer.fill(DARK_GRAY);
buffer.present().expect("Failed to present the softbuffer buffer");
})
}
#[allow(dead_code)]
pub fn fill_window(window: &dyn Window) {
fill_window_with_color(window, 0xff181818);
}
#[allow(dead_code)]
pub fn fill_window_with_animated_color(window: &dyn Window, start: Instant) {
let time = start.elapsed().as_secs_f32() * 1.5;
let blue = (time.sin() * 255.0) as u32;
let green = ((time.cos() * 255.0) as u32) << 8;
let red = ((1.0 - time.sin() * 255.0) as u32) << 16;
let color = red | green | blue;
fill_window_with_color(window, color);
}
#[allow(dead_code)]
pub fn cleanup_window(window: &dyn Window) {
GC.with(|gc| {
@@ -131,24 +108,10 @@ mod platform {
#[cfg(any(target_os = "android", target_os = "ios"))]
mod platform {
#[allow(dead_code)]
pub fn fill_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn fill_window_with_color(_window: &dyn winit::window::Window, _color: u32) {
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn fill_window_with_animated_color(
_window: &dyn winit::window::Window,
_start: std::time::Instant,
) {
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn cleanup_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms.

File diff suppressed because it is too large Load Diff

View File

@@ -6,13 +6,12 @@ fn main() -> Result<(), Box<dyn Error>> {
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::x11::WindowAttributesX11;
use winit::platform::x11::WindowAttributesExtX11;
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
mod fill;
#[derive(Debug)]
pub struct XEmbedDemo {
parent_window_id: u32,
window: Option<Box<dyn Window>>,
@@ -20,12 +19,10 @@ fn main() -> Result<(), Box<dyn Error>> {
impl ApplicationHandler for XEmbedDemo {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let mut window_attributes = WindowAttributes::default()
let window_attributes = WindowAttributes::default()
.with_title("An embedded window!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0));
let x11_attrs =
WindowAttributesX11::default().with_embed_parent_window(self.parent_window_id);
window_attributes = window_attributes.with_platform_attributes(Box::new(x11_attrs));
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0))
.with_embed_parent_window(self.parent_window_id);
self.window = Some(event_loop.create_window(window_attributes).unwrap());
}

View File

@@ -2,11 +2,11 @@
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::event_loop::ActiveEventLoop;
#[cfg(any(docsrs, macos_platform))]
use crate::platform::macos::ApplicationHandlerExtMacOS;
use crate::window::WindowId;
pub mod macos;
/// The handler of application-level events.
/// The handler of the application events.
pub trait ApplicationHandler {
/// Emitted when new events arrive from the OS to be processed.
///
@@ -57,6 +57,7 @@ pub trait ApplicationHandler {
///
/// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended()
/// [`exiting()`]: Self::exiting()
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
@@ -125,9 +126,8 @@ pub trait ApplicationHandler {
/// use std::thread;
/// use std::time::Duration;
///
/// use winit::event_loop::EventLoop;
/// use winit_core::application::ApplicationHandler;
/// use winit_core::event_loop::ActiveEventLoop;
/// use winit::application::ApplicationHandler;
/// use winit::event_loop::{ActiveEventLoop, EventLoop};
///
/// struct MyApp {
/// receiver: mpsc::Receiver<u64>,
@@ -162,6 +162,8 @@ pub trait ApplicationHandler {
///
/// let (sender, receiver) = mpsc::channel();
///
/// let mut app = MyApp { receiver };
///
/// // Send an event in a loop
/// let proxy = event_loop.create_proxy();
/// let background_thread = thread::spawn(move || {
@@ -169,7 +171,7 @@ pub trait ApplicationHandler {
/// loop {
/// println!("sending: {i}");
/// if sender.send(i).is_err() {
/// // Stop sending once the receiver is dropped
/// // Stop sending once `MyApp` is dropped
/// break;
/// }
/// // Trigger the wake-up _after_ we placed the event in the channel.
@@ -180,8 +182,9 @@ pub trait ApplicationHandler {
/// }
/// });
///
/// event_loop.run_app(MyApp { receiver })?;
/// event_loop.run_app(&mut app)?;
///
/// drop(app);
/// background_thread.join().unwrap();
///
/// Ok(())
@@ -200,8 +203,6 @@ pub trait ApplicationHandler {
);
/// Emitted when the OS sends an event to a device.
///
/// Whether device events are delivered depends on the backend in use.
fn device_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
@@ -257,7 +258,7 @@ pub trait ApplicationHandler {
/// to the user. This is a good place to stop refreshing UI, running animations and other visual
/// things. It is driven by Android's [`onStop()`] method.
///
/// After this event the application either receives [`resumed()`] again, or will exit.
/// After this event the application either receives [`resumed()`] again, or [`exiting()`].
///
/// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop()
///
@@ -267,6 +268,7 @@ pub trait ApplicationHandler {
///
/// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended()
/// [`exiting()`]: Self::exiting()
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
@@ -308,6 +310,14 @@ pub trait ApplicationHandler {
let _ = event_loop;
}
/// 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 exit right after.
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted when the application has received a memory warning.
///
/// ## Platform-specific
@@ -339,8 +349,9 @@ pub trait ApplicationHandler {
/// The macOS-specific handler.
///
/// The return value from this should not change at runtime.
#[cfg(any(docsrs, macos_platform))]
#[inline(always)]
fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
None
}
}
@@ -402,13 +413,19 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
(**self).destroy_surfaces(event_loop);
}
#[inline]
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).exiting(event_loop);
}
#[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop);
}
#[cfg(any(docsrs, macos_platform))]
#[inline]
fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
(**self).macos_handler()
}
}
@@ -470,13 +487,19 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
(**self).destroy_surfaces(event_loop);
}
#[inline]
fn exiting(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).exiting(event_loop);
}
#[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop);
}
#[cfg(any(docsrs, macos_platform))]
#[inline]
fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
(**self).macos_handler()
}
}

View File

@@ -59,48 +59,32 @@ changelog entry.
- Implement `Clone`, `Copy`, `Debug`, `Deserialize`, `Eq`, `Hash`, `Ord`, `PartialEq`, `PartialOrd`
and `Serialize` on many types.
- Add `MonitorHandle::current_video_mode()`.
- 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`.
- Add `ApplicationHandlerExtMacOS` trait, and a `macos_handler` method to `ApplicationHandler` which returns a `dyn ApplicationHandlerExtMacOS` which allows for macOS specific extensions to winit.
- Add a `standard_key_binding` method to the `ApplicationHandlerExtMacOS` trait. This allows handling of standard keybindings such as "go to end of line" on macOS.
- On macOS, add `WindowExtMacOS::set_unified_titlebar` and `WindowAttributesMacOS::with_unified_titlebar`
- 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 macOS, add `WindowExtMacOS::set_unified_titlebar` and `WindowAttributesExtMacOS::with_unified_titlebar`
to use a larger style of titlebar.
- Add `WindowId::into_raw()` and `from_raw()`.
- Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId`, `primary` and `position` to all
pointer events as part of the pointer event overhaul.
- Add `DeviceId::into_raw()` and `from_raw()`.
- 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.
- Added `Window::surface_position`, which is the position of the surface inside the window.
- Added `Window::safe_area`, which describes the area of the surface that is unobstructed.
- Added `Window::min_surface_size`.
- Added `Window::max_surface_size`.
- Added `Window::is_transparent`.
- Added `Window::is_blurred`.
- Added `Window::window_level`.
- Added `Window::window_icon`.
- Added `Window::content_protected`.
- Added `Window::cursor`.
- On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes.
- Add ability to make non-activating window on macOS using `NSPanel` with `NSWindowStyleMask::NonactivatingPanel`.
- Implement `MonitorHandleProvider` for `MonitorHandle` to access common monitor API.
- On X11, set an "area" attribute on XIM input connection to convey the cursor area.
- Implement `CustomCursorProvider` for `CustomCursor` to access cursor API.
- Add `CustomCursorSource::Url`, `CustomCursorSource::from_animation`.
- Implement `CustomIconProvider` for `RgbaIcon`.
- Add `icon` module that exposes winit's icon API.
- `VideoMode::new` to create a `VideoMode`.
- `keyboard::ModifiersKey` to track which modifier is exactly pressed.
- `ActivationToken::as_raw` to get a ref to raw token.
- Each platform now has corresponding `WindowAttributes` struct instead of trait extension.
- On Wayland, added implementation for `Window::set_window_icon`
- Add `Window::request_ime_update` to atomically apply set of IME changes.
- Add `Ime::DeleteSurrounding` to let the input method delete text.
### Changed
- Change `ActiveEventLoop` and `Window` to be traits, and added `cast_ref`/`cast_mut`/`cast`
methods to extract the backend type from those.
- Change `ActiveEventLoop` to be a trait.
- Change `Window` to be a trait.
- `ActiveEventLoop::create_window` now returns `Box<dyn Window>`.
- `ApplicationHandler` now uses `dyn ActiveEventLoop`.
- On Web, let events wake up event loop immediately when using `ControlFlow::Poll`.
- Bump MSRV from `1.70` to `1.80`.
- Bump MSRV from `1.70` to `1.73`.
- Changed `ApplicationHandler::user_event` to `user_wake_up`, removing the
generic user event.
@@ -129,8 +113,10 @@ changelog entry.
- Changed how `ModifiersState` is serialized by Serde.
- `VideoModeHandle::refresh_rate_millihertz()` and `bit_depth()` now return a `Option<NonZero*>`.
- `MonitorHandle::position()` now returns an `Option`.
- On macOS, remove custom application delegates. You are now allowed to override the
- On iOS and macOS, 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.
- On X11, remove our dependency on libXcursor. (#3749)
- Renamed the following APIs to make it clearer that the sizes apply to the underlying surface:
- `WindowEvent::Resized` to `SurfaceResized`.
@@ -179,42 +165,6 @@ changelog entry.
- On macOS, no longer emit `Focused` upon window creation.
- On iOS, emit more events immediately, instead of queuing them.
- Update `smol_str` to version `0.3`
- Rename `VideoModeHandle` to `VideoMode`, now it only stores plain data.
- Make `Fullscreen::Exclusive` contain `(MonitorHandle, VideoMode)`.
- Reworked the file drag-and-drop API.
- On macOS, the default menu uses the bundle name or falls back to the process name as before.
The `WindowEvent::DroppedFile`, `WindowEvent::HoveredFile` and `WindowEvent::HoveredFileCancelled`
events have been removed, and replaced with `WindowEvent::DragEntered`, `WindowEvent::DragMoved`,
`WindowEvent::DragDropped` and `WindowEvent::DragLeft`.
The old drag-and-drop events were emitted once per file. This occurred when files were *first*
hovered over the window, dropped, or left the window. The new drag-and-drop events are emitted
once per set of files dragged, and include a list of all dragged files. They also include the
pointer position.
The rough correspondence is:
- `WindowEvent::HoveredFile` -> `WindowEvent::DragEntered`
- `WindowEvent::DroppedFile` -> `WindowEvent::DragDropped`
- `WindowEvent::HoveredFileCancelled` -> `WindowEvent::DragLeft`
The `WindowEvent::DragMoved` event is entirely new, and is emitted whenever the pointer moves
whilst files are being dragged over the window. It doesn't contain any file paths, just the
pointer position.
- Updated `objc2` to `v0.6`.
- Updated `windows-sys` to `v0.59`.
- To match the corresponding changes in `windows-sys`, the `HWND`, `HMONITOR`, and `HMENU` types
now alias to `*mut c_void` instead of `isize`.
- Removed `KeyEventExtModifierSupplement`, and made the fields `text_with_all_modifiers` and
`key_without_modifiers` public on `KeyEvent` instead.
- Move `window::Fullscreen` to `monitor::Fullscreen`.
- Renamed "super" key to "meta", to match the naming in the W3C specification.
`NamedKey::Super` still exists, but it's non-functional and deprecated, `NamedKey::Meta` should be used instead.
- Move `IconExtWindows` into `WinIcon`.
- Move `EventLoopExtPumpEvents` and `PumpStatus` from platform module to `winit::event_loop::pump_events`.
- Move `EventLoopExtRunOnDemand` from platform module to `winit::event_loop::run_on_demand`.
- Use `NamedKey`, `Code` and `Location` from the `keyboard-types` v0.8 crate.
- Deprecate `Window::set_ime_allowed`, `Window::set_ime_cursor_area`, and `Window::set_ime_purpose`.
### Removed
@@ -246,26 +196,23 @@ changelog entry.
- Remove `WindowEvent::Touch` and `Touch` in favor of the new `PointerKind`, `PointerSource` and
`ButtonSource` as part of the new pointer event overhaul.
- Remove `Force::altitude_angle`.
- Remove `Window::inner_position`, use the new `Window::surface_position` instead.
- Remove `CustomCursorExtWeb`, use the `CustomCursorSource`.
- Remove `CustomCursor::from_rgba`, use `CustomCursorSource` instead.
- Remove `ApplicationHandler::exited`, the event loop being shut down can now be listened to in
the `Drop` impl on the application handler.
- Remove `NamedKey::Space`, match on `Key::Character(" ")` instead.
- Remove `PartialEq` impl for `WindowAttributes`.
- `WindowAttributesExt*` platform extensions; use `WindowAttributes*` instead.
- Removed `Window::inner_position`, use the new `Window::surface_position` instead.
### Fixed
- On Orbital, `MonitorHandle::name()` now returns `None` instead of a dummy name.
- On Orbital, implement `fullscreen`.
- 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 iOS, fixed `SurfaceResized` and `Window::surface_size` not reporting the size of the actual surface.
- On macOS, fixed the scancode conversion for audio volume keys.
- On macOS, fixed the scancode conversion for `IntlBackslash`.
- On macOS, fixed redundant `SurfaceResized` event at window creation.
- On macOS, don't panic on monitors with unknown bit-depths.
- On macOS, fixed crash when closing the window on macOS 26+.
- On Windows, account for mouse wheel lines per scroll setting for `WindowEvent::MouseWheel`.
- On Windows, `Window::theme` will return the correct theme after setting it through `Window::set_theme`.
- On Windows, `Window::set_theme` will change the title bar color immediately now.
- On Windows 11, prevent incorrect shifting when dragging window onto a monitor with different DPI.
- On macOS, fix crash when pressing Caps Lock in certain configurations.
- On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.

View File

@@ -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 losing focus.
- On Web, fix touch input not gaining or loosing 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()`.

View File

@@ -1,102 +1,3 @@
## 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

View File

@@ -1,16 +1,13 @@
use core::fmt;
use std::error::Error;
use std::hash::Hash;
use std::ops::Deref;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use std::time::Duration;
#[doc(inline)]
pub use cursor_icon::CursorIcon;
use cursor_icon::CursorIcon;
use crate::as_any::{impl_dyn_casting, AsAny};
use crate::platform_impl::{PlatformCustomCursor, PlatformCustomCursorSource};
/// The maximum width and height for a cursor when using [`CustomCursorSource::from_rgba`].
/// The maximum width and height for a cursor when using [`CustomCursor::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4;
@@ -51,23 +48,22 @@ impl From<CustomCursor> for Cursor {
/// # Example
///
/// ```no_run
/// # use winit_core::event_loop::ActiveEventLoop;
/// # use winit_core::window::Window;
/// # use winit::event_loop::ActiveEventLoop;
/// # use winit::window::Window;
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) {
/// use winit_core::cursor::CustomCursorSource;
/// use winit::window::CustomCursor;
///
/// let w = 10;
/// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize];
///
/// #[cfg(not(target_family = "wasm"))]
/// let source = CustomCursorSource::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
/// let source = CustomCursor::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
///
/// #[cfg(target_family = "wasm")]
/// let source = CustomCursorSource::Url {
/// url: String::from("http://localhost:3000/cursor.png"),
/// hotspot_x: 0,
/// hotspot_y: 0,
/// let source = {
/// use winit::platform::web::CustomCursorExtWeb;
/// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
/// };
///
/// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) {
@@ -75,96 +71,50 @@ impl From<CustomCursor> for Cursor {
/// }
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct CustomCursor(pub Arc<dyn CustomCursorProvider>);
pub trait CustomCursorProvider: AsAny + fmt::Debug + Send + Sync {
/// Whether a cursor was backed by animation.
fn is_animated(&self) -> bool;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CustomCursor {
/// Platforms should make sure this is cheap to clone.
pub(crate) inner: PlatformCustomCursor,
}
impl PartialEq for CustomCursor {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
impl CustomCursor {
/// Creates a new cursor from an rgba buffer.
///
/// The alpha channel is assumed to be **not** premultiplied.
pub fn from_rgba(
rgba: impl Into<Vec<u8>>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<CustomCursorSource, BadImage> {
let _span =
tracing::debug_span!("winit::Cursor::from_rgba", width, height, hotspot_x, hotspot_y)
.entered();
Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::from_rgba(
rgba.into(),
width,
height,
hotspot_x,
hotspot_y,
)?,
})
}
}
impl Eq for CustomCursor {}
impl Hash for CustomCursor {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl Deref for CustomCursor {
type Target = dyn CustomCursorProvider;
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl_dyn_casting!(CustomCursorProvider);
/// Source for [`CustomCursor`].
///
/// See [`CustomCursor`] for more details.
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum CustomCursorSource {
/// Cursor that is backed by RGBA image.
///
/// See [CustomCursorSource::from_rgba] for more.
///
/// ## Platform-specific
///
/// - **iOS / Android / Orbital:** Unsupported
Image(CursorImage),
/// Animated cursor.
///
/// See [CustomCursorSource::from_animation] for more.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported
Animation(CursorAnimation),
/// Creates a new cursor from a URL pointing to an image.
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
///
/// [PNG]: https://en.wikipedia.org/wiki/PNG
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported
Url { hotspot_x: u16, hotspot_y: u16, url: String },
pub struct CustomCursorSource {
// Some platforms don't support custom cursors.
#[allow(dead_code)]
pub(crate) inner: PlatformCustomCursorSource,
}
impl CustomCursorSource {
/// Creates a new cursor from an rgba buffer.
///
/// The alpha channel is assumed to be **not** premultiplied.
pub fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self::Image)
}
/// Crates a new animated cursor from multiple [`CustomCursor`]s
/// Supplied `cursors` can't be empty or other animations.
pub fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<Self, BadAnimation> {
CursorAnimation::new(duration, cursors).map(Self::Animation)
}
}
/// An error produced when using [`CustomCursorSource::from_rgba`] with invalid arguments.
/// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BadImage {
@@ -214,29 +164,47 @@ impl fmt::Display for BadImage {
impl Error for BadImage {}
/// An error produced when using [`CustomCursorSource::from_animation`] with invalid arguments.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BadAnimation {
/// Produced when no cursors were supplied.
Empty,
/// Produced when a supplied cursor is an animation.
Animation,
}
/// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with
/// images.
#[allow(dead_code)]
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
impl fmt::Display for BadAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "No cursors supplied"),
Self::Animation => write!(f, "A supplied cursor is an animation"),
}
#[allow(dead_code)]
impl OnlyCursorImageSource {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self)
}
}
impl Error for BadAnimation {}
/// Platforms export this directly as `PlatformCustomCursor` if they don't implement caching.
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub(crate) struct OnlyCursorImage(pub(crate) Arc<CursorImage>);
impl Hash for OnlyCursorImage {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl PartialEq for OnlyCursorImage {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for OnlyCursorImage {}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct CursorImage {
#[allow(dead_code)]
pub(crate) struct CursorImage {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u16,
pub(crate) height: u16,
@@ -277,60 +245,22 @@ impl CursorImage {
Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y })
}
pub fn buffer(&self) -> &[u8] {
self.rgba.as_slice()
}
pub fn buffer_mut(&mut self) -> &mut [u8] {
self.rgba.as_mut_slice()
}
pub fn width(&self) -> u16 {
self.width
}
pub fn height(&self) -> u16 {
self.height
}
pub fn hotspot_x(&self) -> u16 {
self.hotspot_x
}
pub fn hotspot_y(&self) -> u16 {
self.hotspot_y
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CursorAnimation {
pub(crate) duration: Duration,
pub(crate) cursors: Vec<CustomCursor>,
}
// Platforms that don't support cursors will export this as `PlatformCustomCursor`.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct NoCustomCursor;
impl CursorAnimation {
pub fn new(duration: Duration, cursors: Vec<CustomCursor>) -> Result<Self, BadAnimation> {
if cursors.is_empty() {
return Err(BadAnimation::Empty);
}
if cursors.iter().any(|cursor| cursor.is_animated()) {
return Err(BadAnimation::Animation);
}
Ok(Self { duration, cursors })
}
pub fn duration(&self) -> Duration {
self.duration
}
pub fn cursors(&self) -> &[CustomCursor] {
self.cursors.as_slice()
}
pub fn into_raw(self) -> (Duration, Vec<CustomCursor>) {
(self.duration, self.cursors)
#[allow(dead_code)]
impl NoCustomCursor {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
Ok(Self)
}
}

View File

@@ -19,13 +19,7 @@ pub enum EventLoopError {
impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RecreationAttempt => {
write!(
f,
"EventLoop can't be recreated, only a single instance of it is supported (for \
cross-platform compatibility)"
)
},
Self::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
Self::Os(err) => err.fmt(f),
Self::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
Self::NotSupported(err) => err.fmt(f),
@@ -106,7 +100,7 @@ pub struct NotSupportedError {
}
impl NotSupportedError {
pub fn new(reason: &'static str) -> Self {
pub(crate) fn new(reason: &'static str) -> Self {
Self { reason }
}
}
@@ -127,7 +121,8 @@ pub struct OsError {
}
impl OsError {
pub fn new(
#[allow(dead_code)]
pub(crate) fn new(
line: u32,
file: &'static str,
error: impl Into<Box<dyn Error + Send + Sync + 'static>>,

View File

@@ -1,19 +1,118 @@
//! The event enums and assorted supporting types.
//!
//! These are sent to the closure given to [`EventLoop::run_app(...)`], where they get
//! processed and used to modify the program state. For more details, see the root-level
//! documentation.
//!
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
//! approximate the basic ordering loop of [`EventLoop::run_app(...)`] like this:
//!
//! ```rust,ignore
//! let mut start_cause = StartCause::Init;
//!
//! while !elwt.exiting() {
//! app.new_events(event_loop, start_cause);
//!
//! for event in (window events, user events, device events) {
//! // This will pick the right method on the application based on the event.
//! app.handle_event(event_loop, event);
//! }
//!
//! for window_id in (redraw windows) {
//! app.window_event(event_loop, window_id, RedrawRequested);
//! }
//!
//! app.about_to_wait(event_loop);
//! start_cause = wait_if_necessary();
//! }
//!
//! app.exiting(event_loop);
//! ```
//!
//! This leaves out timing details like [`ControlFlow::WaitUntil`] but hopefully
//! describes what happens in what order.
//!
//! [`EventLoop::run_app(...)`]: crate::event_loop::EventLoop::run_app
//! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil
use std::path::PathBuf;
use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))]
use std::time::Instant;
use dpi::{PhysicalPosition, PhysicalSize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
#[cfg(web_platform)]
use web_time::Instant;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::RequestError;
use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
use crate::platform_impl;
#[cfg(doc)]
use crate::window::Window;
use crate::window::{ActivationToken, Theme};
use crate::Instant;
use crate::window::{ActivationToken, Theme, WindowId};
// TODO: Remove once the backends can call `ApplicationHandler` methods directly. For now backends
// like Windows and Web require `Event` to wire user events, otherwise each backend will have to
// wrap `Event` in some other structure.
/// Describes a generic event.
///
/// See the module-level docs for more information on the event loop manages each event.
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Event {
/// See [`ApplicationHandler::new_events()`] for details.
///
/// [`ApplicationHandler::new_events()`]: crate::application::ApplicationHandler::new_events()
NewEvents(StartCause),
/// See [`ApplicationHandler::window_event()`] for details.
///
/// [`ApplicationHandler::window_event()`]: crate::application::ApplicationHandler::window_event()
#[allow(clippy::enum_variant_names)]
WindowEvent { window_id: WindowId, event: WindowEvent },
/// See [`ApplicationHandler::device_event()`] for details.
///
/// [`ApplicationHandler::device_event()`]: crate::application::ApplicationHandler::device_event()
#[allow(clippy::enum_variant_names)]
DeviceEvent { device_id: Option<DeviceId>, event: DeviceEvent },
/// See [`ApplicationHandler::suspended()`] for details.
///
/// [`ApplicationHandler::suspended()`]: crate::application::ApplicationHandler::suspended()
Suspended,
/// See [`ApplicationHandler::can_create_surfaces()`] for details.
///
/// [`ApplicationHandler::can_create_surfaces()`]: crate::application::ApplicationHandler::can_create_surfaces()
CreateSurfaces,
/// See [`ApplicationHandler::resumed()`] for details.
///
/// [`ApplicationHandler::resumed()`]: crate::application::ApplicationHandler::resumed()
Resumed,
/// See [`ApplicationHandler::about_to_wait()`] for details.
///
/// [`ApplicationHandler::about_to_wait()`]: crate::application::ApplicationHandler::about_to_wait()
AboutToWait,
/// See [`ApplicationHandler::exiting()`] for details.
///
/// [`ApplicationHandler::exiting()`]: crate::application::ApplicationHandler::exiting()
LoopExiting,
/// See [`ApplicationHandler::memory_warning()`] for details.
///
/// [`ApplicationHandler::memory_warning()`]: crate::application::ApplicationHandler::memory_warning()
MemoryWarning,
/// User requested a wake up.
UserWakeUp,
}
/// Describes the reason the event loop is resuming.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -43,6 +142,10 @@ pub enum StartCause {
#[derive(Debug, Clone, PartialEq)]
pub enum WindowEvent {
/// The activation token was delivered back and now could be used.
#[cfg_attr(not(any(x11_platform, wayland_platform)), allow(rustdoc::broken_intra_doc_links))]
/// Delivered in response to [`request_activation_token`].
///
/// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token
ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken },
/// The size of the window's surface has changed.
@@ -72,42 +175,28 @@ pub enum WindowEvent {
/// The window has been destroyed.
Destroyed,
/// A file drag operation has entered the window.
DragEntered {
/// List of paths that are being dragged onto the window.
paths: Vec<PathBuf>,
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
position: PhysicalPosition<f64>,
},
/// A file drag operation has moved over the window.
DragMoved {
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
position: PhysicalPosition<f64>,
},
/// The file drag operation has dropped file(s) on the window.
DragDropped {
/// List of paths that are being dragged onto the window.
paths: Vec<PathBuf>,
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
position: PhysicalPosition<f64>,
},
/// The file drag operation has been cancelled or left the window.
DragLeft {
/// (x,y) coordinates in pixels relative to the top-left corner of the window. May be
/// negative on some platforms if something is dragged over a window's decorations (title
/// bar, frame, etc).
///
/// ## Platform-specific
///
/// - **Windows:** Always emits [`None`].
position: Option<PhysicalPosition<f64>>,
},
/// A file is being hovered over the window.
///
/// When the user hovers multiple files at once, this event will be emitted for each file
/// separately.
HoveredFile(PathBuf),
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted for each file
/// separately.
///
/// The support for this is known to be incomplete, see [#720] for more
/// information.
///
/// [#720]: https://github.com/rust-windowing/winit/issues/720
DroppedFile(PathBuf),
/// A file was hovered, but has exited the window.
///
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
/// hovered.
HoveredFileCancelled,
/// The window gained or lost focus.
///
@@ -480,7 +569,7 @@ pub enum PointerSource {
/// - **MacOS / Orbital / Wayland / X11:** Always emits [`None`].
/// - **Android:** 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).
/// [android documentation](https://developer.android.com/reference/android/view/MotionEvent#AXIS_PRESSURE).#[derive(Debug, Clone, Copy, PartialEq)]
/// - **Web:** Will never be [`None`]. If the device doesn't support pressure sensitivity,
/// force will be 0.5 when a button is pressed or 0.0 otherwise.
force: Option<Force>,
@@ -556,14 +645,16 @@ impl DeviceId {
/// Convert the [`DeviceId`] into the underlying integer.
///
/// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
pub const fn into_raw(self) -> i64 {
#[allow(dead_code)]
pub(crate) const fn into_raw(self) -> i64 {
self.0
}
/// Construct a [`DeviceId`] from the underlying integer.
///
/// This should only be called with integers returned from [`DeviceId::into_raw`].
pub const fn from_raw(id: i64) -> Self {
#[allow(dead_code)]
pub(crate) const fn from_raw(id: i64) -> Self {
Self(id)
}
}
@@ -579,14 +670,16 @@ impl FingerId {
/// Convert the [`FingerId`] into the underlying integer.
///
/// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
pub const fn into_raw(self) -> usize {
#[allow(dead_code)]
pub(crate) const fn into_raw(self) -> usize {
self.0
}
/// Construct a [`FingerId`] from the underlying integer.
///
/// This should only be called with integers returned from [`FingerId::into_raw`].
pub const fn from_raw(id: usize) -> Self {
#[allow(dead_code)]
pub(crate) const fn from_raw(id: usize) -> Self {
Self(id)
}
}
@@ -595,12 +688,10 @@ impl FingerId {
///
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera
/// or first-person game controls. Many physical actions, such as mouse movement, can produce both
/// device and [window events]. Because window events typically arise from virtual devices
/// device and window events. Because window events typically arise from virtual devices
/// (corresponding to GUI pointers and keyboard focus) the device IDs may not match.
///
/// Note that these events are delivered regardless of input focus.
///
/// [window events]: WindowEvent
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DeviceEvent {
/// Change in physical position of a pointing device.
@@ -611,8 +702,17 @@ pub enum DeviceEvent {
/// ## Platform-specific
///
/// **Web:** Only returns raw data, not OS accelerated, if [`CursorGrabMode::Locked`] is used
/// and browser support is available.
/// and browser support is available, see
#[cfg_attr(
any(web_platform, docsrs),
doc = "[`ActiveEventLoopExtWeb::is_cursor_lock_raw()`][crate::platform::web::ActiveEventLoopExtWeb::is_cursor_lock_raw()]."
)]
#[cfg_attr(
not(any(web_platform, docsrs)),
doc = "`ActiveEventLoopExtWeb::is_cursor_lock_raw()`."
)]
///
#[rustfmt::skip]
/// [`CursorGrabMode::Locked`]: crate::window::CursorGrabMode::Locked
PointerMotion {
/// (x, y) change in position in unspecified units.
@@ -678,6 +778,12 @@ pub struct KeyEvent {
/// you somehow see this in the wild, we'd like to know :)
pub physical_key: keyboard::PhysicalKey,
// Allowing `broken_intra_doc_links` for `logical_key`, because
// `key_without_modifiers` is not available on all platforms
#[cfg_attr(
not(any(windows_platform, macos_platform, x11_platform, wayland_platform)),
allow(rustdoc::broken_intra_doc_links)
)]
/// This value is affected by all modifiers except <kbd>Ctrl</kbd>.
///
/// This has two use cases:
@@ -693,7 +799,7 @@ pub struct KeyEvent {
/// - **Web:** Dead keys might be reported as the real key instead of `Dead` depending on the
/// browser/OS.
///
/// [`key_without_modifiers`]: Self::key_without_modifiers
/// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers
pub logical_key: keyboard::Key,
/// Contains the text produced by this keypress.
@@ -714,7 +820,7 @@ pub struct KeyEvent {
/// This is `None` if the current keypress cannot
/// be interpreted as text.
///
/// See also [`text_with_all_modifiers`][Self::text_with_all_modifiers].
/// See also: `text_with_all_modifiers()`
pub text: Option<SmolStr>,
/// Contains the location of this key on the keyboard.
@@ -749,8 +855,8 @@ pub struct KeyEvent {
/// done by ignoring events where this property is set.
///
/// ```no_run
/// use winit_core::event::{ElementState, KeyEvent, WindowEvent};
/// use winit_core::keyboard::{KeyCode, PhysicalKey};
/// use winit::event::{ElementState, KeyEvent, WindowEvent};
/// use winit::keyboard::{KeyCode, PhysicalKey};
/// # let window_event = WindowEvent::RedrawRequested; // To make the example compile
/// match window_event {
/// WindowEvent::KeyboardInput {
@@ -770,33 +876,13 @@ pub struct KeyEvent {
/// ```
pub repeat: bool,
/// Similar to [`text`][Self::text], except that this is affected by <kbd>Ctrl</kbd>.
/// Platform-specific key event information.
///
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
/// On Windows, Linux and macOS, this type contains the key without modifiers and the text with
/// all modifiers applied.
///
/// ## Platform-specific
///
/// - **Android:** Unimplemented, this field is always the same value as `text`.
/// - **iOS:** Unimplemented, this field is always the same value as `text`.
/// - **Web:** Unsupported, this field is always the same value as `text`.
pub text_with_all_modifiers: Option<SmolStr>,
/// This value ignores all modifiers including, but not limited to <kbd>Shift</kbd>,
/// <kbd>Caps Lock</kbd>, and <kbd>Ctrl</kbd>. In most cases this means that the
/// unicode character in the resulting string is lowercase.
///
/// This is useful for key-bindings / shortcut key combinations.
///
/// In case [`logical_key`][Self::logical_key] reports [`Dead`][keyboard::Key::Dead],
/// this will still report the key as `Character` according to the current keyboard
/// layout. This value cannot be `Dead`.
///
/// ## Platform-specific
///
/// - **Android:** Unimplemented, this field is always the same value as `logical_key`.
/// - **iOS:** Unimplemented, this field is always the same value as `logical_key`.
/// - **Web:** Unsupported, this field is always the same value as `logical_key`.
pub key_without_modifiers: keyboard::Key,
/// On Android, iOS, Redox and Web, this type is a no-op.
pub(crate) platform_specific: platform_impl::KeyEventExtra,
}
/// Describes keyboard modifiers event.
@@ -805,61 +891,56 @@ pub struct KeyEvent {
pub struct Modifiers {
pub(crate) state: ModifiersState,
// NOTE: Currently active modifiers keys (logically, but not necessarily physically, pressed).
// NOTE: Currently pressed modifiers keys.
//
// The field providing a metadata, it shouldn't be used as a source of truth.
pub(crate) pressed_mods: ModifiersKeys,
}
impl Modifiers {
/// Create a new modifiers from state and pressed mods.
pub fn new(state: ModifiersState, pressed_mods: ModifiersKeys) -> Self {
Self { state, pressed_mods }
}
/// The logical state of the modifiers.
/// The state of the modifiers.
pub fn state(&self) -> ModifiersState {
self.state
}
/// The logical state of the left shift key.
/// The state of the left shift key.
pub fn lshift_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LSHIFT)
}
/// The logical state of the right shift key.
/// The state of the right shift key.
pub fn rshift_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RSHIFT)
}
/// The logical state of the left alt key.
/// The state of the left alt key.
pub fn lalt_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LALT)
}
/// The logical state of the right alt key.
/// The state of the right alt key.
pub fn ralt_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RALT)
}
/// The logical state of the left control key.
/// The state of the left control key.
pub fn lcontrol_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LCONTROL)
}
/// The logical state of the right control key.
/// The state of the right control key.
pub fn rcontrol_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RCONTROL)
}
/// The logical state of the left super key.
/// The state of the left super key.
pub fn lsuper_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LMETA)
self.mod_state(ModifiersKeys::LSUPER)
}
/// The logical state of the right super key.
/// The state of the right super key.
pub fn rsuper_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RMETA)
self.mod_state(ModifiersKeys::RSUPER)
}
fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState {
@@ -879,8 +960,6 @@ impl From<ModifiersState> for Modifiers {
/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
///
/// The `Ime` events must be applied in the order they arrive.
///
/// This is also called a "composition event".
///
/// Most keypresses using a latin-like keyboard layout simply generate a
@@ -937,7 +1016,7 @@ pub enum Ime {
/// position. When it's `None`, the cursor should be hidden. When `String` is an empty string
/// this indicates that preedit was cleared.
///
/// The cursor position is byte-wise indexed, assuming UTF-8.
/// The cursor position is byte-wise indexed.
Preedit(String, Option<(usize, usize)>),
/// Notifies when text should be inserted into the editor widget.
@@ -945,20 +1024,6 @@ pub enum Ime {
/// Right before this event winit will send empty [`Self::Preedit`] event.
Commit(String),
/// Delete text surrounding the cursor or selection.
///
/// This event does not affect either the pre-edit string.
/// This means that the application must first remove the pre-edit,
/// then execute the deletion, then insert the removed text back.
///
/// This event assumes text is stored in UTF-8.
DeleteSurrounding {
/// Bytes to remove before the selection
before_bytes: usize,
/// Bytes to remove after the selection
after_bytes: usize,
},
/// Notifies when the IME was disabled.
///
/// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or
@@ -1092,7 +1157,8 @@ pub struct SurfaceSizeWriter {
}
impl SurfaceSizeWriter {
pub fn new(new_surface_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
#[cfg(not(orbital_platform))]
pub(crate) fn new(new_surface_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
Self { new_surface_size }
}
@@ -1108,15 +1174,6 @@ impl SurfaceSizeWriter {
Err(RequestError::Ignored)
}
}
/// Get the currently stashed surface size.
pub fn surface_size(&self) -> Result<PhysicalSize<u32>, RequestError> {
if let Some(inner) = self.new_surface_size.upgrade() {
Ok(*inner.lock().unwrap())
} else {
Err(RequestError::Ignored)
}
}
}
impl PartialEq for SurfaceSizeWriter {
@@ -1131,114 +1188,128 @@ impl Eq for SurfaceSizeWriter {}
mod tests {
use std::collections::{BTreeSet, HashSet};
use dpi::PhysicalPosition;
use crate::dpi::PhysicalPosition;
use crate::event;
macro_rules! foreach_event {
($closure:expr) => {{
foreach_event!(window: $closure);
foreach_event!(device: $closure);
}};
(window: $closure:expr) => {{
#[allow(unused_mut)]
let mut with_window_event: &mut dyn FnMut(event::WindowEvent) = &mut $closure;
let mut x = $closure;
let fid = event::FingerId::from_raw(0);
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::event::{PointerKind, PointerSource};
#[allow(deprecated)]
{
use crate::event::Event::*;
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::event::{PointerKind, PointerSource};
use crate::window::WindowId;
with_window_event(CloseRequested);
with_window_event(Destroyed);
with_window_event(Focused(true));
with_window_event(Moved((0, 0).into()));
with_window_event(SurfaceResized((0, 0).into()));
with_window_event(DragEntered { paths: vec!["x.txt".into()], position: (0, 0).into() });
with_window_event(DragMoved { position: (0, 0).into() });
with_window_event(DragDropped { paths: vec!["x.txt".into()], position: (0, 0).into() });
with_window_event(DragLeft { position: Some((0, 0).into()) });
with_window_event(Ime(Enabled));
with_window_event(PointerMoved {
device_id: None,
primary: true,
position: (0, 0).into(),
source: PointerSource::Mouse,
});
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(PointerEntered {
device_id: None,
primary: true,
position: (0, 0).into(),
kind: PointerKind::Mouse,
});
with_window_event(PointerLeft {
primary: true,
device_id: None,
position: Some((0, 0).into()),
kind: PointerKind::Mouse,
});
with_window_event(MouseWheel {
device_id: None,
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Pressed,
position: (0, 0).into(),
button: event::MouseButton::Other(0).into(),
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Released,
position: (0, 0).into(),
button: event::ButtonSource::Touch {
finger_id: fid,
force: Some(event::Force::Normalized(0.0)),
},
});
with_window_event(PinchGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(DoubleTapGesture { device_id: None });
with_window_event(RotationGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(PanGesture {
device_id: None,
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure { device_id: None, pressure: 0.0, stage: 0 });
with_window_event(ThemeChanged(crate::window::Theme::Light));
with_window_event(Occluded(true));
}};
(device: $closure:expr) => {{
use event::DeviceEvent::*;
// Mainline events.
let wid = WindowId::from_raw(0);
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
x(LoopExiting);
x(Suspended);
x(Resumed);
#[allow(unused_mut)]
let mut with_device_event: &mut dyn FnMut(event::DeviceEvent) = &mut $closure;
// Window events.
let with_window_event = |wev| x(WindowEvent { window_id: wid, event: wev });
with_device_event(PointerMotion { delta: (0.0, 0.0).into() });
with_device_event(MouseWheel { delta: event::MouseScrollDelta::LineDelta(0.0, 0.0) });
with_device_event(Button { button: 0, state: event::ElementState::Pressed });
with_window_event(CloseRequested);
with_window_event(Destroyed);
with_window_event(Focused(true));
with_window_event(Moved((0, 0).into()));
with_window_event(SurfaceResized((0, 0).into()));
with_window_event(DroppedFile("x.txt".into()));
with_window_event(HoveredFile("x.txt".into()));
with_window_event(HoveredFileCancelled);
with_window_event(Ime(Enabled));
with_window_event(PointerMoved {
device_id: None,
primary: true,
position: (0, 0).into(),
source: PointerSource::Mouse,
});
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(PointerEntered {
device_id: None,
primary: true,
position: (0, 0).into(),
kind: PointerKind::Mouse,
});
with_window_event(PointerLeft {
primary: true,
device_id: None,
position: Some((0, 0).into()),
kind: PointerKind::Mouse,
});
with_window_event(MouseWheel {
device_id: None,
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Pressed,
position: (0, 0).into(),
button: event::MouseButton::Other(0).into(),
});
with_window_event(PointerButton {
device_id: None,
primary: true,
state: event::ElementState::Released,
position: (0, 0).into(),
button: event::ButtonSource::Touch {
finger_id: fid,
force: Some(event::Force::Normalized(0.0)),
},
});
with_window_event(PinchGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(DoubleTapGesture { device_id: None });
with_window_event(RotationGesture {
device_id: None,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(PanGesture {
device_id: None,
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure { device_id: None, pressure: 0.0, stage: 0 });
with_window_event(ThemeChanged(crate::window::Theme::Light));
with_window_event(Occluded(true));
}
#[allow(deprecated)]
{
use event::DeviceEvent::*;
let with_device_event =
|dev_ev| x(event::Event::DeviceEvent { device_id: None, event: dev_ev });
with_device_event(PointerMotion { delta: (0.0, 0.0).into() });
with_device_event(MouseWheel {
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
});
with_device_event(Button { button: 0, state: event::ElementState::Pressed });
}
}};
}
#[allow(clippy::clone_on_copy)]
#[allow(clippy::redundant_clone)]
#[test]
fn test_event_clone() {
foreach_event!(|event| {
foreach_event!(|event: event::Event| {
let event2 = event.clone();
assert_eq!(event, event2);
});
})
}
#[test]
@@ -1256,7 +1327,7 @@ mod tests {
#[allow(clippy::clone_on_copy)]
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event| {
foreach_event!(|event: event::Event| {
let _ = format!("{event:?}");
});
let _ = event::StartCause::Init.clone();

545
src/event_loop.rs Normal file
View File

@@ -0,0 +1,545 @@
//! The [`EventLoop`] struct and assorted supporting types, including
//! [`ControlFlow`].
//!
//! If you want to send custom events to the event loop, use
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
//! [`wake_up`][EventLoopProxy::wake_up] method. Then during handling the wake up
//! you can poll your event sources.
//!
//! See the root-level documentation for information on how to create and use an event loop to
//! handle events.
use std::fmt;
use std::marker::PhantomData;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
#[cfg(not(web_platform))]
use std::time::{Duration, Instant};
use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
#[cfg(web_platform)]
use web_time::{Duration, Instant};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError};
use crate::monitor::MonitorHandle;
use crate::platform_impl;
use crate::utils::AsAny;
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.
///
/// An `EventLoop` can be seen more or less as a "context". Calling [`EventLoop::new`]
/// initializes everything that will be required to create windows. For example on Linux creating
/// an event loop opens a connection to the X or Wayland server.
///
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
///
/// Note that this cannot be shared across threads (due to platform-dependant logic
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access,
/// the [`Window`] created from this _can_ be sent to an other thread, and the
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
///
/// [`Window`]: crate::window::Window
pub struct EventLoop {
pub(crate) event_loop: platform_impl::EventLoop,
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
}
/// Object that allows building the event loop.
///
/// This is used to make specifying options that affect the whole application
/// easier. But note that constructing multiple event loops is not supported.
///
/// This can be created using [`EventLoop::builder`].
#[derive(Default, PartialEq, Eq, Hash)]
pub struct EventLoopBuilder {
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
}
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
impl EventLoopBuilder {
/// Builds a new event loop.
///
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
/// and only once per application.***
///
/// Calling this function will result in display backend initialisation.
///
/// ## Panics
///
/// Attempting to create the event loop off the main thread will panic. This
/// restriction isn't strictly necessary on all platforms, but is imposed to
/// eliminate any nasty surprises when porting to platforms that require it.
/// `EventLoopBuilderExt::with_any_thread` functions are exposed in the relevant
/// [`platform`] module if the target platform supports creating an event
/// loop on any thread.
///
/// ## Platform-specific
///
/// - **Wayland/X11:** to prevent running under `Wayland` or `X11` unset `WAYLAND_DISPLAY` or
/// `DISPLAY` respectively when building the event loop.
/// - **Android:** must be configured with an `AndroidApp` from `android_main()` by calling
/// [`.with_android_app(app)`] before calling `.build()`, otherwise it'll panic.
///
/// [`platform`]: crate::platform
#[cfg_attr(
android_platform,
doc = "[`.with_android_app(app)`]: \
crate::platform::android::EventLoopBuilderExtAndroid::with_android_app"
)]
#[cfg_attr(
not(android_platform),
doc = "[`.with_android_app(app)`]: #only-available-on-android"
)]
#[inline]
pub fn build(&mut self) -> Result<EventLoop, EventLoopError> {
let _span = tracing::debug_span!("winit::EventLoopBuilder::build").entered();
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
return Err(EventLoopError::RecreationAttempt);
}
// Certain platforms accept a mutable reference in their API.
#[allow(clippy::unnecessary_mut_passed)]
Ok(EventLoop {
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific)?,
_marker: PhantomData,
})
}
#[cfg(web_platform)]
pub(crate) fn allow_event_loop_recreation() {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
}
}
impl fmt::Debug for EventLoopBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopBuilder").finish_non_exhaustive()
}
}
impl fmt::Debug for EventLoop {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoop").finish_non_exhaustive()
}
}
/// Set through [`ActiveEventLoop::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`about_to_wait`] is called.
///
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
/// [`about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process.
Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives.
#[default]
Wait,
/// When the current loop iteration finishes, suspend the thread until either another event
/// arrives or the given time is reached.
///
/// Useful for implementing efficient timers. Applications which want to render at the
/// display's native refresh rate should instead use [`Poll`] and the VSync functionality
/// of a graphics API to reduce odds of missed frames.
///
/// [`Poll`]: Self::Poll
WaitUntil(Instant),
}
impl ControlFlow {
/// Creates a [`ControlFlow`] that waits until a timeout has expired.
///
/// In most cases, this is set to [`WaitUntil`]. However, if the timeout overflows, it is
/// instead set to [`Wait`].
///
/// [`WaitUntil`]: Self::WaitUntil
/// [`Wait`]: Self::Wait
pub fn wait_duration(timeout: Duration) -> Self {
match Instant::now().checked_add(timeout) {
Some(instant) => Self::WaitUntil(instant),
None => Self::Wait,
}
}
}
impl EventLoop {
/// Create the event loop.
///
/// This is an alias of `EventLoop::builder().build()`.
#[inline]
pub fn new() -> Result<EventLoop, EventLoopError> {
Self::builder().build()
}
/// Start building a new event loop.
///
/// This returns an [`EventLoopBuilder`], to allow configuring the event loop before creation.
///
/// To get the actual event loop, call [`build`][EventLoopBuilder::build] on that.
#[inline]
pub fn builder() -> EventLoopBuilder {
EventLoopBuilder { platform_specific: Default::default() }
}
}
impl EventLoop {
/// Run the application with the event loop on the calling thread.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// ## Platform-specific
///
/// - **iOS:** Will never return to the caller and so values not passed to this function will
/// *not* be dropped before the process exits.
/// - **Web:** Will _act_ as if it never returns to the caller by throwing a Javascript
/// exception (that Rust doesn't see) that will also mean that the rest of the function is
/// never executed and any values not passed to this function will *not* be dropped.
///
/// Web applications are recommended to use
#[cfg_attr(
any(web_platform, docsrs),
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^1] instead of [`run_app()`] to avoid the need for the Javascript exception trick, and to
/// make it clearer that the event loop runs asynchronously (via the browser's own,
/// internal, event loop) and doesn't block the current thread of execution like it does
/// on other platforms.
///
/// This function won't be available with `target_feature = "exception-handling"`.
///
/// [^1]: `spawn_app()` is only available on the Web platform.
///
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
/// [`run_app()`]: Self::run_app()
#[inline]
#[cfg(not(all(web_platform, target_feature = "exception-handling")))]
pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
self.event_loop.run_app(app)
}
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
pub fn create_proxy(&self) -> EventLoopProxy {
self.event_loop.window_target().create_proxy()
}
/// Gets a persistent reference to the underlying platform display.
///
/// See the [`OwnedDisplayHandle`] type for more information.
pub fn owned_display_handle(&self) -> OwnedDisplayHandle {
self.event_loop.window_target().owned_display_handle()
}
/// Change if or when [`DeviceEvent`]s are captured.
///
/// See [`ActiveEventLoop::listen_device_events`] for details.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
pub fn listen_device_events(&self, allowed: DeviceEvents) {
let _span = tracing::debug_span!(
"winit::EventLoop::listen_device_events",
allowed = ?allowed
)
.entered();
self.event_loop.window_target().listen_device_events(allowed)
}
/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.event_loop.window_target().set_control_flow(control_flow);
}
/// Create custom cursor.
///
/// ## Platform-specific
///
/// **iOS / Android / Orbital:** Unsupported.
pub fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, RequestError> {
self.event_loop.window_target().create_custom_cursor(custom_cursor)
}
}
impl HasDisplayHandle for EventLoop {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
HasDisplayHandle::display_handle(self.event_loop.window_target().rwh_06_handle())
}
}
#[cfg(any(x11_platform, wayland_platform))]
impl AsFd for EventLoop {
/// Get the underlying [EventLoop]'s `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API.
///
/// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio
/// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events
fn as_fd(&self) -> BorrowedFd<'_> {
self.event_loop.as_fd()
}
}
#[cfg(any(x11_platform, wayland_platform))]
impl AsRawFd for EventLoop {
/// Get the underlying [EventLoop]'s raw `fd` which you can register
/// into other event loop, like [`calloop`] or [`mio`]. When doing so, the
/// loop must be polled with the [`pump_app_events`] API.
///
/// [`calloop`]: https://crates.io/crates/calloop
/// [`mio`]: https://crates.io/crates/mio
/// [`pump_app_events`]: crate::platform::pump_events::EventLoopExtPumpEvents::pump_app_events
fn as_raw_fd(&self) -> RawFd {
self.event_loop.as_raw_fd()
}
}
pub trait ActiveEventLoop: AsAny {
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events
/// to the main event loop, possibly from another thread.
fn create_proxy(&self) -> EventLoopProxy;
/// Create the window.
///
/// Possible causes of error include denied permission, incompatible system, and lack of memory.
///
/// ## Platform-specific
///
/// - **Web:** The window is created but not inserted into the Web page automatically. Please
/// see the Web platform module for more information.
fn create_window(
&self,
window_attributes: WindowAttributes,
) -> Result<Box<dyn Window>, RequestError>;
/// Create custom cursor.
///
/// ## Platform-specific
///
/// **iOS / Android / Orbital:** Unsupported.
fn create_custom_cursor(
&self,
custom_cursor: CustomCursorSource,
) -> Result<CustomCursor, RequestError>;
/// Returns the list of all the monitors available on the system.
///
/// ## Platform-specific
///
/// **Web:** Only returns the current monitor without
#[cfg_attr(
any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
fn available_monitors(&self) -> Box<dyn Iterator<Item = MonitorHandle>>;
/// Returns the primary monitor of the system.
///
/// Returns `None` if it can't identify any monitor as a primary one.
///
/// ## Platform-specific
///
/// - **Wayland:** Always returns `None`.
/// - **Web:** Always returns `None` without
#[cfg_attr(
any(web_platform, docsrs),
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
fn primary_monitor(&self) -> Option<MonitorHandle>;
/// Change if or when [`DeviceEvent`]s are captured.
///
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
/// this at runtime to explicitly capture them again.
///
/// ## Platform-specific
///
/// - **Wayland / macOS / iOS / Android / Orbital:** Unsupported.
///
/// [`DeviceEvent`]: crate::event::DeviceEvent
fn listen_device_events(&self, allowed: DeviceEvents);
/// Returns the current system theme.
///
/// Returns `None` if it cannot be determined on the current platform.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported.
fn system_theme(&self) -> Option<Theme>;
/// Sets the [`ControlFlow`].
fn set_control_flow(&self, control_flow: ControlFlow);
/// Gets the current [`ControlFlow`].
fn control_flow(&self) -> ControlFlow;
/// This exits the event loop.
///
/// See [`exiting`][crate::application::ApplicationHandler::exiting].
fn exit(&self);
/// Returns if the [`EventLoop`] is about to stop.
///
/// See [`exit()`][Self::exit].
fn exiting(&self) -> bool;
/// Gets a persistent reference to the underlying platform display.
///
/// See the [`OwnedDisplayHandle`] type for more information.
fn owned_display_handle(&self) -> OwnedDisplayHandle;
/// Get the raw-window-handle handle.
fn rwh_06_handle(&self) -> &dyn HasDisplayHandle;
}
impl HasDisplayHandle for dyn ActiveEventLoop + '_ {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.rwh_06_handle().display_handle()
}
}
/// A proxy for the underlying display handle.
///
/// 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
/// needed.
///
/// For all platforms, this is one of the following:
///
/// - A zero-sized type that is likely optimized out.
/// - A reference-counted pointer to the underlying type.
#[derive(Clone)]
pub struct OwnedDisplayHandle {
pub(crate) handle: Arc<dyn HasDisplayHandle>,
}
impl OwnedDisplayHandle {
pub(crate) fn new(handle: Arc<dyn HasDisplayHandle>) -> Self {
Self { handle }
}
}
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.handle.display_handle()
}
}
impl fmt::Debug for OwnedDisplayHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
}
}
impl PartialEq for OwnedDisplayHandle {
fn eq(&self, other: &Self) -> bool {
match (self.display_handle(), other.display_handle()) {
(Ok(lhs), Ok(rhs)) => lhs == rhs,
_ => false,
}
}
}
impl Eq for OwnedDisplayHandle {}
pub(crate) trait EventLoopProxyProvider: Send + Sync {
/// See [`EventLoopProxy::wake_up`] for details.
fn wake_up(&self);
}
/// Control the [`EventLoop`], possibly from a different thread, without referencing it directly.
#[derive(Clone)]
pub struct EventLoopProxy {
pub(crate) proxy: Arc<dyn EventLoopProxyProvider>,
}
impl fmt::Debug for EventLoopProxy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopProxy").finish_non_exhaustive()
}
}
impl EventLoopProxy {
/// Wake up the [`EventLoop`], resulting in [`ApplicationHandler::proxy_wake_up()`] being
/// called.
///
/// Calls to this method are coalesced into a single call to [`proxy_wake_up`], see the
/// documentation on that for details.
///
/// If the event loop is no longer running, this is a no-op.
///
/// [`proxy_wake_up`]: ApplicationHandler::proxy_wake_up
///
/// # Platform-specific
///
/// - **Windows**: The wake-up may be ignored under high contention, see [#3687].
///
/// [#3687]: https://github.com/rust-windowing/winit/pull/3687
pub fn wake_up(&self) {
self.proxy.wake_up();
}
pub(crate) fn new(proxy: Arc<dyn EventLoopProxyProvider>) -> Self {
Self { proxy }
}
}
/// Control when device events are captured.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DeviceEvents {
/// Report device events regardless of window focus.
Always,
/// Only capture device events while the window is focused.
#[default]
WhenFocused,
/// Never capture device events.
Never,
}
/// A unique identifier of the winit's async request.
///
/// This could be used to identify the async request once it's done
/// and a specific action must be taken.
///
/// One of the handling scenarios could be to maintain a working list
/// containing [`AsyncRequestSerial`] and some closure associated with it.
/// Then once event is arriving the working list is being traversed and a job
/// executed and removed from the list.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AsyncRequestSerial {
serial: usize,
}
impl AsyncRequestSerial {
// TODO(kchibisov): Remove `cfg` when the clipboard will be added.
#[allow(dead_code)]
pub(crate) fn get() -> Self {
static CURRENT_SERIAL: AtomicUsize = AtomicUsize::new(0);
// NOTE: We rely on wrap around here, while the user may just request
// in the loop usize::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial }
}
}

118
src/icon.rs Normal file
View File

@@ -0,0 +1,118 @@
use std::error::Error;
use std::{fmt, io, mem};
use crate::platform_impl::PlatformIcon;
#[repr(C)]
#[derive(Debug)]
pub(crate) struct Pixel {
pub(crate) r: u8,
pub(crate) g: u8,
pub(crate) b: u8,
pub(crate) a: u8,
}
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
#[derive(Debug)]
/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
pub enum BadIcon {
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
/// safely interpreted as 32bpp RGBA pixels.
ByteCountNotDivisibleBy4 { byte_count: usize },
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
/// At least one of your arguments is incorrect.
DimensionsVsPixelCount { width: u32, height: u32, width_x_height: usize, pixel_count: usize },
/// Produced when underlying OS functionality failed to create the icon
OsError(io::Error),
}
impl fmt::Display for BadIcon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(
f,
"The length of the `rgba` argument ({byte_count:?}) isn't divisible by 4, making \
it impossible to interpret as 32bpp RGBA pixels.",
),
BadIcon::DimensionsVsPixelCount { width, height, width_x_height, pixel_count } => {
write!(
f,
"The specified dimensions ({width:?}x{height:?}) don't match the number of \
pixels supplied by the `rgba` argument ({pixel_count:?}). For those \
dimensions, the expected pixel count is {width_x_height:?}.",
)
},
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {e:?}"),
}
}
}
impl Error for BadIcon {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct RgbaIcon {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u32,
pub(crate) height: u32,
}
/// For platforms which don't have window icons (e.g. Web)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct NoIcon;
#[allow(dead_code)] // These are not used on every platform
mod constructors {
use super::*;
impl RgbaIcon {
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadIcon::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
}
let pixel_count = rgba.len() / PIXEL_SIZE;
if pixel_count != (width * height) as usize {
Err(BadIcon::DimensionsVsPixelCount {
width,
height,
width_x_height: (width * height) as usize,
pixel_count,
})
} else {
Ok(RgbaIcon { rgba, width, height })
}
}
}
impl NoIcon {
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
// Create the rgba icon anyway to validate the input
let _ = RgbaIcon::from_rgba(rgba, width, height)?;
Ok(NoIcon)
}
}
}
/// An icon used for the window titlebar, taskbar, etc.
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct Icon {
pub(crate) inner: PlatformIcon,
}
impl fmt::Debug for Icon {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&self.inner, formatter)
}
}
impl Icon {
/// Creates an icon from 32bpp RGBA data.
///
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
let _span = tracing::debug_span!("winit::Icon::from_rgba", width, height).entered();
Ok(Icon { inner: PlatformIcon::from_rgba(rgba, width, height)? })
}
}

1770
src/keyboard.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,59 @@
//! Winit is a cross-platform window creation and event loop management library.
//!
//! # Usage
//!
//! `winit` can be added to `Cargo.toml` as a dependency. It can be added via `cargo add`.
//!
//! ```bash
//! $ cargo add winit
//! ```
//!
//! To only enable the X11 backend on Free Unix[^unix] systems, disable default features
//! and enable the `x11` feature.
//!
//! ```bash
//! $ cargo add winit --no-default-features --features x11
//! ```
//!
//! To only enable the Wayland backend on Free Unix systems, disable default features
//! and enable the `wayland` feature.
//!
//! ```bash
//! $ cargo add winit --no-default-features --features wayland
//! ```
//!
//! These features have no effect on systems that are not Free Unix.
//!
//! ## Dependencies
//!
//! Dependencies on non-system libraries is managed through Cargo. For the X11
//! backend, the following Ubuntu packages or their equivalents must[^must] be installed.
//!
//! - `libx11-dev`
//! - `libxcb1-dev`
//! - `libxi-dev`
//! - `libxcbcommon-dev`
//! - `libxcbcommon-x11-dev`
//!
//! For the Wayland backend, the following Ubuntu packages or their equivalents
//! must be installed.
//!
//! - `libwayland-dev`
//! - `libxcbcommon-dev`
//! - `libfontconfig` (only with `sctk-adwaita` feature)
//! - `freetype` (only with `sctk-adwaita` feature)
//!
//! The "dev" packages are only needed for building binaries that use `winit`. On
//! deployed system the non-`dev` equivalents need to be installed.
//!
//! The other backends (Windows, macOS, etc) do not have any dependencies on system libraries
//! that don't already come with the operating system. However, note that the Windows backend
//! only supports Windows 10 and above, and the macOS backend only supports macOS
//! 10.14 and above.
//!
//! [^unix]: Unix systems outside of Android and Apple, like Linux or FreeBSD.
//! [^must]: This is not a "must" when the "dlopen" features are enabled
//!
//! # Building windows
//!
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with
@@ -26,14 +80,23 @@
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`].
//!
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will dispatch events
//! for every [`Window`] that was created with that particular [`EventLoop`].
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
//! will run until [`exit()`] is used, at which point [`exiting()`] is called.
//!
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
//! model, since that can't be implemented properly on some platforms (e.g Web, iOS) and works
//! poorly on most other platforms. However, this model can be re-implemented to an extent with
//! [`EventLoopExtPumpEvents::pump_app_events()`] [^1]. See that method's documentation for more
//! reasons about why it's discouraged beyond compatibility reasons.
#![cfg_attr(
any(windows_platform, macos_platform, android_platform, x11_platform, wayland_platform),
doc = "[`EventLoopExtPumpEvents::pump_app_events()`][platform::pump_events::EventLoopExtPumpEvents::pump_app_events()]"
)]
#![cfg_attr(
not(any(windows_platform, macos_platform, android_platform, x11_platform, wayland_platform)),
doc = "`EventLoopExtPumpEvents::pump_app_events()`"
)]
//! [^1]. See that method's documentation for more reasons about why
//! it's discouraged beyond compatibility reasons.
//!
//!
//! ```no_run
@@ -49,19 +112,10 @@
//!
//! impl ApplicationHandler for App {
//! fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
//! // The event loop has launched, and we can initialize our UI state.
//!
//! // Create a simple window with default attributes.
//! self.window = Some(event_loop.create_window(WindowAttributes::default()).unwrap());
//! }
//!
//! fn window_event(
//! &mut self,
//! event_loop: &dyn ActiveEventLoop,
//! id: WindowId,
//! event: WindowEvent,
//! ) {
//! // Called by `EventLoop::run_app` when a new event happens on the window.
//! fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, id: WindowId, event: WindowEvent) {
//! match event {
//! WindowEvent::CloseRequested => {
//! println!("The close button was pressed; stopping");
@@ -82,18 +136,15 @@
//! // applications which do not always need to. Applications that redraw continuously
//! // can render here instead.
//! self.window.as_ref().unwrap().request_redraw();
//! },
//! }
//! _ => (),
//! }
//! }
//! }
//!
//! # // Intentionally use `fn main` for clarity
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Create a new event loop.
//! let event_loop = EventLoop::new()?;
//!
//! // Configure settings before launching.
//! 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.
@@ -104,10 +155,8 @@
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait);
//!
//! // Launch and begin running the event loop.
//! event_loop.run_app(App::default())?;
//!
//! Ok(())
//! let mut app = App::default();
//! event_loop.run_app(&mut app);
//! }
//! ```
//!
@@ -262,13 +311,15 @@
//! [`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
//! [`DeviceEvent`]: event::DeviceEvent
//! [`Event::UserEvent`]: event::Event::UserEvent
//! [`exiting()`]: crate::application::ApplicationHandler::exiting
//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_handle
//! [`EventLoopExtPumpEvents::pump_app_events()`]: crate::event_loop::pump_events::EventLoopExtPumpEvents::pump_app_events()
//! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.
#![deny(rust_2018_idioms)]
@@ -281,20 +332,25 @@
#![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))]
// Re-export DPI types so that users don't have to put it in Cargo.toml.
#[doc(inline)]
pub use dpi;
pub use rwh_06 as raw_window_handle;
pub mod application;
#[cfg(any(doc, doctest, test))]
pub mod changelog;
pub mod event_loop;
pub use winit_core::{application, cursor, error, event, icon, keyboard, monitor, window};
#[macro_use]
mod os_error;
pub mod error;
mod cursor;
pub mod event;
pub mod event_loop;
mod icon;
pub mod keyboard;
pub mod monitor;
mod platform_impl;
mod utils;
pub mod window;
pub mod platform;

200
src/monitor.rs Normal file
View File

@@ -0,0 +1,200 @@
//! Types useful for interacting with a user's monitors.
use std::num::{NonZeroU16, NonZeroU32};
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::platform_impl;
/// A handle to a fullscreen video mode of a specific monitor.
///
/// This can be acquired with [`MonitorHandle::video_modes`].
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VideoModeHandle {
pub(crate) video_mode: platform_impl::VideoModeHandle,
}
impl std::fmt::Debug for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.video_mode.fmt(f)
}
}
impl PartialOrd for VideoModeHandle {
fn partial_cmp(&self, other: &VideoModeHandle) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VideoModeHandle {
fn cmp(&self, other: &VideoModeHandle) -> std::cmp::Ordering {
self.monitor().cmp(&other.monitor()).then(
self.size()
.cmp(&other.size())
.then(
self.refresh_rate_millihertz()
.cmp(&other.refresh_rate_millihertz())
.then(self.bit_depth().cmp(&other.bit_depth())),
)
.reverse(),
)
}
}
impl VideoModeHandle {
/// Returns the resolution of this video mode. This **must not** be used to create your
/// rendering surface. Use [`Window::surface_size()`] instead.
///
/// [`Window::surface_size()`]: crate::window::Window::surface_size
#[inline]
pub fn size(&self) -> PhysicalSize<u32> {
self.video_mode.size()
}
/// Returns the bit depth of this video mode, as in how many bits you have
/// available per color. This is generally 24 bits or 32 bits on modern
/// systems, depending on whether the alpha channel is counted or not.
#[inline]
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.video_mode.bit_depth()
}
/// Returns the refresh rate of this video mode in mHz.
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.video_mode.refresh_rate_millihertz()
}
/// Returns the monitor that this video mode is valid for. Each monitor has
/// a separate set of valid video modes.
#[inline]
pub fn monitor(&self) -> MonitorHandle {
MonitorHandle { inner: self.video_mode.monitor() }
}
}
impl std::fmt::Display for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} {}{}",
self.size().width,
self.size().height,
self.refresh_rate_millihertz().map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(),
self.bit_depth().map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(),
)
}
}
/// Handle to a monitor.
///
/// Allows you to retrieve basic information and metadata about a monitor.
///
/// Can be used in [`Window`] creation to place the window on a specific
/// monitor.
///
/// This can be retrieved from one of the following methods, which return an
/// iterator of [`MonitorHandle`]s:
/// - [`ActiveEventLoop::available_monitors`](crate::event_loop::ActiveEventLoop::available_monitors).
/// - [`Window::available_monitors`](crate::window::Window::available_monitors).
///
/// ## Platform-specific
///
/// **Web:** A [`MonitorHandle`] created without
#[cfg_attr(
any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
/// will always represent the current monitor the browser window is in instead of a specific
/// monitor. See
#[cfg_attr(
any(web_platform, docsrs),
doc = "[`MonitorHandleExtWeb::is_detailed()`][crate::platform::web::MonitorHandleExtWeb::is_detailed]"
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "`MonitorHandleExtWeb::is_detailed()`")]
/// to check.
///
/// [`Window`]: crate::window::Window
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle {
pub(crate) inner: platform_impl::MonitorHandle,
}
impl std::fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
}
impl MonitorHandle {
/// Returns a human-readable name of the monitor.
///
/// Returns `None` if the monitor doesn't exist anymore.
///
/// ## Platform-specific
///
/// **Web:** Always returns [`None`] without
#[cfg_attr(
any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
#[inline]
pub fn name(&self) -> Option<String> {
self.inner.name()
}
/// Returns the top-left corner position of the monitor in desktop coordinates.
///
/// This position is in the same coordinate system as [`Window::outer_position`].
///
/// [`Window::outer_position`]: crate::window::Window::outer_position
///
/// ## Platform-specific
///
/// **Web:** Always returns [`None`] without
#[cfg_attr(
any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
#[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
self.inner.position()
}
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
/// pixels and vice versa, use [`Window::scale_factor`].
///
/// See the [`dpi`] module for more information.
///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **Wayland:** May differ from [`Window::scale_factor`].
/// - **Android:** Always returns 1.0.
/// - **Web:** Always returns `0.0` without
#[cfg_attr(
any(web_platform, docsrs),
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
///
#[rustfmt::skip]
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
#[inline]
pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor()
}
/// Returns the currently active video mode of this monitor.
#[inline]
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
self.inner.current_video_mode().map(|video_mode| VideoModeHandle { video_mode })
}
/// Returns all fullscreen video modes supported by this monitor.
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode })
}
}

View File

@@ -62,26 +62,17 @@
//! 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.12",
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.5",
//! 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
//! logging as above).
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your
//! event loop (as shown above).
#![cfg(target_os = "android")]
mod event_loop;
mod keycodes;
use winit_core::event_loop::ActiveEventLoop as CoreActiveEventLoop;
use winit_core::window::Window as CoreWindow;
use self::activity::{AndroidApp, ConfigurationRef, Rect};
pub use crate::event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
PlatformSpecificWindowAttributes, Window,
};
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::window::{Window, WindowAttributes};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {
@@ -89,6 +80,12 @@ pub trait EventLoopExtAndroid {
fn android_app(&self) -> &AndroidApp;
}
impl EventLoopExtAndroid for EventLoop {
fn android_app(&self) -> &AndroidApp {
&self.event_loop.android_app
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
@@ -102,25 +99,31 @@ pub trait WindowExtAndroid {
fn config(&self) -> ConfigurationRef;
}
impl WindowExtAndroid for dyn CoreWindow + '_ {
impl WindowExtAndroid for dyn Window + '_ {
fn content_rect(&self) -> Rect {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.content_rect()
}
fn config(&self) -> ConfigurationRef {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.config()
}
}
impl ActiveEventLoopExtAndroid for dyn CoreActiveEventLoop + '_ {
impl ActiveEventLoopExtAndroid for dyn ActiveEventLoop + '_ {
fn android_app(&self) -> &AndroidApp {
let event_loop = self.cast_ref::<ActiveEventLoop>().unwrap();
let event_loop =
self.as_any().downcast_ref::<crate::platform_impl::ActiveEventLoop>().unwrap();
&event_loop.app
}
}
/// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop
///
@@ -133,6 +136,18 @@ pub trait EventLoopBuilderExtAndroid {
fn handle_volume_keys(&mut self) -> &mut Self;
}
impl EventLoopBuilderExtAndroid for EventLoopBuilder {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
}
fn handle_volume_keys(&mut self) -> &mut Self {
self.platform_specific.ignore_volume_keys = false;
self
}
}
/// Re-export of the `android_activity` API
///
/// Winit re-exports the `android_activity` API for convenience so that most
@@ -161,5 +176,16 @@ pub mod activity {
// feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page.
#[doc(no_inline)]
#[cfg(android_platform)]
pub use android_activity::*;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct Rect;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct ConfigurationRef;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct AndroidApp;
}

View File

@@ -1,4 +1,4 @@
//! # Winit's UIKit (iOS/tvOS/visionOS) backend
//! # iOS / UIKit
//!
//! Winit has [the same iOS version requirements as `rustc`][rustc-ios-version], although it's
//! frequently only tested on newer iOS versions.
@@ -85,9 +85,12 @@
//!
//! - applicationDidBecomeActive is Resumed
//! - applicationWillResignActive is Suspended
//! - applicationWillTerminate corresponds to `Drop`ping the application handler.
//! - applicationWillTerminate is LoopExiting
//!
//! Note that an app may not receive the `Drop` event if suspended; it might be SIGKILL'ed.
//! Keep in mind that after LoopExiting event is received every attempt to draw with
//! opengl will result in segfault.
//!
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
//!
//! ## Custom `UIApplicationDelegate`
//!
@@ -98,37 +101,24 @@
//! 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
#![cfg(target_vendor = "apple")] // TODO: Remove once `objc2` allows compiling on all platforms
mod app_state;
mod event_loop;
mod monitor;
mod notification_center;
mod view;
mod view_controller;
mod window;
use std::os::raw::c_void;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use winit_core::monitor::{MonitorHandle, VideoMode};
use winit_core::window::{PlatformWindowAttributes, Window};
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
use self::monitor::MonitorHandle as UIKitMonitorHandle;
use self::window::Window as UIKitWindow;
use crate::monitor::{MonitorHandle, VideoModeHandle};
use crate::window::{Window, WindowAttributes};
/// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandleProvider::scale_factor()`].
/// this to [`MonitorHandle::scale_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
/// [`MonitorHandleProvider::scale_factor()`]: crate::monitor::MonitorHandleProvider::scale_factor()
fn set_scale_factor(&self, scale_factor: f64);
/// Sets the valid orientations for the [`Window`].
@@ -222,25 +212,25 @@ pub trait WindowExtIOS {
impl WindowExtIOS for dyn Window + '_ {
#[inline]
fn set_scale_factor(&self, scale_factor: f64) {
let window = self.cast_ref::<UIKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_scale_factor(scale_factor));
}
#[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
let window = self.cast_ref::<UIKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_valid_orientations(valid_orientations));
}
#[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
let window = self.cast_ref::<UIKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden));
}
#[inline]
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let window = self.cast_ref::<UIKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| {
w.set_preferred_screen_edges_deferring_system_gestures(edges)
});
@@ -248,19 +238,19 @@ impl WindowExtIOS for dyn Window + '_ {
#[inline]
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
let window = self.cast_ref::<UIKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_prefers_status_bar_hidden(hidden));
}
#[inline]
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
let window = self.cast_ref::<UIKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
}
#[inline]
fn recognize_pinch_gesture(&self, should_recognize: bool) {
let window = self.cast_ref::<UIKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
}
@@ -271,7 +261,7 @@ impl WindowExtIOS for dyn Window + '_ {
minimum_number_of_touches: u8,
maximum_number_of_touches: u8,
) {
let window = self.cast_ref::<UIKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| {
w.recognize_pan_gesture(
should_recognize,
@@ -283,41 +273,27 @@ impl WindowExtIOS for dyn Window + '_ {
#[inline]
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
let window = self.cast_ref::<UIKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
}
#[inline]
fn recognize_rotation_gesture(&self, should_recognize: bool) {
let window = self.cast_ref::<UIKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
}
}
/// Ios specific window attributes.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct WindowAttributesIos {
pub(crate) scale_factor: Option<f64>,
pub(crate) valid_orientations: ValidOrientations,
pub(crate) prefers_home_indicator_hidden: bool,
pub(crate) prefers_status_bar_hidden: bool,
pub(crate) preferred_status_bar_style: StatusBarStyle,
pub(crate) preferred_screen_edges_deferring_system_gestures: ScreenEdge,
}
impl WindowAttributesIos {
/// Additional methods on [`WindowAttributes`] that are specific to iOS.
pub trait WindowAttributesExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
///
/// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandleProvider::scale_factor()`].
/// this to [`MonitorHandle::scale_factor()`].
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
/// [`MonitorHandleProvider::scale_factor()`]: crate::monitor::MonitorHandleProvider::scale_factor()
pub fn with_scale_factor(mut self, scale_factor: f64) -> Self {
self.scale_factor = Some(scale_factor);
self
}
fn with_scale_factor(self, scale_factor: f64) -> Self;
/// Sets the valid orientations for the [`Window`].
///
@@ -325,10 +301,7 @@ impl WindowAttributesIos {
///
/// This sets the initial value returned by
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
pub fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
self.valid_orientations = valid_orientations;
self
}
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> Self;
/// Sets whether the [`Window`] prefers the home indicator hidden.
///
@@ -338,10 +311,7 @@ impl WindowAttributesIos {
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
///
/// This only has an effect on iOS 11.0+.
pub fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
self.prefers_home_indicator_hidden = hidden;
self
}
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> Self;
/// Sets the screen edges for which the system gestures will take a lower priority than the
/// application's touch handling.
@@ -350,13 +320,7 @@ impl WindowAttributesIos {
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
///
/// This only has an effect on iOS 11.0+.
pub fn with_preferred_screen_edges_deferring_system_gestures(
mut self,
edges: ScreenEdge,
) -> Self {
self.preferred_screen_edges_deferring_system_gestures = edges;
self
}
fn with_preferred_screen_edges_deferring_system_gestures(self, edges: ScreenEdge) -> Self;
/// Sets whether the [`Window`] prefers the status bar hidden.
///
@@ -364,10 +328,7 @@ impl WindowAttributesIos {
///
/// This sets the initial value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
pub fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
self.prefers_status_bar_hidden = hidden;
self
}
fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self;
/// Sets the style of the [`Window`]'s status bar.
///
@@ -375,15 +336,44 @@ impl WindowAttributesIos {
///
/// This sets the initial value returned by
/// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc),
pub fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
self.preferred_status_bar_style = status_bar_style;
self
}
fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
}
impl PlatformWindowAttributes for WindowAttributesIos {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> {
Box::from(self.clone())
impl WindowAttributesExtIOS for WindowAttributes {
#[inline]
fn with_scale_factor(mut self, scale_factor: f64) -> Self {
self.platform_specific.scale_factor = Some(scale_factor);
self
}
#[inline]
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
self.platform_specific.valid_orientations = valid_orientations;
self
}
#[inline]
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
self.platform_specific.prefers_home_indicator_hidden = hidden;
self
}
#[inline]
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
self.platform_specific.preferred_screen_edges_deferring_system_gestures = edges;
self
}
#[inline]
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
self.platform_specific.prefers_status_bar_hidden = hidden;
self
}
#[inline]
fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
self.platform_specific.preferred_status_bar_style = status_bar_style;
self
}
}
@@ -394,25 +384,23 @@ pub trait MonitorHandleExtIOS {
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void;
/// Returns the preferred [`VideoMode`] for this monitor.
/// Returns the preferred [`VideoModeHandle`] for this monitor.
///
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
fn preferred_video_mode(&self) -> VideoMode;
fn preferred_video_mode(&self) -> VideoModeHandle;
}
impl MonitorHandleExtIOS for MonitorHandle {
#[inline]
fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
let monitor = self.cast_ref::<UIKitMonitorHandle>().unwrap();
objc2::rc::Retained::as_ptr(monitor.ui_screen(mtm)) as *mut c_void
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
}
#[inline]
fn preferred_video_mode(&self) -> VideoMode {
let monitor = self.cast_ref::<UIKitMonitorHandle>().unwrap();
monitor.preferred_video_mode()
fn preferred_video_mode(&self) -> VideoModeHandle {
VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
}
}

View File

@@ -17,24 +17,30 @@
//! 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::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly};
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSObject, NSObjectProtocol, NSURL};
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
//! use winit::event_loop::EventLoop;
//!
//! define_class!(
//! #[unsafe(super(NSObject))]
//! #[thread_kind = MainThreadOnly]
//! #[name = "AppDelegate"]
//! 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 {
//! #[unsafe(method(application:openURLs:))]
//! #[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.
@@ -45,7 +51,7 @@
//!
//! impl AppDelegate {
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
//! unsafe { msg_send![super(Self::alloc(mtm).set_ivars(())), init] }
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
//! }
//! }
//!
@@ -63,40 +69,16 @@
//! Ok(())
//! }
//! ```
#![cfg(target_vendor = "apple")] // TODO: Remove once `objc2` allows compiling on all platforms
#[macro_use]
mod util;
mod app;
mod app_state;
mod cursor;
mod event;
mod event_loop;
mod ffi;
mod menu;
mod monitor;
mod notification_center;
mod observer;
mod view;
mod window;
mod window_delegate;
use std::os::raw::c_void;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[doc(inline)]
pub use winit_core::application::macos::ApplicationHandlerExtMacOS;
use winit_core::event_loop::ActiveEventLoop;
use winit_core::monitor::MonitorHandle;
use winit_core::window::{PlatformWindowAttributes, Window};
pub use self::event::{physicalkey_to_scancode, scancode_to_physicalkey};
use self::event_loop::ActiveEventLoop as AppKitActiveEventLoop;
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
use self::monitor::MonitorHandle as AppKitMonitorHandle;
use self::window::Window as AppKitWindow;
use crate::application::ApplicationHandler;
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::window::{Window, WindowAttributes, WindowId};
/// Additional methods on [`Window`] that are specific to MacOS.
pub trait WindowExtMacOS {
@@ -173,9 +155,7 @@ 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.
/// Disable the Menu Bar and Dock in Borderless Fullscreen mode. Useful for games.
fn set_borderless_game(&self, borderless_game: bool);
/// Getter for the [`WindowExtMacOS::set_borderless_game`].
@@ -192,109 +172,109 @@ pub trait WindowExtMacOS {
impl WindowExtMacOS for dyn Window + '_ {
#[inline]
fn simple_fullscreen(&self) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.simple_fullscreen())
}
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
}
#[inline]
fn has_shadow(&self) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.has_shadow())
}
#[inline]
fn set_has_shadow(&self, has_shadow: bool) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_has_shadow(has_shadow));
}
#[inline]
fn set_tabbing_identifier(&self, identifier: &str) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
}
#[inline]
fn tabbing_identifier(&self) -> String {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.tabbing_identifier())
}
#[inline]
fn select_next_tab(&self) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.select_next_tab());
}
#[inline]
fn select_previous_tab(&self) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.select_previous_tab());
}
#[inline]
fn select_tab_at_index(&self, index: usize) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.select_tab_at_index(index));
}
#[inline]
fn num_tabs(&self) -> usize {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.num_tabs())
}
#[inline]
fn is_document_edited(&self) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.is_document_edited())
}
#[inline]
fn set_document_edited(&self, edited: bool) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_document_edited(edited));
}
#[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(move |w| w.set_option_as_alt(option_as_alt));
}
#[inline]
fn option_as_alt(&self) -> OptionAsAlt {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.option_as_alt())
}
#[inline]
fn set_borderless_game(&self, borderless_game: bool) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
}
#[inline]
fn is_borderless_game(&self) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.is_borderless_game())
}
#[inline]
fn set_unified_titlebar(&self, unified_titlebar: bool) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar))
}
#[inline]
fn unified_titlebar(&self) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.unified_titlebar())
}
}
@@ -314,7 +294,7 @@ pub enum ActivationPolicy {
Prohibited,
}
/// Window attributes that are specific to MacOS.
/// Additional methods on [`WindowAttributes`] that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the
/// [`WindowAttributes::with_decorations`] method:
@@ -323,158 +303,114 @@ pub enum ActivationPolicy {
/// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
///
/// [`WindowAttributes::with_decorations`]: crate::window::WindowAttributes::with_decorations
#[derive(Clone, Debug, PartialEq)]
pub struct WindowAttributesMacOS {
pub(crate) movable_by_window_background: bool,
pub(crate) titlebar_transparent: bool,
pub(crate) title_hidden: bool,
pub(crate) titlebar_hidden: bool,
pub(crate) titlebar_buttons_hidden: bool,
pub(crate) fullsize_content_view: bool,
pub(crate) disallow_hidpi: bool,
pub(crate) has_shadow: bool,
pub(crate) accepts_first_mouse: bool,
pub(crate) tabbing_identifier: Option<String>,
pub(crate) option_as_alt: OptionAsAlt,
pub(crate) borderless_game: bool,
pub(crate) unified_titlebar: bool,
pub(crate) panel: bool,
}
impl WindowAttributesMacOS {
pub trait WindowAttributesExtMacOS {
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
#[inline]
pub fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.movable_by_window_background = movable_by_window_background;
self
}
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self;
/// Makes the titlebar transparent and allows the content to appear behind it.
#[inline]
pub fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
self.titlebar_transparent = titlebar_transparent;
self
}
/// Hides the window titlebar.
#[inline]
pub fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.titlebar_hidden = titlebar_hidden;
self
}
/// Hides the window titlebar buttons.
#[inline]
pub fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
self.titlebar_buttons_hidden = titlebar_buttons_hidden;
self
}
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
/// Hides the window title.
#[inline]
pub fn with_title_hidden(mut self, title_hidden: bool) -> Self {
self.title_hidden = title_hidden;
self
}
fn with_title_hidden(self, title_hidden: bool) -> Self;
/// Hides the window titlebar.
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
/// Hides the window titlebar buttons.
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self;
/// Makes the window content appear behind the titlebar.
#[inline]
pub fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
self.fullsize_content_view = fullsize_content_view;
self
}
#[inline]
pub fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
self.disallow_hidpi = disallow_hidpi;
self
}
#[inline]
pub fn with_has_shadow(mut self, has_shadow: bool) -> Self {
self.has_shadow = has_shadow;
self
}
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
fn with_has_shadow(self, has_shadow: bool) -> Self;
/// Window accepts click-through mouse events.
#[inline]
pub fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.accepts_first_mouse = accepts_first_mouse;
self
}
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self;
/// Defines the window tabbing identifier.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
#[inline]
pub fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.tabbing_identifier.replace(tabbing_identifier.to_string());
self
}
fn with_tabbing_identifier(self, identifier: &str) -> Self;
/// Set how the <kbd>Option</kbd> keys are interpreted.
///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
#[inline]
pub fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.option_as_alt = option_as_alt;
self
}
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
/// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
#[inline]
pub fn with_borderless_game(mut self, borderless_game: bool) -> Self {
self.borderless_game = borderless_game;
self
}
fn with_borderless_game(self, borderless_game: bool) -> Self;
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
}
impl WindowAttributesExtMacOS for WindowAttributes {
#[inline]
pub fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
self.unified_titlebar = unified_titlebar;
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.platform_specific.movable_by_window_background = movable_by_window_background;
self
}
/// Use [`NSPanel`] window with [`NonactivatingPanel`] window style mask instead of
/// [`NSWindow`].
///
/// [`NSWindow`]: https://developer.apple.com/documentation/appkit/NSWindow?language=objc
/// [`NSPanel`]: https://developer.apple.com/documentation/appkit/NSPanel?language=objc
/// [`NonactivatingPanel`]: https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.struct/nonactivatingpanel?language=objc
#[inline]
pub fn with_panel(mut self, panel: bool) -> Self {
self.panel = panel;
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
self.platform_specific.titlebar_transparent = titlebar_transparent;
self
}
}
impl Default for WindowAttributesMacOS {
#[inline]
fn default() -> Self {
Self {
movable_by_window_background: false,
titlebar_transparent: false,
title_hidden: false,
titlebar_hidden: false,
titlebar_buttons_hidden: false,
fullsize_content_view: false,
disallow_hidpi: false,
has_shadow: true,
accepts_first_mouse: true,
tabbing_identifier: None,
option_as_alt: Default::default(),
borderless_game: false,
unified_titlebar: false,
panel: false,
}
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.platform_specific.titlebar_hidden = titlebar_hidden;
self
}
}
impl PlatformWindowAttributes for WindowAttributesMacOS {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> {
Box::from(self.clone())
#[inline]
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
self
}
#[inline]
fn with_title_hidden(mut self, title_hidden: bool) -> Self {
self.platform_specific.title_hidden = title_hidden;
self
}
#[inline]
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
self.platform_specific.fullsize_content_view = fullsize_content_view;
self
}
#[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
self.platform_specific.disallow_hidpi = disallow_hidpi;
self
}
#[inline]
fn with_has_shadow(mut self, has_shadow: bool) -> Self {
self.platform_specific.has_shadow = has_shadow;
self
}
#[inline]
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
self
}
#[inline]
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.platform_specific.tabbing_identifier.replace(tabbing_identifier.to_string());
self
}
#[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
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
}
#[inline]
fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
self.platform_specific.unified_titlebar = unified_titlebar;
self
}
}
@@ -533,18 +469,44 @@ pub trait EventLoopBuilderExtMacOS {
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
}
impl EventLoopBuilderExtMacOS for EventLoopBuilder {
#[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = Some(activation_policy);
self
}
#[inline]
fn with_default_menu(&mut self, enable: bool) -> &mut Self {
self.platform_specific.default_menu = enable;
self
}
#[inline]
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
self.platform_specific.activate_ignoring_other_apps = ignore;
self
}
}
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
pub trait MonitorHandleExtMacOS {
/// Returns the identifier of the monitor for Cocoa.
fn native_id(&self) -> u32;
/// Returns a pointer to the NSScreen representing this monitor.
fn ns_screen(&self) -> Option<*mut c_void>;
}
impl MonitorHandleExtMacOS for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
fn ns_screen(&self) -> Option<*mut c_void> {
let monitor = self.cast_ref::<AppKitMonitorHandle>().unwrap();
// SAFETY: We only use the marker to get a pointer
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
monitor.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
}
}
@@ -566,26 +528,34 @@ pub trait ActiveEventLoopExtMacOS {
impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ {
fn hide_application(&self) {
let event_loop =
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
event_loop.hide_application()
}
fn hide_other_applications(&self) {
let event_loop =
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
event_loop.hide_other_applications()
}
fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
let event_loop =
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
event_loop.set_allows_automatic_window_tabbing(enabled);
}
fn allows_automatic_window_tabbing(&self) -> bool {
let event_loop =
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
event_loop.allows_automatic_window_tabbing()
}
}
@@ -609,3 +579,52 @@ pub enum OptionAsAlt {
#[default]
None,
}
/// Additional events on [`ApplicationHandler`] that are specific to macOS.
///
/// This can be registered with [`ApplicationHandler::macos_handler`].
pub trait ApplicationHandlerExtMacOS: ApplicationHandler {
/// The system interpreted a keypress as a standard key binding command.
///
/// Examples include inserting tabs and newlines, or moving the insertion point, see
/// [`NSStandardKeyBindingResponding`] for the full list of key bindings. They are often text
/// editing related.
///
/// This corresponds to the [`doCommandBySelector:`] method on `NSTextInputClient`.
///
/// The `action` parameter contains the string representation of the selector. Examples include
/// `"insertBacktab:"`, `"indent:"` and `"noop:"`.
///
/// # Example
///
/// ```ignore
/// impl ApplicationHandlerExtMacOS for App {
/// fn standard_key_binding(
/// &mut self,
/// event_loop: &dyn ActiveEventLoop,
/// window_id: WindowId,
/// action: &str,
/// ) {
/// match action {
/// "moveBackward:" => self.cursor.position -= 1,
/// "moveForward:" => self.cursor.position += 1,
/// _ => {} // Ignore other actions
/// }
/// }
/// }
/// ```
///
/// [`NSStandardKeyBindingResponding`]: https://developer.apple.com/documentation/appkit/nsstandardkeybindingresponding?language=objc
/// [`doCommandBySelector:`]: https://developer.apple.com/documentation/appkit/nstextinputclient/1438256-docommandbyselector?language=objc
#[doc(alias = "doCommandBySelector:")]
fn standard_key_binding(
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
action: &str,
) {
let _ = event_loop;
let _ = window_id;
let _ = action;
}
}

56
src/platform/mod.rs Normal file
View File

@@ -0,0 +1,56 @@
//! Contains traits with platform-specific methods in them.
//!
//! Only the modules corresponding to the platform you're compiling to will be available.
#[cfg(any(android_platform, docsrs))]
pub mod android;
#[cfg(any(ios_platform, docsrs))]
pub mod ios;
#[cfg(any(macos_platform, docsrs))]
pub mod macos;
#[cfg(any(orbital_platform, docsrs))]
pub mod orbital;
#[cfg(any(x11_platform, wayland_platform, docsrs))]
pub mod startup_notify;
#[cfg(any(wayland_platform, docsrs))]
pub mod wayland;
#[cfg(any(web_platform, docsrs))]
pub mod web;
#[cfg(any(windows_platform, docsrs))]
pub mod windows;
#[cfg(any(x11_platform, docsrs))]
pub mod x11;
#[allow(unused_imports)]
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
docsrs,
))]
pub mod run_on_demand;
#[cfg(any(
windows_platform,
macos_platform,
android_platform,
x11_platform,
wayland_platform,
docsrs,
))]
pub mod pump_events;
#[cfg(any(
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
orbital_platform,
docsrs
))]
pub mod modifier_supplement;
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, docsrs))]
pub mod scancode;

View File

@@ -0,0 +1,35 @@
use crate::event::KeyEvent;
use crate::keyboard::Key;
/// Additional methods for the `KeyEvent` which cannot be implemented on all
/// platforms.
pub trait KeyEventExtModifierSupplement {
/// Identical to `KeyEvent::text` but this is affected by <kbd>Ctrl</kbd>.
///
/// For example, pressing <kbd>Ctrl</kbd>+<kbd>a</kbd> produces `Some("\x01")`.
fn text_with_all_modifiers(&self) -> Option<&str>;
/// This value ignores all modifiers including,
/// but not limited to <kbd>Shift</kbd>, <kbd>Caps Lock</kbd>,
/// and <kbd>Ctrl</kbd>. In most cases this means that the
/// unicode character in the resulting string is lowercase.
///
/// This is useful for key-bindings / shortcut key combinations.
///
/// In case `logical_key` reports `Dead`, this will still report the
/// key as `Character` according to the current keyboard layout. This value
/// cannot be `Dead`.
fn key_without_modifiers(&self) -> Key;
}
impl KeyEventExtModifierSupplement for KeyEvent {
#[inline]
fn text_with_all_modifiers(&self) -> Option<&str> {
self.platform_specific.text_with_all_modifiers.as_ref().map(|s| s.as_str())
}
#[inline]
fn key_without_modifiers(&self) -> Key {
self.platform_specific.key_without_modifiers.clone()
}
}

6
src/platform/orbital.rs Normal file
View File

@@ -0,0 +1,6 @@
//! # Orbital / Redox OS
//!
//! Redox OS has some functionality not yet present that will be implemented
//! when its orbital display server provides it.
// There are no Orbital specific traits yet.

View File

@@ -1,8 +1,8 @@
use std::time::Duration;
use crate::application::ApplicationHandler;
use crate::event_loop::EventLoop;
#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these.
/// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents {
/// Pump the `EventLoop` to check for and dispatch pending events.
@@ -47,19 +47,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
///
@@ -106,6 +106,16 @@ pub trait EventLoopExtPumpEvents {
) -> PumpStatus;
}
impl EventLoopExtPumpEvents for EventLoop {
fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: A,
) -> PumpStatus {
self.event_loop.pump_app_events(timeout, app)
}
}
/// The return status for `pump_events`
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum PumpStatus {

View File

@@ -1,12 +1,11 @@
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event_loop::EventLoop;
#[cfg(doc)]
use crate::{
event_loop::{pump_events::EventLoopExtPumpEvents, ActiveEventLoop},
window::Window,
event_loop::ActiveEventLoop, platform::pump_events::EventLoopExtPumpEvents, window::Window,
};
#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these.
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand {
/// Run the application with the event loop on the calling thread.
@@ -32,7 +31,13 @@ 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 `EventLoopExtWeb::spawn_app()`[^1] more than once instead).
/// backend it is possible to use
#[cfg_attr(
any(web_platform, docsrs),
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^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
@@ -57,3 +62,22 @@ pub trait EventLoopExtRunOnDemand {
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;
}
impl EventLoopExtRunOnDemand for EventLoop {
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError> {
self.event_loop.run_app_on_demand(app)
}
}
/// ```compile_fail
/// use winit::event_loop::EventLoop;
/// use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
///
/// let mut event_loop = EventLoop::new().unwrap();
/// event_loop.run_on_demand(|_, _| {
/// // Attempt to run the event loop re-entrantly; this must fail.
/// event_loop.run_on_demand(|_, _| {});
/// });
/// ```
#[allow(dead_code)]
fn test_run_on_demand_cannot_access_event_loop() {}

View File

@@ -27,7 +27,7 @@ use crate::error::{NotSupportedError, RequestError};
use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial};
#[cfg(wayland_platform)]
use crate::platform::wayland::ActiveEventLoopExtWayland;
use crate::window::{ActivationToken, Window};
use crate::window::{ActivationToken, Window, WindowAttributes};
/// The variable which is used mostly on X11.
const X11_VAR: &str = "DESKTOP_STARTUP_ID";
@@ -65,9 +65,9 @@ impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ {
let _is_wayland = self.is_wayland();
if _is_wayland {
env::var(WAYLAND_VAR).ok().map(ActivationToken::from_raw)
env::var(WAYLAND_VAR).ok().map(ActivationToken::_new)
} else {
env::var(X11_VAR).ok().map(ActivationToken::from_raw)
env::var(X11_VAR).ok().map(ActivationToken::_new)
}
}
}
@@ -75,12 +75,15 @@ impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ {
impl WindowExtStartupNotify for dyn Window + '_ {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
#[cfg(wayland_platform)]
if let Some(window) = self.cast_ref::<crate::platform_impl::wayland::Window>() {
if let Some(window) = self.as_any().downcast_ref::<crate::platform_impl::wayland::Window>()
{
return window.request_activation_token();
}
#[cfg(x11_platform)]
if let Some(window) = self.cast_ref::<crate::platform_impl::x11::Window>() {
if let Some(window) =
self.as_any().downcast_ref::<crate::platform_impl::x11::window::Window>()
{
return window.request_activation_token();
}
@@ -88,6 +91,13 @@ impl WindowExtStartupNotify for dyn Window + '_ {
}
}
impl WindowAttributesExtStartupNotify for WindowAttributes {
fn with_activation_token(mut self, token: ActivationToken) -> Self {
self.platform_specific.activation_token = Some(token);
self
}
}
/// Remove the activation environment variables from the current process.
///
/// This is wise to do before running child processes,
@@ -101,7 +111,6 @@ pub fn reset_activation_token_env() {
///
/// This could be used before running daemon processes.
pub fn set_activation_token_env(token: ActivationToken) {
let token = token.into_raw();
env::set_var(X11_VAR, &token);
env::set_var(WAYLAND_VAR, token);
env::set_var(X11_VAR, &token._token);
env::set_var(WAYLAND_VAR, token._token);
}

112
src/platform/wayland.rs Normal file
View File

@@ -0,0 +1,112 @@
//! # Wayland
//!
//! **Note:** Windows don't appear on Wayland until you draw/present to them.
//!
//! By default, Winit loads system libraries using `dlopen`. This can be
//! disabled by disabling the `"wayland-dlopen"` cargo feature.
//!
//! ## Client-side decorations
//!
//! Winit provides client-side decorations by default, but the behaviour can
//! be controlled with the following feature flags:
//!
//! * `wayland-csd-adwaita` (default).
//! * `wayland-csd-adwaita-crossfont`.
//! * `wayland-csd-adwaita-notitle`.
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
pub use crate::window::Theme;
use crate::window::{Window as CoreWindow, WindowAttributes};
/// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
pub trait ActiveEventLoopExtWayland {
/// True if the [`ActiveEventLoop`] uses Wayland.
fn is_wayland(&self) -> bool;
}
impl ActiveEventLoopExtWayland for dyn ActiveEventLoop + '_ {
#[inline]
fn is_wayland(&self) -> bool {
self.as_any().downcast_ref::<crate::platform_impl::wayland::ActiveEventLoop>().is_some()
}
}
/// Additional methods on [`EventLoop`] that are specific to Wayland.
pub trait EventLoopExtWayland {
/// True if the [`EventLoop`] uses Wayland.
fn is_wayland(&self) -> bool;
}
impl EventLoopExtWayland for EventLoop {
#[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.
fn with_wayland(&mut self) -> &mut Self;
/// Whether to allow the event loop to be created off of the main thread.
///
/// By default, the window is only allowed to be created on the main
/// thread, to make platform compatibility easier.
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl EventLoopBuilderExtWayland for EventLoopBuilder {
#[inline]
fn with_wayland(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::Wayland);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
}
/// Additional methods on [`Window`] that are specific to Wayland.
///
/// [`Window`]: crate::window::Window
pub trait WindowExtWayland {}
impl WindowExtWayland for dyn CoreWindow + '_ {}
/// Additional methods on [`WindowAttributes`] that are specific to Wayland.
pub trait WindowAttributesExtWayland {
/// Build window with the given name.
///
/// The `general` name sets an application ID, which should match the `.desktop`
/// file distributed with your program. The `instance` is a `no-op`.
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
}
impl WindowAttributesExtWayland for WindowAttributes {
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name =
Some(crate::platform_impl::ApplicationName::new(general.into(), instance.into()));
self
}
}
/// Additional methods on `MonitorHandle` that are specific to Wayland.
pub trait MonitorHandleExtWayland {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtWayland for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

View File

@@ -13,10 +13,10 @@
//! yourself.
//!
//! [canvas]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
//! [with_canvas]: WindowAttributesWeb::with_canvas
//! [with_canvas]: WindowAttributesExtWeb::with_canvas
//! [get]: WindowExtWeb::canvas
//! [insert]: WindowAttributesWeb::with_append
//! [wasm_bindgen]: https://docs.rs/wasm-bindgen
//! [insert]: WindowAttributesExtWeb::with_append
#![cfg_attr(not(web_platform), doc = "[wasm_bindgen]: https://docs.rs/wasm-bindgen")]
//! [Rust and WebAssembly book]: https://rustwasm.github.io/book
//!
//! ## CSS properties
@@ -41,70 +41,37 @@
//! [`WindowEvent::PointerLeft`]: crate::event::WindowEvent::PointerLeft
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position
// Brief introduction to the internals of the Web backend:
// The Web backend used to support both wasm-bindgen and stdweb as methods of binding to the
// environment. Because they are both supporting the same underlying APIs, the actual Web bindings
// are cordoned off into backend abstractions, which present the thinnest unifying layer possible.
//
// When adding support for new events or interactions with the browser, first consult trusted
// documentation (such as MDN) to ensure it is well-standardised and supported across many browsers.
// Once you have decided on the relevant Web APIs, add support to both backends.
//
// The backend is used by the rest of the module to implement Winit's business logic, which forms
// the rest of the code. 'device', 'error', 'monitor', and 'window' define Web-specific structures
// for winit's cross-platform structures. They are all relatively simple translations.
//
// The event_loop module handles listening for and processing events. 'Proxy' implements
// EventLoopProxy and 'WindowTarget' implements ActiveEventLoop. WindowTarget also handles
// registering the event handlers. The 'Execution' struct in the 'runner' module handles taking
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
// compliant way.
macro_rules! os_error {
($error:expr) => {{
winit_core::error::OsError::new(line!(), file!(), $error)
}};
}
mod r#async;
mod cursor;
mod event;
pub(crate) mod event_loop;
mod lock;
pub(crate) mod main_thread;
mod monitor;
pub(crate) mod web_sys;
pub(crate) mod window;
use std::cell::Ref;
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
use ::web_sys::HtmlCanvasElement;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::NotSupportedError;
use winit_core::event_loop::ActiveEventLoop;
use winit_core::monitor::MonitorHandleProvider;
use winit_core::window::{PlatformWindowAttributes, Window};
#[cfg(web_platform)]
use web_sys::HtmlCanvasElement;
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
use self::web_sys as backend;
use self::window::Window as WebWindow;
use crate::cursor::CustomCursorFuture as PlatformCustomCursorFuture;
use crate::event_loop::ActiveEventLoop as WebActiveEventLoop;
use crate::main_thread::{MainThreadMarker, MainThreadSafe};
use crate::monitor::{
use crate::application::ApplicationHandler;
use crate::cursor::CustomCursorSource;
use crate::error::NotSupportedError;
use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::monitor::MonitorHandle;
use crate::platform_impl::PlatformCustomCursorSource;
#[cfg(web_platform)]
use crate::platform_impl::{
CustomCursorFuture as PlatformCustomCursorFuture,
HasMonitorPermissionFuture as PlatformHasMonitorPermissionFuture,
MonitorHandle as WebMonitorHandle, MonitorPermissionFuture as PlatformMonitorPermissionFuture,
MonitorPermissionFuture as PlatformMonitorPermissionFuture,
OrientationLockFuture as PlatformOrientationLockFuture,
};
use crate::window::{CustomCursor, Window, WindowAttributes};
#[cfg(not(web_platform))]
#[doc(hidden)]
pub struct HtmlCanvasElement;
pub trait WindowExtWeb {
/// Only returns the canvas if called from inside the window context (the
@@ -138,51 +105,43 @@ pub trait WindowExtWeb {
impl WindowExtWeb for dyn Window + '_ {
#[inline]
fn canvas(&self) -> Option<Ref<'_, HtmlCanvasElement>> {
self.cast_ref::<WebWindow>().expect("non Web window on Web").canvas()
self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.canvas()
}
fn prevent_default(&self) -> bool {
self.cast_ref::<WebWindow>().expect("non Web window on Web").prevent_default()
self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.prevent_default()
}
fn set_prevent_default(&self, prevent_default: bool) {
self.cast_ref::<WebWindow>()
self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.set_prevent_default(prevent_default)
}
fn is_cursor_lock_raw(&self) -> bool {
self.cast_ref::<WebWindow>().expect("non Web window on Web").is_cursor_lock_raw()
self.as_any()
.downcast_ref::<crate::platform_impl::Window>()
.expect("non Web window on Web")
.is_cursor_lock_raw()
}
}
#[derive(Clone, Debug)]
pub struct WindowAttributesWeb {
pub(crate) canvas: Option<Arc<MainThreadSafe<backend::RawCanvasType>>>,
pub(crate) prevent_default: bool,
pub(crate) focusable: bool,
pub(crate) append: bool,
}
impl WindowAttributesWeb {
pub trait WindowAttributesExtWeb {
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`],
/// the default one will be created.
/// [`WindowAttributes::default()`] will create one.
///
/// In any case, the canvas won't be automatically inserted into the Web page.
///
/// [`None`] by default.
pub fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
match canvas {
Some(canvas) => {
let main_thread = MainThreadMarker::new()
.expect("received a `HtmlCanvasElement` outside the window context");
self.canvas = Some(Arc::new(MainThreadSafe::new(main_thread, canvas)));
},
None => self.canvas = None,
}
self
}
#[cfg_attr(not(web_platform), doc = "", doc = "[`HtmlCanvasElement`]: #only-available-on-wasm")]
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
/// Sets whether `event.preventDefault()` should be called on events on the
/// canvas that have side effects.
@@ -190,50 +149,39 @@ impl WindowAttributesWeb {
/// See [`WindowExtWeb::set_prevent_default()`] for more details.
///
/// Enabled by default.
pub fn with_prevent_default(mut self, prevent_default: bool) -> Self {
self.prevent_default = prevent_default;
self
}
fn with_prevent_default(self, prevent_default: bool) -> Self;
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
/// canvas keyboard events.
///
/// Enabled by default.
pub fn with_focusable(mut self, focusable: bool) -> Self {
self.focusable = focusable;
self
}
fn with_focusable(self, focusable: bool) -> Self;
/// On window creation, append the canvas element to the Web page if it isn't already.
///
/// Disabled by default.
pub fn with_append(mut self, append: bool) -> Self {
self.append = append;
fn with_append(self, append: bool) -> Self;
}
impl WindowAttributesExtWeb for WindowAttributes {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.set_canvas(canvas);
self
}
}
impl PlatformWindowAttributes for WindowAttributesWeb {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> {
Box::from(self.clone())
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
self.platform_specific.prevent_default = prevent_default;
self
}
}
impl PartialEq for WindowAttributesWeb {
fn eq(&self, other: &Self) -> bool {
(match (&self.canvas, &other.canvas) {
(Some(this), Some(other)) => Arc::ptr_eq(this, other),
(None, None) => true,
_ => false,
}) && self.prevent_default.eq(&other.prevent_default)
&& self.focusable.eq(&other.focusable)
&& self.append.eq(&other.append)
fn with_focusable(mut self, focusable: bool) -> Self {
self.platform_specific.focusable = focusable;
self
}
}
impl Default for WindowAttributesWeb {
fn default() -> Self {
Self { canvas: None, prevent_default: true, focusable: true, append: false }
fn with_append(mut self, append: bool) -> Self {
self.platform_specific.append = append;
self
}
}
@@ -242,9 +190,9 @@ pub trait EventLoopExtWeb {
/// Initializes the winit event loop.
///
/// Unlike
#[cfg_attr(target_feature = "exception-handling", doc = "`run_app()`")]
#[cfg_attr(all(web_platform, target_feature = "exception-handling"), doc = "`run_app()`")]
#[cfg_attr(
not(target_feature = "exception-handling"),
not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run_app()`]"
)]
/// [^1], this returns immediately, and doesn't throw an exception in order to
@@ -257,7 +205,7 @@ pub trait EventLoopExtWeb {
#[rustfmt::skip]
///
#[cfg_attr(
not(target_feature = "exception-handling"),
not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run_app()`]: EventLoop::run_app()"
)]
/// [^1]: `run_app()` is _not_ available on Wasm when the target supports `exception-handling`.
@@ -305,21 +253,49 @@ pub trait EventLoopExtWeb {
///
/// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New
/// [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture;
/// Returns whether the user has given permission to access detailed monitor information.
///
/// [`MonitorHandle`]s don't automatically make use of detailed monitor information after
/// permission is granted. New [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn has_detailed_monitor_permission(&self) -> HasMonitorPermissionFuture;
}
impl EventLoopExtWeb for EventLoop {
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
self.event_loop.spawn_app(app);
}
fn set_poll_strategy(&self, strategy: PollStrategy) {
self.event_loop.set_poll_strategy(strategy);
}
fn poll_strategy(&self) -> PollStrategy {
self.event_loop.poll_strategy()
}
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
self.event_loop.set_wait_until_strategy(strategy);
}
fn wait_until_strategy(&self) -> WaitUntilStrategy {
self.event_loop.wait_until_strategy()
}
fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> {
self.event_loop.has_multiple_screens()
}
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
MonitorPermissionFuture(self.event_loop.request_detailed_monitor_permission())
}
fn has_detailed_monitor_permission(&self) -> HasMonitorPermissionFuture {
HasMonitorPermissionFuture(self.event_loop.has_detailed_monitor_permission())
}
}
pub trait ActiveEventLoopExtWeb {
/// Sets the strategy for [`ControlFlow::Poll`].
///
@@ -372,71 +348,94 @@ pub trait ActiveEventLoopExtWeb {
///
/// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New
/// [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture;
/// Returns whether the user has given permission to access detailed monitor information.
///
/// [`MonitorHandle`]s don't automatically make use of detailed monitor information after
/// permission is granted. New [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn has_detailed_monitor_permission(&self) -> bool;
}
impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline]
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture {
let event_loop = self.cast_ref::<WebActiveEventLoop>().expect("non Web event loop on Web");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.create_custom_cursor_async(source)
}
#[inline]
fn set_poll_strategy(&self, strategy: PollStrategy) {
let event_loop = self.cast_ref::<WebActiveEventLoop>().expect("non Web event loop on Web");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.set_poll_strategy(strategy);
}
#[inline]
fn poll_strategy(&self) -> PollStrategy {
let event_loop = self.cast_ref::<WebActiveEventLoop>().expect("non Web event loop on Web");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.poll_strategy()
}
#[inline]
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) {
let event_loop = self.cast_ref::<WebActiveEventLoop>().expect("non Web event loop on Web");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.set_wait_until_strategy(strategy);
}
#[inline]
fn wait_until_strategy(&self) -> WaitUntilStrategy {
let event_loop = self.cast_ref::<WebActiveEventLoop>().expect("non Web event loop on Web");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.wait_until_strategy()
}
#[inline]
fn is_cursor_lock_raw(&self) -> bool {
let event_loop = self.cast_ref::<WebActiveEventLoop>().expect("non Web event loop on Web");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.is_cursor_lock_raw()
}
#[inline]
fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> {
let event_loop = self.cast_ref::<WebActiveEventLoop>().expect("non Web event loop on Web");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.has_multiple_screens()
}
#[inline]
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
let event_loop = self.cast_ref::<WebActiveEventLoop>().expect("non Web event loop on Web");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
MonitorPermissionFuture(event_loop.request_detailed_monitor_permission())
}
#[inline]
fn has_detailed_monitor_permission(&self) -> bool {
let event_loop = self.cast_ref::<WebActiveEventLoop>().expect("non Web event loop on Web");
let event_loop = self
.as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non Web event loop on Web");
event_loop.has_detailed_monitor_permission()
}
}
@@ -492,6 +491,76 @@ pub enum WaitUntilStrategy {
Worker,
}
pub trait CustomCursorExtWeb {
/// Returns if this cursor is an animation.
fn is_animation(&self) -> bool;
/// Creates a new cursor from a URL pointing to an image.
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
///
/// [PNG]: https://en.wikipedia.org/wiki/PNG
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource;
/// Crates a new animated cursor from multiple [`CustomCursor`]s.
/// Supplied `cursors` can't be empty or other animations.
fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<CustomCursorSource, BadAnimation>;
}
impl CustomCursorExtWeb for CustomCursor {
fn is_animation(&self) -> bool {
self.inner.animation
}
fn from_url(url: String, hotspot_x: u16, hotspot_y: u16) -> CustomCursorSource {
CustomCursorSource { inner: PlatformCustomCursorSource::Url { url, hotspot_x, hotspot_y } }
}
fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<CustomCursorSource, BadAnimation> {
if cursors.is_empty() {
return Err(BadAnimation::Empty);
}
if cursors.iter().any(CustomCursor::is_animation) {
return Err(BadAnimation::Animation);
}
Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::Animation { duration, cursors },
})
}
}
/// An error produced when using [`CustomCursor::from_animation`] with invalid arguments.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum BadAnimation {
/// Produced when no cursors were supplied.
Empty,
/// Produced when a supplied cursor is an animation.
Animation,
}
impl fmt::Display for BadAnimation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "No cursors supplied"),
Self::Animation => write!(f, "A supplied cursor is an animation"),
}
}
}
impl Error for BadAnimation {}
#[cfg(not(web_platform))]
struct PlatformCustomCursorFuture;
#[derive(Debug)]
pub struct CustomCursorFuture(pub(crate) PlatformCustomCursorFuture);
@@ -499,7 +568,7 @@ impl Future for CustomCursorFuture {
type Output = Result<CustomCursor, CustomCursorError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor(Arc::new(cursor)))
Pin::new(&mut self.0).poll(cx).map_ok(|cursor| CustomCursor { inner: cursor })
}
}
@@ -521,6 +590,9 @@ impl Display for CustomCursorError {
impl Error for CustomCursorError {}
#[cfg(not(web_platform))]
struct PlatformMonitorPermissionFuture;
/// Can be dropped without aborting the request for detailed monitor permissions.
#[derive(Debug)]
pub struct MonitorPermissionFuture(pub(crate) PlatformMonitorPermissionFuture);
@@ -563,6 +635,9 @@ impl Display for MonitorPermissionError {
impl Error for MonitorPermissionError {}
#[cfg(not(web_platform))]
struct PlatformHasMonitorPermissionFuture;
#[derive(Debug)]
pub struct HasMonitorPermissionFuture(PlatformHasMonitorPermissionFuture);
@@ -575,8 +650,6 @@ impl Future for HasMonitorPermissionFuture {
}
/// Additional methods on [`MonitorHandle`] that are specific to the Web.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
pub trait MonitorHandleExtWeb {
/// Returns whether the screen is internal to the device or external.
///
@@ -604,31 +677,28 @@ pub trait MonitorHandleExtWeb {
/// specific monitor.
///
/// See [`ActiveEventLoopExtWeb::request_detailed_monitor_permission()`].
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn is_detailed(&self) -> bool;
}
impl MonitorHandleExtWeb for dyn MonitorHandleProvider + '_ {
impl MonitorHandleExtWeb for MonitorHandle {
fn is_internal(&self) -> Option<bool> {
self.cast_ref::<WebMonitorHandle>().unwrap().is_internal()
self.inner.is_internal()
}
fn orientation(&self) -> OrientationData {
self.cast_ref::<WebMonitorHandle>().unwrap().orientation()
self.inner.orientation()
}
fn request_lock(&self, orientation_lock: OrientationLock) -> OrientationLockFuture {
let future = self.cast_ref::<WebMonitorHandle>().unwrap().request_lock(orientation_lock);
OrientationLockFuture(future)
OrientationLockFuture(self.inner.request_lock(orientation_lock))
}
fn unlock(&self) -> Result<(), OrientationLockError> {
self.cast_ref::<WebMonitorHandle>().unwrap().unlock()
self.inner.unlock()
}
fn is_detailed(&self) -> bool {
self.cast_ref::<WebMonitorHandle>().unwrap().is_detailed()
self.inner.is_detailed()
}
}
@@ -653,7 +723,7 @@ pub enum Orientation {
Portrait,
}
/// Screen orientation lock options. Represents which orientations a user can use.
/// Screen orientation lock options. Reoresents which orientations a user can use.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum OrientationLock {
/// User is free to use any orientation.
@@ -678,6 +748,9 @@ pub enum OrientationLock {
},
}
#[cfg(not(web_platform))]
struct PlatformOrientationLockFuture;
/// Can be dropped without aborting the request to lock the screen.
#[derive(Debug)]
pub struct OrientationLockFuture(PlatformOrientationLockFuture);

View File

@@ -1,51 +1,29 @@
//! # Winit Win32 / Windows backend
//! # Windows
//!
//! The supported OS version is Windows 7 or higher, though Windows 10 is
//! tested regularly.
#![cfg(target_os = "windows")] // FIXME(madsmtm): Allow compiling on all platforms.
#[macro_use]
mod util;
mod dark_mode;
mod definitions;
mod dpi;
mod drop_handler;
mod event_loop;
mod icon;
mod ime;
mod keyboard;
mod keyboard_layout;
mod monitor;
mod raw_input;
mod window;
mod window_state;
use std::borrow::Borrow;
use std::ffi::c_void;
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc;
use ::dpi::PhysicalSize;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(windows_platform)]
use windows_sys::Win32::Foundation::HANDLE;
use winit_core::event::DeviceId;
use winit_core::icon::{BadIcon, Icon};
use winit_core::window::{PlatformWindowAttributes, Window as CoreWindow};
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
use self::icon::{RaiiIcon, SelectedCursor};
pub use self::keyboard::{physicalkey_to_scancode, scancode_to_physicalkey};
pub use self::monitor::{MonitorHandle, VideoModeHandle};
pub use self::window::Window;
use crate::dpi::PhysicalSize;
use crate::event::DeviceId;
use crate::event_loop::EventLoopBuilder;
use crate::monitor::MonitorHandle;
use crate::window::{BadIcon, Icon, Window, WindowAttributes};
/// Window Handle type used by Win32 API
pub type HWND = *mut c_void;
pub type HWND = isize;
/// Menu Handle type used by Win32 API
pub type HMENU = *mut c_void;
pub type HMENU = isize;
/// Monitor Handle type used by Win32 API
pub type HMONITOR = *mut c_void;
pub type HMONITOR = isize;
/// Describes a system-drawn backdrop material of a window.
///
@@ -140,24 +118,24 @@ pub enum CornerPreference {
///
/// See [`WindowBorrowExtWindows::any_thread`] for more information.
#[derive(Clone, Debug)]
pub struct AnyThread<W: CoreWindow>(W);
pub struct AnyThread<W: Window>(W);
impl<W: CoreWindow> AnyThread<W> {
impl<W: Window> AnyThread<W> {
/// Get a reference to the inner window.
#[inline]
pub fn get_ref(&self) -> &dyn CoreWindow {
pub fn get_ref(&self) -> &dyn Window {
&self.0
}
}
impl<W: CoreWindow> Deref for AnyThread<W> {
impl<W: Window> Deref for AnyThread<W> {
type Target = W;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<W: CoreWindow> rwh_06::HasWindowHandle for AnyThread<W> {
impl<W: Window> rwh_06::HasWindowHandle for AnyThread<W> {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
// SAFETY: The top level user has asserted this is only used safely.
unsafe { self.get_ref().window_handle_any_thread() }
@@ -232,13 +210,36 @@ pub trait EventLoopBuilderExtWindows {
F: FnMut(*const c_void) -> bool + 'static;
}
impl EventLoopBuilderExtWindows for EventLoopBuilder {
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
#[inline]
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self {
self.platform_specific.dpi_aware = dpi_aware;
self
}
#[inline]
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
where
F: FnMut(*const c_void) -> bool + 'static,
{
self.platform_specific.msg_hook = Some(Box::new(callback));
self
}
}
/// Additional methods on `Window` that are specific to Windows.
pub trait WindowExtWindows {
/// Enables or disables mouse and keyboard input to the specified window.
///
/// A window must be enabled before it can be activated.
/// If an application has create a modal dialog box by disabling its owner window
/// (as described in [`WindowAttributesWindows::with_owner_window`]), the application must
/// (as described in [`WindowAttributesExtWindows::with_owner_window`]), the application must
/// enable the owner window before destroying the dialog box.
/// Otherwise, another window will receive the keyboard focus and be activated.
///
@@ -285,15 +286,6 @@ pub trait WindowExtWindows {
/// Supported starting with Windows 11 Build 22000.
fn set_corner_preference(&self, preference: CornerPreference);
/// Sets if the reported [`winit_core::event::WindowEvent::MouseWheel`] event
/// should account for scroll speed system settings.
///
/// The default scroll speed on Windows is 3 lines/characters per scroll,
/// this will be 1 if you set it to false.
///
/// The default is `true`.
fn set_use_system_scroll_speed(&self, should_use: bool);
/// Get the raw window handle for this [`Window`] without checking for thread affinity.
///
/// Window handles in Win32 have a property called "thread affinity" that ties them to their
@@ -349,40 +341,40 @@ pub trait WindowExtWindows {
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError>;
}
impl WindowExtWindows for dyn CoreWindow + '_ {
impl WindowExtWindows for dyn Window + '_ {
#[inline]
fn set_enable(&self, enabled: bool) {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_enable(enabled)
}
#[inline]
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_taskbar_icon(taskbar_icon)
}
#[inline]
fn set_skip_taskbar(&self, skip: bool) {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_skip_taskbar(skip)
}
#[inline]
fn set_undecorated_shadow(&self, shadow: bool) {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_undecorated_shadow(shadow)
}
#[inline]
fn set_system_backdrop(&self, backdrop_type: BackdropType) {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_system_backdrop(backdrop_type)
}
#[inline]
fn set_border_color(&self, color: Option<Color>) {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_border_color(color.unwrap_or(Color::NONE))
}
@@ -391,31 +383,26 @@ impl WindowExtWindows for dyn CoreWindow + '_ {
// The windows docs don't mention NONE as a valid options but it works in practice and is
// useful to circumvent the Windows option "Show accent color on title bars and
// window borders"
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_title_background_color(color.unwrap_or(Color::NONE))
}
#[inline]
fn set_title_text_color(&self, color: Color) {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_title_text_color(color)
}
#[inline]
fn set_corner_preference(&self, preference: CornerPreference) {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_corner_preference(preference)
}
fn set_use_system_scroll_speed(&self, should_use: bool) {
let window = self.cast_ref::<Window>().unwrap();
window.set_use_system_scroll_speed(should_use)
}
unsafe fn window_handle_any_thread(
&self,
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let window = self.cast_ref::<Window>().unwrap();
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
unsafe {
let handle = window.rwh_06_no_thread_check()?;
@@ -428,7 +415,7 @@ impl WindowExtWindows for dyn CoreWindow + '_ {
/// Additional methods for anything that dereference to [`Window`].
///
/// [`Window`]: crate::window::Window
pub trait WindowBorrowExtWindows: Borrow<dyn CoreWindow> + Sized {
pub trait WindowBorrowExtWindows: Borrow<dyn Window> + Sized {
/// Create an object that allows accessing the inner window handle in a thread-unsafe way.
///
/// It is possible to call [`window_handle_any_thread`] to get around Windows's thread
@@ -449,59 +436,17 @@ pub trait WindowBorrowExtWindows: Borrow<dyn CoreWindow> + Sized {
/// [`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread
unsafe fn any_thread(self) -> AnyThread<Self>
where
Self: CoreWindow,
Self: Window,
{
AnyThread(self)
}
}
impl<W: Borrow<dyn CoreWindow> + Sized> WindowBorrowExtWindows for W {}
impl<W: Borrow<dyn Window> + Sized> WindowBorrowExtWindows for W {}
#[derive(Clone, Debug)]
pub struct WindowAttributesWindows {
pub(crate) owner: Option<HWND>,
pub(crate) menu: Option<HMENU>,
pub(crate) taskbar_icon: Option<Icon>,
pub(crate) no_redirection_bitmap: bool,
pub(crate) drag_and_drop: bool,
pub(crate) skip_taskbar: bool,
pub(crate) class_name: String,
pub(crate) decoration_shadow: bool,
pub(crate) backdrop_type: BackdropType,
pub(crate) clip_children: bool,
pub(crate) border_color: Option<Color>,
pub(crate) title_background_color: Option<Color>,
pub(crate) title_text_color: Option<Color>,
pub(crate) corner_preference: Option<CornerPreference>,
pub(crate) use_system_wheel_speed: bool,
}
impl Default for WindowAttributesWindows {
fn default() -> Self {
Self {
owner: None,
menu: None,
taskbar_icon: None,
no_redirection_bitmap: false,
drag_and_drop: true,
skip_taskbar: false,
class_name: "Window Class".to_string(),
decoration_shadow: false,
backdrop_type: BackdropType::default(),
clip_children: true,
border_color: None,
title_background_color: None,
title_text_color: None,
corner_preference: None,
use_system_wheel_speed: true,
}
}
}
unsafe impl Send for WindowAttributesWindows {}
unsafe impl Sync for WindowAttributesWindows {}
impl WindowAttributesWindows {
/// Additional methods on `WindowAttributes` that are specific to Windows.
#[allow(rustdoc::broken_intra_doc_links)]
pub trait WindowAttributesExtWindows {
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
/// This only works when [`WindowAttributes::with_parent_window`] isn't called or set to `None`.
/// Can be used in combination with
@@ -514,12 +459,7 @@ impl WindowAttributesWindows {
/// - An owned window is hidden when its owner is minimized.
///
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
///
/// [`WindowAttributes::with_parent_window`]: winit_core::window::WindowAttributes::with_parent_window
pub fn with_owner_window(mut self, parent: HWND) -> Self {
self.owner = Some(parent);
self
}
fn with_owner_window(self, parent: HWND) -> Self;
/// Sets a menu on the window to be created.
///
@@ -530,24 +470,18 @@ impl WindowAttributesWindows {
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how
/// 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.
///
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
pub fn with_menu(mut self, menu: HMENU) -> Self {
self.menu = Some(menu);
self
}
#[cfg_attr(
windows_platform,
doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
)]
#[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.
pub fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self {
self.taskbar_icon = taskbar_icon;
self
}
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> Self;
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
pub fn with_no_redirection_bitmap(mut self, flag: bool) -> Self {
self.no_redirection_bitmap = flag;
self
}
fn with_no_redirection_bitmap(self, flag: bool) -> Self;
/// Enables or disables drag and drop support (enabled by default). Will interfere with other
/// crates that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED`
@@ -555,94 +489,153 @@ impl WindowAttributesWindows {
/// attempt to initialize COM API regardless of this option. Currently only fullscreen mode
/// does that, but there may be more in the future. If you need COM API with
/// `COINIT_MULTITHREADED` you must initialize it before calling any winit functions. See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
pub fn with_drag_and_drop(mut self, flag: bool) -> Self {
self.drag_and_drop = flag;
self
}
fn with_drag_and_drop(self, flag: bool) -> Self;
/// Whether show or hide the window icon in the taskbar.
pub fn with_skip_taskbar(mut self, skip: bool) -> Self {
self.skip_taskbar = skip;
self
}
fn with_skip_taskbar(self, skip: bool) -> Self;
/// Customize the window class name.
pub fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self {
self.class_name = class_name.into();
self
}
fn with_class_name<S: Into<String>>(self, class_name: S) -> Self;
/// Shows or hides the background drop shadow for undecorated windows.
///
/// The shadow is hidden by default.
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
pub fn with_undecorated_shadow(mut self, shadow: bool) -> Self {
self.decoration_shadow = shadow;
self
}
fn with_undecorated_shadow(self, shadow: bool) -> Self;
/// Sets system-drawn backdrop type.
///
/// Requires Windows 11 build 22523+.
pub fn with_system_backdrop(mut self, backdrop_type: BackdropType) -> Self {
self.backdrop_type = backdrop_type;
self
}
fn with_system_backdrop(self, backdrop_type: BackdropType) -> Self;
/// This sets or removes `WS_CLIPCHILDREN` style.
pub fn with_clip_children(mut self, flag: bool) -> Self {
self.clip_children = flag;
self
}
fn with_clip_children(self, flag: bool) -> Self;
/// Sets the color of the window border.
///
/// Supported starting with Windows 11 Build 22000.
pub fn with_border_color(mut self, color: Option<Color>) -> Self {
self.border_color = Some(color.unwrap_or(Color::NONE));
self
}
fn with_border_color(self, color: Option<Color>) -> Self;
/// Sets the background color of the title bar.
///
/// Supported starting with Windows 11 Build 22000.
pub fn with_title_background_color(mut self, color: Option<Color>) -> Self {
self.title_background_color = Some(color.unwrap_or(Color::NONE));
self
}
fn with_title_background_color(self, color: Option<Color>) -> Self;
/// Sets the color of the window title.
///
/// Supported starting with Windows 11 Build 22000.
pub fn with_title_text_color(mut self, color: Color) -> Self {
self.title_text_color = Some(color);
self
}
fn with_title_text_color(self, color: Color) -> Self;
/// Sets the preferred style of the window corners.
///
/// Supported starting with Windows 11 Build 22000.
pub fn with_corner_preference(mut self, corners: CornerPreference) -> Self {
self.corner_preference = Some(corners);
fn with_corner_preference(self, corners: CornerPreference) -> Self;
}
impl WindowAttributesExtWindows for WindowAttributes {
#[inline]
fn with_owner_window(mut self, parent: HWND) -> Self {
self.platform_specific.owner = Some(parent);
self
}
/// Sets if the reported [`winit_core::event::WindowEvent::MouseWheel`] event
/// should account for scroll speed system settings.
///
/// The default scroll speed on Windows is 3 lines/characters per scroll,
/// this will be 1 if you set it to false.
///
/// The default is `true`.
pub fn with_use_system_scroll_speed(mut self, should_use: bool) -> Self {
self.use_system_wheel_speed = should_use;
#[inline]
fn with_menu(mut self, menu: HMENU) -> Self {
self.platform_specific.menu = Some(menu);
self
}
#[inline]
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self {
self.platform_specific.taskbar_icon = taskbar_icon;
self
}
#[inline]
fn with_no_redirection_bitmap(mut self, flag: bool) -> Self {
self.platform_specific.no_redirection_bitmap = flag;
self
}
#[inline]
fn with_drag_and_drop(mut self, flag: bool) -> Self {
self.platform_specific.drag_and_drop = flag;
self
}
#[inline]
fn with_skip_taskbar(mut self, skip: bool) -> Self {
self.platform_specific.skip_taskbar = skip;
self
}
#[inline]
fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self {
self.platform_specific.class_name = class_name.into();
self
}
#[inline]
fn with_undecorated_shadow(mut self, shadow: bool) -> Self {
self.platform_specific.decoration_shadow = shadow;
self
}
#[inline]
fn with_system_backdrop(mut self, backdrop_type: BackdropType) -> Self {
self.platform_specific.backdrop_type = backdrop_type;
self
}
#[inline]
fn with_clip_children(mut self, flag: bool) -> Self {
self.platform_specific.clip_children = flag;
self
}
#[inline]
fn with_border_color(mut self, color: Option<Color>) -> Self {
self.platform_specific.border_color = Some(color.unwrap_or(Color::NONE));
self
}
#[inline]
fn with_title_background_color(mut self, color: Option<Color>) -> Self {
self.platform_specific.title_background_color = Some(color.unwrap_or(Color::NONE));
self
}
#[inline]
fn with_title_text_color(mut self, color: Color) -> Self {
self.platform_specific.title_text_color = Some(color);
self
}
#[inline]
fn with_corner_preference(mut self, corners: CornerPreference) -> Self {
self.platform_specific.corner_preference = Some(corners);
self
}
}
impl PlatformWindowAttributes for WindowAttributesWindows {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> {
Box::from(self.clone())
/// Additional methods on `MonitorHandle` that are specific to Windows.
pub trait MonitorHandleExtWindows {
/// Returns the name of the monitor adapter specific to the Win32 API.
fn native_id(&self) -> String;
/// Returns the handle of the monitor - `HMONITOR`.
fn hmonitor(&self) -> HMONITOR;
}
impl MonitorHandleExtWindows for MonitorHandle {
#[inline]
fn native_id(&self) -> String {
self.inner.native_identifier()
}
#[inline]
fn hmonitor(&self) -> HMONITOR {
self.inner.hmonitor()
}
}
@@ -654,35 +647,20 @@ pub trait DeviceIdExtWindows {
fn persistent_identifier(&self) -> Option<String>;
}
#[cfg(windows_platform)]
impl DeviceIdExtWindows for DeviceId {
fn persistent_identifier(&self) -> Option<String> {
let raw_id = self.into_raw();
if raw_id != 0 {
raw_input::get_raw_input_device_name(raw_id as HANDLE)
crate::platform_impl::raw_input::get_raw_input_device_name(raw_id as HANDLE)
} else {
None
}
}
}
/// Windows specific `Icon`.
///
/// 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.
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct WinIcon {
pub(crate) inner: Arc<RaiiIcon>,
}
impl WinIcon {
/// Additional methods on `Icon` that are specific to Windows.
pub trait IconExtWindows: Sized {
/// Create an icon from a file path.
///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
@@ -690,88 +668,30 @@ impl WinIcon {
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
pub fn from_path<P: AsRef<Path>>(
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.
///
/// 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.
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon>;
}
impl IconExtWindows for Icon {
fn from_path<P: AsRef<Path>>(
path: P,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
Self::from_path_impl(path, size)
let win_icon = crate::platform_impl::WinIcon::from_path(path, size)?;
Ok(Icon { inner: win_icon })
}
/// 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`]: Self::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.
///
/// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size.
pub fn from_resource(
resource_id: u16,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
Self::from_resource_impl(resource_id, size)
}
/// 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`]: Self::from_resource
///
/// ```rust,no_run
/// use winit::platform::windows::WinIcon;
///
/// assert!(WinIcon::from_resource_name("app", None).is_ok());
/// assert!(WinIcon::from_resource(1, None).is_ok());
/// assert!(WinIcon::from_resource(27, None).is_ok());
/// assert!(WinIcon::from_resource_name("27", None).is_err());
/// assert!(WinIcon::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::WinIcon;
/// # use winit::icon::Icon;
/// assert!(WinIcon::from_resource_name("0", None).is_ok());
/// assert!(WinIcon::from_resource(0, None).is_err());
/// ```
pub fn from_resource_name(
resource_name: &str,
size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> {
Self::from_resource_name_impl(resource_name, size)
}
}
impl From<WinIcon> for Icon {
fn from(value: WinIcon) -> Self {
Self(Arc::new(value))
fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
Ok(Icon { inner: win_icon })
}
}

View File

@@ -1,32 +1,11 @@
//! # X11
use dpi::Size;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use winit_core::event_loop::ActiveEventLoop as CoreActiveEventLoop;
use winit_core::window::{ActivationToken, PlatformWindowAttributes, Window as CoreWindow};
pub use crate::event_loop::{ActiveEventLoop, EventLoop};
pub use crate::window::Window;
macro_rules! os_error {
($error:expr) => {{
winit_core::error::OsError::new(line!(), file!(), $error)
}};
}
mod activation;
mod atoms;
mod dnd;
mod event_loop;
mod event_processor;
pub mod ffi;
mod ime;
mod monitor;
mod util;
mod window;
mod xdisplay;
mod xsettings;
use crate::dpi::Size;
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::window::{Window as CoreWindow, WindowAttributes};
/// X window type. Maps directly to
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
@@ -101,21 +80,19 @@ pub type XWindow = u32;
#[inline]
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook.
crate::event_loop::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.
///
/// [`ActiveEventLoop`]: winit_core::event_loop::ActiveEventLoop
pub trait ActiveEventLoopExtX11 {
/// True if the event loop uses X11.
/// True if the [`ActiveEventLoop`] uses X11.
fn is_x11(&self) -> bool;
}
impl ActiveEventLoopExtX11 for dyn CoreActiveEventLoop + '_ {
impl ActiveEventLoopExtX11 for dyn ActiveEventLoop + '_ {
#[inline]
fn is_x11(&self) -> bool {
self.cast_ref::<ActiveEventLoop>().is_some()
self.as_any().downcast_ref::<crate::platform_impl::x11::ActiveEventLoop>().is_some()
}
}
@@ -125,6 +102,13 @@ pub trait EventLoopExtX11 {
fn is_x11(&self) -> bool;
}
impl EventLoopExtX11 for EventLoop {
#[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.
@@ -137,6 +121,20 @@ pub trait EventLoopBuilderExtX11 {
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
}
impl EventLoopBuilderExtX11 for EventLoopBuilder {
#[inline]
fn with_x11(&mut self) -> &mut Self {
self.platform_specific.forced_backend = Some(crate::platform_impl::Backend::X);
self
}
#[inline]
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
self.platform_specific.any_thread = any_thread;
self
}
}
/// Additional methods on [`Window`] that are specific to X11.
///
/// [`Window`]: crate::window::Window
@@ -144,52 +142,12 @@ pub trait WindowExtX11 {}
impl WindowExtX11 for dyn CoreWindow {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ApplicationName {
pub(crate) general: String,
pub(crate) instance: String,
}
#[derive(Clone, Debug)]
pub struct WindowAttributesX11 {
pub(crate) name: Option<ApplicationName>,
pub(crate) activation_token: Option<ActivationToken>,
pub(crate) visual_id: Option<XVisualID>,
pub(crate) screen_id: Option<i32>,
pub(crate) base_size: Option<Size>,
pub(crate) override_redirect: bool,
pub(crate) x11_window_types: Vec<WindowType>,
/// The parent window to embed this window into.
pub(crate) embed_window: Option<XWindow>,
}
impl Default for WindowAttributesX11 {
fn default() -> Self {
Self {
name: None,
activation_token: None,
visual_id: None,
screen_id: None,
base_size: None,
override_redirect: false,
x11_window_types: vec![WindowType::Normal],
embed_window: None,
}
}
}
impl WindowAttributesX11 {
/// Additional methods on [`WindowAttributes`] that are specific to X11.
pub trait WindowAttributesExtX11 {
/// Create this window with a specific X11 visual.
pub fn with_x11_visual(mut self, visual_id: XVisualID) -> Self {
self.visual_id = Some(visual_id);
self
}
fn with_x11_visual(self, visual_id: XVisualID) -> Self;
pub fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.screen_id = Some(screen_id);
self
}
fn with_x11_screen(self, screen_id: i32) -> Self;
/// Build window with the given `general` and `instance` names.
///
@@ -199,39 +157,27 @@ impl WindowAttributesX11 {
///
/// For details about application ID conventions, see the
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
pub fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.name = Some(ApplicationName { general: general.into(), instance: instance.into() });
self
}
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
/// Build window with override-redirect flag; defaults to false.
pub fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.override_redirect = override_redirect;
self
}
fn with_override_redirect(self, override_redirect: bool) -> Self;
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`.
pub fn with_x11_window_type(mut self, x11_window_types: Vec<WindowType>) -> Self {
self.x11_window_types = x11_window_types;
self
}
fn with_x11_window_type(self, x11_window_type: Vec<WindowType>) -> Self;
/// Build window with base size hint.
///
/// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::{Window, WindowAttributes};
/// # use winit::platform::x11::WindowAttributesX11;
/// # use winit::platform::x11::WindowAttributesExtX11;
/// // Specify the size in logical dimensions like this:
/// WindowAttributesX11::default().with_base_size(LogicalSize::new(400.0, 200.0));
/// WindowAttributes::default().with_base_size(LogicalSize::new(400.0, 200.0));
///
/// // Or specify the size in physical dimensions like this:
/// WindowAttributesX11::default().with_base_size(PhysicalSize::new(400, 200));
/// WindowAttributes::default().with_base_size(PhysicalSize::new(400, 200));
/// ```
pub fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.base_size = Some(base_size.into());
self
}
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
/// Embed this window into another parent window.
///
@@ -240,28 +186,70 @@ impl WindowAttributesX11 {
/// ```no_run
/// use winit::window::{Window, WindowAttributes};
/// use winit::event_loop::ActiveEventLoop;
/// use winit::platform::x11::{XWindow, WindowAttributesX11};
/// use winit::platform::x11::{XWindow, WindowAttributesExtX11};
/// # fn create_window(event_loop: &dyn ActiveEventLoop) -> Result<(), Box<dyn std::error::Error>> {
/// let parent_window_id = std::env::args().nth(1).unwrap().parse::<XWindow>()?;
/// let window_x11_attributes = WindowAttributesX11::default().with_embed_parent_window(parent_window_id);
/// let window_attributes = WindowAttributes::default().with_platform_attributes(Box::new(window_x11_attributes));
/// let window_attributes = WindowAttributes::default().with_embed_parent_window(parent_window_id);
/// let window = event_loop.create_window(window_attributes)?;
/// # Ok(()) }
/// ```
pub fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self {
self.embed_window = Some(parent_window_id);
fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self;
}
impl WindowAttributesExtX11 for WindowAttributes {
#[inline]
fn with_x11_visual(mut self, visual_id: XVisualID) -> Self {
self.platform_specific.x11.visual_id = Some(visual_id);
self
}
#[inline]
pub fn with_activation_token(mut self, token: ActivationToken) -> Self {
self.activation_token = Some(token);
fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.platform_specific.x11.screen_id = Some(screen_id);
self
}
#[inline]
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
self.platform_specific.name =
Some(crate::platform_impl::ApplicationName::new(general.into(), instance.into()));
self
}
#[inline]
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
self.platform_specific.x11.override_redirect = override_redirect;
self
}
#[inline]
fn with_x11_window_type(mut self, x11_window_types: Vec<WindowType>) -> Self {
self.platform_specific.x11.x11_window_types = x11_window_types;
self
}
#[inline]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.platform_specific.x11.base_size = Some(base_size.into());
self
}
#[inline]
fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self {
self.platform_specific.x11.embed_window = Some(parent_window_id);
self
}
}
impl PlatformWindowAttributes for WindowAttributesX11 {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> {
Box::from(self.clone())
/// Additional methods on `MonitorHandle` that are specific to X11.
pub trait MonitorHandleExtX11 {
/// Returns the inner identifier of the monitor.
fn native_id(&self) -> u32;
}
impl MonitorHandleExtX11 for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
}

View File

@@ -1,8 +1,7 @@
use android_activity::input::{KeyAction, KeyEvent, KeyMapChar, Keycode};
use android_activity::AndroidApp;
use winit_core::keyboard::{
Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
PhysicalKey::Code(match keycode {
@@ -144,8 +143,8 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::AltLeft => KeyCode::AltLeft,
Keycode::AltRight => KeyCode::AltRight,
Keycode::MetaLeft => KeyCode::MetaLeft,
Keycode::MetaRight => KeyCode::MetaRight,
Keycode::MetaLeft => KeyCode::SuperLeft,
Keycode::MetaRight => KeyCode::SuperRight,
Keycode::LeftBracket => KeyCode::BracketLeft,
Keycode::RightBracket => KeyCode::BracketRight,
@@ -310,7 +309,7 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
ShiftLeft => Key::Named(NamedKey::Shift),
ShiftRight => Key::Named(NamedKey::Shift),
Tab => Key::Named(NamedKey::Tab),
Space => Key::Character(" ".into()),
Space => Key::Named(NamedKey::Space),
Sym => Key::Named(NamedKey::Symbol),
Explorer => Key::Named(NamedKey::LaunchWebBrowser),
Envelope => Key::Named(NamedKey::LaunchMail),
@@ -341,8 +340,8 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
CtrlRight => Key::Named(NamedKey::Control),
CapsLock => Key::Named(NamedKey::CapsLock),
ScrollLock => Key::Named(NamedKey::ScrollLock),
MetaLeft => Key::Named(NamedKey::Meta),
MetaRight => Key::Named(NamedKey::Meta),
MetaLeft => Key::Named(NamedKey::Super),
MetaRight => Key::Named(NamedKey::Super),
Function => Key::Named(NamedKey::Fn),
Sysrq => Key::Named(NamedKey::PrintScreen),
Break => Key::Named(NamedKey::Pause),

View File

@@ -1,6 +1,6 @@
use std::cell::Cell;
use std::fmt;
use std::hash::Hash;
use std::num::{NonZeroU16, NonZeroU32};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
@@ -9,26 +9,32 @@ use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
};
use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use tracing::{debug, trace, warn};
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{Cursor, CustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
use winit_core::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter};
use winit_core::event_loop::pump_events::PumpStatus;
use winit_core::event_loop::{
use crate::application::ApplicationHandler;
use crate::cursor::Cursor;
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use winit_core::window::{
self, CursorGrabMode, ImeCapabilities, ImePurpose, ImeRequest, ImeRequestError,
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::pump_events::PumpStatus;
use crate::window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, ImePurpose,
ResizeDirection, Theme, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
WindowLevel,
};
use crate::keycodes;
mod keycodes;
pub(crate) use crate::cursor::{
NoCustomCursor as PlatformCustomCursor, NoCustomCursor as PlatformCustomCursorSource,
};
pub(crate) use crate::icon::NoIcon as PlatformIcon;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
@@ -39,17 +45,16 @@ fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}
#[derive(Clone, Debug)]
#[derive(Clone)]
struct SharedFlagSetter {
flag: Arc<AtomicBool>,
}
impl SharedFlagSetter {
fn set(&self) -> bool {
pub fn set(&self) -> bool {
self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok()
}
}
#[derive(Debug)]
struct SharedFlag {
flag: Arc<AtomicBool>,
}
@@ -59,37 +64,31 @@ struct SharedFlag {
// we just need to know at the start of a main loop iteration if a redraw
// was queued and be able to read and clear the state atomically)
impl SharedFlag {
fn new() -> Self {
pub fn new() -> Self {
Self { flag: Arc::new(AtomicBool::new(false)) }
}
fn setter(&self) -> SharedFlagSetter {
pub fn setter(&self) -> SharedFlagSetter {
SharedFlagSetter { flag: self.flag.clone() }
}
fn get_and_reset(&self) -> bool {
pub fn get_and_reset(&self) -> bool {
self.flag.swap(false, std::sync::atomic::Ordering::AcqRel)
}
}
#[derive(Clone)]
struct RedrawRequester {
pub struct RedrawRequester {
flag: SharedFlagSetter,
waker: AndroidAppWaker,
}
impl fmt::Debug for RedrawRequester {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RedrawRequester").field("flag", &self.flag).finish_non_exhaustive()
}
}
impl RedrawRequester {
fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self {
RedrawRequester { flag: flag.setter(), waker }
}
fn request_redraw(&self) {
pub fn request_redraw(&self) {
if self.flag.set() {
// Only explicitly try to wake up the main loop when the flag
// value changes
@@ -98,9 +97,11 @@ impl RedrawRequester {
}
}
#[derive(Debug)]
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {}
pub struct EventLoop {
pub android_app: AndroidApp,
pub(crate) android_app: AndroidApp,
window_target: ActiveEventLoop,
redraw_flag: SharedFlag,
loop_running: bool, // Dispatched `NewEvents<Init>`
@@ -113,9 +114,9 @@ pub struct EventLoop {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PlatformSpecificEventLoopAttributes {
pub android_app: Option<AndroidApp>,
pub ignore_volume_keys: bool,
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) android_app: Option<AndroidApp>,
pub(crate) ignore_volume_keys: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
@@ -128,13 +129,9 @@ impl Default for PlatformSpecificEventLoopAttributes {
const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0);
impl EventLoop {
pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
// For better cross-platformness.
return Err(EventLoopError::RecreationAttempt);
}
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
Android",
@@ -164,7 +161,7 @@ impl EventLoop {
})
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
pub(crate) fn window_target(&self) -> &dyn RootActiveEventLoop {
&self.window_target
}
@@ -482,8 +479,7 @@ impl EventLoop {
location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0,
text: None,
text_with_all_modifiers: None,
key_without_modifiers: keycodes::to_logical(key_char, keycode),
platform_specific: KeyEventExtra {},
},
is_synthetic: false,
};
@@ -550,6 +546,8 @@ impl EventLoop {
if self.exiting() {
self.loop_running = false;
app.exiting(&self.window_target);
PumpStatus::Exit(0)
} else {
PumpStatus::Continue
@@ -644,12 +642,6 @@ pub struct EventLoopProxy {
waker: AndroidAppWaker,
}
impl fmt::Debug for EventLoopProxy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EventLoopProxy").field("wake_up", &self.wake_up).finish_non_exhaustive()
}
}
impl EventLoopProxy {
fn new(waker: AndroidAppWaker) -> Self {
Self { wake_up: AtomicBool::new(false), waker }
@@ -663,7 +655,6 @@ impl EventLoopProxyProvider for EventLoopProxy {
}
}
#[derive(Debug)]
pub struct ActiveEventLoop {
pub(crate) app: AndroidApp,
control_flow: Cell<ControlFlow>,
@@ -697,11 +688,11 @@ impl RootActiveEventLoop for ActiveEventLoop {
Err(NotSupportedError::new("create_custom_cursor is not supported").into())
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new(std::iter::empty())
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
fn primary_monitor(&self) -> Option<RootMonitorHandle> {
None
}
@@ -756,10 +747,8 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowAttributes;
#[derive(Debug)]
pub struct Window {
pub(crate) struct Window {
app: AndroidApp,
ime_capabilities: Mutex<Option<ImeCapabilities>>,
redraw_requester: RedrawRequester,
}
@@ -770,18 +759,14 @@ impl Window {
) -> Result<Self, RequestError> {
// FIXME this ignores requested window attributes
Ok(Self {
app: el.app.clone(),
ime_capabilities: Default::default(),
redraw_requester: el.redraw_requester.clone(),
})
Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() })
}
pub(crate) fn config(&self) -> ConfigurationRef {
pub fn config(&self) -> ConfigurationRef {
self.app.config()
}
pub(crate) fn content_rect(&self) -> Rect {
pub fn content_rect(&self) -> Rect {
self.app.content_rect()
}
@@ -826,15 +811,15 @@ impl CoreWindow for Window {
GLOBAL_WINDOW
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
fn primary_monitor(&self) -> Option<RootMonitorHandle> {
None
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new(std::iter::empty())
}
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
fn current_monitor(&self) -> Option<RootMonitorHandle> {
None
}
@@ -876,16 +861,8 @@ impl CoreWindow for Window {
PhysicalInsets::new(0, 0, 0, 0)
}
fn min_surface_size(&self) -> Option<PhysicalSize<u32>> {
None
}
fn set_min_surface_size(&self, _: Option<Size>) {}
fn max_surface_size(&self) -> Option<PhysicalSize<u32>> {
None
}
fn set_max_surface_size(&self, _: Option<Size>) {}
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
@@ -896,16 +873,8 @@ impl CoreWindow for Window {
fn set_title(&self, _title: &str) {}
fn is_transparent(&self) -> bool {
false
}
fn set_transparent(&self, _transparent: bool) {}
fn is_blurred(&self) -> bool {
false
}
fn set_blur(&self, _blur: bool) {}
fn set_visible(&self, _visibility: bool) {}
@@ -952,47 +921,18 @@ impl CoreWindow for Window {
true
}
fn window_level(&self) -> WindowLevel {
WindowLevel::default()
}
fn set_window_level(&self, _level: WindowLevel) {}
fn window_icon(&self) -> Option<winit_core::icon::Icon> {
None
}
fn set_window_icon(&self, _window_icon: Option<winit_core::icon::Icon>) {}
fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
let mut current_caps = self.ime_capabilities.lock().unwrap();
match request {
ImeRequest::Enable(enable) => {
let (capabilities, _) = enable.into_raw();
if current_caps.is_some() {
return Err(ImeRequestError::AlreadyEnabled);
}
*current_caps = Some(capabilities);
self.app.show_soft_input(true);
},
ImeRequest::Update(_) => {
if current_caps.is_none() {
return Err(ImeRequestError::NotEnabled);
}
},
ImeRequest::Disable => {
*current_caps = None;
self.app.hide_soft_input(true);
},
fn set_ime_allowed(&self, allowed: bool) {
if allowed {
self.app.show_soft_input(true);
} else {
self.app.hide_soft_input(true);
}
Ok(())
}
fn ime_capabilities(&self) -> Option<ImeCapabilities> {
*self.ime_capabilities.lock().unwrap()
}
fn set_ime_purpose(&self, _purpose: ImePurpose) {}
@@ -1001,10 +941,6 @@ impl CoreWindow for Window {
fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
fn cursor(&self) -> Cursor {
Cursor::default()
}
fn set_cursor(&self, _: Cursor) {}
fn set_cursor_position(&self, _: Position) -> Result<(), RequestError> {
@@ -1038,10 +974,6 @@ impl CoreWindow for Window {
None
}
fn content_protected(&self) -> bool {
false
}
fn set_content_protected(&self, _protected: bool) {}
fn has_focus(&self) -> bool {
@@ -1063,6 +995,62 @@ impl CoreWindow for Window {
}
}
#[derive(Default, Clone, Debug)]
pub struct OsError;
use std::fmt::{self, Display, Formatter};
impl Display for OsError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(fmt, "Android OS Error")
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MonitorHandle;
impl MonitorHandle {
pub fn name(&self) -> Option<String> {
unreachable!()
}
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
unreachable!()
}
pub fn scale_factor(&self) -> f64 {
unreachable!()
}
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
unreachable!()
}
pub fn video_modes(&self) -> std::iter::Empty<VideoModeHandle> {
unreachable!()
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct VideoModeHandle;
impl VideoModeHandle {
pub fn size(&self) -> PhysicalSize<u32> {
unreachable!()
}
pub fn bit_depth(&self) -> Option<NonZeroU16> {
unreachable!()
}
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
unreachable!()
}
pub fn monitor(&self) -> MonitorHandle {
unreachable!()
}
}
fn screen_size(app: &AndroidApp) -> PhysicalSize<u32> {
if let Some(native_window) = app.native_window() {
PhysicalSize::new(native_window.width() as _, native_window.height() as _)

View File

@@ -0,0 +1,89 @@
#![allow(clippy::unnecessary_cast)]
use std::rc::Rc;
use objc2::{declare_class, msg_send, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use objc2_foundation::{MainThreadMarker, NSObject};
use super::app_state::AppState;
use crate::event::{DeviceEvent, ElementState};
declare_class!(
pub(super) struct WinitApplication;
unsafe impl ClassType for WinitApplication {
#[inherits(NSResponder, NSObject)]
type Super = NSApplication;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplication";
}
impl DeclaredClass for WinitApplication {}
unsafe impl WinitApplication {
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
#[method(sendEvent:)]
fn send_event(&self, event: &NSEvent) {
// For posterity, there are some undocumented event types
// (https://github.com/servo/cocoa-rs/issues/155)
// but that doesn't really matter here.
let event_type = unsafe { event.r#type() };
let modifier_flags = unsafe { event.modifierFlags() };
if event_type == NSEventType::KeyUp
&& modifier_flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand)
{
if let Some(key_window) = self.keyWindow() {
key_window.sendEvent(event);
}
} else {
let app_state = AppState::get(MainThreadMarker::from(self));
maybe_dispatch_device_event(&app_state, event);
unsafe { msg_send![super(self), sendEvent: event] }
}
}
}
);
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
let event_type = unsafe { event.r#type() };
#[allow(non_upper_case_globals)]
match event_type {
NSEventType::MouseMoved
| NSEventType::LeftMouseDragged
| NSEventType::OtherMouseDragged
| NSEventType::RightMouseDragged => {
let delta_x = unsafe { event.deltaX() } as f64;
let delta_y = unsafe { event.deltaY() } as f64;
if delta_x != 0.0 || delta_y != 0.0 {
app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::PointerMotion {
delta: (delta_x, delta_y),
});
});
}
},
NSEventType::LeftMouseDown | NSEventType::RightMouseDown | NSEventType::OtherMouseDown => {
let button = unsafe { event.buttonNumber() } as u32;
app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::Button {
button,
state: ElementState::Pressed,
});
});
},
NSEventType::LeftMouseUp | NSEventType::RightMouseUp | NSEventType::OtherMouseUp => {
let button = unsafe { event.buttonNumber() } as u32;
app_state.maybe_queue_with_handler(move |app, event_loop| {
app.device_event(event_loop, None, DeviceEvent::Button {
button,
state: ElementState::Released,
});
});
},
_ => (),
}
}

View File

@@ -1,23 +1,21 @@
use std::cell::{Cell, OnceCell, RefCell};
use std::mem;
use std::rc::Rc;
use std::rc::{Rc, Weak};
use std::sync::atomic::Ordering as AtomicOrdering;
use std::sync::Arc;
use std::time::Instant;
use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::NSNotification;
use winit_common::core_foundation::EventLoopProxy;
use winit_common::event_handler::EventHandler;
use winit_core::application::ApplicationHandler;
use winit_core::event::{StartCause, WindowEvent};
use winit_core::event_loop::ControlFlow;
use winit_core::window::WindowId;
use objc2_foundation::{MainThreadMarker, NSNotification};
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop};
use super::super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo};
use super::menu;
use super::observer::{EventLoopWaker, RunLoop};
use crate::application::ApplicationHandler;
use crate::event::{StartCause, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
#[derive(Debug)]
pub(super) struct AppState {
@@ -47,10 +45,22 @@ pub(super) struct AppState {
// as such should be careful to not add fields that, in turn, strongly reference those.
}
// SAFETY: Creating `MainThreadBound` in a `const` context, where there is no concept of the
// TODO(madsmtm): Use `MainThreadBound` once that is possible in `static`s.
struct StaticMainThreadBound<T>(T);
impl<T> StaticMainThreadBound<T> {
const fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
}
unsafe impl<T> Send for StaticMainThreadBound<T> {}
unsafe impl<T> Sync for StaticMainThreadBound<T> {}
// SAFETY: Creating `StaticMainThreadBound` in a `const` context, where there is no concept of the
// main thread.
static GLOBAL: MainThreadBound<OnceCell<Rc<AppState>>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
static GLOBAL: StaticMainThreadBound<OnceCell<Rc<AppState>>> =
StaticMainThreadBound(OnceCell::new());
impl AppState {
pub(super) fn setup_global(
@@ -58,18 +68,14 @@ impl AppState {
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Option<Rc<Self>> {
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}));
let this = Rc::new(Self {
) -> Rc<Self> {
let this = Rc::new(AppState {
mtm,
activation_policy,
event_loop_proxy: Arc::new(EventLoopProxy::new()),
default_menu,
activate_ignoring_other_apps,
run_loop: RunLoop::main(mtm),
event_loop_proxy,
event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false),
stop_before_wait: Cell::new(false),
@@ -85,7 +91,8 @@ impl AppState {
pending_redraw: RefCell::new(vec![]),
});
GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this))
GLOBAL.get(mtm).set(this.clone()).expect("application state can only be set once");
this
}
pub fn get(mtm: MainThreadMarker) -> Rc<Self> {
@@ -155,9 +162,7 @@ impl AppState {
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationWillTerminateNotification");
let app = NSApplication::sharedApplication(self.mtm);
notify_windows_of_exit(&app);
self.event_handler.terminate();
// TODO: Notify every window that it will be destroyed, like done in iOS?
self.internal_exit();
}
@@ -165,10 +170,10 @@ impl AppState {
/// of the given closure.
pub fn set_event_handler<R>(
&self,
handler: impl ApplicationHandler,
handler: &mut dyn ApplicationHandler,
closure: impl FnOnce() -> R,
) -> R {
self.event_handler.set(Box::new(handler), closure)
self.event_handler.set(handler, closure)
}
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
@@ -203,6 +208,10 @@ impl AppState {
/// 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: &Rc<Self>) {
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
self.set_is_running(false);
self.set_stop_on_redraw(false);
self.set_stop_before_wait(false);
@@ -308,9 +317,13 @@ impl AppState {
}
// Called by RunLoopObserver after finishing waiting for new events
pub fn wakeup(self: &Rc<Self>) {
pub fn wakeup(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
if !self.event_handler.ready() || !self.is_running() {
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
return;
}
@@ -336,14 +349,22 @@ impl AppState {
}
// Called by RunLoopObserver before waiting for new events
pub fn cleared(self: &Rc<Self>) {
pub fn cleared(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
// we're about to return to the `CFRunLoop` to poll for new events?
if !self.event_handler.ready() || !self.is_running() {
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
return;
}
if self.event_loop_proxy.wake_up.swap(false, AtomicOrdering::Relaxed) {
self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw {
self.with_handler(|app, event_loop| {
@@ -357,7 +378,6 @@ impl AppState {
if self.exiting() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
notify_windows_of_exit(&app);
}
if self.stop_before_wait.get() {

View File

@@ -4,44 +4,34 @@ use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::{available, msg_send, sel, AllocAnyThread, ClassType};
use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString,
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
NSString,
};
use winit_core::cursor::{CursorIcon, CursorImage, CustomCursorProvider, CustomCursorSource};
use winit_core::error::{NotSupportedError, RequestError};
use crate::cursor::{CursorImage, OnlyCursorImageSource};
use crate::error::RequestError;
use crate::window::CursorIcon;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CustomCursor(pub(crate) Retained<NSCursor>);
impl CustomCursorProvider for CustomCursor {
fn is_animated(&self) -> bool {
false
}
}
// SAFETY: NSCursor is immutable and thread-safe
// TODO(madsmtm): Put this logic in objc2-app-kit itself
unsafe impl Send for CustomCursor {}
unsafe impl Sync for CustomCursor {}
impl CustomCursor {
pub(crate) fn new(cursor: CustomCursorSource) -> Result<CustomCursor, RequestError> {
let cursor = match cursor {
CustomCursorSource::Image(cursor_image) => cursor_image,
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
return Err(NotSupportedError::new("unsupported cursor kind").into())
},
};
cursor_from_image(&cursor).map(Self)
pub(crate) fn new(cursor: OnlyCursorImageSource) -> Result<CustomCursor, RequestError> {
cursor_from_image(&cursor.0).map(Self)
}
}
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCursor>, RequestError> {
let width = cursor.width();
let height = cursor.height();
let width = cursor.width;
let height = cursor.height;
let bitmap = unsafe {
NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(
@@ -58,16 +48,15 @@ pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCurso
32,
)
}.ok_or_else(|| os_error!("parent view should be installed in a window"))?;
let bitmap_data =
unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.buffer().len()) };
bitmap_data.copy_from_slice(cursor.buffer());
let bitmap_data = unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.rgba.len()) };
bitmap_data.copy_from_slice(&cursor.rgba);
let image = unsafe {
NSImage::initWithSize(NSImage::alloc(), NSSize::new(width.into(), height.into()))
};
unsafe { image.addRepresentation(&bitmap) };
let hotspot = NSPoint::new(cursor.hotspot_x() as f64, cursor.hotspot_y() as f64);
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64);
Ok(NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot))
}
@@ -78,8 +67,8 @@ pub(crate) fn default_cursor() -> Retained<NSCursor> {
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
let cls = NSCursor::class();
if unsafe { msg_send![cls, respondsToSelector: sel] } {
let cursor: Retained<NSCursor> = unsafe { msg_send![cls, performSelector: sel] };
if cls.responds_to(sel) {
let cursor: Retained<NSCursor> = unsafe { msg_send_id![cls, performSelector: sel] };
Some(cursor)
} else {
tracing::warn!("cursor `{sel}` appears to be invalid");
@@ -141,21 +130,25 @@ unsafe fn load_webkit_cursor(name: &NSString) -> Retained<NSCursor> {
// TODO: Handle PLists better
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe {
msg_send![
msg_send_id![
<NSDictionary<NSObject, NSObject>>::class(),
dictionaryWithContentsOfFile: &*info_path,
]
};
let mut x = 0.0;
if let Some(n) = info.objectForKey(ns_string!("hotx")) {
if let Ok(n) = n.downcast::<NSNumber>() {
x = n.as_cgfloat();
if let Some(n) = info.get(&*ns_string!("hotx")) {
if n.is_kind_of::<NSNumber>() {
let ptr: *const NSObject = n;
let ptr: *const NSNumber = ptr.cast();
x = unsafe { &*ptr }.as_cgfloat()
}
}
let mut y = 0.0;
if let Some(n) = info.objectForKey(ns_string!("hoty")) {
if let Ok(n) = n.downcast::<NSNumber>() {
y = n.as_cgfloat();
if let Some(n) = info.get(&*ns_string!("hotx")) {
if n.is_kind_of::<NSNumber>() {
let ptr: *const NSObject = n;
let ptr: *const NSNumber = ptr.cast();
y = unsafe { &*ptr }.as_cgfloat()
}
}
@@ -195,7 +188,6 @@ pub(crate) fn invisible_cursor() -> Retained<NSCursor> {
CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone()
}
#[allow(deprecated)]
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
match icon {
CursorIcon::Default => default_cursor(),
@@ -216,9 +208,7 @@ pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
CursorIcon::Help => _helpCursor(),
CursorIcon::ZoomIn if available!(macos = 15.0) => unsafe { NSCursor::zoomInCursor() },
CursorIcon::ZoomIn => _zoomInCursor(),
CursorIcon::ZoomOut if available!(macos = 15.0) => unsafe { NSCursor::zoomOutCursor() },
CursorIcon::ZoomOut => _zoomOutCursor(),
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
CursorIcon::NwResize => _windowResizeNorthWestCursor(),

View File

@@ -1,42 +1,50 @@
use std::ptr::NonNull;
use std::ffi::c_void;
use dispatch2::run_on_main;
use core_foundation::base::CFRelease;
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
use objc2::rc::Retained;
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_core_foundation::{CFData, CFRetained};
use objc2_foundation::NSPoint;
use objc2_foundation::{run_on_main, NSPoint};
use smol_str::SmolStr;
use winit_core::event::{ElementState, KeyEvent, Modifiers};
use winit_core::keyboard::{
use super::ffi;
use crate::event::{ElementState, KeyEvent, Modifiers};
use crate::keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode,
PhysicalKey,
};
use super::ffi;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,
pub key_without_modifiers: Key,
}
/// Ignores ALL modifiers.
pub fn get_modifierless_char(scancode: u16) -> Key {
let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else {
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode));
};
let input_source = unsafe { CFRetained::from_raw(ptr) };
let layout_data = unsafe {
ffi::TISGetInputSourceProperty(&input_source, ffi::kTISPropertyUnicodeKeyLayoutData)
};
let Some(layout_data) = (unsafe { layout_data.cast::<CFData>().as_ref() }) else {
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode));
};
let layout = layout_data.byte_ptr().cast();
let mut string = [0; 16];
let input_source;
let layout;
unsafe {
input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource();
if input_source.is_null() {
tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode));
}
let layout_data =
ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData);
if layout_data.is_null() {
CFRelease(input_source as *mut c_void);
tracing::error!("`TISGetInputSourceProperty` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode));
}
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
}
let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
let mut result_len = 0;
let mut dead_keys = 0;
let modifiers = 0;
let mut string = [0; 16];
let translate_result = unsafe {
ffi::UCKeyTranslate(
layout,
@@ -51,6 +59,9 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
string.as_mut_ptr(),
)
};
unsafe {
CFRelease(input_source as *mut c_void);
}
if translate_result != 0 {
tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result);
return Key::Unidentified(NativeKey::MacOS(scancode));
@@ -112,8 +123,8 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
let key_without_modifiers = get_modifierless_char(scancode);
let modifiers = unsafe { ns_event.modifierFlags() };
let has_ctrl = modifiers.contains(NSEventModifierFlags::Control);
let has_cmd = modifiers.contains(NSEventModifierFlags::Command);
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
@@ -151,8 +162,7 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
repeat: is_repeat,
state,
text,
text_with_all_modifiers,
key_without_modifiers,
platform_specific: KeyEventExtra { text_with_all_modifiers, key_without_modifiers },
}
}
@@ -162,34 +172,28 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()),
};
// Roughly same handling as Firefox and Chromium:
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMKeyName.h
// https://chromium.googlesource.com/chromium/src.git/+/010a75a426c4a2292955a52f480e9251cacf750e/ui/events/keycodes/keyboard_code_conversion_mac.mm#100
Key::Named(match code {
KeyCode::Enter => NamedKey::Enter,
KeyCode::Tab => NamedKey::Tab,
KeyCode::Space => return Key::Character(" ".into()),
KeyCode::Space => NamedKey::Space,
KeyCode::Backspace => NamedKey::Backspace,
KeyCode::Escape => NamedKey::Escape,
KeyCode::MetaRight => NamedKey::Meta,
KeyCode::MetaLeft => NamedKey::Meta,
KeyCode::SuperRight => NamedKey::Super,
KeyCode::SuperLeft => NamedKey::Super,
KeyCode::ShiftLeft => NamedKey::Shift,
KeyCode::AltLeft => NamedKey::Alt,
KeyCode::ControlLeft => NamedKey::Control,
KeyCode::ShiftRight => NamedKey::Shift,
KeyCode::AltRight => NamedKey::Alt,
KeyCode::ControlRight => NamedKey::Control,
KeyCode::CapsLock => NamedKey::CapsLock,
KeyCode::NumLock => NamedKey::NumLock,
KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp,
KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown,
KeyCode::AudioVolumeMute => NamedKey::AudioVolumeMute,
// Other numpad keys all generate text on macOS (if I understand correctly)
KeyCode::NumpadEnter => NamedKey::Enter,
KeyCode::Fn => NamedKey::Fn,
KeyCode::F1 => NamedKey::F1,
KeyCode::F2 => NamedKey::F2,
KeyCode::F3 => NamedKey::F3,
@@ -210,27 +214,17 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
KeyCode::F18 => NamedKey::F18,
KeyCode::F19 => NamedKey::F19,
KeyCode::F20 => NamedKey::F20,
KeyCode::F21 => NamedKey::F21,
KeyCode::F22 => NamedKey::F22,
KeyCode::F23 => NamedKey::F23,
KeyCode::F24 => NamedKey::F24,
KeyCode::Insert => NamedKey::Insert,
KeyCode::Home => NamedKey::Home,
KeyCode::PageUp => NamedKey::PageUp,
KeyCode::Delete => NamedKey::Delete,
KeyCode::End => NamedKey::End,
KeyCode::Help => NamedKey::Help,
KeyCode::PageDown => NamedKey::PageDown,
KeyCode::ArrowLeft => NamedKey::ArrowLeft,
KeyCode::ArrowRight => NamedKey::ArrowRight,
KeyCode::ArrowDown => NamedKey::ArrowDown,
KeyCode::ArrowUp => NamedKey::ArrowUp,
KeyCode::ContextMenu => NamedKey::ContextMenu,
KeyCode::Lang2 => NamedKey::Eisu,
KeyCode::Lang1 => NamedKey::KanjiMode,
_ => return Key::Unidentified(NativeKey::MacOS(scancode)),
})
}
@@ -242,8 +236,8 @@ pub fn code_to_location(key: PhysicalKey) -> KeyLocation {
};
match code {
KeyCode::MetaRight => KeyLocation::Right,
KeyCode::MetaLeft => KeyLocation::Left,
KeyCode::SuperRight => KeyLocation::Right,
KeyCode::SuperLeft => KeyLocation::Left,
KeyCode::ShiftLeft => KeyLocation::Left,
KeyCode::AltLeft => KeyLocation::Left,
KeyCode::ControlLeft => KeyLocation::Left,
@@ -314,23 +308,30 @@ 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(NSEventModifierFlags::Shift));
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(NSEventModifierFlags::Control));
state.set(
ModifiersState::CONTROL,
flags.contains(NSEventModifierFlags::NSEventModifierFlagControl),
);
pressed_mods.set(ModifiersKeys::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK));
pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK));
state.set(ModifiersState::ALT, flags.contains(NSEventModifierFlags::Option));
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::META, flags.contains(NSEventModifierFlags::Command));
pressed_mods.set(ModifiersKeys::LMETA, flags.contains(NX_DEVICELCMDKEYMASK));
pressed_mods.set(ModifiersKeys::RMETA, flags.contains(NX_DEVICERCMDKEYMASK));
state.set(
ModifiersState::SUPER,
flags.contains(NSEventModifierFlags::NSEventModifierFlagCommand),
);
pressed_mods.set(ModifiersKeys::LSUPER, flags.contains(NX_DEVICELCMDKEYMASK));
pressed_mods.set(ModifiersKeys::RSUPER, flags.contains(NX_DEVICERCMDKEYMASK));
Modifiers::new(state, pressed_mods)
Modifiers { state, pressed_mods }
}
pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
@@ -349,7 +350,7 @@ pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
}
}
pub fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32> {
pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32> {
let code = match physical_key {
PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(_) => return None,
@@ -409,8 +410,8 @@ pub fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32> {
KeyCode::Backquote => Some(0x32),
KeyCode::Backspace => Some(0x33),
KeyCode::Escape => Some(0x35),
KeyCode::MetaRight => Some(0x36),
KeyCode::MetaLeft => Some(0x37),
KeyCode::SuperRight => Some(0x36),
KeyCode::SuperLeft => Some(0x37),
KeyCode::ShiftLeft => Some(0x38),
KeyCode::CapsLock => Some(0x39),
KeyCode::AltLeft => Some(0x3a),
@@ -481,7 +482,7 @@ pub fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32> {
}
}
pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// Follows what Chromium and Firefox do:
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
@@ -555,8 +556,8 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x33 => KeyCode::Backspace,
// 0x34 => unknown, // kVK_Powerbook_KeypadEnter
0x35 => KeyCode::Escape,
0x36 => KeyCode::MetaRight,
0x37 => KeyCode::MetaLeft,
0x36 => KeyCode::SuperRight,
0x37 => KeyCode::SuperLeft,
0x38 => KeyCode::ShiftLeft,
0x39 => KeyCode::CapsLock,
0x3a => KeyCode::AltLeft,

View File

@@ -1,36 +1,76 @@
use std::rc::Rc;
use std::any::Any;
use std::cell::Cell;
use std::os::raw::c_void;
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use core_foundation::base::{CFIndex, CFRelease};
use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::ProtocolObject;
use objc2::{available, MainThreadMarker};
use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSWindow,
};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol};
use rwh_06::HasDisplayHandle;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, RequestError};
use winit_core::event_loop::pump_events::PumpStatus;
use winit_core::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::Theme;
use super::app::override_send_event;
use super::super::notification_center::create_observer;
use super::app::WinitApplication;
use super::app_state::AppState;
use super::cursor::CustomCursor;
use super::event::dummy_event;
use super::monitor;
use super::notification_center::create_observer;
use super::observer::setup_control_flow_observers;
use crate::window::Window;
use crate::ActivationPolicy;
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as RootMonitorHandle;
use crate::platform::macos::ActivationPolicy;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Window;
use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme};
#[derive(Default)]
pub struct PanicInfo {
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
}
// WARNING:
// As long as this struct is used through its `impl`, it is UnwindSafe.
// (If `get_mut` is called on `inner`, unwind safety may get broken.)
impl UnwindSafe for PanicInfo {}
impl RefUnwindSafe for PanicInfo {}
impl PanicInfo {
pub fn is_panicking(&self) -> bool {
let inner = self.inner.take();
let result = inner.is_some();
self.inner.set(inner);
result
}
/// Overwrites the current state if the current state is not panicking
pub fn set_panic(&self, p: Box<dyn Any + Send + 'static>) {
if !self.is_panicking() {
self.inner.set(Some(p));
}
}
pub fn take(&self) -> Option<Box<dyn Any + Send + 'static>> {
self.inner.take()
}
}
#[derive(Debug)]
pub struct ActiveEventLoop {
@@ -63,29 +103,25 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_window(
&self,
window_attributes: winit_core::window::WindowAttributes,
) -> Result<Box<dyn winit_core::window::Window>, RequestError> {
window_attributes: crate::window::WindowAttributes,
) -> Result<Box<dyn crate::window::Window>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
source: CustomCursorSource,
) -> Result<CoreCustomCursor, RequestError> {
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(source)?)))
) -> Result<RootCustomCursor, RequestError> {
Ok(RootCustomCursor { inner: CustomCursor::new(source.inner)? })
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Box::new(
monitor::available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new(monitor::available_monitors().into_iter().map(|inner| RootMonitorHandle { inner }))
}
fn primary_monitor(&self) -> Option<winit_core::monitor::MonitorHandle> {
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
let monitor = monitor::primary_monitor();
Some(CoreMonitorHandle(Arc::new(monitor)))
Some(RootMonitorHandle { inner: monitor })
}
fn listen_device_events(&self, _allowed: DeviceEvents) {}
@@ -93,8 +129,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm);
// Dark appearance was introduced in macOS 10.14
if available!(macos = 10.14) {
if app.respondsToSelector(sel!(effectiveAppearance)) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
@@ -133,7 +168,6 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
}
}
#[derive(Debug)]
pub struct EventLoop {
/// Store a reference to the application for convenience.
///
@@ -143,20 +177,21 @@ pub struct EventLoop {
app_state: Rc<AppState>,
window_target: ActiveEventLoop,
panic_info: Rc<PanicInfo>,
// Since macOS 10.11, 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<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_finish_launching_observer: Retained<NSObject>,
_will_terminate_observer: Retained<NSObject>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct PlatformSpecificEventLoopAttributes {
pub activation_policy: Option<ActivationPolicy>,
pub default_menu: bool,
pub activate_ignoring_other_apps: bool,
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) activation_policy: Option<ActivationPolicy>,
pub(crate) default_menu: bool,
pub(crate) activate_ignoring_other_apps: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
@@ -166,10 +201,22 @@ impl Default for PlatformSpecificEventLoopAttributes {
}
impl EventLoop {
pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!");
let app: Retained<NSApplication> =
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
if !app.is_kind_of::<WinitApplication>() {
panic!(
"`winit` requires control over the principal class. You must create the event \
loop before other parts of your application initialize NSApplication"
);
}
let activation_policy = match attributes.activation_policy {
None => None,
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
@@ -182,14 +229,7 @@ impl EventLoop {
activation_policy,
attributes.default_menu,
attributes.activate_ignoring_other_apps,
)
.ok_or_else(|| EventLoopError::RecreationAttempt)?;
// Initialize the application (if it has not already been).
let app = NSApplication::sharedApplication(mtm);
// Override `sendEvent:` on the application to forward to our application state.
override_send_event(&app);
);
let center = unsafe { NSNotificationCenter::defaultCenter() };
@@ -217,12 +257,14 @@ impl EventLoop {
},
);
setup_control_flow_observers(mtm);
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
Ok(EventLoop {
app,
app_state: app_state.clone(),
window_target: ActiveEventLoop { app_state, mtm },
panic_info,
_did_finish_launching_observer,
_will_terminate_observer,
})
@@ -242,10 +284,10 @@ impl EventLoop {
// redundant wake ups.
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: A,
mut app: A,
) -> Result<(), EventLoopError> {
self.app_state.clear_exit();
self.app_state.set_event_handler(app, || {
self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| {
// clear / normalize pump_events state
self.app_state.set_wait_timeout(None);
@@ -259,8 +301,17 @@ impl EventLoop {
self.app_state.dispatch_init_events();
}
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
self.app.run();
// SAFETY: We do not run the application re-entrantly
unsafe { self.app.run() };
// While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which
// will lead to us stopping the `NSApplication` and saving the
// `PanicInfo` so that we can resume the unwind at a controlled,
// safe point in time.
if let Some(panic) = self.panic_info.take() {
resume_unwind(panic);
}
self.app_state.internal_exit()
})
@@ -272,9 +323,9 @@ impl EventLoop {
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: A,
mut app: A,
) -> PumpStatus {
self.app_state.set_event_handler(app, || {
self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| {
// As a special case, if the application hasn't been launched yet then we at least
// run the loop until it has fully launched.
@@ -282,7 +333,8 @@ impl EventLoop {
debug_assert!(!self.app_state.is_running());
self.app_state.set_stop_on_launch();
self.app.run();
// SAFETY: We do not run the application re-entrantly
unsafe { self.app.run() };
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
// has launched
@@ -314,7 +366,17 @@ impl EventLoop {
},
}
self.app_state.set_stop_on_redraw(true);
self.app.run();
// SAFETY: We do not run the application re-entrantly
unsafe { self.app.run() };
}
// While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which
// will lead to us stopping the application and saving the
// `PanicInfo` so that we can resume the unwind at a controlled,
// safe point in time.
if let Some(panic) = self.panic_info.take() {
resume_unwind(panic);
}
if self.app_state.exiting() {
@@ -346,18 +408,87 @@ 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]
pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
mtm: MainThreadMarker,
panic_info: Weak<PanicInfo>,
f: F,
) -> Option<R> {
match catch_unwind(f) {
Ok(r) => Some(r),
Err(e) => {
// It's important that we set the panic before requesting a `stop`
// because some callback are still called during the `stop` message
// and we need to know in those callbacks if the application is currently
// panicking
{
let panic_info = panic_info.upgrade().unwrap();
panic_info.set_panic(e);
}
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
None
},
}
}
#[derive(Debug)]
pub struct EventLoopProxy {
pub(crate) wake_up: AtomicBool,
source: CFRunLoopSourceRef,
}
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl Drop for EventLoopProxy {
fn drop(&mut self) {
unsafe {
CFRelease(self.source as _);
}
}
}
impl EventLoopProxy {
pub(crate) fn new() -> Self {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::MAX - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
EventLoopProxy { wake_up: AtomicBool::new(false), source }
}
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
self.wake_up.store(true, AtomicOrdering::Relaxed);
unsafe {
// Let the main thread know there's a new event.
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}
}

View File

@@ -0,0 +1,254 @@
// TODO: Upstream these
#![allow(dead_code, non_snake_case, non_upper_case_globals)]
use std::ffi::c_void;
use core_foundation::array::CFArrayRef;
use core_foundation::dictionary::CFDictionaryRef;
use core_foundation::string::CFStringRef;
use core_foundation::uuid::CFUUIDRef;
use core_graphics::base::CGError;
use core_graphics::display::{CGDirectDisplayID, CGDisplayConfigRef};
use objc2::ffi::NSInteger;
use objc2::runtime::AnyObject;
pub type CGDisplayFadeInterval = f32;
pub type CGDisplayReservationInterval = f32;
pub type CGDisplayBlendFraction = f32;
pub const kCGDisplayBlendNormal: f32 = 0.0;
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
pub type CGDisplayFadeReservationToken = u32;
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
pub type Boolean = u8;
pub const FALSE: Boolean = 0;
pub const TRUE: Boolean = 1;
pub const kCGErrorSuccess: i32 = 0;
pub const kCGErrorFailure: i32 = 1000;
pub const kCGErrorIllegalArgument: i32 = 1001;
pub const kCGErrorInvalidConnection: i32 = 1002;
pub const kCGErrorInvalidContext: i32 = 1003;
pub const kCGErrorCannotComplete: i32 = 1004;
pub const kCGErrorNotImplemented: i32 = 1006;
pub const kCGErrorRangeCheck: i32 = 1007;
pub const kCGErrorTypeCheck: i32 = 1008;
pub const kCGErrorInvalidOperation: i32 = 1010;
pub const kCGErrorNoneAvailable: i32 = 1011;
pub const IO1BitIndexedPixels: &str = "P";
pub const IO2BitIndexedPixels: &str = "PP";
pub const IO4BitIndexedPixels: &str = "PPPP";
pub const IO8BitIndexedPixels: &str = "PPPPPPPP";
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16";
pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
pub const IOYUV422Pixels: &str = "Y4U2V2";
pub const IO8BitOverlayPixels: &str = "O8";
pub type CGWindowLevel = i32;
pub type CGDisplayModeRef = *mut c_void;
// `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync` framework.
// However, that framework was only introduced "publicly" in macOS 10.13.
//
// Since we want to support older versions, we can't link to `ColorSync`
// directly. Fortunately, it has always been available as a subframework of
// `ApplicationServices`, see:
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemFrameworks/SystemFrameworks.html#//apple_ref/doc/uid/TP40001067-CH210-BBCFFIEG
#[link(name = "ApplicationServices", kind = "framework")]
extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
}
#[link(name = "CoreGraphics", kind = "framework")]
extern "C" {
pub fn CGRestorePermanentDisplayConfiguration();
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
pub fn CGConfigureDisplayFadeEffect(
config: CGDisplayConfigRef,
fadeOutSeconds: CGDisplayFadeInterval,
fadeInSeconds: CGDisplayFadeInterval,
fadeRed: f32,
fadeGreen: f32,
fadeBlue: f32,
) -> CGError;
pub fn CGAcquireDisplayFadeReservation(
seconds: CGDisplayReservationInterval,
token: *mut CGDisplayFadeReservationToken,
) -> CGError;
pub fn CGDisplayFade(
token: CGDisplayFadeReservationToken,
duration: CGDisplayFadeInterval,
startBlend: CGDisplayBlendFraction,
endBlend: CGDisplayBlendFraction,
redBlend: f32,
greenBlend: f32,
blueBlend: f32,
synchronous: Boolean,
) -> CGError;
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
pub fn CGDisplaySetDisplayMode(
display: CGDirectDisplayID,
mode: CGDisplayModeRef,
options: CFDictionaryRef,
) -> CGError;
pub fn CGDisplayCopyAllDisplayModes(
display: CGDirectDisplayID,
options: CFDictionaryRef,
) -> CFArrayRef;
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
// Wildly used private APIs; Apple uses them for their Terminal.app.
pub fn CGSMainConnectionID() -> *mut AnyObject;
pub fn CGSSetWindowBackgroundBlurRadius(
connection_id: *mut AnyObject,
window_id: NSInteger,
radius: i64,
) -> i32;
}
mod core_video {
use super::*;
#[link(name = "CoreVideo", kind = "framework")]
extern "C" {}
// CVBase.h
pub type CVTimeFlags = i32; // int32_t
pub const kCVTimeIsIndefinite: CVTimeFlags = 1 << 0;
#[repr(C)]
#[derive(Debug, Clone)]
pub struct CVTime {
pub time_value: i64, // int64_t
pub time_scale: i32, // int32_t
pub flags: i32, // int32_t
}
// CVReturn.h
pub type CVReturn = i32; // int32_t
pub const kCVReturnSuccess: CVReturn = 0;
// CVDisplayLink.h
pub type CVDisplayLinkRef = *mut c_void;
extern "C" {
pub fn CVDisplayLinkCreateWithCGDisplay(
displayID: CGDirectDisplayID,
displayLinkOut: *mut CVDisplayLinkRef,
) -> CVReturn;
pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod(
displayLink: CVDisplayLinkRef,
) -> CVTime;
pub fn CVDisplayLinkRelease(displayLink: CVDisplayLinkRef);
}
}
pub use core_video::*;
#[repr(transparent)]
pub struct TISInputSource(std::ffi::c_void);
pub type TISInputSourceRef = *mut TISInputSource;
#[repr(transparent)]
pub struct UCKeyboardLayout(std::ffi::c_void);
pub type OptionBits = u32;
pub type UniCharCount = std::os::raw::c_ulong;
pub type UniChar = std::os::raw::c_ushort;
pub type OSStatus = i32;
#[allow(non_upper_case_globals)]
pub const kUCKeyActionDisplay: u16 = 3;
#[allow(non_upper_case_globals)]
pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1;
#[link(name = "Carbon", kind = "framework")]
extern "C" {
pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef;
#[allow(non_snake_case)]
pub fn TISGetInputSourceProperty(
inputSource: TISInputSourceRef,
propertyKey: CFStringRef,
) -> *mut c_void;
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
pub fn LMGetKbdType() -> u8;
#[allow(non_snake_case)]
pub fn UCKeyTranslate(
keyLayoutPtr: *const UCKeyboardLayout,
virtualKeyCode: u16,
keyAction: u16,
modifierKeyState: u32,
keyboardType: u32,
keyTranslateOptions: OptionBits,
deadKeyState: *mut u32,
maxStringLength: UniCharCount,
actualStringLength: *mut UniCharCount,
unicodeString: *mut UniChar,
) -> OSStatus;
}
// CGWindowLevel.h
//
// Note: There are two different things at play in this header:
// `CGWindowLevel` and `CGWindowLevelKey`.
//
// It seems like there was a push towards using "key" values instead of the
// raw window level values, and then you were supposed to use
// `CGWindowLevelForKey` to get the actual level.
//
// But the values that `NSWindowLevel` has are compiled in, and as such has
// to remain ABI compatible, so they're safe for us to hardcode as well.
#[allow(dead_code, non_upper_case_globals)]
mod window_level {
const kCGNumReservedWindowLevels: i32 = 16;
const kCGNumReservedBaseWindowLevels: i32 = 5;
pub const kCGBaseWindowLevel: i32 = i32::MIN;
pub const kCGMinimumWindowLevel: i32 = kCGBaseWindowLevel + kCGNumReservedBaseWindowLevels;
pub const kCGMaximumWindowLevel: i32 = i32::MAX - kCGNumReservedWindowLevels;
pub const kCGDesktopWindowLevel: i32 = kCGMinimumWindowLevel + 20;
pub const kCGDesktopIconWindowLevel: i32 = kCGDesktopWindowLevel + 20;
pub const kCGBackstopMenuLevel: i32 = -20;
pub const kCGNormalWindowLevel: i32 = 0;
pub const kCGFloatingWindowLevel: i32 = 3;
pub const kCGTornOffMenuWindowLevel: i32 = 3;
pub const kCGModalPanelWindowLevel: i32 = 8;
pub const kCGUtilityWindowLevel: i32 = 19;
pub const kCGDockWindowLevel: i32 = 20;
pub const kCGMainMenuWindowLevel: i32 = 24;
pub const kCGStatusWindowLevel: i32 = 25;
pub const kCGPopUpMenuWindowLevel: i32 = 101;
pub const kCGOverlayWindowLevel: i32 = 102;
pub const kCGHelpWindowLevel: i32 = 200;
pub const kCGDraggingWindowLevel: i32 = 500;
pub const kCGScreenSaverWindowLevel: i32 = 1000;
pub const kCGAssistiveTechHighWindowLevel: i32 = 1500;
pub const kCGCursorWindowLevel: i32 = kCGMaximumWindowLevel - 1;
}
pub use window_level::*;

View File

@@ -1,8 +1,8 @@
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::{sel, MainThreadMarker};
use objc2::sel;
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use objc2_foundation::{ns_string, NSBundle, NSProcessInfo, NSString};
use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
struct KeyEquivalent<'a> {
key: &'a NSString,
@@ -16,10 +16,7 @@ pub fn initialize(app: &NSApplication) {
menubar.addItem(&app_menu_item);
let app_menu = NSMenu::new(mtm);
let process_name = match NSBundle::mainBundle().name() {
Some(bundle_name) => bundle_name,
None => NSProcessInfo::processInfo().processName(),
};
let process_name = NSProcessInfo::processInfo().processName();
// About menu item
let about_item_title = ns_string!("About ").stringByAppendingString(&process_name);
@@ -51,7 +48,10 @@ pub fn initialize(app: &NSApplication) {
Some(sel!(hideOtherApplications:)),
Some(KeyEquivalent {
key: ns_string!("h"),
masks: Some(NSEventModifierFlags::Option | NSEventModifierFlags::Command),
masks: Some(
NSEventModifierFlags::NSEventModifierFlagOption
| NSEventModifierFlags::NSEventModifierFlagCommand,
),
}),
);

View File

@@ -0,0 +1,27 @@
#[macro_use]
mod util;
mod app;
mod app_state;
mod cursor;
mod event;
mod event_loop;
mod ffi;
mod menu;
mod monitor;
mod observer;
mod view;
mod window;
mod window_delegate;
pub(crate) use self::cursor::CustomCursor as PlatformCustomCursor;
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey, KeyEventExtra};
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::{MonitorHandle, VideoModeHandle};
pub(crate) use self::window::Window;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;
pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;

View File

@@ -0,0 +1,368 @@
#![allow(clippy::unnecessary_cast)]
use std::collections::VecDeque;
use std::fmt;
use std::num::{NonZeroU16, NonZeroU32};
use core_foundation::array::{CFArrayGetCount, CFArrayGetValueAtIndex};
use core_foundation::base::{CFRelease, TCFType};
use core_foundation::string::CFString;
use core_graphics::display::{
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode,
};
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 super::ffi;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
#[derive(Clone)]
pub struct VideoModeHandle {
size: PhysicalSize<u32>,
bit_depth: Option<NonZeroU16>,
refresh_rate_millihertz: Option<NonZeroU32>,
pub(crate) monitor: MonitorHandle,
pub(crate) native_mode: NativeDisplayMode,
}
impl PartialEq for VideoModeHandle {
fn eq(&self, other: &Self) -> bool {
self.size == other.size
&& self.bit_depth == other.bit_depth
&& self.refresh_rate_millihertz == other.refresh_rate_millihertz
&& self.monitor == other.monitor
}
}
impl Eq for VideoModeHandle {}
impl std::hash::Hash for VideoModeHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.size.hash(state);
self.bit_depth.hash(state);
self.refresh_rate_millihertz.hash(state);
self.monitor.hash(state);
}
}
impl std::fmt::Debug for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VideoModeHandle")
.field("size", &self.size)
.field("bit_depth", &self.bit_depth)
.field("refresh_rate_millihertz", &self.refresh_rate_millihertz)
.field("monitor", &self.monitor)
.finish()
}
}
pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
unsafe impl Send for NativeDisplayMode {}
unsafe impl Sync for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
ffi::CGDisplayModeRelease(self.0);
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
ffi::CGDisplayModeRetain(self.0);
}
NativeDisplayMode(self.0)
}
}
impl VideoModeHandle {
fn new(
monitor: MonitorHandle,
mode: NativeDisplayMode,
refresh_rate_millihertz: Option<NonZeroU32>,
) -> Self {
unsafe {
let pixel_encoding =
CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode.0))
.to_string();
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
32
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
16
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) {
30
} else {
unimplemented!()
};
VideoModeHandle {
size: PhysicalSize::new(
ffi::CGDisplayModeGetPixelWidth(mode.0) as u32,
ffi::CGDisplayModeGetPixelHeight(mode.0) as u32,
),
refresh_rate_millihertz,
bit_depth: NonZeroU16::new(bit_depth),
monitor: monitor.clone(),
native_mode: mode,
}
}
}
pub fn size(&self) -> PhysicalSize<u32> {
self.size
}
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.bit_depth
}
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
self.refresh_rate_millihertz
}
pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone()
}
}
#[derive(Clone)]
pub struct MonitorHandle(CGDirectDisplayID);
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
// unique identifier that persists even across system reboots
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
}
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
}
}
}
impl std::hash::Hash for MonitorHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
unsafe {
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
}
}
}
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));
}
monitors
} else {
VecDeque::with_capacity(0)
}
}
pub fn primary_monitor() -> MonitorHandle {
MonitorHandle(CGDisplay::main().id)
}
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MonitorHandle")
.field("name", &self.name())
.field("native_identifier", &self.native_identifier())
.field("position", &self.position())
.field("scale_factor", &self.scale_factor())
.finish_non_exhaustive()
}
}
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();
Some(format!("Monitor #{screen_num}"))
}
#[inline]
pub fn native_identifier(&self) -> u32 {
self.0
}
#[inline]
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
// This is already in screen coordinates. If we were using `NSScreen`,
// then a conversion would've been needed:
// flip_window_screen_coordinates(self.ns_screen(mtm)?.frame())
let bounds = unsafe { CGDisplayBounds(self.native_identifier()) };
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
Some(position.to_physical(self.scale_factor()))
}
pub fn scale_factor(&self) -> f64 {
run_on_main(|mtm| {
match self.ns_screen(mtm) {
Some(screen) => screen.backingScaleFactor() as f64,
None => 1.0, // default to 1.0 when we can't find the screen
}
})
}
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
let current_display_mode =
NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
refresh_rate_millihertz(self.0, &current_display_mode)
}
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
let mode = NativeDisplayMode(unsafe { CGDisplayCopyDisplayMode(self.0) } as _);
let refresh_rate_millihertz = refresh_rate_millihertz(self.0, &mode);
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz))
}
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
let refresh_rate_millihertz = self.refresh_rate_millihertz();
let monitor = self.clone();
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
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 {
NonZeroU32::new((cg_refresh_rate_hertz * 1000) as u32)
} else {
refresh_rate_millihertz
};
VideoModeHandle::new(
monitor.clone(),
NativeDisplayMode(mode),
refresh_rate_millihertz,
)
})
}
}
pub(crate) fn ns_screen(&self, mtm: MainThreadMarker) -> Option<Retained<NSScreen>> {
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
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
})
}
}
pub(crate) fn get_display_id(screen: &NSScreen) -> u32 {
let key = ns_string!("NSScreenNumber");
objc2::rc::autoreleasepool(|_| {
let device_description = screen.deviceDescription();
// Retrieve the CGDirectDisplayID associated with this screen
//
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
// to be an NSNumber. See documentation for `deviceDescription` for details:
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
let obj = device_description
.get(key)
.expect("failed getting screen display id from device description");
let obj: *const AnyObject = obj;
let obj: *const NSNumber = obj.cast();
let obj: &NSNumber = unsafe { &*obj };
obj.as_u32()
})
}
/// Core graphics screen coordinates are relative to the top-left corner of
/// the so-called "main" display, with y increasing downwards - which is
/// exactly what we want in Winit.
///
/// However, `NSWindow` and `NSScreen` changes these coordinates to:
/// 1. Be relative to the bottom-left corner of the "main" screen.
/// 2. Be relative to the bottom-left corner of the window/screen itself.
/// 3. Have y increasing upwards.
///
/// This conversion happens to be symmetric, so we only need this one function
/// to convert between the two coordinate systems.
pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint {
// It is intentional that we use `CGMainDisplayID` (as opposed to
// `NSScreen::mainScreen`), because that's what the screen coordinates
// are relative to, no matter which display the window is currently on.
let main_screen_height = CGDisplay::main().bounds().size.height;
let y = main_screen_height - frame.size.height - frame.origin.y;
NSPoint::new(frame.origin.x, y)
}
fn refresh_rate_millihertz(id: CGDirectDisplayID, mode: &NativeDisplayMode) -> Option<NonZeroU32> {
unsafe {
let refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode.0);
if refresh_rate > 0.0 {
return NonZeroU32::new((refresh_rate * 1000.0).round() as u32);
}
let mut display_link = std::ptr::null_mut();
if ffi::CVDisplayLinkCreateWithCGDisplay(id, &mut display_link) != ffi::kCVReturnSuccess {
return None;
}
let time = ffi::CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
ffi::CVDisplayLinkRelease(display_link);
// This value is indefinite if an invalid display link was specified
if time.flags & ffi::kCVTimeIsIndefinite != 0 {
return None;
}
(time.time_scale as i64)
.checked_div(time.time_value)
.map(|v| (v * 1000) as u32)
.and_then(NonZeroU32::new)
}
}

View File

@@ -4,76 +4,132 @@
//! <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 objc2::MainThreadMarker;
use objc2_core_foundation::{
kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained,
CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, CFRunLoopObserverCallBack,
CFRunLoopObserverContext, CFRunLoopTimer,
use block2::Block;
use core_foundation::base::{CFIndex, CFOptionFlags, CFRelease, CFTypeRef};
use core_foundation::date::CFAbsoluteTimeGetCurrent;
use core_foundation::runloop::{
kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode,
kCFRunLoopExit, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddTimer, CFRunLoopGetMain,
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopObserverCreate,
CFRunLoopObserverRef, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate,
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopWakeUp,
};
use objc2_foundation::MainThreadMarker;
use tracing::error;
use super::app_state::AppState;
use super::event_loop::{stop_app_on_panic, PanicInfo};
use super::ffi;
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
where
F: FnOnce(Weak<PanicInfo>) + UnwindSafe,
{
let info_from_raw = unsafe { Weak::from_raw(panic_info as *mut PanicInfo) };
// Asserting unwind safety on this type should be fine because `PanicInfo` is
// `RefUnwindSafe` and `Rc<T>` is `UnwindSafe` if `T` is `RefUnwindSafe`.
let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw));
// `from_raw` takes ownership of the data behind the pointer.
// But if this scope takes ownership of the weak pointer, then
// the weak pointer will get free'd at the end of the scope.
// However we want to keep that weak reference around after the function.
std::mem::forget(info_from_raw);
let mtm = MainThreadMarker::new().unwrap();
stop_app_on_panic(mtm, Weak::clone(&panic_info), move || {
let _ = &panic_info;
f(panic_info.0)
});
}
// begin is queued with the highest priority to ensure it is processed before other observers
extern "C-unwind" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver,
extern "C" fn control_flow_begin_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_info: *mut c_void,
panic_info: *mut c_void,
) {
match activity {
CFRunLoopActivity::AfterWaiting => {
AppState::get(MainThreadMarker::new().unwrap()).wakeup();
},
_ => unreachable!(),
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopAfterWaiting => {
// trace!("Triggered `CFRunLoopAfterWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
// trace!("Completed `CFRunLoopAfterWaiting`");
},
_ => unreachable!(),
}
});
}
}
// end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopExiting would get sent after AboutToWait
extern "C-unwind" fn control_flow_end_handler(
_: *mut CFRunLoopObserver,
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,
_info: *mut c_void,
panic_info: *mut c_void,
) {
match activity {
CFRunLoopActivity::BeforeWaiting => {
AppState::get(MainThreadMarker::new().unwrap()).cleared();
},
CFRunLoopActivity::Exit => (), // unimplemented!(), // not expected to ever happen
_ => unreachable!(),
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
kCFRunLoopBeforeWaiting => {
// trace!("Triggered `CFRunLoopBeforeWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
// trace!("Completed `CFRunLoopBeforeWaiting`");
},
kCFRunLoopExit => (), // unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
});
}
}
#[derive(Debug)]
pub struct RunLoop(CFRetained<CFRunLoop>);
pub struct RunLoop(CFRunLoopRef);
impl Default for RunLoop {
fn default() -> Self {
Self(ptr::null_mut())
}
}
impl RunLoop {
pub fn main(mtm: MainThreadMarker) -> Self {
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
let _ = mtm;
RunLoop(CFRunLoop::main().unwrap())
RunLoop(unsafe { CFRunLoopGetMain() })
}
pub fn wakeup(&self) {
self.0.wake_up();
unsafe { CFRunLoopWakeUp(self.0) }
}
unsafe fn add_observer(
&self,
flags: CFRunLoopActivity,
// The lower the value, the sooner this will run
flags: CFOptionFlags,
priority: CFIndex,
handler: CFRunLoopObserverCallBack,
context: *mut CFRunLoopObserverContext,
) {
let observer =
unsafe { CFRunLoopObserver::new(None, flags.0, true, priority, handler, context) }
.unwrap();
self.0.add_observer(Some(&observer), unsafe { kCFRunLoopCommonModes });
let observer = unsafe {
CFRunLoopObserverCreate(
ptr::null_mut(),
flags,
ffi::TRUE, // Indicates we want this to run repeatedly
priority, // The lower the value, the sooner this will run
handler,
context,
)
};
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
@@ -110,6 +166,10 @@ impl RunLoop {
/// 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 || {
@@ -135,33 +195,33 @@ impl RunLoop {
// and be delivered to the application afterwards.
//
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
let mode = unsafe { kCFRunLoopDefaultMode as CFTypeRef };
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
unsafe { self.0.perform_block(Some(mode), Some(&block)) }
unsafe { CFRunLoopPerformBlock(self.0, mode, &block) }
}
}
pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
let run_loop = RunLoop::main(mtm);
unsafe {
let mut context = CFRunLoopObserverContext {
info: ptr::null_mut(),
info: Weak::into_raw(panic_info) as *mut _,
version: 0,
retain: None,
release: None,
copyDescription: None,
};
run_loop.add_observer(
CFRunLoopActivity::AfterWaiting,
kCFRunLoopAfterWaiting,
CFIndex::MIN,
Some(control_flow_begin_handler),
control_flow_begin_handler,
&mut context as *mut _,
);
run_loop.add_observer(
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
CFIndex::MAX,
Some(control_flow_end_handler),
control_flow_end_handler,
&mut context as *mut _,
);
}
@@ -169,7 +229,7 @@ pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
#[derive(Debug)]
pub struct EventLoopWaker {
timer: CFRetained<CFRunLoopTimer>,
timer: CFRunLoopTimerRef,
/// An arbitrary instant in the past, that will trigger an immediate wake
/// We save this as the `next_fire_date` for consistency so we can
@@ -184,28 +244,30 @@ pub struct EventLoopWaker {
impl Drop for EventLoopWaker {
fn drop(&mut self) {
self.timer.invalidate();
unsafe {
CFRunLoopTimerInvalidate(self.timer);
CFRelease(self.timer as _);
}
}
}
impl EventLoopWaker {
pub(crate) fn new() -> Self {
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
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.
// It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimer::new(
None,
let timer = CFRunLoopTimerCreate(
ptr::null_mut(),
f64::MAX,
0.000_000_1,
0,
0,
Some(wakeup_main_loop),
wakeup_main_loop,
ptr::null_mut(),
)
.unwrap();
CFRunLoop::main().unwrap().add_timer(Some(&timer), kCFRunLoopCommonModes);
);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
Self { timer, start_instant: Instant::now(), next_fire_date: None }
}
}
@@ -213,14 +275,14 @@ impl EventLoopWaker {
pub fn stop(&mut self) {
if self.next_fire_date.is_some() {
self.next_fire_date = None;
self.timer.set_next_fire_date(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);
self.timer.set_next_fire_date(f64::MIN);
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, f64::MIN) }
}
}
@@ -233,11 +295,13 @@ impl EventLoopWaker {
Some(instant) => {
if self.next_fire_date != Some(instant) {
self.next_fire_date = Some(instant);
let current = CFAbsoluteTimeGetCurrent();
let duration = instant - now;
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
+ duration.as_secs() as f64;
self.timer.set_next_fire_date(current + fsecs);
unsafe {
let current = CFAbsoluteTimeGetCurrent();
let duration = instant - now;
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
+ duration.as_secs() as f64;
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
}
}
},
None => {

View File

@@ -1,16 +1,9 @@
use objc2_core_graphics::CGError;
use tracing::trace;
use winit_core::error::OsError;
macro_rules! os_error {
($error:expr) => {{
winit_core::error::OsError::new(line!(), file!(), $error)
}};
}
macro_rules! trace_scope {
($s:literal) => {
let _crate = $crate::util::TraceGuard::new(module_path!(), $s);
let _crate =
$crate::platform_impl::platform::appkit::util::TraceGuard::new(module_path!(), $s);
};
}
@@ -33,12 +26,3 @@ impl Drop for TraceGuard {
trace!(target = self.module_path, "Completed `{}`", self.called_from_fn);
}
}
#[track_caller]
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
if err == CGError::Success {
Ok(())
} else {
Err(os_error!(format!("CGError {err:?}")))
}
}

View File

@@ -4,34 +4,33 @@ use std::collections::{HashMap, VecDeque};
use std::ptr;
use std::rc::Rc;
use dpi::{LogicalPosition, LogicalSize};
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel};
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker};
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSWindow,
NSTrackingRectTag, NSView,
};
use objc2_core_foundation::CGRect;
use objc2_foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect,
NSSize, NSString, NSUInteger,
};
use winit_core::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
};
use winit_core::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use winit_core::window::ImeCapabilities;
use super::app_state::AppState;
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::window_id;
use crate::OptionAsAlt;
use super::window::WinitWindow;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt;
#[derive(Debug)]
struct CursorState {
@@ -83,7 +82,7 @@ fn key_to_modifier(key: &Key) -> Option<ModifiersState> {
match key {
Key::Named(NamedKey::Alt) => Some(ModifiersState::ALT),
Key::Named(NamedKey::Control) => Some(ModifiersState::CONTROL),
Key::Named(NamedKey::Meta) => Some(ModifiersState::META),
Key::Named(NamedKey::Super) => Some(ModifiersState::SUPER),
Key::Named(NamedKey::Shift) => Some(ModifiersState::SHIFT),
_ => None,
}
@@ -94,7 +93,7 @@ fn get_right_modifier_code(key: &Key) -> KeyCode {
Key::Named(NamedKey::Alt) => KeyCode::AltRight,
Key::Named(NamedKey::Control) => KeyCode::ControlRight,
Key::Named(NamedKey::Shift) => KeyCode::ShiftRight,
Key::Named(NamedKey::Meta) => KeyCode::MetaRight,
Key::Named(NamedKey::Super) => KeyCode::SuperRight,
_ => unreachable!(),
}
}
@@ -104,7 +103,7 @@ fn get_left_modifier_code(key: &Key) -> KeyCode {
Key::Named(NamedKey::Alt) => KeyCode::AltLeft,
Key::Named(NamedKey::Control) => KeyCode::ControlLeft,
Key::Named(NamedKey::Shift) => KeyCode::ShiftLeft,
Key::Named(NamedKey::Meta) => KeyCode::MetaLeft,
Key::Named(NamedKey::Super) => KeyCode::SuperLeft,
_ => unreachable!(),
}
}
@@ -126,7 +125,7 @@ pub struct ViewState {
/// True iff the application wants IME events.
///
/// Can be set using `set_ime_allowed`
ime_capabilities: Cell<Option<ImeCapabilities>>,
ime_allowed: Cell<bool>,
/// True if the current key event should be forwarded
/// to the application, even during IME
@@ -139,21 +138,28 @@ pub struct ViewState {
option_as_alt: Cell<OptionAsAlt>,
}
define_class!(
#[unsafe(super(NSView, NSResponder, NSObject))]
#[ivars = ViewState]
#[name = "WinitView"]
declare_class!(
pub(super) struct WinitView;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitView {
#[unsafe(method(isFlipped))]
unsafe impl ClassType for WinitView {
#[inherits(NSResponder, NSObject)]
type Super = NSView;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitView";
}
impl DeclaredClass for WinitView {
type Ivars = ViewState;
}
unsafe impl WinitView {
#[method(isFlipped)]
fn is_flipped(&self) -> bool {
// `winit` uses the upper-left corner as the origin.
true
}
#[unsafe(method(viewDidMoveToWindow))]
#[method(viewDidMoveToWindow)]
fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
@@ -169,7 +175,7 @@ define_class!(
}
// Not a normal method on `NSView`, it's triggered by `NSViewFrameDidChangeNotification`.
#[unsafe(method(viewFrameDidChangeNotification:))]
#[method(viewFrameDidChangeNotification:)]
fn frame_did_change(&self, _notification: Option<&AnyObject>) {
trace_scope!("NSViewFrameDidChangeNotification");
if let Some(tracking_rect) = self.ivars().tracking_rect.take() {
@@ -184,45 +190,38 @@ define_class!(
self.ivars().tracking_rect.set(Some(tracking_rect));
// Emit resize event here rather than from windowDidResize because:
// 1. When a new window is created as a tab, the frame size may change without a window
// resize occurring.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong
// size (includes tab height).
// 1. When a new window is created as a tab, the frame size may change without a window resize occurring.
// 2. Even when a window resize does occur on a new tabbed window, it contains the wrong size (includes tab height).
let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let size = logical_size.to_physical::<u32>(self.scale_factor());
self.queue_event(WindowEvent::SurfaceResized(size));
}
#[unsafe(method(drawRect:))]
#[method(drawRect:)]
fn draw_rect(&self, _rect: NSRect) {
trace_scope!("drawRect:");
self.ivars().app_state.handle_redraw(window_id(&self.window()));
self.ivars().app_state.handle_redraw(self.window().id());
// This is a direct subclass of NSView, no need to call superclass' drawRect:
}
#[unsafe(method(acceptsFirstResponder))]
#[method(acceptsFirstResponder)]
fn accepts_first_responder(&self) -> bool {
trace_scope!("acceptsFirstResponder");
true
}
// This is necessary to prevent a beefy terminal error on MacBook Pros:
// 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`
#[unsafe(method_id(touchBar))]
// 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<Retained<NSObject>> {
trace_scope!("touchBar");
None
}
#[unsafe(method(resetCursorRects))]
#[method(resetCursorRects)]
fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects");
let bounds = self.bounds();
@@ -237,13 +236,13 @@ define_class!(
}
unsafe impl NSTextInputClient for WinitView {
#[unsafe(method(hasMarkedText))]
#[method(hasMarkedText)]
fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText");
self.ivars().marked_text.borrow().length() > 0
}
#[unsafe(method(markedRange))]
#[method(markedRange)]
fn marked_range(&self) -> NSRange {
trace_scope!("markedRange");
let length = self.ivars().marked_text.borrow().length();
@@ -255,14 +254,14 @@ define_class!(
}
}
#[unsafe(method(selectedRange))]
#[method(selectedRange)]
fn selected_range(&self) -> NSRange {
trace_scope!("selectedRange");
// Documented to return `{NSNotFound, 0}` if there is no selection.
NSRange::new(NSNotFound as NSUInteger, 0)
}
#[unsafe(method(setMarkedText:selectedRange:replacementRange:))]
#[method(setMarkedText:selectedRange:replacementRange:)]
fn set_marked_text(
&self,
string: &NSObject,
@@ -272,15 +271,23 @@ define_class!(
// TODO: Use _replacement_range, requires changing the event to report surrounding text.
trace_scope!("setMarkedText:selectedRange:replacementRange:");
let (marked_text, string) = if let Some(string) =
string.downcast_ref::<NSAttributedString>()
{
(NSMutableAttributedString::from_attributed_nsstring(string), string.string())
} else if let Some(string) = string.downcast_ref::<NSString>() {
(NSMutableAttributedString::from_nsstring(string), string.copy())
// SAFETY: This method is guaranteed to get either a `NSString` or a `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(),
)
} else {
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
panic!("unexpected text {string:?}")
let string: *const NSObject = string;
let string: *const NSString = string.cast();
let string = unsafe { &*string };
(
NSMutableAttributedString::from_nsstring(string),
string.copy(),
)
};
// Update marked text.
@@ -316,7 +323,7 @@ define_class!(
self.queue_event(WindowEvent::Ime(Ime::Preedit(string.to_string(), cursor_range)));
}
#[unsafe(method(unmarkText))]
#[method(unmarkText)]
fn unmark_text(&self) {
trace_scope!("unmarkText");
*self.ivars().marked_text.borrow_mut() = NSMutableAttributedString::new();
@@ -333,13 +340,13 @@ define_class!(
}
}
#[unsafe(method_id(validAttributesForMarkedText))]
#[method_id(validAttributesForMarkedText)]
fn valid_attributes_for_marked_text(&self) -> Retained<NSArray<NSAttributedStringKey>> {
trace_scope!("validAttributesForMarkedText");
NSArray::new()
}
#[unsafe(method_id(attributedSubstringForProposedRange:actualRange:))]
#[method_id(attributedSubstringForProposedRange:actualRange:)]
fn attributed_substring_for_proposed_range(
&self,
_range: NSRange,
@@ -349,43 +356,42 @@ define_class!(
None
}
#[unsafe(method(characterIndexForPoint:))]
#[method(characterIndexForPoint:)]
fn character_index_for_point(&self, _point: NSPoint) -> NSUInteger {
trace_scope!("characterIndexForPoint:");
0
}
#[unsafe(method(firstRectForCharacterRange:actualRange:))]
#[method(firstRectForCharacterRange:actualRange:)]
fn first_rect_for_character_range(
&self,
_range: NSRange,
_actual_range: *mut NSRange,
) -> NSRect {
trace_scope!("firstRectForCharacterRange:actualRange:");
// Guard when the view is no longer in a window during teardown.
let Some(window) = (**self).window() else {
return CGRect::ZERO;
};
// Return value is expected to be in screen coordinates, so we need a conversion
let rect = NSRect::new(self.ivars().ime_position.get(), self.ivars().ime_size.get());
let view_rect = self.convertRect_toView(rect, None);
window.convertRectToScreen(view_rect)
let rect = NSRect::new(
self.ivars().ime_position.get(),
self.ivars().ime_size.get()
);
// Return value is expected to be in screen coordinates, so we need a conversion here
self.window()
.convertRectToScreen(self.convertRect_toView(rect, None))
}
#[unsafe(method(insertText:replacementRange:))]
#[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:");
let string = if let Some(string) = string.downcast_ref::<NSAttributedString>() {
string.string().to_string()
} else if let Some(string) = string.downcast_ref::<NSString>() {
string.to_string()
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
let string = if string.is_kind_of::<NSAttributedString>() {
let string: *const NSObject = string;
let string: *const NSAttributedString = string.cast();
unsafe { &*string }.string().to_string()
} else {
// This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
panic!("unexpected text {string:?}")
let string: *const NSObject = string;
let string: *const NSString = string.cast();
unsafe { &*string }.to_string()
};
let is_control = string.chars().next().is_some_and(|c| c.is_control());
@@ -398,16 +404,15 @@ define_class!(
}
}
// Basically, we're sent this message whenever a keyboard event that doesn't generate a
// "human readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[unsafe(method(doCommandBySelector:))]
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human
// readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[method(doCommandBySelector:)]
fn do_command_by_selector(&self, command: Sel) {
trace_scope!("doCommandBySelector:");
// We shouldn't forward any character from just committed text, since we'll end up
// sending it twice with some IMEs like Korean one. We'll also always send
// `Enter` in that case, which is not desired given it was used to confirm
// IME input.
// We shouldn't forward any character from just committed text, since we'll end up sending
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
// which is not desired given it was used to confirm IME input.
if self.ivars().ime_state.get() == ImeState::Committed {
return;
}
@@ -421,14 +426,10 @@ define_class!(
}
// Send command action to user if they requested it.
let window_id = window_id(&self.window());
let window_id = self.window().id();
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
if let Some(handler) = app.macos_handler() {
handler.standard_key_binding(
event_loop,
window_id,
command.name().to_str().unwrap(),
);
handler.standard_key_binding(event_loop, window_id, command.name());
}
});
@@ -438,9 +439,8 @@ define_class!(
}
}
/// This documentation attribute makes rustfmt work for some reason?
impl WinitView {
#[unsafe(method(keyDown:))]
unsafe impl WinitView {
#[method(keyDown:)]
fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:");
{
@@ -465,7 +465,7 @@ define_class!(
// we must send the `KeyboardInput` event during IME if it triggered
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input
// is not handled by IME and should be handled by the application)
if self.ivars().ime_capabilities.get().is_some() {
if self.ivars().ime_allowed.get() {
let events_for_nsview = NSArray::from_slice(&[&*event]);
unsafe { self.interpretKeyEvents(&events_for_nsview) };
@@ -483,7 +483,7 @@ define_class!(
// Allow normal input after the commit.
self.ivars().ime_state.set(ImeState::Ground);
true
},
}
ImeState::Preedit => true,
// `key_down` could result in preedit clear, so compare old and current state.
_ => old_ime_state != self.ivars().ime_state.get(),
@@ -499,7 +499,7 @@ define_class!(
}
}
#[unsafe(method(keyUp:))]
#[method(keyUp:)]
fn key_up(&self, event: &NSEvent) {
trace_scope!("keyUp:");
@@ -507,7 +507,10 @@ define_class!(
self.update_modifiers(&event, false);
// We want to send keyboard input when we are currently in the ground state.
if matches!(self.ivars().ime_state.get(), ImeState::Ground | ImeState::Disabled) {
if matches!(
self.ivars().ime_state.get(),
ImeState::Ground | ImeState::Disabled
) {
self.queue_event(WindowEvent::KeyboardInput {
device_id: None,
event: create_key_event(&event, false, false),
@@ -516,14 +519,14 @@ define_class!(
}
}
#[unsafe(method(flagsChanged:))]
#[method(flagsChanged:)]
fn flags_changed(&self, event: &NSEvent) {
trace_scope!("flagsChanged:");
self.update_modifiers(event, true);
}
#[unsafe(method(insertTab:))]
#[method(insertTab:)]
fn insert_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertTab:");
let window = self.window();
@@ -534,7 +537,7 @@ define_class!(
}
}
#[unsafe(method(insertBackTab:))]
#[method(insertBackTab:)]
fn insert_back_tab(&self, _sender: Option<&AnyObject>) {
trace_scope!("insertBackTab:");
let window = self.window();
@@ -547,7 +550,7 @@ define_class!(
// Allows us to receive Cmd-. (the shortcut for closing a dialog)
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
#[unsafe(method(cancelOperation:))]
#[method(cancelOperation:)]
fn cancel_operation(&self, _sender: Option<&AnyObject>) {
let mtm = MainThreadMarker::from(self);
trace_scope!("cancelOperation:");
@@ -577,42 +580,42 @@ define_class!(
//
// See https://github.com/rust-windowing/winit/pull/1490 for history.
#[unsafe(method(mouseDown:))]
#[method(mouseDown:)]
fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[unsafe(method(mouseUp:))]
#[method(mouseUp:)]
fn mouse_up(&self, event: &NSEvent) {
trace_scope!("mouseUp:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[unsafe(method(rightMouseDown:))]
#[method(rightMouseDown:)]
fn right_mouse_down(&self, event: &NSEvent) {
trace_scope!("rightMouseDown:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[unsafe(method(rightMouseUp:))]
#[method(rightMouseUp:)]
fn right_mouse_up(&self, event: &NSEvent) {
trace_scope!("rightMouseUp:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Released);
}
#[unsafe(method(otherMouseDown:))]
#[method(otherMouseDown:)]
fn other_mouse_down(&self, event: &NSEvent) {
trace_scope!("otherMouseDown:");
self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed);
}
#[unsafe(method(otherMouseUp:))]
#[method(otherMouseUp:)]
fn other_mouse_up(&self, event: &NSEvent) {
trace_scope!("otherMouseUp:");
self.mouse_motion(event);
@@ -621,27 +624,27 @@ define_class!(
// No tracing on these because that would be overly verbose
#[unsafe(method(mouseMoved:))]
#[method(mouseMoved:)]
fn mouse_moved(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[unsafe(method(mouseDragged:))]
#[method(mouseDragged:)]
fn mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[unsafe(method(rightMouseDragged:))]
#[method(rightMouseDragged:)]
fn right_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[unsafe(method(otherMouseDragged:))]
#[method(otherMouseDragged:)]
fn other_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event);
}
#[unsafe(method(mouseEntered:))]
#[method(mouseEntered:)]
fn mouse_entered(&self, event: &NSEvent) {
trace_scope!("mouseEntered:");
@@ -655,7 +658,7 @@ define_class!(
});
}
#[unsafe(method(mouseExited:))]
#[method(mouseExited:)]
fn mouse_exited(&self, event: &NSEvent) {
trace_scope!("mouseExited:");
@@ -669,7 +672,7 @@ define_class!(
});
}
#[unsafe(method(scrollWheel:))]
#[method(scrollWheel:)]
fn scroll_wheel(&self, event: &NSEvent) {
trace_scope!("scrollWheel:");
@@ -686,9 +689,9 @@ define_class!(
};
// The "momentum phase," if any, has higher priority than touch phase (the two should
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no
// momentum phase is recorded (or rather, the started/ended cases of the
// momentum phase) then we report the touch phase.
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum
// phase is recorded (or rather, the started/ended cases of the momentum phase) then we
// report the touch phase.
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.momentumPhase() } {
NSEventPhase::MayBegin | NSEventPhase::Began => TouchPhase::Started,
@@ -702,13 +705,17 @@ define_class!(
self.update_modifiers(event, false);
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop|
app.device_event(event_loop, None, DeviceEvent::MouseWheel { delta })
);
self.queue_event(WindowEvent::MouseWheel {
device_id: None,
delta,
phase,
});
self.queue_event(WindowEvent::MouseWheel { device_id: None, delta, phase });
}
#[unsafe(method(magnifyWithEvent:))]
#[method(magnifyWithEvent:)]
fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:");
@@ -730,16 +737,18 @@ define_class!(
});
}
#[unsafe(method(smartMagnifyWithEvent:))]
#[method(smartMagnifyWithEvent:)]
fn smart_magnify_with_event(&self, event: &NSEvent) {
trace_scope!("smartMagnifyWithEvent:");
self.mouse_motion(event);
self.queue_event(WindowEvent::DoubleTapGesture { device_id: None });
self.queue_event(WindowEvent::DoubleTapGesture {
device_id: None,
});
}
#[unsafe(method(rotateWithEvent:))]
#[method(rotateWithEvent:)]
fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:");
@@ -761,7 +770,7 @@ define_class!(
});
}
#[unsafe(method(pressureChangeWithEvent:))]
#[method(pressureChangeWithEvent:)]
fn pressure_change_with_event(&self, event: &NSEvent) {
trace_scope!("pressureChangeWithEvent:");
@@ -775,13 +784,13 @@ define_class!(
// Allows us to receive Ctrl-Tab and Ctrl-Esc.
// Note that this *doesn't* help with any missing Cmd inputs.
// https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816
#[unsafe(method(_wantsKeyDownForEvent:))]
#[method(_wantsKeyDownForEvent:)]
fn wants_key_down_for_event(&self, _event: &NSEvent) -> bool {
trace_scope!("_wantsKeyDownForEvent:");
true
}
#[unsafe(method(acceptsFirstMouse:))]
#[method(acceptsFirstMouse:)]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:");
self.ivars().accepts_first_mouse
@@ -806,25 +815,32 @@ impl WinitView {
tracking_rect: Default::default(),
ime_state: Default::default(),
input_source: Default::default(),
ime_capabilities: Default::default(),
ime_allowed: Default::default(),
forward_key_to_app: Default::default(),
marked_text: Default::default(),
accepts_first_mouse,
option_as_alt: Cell::new(option_as_alt),
});
let this: Retained<Self> = unsafe { msg_send![super(this), init] };
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
*this.ivars().input_source.borrow_mut() = this.current_input_source();
this
}
fn window(&self) -> Retained<NSWindow> {
(**self).window().expect("view must be installed in a window")
fn window(&self) -> Retained<WinitWindow> {
let window = (**self).window().expect("view must be installed in a window");
if !window.isKindOfClass(WinitWindow::class()) {
unreachable!("view installed in non-WinitWindow");
}
// SAFETY: Just checked that the window is `WinitWindow`
unsafe { Retained::cast(window) }
}
fn queue_event(&self, event: WindowEvent) {
let window_id = window_id(&self.window());
let window_id = self.window().id();
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
app.window_event(event_loop, window_id, event);
});
@@ -868,13 +884,12 @@ impl WinitView {
}
}
pub(super) fn set_ime_allowed(&self, capabilities: Option<ImeCapabilities>) {
if self.ivars().ime_capabilities.get().is_some() {
pub(super) fn set_ime_allowed(&self, ime_allowed: bool) {
if self.ivars().ime_allowed.get() == ime_allowed {
return;
}
self.ivars().ime_capabilities.set(capabilities);
if capabilities.is_some() {
self.ivars().ime_allowed.set(ime_allowed);
if self.ivars().ime_allowed.get() {
return;
}
@@ -887,10 +902,6 @@ impl WinitView {
}
}
pub(super) fn ime_capabilities(&self) -> Option<ImeCapabilities> {
self.ivars().ime_capabilities.get()
}
pub(super) fn set_ime_cursor_area(&self, position: NSPoint, size: NSSize) {
self.ivars().ime_position.set(position);
self.ivars().ime_size.set(size);
@@ -954,8 +965,10 @@ impl WinitView {
// We'll correct this later.
state: Pressed,
text: None,
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
platform_specific: KeyEventExtra {
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
},
};
let location_mask = ModLocationMask::from_location(event.location);
@@ -1110,14 +1123,14 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
// 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) -> Retained<NSEvent> {
let ev_mods = event_mods(event).state();
let ev_mods = event_mods(event).state;
let ignore_alt_characters = match option_as_alt {
OptionAsAlt::OnlyLeft if lalt_pressed(event) => true,
OptionAsAlt::OnlyRight if ralt_pressed(event) => true,
OptionAsAlt::Both if ev_mods.alt_key() => true,
_ => false,
} && !ev_mods.control_key()
&& !ev_mods.meta_key();
&& !ev_mods.super_key();
if ignore_alt_characters {
let ns_chars = unsafe {

View File

@@ -1,28 +1,22 @@
#![allow(clippy::unnecessary_cast)]
use std::sync::Arc;
use dispatch2::MainThreadBound;
use dpi::{PhysicalSize, Position, Size};
use dpi::{Position, Size};
use objc2::rc::{autoreleasepool, Retained};
use objc2::{define_class, MainThreadMarker, Message};
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
use objc2_foundation::NSObject;
use winit_core::cursor::Cursor;
use winit_core::error::RequestError;
use winit_core::icon::Icon;
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use winit_core::window::{
ImeCapabilities, ImeRequest, ImeRequestError, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSResponder, NSWindow};
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
use crate::error::RequestError;
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::window::{
Cursor, Fullscreen, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
#[derive(Debug)]
pub(crate) struct Window {
window: MainThreadBound<Retained<NSWindow>>,
window: MainThreadBound<Retained<WinitWindow>>,
/// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Retained<WindowDelegate>>,
}
@@ -70,7 +64,7 @@ impl Window {
impl Drop for Window {
fn drop(&mut self) {
// Restore the video mode.
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_, _))) {
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_))) {
self.set_fullscreen(None);
}
@@ -93,7 +87,7 @@ impl rwh_06::HasWindowHandle for Window {
}
impl CoreWindow for Window {
fn id(&self) -> winit_core::window::WindowId {
fn id(&self) -> crate::window::WindowId {
self.maybe_wait_on_main(|delegate| delegate.id())
}
@@ -141,18 +135,10 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.safe_area())
}
fn min_surface_size(&self) -> Option<PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.min_surface_size())
}
fn set_min_surface_size(&self, min_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size))
}
fn max_surface_size(&self) -> Option<PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.max_surface_size())
}
fn set_max_surface_size(&self, max_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_max_surface_size(max_size));
}
@@ -169,18 +155,10 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.set_title(title));
}
fn is_transparent(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_transparent())
}
fn set_transparent(&self, transparent: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_transparent(transparent));
}
fn is_blurred(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_blurred())
}
fn set_blur(&self, blur: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_blur(blur));
}
@@ -226,11 +204,11 @@ impl CoreWindow for Window {
}
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen))
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen.map(Into::into)))
}
fn fullscreen(&self) -> Option<Fullscreen> {
self.maybe_wait_on_main(|delegate| delegate.fullscreen())
self.maybe_wait_on_main(|delegate| delegate.fullscreen().map(Into::into))
}
fn set_decorations(&self, decorations: bool) {
@@ -241,28 +219,24 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.is_decorated())
}
fn window_level(&self) -> WindowLevel {
self.maybe_wait_on_main(|delegate| delegate.window_level())
}
fn set_window_level(&self, level: WindowLevel) {
self.maybe_wait_on_main(|delegate| delegate.set_window_level(level));
}
fn window_icon(&self) -> Option<Icon> {
self.maybe_wait_on_main(|delegate| delegate.window_icon())
}
fn set_window_icon(&self, window_icon: Option<Icon>) {
self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon));
}
fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
self.maybe_wait_on_main(|delegate| delegate.request_ime_update(request))
fn set_ime_cursor_area(&self, position: Position, size: Size) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size));
}
fn ime_capabilities(&self) -> Option<ImeCapabilities> {
self.maybe_wait_on_main(|delegate| delegate.ime_capabilities())
fn set_ime_allowed(&self, allowed: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed));
}
fn set_ime_purpose(&self, purpose: ImePurpose) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose));
}
fn focus_window(&self) {
@@ -285,10 +259,6 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.theme())
}
fn content_protected(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.content_protected())
}
fn set_content_protected(&self, protected: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_content_protected(protected));
}
@@ -297,10 +267,6 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.title())
}
fn cursor(&self) -> Cursor {
self.maybe_wait_on_main(|delegate| delegate.cursor())
}
fn set_cursor(&self, cursor: Cursor) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor(cursor));
}
@@ -309,10 +275,7 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position))
}
fn set_cursor_grab(
&self,
mode: winit_core::window::CursorGrabMode,
) -> Result<(), RequestError> {
fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode))
}
@@ -326,7 +289,7 @@ impl CoreWindow for Window {
fn drag_resize_window(
&self,
direction: winit_core::window::ResizeDirection,
direction: crate::window::ResizeDirection,
) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?)
}
@@ -342,24 +305,21 @@ impl CoreWindow for Window {
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.current_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
delegate.current_monitor().map(|inner| CoreMonitorHandle { inner })
})
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
self.maybe_wait_on_main(|delegate| {
Box::new(
delegate
.available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
delegate.available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner }),
)
})
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.primary_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
delegate.primary_monitor().map(|inner| CoreMonitorHandle { inner })
})
}
@@ -372,21 +332,27 @@ impl CoreWindow for Window {
}
}
define_class!(
#[unsafe(super(NSWindow, NSResponder, NSObject))]
#[name = "WinitWindow"]
declare_class!(
#[derive(Debug)]
pub struct WinitWindow;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitWindow {
#[unsafe(method(canBecomeMainWindow))]
unsafe impl ClassType for WinitWindow {
#[inherits(NSResponder, NSObject)]
type Super = NSWindow;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitWindow";
}
impl DeclaredClass for WinitWindow {}
unsafe impl WinitWindow {
#[method(canBecomeMainWindow)]
fn can_become_main_window(&self) -> bool {
trace_scope!("canBecomeMainWindow");
true
}
#[unsafe(method(canBecomeKeyWindow))]
#[method(canBecomeKeyWindow)]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
true
@@ -394,24 +360,8 @@ define_class!(
}
);
define_class!(
#[unsafe(super(NSPanel, NSWindow, NSResponder, NSObject))]
#[name = "WinitPanel"]
#[derive(Debug)]
pub struct WinitPanel;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitPanel {
// although NSPanel can become key window
// it doesn't if window doesn't have NSWindowStyleMask::Titled
#[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool {
trace_scope!("canBecomeKeyWindow");
true
}
impl WinitWindow {
pub(super) fn id(&self) -> WindowId {
WindowId::from_raw(self as *const Self as usize)
}
);
pub(super) fn window_id(window: &NSWindow) -> WindowId {
WindowId::from_raw(window as *const _ as usize)
}

View File

@@ -1,17 +1,17 @@
use std::cell::RefCell;
use std::{fmt, mem};
use winit_core::application::ApplicationHandler;
use crate::application::ApplicationHandler;
/// A helper type for storing a reference to `ApplicationHandler`, allowing interior mutable access
/// to it within the execution of a closure.
#[derive(Default)]
pub struct EventHandler {
pub(crate) struct EventHandler {
/// This can be in the following states:
/// - Not registered by the event loop, or terminated (None).
/// - Not registered by the event loop (None).
/// - Present (Some(handler)).
/// - Currently executing the handler / in use (RefCell borrowed).
inner: RefCell<Option<Box<dyn ApplicationHandler + 'static>>>,
inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
}
impl fmt::Debug for EventHandler {
@@ -26,7 +26,7 @@ impl fmt::Debug for EventHandler {
}
impl EventHandler {
pub fn new() -> Self {
pub(crate) fn new() -> Self {
Self { inner: RefCell::new(None) }
}
@@ -35,9 +35,9 @@ impl EventHandler {
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates
/// to store the handler in a thread local, such that it can be accessed
/// from within the closure.
pub fn set<'handler, R>(
pub(crate) fn set<'handler, R>(
&self,
app: Box<dyn ApplicationHandler + 'handler>,
app: &'handler mut dyn ApplicationHandler,
closure: impl FnOnce() -> R,
) -> R {
// SAFETY: We extend the lifetime of the handler here so that we can
@@ -48,8 +48,8 @@ impl EventHandler {
// extended beyond `'handler`.
let handler = unsafe {
mem::transmute::<
Box<dyn ApplicationHandler + 'handler>,
Box<dyn ApplicationHandler + 'static>,
&'handler mut dyn ApplicationHandler,
&'static mut dyn ApplicationHandler,
>(app)
};
@@ -71,13 +71,10 @@ impl EventHandler {
fn drop(&mut self) {
match self.0.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => {
let handler = data.take();
// Explicitly `Drop` the application handler.
drop(handler);
*data = None;
},
Ok(None) => {
// Allowed, happens if the handler was cleared manually
// elsewhere (such as in `applicationWillTerminate:`).
tracing::error!("tried to clear handler, but no handler was set");
},
Err(_) => {
// Note: This is not expected to ever happen, this
@@ -104,24 +101,25 @@ impl EventHandler {
// soundness.
}
pub fn in_use(&self) -> bool {
#[cfg(target_os = "macos")]
pub(crate) fn in_use(&self) -> bool {
self.inner.try_borrow().is_err()
}
pub fn ready(&self) -> bool {
pub(crate) fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}
pub fn handle(&self, callback: impl FnOnce(&mut (dyn ApplicationHandler + '_))) {
pub(crate) fn handle(&self, callback: impl FnOnce(&mut dyn ApplicationHandler)) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(ref mut user_app)) => {
Ok(Some(user_app)) => {
// It is important that we keep the reference borrowed here,
// so that `in_use` can properly detect that the handler is
// still in use.
//
// If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed.
callback(&mut **user_app);
callback(*user_app);
},
Ok(None) => {
// `NSApplication`, our app state and this handler are all
@@ -135,21 +133,4 @@ impl EventHandler {
},
}
}
pub fn terminate(&self) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => {
let handler = data.take();
// Explicitly `Drop` the application handler.
drop(handler);
},
Ok(None) => {
// When terminating, we expect the application handler to still be registered.
tracing::error!("tried to clear handler, but no handler was set");
},
Err(_) => {
panic!("tried to clear handler while an event is currently being handled");
},
}
}
}

View File

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

View File

@@ -1,22 +1,18 @@
// NOTE: This is symlinked to be contained in both the AppKit and UIKit implementations.
use std::ptr::NonNull;
use block2::RcBlock;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_foundation::{
NSNotification, NSNotificationCenter, NSNotificationName, NSObjectProtocol,
};
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(crate) fn create_observer(
pub fn create_observer(
center: &NSNotificationCenter,
name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static,
) -> Retained<ProtocolObject<dyn NSObjectProtocol>> {
) -> Retained<NSObject> {
let block = RcBlock::new(move |notification: NonNull<NSNotification>| {
handler(unsafe { notification.as_ref() });
});

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