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
324 changed files with 15513 additions and 17891 deletions

31
.github/CODEOWNERS vendored
View File

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

View File

@@ -1,8 +1,8 @@
name: iOS bug name: iOS bug
description: Create an iOS/UIKit-specific bug report description: Create an iOS-specific bug report
labels: labels:
- B - bug - B - bug
- DS - uikit - DS - ios
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -1,8 +1,8 @@
name: macOS bug name: MacOS bug
description: Create a macOS-specific bug report description: Create a macOS-specific bug report
labels: labels:
- B - bug - B - bug
- DS - appkit - DS - macos
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -2,7 +2,7 @@ name: Windows bug
description: Create a Windows-specific bug report description: Create a Windows-specific bug report
labels: labels:
- B - bug - B - bug
- DS - win32 - DS - windows
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -55,7 +55,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
toolchain: [stable, nightly, '1.85'] toolchain: [stable, nightly, '1.73']
platform: platform:
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml! # 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, } - { 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: '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: '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: '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: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
- { name: 'macOS x86_64', target: x86_64-apple-darwin, os: macos-latest, } - { name: 'macOS x86_64', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'macOS Aarch64', target: aarch64-apple-darwin, os: macos-latest, } - { name: 'macOS Aarch64', target: aarch64-apple-darwin, os: macos-latest, }
@@ -75,21 +75,17 @@ jobs:
- { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, } - { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
exclude: exclude:
# Web on nightly needs extra arguments # Web on nightly needs extra arguments
- toolchain: nightly - toolchain: nightly
platform: { name: 'Web' } platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest }
# Rustup is broken.
- toolchain: nightly
platform: { name: 'Windows 32bit GNU' }
# Android is tested on stable-3 # Android is tested on stable-3
- toolchain: '1.85' - toolchain: '1.73'
platform: { name: 'Android' } 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 # Redox OS doesn't follow MSRV
- toolchain: '1.85' - toolchain: '1.73'
platform: { name: 'Redox OS' } platform: { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest }
include: include:
- toolchain: '1.85' - toolchain: '1.73'
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package winit --features=android-native-activity', cmd: 'apk -- ' } platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
- toolchain: 'nightly' - toolchain: 'nightly'
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, test-options: -Zdoctest-xcompile } platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, test-options: -Zdoctest-xcompile }
- toolchain: 'nightly' - toolchain: 'nightly'
@@ -124,7 +120,7 @@ jobs:
# the cache has been downloaded. # the cache has been downloaded.
# #
# This could be avoided if we added Cargo.lock to the repository. # This could be avoided if we added Cargo.lock to the repository.
uses: actions/cache/restore@v6 uses: actions/cache/restore@v4
with: with:
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci # https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
path: | path: |
@@ -136,7 +132,7 @@ jobs:
- name: Generate lockfile - name: Generate lockfile
# Also updates the crates.io index # Also updates the crates.io index
run: cargo generate-lockfile && cargo update -p smol_str --precise 0.3.2 && cargo update -p unicode-segmentation --precise 1.12.0 && cargo update -p wayland-protocols --precise 0.32.12 run: cargo generate-lockfile && cargo update -p ahash --precise 0.8.7 && cargo update -p bumpalo --precise 3.14.0
- name: Install GCC Multilib - name: Install GCC Multilib
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
@@ -145,7 +141,7 @@ jobs:
- name: Cache cargo-apk - name: Cache cargo-apk
if: contains(matrix.platform.target, 'android') if: contains(matrix.platform.target, 'android')
id: cargo-apk-cache id: cargo-apk-cache
uses: actions/cache@v6 uses: actions/cache@v4
with: with:
path: ~/.cargo/bin/cargo-apk path: ~/.cargo/bin/cargo-apk
# Change this key if we update the required cargo-apk version # Change this key if we update the required cargo-apk version
@@ -160,7 +156,7 @@ jobs:
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true') if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
run: cargo install cargo-apk --version=^0.9.7 --locked run: cargo install cargo-apk --version=^0.9.7 --locked
- uses: taiki-e/cache-cargo-install-action@v3 - uses: taiki-e/cache-cargo-install-action@v2
if: contains(matrix.platform.target, 'wasm32') && matrix.toolchain == 'nightly' if: contains(matrix.platform.target, 'wasm32') && matrix.toolchain == 'nightly'
with: with:
tool: wasm-bindgen-cli tool: wasm-bindgen-cli
@@ -179,70 +175,17 @@ jobs:
- name: Build crate - name: Build crate
run: cargo $CMD build $OPTIONS 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. # Test only on Linux x86_64, so we avoid spending unnecessary CI hours.
- name: Test dpi crate - name: Test dpi crate
if: > if: >
contains(matrix.platform.name, 'Linux 64bit') && contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.85' matrix.toolchain != '1.73'
run: cargo test -p dpi run: cargo test -p dpi
- name: Check dpi crate (no_std)
if: >
contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.85'
run: cargo check -p dpi --no-default-features
- name: Build tests - name: Build tests
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.85' matrix.toolchain != '1.73'
run: cargo $CMD test --no-run $OPTIONS run: cargo $CMD test --no-run $OPTIONS
- name: Run tests - name: Run tests
@@ -251,7 +194,7 @@ jobs:
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') && (!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.85' matrix.toolchain != '1.73'
run: cargo $CMD test $OPTIONS run: cargo $CMD test $OPTIONS
- name: Lint with clippy - name: Lint with clippy
@@ -261,7 +204,7 @@ jobs:
- name: Build tests with serde enabled - name: Build tests with serde enabled
if: > if: >
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.85' matrix.toolchain != '1.73'
run: cargo $CMD test --no-run $OPTIONS $TEST_OPTIONS --features serde run: cargo $CMD test --no-run $OPTIONS $TEST_OPTIONS --features serde
- name: Run tests with serde enabled - name: Run tests with serde enabled
@@ -270,7 +213,7 @@ jobs:
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') && (!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.85' matrix.toolchain != '1.73'
run: cargo $CMD test $OPTIONS $TEST_OPTIONS --features serde run: cargo $CMD test $OPTIONS $TEST_OPTIONS --features serde
- name: Check docs.rs documentation - name: Check docs.rs documentation
@@ -281,7 +224,7 @@ jobs:
# See restore step above # See restore step above
- name: Save cache of cargo folder - name: Save cache of cargo folder
uses: actions/cache/save@v6 uses: actions/cache/save@v4
with: with:
path: | path: |
~/.cargo/registry/index/ ~/.cargo/registry/index/
@@ -290,14 +233,30 @@ jobs:
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }} key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
cargo-deny: cargo-deny:
name: Run cargo-deny name: Run cargo-deny on ${{ matrix.platform.name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
strategy:
fail-fast: false
matrix:
platform:
- { name: 'Android', target: aarch64-linux-android }
- { name: 'iOS', target: aarch64-apple-ios }
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
- { name: 'macOS', target: aarch64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'Web', target: wasm32-unknown-unknown }
- { name: 'Windows GNU', target: x86_64-pc-windows-gnu }
- { name: 'Windows MSVC', target: x86_64-pc-windows-msvc }
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: taiki-e/checkout-action@v1
- uses: EmbarkStudios/cargo-deny-action@v2 - uses: EmbarkStudios/cargo-deny-action@v2
with: with:
command: check
log-level: error log-level: error
arguments: --all-features --target ${{ matrix.platform.target }}
eslint: eslint:
name: ESLint name: ESLint
@@ -305,14 +264,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
working-directory: ./winit-web/src/script working-directory: ./src/platform_impl/web/script
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: taiki-e/checkout-action@v1
- name: Setup NPM - name: Setup NPM
run: npm install run: npm install
- name: Run ESLint - name: Run ESLint
run: npx eslint@9.38.0 run: npx eslint
swc: swc:
name: Minimize JavaScript name: Minimize JavaScript
@@ -320,7 +279,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults: defaults:
run: run:
working-directory: ./winit-web/src/script working-directory: ./src/platform_impl/web/script
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: taiki-e/checkout-action@v1

View File

@@ -19,7 +19,7 @@ jobs:
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
@@ -32,7 +32,7 @@ jobs:
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=serde,mint,android-native-activity cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=serde,mint,android-native-activity
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v6 uses: actions/configure-pages@v5
- name: Fix permissions - name: Fix permissions
run: | run: |
@@ -41,10 +41,10 @@ jobs:
done done
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v5 uses: actions/upload-pages-artifact@v3
with: with:
path: target/doc path: target/doc
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v5 uses: actions/deploy-pages@v4

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 The changelog can also be viewed [on docs.rs][docs_rs] or [on the current
master docs][master_docs]. 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 [docs_rs]: https://docs.rs/winit/latest/winit/changelog/index.html
[master_docs]: https://rust-windowing.github.io/winit/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 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. 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 ### Submitting your work and handling review
All patches have to be sent on Github as [pull requests][prs]. To simplify your 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 life during review it's recommended to check the "give contributors write access
to the branch" checkbox. 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 #### Handling review
During the review process certain events could require an action from your side, During the review process certain events could require an action from your side,

View File

@@ -1,97 +1,364 @@
[workspace] [package]
default-members = ["winit"] authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
members = ["dpi", "winit*"] categories = ["gui"]
resolver = "2" 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] [package.metadata.docs.rs]
edition = "2024" features = [
license = "Apache-2.0" "serde",
repository = "https://github.com/rust-windowing/winit" "mint",
rust-version = "1.85" # Enabled to get docs to compile
version = "0.31.0-beta.2" "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] # Features are documented in either `lib.rs` or under `winit::platform`.
# Workspace dependencies. [features]
# `winit` has no version here to allow using it in dev deps for docs. android-game-activity = ["android-activity/game-activity"]
winit = { path = "winit" } android-native-activity = ["android-activity/native-activity"]
winit-android = { version = "=0.31.0-beta.2", path = "winit-android" } default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
winit-appkit = { version = "=0.31.0-beta.2", path = "winit-appkit" } mint = ["dpi/mint"]
winit-common = { version = "=0.31.0-beta.2", path = "winit-common" } serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde", "bitflags/serde"]
winit-core = { version = "=0.31.0-beta.2", path = "winit-core" } wayland = [
winit-orbital = { version = "=0.31.0-beta.2", path = "winit-orbital" } "wayland-client",
winit-uikit = { version = "=0.31.0-beta.2", path = "winit-uikit" } "wayland-backend",
winit-wayland = { version = "=0.31.0-beta.2", path = "winit-wayland", default-features = false } "wayland-protocols",
winit-web = { version = "=0.31.0-beta.2", path = "winit-web" } "wayland-protocols-plasma",
winit-win32 = { version = "=0.31.0-beta.2", path = "winit-win32" } "sctk",
winit-x11 = { version = "=0.31.0-beta.2", path = "winit-x11" } "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. [build-dependencies]
bitflags = "2"
cfg_aliases = "0.2.1" cfg_aliases = "0.2.1"
[dependencies]
bitflags = "2"
cursor-icon = "1.1.0" cursor-icon = "1.1.0"
dpi = { version = "0.1.2", path = "dpi" } dpi = { version = "0.1.1", path = "dpi" }
keyboard-types = "0.8.0"
mint = "0.5.6"
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] } 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" smol_str = "0.3"
tracing = { version = "0.1.40", default-features = false } tracing = { version = "0.1.40", default-features = false }
# Dev dependencies. [dev-dependencies]
image = { version = "0.25.0", default-features = false } image = { version = "0.25.0", default-features = false, features = ["png"] }
softbuffer = { version = "0.4.8", default-features = false, features = [ 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",
"x11-dlopen", "x11-dlopen",
"wayland", "wayland",
"wayland-dlopen", "wayland-dlopen",
] } ] }
tracing-subscriber = "0.3.18"
# Android dependencies. # Android
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0" android-activity = "0.6.0"
ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false } ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
# Apple dependencies. # AppKit or UIKit
block2 = "0.6.1" [target.'cfg(target_vendor = "apple")'.dependencies]
dispatch2 = { version = "0.3.0", default-features = false, features = ["std", "objc2"] } block2 = "0.5.1"
objc2 = { version = "0.6.1", features = ["relax-sign-encoding"] } core-foundation = "0.9.3"
objc2-app-kit = { version = "0.3.2", default-features = false } objc2 = "0.5.2"
objc2-core-foundation = { version = "0.3.2", default-features = false }
objc2-core-graphics = { version = "0.3.2", default-features = false }
objc2-core-video = { version = "0.3.2", default-features = false }
objc2-foundation = { version = "0.3.2", default-features = false }
objc2-ui-kit = { version = "0.3.2", default-features = false }
# 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" unicode-segmentation = "1.7.1"
windows-sys = "0.61" 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. # Linux
bytemuck = { version = "1.13.1", default-features = false } [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
calloop = "0.14.3" ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
foldhash = { version = "0.2.0", default-features = false, features = ["std"] } bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.13.0"
libc = "0.2.64" libc = "0.2.64"
memmap2 = "0.9.0" memmap2 = { version = "0.9.0", optional = true }
percent-encoding = "2.0" percent-encoding = { version = "2.0", optional = true }
rustix = { version = "1.0.7", default-features = false } rustix = { version = "0.38.4", default-features = false, features = [
x11-dl = "2.19.1" "std",
x11rb = { version = "0.13.0", default-features = false } "system",
"thread",
"process",
] }
sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [
"calloop",
], optional = true }
sctk-adwaita = { version = "0.10.1", default-features = false, optional = true }
wayland-backend = { version = "0.3.5", default-features = false, features = [
"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" xkbcommon-dl = "0.4.2"
# Orbital dependencies. # Orbital
libredox = "0.1.12" [target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false } orbclient = { version = "0.3.47", default-features = false }
redox_event = { package = "redox_event", version = "0.4.5" } redox_syscall = "0.5.7"
# Web dependencies. # Web
atomic-waker = "1" [target.'cfg(target_family = "wasm")'.dependencies]
concurrent-queue = { version = "2", default-features = false }
console_error_panic_hook = "0.1"
js-sys = "0.3.70" js-sys = "0.3.70"
pin-project = "1" pin-project = "1"
tracing-web = "0.1"
wasm-bindgen = "0.2.93" wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43" wasm-bindgen-futures = "0.4.43"
wasm-bindgen-test = "0.3"
web-time = "1" 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 ```toml
[dependencies] [dependencies]
winit = "0.31.0-beta.2" winit = "0.30.5"
``` ```
## [Documentation](https://docs.rs/winit) ## [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 show something on the window you need to use the platform-specific getters provided by winit, or
another library. another library.
## CONTRIBUTING
For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md).
## MSRV Policy ## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.85**. 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. 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 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 ### Platform-specific usage
Check out the [`winit::platform`](https://docs.rs/winit/latest/winit/platform/index.html) module for 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,9 +1,7 @@
use cfg_aliases::cfg_aliases; use cfg_aliases::cfg_aliases;
// Only relevant for examples and Winit, our usage of println! is fine here.
#[allow(clippy::disallowed_macros)]
fn main() { fn main() {
// Dummy invocation to enable change-tracking in build scripts. // The script doesn't depend on our code.
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
// Setup cfg aliases. // Setup cfg aliases.

View File

@@ -1,27 +1,16 @@
# Using allow-invalid because this is platform-specific code
disallowed-macros = [
{ path = "std::print", reason = "works badly on web", replacement = "tracing::info" },
{ path = "std::println", reason = "works badly on web", replacement = "tracing::info" },
{ path = "std::eprint", reason = "works badly on web", replacement = "tracing::error" },
{ path = "std::eprintln", reason = "works badly on web", replacement = "tracing::error" },
{ path = "std::dbg", reason = "leftover debugging aid, remove it or use tracing" },
]
disallowed-methods = [ disallowed-methods = [
{ allow-invalid = true, path = "objc2_app_kit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." }, { 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" }, { 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" }, { 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" }, { 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" }, { 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" }, { 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" }, { 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" }, { 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" }, { 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" }, { path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::MouseEvent::buttons", reason = "Use `backend::event::cursor_buttons()` to avoid wrong conversions" }, { path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::MouseEvent::button", reason = "Use `backend::event::cursor_button()` to avoid wrong conversions" }, { path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::PointerEvent::pointer_type", reason = "Use `WebPointerType` to emit warnings" }, { path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" }, { path = "web_sys::window", reason = "is not available in every context" },
{ 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" },
] ]

View File

@@ -1,25 +1,27 @@
# https://embarkstudios.github.io/cargo-deny # https://embarkstudios.github.io/cargo-deny
# cargo install cargo-deny # cargo install cargo-deny
# cargo update && cargo deny check # cargo update && cargo deny --target aarch64-apple-ios check
# Note: running just `cargo deny check` without a `--target` will result in
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
[graph] [graph]
all-features = true all-features = true
exclude-dev = true exclude-dev = true
targets = [ targets = [
"aarch64-apple-darwin", { triple = "aarch64-apple-darwin" },
"aarch64-apple-ios", { triple = "aarch64-apple-ios" },
"aarch64-linux-android", { triple = "aarch64-linux-android" },
"i686-pc-windows-gnu", { triple = "i686-pc-windows-gnu" },
"i686-pc-windows-msvc", { triple = "i686-pc-windows-msvc" },
"i686-unknown-linux-gnu", { triple = "i686-unknown-linux-gnu" },
{ triple = "wasm32-unknown-unknown", features = [ { triple = "wasm32-unknown-unknown", features = [
"atomics", "atomics",
] }, ] },
"x86_64-apple-darwin", { triple = "x86_64-apple-darwin" },
"x86_64-apple-ios", { triple = "x86_64-apple-ios" },
"x86_64-pc-windows-gnu", { triple = "x86_64-pc-windows-gnu" },
"x86_64-pc-windows-msvc", { triple = "x86_64-pc-windows-msvc" },
"x86_64-unknown-linux-gnu", { triple = "x86_64-unknown-linux-gnu" },
"x86_64-unknown-redox", { triple = "x86_64-unknown-redox" },
] ]
[licenses] [licenses]
@@ -30,32 +32,41 @@ allow = [
"ISC", # https://tldrlegal.com/license/isc-license "ISC", # https://tldrlegal.com/license/isc-license
"MIT", # https://tldrlegal.com/license/mit-license "MIT", # https://tldrlegal.com/license/mit-license
"Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html "Unicode-3.0", # https://spdx.org/licenses/Unicode-3.0.html
"Zlib", # https://spdx.org/licenses/Zlib.html
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/
] ]
confidence-threshold = 1.0 confidence-threshold = 1.0
private = { ignore = true } private = { ignore = true }
[bans] [bans]
multiple-versions = "deny" multiple-versions = "deny"
skip = [ skip = [{ crate = "bitflags@1", reason = "the ecosystem is in the process of migrating" }]
{ crate = "jni-sys@0.3", reason = "uses the semver trick to depend on v0.4, but `ndk` hasn't been updated to v0.4 yet" }, wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
{ crate = "thiserror@1.0", reason = "dep of `ndk` crate, yet to be updated" },
{ crate = "thiserror-impl@1.0", reason = "dep of `thiserror`" },
{ crate = "objc2@0.5", reason = "used by crossfont" },
{ crate = "objc2-foundation@0.2", reason = "used by crossfont" },
]
skip-tree = [
{ crate = "windows-sys", reason = "foundational but bumps fairly often, nothing we can do about it not having a shared version" },
]
[bans.build] [bans.build]
bypass = [
{ crate = "android-activity", allow-globs = ["android-games-sdk/import-games-sdk.sh"] },
{ crate = "freetype-sys", allow-globs = ["freetype2/*"] },
# `crossfont` still depends (partially transitively) on `winapi`.
{ crate = "winapi-i686-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
{ crate = "winapi-x86_64-pc-windows-gnu", allow-globs = ["lib/lib*.a"] },
]
include-archives = true include-archives = true
interpreted = "deny" interpreted = "deny"
[[bans.build.bypass]]
allow = [
{ path = "generate-bindings.sh", checksum = "268ec23248218d779e33853cdc60e2985e70214ff004716cd734270de1f6b561" },
]
crate = "android-activity"
[[bans.build.bypass]]
allow-globs = ["freetype2/*"]
crate = "freetype-sys"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_i686_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_i686_msvc"
[[bans.build.bypass]]
allow-globs = ["lib/*.a"]
crate = "windows_x86_64_gnu"
[[bans.build.bypass]]
allow-globs = ["lib/*.lib"]
crate = "windows_x86_64_msvc"

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 ## Unreleased
## 0.1.2
- Added `Insets`, `LogicalInsets` and `PhysicalInsets` types. - Added `Insets`, `LogicalInsets` and `PhysicalInsets` types.
- Make `no_std` compatible. If you use this functionality, DPI's license has changed.
## 0.1.1 ## 0.1.1

View File

@@ -3,22 +3,16 @@ categories = ["gui"]
description = "Types for handling UI scaling" description = "Types for handling UI scaling"
edition.workspace = true edition.workspace = true
keywords = ["DPI", "HiDPI", "scale-factor"] keywords = ["DPI", "HiDPI", "scale-factor"]
# N.B. This is "AND", because of the imported libm code. license.workspace = true
license = "Apache-2.0 AND MIT"
name = "dpi" name = "dpi"
repository.workspace = true repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
version = "0.1.2" version = "0.1.1"
[features] [features]
default = ["std"]
mint = ["dep:mint"] mint = ["dep:mint"]
serde = ["dep:serde"] serde = ["dep:serde"]
# Access mathematical functions using the standard library implementations
std = []
[dependencies] [dependencies]
mint = { workspace = true, optional = true } mint = { workspace = true, optional = true }
serde = { 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). //! * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
//! * `mint`: Enables mint (math interoperability standard types) conversions. //! * `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) //! [points]: https://en.wikipedia.org/wiki/Point_(typography)
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
#![cfg_attr(docsrs, feature(doc_cfg), doc(auto_cfg(hide(doc, docsrs))))] #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
#![cfg_attr(feature = "std", forbid(unsafe_code))] #![forbid(unsafe_code)]
#![no_std]
#[cfg(not(feature = "std"))]
mod libm;
#[cfg(any(feature = "std", test))]
extern crate std;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -84,18 +72,36 @@ pub trait Pixel: Copy + Into<f64> {
} }
} }
macro_rules! pixel_int_impl { impl Pixel for u8 {
($($t:ty),*) => {$(
impl Pixel for $t {
fn from_f64(f: f64) -> Self { fn from_f64(f: f64) -> Self {
round(f) as $t f.round() as u8
} }
} }
)*} impl Pixel for u16 {
fn from_f64(f: f64) -> Self {
f.round() as u16
}
}
impl Pixel for u32 {
fn from_f64(f: f64) -> Self {
f.round() as u32
}
}
impl Pixel for i8 {
fn from_f64(f: f64) -> Self {
f.round() as i8
}
}
impl Pixel for i16 {
fn from_f64(f: f64) -> Self {
f.round() as i16
}
}
impl Pixel for i32 {
fn from_f64(f: f64) -> Self {
f.round() as i32
}
} }
pixel_int_impl!(u8, u16, u32, i8, i16, i32);
impl Pixel for f32 { impl Pixel for f32 {
fn from_f64(f: f64) -> Self { fn from_f64(f: f64) -> Self {
f as f32 f as f32
@@ -107,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`. /// 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 /// All functions that take a scale factor assert that this will return `true`. If you're sourcing
@@ -360,48 +357,6 @@ impl<P: Pixel> From<LogicalUnit<P>> for PixelUnit {
} }
} }
macro_rules! vec2_from_impls {
($t:ident, $a:ident, $b:ident, $mint_ty:ident) => {
impl<P: Pixel, X: Pixel> From<(X, X)> for $t<P> {
fn from(($a, $b): (X, X)) -> Self {
Self::new($a.cast(), $b.cast())
}
}
impl<P: Pixel, X: Pixel> From<$t<P>> for (X, X) {
fn from(p: $t<P>) -> Self {
(p.$a.cast(), p.$b.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for $t<P> {
fn from([$a, $b]: [X; 2]) -> Self {
Self::new($a.cast(), $b.cast())
}
}
impl<P: Pixel, X: Pixel> From<$t<P>> for [X; 2] {
fn from(p: $t<P>) -> Self {
[p.$a.cast(), p.$b.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::$mint_ty<P>> for $t<P> {
fn from(p: mint::$mint_ty<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<$t<P>> for mint::$mint_ty<P> {
fn from(p: $t<P>) -> Self {
Self { x: p.$a, y: p.$b }
}
}
};
}
/// A position represented in logical pixels. /// A position represented in logical pixels.
/// ///
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the /// The position is stored as floats, so please be careful. Casting floats to integers truncates the
@@ -444,7 +399,43 @@ impl<P: Pixel> LogicalPosition<P> {
} }
} }
vec2_from_impls!(LogicalPosition, x, y, Point2); impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
fn from((x, y): (X, X)) -> LogicalPosition<P> {
LogicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for (X, X) {
fn from(p: LogicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
LogicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalPosition<P>> for [X; 2] {
fn from(p: LogicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for LogicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalPosition<P>> for mint::Point2<P> {
fn from(p: LogicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.y }
}
}
/// A position represented in physical pixels. /// A position represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
@@ -484,7 +475,43 @@ impl<P: Pixel> PhysicalPosition<P> {
} }
} }
vec2_from_impls!(PhysicalPosition, x, y, Point2); impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
PhysicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for (X, X) {
fn from(p: PhysicalPosition<P>) -> (X, X) {
(p.x.cast(), p.y.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
PhysicalPosition::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalPosition<P>> for [X; 2] {
fn from(p: PhysicalPosition<P>) -> [X; 2] {
[p.x.cast(), p.y.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Point2<P>> for PhysicalPosition<P> {
fn from(p: mint::Point2<P>) -> Self {
Self::new(p.x, p.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalPosition<P>> for mint::Point2<P> {
fn from(p: PhysicalPosition<P>) -> Self {
mint::Point2 { x: p.x, y: p.y }
}
}
/// A size represented in logical pixels. /// A size represented in logical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
@@ -524,7 +551,43 @@ impl<P: Pixel> LogicalSize<P> {
} }
} }
vec2_from_impls!(LogicalSize, width, height, Vector2); impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
fn from((x, y): (X, X)) -> LogicalSize<P> {
LogicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for (X, X) {
fn from(s: LogicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
fn from([x, y]: [X; 2]) -> LogicalSize<P> {
LogicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<LogicalSize<P>> for [X; 2] {
fn from(s: LogicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for LogicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<LogicalSize<P>> for mint::Vector2<P> {
fn from(s: LogicalSize<P>) -> Self {
mint::Vector2 { x: s.width, y: s.height }
}
}
/// A size represented in physical pixels. /// A size represented in physical pixels.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
@@ -561,7 +624,43 @@ impl<P: Pixel> PhysicalSize<P> {
} }
} }
vec2_from_impls!(PhysicalSize, width, height, Vector2); impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
fn from((x, y): (X, X)) -> PhysicalSize<P> {
PhysicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for (X, X) {
fn from(s: PhysicalSize<P>) -> (X, X) {
(s.width.cast(), s.height.cast())
}
}
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
fn from([x, y]: [X; 2]) -> PhysicalSize<P> {
PhysicalSize::new(x.cast(), y.cast())
}
}
impl<P: Pixel, X: Pixel> From<PhysicalSize<P>> for [X; 2] {
fn from(s: PhysicalSize<P>) -> [X; 2] {
[s.width.cast(), s.height.cast()]
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<mint::Vector2<P>> for PhysicalSize<P> {
fn from(v: mint::Vector2<P>) -> Self {
Self::new(v.x, v.y)
}
}
#[cfg(feature = "mint")]
impl<P: Pixel> From<PhysicalSize<P>> for mint::Vector2<P> {
fn from(s: PhysicalSize<P>) -> Self {
mint::Vector2 { x: s.width, y: s.height }
}
}
/// A size that's either physical or logical. /// A size that's either physical or logical.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
@@ -1171,20 +1270,20 @@ mod tests {
// Eat coverage for the Debug impls et al // Eat coverage for the Debug impls et al
#[test] #[test]
fn ensure_attrs_do_not_panic() { 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()); 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()); 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()); 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()); HashSet::new().insert(PhysicalSize::<u32>::default());
let _ = std::format!("{:?}", Size::Physical((1, 2).into()).clone()); let _ = format!("{:?}", Size::Physical((1, 2).into()).clone());
let _ = std::format!("{:?}", Position::Physical((1, 2).into()).clone()); let _ = format!("{:?}", Position::Physical((1, 2).into()).clone());
} }
#[test] #[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)
}

View File

@@ -1 +0,0 @@
winit/examples

View File

@@ -3,36 +3,20 @@
fn main() -> Result<(), impl std::error::Error> { fn main() -> Result<(), impl std::error::Error> {
use std::collections::HashMap; use std::collections::HashMap;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize, Position}; use winit::dpi::{LogicalPosition, LogicalSize, Position};
use winit::event::{ElementState, KeyEvent, WindowEvent}; use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::raw_window_handle::HasRawWindowHandle; use winit::raw_window_handle::HasRawWindowHandle;
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[derive(Debug)] #[derive(Default)]
struct WindowData {
surface: Surface<OwnedDisplayHandle, Box<dyn Window>>,
color: u32,
}
impl WindowData {
fn new(context: &Context<OwnedDisplayHandle>, window: Box<dyn Window>, color: u32) -> Self {
let surface = Surface::new(context, window).unwrap();
Self { surface, color }
}
}
#[derive(Debug)]
struct Application { struct Application {
context: Context<OwnedDisplayHandle>,
parent_window_id: Option<WindowId>, parent_window_id: Option<WindowId>,
windows: HashMap<WindowId, WindowData>, windows: HashMap<WindowId, Box<dyn Window>>,
} }
impl ApplicationHandler for Application { impl ApplicationHandler for Application {
@@ -42,10 +26,11 @@ fn main() -> Result<(), impl std::error::Error> {
.with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_surface_size(LogicalSize::new(640.0f32, 480.0f32)); .with_surface_size(LogicalSize::new(640.0f32, 480.0f32));
let window = event_loop.create_window(attributes).unwrap(); let window = event_loop.create_window(attributes).unwrap();
info!("Parent window id: {:?})", window.id());
println!("Parent window id: {:?})", window.id());
self.parent_window_id = Some(window.id()); self.parent_window_id = Some(window.id());
self.windows.insert(window.id(), WindowData::new(&self.context, window, 0xffbbbbbb)); self.windows.insert(window.id(), window);
} }
fn window_event( fn window_event(
@@ -60,41 +45,26 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.exit(); event_loop.exit();
}, },
WindowEvent::PointerEntered { device_id: _, .. } => { WindowEvent::PointerEntered { device_id: _, .. } => {
// On x11, log when the cursor entered in a window even if the child window // On x11, println when the cursor entered in a window even if the child window
// is created by some key inputs. // is created by some key inputs.
// the child windows are always placed at (0, 0) with size (200, 200) in the // the child windows are always placed at (0, 0) with size (200, 200) in the
// parent window, so we also can see this log when we move // parent window, so we also can see this log when we move
// the cursor around (200, 200) in parent window. // the cursor around (200, 200) in parent window.
info!("cursor entered in the window {window_id:?}"); println!("cursor entered in the window {window_id:?}");
}, },
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
event: KeyEvent { state: ElementState::Pressed, .. }, 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 parent_window = self.windows.get(&self.parent_window_id.unwrap()).unwrap();
let child_window = spawn_child_window( let child_window = spawn_child_window(parent_window.as_ref(), event_loop);
parent_window.surface.window().as_ref(),
event_loop,
child_index,
);
let child_id = child_window.id(); let child_id = child_window.id();
info!("Child window created with id: {child_id:?}"); println!("Child window created with id: {child_id:?}");
self.windows.insert( self.windows.insert(child_id, child_window);
child_id,
WindowData::new(&self.context, child_window, child_color),
);
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
if let Some(window) = self.windows.get_mut(&window_id) { if let Some(window) = self.windows.get(&window_id) {
if window_id == self.parent_window_id.unwrap() { fill::fill_window(window.as_ref());
fill::fill(&mut window.surface);
} else {
fill::fill_with_color(&mut window.surface, window.color);
}
} }
}, },
_ => (), _ => (),
@@ -105,20 +75,12 @@ fn main() -> Result<(), impl std::error::Error> {
fn spawn_child_window( fn spawn_child_window(
parent: &dyn Window, parent: &dyn Window,
event_loop: &dyn ActiveEventLoop, event_loop: &dyn ActiveEventLoop,
child_count: usize,
) -> Box<dyn Window> { ) -> Box<dyn Window> {
let parent = parent.raw_window_handle().unwrap(); 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() let mut window_attributes = WindowAttributes::default()
.with_title("child window") .with_title("child window")
.with_surface_size(LogicalSize::new(128.0f32, 96.0)) .with_surface_size(LogicalSize::new(200.0f32, 200.0f32))
.with_position(Position::Logical(LogicalPosition::new(x, y))) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
.with_visible(true); .with_visible(true);
// `with_parent_window` is unsafe. Parent window must be a valid window. // `with_parent_window` is unsafe. Parent window must be a valid window.
window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) }; window_attributes = unsafe { window_attributes.with_parent_window(Some(parent)) };
@@ -127,8 +89,7 @@ fn main() -> Result<(), impl std::error::Error> {
} }
let event_loop = EventLoop::new().unwrap(); let event_loop = EventLoop::new().unwrap();
let context = Context::new(event_loop.owned_display_handle()).unwrap(); event_loop.run_app(Application::default())
event_loop.run_app(Application { context, parent_window_id: None, windows: HashMap::new() })
} }
#[cfg(not(any(x11_platform, macos_platform, windows_platform)))] #[cfg(not(any(x11_platform, macos_platform, windows_platform)))]

View File

@@ -4,13 +4,12 @@ use std::thread;
#[cfg(not(web_platform))] #[cfg(not(web_platform))]
use std::time; use std::time;
use softbuffer::{Context, Surface}; use ::tracing::{info, warn};
use tracing::{info, warn};
#[cfg(web_platform)] #[cfg(web_platform)]
use web_time as time; use web_time as time;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent}; use winit::event::{ElementState, KeyEvent, StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::{Key, NamedKey}; use winit::keyboard::{Key, NamedKey};
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
@@ -47,13 +46,13 @@ fn main() -> Result<(), impl std::error::Error> {
event_loop.run_app(ControlFlowDemo::default()) event_loop.run_app(ControlFlowDemo::default())
} }
#[derive(Default, Debug)] #[derive(Default)]
struct ControlFlowDemo { struct ControlFlowDemo {
mode: Mode, mode: Mode,
request_redraw: bool, request_redraw: bool,
wait_cancelled: bool, wait_cancelled: bool,
close_requested: bool, close_requested: bool,
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>, window: Option<Box<dyn Window>>,
} }
impl ApplicationHandler for ControlFlowDemo { impl ApplicationHandler for ControlFlowDemo {
@@ -70,10 +69,7 @@ impl ApplicationHandler for ControlFlowDemo {
let window_attributes = WindowAttributes::default().with_title( let window_attributes = WindowAttributes::default().with_title(
"Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.", "Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.",
); );
let window = event_loop.create_window(window_attributes).unwrap(); self.window = Some(event_loop.create_window(window_attributes).unwrap());
let context = Context::new(event_loop.owned_display_handle()).unwrap();
let surface = Surface::new(&context, window).unwrap();
self.surface = Some(surface);
} }
fn window_event( fn window_event(
@@ -116,9 +112,9 @@ impl ApplicationHandler for ControlFlowDemo {
_ => (), _ => (),
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
let surface = self.surface.as_mut().unwrap(); let window = self.window.as_ref().unwrap();
surface.window().pre_present_notify(); window.pre_present_notify();
fill::fill(surface); fill::fill_window(window.as_ref());
}, },
_ => (), _ => (),
} }
@@ -126,7 +122,7 @@ impl ApplicationHandler for ControlFlowDemo {
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
if self.request_redraw && !self.wait_cancelled && !self.close_requested { if self.request_redraw && !self.wait_cancelled && !self.close_requested {
self.surface.as_ref().unwrap().window().request_redraw(); self.window.as_ref().unwrap().request_redraw();
} }
match self.mode { match self.mode {

View File

Before

Width:  |  Height:  |  Size: 159 B

After

Width:  |  Height:  |  Size: 159 B

View File

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 129 B

View File

Before

Width:  |  Height:  |  Size: 229 B

After

Width:  |  Height:  |  Size: 229 B

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -7,31 +7,24 @@ fn main() -> std::process::ExitCode {
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::pump_events::{EventLoopExtPumpEvents, PumpStatus}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[path = "util/tracing.rs"]
mod tracing;
#[derive(Default, Debug)] #[derive(Default)]
struct PumpDemo { struct PumpDemo {
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>, window: Option<Box<dyn Window>>,
} }
impl ApplicationHandler for PumpDemo { impl ApplicationHandler for PumpDemo {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default().with_title("A fantastic window!"); let window_attributes = WindowAttributes::default().with_title("A fantastic window!");
let window = event_loop.create_window(window_attributes).unwrap(); self.window = Some(event_loop.create_window(window_attributes).unwrap());
let context = Context::new(event_loop.owned_display_handle()).unwrap();
self.surface = Some(Surface::new(&context, window).unwrap());
} }
fn window_event( fn window_event(
@@ -40,28 +33,28 @@ fn main() -> std::process::ExitCode {
_window_id: WindowId, _window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
info!("{event:?}"); println!("{event:?}");
let surface = match self.surface.as_mut() { let window = match self.window.as_ref() {
Some(surface) => surface, Some(window) => window,
None => return, None => return,
}; };
match event { match event {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
fill::fill(surface); fill::fill_window(window.as_ref());
surface.window().request_redraw(); window.request_redraw();
}, },
_ => (), _ => (),
} }
} }
} }
tracing::init();
let mut event_loop = EventLoop::new().unwrap(); let mut event_loop = EventLoop::new().unwrap();
tracing_subscriber::fmt::init();
let mut app = PumpDemo::default(); let mut app = PumpDemo::default();
loop { loop {
@@ -76,12 +69,12 @@ fn main() -> std::process::ExitCode {
// //
// Since `pump_events` doesn't block it will be important to // Since `pump_events` doesn't block it will be important to
// throttle the loop in the app somehow. // throttle the loop in the app somehow.
info!("Update()"); println!("Update()");
sleep(Duration::from_millis(16)); sleep(Duration::from_millis(16));
} }
} }
#[cfg(any(ios_platform, web_platform, orbital_platform))] #[cfg(any(ios_platform, web_platform, orbital_platform))]
fn main() { fn main() {
panic!("This platform doesn't support pump_events.") println!("This platform doesn't support pump_events.");
} }

View File

@@ -1,47 +1,40 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
// Limit this example to only compatible platforms. // Limit this example to only compatible platforms.
#[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform, orbital_platform))] #[cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform,))]
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::time::Duration; use std::time::Duration;
use softbuffer::{Context, Surface};
use tracing::info;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[path = "util/tracing.rs"]
mod tracing;
#[derive(Debug)] #[derive(Default)]
struct App { struct App {
context: Context<OwnedDisplayHandle>,
idx: usize, idx: usize,
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>,
window_id: Option<WindowId>, window_id: Option<WindowId>,
window: Option<Box<dyn Window>>,
} }
impl ApplicationHandler for App { impl ApplicationHandler for App {
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
if let Some(surface) = self.surface.as_ref() { if let Some(window) = self.window.as_ref() {
surface.window().request_redraw(); window.request_redraw();
} }
} }
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = WindowAttributes::default() let window_attributes = WindowAttributes::default()
.with_title(format!("Fantastic window number {}!", self.idx)) .with_title("Fantastic window number one!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0)); .with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0));
let window = event_loop.create_window(window_attributes).unwrap(); let window = event_loop.create_window(window_attributes).unwrap();
self.window_id = Some(window.id()); self.window_id = Some(window.id());
self.window = Some(window);
let surface = Surface::new(&self.context, window).unwrap();
self.surface = Some(surface);
} }
fn window_event( fn window_event(
@@ -51,54 +44,56 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
event: WindowEvent, event: WindowEvent,
) { ) {
if event == WindowEvent::Destroyed && self.window_id == Some(window_id) { if event == WindowEvent::Destroyed && self.window_id == Some(window_id) {
info!("Window {} Destroyed", self.idx); println!(
"--------------------------------------------------------- Window {} Destroyed",
self.idx
);
self.window_id = None; self.window_id = None;
event_loop.exit(); event_loop.exit();
return; return;
} }
let Some(surface) = self.surface.as_mut() else { let window = match self.window.as_mut() {
return; Some(window) => window,
None => return,
}; };
match event { match event {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
info!("Window {} CloseRequested", self.idx); println!(
self.surface = None; "--------------------------------------------------------- Window {} \
CloseRequested",
self.idx
);
fill::cleanup_window(window.as_ref());
self.window = None;
}, },
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
fill::fill(surface); fill::fill_window(window.as_ref());
}, },
_ => (), _ => (),
} }
} }
} }
tracing::init(); tracing_subscriber::fmt::init();
let mut event_loop = EventLoop::new().unwrap(); let mut event_loop = EventLoop::new().unwrap();
let context = Context::new(event_loop.owned_display_handle()).unwrap(); let mut app = App { idx: 1, ..Default::default() };
let mut app = App { context, idx: 1, surface: None, window_id: None };
event_loop.run_app_on_demand(&mut app)?; event_loop.run_app_on_demand(&mut app)?;
info!("Finished first loop"); println!("--------------------------------------------------------- Finished first loop");
info!("Waiting 5 seconds"); println!("--------------------------------------------------------- Waiting 5 seconds");
std::thread::sleep(Duration::from_secs(5)); std::thread::sleep(Duration::from_secs(5));
app.idx += 1; app.idx += 1;
event_loop.run_app_on_demand(&mut app)?; event_loop.run_app_on_demand(&mut app)?;
info!("Finished second loop"); println!("--------------------------------------------------------- Finished second loop");
Ok(()) Ok(())
} }
#[cfg(not(any( #[cfg(not(any(windows_platform, macos_platform, x11_platform, wayland_platform,)))]
windows_platform,
macos_platform,
x11_platform,
wayland_platform,
orbital_platform
)))]
fn main() { fn main() {
panic!("This example is not supported on this platform") println!("This example is not supported on this platform");
} }

119
examples/util/fill.rs Normal file
View File

@@ -0,0 +1,119 @@
//! Fill the window buffer with a solid color.
//!
//! Launching a window without drawing to it has unpredictable results varying from platform to
//! platform. In order to have well-defined examples, this module provides an easy way to
//! fill the window buffer with a solid color.
//!
//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could
//! also be used to fill the window buffer, but they are more complicated to use.
#[allow(unused_imports)]
pub use platform::cleanup_window;
pub use platform::fill_window;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod platform {
use std::cell::RefCell;
use std::collections::HashMap;
use std::mem;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
use softbuffer::{Context, Surface};
use winit::window::{Window, WindowId};
thread_local! {
// NOTE: You should never do things like that, create context and drop it before
// you drop the event loop. We do this for brevity to not blow up examples. We use
// ManuallyDrop to prevent destructors from running.
//
// A static, thread-local map of graphics contexts to open windows.
static GC: ManuallyDrop<RefCell<Option<GraphicsContext>>> = const { ManuallyDrop::new(RefCell::new(None)) };
}
/// The graphics context used to draw to a window.
struct GraphicsContext {
/// The global softbuffer context.
context: RefCell<Context<&'static dyn Window>>,
/// The hash map of window IDs to surfaces.
surfaces: HashMap<WindowId, Surface<&'static dyn Window, &'static dyn Window>>,
}
impl GraphicsContext {
fn new(w: &dyn Window) -> Self {
Self {
context: RefCell::new(
Context::new(unsafe {
mem::transmute::<&'_ dyn Window, &'static dyn Window>(w)
})
.expect("Failed to create a softbuffer context"),
),
surfaces: HashMap::new(),
}
}
fn create_surface(
&mut self,
window: &dyn Window,
) -> &mut Surface<&'static dyn Window, &'static dyn Window> {
self.surfaces.entry(window.id()).or_insert_with(|| {
Surface::new(&self.context.borrow(), unsafe {
mem::transmute::<&'_ dyn Window, &'static dyn Window>(window)
})
.expect("Failed to create a softbuffer surface")
})
}
fn destroy_surface(&mut self, window: &dyn Window) {
self.surfaces.remove(&window.id());
}
}
pub fn fill_window(window: &dyn Window) {
GC.with(|gc| {
let size = window.surface_size();
let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
else {
return;
};
// Either get the last context used or create a new one.
let mut gc = gc.borrow_mut();
let surface =
gc.get_or_insert_with(|| GraphicsContext::new(window)).create_surface(window);
// Fill a buffer with a solid color.
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(DARK_GRAY);
buffer.present().expect("Failed to present the softbuffer buffer");
})
}
#[allow(dead_code)]
pub fn cleanup_window(window: &dyn Window) {
GC.with(|gc| {
let mut gc = gc.borrow_mut();
if let Some(context) = gc.as_mut() {
context.destroy_surface(window);
}
});
}
}
#[cfg(any(target_os = "android", target_os = "ios"))]
mod platform {
pub fn fill_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms.
}
#[allow(dead_code)]
pub fn cleanup_window(_window: &dyn winit::window::Window) {
// No-op on mobile platforms.
}
}

View File

@@ -23,6 +23,3 @@ pub fn init() {
) )
.init(); .init();
} }
#[allow(unused_imports)]
pub use ::tracing::*;

View File

@@ -1,48 +1,45 @@
//! Simple winit application. //! Simple winit application.
//!
//! Note that a real application accepting text input **should** support
//! the IME interface. See the `ime` example.
use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::fmt::Debug; use std::fmt::Debug;
#[cfg(not(android_platform))]
use std::num::NonZeroU32; use std::num::NonZeroU32;
use std::sync::Arc;
use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::mpsc::{self, Receiver, Sender};
#[cfg(not(web_platform))] use std::sync::Arc;
use std::time::Instant;
use std::{fmt, mem}; use std::{fmt, mem};
use ::tracing::{error, info};
use cursor_icon::CursorIcon; use cursor_icon::CursorIcon;
#[cfg(not(android_platform))]
use rwh_06::{DisplayHandle, HasDisplayHandle};
#[cfg(not(android_platform))]
use softbuffer::{Context, Surface}; use softbuffer::{Context, Surface};
use tracing::{error, info};
#[cfg(web_platform)]
use web_time::Instant;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::cursor::{Cursor, CustomCursor, CustomCursorSource};
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::error::RequestError; use winit::error::RequestError;
use winit::event::{DeviceEvent, DeviceId, MouseButton, MouseScrollDelta, WindowEvent}; use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::icon::{Icon, RgbaIcon};
use winit::keyboard::{Key, ModifiersState}; use winit::keyboard::{Key, ModifiersState};
use winit::monitor::Fullscreen;
#[cfg(macos_platform)] #[cfg(macos_platform)]
use winit::platform::macos::{OptionAsAlt, WindowAttributesMacOS, WindowExtMacOS}; use winit::platform::macos::{
ApplicationHandlerExtMacOS, OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS,
};
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
use winit::platform::startup_notify::{self, EventLoopExtStartupNotify, WindowExtStartupNotify}; use winit::platform::startup_notify::{
#[cfg(wayland_platform)] self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
use winit::platform::wayland::{ActiveEventLoopExtWayland, WindowAttributesWayland}; };
#[cfg(web_platform)] #[cfg(web_platform)]
use winit::platform::web::{ActiveEventLoopExtWeb, WindowAttributesWeb}; use winit::platform::web::{ActiveEventLoopExtWeb, CustomCursorExtWeb, WindowAttributesExtWeb};
#[cfg(x11_platform)] #[cfg(x11_platform)]
use winit::platform::x11::{ActiveEventLoopExtX11, WindowAttributesX11}; use winit::platform::x11::WindowAttributesExtX11;
use winit::window::{CursorGrabMode, ResizeDirection, Theme, Window, WindowAttributes, WindowId}; use winit::window::{
use winit_core::application::macos::ApplicationHandlerExtMacOS; Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection,
Theme, Window, WindowAttributes, WindowId,
};
#[path = "util/tracing.rs"] #[path = "util/tracing.rs"]
mod tracing_init; mod tracing;
/// The amount of points to around the window for drag resize direction calculations. /// The amount of points to around the window for drag resize direction calculations.
const BORDER_SIZE: f64 = 20.; const BORDER_SIZE: f64 = 20.;
@@ -51,7 +48,7 @@ fn main() -> Result<(), Box<dyn Error>> {
#[cfg(web_platform)] #[cfg(web_platform)]
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
tracing_init::init(); tracing::init();
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
let (sender, receiver) = mpsc::channel(); let (sender, receiver) = mpsc::channel();
@@ -90,12 +87,22 @@ struct Application {
/// Drawing context. /// Drawing context.
/// ///
/// With OpenGL it could be EGLDisplay. /// With OpenGL it could be EGLDisplay.
context: Context<OwnedDisplayHandle>, #[cfg(not(android_platform))]
context: Option<Context<DisplayHandle<'static>>>,
} }
impl Application { impl Application {
fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self { fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
let context = Context::new(event_loop.owned_display_handle()).unwrap(); // SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
#[cfg(not(android_platform))]
let context = Some(
Context::new(unsafe {
std::mem::transmute::<DisplayHandle<'_>, DisplayHandle<'static>>(
event_loop.display_handle().unwrap(),
)
})
.unwrap(),
);
// You'll have to choose an icon size at your own discretion. On X11, the desired size // You'll have to choose an icon size at your own discretion. On X11, the desired size
// varies by WM, and on Windows, you still have to account for screen scaling. Here // varies by WM, and on Windows, you still have to account for screen scaling. Here
@@ -113,7 +120,15 @@ impl Application {
.into_iter() .into_iter()
.collect(); .collect();
Self { receiver, sender, context, custom_cursors, icon, windows: Default::default() } Self {
receiver,
sender,
#[cfg(not(android_platform))]
context,
custom_cursors,
icon,
windows: Default::default(),
}
} }
fn create_window( fn create_window(
@@ -129,29 +144,43 @@ impl Application {
.with_transparent(true) .with_transparent(true)
.with_window_icon(Some(self.icon.clone())); .with_window_icon(Some(self.icon.clone()));
#[cfg(x11_platform)] #[cfg(any(x11_platform, wayland_platform))]
if event_loop.is_x11() { if let Some(token) = event_loop.read_token_from_env() {
window_attributes = window_attributes startup_notify::reset_activation_token_env();
.with_platform_attributes(Box::new(window_attributes_x11(event_loop)?)); info!("Using token {:?} to activate a window", token);
window_attributes = window_attributes.with_activation_token(token);
} }
#[cfg(wayland_platform)] #[cfg(x11_platform)]
if event_loop.is_wayland() { match std::env::var("X11_VISUAL_ID") {
window_attributes = window_attributes Ok(visual_id_str) => {
.with_platform_attributes(Box::new(window_attributes_wayland(event_loop))); info!("Using X11 visual id {visual_id_str}");
let visual_id = visual_id_str.parse()?;
window_attributes = window_attributes.with_x11_visual(visual_id);
},
Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"),
}
#[cfg(x11_platform)]
match std::env::var("X11_SCREEN_ID") {
Ok(screen_id_str) => {
info!("Placing the window on X11 screen {screen_id_str}");
let screen_id = screen_id_str.parse()?;
window_attributes = window_attributes.with_x11_screen(screen_id);
},
Err(_) => info!(
"Set the X11_SCREEN_ID env variable to place the window on non-default screen"
),
} }
#[cfg(macos_platform)] #[cfg(macos_platform)]
if let Some(tab_id) = _tab_id { if let Some(tab_id) = _tab_id {
let window_attributes_macos = window_attributes = window_attributes.with_tabbing_identifier(&tab_id);
Box::new(WindowAttributesMacOS::default().with_tabbing_identifier(&tab_id));
window_attributes = window_attributes.with_platform_attributes(window_attributes_macos);
} }
#[cfg(web_platform)] #[cfg(web_platform)]
{ {
window_attributes = window_attributes = window_attributes.with_append(true);
window_attributes.with_platform_attributes(Box::new(window_attributes_web()));
} }
let window = event_loop.create_window(window_attributes)?; let window = event_loop.create_window(window_attributes)?;
@@ -178,15 +207,6 @@ impl Application {
Action::DumpMonitors => self.dump_monitors(_event_loop), Action::DumpMonitors => self.dump_monitors(_event_loop),
Action::Message => { Action::Message => {
info!("User wake up"); info!("User wake up");
for (id, window) in self.windows.iter() {
if window.emit_surface_size {
let size = window.window.surface_size();
info!(
"Window {id:?} has physical surface size {}x{}",
size.width, size.height
);
}
}
}, },
_ => unreachable!("Tried to execute invalid action without `WindowId`"), _ => unreachable!("Tried to execute invalid action without `WindowId`"),
} }
@@ -226,13 +246,8 @@ impl Application {
Action::ToggleSimpleFullscreen => { Action::ToggleSimpleFullscreen => {
window.window.set_simple_fullscreen(!window.window.simple_fullscreen()); window.window.set_simple_fullscreen(!window.window.simple_fullscreen());
}, },
#[cfg(macos_platform)]
Action::ToggleBorderlessGame => {
let current = window.window.is_borderless_game();
window.window.set_borderless_game(!current);
info!("Borderless game: {}", !current);
},
Action::ToggleMaximize => window.toggle_maximize(), Action::ToggleMaximize => window.toggle_maximize(),
Action::ToggleImeInput => window.toggle_ime(),
Action::Minimize => window.minimize(), Action::Minimize => window.minimize(),
Action::NextCursor => window.next_cursor(), Action::NextCursor => window.next_cursor(),
Action::NextCustomCursor => { Action::NextCustomCursor => {
@@ -298,16 +313,6 @@ impl Application {
self.sender.send(Action::Message).unwrap(); self.sender.send(Action::Message).unwrap();
event_loop.create_proxy().wake_up(); event_loop.create_proxy().wake_up();
}, },
Action::ToggleAnimatedFillColor => {
window.animated_fill_color = !window.animated_fill_color;
},
Action::ToggleContinuousRedraw => {
window.continuous_redraw = !window.continuous_redraw;
window.window.request_redraw();
},
Action::EmitSurfaceSize => {
window.toggle_emit_surface_size();
},
} }
} }
@@ -435,9 +440,6 @@ impl ApplicationHandler for Application {
if let Err(err) = window.draw() { if let Err(err) = window.draw() {
error!("Error drawing window: {err}"); error!("Error drawing window: {err}");
} }
if window.continuous_redraw {
window.window.request_redraw();
}
}, },
WindowEvent::Occluded(occluded) => { WindowEvent::Occluded(occluded) => {
window.set_occluded(occluded); window.set_occluded(occluded);
@@ -463,7 +465,7 @@ impl ApplicationHandler for Application {
// Dispatch actions only on press. // Dispatch actions only on press.
if event.state.is_pressed() { if event.state.is_pressed() {
let action = if let Key::Character(ch) = event.key_without_modifiers.as_ref() { let action = if let Key::Character(ch) = event.logical_key.as_ref() {
Self::process_key_binding(&ch.to_uppercase(), &mods) Self::process_key_binding(&ch.to_uppercase(), &mods)
} else { } else {
None None
@@ -479,9 +481,8 @@ impl ApplicationHandler for Application {
let mods = window.modifiers; let mods = window.modifiers;
if let Some(action) = state if let Some(action) = state
.is_pressed() .is_pressed()
.then(|| button.mouse_button()) .then(|| Self::process_mouse_binding(button.mouse_button(), &mods))
.flatten() .flatten()
.and_then(|button| Self::process_mouse_binding(button, &mods))
{ {
self.handle_action_with_window(event_loop, window_id, action); self.handle_action_with_window(event_loop, window_id, action);
} }
@@ -503,6 +504,16 @@ impl ApplicationHandler for Application {
} }
} }
}, },
WindowEvent::Ime(event) => match event {
Ime::Enabled => info!("IME enabled for Window={window_id:?}"),
Ime::Preedit(text, caret_pos) => {
info!("Preedit: {}, with caret at {:?}", text, caret_pos);
},
Ime::Commit(text) => {
info!("Committed: {}", text);
},
Ime::Disabled => info!("IME disabled for Window={window_id:?}"),
},
WindowEvent::PinchGesture { delta, .. } => { WindowEvent::PinchGesture { delta, .. } => {
window.zoom += delta; window.zoom += delta;
let zoom = window.zoom; let zoom = window.zoom;
@@ -529,7 +540,14 @@ impl ApplicationHandler for Application {
WindowEvent::DoubleTapGesture { .. } => { WindowEvent::DoubleTapGesture { .. } => {
info!("Smart zoom"); info!("Smart zoom");
}, },
_ => (), WindowEvent::TouchpadPressure { .. }
| WindowEvent::HoveredFileCancelled
| WindowEvent::KeyboardInput { .. }
| WindowEvent::PointerEntered { .. }
| WindowEvent::DroppedFile(_)
| WindowEvent::HoveredFile(_)
| WindowEvent::Destroyed
| WindowEvent::Moved(_) => (),
} }
} }
@@ -559,17 +577,19 @@ impl ApplicationHandler for Application {
} }
} }
#[cfg(not(android_platform))]
fn exiting(&mut self, _event_loop: &dyn ActiveEventLoop) {
// We must drop the context here.
self.context = None;
}
#[cfg(target_os = "macos")]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> { fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
Some(self) Some(self)
} }
} }
impl Drop for Application { #[cfg(target_os = "macos")]
fn drop(&mut self) {
info!("Application exited");
}
}
impl ApplicationHandlerExtMacOS for Application { impl ApplicationHandlerExtMacOS for Application {
fn standard_key_binding( fn standard_key_binding(
&mut self, &mut self,
@@ -583,20 +603,17 @@ impl ApplicationHandlerExtMacOS for Application {
/// State of the window. /// State of the window.
struct WindowState { struct WindowState {
/// IME input.
ime: bool,
/// Render surface. /// Render surface.
surface: Surface<OwnedDisplayHandle, Arc<dyn Window>>, ///
/// NOTE: This surface must be dropped before the `Window`.
#[cfg(not(android_platform))]
surface: Surface<DisplayHandle<'static>, Arc<dyn Window>>,
/// The actual winit Window. /// The actual winit Window.
window: Arc<dyn Window>, window: Arc<dyn Window>,
/// The window theme we're drawing with. /// The window theme we're drawing with.
theme: Theme, theme: Theme,
/// Fill the window with animated color
animated_fill_color: bool,
/// The application start time. Used for color fill animation
start_time: Instant,
/// Redraw continuously
continuous_redraw: bool,
/// Periodically emit the surface size
emit_surface_size: bool,
/// Cursor position over the window. /// Cursor position over the window.
cursor_position: Option<PhysicalPosition<f64>>, cursor_position: Option<PhysicalPosition<f64>>,
/// Window modifiers state. /// Window modifiers state.
@@ -625,13 +642,20 @@ impl WindowState {
fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> { fn new(app: &Application, window: Box<dyn Window>) -> Result<Self, Box<dyn Error>> {
let window: Arc<dyn Window> = Arc::from(window); let window: Arc<dyn Window> = Arc::from(window);
let surface = Surface::new(&app.context, Arc::clone(&window))?; // SAFETY: the surface is dropped before the `window` which provided it with handle, thus
// it doesn't outlive it.
#[cfg(not(android_platform))]
let surface = Surface::new(app.context.as_ref().unwrap(), Arc::clone(&window))?;
let theme = window.theme().unwrap_or(Theme::Dark); let theme = window.theme().unwrap_or(Theme::Dark);
info!("Theme: {theme:?}"); info!("Theme: {theme:?}");
let named_idx = 0; let named_idx = 0;
window.set_cursor(CURSORS[named_idx].into()); window.set_cursor(CURSORS[named_idx].into());
// Allow IME out of the box.
let ime = true;
window.set_ime_allowed(ime);
let size = window.surface_size(); let size = window.surface_size();
let mut state = Self { let mut state = Self {
#[cfg(macos_platform)] #[cfg(macos_platform)]
@@ -639,13 +663,11 @@ impl WindowState {
custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1, custom_idx: app.custom_cursors.as_ref().map(Vec::len).unwrap_or(1) - 1,
cursor_grab: CursorGrabMode::None, cursor_grab: CursorGrabMode::None,
named_idx, named_idx,
#[cfg(not(android_platform))]
surface, surface,
window, window,
theme, theme,
animated_fill_color: false, ime,
continuous_redraw: false,
emit_surface_size: false,
start_time: Instant::now(),
cursor_position: Default::default(), cursor_position: Default::default(),
cursor_hidden: Default::default(), cursor_hidden: Default::default(),
modifiers: Default::default(), modifiers: Default::default(),
@@ -659,12 +681,23 @@ impl WindowState {
Ok(state) Ok(state)
} }
pub fn toggle_ime(&mut self) {
self.ime = !self.ime;
self.window.set_ime_allowed(self.ime);
if let Some(position) = self.ime.then_some(self.cursor_position).flatten() {
self.window.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into());
}
}
pub fn minimize(&mut self) { pub fn minimize(&mut self) {
self.window.set_minimized(true); self.window.set_minimized(true);
} }
pub fn cursor_moved(&mut self, position: PhysicalPosition<f64>) { pub fn cursor_moved(&mut self, position: PhysicalPosition<f64>) {
self.cursor_position = Some(position); self.cursor_position = Some(position);
if self.ime {
self.window.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into());
}
} }
pub fn cursor_left(&mut self) { pub fn cursor_left(&mut self) {
@@ -716,10 +749,6 @@ impl WindowState {
self.window.set_fullscreen(fullscreen); self.window.set_fullscreen(fullscreen);
} }
fn toggle_emit_surface_size(&mut self) {
self.emit_surface_size = !self.emit_surface_size;
}
/// Cycle through the grab modes ignoring errors. /// Cycle through the grab modes ignoring errors.
fn cycle_cursor_grab(&mut self) { fn cycle_cursor_grab(&mut self) {
self.cursor_grab = match self.cursor_grab { self.cursor_grab = match self.cursor_grab {
@@ -805,7 +834,7 @@ impl WindowState {
custom_cursors[1].clone(), custom_cursors[1].clone(),
event_loop.create_custom_cursor(url_custom_cursor())?, event_loop.create_custom_cursor(url_custom_cursor())?,
]; ];
let cursor = CustomCursorSource::from_animation(Duration::from_secs(3), cursors).unwrap(); let cursor = CustomCursor::from_animation(Duration::from_secs(3), cursors).unwrap();
let cursor = event_loop.create_custom_cursor(cursor)?; let cursor = event_loop.create_custom_cursor(cursor)?;
self.window.set_cursor(cursor.into()); self.window.set_cursor(cursor.into());
@@ -816,12 +845,15 @@ impl WindowState {
/// Resize the surface to the new size. /// Resize the surface to the new size.
fn resize(&mut self, size: PhysicalSize<u32>) { fn resize(&mut self, size: PhysicalSize<u32>) {
info!("Surface resized to {size:?}"); info!("Surface resized to {size:?}");
let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) { #[cfg(not(android_platform))]
{
let (width, height) = match (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
(Some(width), Some(height)) => (width, height), (Some(width), Some(height)) => (width, height),
_ => return, _ => return,
}; };
self.surface.resize(width, height).expect("failed to resize inner buffer"); self.surface.resize(width, height).expect("failed to resize inner buffer");
}
self.window.request_redraw(); self.window.request_redraw();
} }
@@ -906,6 +938,7 @@ impl WindowState {
} }
/// Draw the window contents. /// Draw the window contents.
#[cfg(not(android_platform))]
fn draw(&mut self) -> Result<(), Box<dyn Error>> { fn draw(&mut self) -> Result<(), Box<dyn Error>> {
if self.occluded { if self.occluded {
info!("Skipping drawing occluded window={:?}", self.window.id()); info!("Skipping drawing occluded window={:?}", self.window.id());
@@ -914,15 +947,6 @@ impl WindowState {
let mut buffer = self.surface.buffer_mut()?; let mut buffer = self.surface.buffer_mut()?;
if self.animated_fill_color {
// Fill the entire buffer with a single color.
let time = self.start_time.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;
buffer.fill(color);
} else {
// Draw a different color inside the safe area // Draw a different color inside the safe area
let surface_size = self.window.surface_size(); let surface_size = self.window.surface_size();
let insets = self.window.safe_area(); let insets = self.window.safe_area();
@@ -948,7 +972,6 @@ impl WindowState {
} }
} }
} }
}
// Present the buffer // Present the buffer
self.window.pre_present_notify(); self.window.pre_present_notify();
@@ -956,6 +979,12 @@ impl WindowState {
Ok(()) Ok(())
} }
#[cfg(android_platform)]
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
info!("Drawing but without rendering...");
Ok(())
}
} }
struct Binding<T: Eq> { struct Binding<T: Eq> {
@@ -980,13 +1009,12 @@ enum Action {
ToggleCursorVisibility, ToggleCursorVisibility,
CreateNewWindow, CreateNewWindow,
ToggleResizeIncrements, ToggleResizeIncrements,
ToggleImeInput,
ToggleDecorations, ToggleDecorations,
ToggleResizable, ToggleResizable,
ToggleFullscreen, ToggleFullscreen,
#[cfg(macos_platform)] #[cfg(macos_platform)]
ToggleSimpleFullscreen, ToggleSimpleFullscreen,
#[cfg(macos_platform)]
ToggleBorderlessGame,
ToggleMaximize, ToggleMaximize,
Minimize, Minimize,
NextCursor, NextCursor,
@@ -1008,9 +1036,6 @@ enum Action {
RequestResize, RequestResize,
DumpMonitors, DumpMonitors,
Message, Message,
ToggleAnimatedFillColor,
ToggleContinuousRedraw,
EmitSurfaceSize,
} }
impl Action { impl Action {
@@ -1019,13 +1044,12 @@ impl Action {
Action::CloseWindow => "Close window", Action::CloseWindow => "Close window",
Action::ToggleCursorVisibility => "Hide cursor", Action::ToggleCursorVisibility => "Hide cursor",
Action::CreateNewWindow => "Create new window", Action::CreateNewWindow => "Create new window",
Action::ToggleImeInput => "Toggle IME input",
Action::ToggleDecorations => "Toggle decorations", Action::ToggleDecorations => "Toggle decorations",
Action::ToggleResizable => "Toggle window resizable state", Action::ToggleResizable => "Toggle window resizable state",
Action::ToggleFullscreen => "Toggle fullscreen", Action::ToggleFullscreen => "Toggle fullscreen",
#[cfg(macos_platform)] #[cfg(macos_platform)]
Action::ToggleSimpleFullscreen => "Toggle simple fullscreen", Action::ToggleSimpleFullscreen => "Toggle simple fullscreen",
#[cfg(macos_platform)]
Action::ToggleBorderlessGame => "Toggle borderless game mode",
Action::ToggleMaximize => "Maximize", Action::ToggleMaximize => "Maximize",
Action::Minimize => "Minimize", Action::Minimize => "Minimize",
Action::ToggleResizeIncrements => "Use resize increments when resizing window", Action::ToggleResizeIncrements => "Use resize increments when resizing window",
@@ -1056,9 +1080,6 @@ impl Action {
information" information"
}, },
Action::Message => "Prints a message through a user wake up", Action::Message => "Prints a message through a user wake up",
Action::ToggleAnimatedFillColor => "Toggle animated fill color",
Action::ToggleContinuousRedraw => "Toggle continuous redraw",
Action::EmitSurfaceSize => "Periodically print the surface size",
} }
} }
} }
@@ -1074,7 +1095,7 @@ fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
let samples = img.into_flat_samples(); let samples = img.into_flat_samples();
let (_, w, h) = samples.extents(); let (_, w, h) = samples.extents();
let (w, h) = (w as u16, h as u16); let (w, h) = (w as u16, h as u16);
CustomCursorSource::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap() CustomCursor::from_rgba(samples.samples, w, h, w / 2, h / 2).unwrap()
} }
#[cfg(web_platform)] #[cfg(web_platform)]
@@ -1083,14 +1104,11 @@ fn url_custom_cursor() -> CustomCursorSource {
static URL_COUNTER: AtomicU64 = AtomicU64::new(0); static URL_COUNTER: AtomicU64 = AtomicU64::new(0);
CustomCursorSource::Url { CustomCursor::from_url(
hotspot_x: 64, format!("https://picsum.photos/128?random={}", URL_COUNTER.fetch_add(1, Ordering::Relaxed)),
hotspot_y: 64, 64,
url: format!( 64,
"https://picsum.photos/128?random={}", )
URL_COUNTER.fetch_add(1, Ordering::Relaxed)
),
}
} }
fn load_icon(bytes: &[u8]) -> Icon { fn load_icon(bytes: &[u8]) -> Icon {
@@ -1100,23 +1118,14 @@ fn load_icon(bytes: &[u8]) -> Icon {
let rgba = image.into_raw(); let rgba = image.into_raw();
(rgba, width, height) (rgba, width, height)
}; };
RgbaIcon::new(icon_rgba, icon_width, icon_height).expect("Failed to open icon").into() Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
} }
fn modifiers_to_string(mods: ModifiersState) -> String { fn modifiers_to_string(mods: ModifiersState) -> String {
let mut mods_line = String::new(); let mut mods_line = String::new();
// Always add + since it's printed as a part of the bindings. // Always add + since it's printed as a part of the bindings.
for (modifier, desc) in [ for (modifier, desc) in [
( (ModifiersState::SUPER, "Super+"),
ModifiersState::META,
if cfg!(target_os = "windows") {
"Win+"
} else if cfg!(target_vendor = "apple") {
"Cmd+"
} else {
"Super+"
},
),
(ModifiersState::ALT, "Alt+"), (ModifiersState::ALT, "Alt+"),
(ModifiersState::CONTROL, "Ctrl+"), (ModifiersState::CONTROL, "Ctrl+"),
(ModifiersState::SHIFT, "Shift+"), (ModifiersState::SHIFT, "Shift+"),
@@ -1130,70 +1139,15 @@ fn modifiers_to_string(mods: ModifiersState) -> String {
mods_line mods_line
} }
fn mouse_button_to_string(button: MouseButton) -> Cow<'static, str> { fn mouse_button_to_string(button: MouseButton) -> &'static str {
match button { match button {
MouseButton::Left => "LMB", MouseButton::Left => "LMB",
MouseButton::Right => "RMB", MouseButton::Right => "RMB",
MouseButton::Middle => "MMB", MouseButton::Middle => "MMB",
MouseButton::Back => "Back", MouseButton::Back => "Back",
MouseButton::Forward => "Forward", MouseButton::Forward => "Forward",
other => return format!("button {}", other as u8 + 1).into(), MouseButton::Other(_) => "",
} }
.into()
}
#[cfg(web_platform)]
fn window_attributes_web() -> WindowAttributesWeb {
WindowAttributesWeb::default().with_append(true)
}
#[cfg(wayland_platform)]
fn window_attributes_wayland(event_loop: &dyn ActiveEventLoop) -> WindowAttributesWayland {
let mut window_attributes = WindowAttributesWayland::default();
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
info!("Using token {:?} to activate a window", token);
window_attributes = window_attributes.with_activation_token(token);
}
window_attributes
}
#[cfg(x11_platform)]
fn window_attributes_x11(
event_loop: &dyn ActiveEventLoop,
) -> Result<WindowAttributesX11, Box<dyn Error>> {
let mut window_attributes = WindowAttributesX11::default();
#[cfg(any(x11_platform, wayland_platform))]
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
info!("Using token {:?} to activate a window", token);
window_attributes = window_attributes.with_activation_token(token);
}
match std::env::var("X11_VISUAL_ID") {
Ok(visual_id_str) => {
info!("Using X11 visual id {visual_id_str}");
let visual_id = visual_id_str.parse()?;
window_attributes = window_attributes.with_x11_visual(visual_id);
},
Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"),
}
match std::env::var("X11_SCREEN_ID") {
Ok(screen_id_str) => {
info!("Placing the window on X11 screen {screen_id_str}");
let screen_id = screen_id_str.parse()?;
window_attributes = window_attributes.with_x11_screen(screen_id);
},
Err(_) => {
info!("Set the X11_SCREEN_ID env variable to place the window on non-default screen")
},
}
Ok(window_attributes)
} }
/// Cursor list to cycle through. /// Cursor list to cycle through.
@@ -1237,16 +1191,15 @@ const CURSORS: &[CursorIcon] = &[
const KEY_BINDINGS: &[Binding<&'static str>] = &[ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow), Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow),
Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp), Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp),
Binding::new("F", ModifiersState::SHIFT, Action::ToggleAnimatedFillColor),
Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen), Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen),
#[cfg(macos_platform)] #[cfg(macos_platform)]
Binding::new("F", ModifiersState::ALT, Action::ToggleSimpleFullscreen), Binding::new("F", ModifiersState::ALT, Action::ToggleSimpleFullscreen),
Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations), Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations),
Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput),
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab), Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements), Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements),
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable), Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
Binding::new("R", ModifiersState::ALT, Action::RequestResize), Binding::new("R", ModifiersState::ALT, Action::RequestResize),
Binding::new("R", ModifiersState::SHIFT, Action::ToggleContinuousRedraw),
// M. // M.
Binding::new("M", ModifiersState::CONTROL.union(ModifiersState::ALT), Action::DumpMonitors), Binding::new("M", ModifiersState::CONTROL.union(ModifiersState::ALT), Action::DumpMonitors),
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize), Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
@@ -1271,15 +1224,12 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility), Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
// K. // K.
Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)), Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)),
Binding::new("K", ModifiersState::META, Action::SetTheme(Some(Theme::Light))), Binding::new("K", ModifiersState::SUPER, Action::SetTheme(Some(Theme::Light))),
Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))), Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))),
#[cfg(macos_platform)] #[cfg(macos_platform)]
Binding::new("T", ModifiersState::META, Action::CreateNewTab), Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
#[cfg(macos_platform)] #[cfg(macos_platform)]
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt), Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
#[cfg(macos_platform)]
Binding::new("B", ModifiersState::CONTROL, Action::ToggleBorderlessGame),
Binding::new("S", ModifiersState::ALT, Action::EmitSurfaceSize),
Binding::new("S", ModifiersState::CONTROL, Action::Message), Binding::new("S", ModifiersState::CONTROL, Action::Message),
]; ];

View File

@@ -3,37 +3,28 @@ use std::error::Error;
#[cfg(x11_platform)] #[cfg(x11_platform)]
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
use softbuffer::{Context, Surface};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle}; use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::x11::WindowAttributesX11; use winit::platform::x11::WindowAttributesExtX11;
use winit::window::{Window, WindowAttributes, WindowId}; use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"] #[path = "util/fill.rs"]
mod fill; mod fill;
#[path = "util/tracing.rs"]
mod tracing;
#[derive(Debug)]
pub struct XEmbedDemo { pub struct XEmbedDemo {
parent_window_id: u32, parent_window_id: u32,
surface: Option<Surface<OwnedDisplayHandle, Box<dyn Window>>>, window: Option<Box<dyn Window>>,
} }
impl ApplicationHandler for XEmbedDemo { impl ApplicationHandler for XEmbedDemo {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { 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_title("An embedded window!")
.with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0)); .with_surface_size(winit::dpi::LogicalSize::new(128.0, 128.0))
let x11_attrs = .with_embed_parent_window(self.parent_window_id);
WindowAttributesX11::default().with_embed_parent_window(self.parent_window_id);
window_attributes = window_attributes.with_platform_attributes(Box::new(x11_attrs));
let window = event_loop.create_window(window_attributes).unwrap(); self.window = Some(event_loop.create_window(window_attributes).unwrap());
let context = Context::new(event_loop.owned_display_handle()).unwrap();
let surface = Surface::new(&context, window).unwrap();
self.surface = Some(surface);
} }
fn window_event( fn window_event(
@@ -42,19 +33,19 @@ fn main() -> Result<(), Box<dyn Error>> {
_window_id: WindowId, _window_id: WindowId,
event: WindowEvent, event: WindowEvent,
) { ) {
let window = self.window.as_ref().unwrap();
match event { match event {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
let surface = self.surface.as_mut().unwrap(); window.pre_present_notify();
surface.window().pre_present_notify(); fill::fill_window(window.as_ref());
fill::fill(surface);
}, },
_ => (), _ => (),
} }
} }
fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) { fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) {
self.surface.as_ref().unwrap().window().request_redraw(); self.window.as_ref().unwrap().request_redraw();
} }
} }
@@ -64,13 +55,14 @@ fn main() -> Result<(), Box<dyn Error>> {
.ok_or("Expected a 32-bit X11 window ID as the first argument.")? .ok_or("Expected a 32-bit X11 window ID as the first argument.")?
.parse::<u32>()?; .parse::<u32>()?;
tracing::init(); tracing_subscriber::fmt::init();
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
Ok(event_loop.run_app(XEmbedDemo { parent_window_id, surface: None })?) Ok(event_loop.run_app(XEmbedDemo { parent_window_id, window: None })?)
} }
#[cfg(not(x11_platform))] #[cfg(not(x11_platform))]
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
panic!("This example is only supported on X11 platforms.") println!("This example is only supported on X11 platforms.");
Ok(())
} }

View File

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

View File

@@ -1,21 +1,44 @@
## 0.31.0-beta.2 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
The sections should follow the order `Added`, `Changed`, `Deprecated`,
`Removed`, and `Fixed`.
Platform specific changed should be added to the end of the section and grouped
by platform name. Common API additions should have `, implemented` at the end
for platforms where the API was initially implemented. See the following example
on how to add them:
```md
### Added ### Added
- Add `EventLoopExtRegister::register_app` for being explicit about how the event loop runs on Web. - Add `Window::turbo()`, implemented on X11, Wayland, and Web.
- Add `EventLoopExtNeverReturn::run_app_never_return` for being explicit about how the event loop runs on iOS. - On X11, add `Window::some_rare_api`.
- On X11, add `Window::even_more_rare_api`.
- On Wayland, add `Window::common_api`.
- On Windows, add `Window::some_rare_api`.
```
### Changed When the change requires non-trivial amount of work for users to comply
with it, the migration guide should be added below the entry, like:
- On Web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller. ```md
This requires passing a `'static` application to ensure that the application state will live as long as necessary. - Deprecate `Window` creation outside of `EventLoop::run`
- On Web, the event loop can now always be re-created once it has finished running.
### Fixed This was done to simply migration in the future. Consider the
following code:
- Fixed panic when calling `Window::set_ime_allowed`. // Code snippet.
## 0.31.0-beta.1 To migrate it we should do X, Y, and then Z, for example:
// Code snippet.
```
The migration guide could reference other migration examples in the current
changelog entry.
## Unreleased
### Added ### Added
@@ -36,44 +59,32 @@
- Implement `Clone`, `Copy`, `Debug`, `Deserialize`, `Eq`, `Hash`, `Ord`, `PartialEq`, `PartialOrd` - Implement `Clone`, `Copy`, `Debug`, `Deserialize`, `Eq`, `Hash`, `Ord`, `PartialEq`, `PartialOrd`
and `Serialize` on many types. and `Serialize` on many types.
- Add `MonitorHandle::current_video_mode()`. - 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 `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. - 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. to use a larger style of titlebar.
- Add `WindowId::into_raw()` and `from_raw()`. - Add `WindowId::into_raw()` and `from_raw()`.
- Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId`, `primary` and `position` to all - Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId`, `primary` and `position` to all
pointer events as part of the pointer event overhaul. pointer events as part of the pointer event overhaul.
- Add `DeviceId::into_raw()` and `from_raw()`. - 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::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::safe_area`, which describes the area of the surface that is unobstructed.
- On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes. - 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`
- On Wayland, added `PanGesture`, `PinchGesture`, and `RotationGesture`
- Add `Window::request_ime_update` to atomically apply set of IME changes.
- Add `Ime::DeleteSurrounding` to let the input method delete text.
- Add more `ImePurpose` values.
- Add `ImeHints` to request particular IME behaviour.
- Add Pen input support on Wayland, Windows, and Web via new Pointer event.
### Changed ### Changed
- Change `ActiveEventLoop` and `Window` to be traits, and added `cast_ref`/`cast_mut`/`cast` - Change `ActiveEventLoop` to be a trait.
methods to extract the backend type from those. - Change `Window` to be a trait.
- `ActiveEventLoop::create_window` now returns `Box<dyn Window>`. - `ActiveEventLoop::create_window` now returns `Box<dyn Window>`.
- `ApplicationHandler` now uses `dyn ActiveEventLoop`. - `ApplicationHandler` now uses `dyn ActiveEventLoop`.
- On Web, let events wake up event loop immediately when using `ControlFlow::Poll`. - On Web, let events wake up event loop immediately when using `ControlFlow::Poll`.
- Bump MSRV from `1.70` to `1.85`. - Bump MSRV from `1.70` to `1.73`.
- Changed `ApplicationHandler::user_event` to `user_wake_up`, removing the - Changed `ApplicationHandler::user_event` to `user_wake_up`, removing the
generic user event. generic user event.
@@ -102,8 +113,10 @@
- Changed how `ModifiersState` is serialized by Serde. - Changed how `ModifiersState` is serialized by Serde.
- `VideoModeHandle::refresh_rate_millihertz()` and `bit_depth()` now return a `Option<NonZero*>`. - `VideoModeHandle::refresh_rate_millihertz()` and `bit_depth()` now return a `Option<NonZero*>`.
- `MonitorHandle::position()` now returns an `Option`. - `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. 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) - On X11, remove our dependency on libXcursor. (#3749)
- Renamed the following APIs to make it clearer that the sizes apply to the underlying surface: - Renamed the following APIs to make it clearer that the sizes apply to the underlying surface:
- `WindowEvent::Resized` to `SurfaceResized`. - `WindowEvent::Resized` to `SurfaceResized`.
@@ -152,44 +165,6 @@
- On macOS, no longer emit `Focused` upon window creation. - On macOS, no longer emit `Focused` upon window creation.
- On iOS, emit more events immediately, instead of queuing them. - On iOS, emit more events immediately, instead of queuing them.
- Update `smol_str` to version `0.3` - 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`.
- `Force::normalized()` now takes a `Option<ToolAngle>` to calculate the perpendicular force.
- On Windows, don't confine cursor to center of window when grabbed and hidden.
### Removed ### Removed
@@ -221,31 +196,23 @@
- Remove `WindowEvent::Touch` and `Touch` in favor of the new `PointerKind`, `PointerSource` and - Remove `WindowEvent::Touch` and `Touch` in favor of the new `PointerKind`, `PointerSource` and
`ButtonSource` as part of the new pointer event overhaul. `ButtonSource` as part of the new pointer event overhaul.
- Remove `Force::altitude_angle`. - Remove `Force::altitude_angle`.
- Remove `Window::inner_position`, use the new `Window::surface_position` instead. - Removed `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.
- Remove `Force::Calibrated::altitude_angle` in favor of `ToolAngle::altitude`.
### Fixed ### Fixed
- On Orbital, `MonitorHandle::name()` now returns `None` instead of a dummy name. - 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 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 audio volume keys.
- On macOS, fixed the scancode conversion for `IntlBackslash`. - On macOS, fixed the scancode conversion for `IntlBackslash`.
- On macOS, fixed redundant `SurfaceResized` event at window creation. - On macOS, fixed redundant `SurfaceResized` event at window creation.
- On macOS, don't panic on monitors with unknown bit-depths. - On macOS, fix crash when pressing Caps Lock in certain configurations.
- On macOS, fixed crash when closing the window on macOS 26+. - On iOS, fixed `MonitorHandle`'s `PartialEq` and `Hash` implementations.
- 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 Windows, avoid returning `SurfaceResized` with size zero when an application is minimized. Let `Window::surface_size` return the pre-minimization window size even while minimized.
- On Web, device events are emitted regardless of cursor type.
- On Wayland, `axis_value120` scroll events now generate `MouseScrollDelta::LineDelta`
- On X11, mouse scroll button events no longer cause duplicated `WindowEvent::MouseWheel` events.

View File

@@ -250,7 +250,7 @@
- On Web, fix some `WindowBuilder` methods doing nothing. - 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 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 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, 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, handle coalesced pointer events, which increases the resolution of pointer inputs.
- On Web, implement `Window::focus_window()`. - On Web, implement `Window::focus_window()`.

View File

@@ -1,117 +1,3 @@
## 0.30.13
### Added
- On Wayland, add `Window::set_resize_increments`.
### Fixed
- On macOS, fixed crash when dragging non-file content onto window.
- On X11, fix `set_hittest` not working on some window managers.
- On X11, fix debug mode overflow panic in `set_timestamp`.
- On macOS, fix crash in `set_marked_text` when native Pinyin IME sends out-of-bounds `selected_range`.
- On Windows, fix `WM_IME_SETCONTEXT` IME UI flag masking on `lParam`.
- On Android, populate `KeyEvent::text` and `KeyEvent::text_with_all_modifiers` via `Key::to_text()`
## 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 ## 0.30.5
### Added ### Added

View File

@@ -1,16 +1,13 @@
use core::fmt; use core::fmt;
use std::error::Error; use std::error::Error;
use std::hash::Hash; use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
#[doc(inline)] use cursor_icon::CursorIcon;
pub use cursor_icon::CursorIcon;
use crate::as_any::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; pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4; const PIXEL_SIZE: usize = 4;
@@ -51,23 +48,22 @@ impl From<CustomCursor> for Cursor {
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use winit_core::event_loop::ActiveEventLoop; /// # use winit::event_loop::ActiveEventLoop;
/// # use winit_core::window::Window; /// # use winit::window::Window;
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) { /// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) {
/// use winit_core::cursor::CustomCursorSource; /// use winit::window::CustomCursor;
/// ///
/// let w = 10; /// let w = 10;
/// let h = 10; /// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize]; /// let rgba = vec![255; (w * h * 4) as usize];
/// ///
/// #[cfg(not(target_family = "wasm"))] /// #[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")] /// #[cfg(target_family = "wasm")]
/// let source = CustomCursorSource::Url { /// let source = {
/// url: String::from("http://localhost:3000/cursor.png"), /// use winit::platform::web::CustomCursorExtWeb;
/// hotspot_x: 0, /// CustomCursor::from_url(String::from("http://localhost:3000/cursor.png"), 0, 0)
/// hotspot_y: 0,
/// }; /// };
/// ///
/// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) { /// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) {
@@ -75,96 +71,50 @@ impl From<CustomCursor> for Cursor {
/// } /// }
/// # } /// # }
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CustomCursor(pub Arc<dyn CustomCursorProvider>); pub struct CustomCursor {
/// Platforms should make sure this is cheap to clone.
pub trait CustomCursorProvider: AsAny + fmt::Debug + Send + Sync { pub(crate) inner: PlatformCustomCursor,
/// Whether a cursor was backed by animation.
fn is_animated(&self) -> bool;
} }
impl PartialEq for CustomCursor { impl CustomCursor {
fn eq(&self, other: &Self) -> bool { /// Creates a new cursor from an rgba buffer.
Arc::ptr_eq(&self.0, &other.0) ///
} /// 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();
impl Eq for CustomCursor {} Ok(CustomCursorSource {
inner: PlatformCustomCursorSource::from_rgba(
impl Hash for CustomCursor { rgba.into(),
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { width,
Arc::as_ptr(&self.0).hash(state); height,
hotspot_x,
hotspot_y,
)?,
})
} }
} }
impl Deref for CustomCursor {
type Target = dyn CustomCursorProvider;
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl_dyn_casting!(CustomCursorProvider);
/// Source for [`CustomCursor`]. /// Source for [`CustomCursor`].
/// ///
/// See [`CustomCursor`] for more details. /// See [`CustomCursor`] for more details.
#[derive(Debug, Clone, Eq, Hash, PartialEq)] #[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum CustomCursorSource { pub struct CustomCursorSource {
/// Cursor that is backed by RGBA image. // Some platforms don't support custom cursors.
/// #[allow(dead_code)]
/// See [CustomCursorSource::from_rgba] for more. pub(crate) inner: PlatformCustomCursorSource,
///
/// ## 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 },
} }
impl CustomCursorSource { /// An error produced when using [`CustomCursor::from_rgba`] with invalid arguments.
/// 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.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BadImage { pub enum BadImage {
@@ -214,29 +164,47 @@ impl fmt::Display for BadImage {
impl Error for BadImage {} impl Error for BadImage {}
/// An error produced when using [`CustomCursorSource::from_animation`] with invalid arguments. /// Platforms export this directly as `PlatformCustomCursorSource` if they need to only work with
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] /// images.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[allow(dead_code)]
pub enum BadAnimation { #[derive(Debug, Clone, Eq, Hash, PartialEq)]
/// Produced when no cursors were supplied. pub(crate) struct OnlyCursorImageSource(pub(crate) CursorImage);
Empty,
/// Produced when a supplied cursor is an animation.
Animation,
}
impl fmt::Display for BadAnimation { #[allow(dead_code)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { impl OnlyCursorImageSource {
match self { pub(crate) fn from_rgba(
Self::Empty => write!(f, "No cursors supplied"), rgba: Vec<u8>,
Self::Animation => write!(f, "A supplied cursor is an animation"), 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)] #[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct CursorImage { #[allow(dead_code)]
pub(crate) struct CursorImage {
pub(crate) rgba: Vec<u8>, pub(crate) rgba: Vec<u8>,
pub(crate) width: u16, pub(crate) width: u16,
pub(crate) height: u16, pub(crate) height: u16,
@@ -277,60 +245,22 @@ impl CursorImage {
Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y }) 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] { // Platforms that don't support cursors will export this as `PlatformCustomCursor`.
self.rgba.as_mut_slice() #[derive(Debug, Clone, Hash, PartialEq, Eq)]
} pub(crate) struct NoCustomCursor;
pub fn width(&self) -> u16 { #[allow(dead_code)]
self.width impl NoCustomCursor {
} pub(crate) fn from_rgba(
rgba: Vec<u8>,
pub fn height(&self) -> u16 { width: u16,
self.height height: u16,
} hotspot_x: u16,
hotspot_y: u16,
pub fn hotspot_x(&self) -> u16 { ) -> Result<Self, BadImage> {
self.hotspot_x CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y)?;
} Ok(Self)
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>,
}
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)
} }
} }

View File

@@ -19,13 +19,7 @@ pub enum EventLoopError {
impl fmt::Display for EventLoopError { impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::RecreationAttempt => { Self::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
write!(
f,
"EventLoop can't be recreated, only a single instance of it is supported (for \
cross-platform compatibility)"
)
},
Self::Os(err) => err.fmt(f), Self::Os(err) => err.fmt(f),
Self::ExitFailure(status) => write!(f, "Exit Failure: {status}"), Self::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
Self::NotSupported(err) => err.fmt(f), Self::NotSupported(err) => err.fmt(f),
@@ -35,7 +29,11 @@ impl fmt::Display for EventLoopError {
impl Error for EventLoopError { impl Error for EventLoopError {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::Os(err) = self { err.source() } else { None } if let Self::Os(err) = self {
err.source()
} else {
None
}
} }
} }
@@ -74,7 +72,11 @@ impl Display for RequestError {
} }
impl Error for RequestError { impl Error for RequestError {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::Os(err) = self { err.source() } else { None } if let Self::Os(err) = self {
err.source()
} else {
None
}
} }
} }
@@ -98,7 +100,7 @@ pub struct NotSupportedError {
} }
impl NotSupportedError { impl NotSupportedError {
pub fn new(reason: &'static str) -> Self { pub(crate) fn new(reason: &'static str) -> Self {
Self { reason } Self { reason }
} }
} }
@@ -119,7 +121,8 @@ pub struct OsError {
} }
impl OsError { impl OsError {
pub fn new( #[allow(dead_code)]
pub(crate) fn new(
line: u32, line: u32,
file: &'static str, file: &'static str,
error: impl Into<Box<dyn Error + Send + Sync + 'static>>, error: impl Into<Box<dyn Error + Send + Sync + 'static>>,
@@ -141,5 +144,7 @@ impl Error for OsError {
#[allow(unused_macros)] #[allow(unused_macros)]
macro_rules! os_error { macro_rules! os_error {
($error:expr) => {{ crate::error::OsError::new(line!(), file!(), $error) }}; ($error:expr) => {{
crate::error::OsError::new(line!(), file!(), $error)
}};
} }

File diff suppressed because it is too large Load Diff

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. //! 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 //! # Building windows
//! //!
//! Before you can create a [`Window`], you first need to build an [`EventLoop`]. This is done with //! 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 //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
//! [`DeviceEvent`]. //! [`DeviceEvent`].
//! //!
//! You can retrieve events by calling [`EventLoop::run_app()`]. This function will dispatch events //! You can retrieve events by calling [`EventLoop::run_app()`]. This function will
//! for every [`Window`] that was created with that particular [`EventLoop`]. //! 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 //! 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 //! 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 //! 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 #![cfg_attr(
//! reasons about why it's discouraged beyond compatibility reasons. 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 //! ```no_run
@@ -49,19 +112,10 @@
//! //!
//! impl ApplicationHandler for App { //! impl ApplicationHandler for App {
//! fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { //! 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()); //! self.window = Some(event_loop.create_window(WindowAttributes::default()).unwrap());
//! } //! }
//! //!
//! fn window_event( //! fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, id: WindowId, event: WindowEvent) {
//! &mut self,
//! event_loop: &dyn ActiveEventLoop,
//! id: WindowId,
//! event: WindowEvent,
//! ) {
//! // Called by `EventLoop::run_app` when a new event happens on the window.
//! match event { //! match event {
//! WindowEvent::CloseRequested => { //! WindowEvent::CloseRequested => {
//! println!("The close button was pressed; stopping"); //! println!("The close button was pressed; stopping");
@@ -82,18 +136,15 @@
//! // applications which do not always need to. Applications that redraw continuously //! // applications which do not always need to. Applications that redraw continuously
//! // can render here instead. //! // can render here instead.
//! self.window.as_ref().unwrap().request_redraw(); //! self.window.as_ref().unwrap().request_redraw();
//! }, //! }
//! _ => (), //! _ => (),
//! } //! }
//! } //! }
//! } //! }
//! //!
//! # // Intentionally use `fn main` for clarity //! # // Intentionally use `fn main` for clarity
//! fn main() -> Result<(), Box<dyn std::error::Error>> { //! fn main() {
//! // Create a new event loop. //! let event_loop = EventLoop::new().unwrap();
//! let event_loop = EventLoop::new()?;
//!
//! // Configure settings before launching.
//! //!
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't //! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
//! // dispatched any events. This is ideal for games and similar applications. //! // 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. //! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
//! event_loop.set_control_flow(ControlFlow::Wait); //! event_loop.set_control_flow(ControlFlow::Wait);
//! //!
//! // Launch and begin running the event loop. //! let mut app = App::default();
//! event_loop.run_app(App::default())?; //! event_loop.run_app(&mut app);
//!
//! Ok(())
//! } //! }
//! ``` //! ```
//! //!
@@ -262,13 +311,15 @@
//! [`Window`]: window::Window //! [`Window`]: window::Window
//! [`WindowId`]: window::WindowId //! [`WindowId`]: window::WindowId
//! [`WindowAttributes`]: window::WindowAttributes //! [`WindowAttributes`]: window::WindowAttributes
//! [window_new]: window::Window::new
//! [`create_window`]: event_loop::ActiveEventLoop::create_window //! [`create_window`]: event_loop::ActiveEventLoop::create_window
//! [`Window::id()`]: window::Window::id //! [`Window::id()`]: window::Window::id
//! [`WindowEvent`]: event::WindowEvent //! [`WindowEvent`]: event::WindowEvent
//! [`DeviceEvent`]: event::DeviceEvent //! [`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_window_handle`]: ./window/struct.Window.html#method.raw_window_handle
//! [`raw_display_handle`]: ./window/struct.Window.html#method.raw_display_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. //! [^1]: `EventLoopExtPumpEvents::pump_app_events()` is only available on Windows, macOS, Android, X11 and Wayland.
#![deny(rust_2018_idioms)] #![deny(rust_2018_idioms)]
@@ -278,23 +329,28 @@
#![cfg_attr(clippy, deny(warnings))] #![cfg_attr(clippy, deny(warnings))]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly // Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly
// doc // doc
#![cfg_attr(docsrs, feature(doc_cfg), doc(auto_cfg(hide(doc, docsrs))))] #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))]
#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_safety_doc)]
#![warn(clippy::uninlined_format_args)] #![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. // Re-export DPI types so that users don't have to put it in Cargo.toml.
#[doc(inline)] #[doc(inline)]
pub use dpi; pub use dpi;
pub use rwh_06 as raw_window_handle; pub use rwh_06 as raw_window_handle;
pub mod application;
#[cfg(any(doc, doctest, test))] #[cfg(any(doc, doctest, test))]
pub mod changelog; pub mod changelog;
pub mod event_loop;
pub use winit_core::{application, cursor, error, event, icon, keyboard, monitor, window};
#[macro_use] #[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 platform_impl;
mod utils;
pub mod window;
pub mod platform; 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 //! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
//! with `cargo apk`, then the minimal changes would be: //! with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml` //! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = //! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.5",
//! "0.31.0-beta.2", features = [ "android-native-activity" ] }` //! features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc //! 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 //! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
//! logging as above). //! logging as above).
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your //! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your
//! event loop (as shown above). //! 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}; use self::activity::{AndroidApp, ConfigurationRef, Rect};
pub use crate::event_loop::{ use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes, use crate::window::{Window, WindowAttributes};
PlatformSpecificWindowAttributes, Window,
};
/// Additional methods on [`EventLoop`] that are specific to Android. /// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid { pub trait EventLoopExtAndroid {
@@ -89,6 +80,12 @@ pub trait EventLoopExtAndroid {
fn android_app(&self) -> &AndroidApp; 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. /// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid { pub trait ActiveEventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop. /// Get the [`AndroidApp`] which was used to create this event loop.
@@ -102,25 +99,31 @@ pub trait WindowExtAndroid {
fn config(&self) -> ConfigurationRef; fn config(&self) -> ConfigurationRef;
} }
impl WindowExtAndroid for dyn CoreWindow + '_ { impl WindowExtAndroid for dyn Window + '_ {
fn content_rect(&self) -> Rect { 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() window.content_rect()
} }
fn config(&self) -> ConfigurationRef { 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() window.config()
} }
} }
impl ActiveEventLoopExtAndroid for dyn CoreActiveEventLoop + '_ { impl ActiveEventLoopExtAndroid for dyn ActiveEventLoop + '_ {
fn android_app(&self) -> &AndroidApp { 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 &event_loop.app
} }
} }
/// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid { pub trait EventLoopBuilderExtAndroid {
/// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop /// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop
/// ///
@@ -133,6 +136,18 @@ pub trait EventLoopBuilderExtAndroid {
fn handle_volume_keys(&mut self) -> &mut Self; 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 /// Re-export of the `android_activity` API
/// ///
/// Winit re-exports the `android_activity` API for convenience so that most /// 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 // feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page. // it on the crate's own docs.rs page.
#[doc(no_inline)] #[doc(no_inline)]
#[cfg(android_platform)]
pub use android_activity::*; 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 //! Winit has [the same iOS version requirements as `rustc`][rustc-ios-version], although it's
//! frequently only tested on newer iOS versions. //! frequently only tested on newer iOS versions.
@@ -85,9 +85,12 @@
//! //!
//! - applicationDidBecomeActive is Resumed //! - applicationDidBecomeActive is Resumed
//! - applicationWillResignActive is Suspended //! - 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` //! ## Custom `UIApplicationDelegate`
//! //!
@@ -98,36 +101,24 @@
//! register an application delegate, so you can set up a custom one in a nib file instead. //! 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 //! [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 view;
mod view_controller;
mod window;
use std::os::raw::c_void; use std::os::raw::c_void;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use winit_core::monitor::{MonitorHandle, VideoMode};
use winit_core::window::{PlatformWindowAttributes, Window};
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes}; use crate::monitor::{MonitorHandle, VideoModeHandle};
use self::monitor::MonitorHandle as UIKitMonitorHandle; use crate::window::{Window, WindowAttributes};
use self::window::Window as UIKitWindow;
/// Additional methods on [`Window`] that are specific to iOS. /// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS { pub trait WindowExtIOS {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. /// 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 /// 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 /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?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); fn set_scale_factor(&self, scale_factor: f64);
/// Sets the valid orientations for the [`Window`]. /// Sets the valid orientations for the [`Window`].
@@ -221,25 +212,25 @@ pub trait WindowExtIOS {
impl WindowExtIOS for dyn Window + '_ { impl WindowExtIOS for dyn Window + '_ {
#[inline] #[inline]
fn set_scale_factor(&self, scale_factor: f64) { 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)); window.maybe_wait_on_main(move |w| w.set_scale_factor(scale_factor));
} }
#[inline] #[inline]
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { 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)); window.maybe_wait_on_main(move |w| w.set_valid_orientations(valid_orientations));
} }
#[inline] #[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) { 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)); window.maybe_wait_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden));
} }
#[inline] #[inline]
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { 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| { window.maybe_wait_on_main(move |w| {
w.set_preferred_screen_edges_deferring_system_gestures(edges) w.set_preferred_screen_edges_deferring_system_gestures(edges)
}); });
@@ -247,19 +238,19 @@ impl WindowExtIOS for dyn Window + '_ {
#[inline] #[inline]
fn set_prefers_status_bar_hidden(&self, hidden: bool) { 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)); window.maybe_wait_on_main(move |w| w.set_prefers_status_bar_hidden(hidden));
} }
#[inline] #[inline]
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) { 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)) window.maybe_wait_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
} }
#[inline] #[inline]
fn recognize_pinch_gesture(&self, should_recognize: bool) { 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)); window.maybe_wait_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
} }
@@ -270,7 +261,7 @@ impl WindowExtIOS for dyn Window + '_ {
minimum_number_of_touches: u8, minimum_number_of_touches: u8,
maximum_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| { window.maybe_wait_on_main(move |w| {
w.recognize_pan_gesture( w.recognize_pan_gesture(
should_recognize, should_recognize,
@@ -282,41 +273,27 @@ impl WindowExtIOS for dyn Window + '_ {
#[inline] #[inline]
fn recognize_doubletap_gesture(&self, should_recognize: bool) { 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)); window.maybe_wait_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
} }
#[inline] #[inline]
fn recognize_rotation_gesture(&self, should_recognize: bool) { 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)); window.maybe_wait_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
} }
} }
/// Ios specific window attributes. /// Additional methods on [`WindowAttributes`] that are specific to iOS.
#[derive(Clone, Debug, Default, PartialEq)] pub trait WindowAttributesExtIOS {
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 {
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. /// 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 /// 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 /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
/// [`MonitorHandleProvider::scale_factor()`]: crate::monitor::MonitorHandleProvider::scale_factor() fn with_scale_factor(self, scale_factor: f64) -> Self;
pub fn with_scale_factor(mut self, scale_factor: f64) -> Self {
self.scale_factor = Some(scale_factor);
self
}
/// Sets the valid orientations for the [`Window`]. /// Sets the valid orientations for the [`Window`].
/// ///
@@ -324,10 +301,7 @@ impl WindowAttributesIos {
/// ///
/// This sets the initial value returned by /// This sets the initial value returned by
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc). /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
pub fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self { fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> Self;
self.valid_orientations = valid_orientations;
self
}
/// Sets whether the [`Window`] prefers the home indicator hidden. /// Sets whether the [`Window`] prefers the home indicator hidden.
/// ///
@@ -337,10 +311,7 @@ impl WindowAttributesIos {
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc). /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
/// ///
/// This only has an effect on iOS 11.0+. /// This only has an effect on iOS 11.0+.
pub fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self { fn with_prefers_home_indicator_hidden(self, hidden: bool) -> Self;
self.prefers_home_indicator_hidden = hidden;
self
}
/// Sets the screen edges for which the system gestures will take a lower priority than the /// Sets the screen edges for which the system gestures will take a lower priority than the
/// application's touch handling. /// application's touch handling.
@@ -349,13 +320,7 @@ impl WindowAttributesIos {
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc). /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
/// ///
/// This only has an effect on iOS 11.0+. /// This only has an effect on iOS 11.0+.
pub fn with_preferred_screen_edges_deferring_system_gestures( fn with_preferred_screen_edges_deferring_system_gestures(self, edges: ScreenEdge) -> Self;
mut self,
edges: ScreenEdge,
) -> Self {
self.preferred_screen_edges_deferring_system_gestures = edges;
self
}
/// Sets whether the [`Window`] prefers the status bar hidden. /// Sets whether the [`Window`] prefers the status bar hidden.
/// ///
@@ -363,10 +328,7 @@ impl WindowAttributesIos {
/// ///
/// This sets the initial value returned by /// This sets the initial value returned by
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc). /// [`-[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 { fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self;
self.prefers_status_bar_hidden = hidden;
self
}
/// Sets the style of the [`Window`]'s status bar. /// Sets the style of the [`Window`]'s status bar.
/// ///
@@ -374,15 +336,44 @@ impl WindowAttributesIos {
/// ///
/// This sets the initial value returned by /// This sets the initial value returned by
/// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc), /// [`-[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 { fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
self.preferred_status_bar_style = status_bar_style;
self
}
} }
impl PlatformWindowAttributes for WindowAttributesIos { impl WindowAttributesExtIOS for WindowAttributes {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> { #[inline]
Box::from(self.clone()) 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
} }
} }
@@ -393,25 +384,23 @@ pub trait MonitorHandleExtIOS {
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
fn ui_screen(&self) -> *mut c_void; 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). /// 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 { impl MonitorHandleExtIOS for MonitorHandle {
#[inline] #[inline]
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
// SAFETY: The marker is only used to get the pointer of the screen // SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() }; let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
let monitor = self.cast_ref::<UIKitMonitorHandle>().unwrap(); objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
objc2::rc::Retained::as_ptr(monitor.ui_screen(mtm)) as *mut c_void
} }
#[inline] #[inline]
fn preferred_video_mode(&self) -> VideoMode { fn preferred_video_mode(&self) -> VideoModeHandle {
let monitor = self.cast_ref::<UIKitMonitorHandle>().unwrap(); VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
monitor.preferred_video_mode()
} }
} }

View File

@@ -17,24 +17,30 @@
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is //! 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 //! to register your own application delegate, as outlined in the following example (see
//! `objc2-app-kit` for more detailed information). //! `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::rc::Retained;
//! use objc2::runtime::ProtocolObject; //! use objc2::runtime::ProtocolObject;
//! use objc2::{DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send}; //! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate}; //! 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; //! use winit::event_loop::EventLoop;
//! //!
//! define_class!( //! declare_class!(
//! #[unsafe(super(NSObject))]
//! #[thread_kind = MainThreadOnly]
//! #[name = "AppDelegate"]
//! struct AppDelegate; //! 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 NSObjectProtocol for AppDelegate {}
//! //!
//! unsafe impl NSApplicationDelegate for AppDelegate { //! unsafe impl NSApplicationDelegate for AppDelegate {
//! #[unsafe(method(application:openURLs:))] //! #[method(application:openURLs:)]
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) { //! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
//! // Note: To specifically get `application:openURLs:` to work, you _might_ //! // Note: To specifically get `application:openURLs:` to work, you _might_
//! // have to bundle your application. This is not done in this example. //! // have to bundle your application. This is not done in this example.
@@ -45,7 +51,7 @@
//! //!
//! impl AppDelegate { //! impl AppDelegate {
//! fn new(mtm: MainThreadMarker) -> Retained<Self> { //! 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,39 +69,16 @@
//! Ok(()) //! 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 observer;
mod view;
mod window;
mod window_delegate;
use std::os::raw::c_void; use std::os::raw::c_void;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; 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 crate::application::ApplicationHandler;
use self::event_loop::ActiveEventLoop as AppKitActiveEventLoop; use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes}; use crate::monitor::MonitorHandle;
use self::monitor::MonitorHandle as AppKitMonitorHandle; use crate::window::{Window, WindowAttributes, WindowId};
use self::window::Window as AppKitWindow;
/// Additional methods on [`Window`] that are specific to MacOS. /// Additional methods on [`Window`] that are specific to MacOS.
pub trait WindowExtMacOS { pub trait WindowExtMacOS {
@@ -172,9 +155,7 @@ pub trait WindowExtMacOS {
/// Getter for the [`WindowExtMacOS::set_option_as_alt`]. /// Getter for the [`WindowExtMacOS::set_option_as_alt`].
fn option_as_alt(&self) -> OptionAsAlt; fn option_as_alt(&self) -> OptionAsAlt;
/// Disable the Menu Bar and Dock in Simple or Borderless Fullscreen mode. Useful for games. /// Disable the Menu Bar and Dock in Borderless Fullscreen mode. Useful for games.
/// The effect is applied when [`WindowExtMacOS::set_simple_fullscreen`] or
/// [`Window::set_fullscreen`] is called.
fn set_borderless_game(&self, borderless_game: bool); fn set_borderless_game(&self, borderless_game: bool);
/// Getter for the [`WindowExtMacOS::set_borderless_game`]. /// Getter for the [`WindowExtMacOS::set_borderless_game`].
@@ -191,109 +172,109 @@ pub trait WindowExtMacOS {
impl WindowExtMacOS for dyn Window + '_ { impl WindowExtMacOS for dyn Window + '_ {
#[inline] #[inline]
fn simple_fullscreen(&self) -> bool { 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()) window.maybe_wait_on_main(|w| w.simple_fullscreen())
} }
#[inline] #[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { 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)) window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
} }
#[inline] #[inline]
fn has_shadow(&self) -> bool { 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()) window.maybe_wait_on_main(|w| w.has_shadow())
} }
#[inline] #[inline]
fn set_has_shadow(&self, has_shadow: bool) { 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)); window.maybe_wait_on_main(move |w| w.set_has_shadow(has_shadow));
} }
#[inline] #[inline]
fn set_tabbing_identifier(&self, identifier: &str) { 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)) window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
} }
#[inline] #[inline]
fn tabbing_identifier(&self) -> String { 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()) window.maybe_wait_on_main(|w| w.tabbing_identifier())
} }
#[inline] #[inline]
fn select_next_tab(&self) { 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()); window.maybe_wait_on_main(|w| w.select_next_tab());
} }
#[inline] #[inline]
fn select_previous_tab(&self) { 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()); window.maybe_wait_on_main(|w| w.select_previous_tab());
} }
#[inline] #[inline]
fn select_tab_at_index(&self, index: usize) { 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)); window.maybe_wait_on_main(move |w| w.select_tab_at_index(index));
} }
#[inline] #[inline]
fn num_tabs(&self) -> usize { 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()) window.maybe_wait_on_main(|w| w.num_tabs())
} }
#[inline] #[inline]
fn is_document_edited(&self) -> bool { 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()) window.maybe_wait_on_main(|w| w.is_document_edited())
} }
#[inline] #[inline]
fn set_document_edited(&self, edited: bool) { 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)); window.maybe_wait_on_main(move |w| w.set_document_edited(edited));
} }
#[inline] #[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { 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)); window.maybe_wait_on_main(move |w| w.set_option_as_alt(option_as_alt));
} }
#[inline] #[inline]
fn option_as_alt(&self) -> OptionAsAlt { 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()) window.maybe_wait_on_main(|w| w.option_as_alt())
} }
#[inline] #[inline]
fn set_borderless_game(&self, borderless_game: bool) { 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)) window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
} }
#[inline] #[inline]
fn is_borderless_game(&self) -> bool { 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()) window.maybe_wait_on_main(|w| w.is_borderless_game())
} }
#[inline] #[inline]
fn set_unified_titlebar(&self, unified_titlebar: bool) { 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)) window.maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar))
} }
#[inline] #[inline]
fn unified_titlebar(&self) -> bool { 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()) window.maybe_wait_on_main(|w| w.unified_titlebar())
} }
} }
@@ -313,7 +294,7 @@ pub enum ActivationPolicy {
Prohibited, 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 /// **Note:** Properties dealing with the titlebar will be overwritten by the
/// [`WindowAttributes::with_decorations`] method: /// [`WindowAttributes::with_decorations`] method:
@@ -322,158 +303,114 @@ pub enum ActivationPolicy {
/// - `with_titlebar_hidden` /// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden` /// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view` /// - `with_fullsize_content_view`
/// pub trait WindowAttributesExtMacOS {
/// [`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 {
/// Enables click-and-drag behavior for the entire window, not just the titlebar. /// Enables click-and-drag behavior for the entire window, not just the titlebar.
#[inline] fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self;
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
}
/// Makes the titlebar transparent and allows the content to appear behind it. /// Makes the titlebar transparent and allows the content to appear behind it.
#[inline] fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
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
}
/// Hides the window title. /// Hides the window title.
#[inline] fn with_title_hidden(self, title_hidden: bool) -> Self;
pub fn with_title_hidden(mut self, title_hidden: bool) -> Self { /// Hides the window titlebar.
self.title_hidden = title_hidden; fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
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. /// Makes the window content appear behind the titlebar.
#[inline] fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
pub fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self { fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
self.fullsize_content_view = fullsize_content_view; fn with_has_shadow(self, has_shadow: bool) -> Self;
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
}
/// Window accepts click-through mouse events. /// Window accepts click-through mouse events.
#[inline] fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self;
pub fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.accepts_first_mouse = accepts_first_mouse;
self
}
/// Defines the window tabbing identifier. /// Defines the window tabbing identifier.
/// ///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier> /// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
#[inline] fn with_tabbing_identifier(self, identifier: &str) -> Self;
pub fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.tabbing_identifier.replace(tabbing_identifier.to_string());
self
}
/// Set how the <kbd>Option</kbd> keys are interpreted. /// Set how the <kbd>Option</kbd> keys are interpreted.
/// ///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set. /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
#[inline] fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
pub fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.option_as_alt = option_as_alt;
self
}
/// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set. /// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
#[inline] fn with_borderless_game(self, borderless_game: bool) -> Self;
pub fn with_borderless_game(mut self, borderless_game: bool) -> Self {
self.borderless_game = borderless_game;
self
}
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set. /// 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] #[inline]
pub fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self { fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.unified_titlebar = unified_titlebar; self.platform_specific.movable_by_window_background = movable_by_window_background;
self 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] #[inline]
pub fn with_panel(mut self, panel: bool) -> Self { fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
self.panel = panel; self.platform_specific.titlebar_transparent = titlebar_transparent;
self self
} }
}
impl Default for WindowAttributesMacOS {
#[inline] #[inline]
fn default() -> Self { fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
Self { self.platform_specific.titlebar_hidden = titlebar_hidden;
movable_by_window_background: false, self
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,
}
}
} }
impl PlatformWindowAttributes for WindowAttributesMacOS { #[inline]
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> { fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
Box::from(self.clone()) 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
} }
} }
@@ -532,18 +469,44 @@ pub trait EventLoopBuilderExtMacOS {
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self; 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. /// Additional methods on [`MonitorHandle`] that are specific to MacOS.
pub trait MonitorHandleExtMacOS { 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. /// Returns a pointer to the NSScreen representing this monitor.
fn ns_screen(&self) -> Option<*mut c_void>; fn ns_screen(&self) -> Option<*mut c_void>;
} }
impl MonitorHandleExtMacOS for MonitorHandle { impl MonitorHandleExtMacOS for MonitorHandle {
#[inline]
fn native_id(&self) -> u32 {
self.inner.native_identifier()
}
fn ns_screen(&self) -> Option<*mut c_void> { 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 // SAFETY: We only use the marker to get a pointer
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() }; let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
monitor.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _) self.inner.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
} }
} }
@@ -565,26 +528,34 @@ pub trait ActiveEventLoopExtMacOS {
impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ {
fn hide_application(&self) { fn hide_application(&self) {
let event_loop = let event_loop = self
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS"); .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
event_loop.hide_application() event_loop.hide_application()
} }
fn hide_other_applications(&self) { fn hide_other_applications(&self) {
let event_loop = let event_loop = self
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS"); .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
event_loop.hide_other_applications() event_loop.hide_other_applications()
} }
fn set_allows_automatic_window_tabbing(&self, enabled: bool) { fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
let event_loop = let event_loop = self
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS"); .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
event_loop.set_allows_automatic_window_tabbing(enabled); event_loop.set_allows_automatic_window_tabbing(enabled);
} }
fn allows_automatic_window_tabbing(&self) -> bool { fn allows_automatic_window_tabbing(&self) -> bool {
let event_loop = let event_loop = self
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS"); .as_any()
.downcast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
event_loop.allows_automatic_window_tabbing() event_loop.allows_automatic_window_tabbing()
} }
} }
@@ -608,3 +579,52 @@ pub enum OptionAsAlt {
#[default] #[default]
None, 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 std::time::Duration;
use crate::application::ApplicationHandler; 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 /// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents { pub trait EventLoopExtPumpEvents {
/// Pump the `EventLoop` to check for and dispatch pending events. /// Pump the `EventLoop` to check for and dispatch pending events.
@@ -106,6 +106,16 @@ pub trait EventLoopExtPumpEvents {
) -> PumpStatus; ) -> 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` /// The return status for `pump_events`
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum PumpStatus { pub enum PumpStatus {

View File

@@ -1,9 +1,9 @@
use crate::application::ApplicationHandler; use crate::application::ApplicationHandler;
use crate::error::EventLoopError; use crate::error::EventLoopError;
use crate::event_loop::EventLoop;
#[cfg(doc)] #[cfg(doc)]
use crate::{ use crate::{
event_loop::{ActiveEventLoop, pump_events::EventLoopExtPumpEvents}, event_loop::ActiveEventLoop, platform::pump_events::EventLoopExtPumpEvents, window::Window,
window::Window,
}; };
/// Additional methods on [`EventLoop`] to return control flow to the caller. /// Additional methods on [`EventLoop`] to return control flow to the caller.
@@ -11,8 +11,9 @@ pub trait EventLoopExtRunOnDemand {
/// Run the application with the event loop on the calling thread. /// Run the application with the event loop on the calling thread.
/// ///
/// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`) /// Unlike [`EventLoop::run_app`], this function accepts non-`'static` (i.e. non-`move`)
/// state and it is possible to return control back to the caller without consuming the /// closures and it is possible to return control back to the caller without
/// `EventLoop` (by using [`exit()`]) and so the event loop can be re-run after it has exit. /// consuming the `EventLoop` (by using [`exit()`]) and
/// so the event loop can be re-run after it has exit.
/// ///
/// It's expected that each run of the loop will be for orthogonal instantiations of your /// It's expected that each run of the loop will be for orthogonal instantiations of your
/// Winit application, but internally each instantiation may re-use some common window /// Winit application, but internally each instantiation may re-use some common window
@@ -29,7 +30,14 @@ pub trait EventLoopExtRunOnDemand {
/// ///
/// # Caveats /// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return /// - 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). /// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// 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. /// - 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 /// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
@@ -48,7 +56,28 @@ pub trait EventLoopExtRunOnDemand {
/// are delivered via callbacks based on an event loop that is internal to the browser itself. /// are delivered via callbacks based on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS. /// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
/// ///
/// [^1]: `spawn_app()` is only available on the Web platforms.
///
/// [`exit()`]: ActiveEventLoop::exit() /// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow() /// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>; 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}; use crate::event_loop::{ActiveEventLoop, AsyncRequestSerial};
#[cfg(wayland_platform)] #[cfg(wayland_platform)]
use crate::platform::wayland::ActiveEventLoopExtWayland; use crate::platform::wayland::ActiveEventLoopExtWayland;
use crate::window::{ActivationToken, Window}; use crate::window::{ActivationToken, Window, WindowAttributes};
/// The variable which is used mostly on X11. /// The variable which is used mostly on X11.
const X11_VAR: &str = "DESKTOP_STARTUP_ID"; const X11_VAR: &str = "DESKTOP_STARTUP_ID";
@@ -65,9 +65,9 @@ impl EventLoopExtStartupNotify for dyn ActiveEventLoop + '_ {
let _is_wayland = self.is_wayland(); let _is_wayland = self.is_wayland();
if _is_wayland { if _is_wayland {
env::var(WAYLAND_VAR).ok().map(ActivationToken::from_raw) env::var(WAYLAND_VAR).ok().map(ActivationToken::_new)
} else { } 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 + '_ { impl WindowExtStartupNotify for dyn Window + '_ {
fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> { fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
#[cfg(wayland_platform)] #[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(); return window.request_activation_token();
} }
#[cfg(x11_platform)] #[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(); return window.request_activation_token();
} }
@@ -88,24 +91,26 @@ 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. /// Remove the activation environment variables from the current process.
/// ///
/// This is wise to do before running child processes, /// This is wise to do before running child processes,
/// which may not to support the activation token. /// which may not to support the activation token.
pub fn reset_activation_token_env() { pub fn reset_activation_token_env() {
unsafe {
env::remove_var(X11_VAR); env::remove_var(X11_VAR);
env::remove_var(WAYLAND_VAR); env::remove_var(WAYLAND_VAR);
} }
}
/// Set environment variables responsible for activation token. /// Set environment variables responsible for activation token.
/// ///
/// This could be used before running daemon processes. /// This could be used before running daemon processes.
pub fn set_activation_token_env(token: ActivationToken) { pub fn set_activation_token_env(token: ActivationToken) {
let token = token.into_raw(); env::set_var(X11_VAR, &token._token);
unsafe { env::set_var(WAYLAND_VAR, token._token);
env::set_var(X11_VAR, &token);
env::set_var(WAYLAND_VAR, 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. //! yourself.
//! //!
//! [canvas]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement //! [canvas]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
//! [with_canvas]: WindowAttributesWeb::with_canvas //! [with_canvas]: WindowAttributesExtWeb::with_canvas
//! [get]: WindowExtWeb::canvas //! [get]: WindowExtWeb::canvas
//! [insert]: WindowAttributesWeb::with_append //! [insert]: WindowAttributesExtWeb::with_append
//! [wasm_bindgen]: https://docs.rs/wasm-bindgen #![cfg_attr(not(web_platform), doc = "[wasm_bindgen]: https://docs.rs/wasm-bindgen")]
//! [Rust and WebAssembly book]: https://rustwasm.github.io/book //! [Rust and WebAssembly book]: https://rustwasm.github.io/book
//! //!
//! ## CSS properties //! ## CSS properties
@@ -41,67 +41,37 @@
//! [`WindowEvent::PointerLeft`]: crate::event::WindowEvent::PointerLeft //! [`WindowEvent::PointerLeft`]: crate::event::WindowEvent::PointerLeft
//! [`Window::set_outer_position()`]: crate::window::Window::set_outer_position //! [`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::cell::Ref;
use std::error::Error; use std::error::Error;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::Duration;
use ::web_sys::HtmlCanvasElement;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use winit_core::cursor::{CustomCursor, CustomCursorSource}; #[cfg(web_platform)]
use winit_core::error::NotSupportedError; use web_sys::HtmlCanvasElement;
use winit_core::event_loop::ActiveEventLoop;
use winit_core::monitor::MonitorHandleProvider;
use winit_core::window::{PlatformWindowAttributes, Window};
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes}; use crate::application::ApplicationHandler;
use self::web_sys as backend; use crate::cursor::CustomCursorSource;
use self::window::Window as WebWindow; use crate::error::NotSupportedError;
use crate::cursor::CustomCursorFuture as PlatformCustomCursorFuture; use crate::event_loop::{ActiveEventLoop, EventLoop};
use crate::event_loop::ActiveEventLoop as WebActiveEventLoop; use crate::monitor::MonitorHandle;
use crate::main_thread::{MainThreadMarker, MainThreadSafe}; use crate::platform_impl::PlatformCustomCursorSource;
use crate::monitor::{ #[cfg(web_platform)]
use crate::platform_impl::{
CustomCursorFuture as PlatformCustomCursorFuture,
HasMonitorPermissionFuture as PlatformHasMonitorPermissionFuture, HasMonitorPermissionFuture as PlatformHasMonitorPermissionFuture,
MonitorHandle as WebMonitorHandle, MonitorPermissionFuture as PlatformMonitorPermissionFuture, MonitorPermissionFuture as PlatformMonitorPermissionFuture,
OrientationLockFuture as PlatformOrientationLockFuture, OrientationLockFuture as PlatformOrientationLockFuture,
}; };
use crate::window::{CustomCursor, Window, WindowAttributes};
#[cfg(not(web_platform))]
#[doc(hidden)]
pub struct HtmlCanvasElement;
pub trait WindowExtWeb { pub trait WindowExtWeb {
/// Only returns the canvas if called from inside the window context (the /// Only returns the canvas if called from inside the window context (the
@@ -135,51 +105,43 @@ pub trait WindowExtWeb {
impl WindowExtWeb for dyn Window + '_ { impl WindowExtWeb for dyn Window + '_ {
#[inline] #[inline]
fn canvas(&self) -> Option<Ref<'_, HtmlCanvasElement>> { 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 { 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) { 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") .expect("non Web window on Web")
.set_prevent_default(prevent_default) .set_prevent_default(prevent_default)
} }
fn is_cursor_lock_raw(&self) -> bool { 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 trait WindowAttributesExtWeb {
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 {
/// Pass an [`HtmlCanvasElement`] to be used for this [`Window`]. If [`None`], /// 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. /// In any case, the canvas won't be automatically inserted into the Web page.
/// ///
/// [`None`] by default. /// [`None`] by default.
pub fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self { #[cfg_attr(not(web_platform), doc = "", doc = "[`HtmlCanvasElement`]: #only-available-on-wasm")]
match canvas { fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
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
}
/// Sets whether `event.preventDefault()` should be called on events on the /// Sets whether `event.preventDefault()` should be called on events on the
/// canvas that have side effects. /// canvas that have side effects.
@@ -187,55 +149,68 @@ impl WindowAttributesWeb {
/// See [`WindowExtWeb::set_prevent_default()`] for more details. /// See [`WindowExtWeb::set_prevent_default()`] for more details.
/// ///
/// Enabled by default. /// Enabled by default.
pub fn with_prevent_default(mut self, prevent_default: bool) -> Self { fn with_prevent_default(self, prevent_default: bool) -> Self;
self.prevent_default = prevent_default;
self
}
/// Whether the canvas should be focusable using the tab key. This is necessary to capture /// Whether the canvas should be focusable using the tab key. This is necessary to capture
/// canvas keyboard events. /// canvas keyboard events.
/// ///
/// Enabled by default. /// Enabled by default.
pub fn with_focusable(mut self, focusable: bool) -> Self { fn with_focusable(self, focusable: bool) -> Self;
self.focusable = focusable;
self
}
/// On window creation, append the canvas element to the Web page if it isn't already. /// On window creation, append the canvas element to the Web page if it isn't already.
/// ///
/// Disabled by default. /// Disabled by default.
pub fn with_append(mut self, append: bool) -> Self { fn with_append(self, append: bool) -> Self;
self.append = append; }
impl WindowAttributesExtWeb for WindowAttributes {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.set_canvas(canvas);
self self
} }
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
self.platform_specific.prevent_default = prevent_default;
self
} }
impl PlatformWindowAttributes for WindowAttributesWeb { fn with_focusable(mut self, focusable: bool) -> Self {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> { self.platform_specific.focusable = focusable;
Box::from(self.clone()) self
}
} }
impl PartialEq for WindowAttributesWeb { fn with_append(mut self, append: bool) -> Self {
fn eq(&self, other: &Self) -> bool { self.platform_specific.append = append;
(match (&self.canvas, &other.canvas) { self
(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)
}
}
impl Default for WindowAttributesWeb {
fn default() -> Self {
Self { canvas: None, prevent_default: true, focusable: true, append: false }
} }
} }
/// Additional methods on `EventLoop` that are specific to the Web. /// Additional methods on `EventLoop` that are specific to the Web.
pub trait EventLoopExtWeb { pub trait EventLoopExtWeb {
/// Initializes the winit event loop.
///
/// Unlike
#[cfg_attr(all(web_platform, target_feature = "exception-handling"), doc = "`run_app()`")]
#[cfg_attr(
not(all(web_platform, target_feature = "exception-handling")),
doc = "[`run_app()`]"
)]
/// [^1], this returns immediately, and doesn't throw an exception in order to
/// satisfy its [`!`] return type.
///
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
/// by calling this function again. This can be useful if you want to recreate the event loop
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
/// event loop when switching between tabs on a single page application.
#[rustfmt::skip]
///
#[cfg_attr(
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`.
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
/// Sets the strategy for [`ControlFlow::Poll`]. /// Sets the strategy for [`ControlFlow::Poll`].
/// ///
/// See [`PollStrategy`]. /// See [`PollStrategy`].
@@ -278,21 +253,49 @@ pub trait EventLoopExtWeb {
/// ///
/// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New /// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New
/// [`MonitorHandle`]s have to be created instead. /// [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture; fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture;
/// Returns whether the user has given permission to access detailed monitor information. /// Returns whether the user has given permission to access detailed monitor information.
/// ///
/// [`MonitorHandle`]s don't automatically make use of detailed monitor information after /// [`MonitorHandle`]s don't automatically make use of detailed monitor information after
/// permission is granted. New [`MonitorHandle`]s have to be created instead. /// 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; 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 { pub trait ActiveEventLoopExtWeb {
/// Sets the strategy for [`ControlFlow::Poll`]. /// Sets the strategy for [`ControlFlow::Poll`].
/// ///
@@ -345,71 +348,94 @@ pub trait ActiveEventLoopExtWeb {
/// ///
/// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New /// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New
/// [`MonitorHandle`]s have to be created instead. /// [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture; fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture;
/// Returns whether the user has given permission to access detailed monitor information. /// Returns whether the user has given permission to access detailed monitor information.
/// ///
/// [`MonitorHandle`]s don't automatically make use of detailed monitor information after /// [`MonitorHandle`]s don't automatically make use of detailed monitor information after
/// permission is granted. New [`MonitorHandle`]s have to be created instead. /// permission is granted. New [`MonitorHandle`]s have to be created instead.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn has_detailed_monitor_permission(&self) -> bool; fn has_detailed_monitor_permission(&self) -> bool;
} }
impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ { impl ActiveEventLoopExtWeb for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn create_custom_cursor_async(&self, source: CustomCursorSource) -> CustomCursorFuture { 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) event_loop.create_custom_cursor_async(source)
} }
#[inline] #[inline]
fn set_poll_strategy(&self, strategy: PollStrategy) { 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); event_loop.set_poll_strategy(strategy);
} }
#[inline] #[inline]
fn poll_strategy(&self) -> PollStrategy { 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() event_loop.poll_strategy()
} }
#[inline] #[inline]
fn set_wait_until_strategy(&self, strategy: WaitUntilStrategy) { 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); event_loop.set_wait_until_strategy(strategy);
} }
#[inline] #[inline]
fn wait_until_strategy(&self) -> WaitUntilStrategy { 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() event_loop.wait_until_strategy()
} }
#[inline] #[inline]
fn is_cursor_lock_raw(&self) -> bool { 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() event_loop.is_cursor_lock_raw()
} }
#[inline] #[inline]
fn has_multiple_screens(&self) -> Result<bool, NotSupportedError> { 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() event_loop.has_multiple_screens()
} }
#[inline] #[inline]
fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture { 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()) MonitorPermissionFuture(event_loop.request_detailed_monitor_permission())
} }
#[inline] #[inline]
fn has_detailed_monitor_permission(&self) -> bool { 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() event_loop.has_detailed_monitor_permission()
} }
} }
@@ -465,6 +491,76 @@ pub enum WaitUntilStrategy {
Worker, 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)] #[derive(Debug)]
pub struct CustomCursorFuture(pub(crate) PlatformCustomCursorFuture); pub struct CustomCursorFuture(pub(crate) PlatformCustomCursorFuture);
@@ -472,7 +568,7 @@ impl Future for CustomCursorFuture {
type Output = Result<CustomCursor, CustomCursorError>; type Output = Result<CustomCursor, CustomCursorError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { 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 })
} }
} }
@@ -494,6 +590,9 @@ impl Display for CustomCursorError {
impl Error for CustomCursorError {} impl Error for CustomCursorError {}
#[cfg(not(web_platform))]
struct PlatformMonitorPermissionFuture;
/// Can be dropped without aborting the request for detailed monitor permissions. /// Can be dropped without aborting the request for detailed monitor permissions.
#[derive(Debug)] #[derive(Debug)]
pub struct MonitorPermissionFuture(pub(crate) PlatformMonitorPermissionFuture); pub struct MonitorPermissionFuture(pub(crate) PlatformMonitorPermissionFuture);
@@ -536,6 +635,9 @@ impl Display for MonitorPermissionError {
impl Error for MonitorPermissionError {} impl Error for MonitorPermissionError {}
#[cfg(not(web_platform))]
struct PlatformHasMonitorPermissionFuture;
#[derive(Debug)] #[derive(Debug)]
pub struct HasMonitorPermissionFuture(PlatformHasMonitorPermissionFuture); pub struct HasMonitorPermissionFuture(PlatformHasMonitorPermissionFuture);
@@ -548,8 +650,6 @@ impl Future for HasMonitorPermissionFuture {
} }
/// Additional methods on [`MonitorHandle`] that are specific to the Web. /// Additional methods on [`MonitorHandle`] that are specific to the Web.
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
pub trait MonitorHandleExtWeb { pub trait MonitorHandleExtWeb {
/// Returns whether the screen is internal to the device or external. /// Returns whether the screen is internal to the device or external.
/// ///
@@ -577,31 +677,28 @@ pub trait MonitorHandleExtWeb {
/// specific monitor. /// specific monitor.
/// ///
/// See [`ActiveEventLoopExtWeb::request_detailed_monitor_permission()`]. /// See [`ActiveEventLoopExtWeb::request_detailed_monitor_permission()`].
///
/// [`MonitorHandle`]: crate::monitor::MonitorHandle
fn is_detailed(&self) -> bool; fn is_detailed(&self) -> bool;
} }
impl MonitorHandleExtWeb for dyn MonitorHandleProvider + '_ { impl MonitorHandleExtWeb for MonitorHandle {
fn is_internal(&self) -> Option<bool> { fn is_internal(&self) -> Option<bool> {
self.cast_ref::<WebMonitorHandle>().unwrap().is_internal() self.inner.is_internal()
} }
fn orientation(&self) -> OrientationData { fn orientation(&self) -> OrientationData {
self.cast_ref::<WebMonitorHandle>().unwrap().orientation() self.inner.orientation()
} }
fn request_lock(&self, orientation_lock: OrientationLock) -> OrientationLockFuture { fn request_lock(&self, orientation_lock: OrientationLock) -> OrientationLockFuture {
let future = self.cast_ref::<WebMonitorHandle>().unwrap().request_lock(orientation_lock); OrientationLockFuture(self.inner.request_lock(orientation_lock))
OrientationLockFuture(future)
} }
fn unlock(&self) -> Result<(), OrientationLockError> { fn unlock(&self) -> Result<(), OrientationLockError> {
self.cast_ref::<WebMonitorHandle>().unwrap().unlock() self.inner.unlock()
} }
fn is_detailed(&self) -> bool { fn is_detailed(&self) -> bool {
self.cast_ref::<WebMonitorHandle>().unwrap().is_detailed() self.inner.is_detailed()
} }
} }
@@ -626,7 +723,7 @@ pub enum Orientation {
Portrait, 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)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum OrientationLock { pub enum OrientationLock {
/// User is free to use any orientation. /// User is free to use any orientation.
@@ -651,6 +748,9 @@ pub enum OrientationLock {
}, },
} }
#[cfg(not(web_platform))]
struct PlatformOrientationLockFuture;
/// Can be dropped without aborting the request to lock the screen. /// Can be dropped without aborting the request to lock the screen.
#[derive(Debug)] #[derive(Debug)]
pub struct OrientationLockFuture(PlatformOrientationLockFuture); 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 //! The supported OS version is Windows 7 or higher, though Windows 10 is
//! tested regularly. //! 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::borrow::Borrow;
use std::ffi::c_void; use std::ffi::c_void;
use std::ops::Deref; use std::ops::Deref;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
use ::dpi::PhysicalSize;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(windows_platform)]
use windows_sys::Win32::Foundation::HANDLE; 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 crate::dpi::PhysicalSize;
use self::icon::{RaiiIcon, SelectedCursor}; use crate::event::DeviceId;
pub use self::keyboard::{physicalkey_to_scancode, scancode_to_physicalkey}; use crate::event_loop::EventLoopBuilder;
pub use self::monitor::{MonitorHandle, VideoModeHandle}; use crate::monitor::MonitorHandle;
pub use self::window::Window; use crate::window::{BadIcon, Icon, Window, WindowAttributes};
/// Window Handle type used by Win32 API /// Window Handle type used by Win32 API
pub type HWND = *mut c_void; pub type HWND = isize;
/// Menu Handle type used by Win32 API /// Menu Handle type used by Win32 API
pub type HMENU = *mut c_void; pub type HMENU = isize;
/// Monitor Handle type used by Win32 API /// 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. /// Describes a system-drawn backdrop material of a window.
/// ///
@@ -140,24 +118,24 @@ pub enum CornerPreference {
/// ///
/// See [`WindowBorrowExtWindows::any_thread`] for more information. /// See [`WindowBorrowExtWindows::any_thread`] for more information.
#[derive(Clone, Debug)] #[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. /// Get a reference to the inner window.
#[inline] #[inline]
pub fn get_ref(&self) -> &dyn CoreWindow { pub fn get_ref(&self) -> &dyn Window {
&self.0 &self.0
} }
} }
impl<W: CoreWindow> Deref for AnyThread<W> { impl<W: Window> Deref for AnyThread<W> {
type Target = W; type Target = W;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &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> { fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
// SAFETY: The top level user has asserted this is only used safely. // SAFETY: The top level user has asserted this is only used safely.
unsafe { self.get_ref().window_handle_any_thread() } unsafe { self.get_ref().window_handle_any_thread() }
@@ -232,13 +210,36 @@ pub trait EventLoopBuilderExtWindows {
F: FnMut(*const c_void) -> bool + 'static; 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. /// Additional methods on `Window` that are specific to Windows.
pub trait WindowExtWindows { pub trait WindowExtWindows {
/// Enables or disables mouse and keyboard input to the specified window. /// Enables or disables mouse and keyboard input to the specified window.
/// ///
/// A window must be enabled before it can be activated. /// A window must be enabled before it can be activated.
/// If an application has create a modal dialog box by disabling its owner window /// 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. /// enable the owner window before destroying the dialog box.
/// Otherwise, another window will receive the keyboard focus and be activated. /// 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. /// Supported starting with Windows 11 Build 22000.
fn set_corner_preference(&self, preference: CornerPreference); 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. /// 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 /// 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>; ) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError>;
} }
impl WindowExtWindows for dyn CoreWindow + '_ { impl WindowExtWindows for dyn Window + '_ {
#[inline] #[inline]
fn set_enable(&self, enabled: bool) { 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) window.set_enable(enabled)
} }
#[inline] #[inline]
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) { 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) window.set_taskbar_icon(taskbar_icon)
} }
#[inline] #[inline]
fn set_skip_taskbar(&self, skip: bool) { 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) window.set_skip_taskbar(skip)
} }
#[inline] #[inline]
fn set_undecorated_shadow(&self, shadow: bool) { 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) window.set_undecorated_shadow(shadow)
} }
#[inline] #[inline]
fn set_system_backdrop(&self, backdrop_type: BackdropType) { 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) window.set_system_backdrop(backdrop_type)
} }
#[inline] #[inline]
fn set_border_color(&self, color: Option<Color>) { 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)) 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 // 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 // useful to circumvent the Windows option "Show accent color on title bars and
// window borders" // 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)) window.set_title_background_color(color.unwrap_or(Color::NONE))
} }
#[inline] #[inline]
fn set_title_text_color(&self, color: Color) { 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) window.set_title_text_color(color)
} }
#[inline] #[inline]
fn set_corner_preference(&self, preference: CornerPreference) { 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) 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( unsafe fn window_handle_any_thread(
&self, &self,
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> { ) -> 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 { unsafe {
let handle = window.rwh_06_no_thread_check()?; 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`]. /// Additional methods for anything that dereference to [`Window`].
/// ///
/// [`Window`]: crate::window::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. /// 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 /// 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 /// [`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread
unsafe fn any_thread(self) -> AnyThread<Self> unsafe fn any_thread(self) -> AnyThread<Self>
where where
Self: CoreWindow, Self: Window,
{ {
AnyThread(self) AnyThread(self)
} }
} }
impl<W: Borrow<dyn CoreWindow> + Sized> WindowBorrowExtWindows for W {} impl<W: Borrow<dyn Window> + Sized> WindowBorrowExtWindows for W {}
#[derive(Clone, Debug)] /// Additional methods on `WindowAttributes` that are specific to Windows.
pub struct WindowAttributesWindows { #[allow(rustdoc::broken_intra_doc_links)]
pub(crate) owner: Option<HWND>, pub trait WindowAttributesExtWindows {
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 {
/// Set an owner to the window to be created. Can be used to create a dialog box, for example. /// 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`. /// This only works when [`WindowAttributes::with_parent_window`] isn't called or set to `None`.
/// Can be used in combination with /// Can be used in combination with
@@ -514,12 +459,7 @@ impl WindowAttributesWindows {
/// - An owned window is hidden when its owner is minimized. /// - 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> /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
/// fn with_owner_window(self, parent: HWND) -> Self;
/// [`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
}
/// Sets a menu on the window to be created. /// 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 /// 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 /// 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. /// `with_theme(Some(Theme::Light))` to avoid a jarring effect.
/// #[cfg_attr(
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu" windows_platform,
pub fn with_menu(mut self, menu: HMENU) -> Self { doc = "[`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
self.menu = Some(menu); )]
self #[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. /// This sets `ICON_BIG`. A good ceiling here is 256x256.
pub fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self { fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> Self;
self.taskbar_icon = taskbar_icon;
self
}
/// This sets `WS_EX_NOREDIRECTIONBITMAP`. /// This sets `WS_EX_NOREDIRECTIONBITMAP`.
pub fn with_no_redirection_bitmap(mut self, flag: bool) -> Self { fn with_no_redirection_bitmap(self, flag: bool) -> Self;
self.no_redirection_bitmap = flag;
self
}
/// Enables or disables drag and drop support (enabled by default). Will interfere with other /// 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` /// 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 /// 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 /// 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. /// `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 { fn with_drag_and_drop(self, flag: bool) -> Self;
self.drag_and_drop = flag;
self
}
/// Whether show or hide the window icon in the taskbar. /// Whether show or hide the window icon in the taskbar.
pub fn with_skip_taskbar(mut self, skip: bool) -> Self { fn with_skip_taskbar(self, skip: bool) -> Self;
self.skip_taskbar = skip;
self
}
/// Customize the window class name. /// Customize the window class name.
pub fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self { fn with_class_name<S: Into<String>>(self, class_name: S) -> Self;
self.class_name = class_name.into();
self
}
/// Shows or hides the background drop shadow for undecorated windows. /// Shows or hides the background drop shadow for undecorated windows.
/// ///
/// The shadow is hidden by default. /// The shadow is hidden by default.
/// Enabling the shadow causes a thin 1px line to appear on the top of the window. /// 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 { fn with_undecorated_shadow(self, shadow: bool) -> Self;
self.decoration_shadow = shadow;
self
}
/// Sets system-drawn backdrop type. /// Sets system-drawn backdrop type.
/// ///
/// Requires Windows 11 build 22523+. /// Requires Windows 11 build 22523+.
pub fn with_system_backdrop(mut self, backdrop_type: BackdropType) -> Self { fn with_system_backdrop(self, backdrop_type: BackdropType) -> Self;
self.backdrop_type = backdrop_type;
self
}
/// This sets or removes `WS_CLIPCHILDREN` style. /// This sets or removes `WS_CLIPCHILDREN` style.
pub fn with_clip_children(mut self, flag: bool) -> Self { fn with_clip_children(self, flag: bool) -> Self;
self.clip_children = flag;
self
}
/// Sets the color of the window border. /// Sets the color of the window border.
/// ///
/// Supported starting with Windows 11 Build 22000. /// Supported starting with Windows 11 Build 22000.
pub fn with_border_color(mut self, color: Option<Color>) -> Self { fn with_border_color(self, color: Option<Color>) -> Self;
self.border_color = Some(color.unwrap_or(Color::NONE));
self
}
/// Sets the background color of the title bar. /// Sets the background color of the title bar.
/// ///
/// Supported starting with Windows 11 Build 22000. /// Supported starting with Windows 11 Build 22000.
pub fn with_title_background_color(mut self, color: Option<Color>) -> Self { fn with_title_background_color(self, color: Option<Color>) -> Self;
self.title_background_color = Some(color.unwrap_or(Color::NONE));
self
}
/// Sets the color of the window title. /// Sets the color of the window title.
/// ///
/// Supported starting with Windows 11 Build 22000. /// Supported starting with Windows 11 Build 22000.
pub fn with_title_text_color(mut self, color: Color) -> Self { fn with_title_text_color(self, color: Color) -> Self;
self.title_text_color = Some(color);
self
}
/// Sets the preferred style of the window corners. /// Sets the preferred style of the window corners.
/// ///
/// Supported starting with Windows 11 Build 22000. /// Supported starting with Windows 11 Build 22000.
pub fn with_corner_preference(mut self, corners: CornerPreference) -> Self { fn with_corner_preference(self, corners: CornerPreference) -> Self;
self.corner_preference = Some(corners); }
impl WindowAttributesExtWindows for WindowAttributes {
#[inline]
fn with_owner_window(mut self, parent: HWND) -> Self {
self.platform_specific.owner = Some(parent);
self self
} }
/// Sets if the reported [`winit_core::event::WindowEvent::MouseWheel`] event #[inline]
/// should account for scroll speed system settings. fn with_menu(mut self, menu: HMENU) -> Self {
/// self.platform_specific.menu = Some(menu);
/// The default scroll speed on Windows is 3 lines/characters per scroll, self
/// this will be 1 if you set it to false. }
///
/// The default is `true`. #[inline]
pub fn with_use_system_scroll_speed(mut self, should_use: bool) -> Self { fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self {
self.use_system_wheel_speed = should_use; 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 self
} }
} }
impl PlatformWindowAttributes for WindowAttributesWindows { /// Additional methods on `MonitorHandle` that are specific to Windows.
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> { pub trait MonitorHandleExtWindows {
Box::from(self.clone()) /// 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,31 +647,20 @@ pub trait DeviceIdExtWindows {
fn persistent_identifier(&self) -> Option<String>; fn persistent_identifier(&self) -> Option<String>;
} }
#[cfg(windows_platform)]
impl DeviceIdExtWindows for DeviceId { impl DeviceIdExtWindows for DeviceId {
fn persistent_identifier(&self) -> Option<String> { fn persistent_identifier(&self) -> Option<String> {
let raw_id = self.into_raw(); let raw_id = self.into_raw();
if raw_id != 0 { raw_input::get_raw_input_device_name(raw_id as HANDLE) } else { None } if raw_id != 0 {
crate::platform_impl::raw_input::get_raw_input_device_name(raw_id as HANDLE)
} else {
None
}
} }
} }
/// Windows specific `Icon`. /// Additional methods on `Icon` that are specific to Windows.
/// pub trait IconExtWindows: Sized {
/// 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 {
/// Create an icon from a file path. /// Create an icon from a file path.
/// ///
/// Specify `size` to load a specific icon size from the file, or `None` to load the default /// Specify `size` to load a specific icon size from the file, or `None` to load the default
@@ -686,88 +668,30 @@ impl WinIcon {
/// ///
/// In cases where the specified size does not exist in the file, Windows may perform scaling /// In cases where the specified size does not exist in the file, Windows may perform scaling
/// to get an icon of the desired size. /// 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, path: P,
size: Option<PhysicalSize<u32>>, size: Option<PhysicalSize<u32>>,
) -> Result<Self, BadIcon> { ) -> 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. fn from_resource(ordinal: u16, size: Option<PhysicalSize<u32>>) -> Result<Self, BadIcon> {
/// let win_icon = crate::platform_impl::WinIcon::from_resource(ordinal, size)?;
/// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an Ok(Icon { inner: win_icon })
/// 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))
} }
} }

View File

@@ -1,30 +1,11 @@
//! # X11 //! # X11
use dpi::Size;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; 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}; use crate::dpi::Size;
pub use crate::window::Window; use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
macro_rules! os_error { use crate::window::{Window as CoreWindow, WindowAttributes};
($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;
/// X window type. Maps directly to /// X window type. Maps directly to
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html). /// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
@@ -99,21 +80,19 @@ pub type XWindow = u32;
#[inline] #[inline]
pub fn register_xlib_error_hook(hook: XlibErrorHook) { pub fn register_xlib_error_hook(hook: XlibErrorHook) {
// Append new hook. // 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. /// Additional methods on [`ActiveEventLoop`] that are specific to X11.
///
/// [`ActiveEventLoop`]: winit_core::event_loop::ActiveEventLoop
pub trait ActiveEventLoopExtX11 { pub trait ActiveEventLoopExtX11 {
/// True if the event loop uses X11. /// True if the [`ActiveEventLoop`] uses X11.
fn is_x11(&self) -> bool; fn is_x11(&self) -> bool;
} }
impl ActiveEventLoopExtX11 for dyn CoreActiveEventLoop + '_ { impl ActiveEventLoopExtX11 for dyn ActiveEventLoop + '_ {
#[inline] #[inline]
fn is_x11(&self) -> bool { fn is_x11(&self) -> bool {
self.cast_ref::<ActiveEventLoop>().is_some() self.as_any().downcast_ref::<crate::platform_impl::x11::ActiveEventLoop>().is_some()
} }
} }
@@ -123,7 +102,14 @@ pub trait EventLoopExtX11 {
fn is_x11(&self) -> bool; fn is_x11(&self) -> bool;
} }
/// Additional methods when building event loop that are specific to X11. 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 { pub trait EventLoopBuilderExtX11 {
/// Force using X11. /// Force using X11.
fn with_x11(&mut self) -> &mut Self; fn with_x11(&mut self) -> &mut Self;
@@ -135,6 +121,20 @@ pub trait EventLoopBuilderExtX11 {
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self; 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. /// Additional methods on [`Window`] that are specific to X11.
/// ///
/// [`Window`]: crate::window::Window /// [`Window`]: crate::window::Window
@@ -142,52 +142,12 @@ pub trait WindowExtX11 {}
impl WindowExtX11 for dyn CoreWindow {} impl WindowExtX11 for dyn CoreWindow {}
#[derive(Debug, Clone, PartialEq, Eq)] /// Additional methods on [`WindowAttributes`] that are specific to X11.
pub(crate) struct ApplicationName { pub trait WindowAttributesExtX11 {
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 {
/// Create this window with a specific X11 visual. /// Create this window with a specific X11 visual.
pub fn with_x11_visual(mut self, visual_id: XVisualID) -> Self { fn with_x11_visual(self, visual_id: XVisualID) -> Self;
self.visual_id = Some(visual_id);
self
}
pub fn with_x11_screen(mut self, screen_id: i32) -> Self { fn with_x11_screen(self, screen_id: i32) -> Self;
self.screen_id = Some(screen_id);
self
}
/// Build window with the given `general` and `instance` names. /// Build window with the given `general` and `instance` names.
/// ///
@@ -197,39 +157,27 @@ impl WindowAttributesX11 {
/// ///
/// For details about application ID conventions, see the /// 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) /// [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 { fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
self.name = Some(ApplicationName { general: general.into(), instance: instance.into() });
self
}
/// Build window with override-redirect flag; defaults to false. /// Build window with override-redirect flag; defaults to false.
pub fn with_override_redirect(mut self, override_redirect: bool) -> Self { fn with_override_redirect(self, override_redirect: bool) -> Self;
self.override_redirect = override_redirect;
self
}
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. /// 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 { fn with_x11_window_type(self, x11_window_type: Vec<WindowType>) -> Self;
self.x11_window_types = x11_window_types;
self
}
/// Build window with base size hint. /// Build window with base size hint.
/// ///
/// ``` /// ```
/// # use winit::dpi::{LogicalSize, PhysicalSize}; /// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::{Window, WindowAttributes}; /// # use winit::window::{Window, WindowAttributes};
/// # use winit::platform::x11::WindowAttributesX11; /// # use winit::platform::x11::WindowAttributesExtX11;
/// // Specify the size in logical dimensions like this: /// // 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: /// // 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 { fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
self.base_size = Some(base_size.into());
self
}
/// Embed this window into another parent window. /// Embed this window into another parent window.
/// ///
@@ -238,28 +186,70 @@ impl WindowAttributesX11 {
/// ```no_run /// ```no_run
/// use winit::window::{Window, WindowAttributes}; /// use winit::window::{Window, WindowAttributes};
/// use winit::event_loop::ActiveEventLoop; /// 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>> { /// # 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 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_embed_parent_window(parent_window_id);
/// let window_attributes = WindowAttributes::default().with_platform_attributes(Box::new(window_x11_attributes));
/// let window = event_loop.create_window(window_attributes)?; /// let window = event_loop.create_window(window_attributes)?;
/// # Ok(()) } /// # Ok(()) }
/// ``` /// ```
pub fn with_embed_parent_window(mut self, parent_window_id: XWindow) -> Self { fn with_embed_parent_window(self, parent_window_id: XWindow) -> Self;
self.embed_window = Some(parent_window_id); }
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 self
} }
#[inline] #[inline]
pub fn with_activation_token(mut self, token: ActivationToken) -> Self { fn with_x11_screen(mut self, screen_id: i32) -> Self {
self.activation_token = Some(token); 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 self
} }
} }
impl PlatformWindowAttributes for WindowAttributesX11 { /// Additional methods on `MonitorHandle` that are specific to X11.
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> { pub trait MonitorHandleExtX11 {
Box::from(self.clone()) /// 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::AndroidApp;
use android_activity::input::{KeyAction, KeyEvent, KeyMapChar, Keycode}; use android_activity::input::{KeyAction, KeyEvent, KeyMapChar, Keycode};
use winit_core::keyboard::{ use android_activity::AndroidApp;
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 { pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
PhysicalKey::Code(match keycode { PhysicalKey::Code(match keycode {
@@ -109,7 +108,6 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::MediaStop => KeyCode::MediaStop, Keycode::MediaStop => KeyCode::MediaStop,
Keycode::MediaNext => KeyCode::MediaTrackNext, Keycode::MediaNext => KeyCode::MediaTrackNext,
Keycode::MediaPrevious => KeyCode::MediaTrackPrevious, Keycode::MediaPrevious => KeyCode::MediaTrackPrevious,
Keycode::MediaEject => KeyCode::Eject,
Keycode::Plus => KeyCode::Equal, Keycode::Plus => KeyCode::Equal,
Keycode::Minus => KeyCode::Minus, Keycode::Minus => KeyCode::Minus,
@@ -132,11 +130,7 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
// These are exactly the same // These are exactly the same
Keycode::ScrollLock => KeyCode::ScrollLock, Keycode::ScrollLock => KeyCode::ScrollLock,
Keycode::Eisu => KeyCode::Lang2,
Keycode::Muhenkan => KeyCode::NonConvert,
Keycode::Henkan => KeyCode::Convert,
Keycode::Yen => KeyCode::IntlYen, Keycode::Yen => KeyCode::IntlYen,
Keycode::Ro => KeyCode::IntlRo,
Keycode::Kana => KeyCode::Lang1, Keycode::Kana => KeyCode::Lang1,
Keycode::KatakanaHiragana => KeyCode::KanaMode, Keycode::KatakanaHiragana => KeyCode::KanaMode,
@@ -149,8 +143,8 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::AltLeft => KeyCode::AltLeft, Keycode::AltLeft => KeyCode::AltLeft,
Keycode::AltRight => KeyCode::AltRight, Keycode::AltRight => KeyCode::AltRight,
Keycode::MetaLeft => KeyCode::MetaLeft, Keycode::MetaLeft => KeyCode::SuperLeft,
Keycode::MetaRight => KeyCode::MetaRight, Keycode::MetaRight => KeyCode::SuperRight,
Keycode::LeftBracket => KeyCode::BracketLeft, Keycode::LeftBracket => KeyCode::BracketLeft,
Keycode::RightBracket => KeyCode::BracketRight, Keycode::RightBracket => KeyCode::BracketRight,
@@ -159,14 +153,6 @@ pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep? Keycode::Sleep => KeyCode::Sleep, // what about SoftSleep?
Keycode::Wakeup => KeyCode::WakeUp, Keycode::Wakeup => KeyCode::WakeUp,
Keycode::CapsLock => KeyCode::CapsLock,
Keycode::Help => KeyCode::Help,
Keycode::Back => KeyCode::BrowserBack,
Keycode::Forward => KeyCode::BrowserForward,
Keycode::Refresh => KeyCode::BrowserRefresh,
Keycode::Search => KeyCode::BrowserSearch,
keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())), keycode => return PhysicalKey::Unidentified(NativeKeyCode::Android(keycode.into())),
}) })
} }
@@ -323,7 +309,7 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
ShiftLeft => Key::Named(NamedKey::Shift), ShiftLeft => Key::Named(NamedKey::Shift),
ShiftRight => Key::Named(NamedKey::Shift), ShiftRight => Key::Named(NamedKey::Shift),
Tab => Key::Named(NamedKey::Tab), Tab => Key::Named(NamedKey::Tab),
Space => Key::Character(" ".into()), Space => Key::Named(NamedKey::Space),
Sym => Key::Named(NamedKey::Symbol), Sym => Key::Named(NamedKey::Symbol),
Explorer => Key::Named(NamedKey::LaunchWebBrowser), Explorer => Key::Named(NamedKey::LaunchWebBrowser),
Envelope => Key::Named(NamedKey::LaunchMail), Envelope => Key::Named(NamedKey::LaunchMail),
@@ -354,8 +340,8 @@ pub fn to_logical(key_char: Option<KeyMapChar>, keycode: Keycode) -> Key {
CtrlRight => Key::Named(NamedKey::Control), CtrlRight => Key::Named(NamedKey::Control),
CapsLock => Key::Named(NamedKey::CapsLock), CapsLock => Key::Named(NamedKey::CapsLock),
ScrollLock => Key::Named(NamedKey::ScrollLock), ScrollLock => Key::Named(NamedKey::ScrollLock),
MetaLeft => Key::Named(NamedKey::Meta), MetaLeft => Key::Named(NamedKey::Super),
MetaRight => Key::Named(NamedKey::Meta), MetaRight => Key::Named(NamedKey::Super),
Function => Key::Named(NamedKey::Fn), Function => Key::Named(NamedKey::Fn),
Sysrq => Key::Named(NamedKey::PrintScreen), Sysrq => Key::Named(NamedKey::PrintScreen),
Break => Key::Named(NamedKey::Pause), Break => Key::Named(NamedKey::Pause),

View File

@@ -1,6 +1,6 @@
use std::cell::Cell; use std::cell::Cell;
use std::fmt;
use std::hash::Hash; use std::hash::Hash;
use std::num::{NonZeroU16, NonZeroU32};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -9,26 +9,32 @@ use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{ use android_activity::{
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect, AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
}; };
use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{Cursor, CustomCursor, CustomCursorSource}; use crate::application::ApplicationHandler;
use winit_core::error::{EventLoopError, NotSupportedError, RequestError}; use crate::cursor::Cursor;
use winit_core::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter}; use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use winit_core::event_loop::pump_events::PumpStatus; use crate::error::{EventLoopError, NotSupportedError, RequestError};
use winit_core::event_loop::{ use crate::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle, OwnedDisplayHandle as CoreOwnedDisplayHandle,
}; };
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle}; use crate::monitor::MonitorHandle as RootMonitorHandle;
use winit_core::window::{ use crate::platform::pump_events::PumpStatus;
self, CursorGrabMode, ImeCapabilities, ImePurpose, ImeRequest, ImeRequestError, use crate::window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, ImePurpose,
ResizeDirection, Theme, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, ResizeDirection, Theme, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
WindowLevel, 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); 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)))) 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 { struct SharedFlagSetter {
flag: Arc<AtomicBool>, flag: Arc<AtomicBool>,
} }
impl SharedFlagSetter { impl SharedFlagSetter {
fn set(&self) -> bool { pub fn set(&self) -> bool {
self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok() self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok()
} }
} }
#[derive(Debug)]
struct SharedFlag { struct SharedFlag {
flag: Arc<AtomicBool>, 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 // 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) // was queued and be able to read and clear the state atomically)
impl SharedFlag { impl SharedFlag {
fn new() -> Self { pub fn new() -> Self {
Self { flag: Arc::new(AtomicBool::new(false)) } Self { flag: Arc::new(AtomicBool::new(false)) }
} }
fn setter(&self) -> SharedFlagSetter { pub fn setter(&self) -> SharedFlagSetter {
SharedFlagSetter { flag: self.flag.clone() } 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) self.flag.swap(false, std::sync::atomic::Ordering::AcqRel)
} }
} }
#[derive(Clone)] #[derive(Clone)]
struct RedrawRequester { pub struct RedrawRequester {
flag: SharedFlagSetter, flag: SharedFlagSetter,
waker: AndroidAppWaker, 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 { impl RedrawRequester {
fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self { fn new(flag: &SharedFlag, waker: AndroidAppWaker) -> Self {
RedrawRequester { flag: flag.setter(), waker } RedrawRequester { flag: flag.setter(), waker }
} }
fn request_redraw(&self) { pub fn request_redraw(&self) {
if self.flag.set() { if self.flag.set() {
// Only explicitly try to wake up the main loop when the flag // Only explicitly try to wake up the main loop when the flag
// value changes // value changes
@@ -98,9 +97,11 @@ impl RedrawRequester {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {}
pub struct EventLoop { pub struct EventLoop {
pub android_app: AndroidApp, pub(crate) android_app: AndroidApp,
window_target: ActiveEventLoop, window_target: ActiveEventLoop,
redraw_flag: SharedFlag, redraw_flag: SharedFlag,
loop_running: bool, // Dispatched `NewEvents<Init>` loop_running: bool, // Dispatched `NewEvents<Init>`
@@ -113,9 +114,9 @@ pub struct EventLoop {
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PlatformSpecificEventLoopAttributes { pub(crate) struct PlatformSpecificEventLoopAttributes {
pub android_app: Option<AndroidApp>, pub(crate) android_app: Option<AndroidApp>,
pub ignore_volume_keys: bool, pub(crate) ignore_volume_keys: bool,
} }
impl Default for PlatformSpecificEventLoopAttributes { impl Default for PlatformSpecificEventLoopAttributes {
@@ -128,13 +129,9 @@ impl Default for PlatformSpecificEventLoopAttributes {
const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0); const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0);
impl EventLoop { impl EventLoop {
pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> { pub(crate) fn new(
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false); attributes: &PlatformSpecificEventLoopAttributes,
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) { ) -> Result<Self, EventLoopError> {
// For better cross-platformness.
return Err(EventLoopError::RecreationAttempt);
}
let android_app = attributes.android_app.as_ref().expect( let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \ "An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
Android", 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 &self.window_target
} }
@@ -473,24 +470,16 @@ impl EventLoop {
&mut self.combining_accent, &mut self.combining_accent,
); );
let logical_key = keycodes::to_logical(key_char, keycode);
let text = if state == event::ElementState::Pressed {
logical_key.to_text().map(smol_str::SmolStr::new)
} else {
None
};
let event = event::WindowEvent::KeyboardInput { let event = event::WindowEvent::KeyboardInput {
device_id: Some(DeviceId::from_raw(key.device_id() as i64)), device_id: Some(DeviceId::from_raw(key.device_id() as i64)),
event: event::KeyEvent { event: event::KeyEvent {
state, state,
physical_key: keycodes::to_physical_key(keycode), physical_key: keycodes::to_physical_key(keycode),
logical_key, logical_key: keycodes::to_logical(key_char, keycode),
location: keycodes::to_location(keycode), location: keycodes::to_location(keycode),
repeat: key.repeat_count() > 0, repeat: key.repeat_count() > 0,
text: text.clone(), text: None,
text_with_all_modifiers: text, platform_specific: KeyEventExtra {},
key_without_modifiers: keycodes::to_logical(key_char, keycode),
}, },
is_synthetic: false, is_synthetic: false,
}; };
@@ -507,6 +496,10 @@ impl EventLoop {
input_status input_status
} }
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
pub fn run_app_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
mut app: A, mut app: A,
@@ -553,6 +546,8 @@ impl EventLoop {
if self.exiting() { if self.exiting() {
self.loop_running = false; self.loop_running = false;
app.exiting(&self.window_target);
PumpStatus::Exit(0) PumpStatus::Exit(0)
} else { } else {
PumpStatus::Continue PumpStatus::Continue
@@ -647,12 +642,6 @@ pub struct EventLoopProxy {
waker: AndroidAppWaker, 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 { impl EventLoopProxy {
fn new(waker: AndroidAppWaker) -> Self { fn new(waker: AndroidAppWaker) -> Self {
Self { wake_up: AtomicBool::new(false), waker } Self { wake_up: AtomicBool::new(false), waker }
@@ -666,7 +655,6 @@ impl EventLoopProxyProvider for EventLoopProxy {
} }
} }
#[derive(Debug)]
pub struct ActiveEventLoop { pub struct ActiveEventLoop {
pub(crate) app: AndroidApp, pub(crate) app: AndroidApp,
control_flow: Cell<ControlFlow>, control_flow: Cell<ControlFlow>,
@@ -700,11 +688,11 @@ impl RootActiveEventLoop for ActiveEventLoop {
Err(NotSupportedError::new("create_custom_cursor is not supported").into()) 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()) Box::new(std::iter::empty())
} }
fn primary_monitor(&self) -> Option<CoreMonitorHandle> { fn primary_monitor(&self) -> Option<RootMonitorHandle> {
None None
} }
@@ -759,10 +747,8 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowAttributes; pub struct PlatformSpecificWindowAttributes;
#[derive(Debug)] pub(crate) struct Window {
pub struct Window {
app: AndroidApp, app: AndroidApp,
ime_capabilities: Mutex<Option<ImeCapabilities>>,
redraw_requester: RedrawRequester, redraw_requester: RedrawRequester,
} }
@@ -773,18 +759,14 @@ impl Window {
) -> Result<Self, RequestError> { ) -> Result<Self, RequestError> {
// FIXME this ignores requested window attributes // FIXME this ignores requested window attributes
Ok(Self { Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() })
app: el.app.clone(),
ime_capabilities: Default::default(),
redraw_requester: el.redraw_requester.clone(),
})
} }
pub(crate) fn config(&self) -> ConfigurationRef { pub fn config(&self) -> ConfigurationRef {
self.app.config() self.app.config()
} }
pub(crate) fn content_rect(&self) -> Rect { pub fn content_rect(&self) -> Rect {
self.app.content_rect() self.app.content_rect()
} }
@@ -829,15 +811,15 @@ impl CoreWindow for Window {
GLOBAL_WINDOW GLOBAL_WINDOW
} }
fn primary_monitor(&self) -> Option<CoreMonitorHandle> { fn primary_monitor(&self) -> Option<RootMonitorHandle> {
None None
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new(std::iter::empty()) Box::new(std::iter::empty())
} }
fn current_monitor(&self) -> Option<CoreMonitorHandle> { fn current_monitor(&self) -> Option<RootMonitorHandle> {
None None
} }
@@ -941,37 +923,16 @@ impl CoreWindow for Window {
fn set_window_level(&self, _level: WindowLevel) {} fn set_window_level(&self, _level: WindowLevel) {}
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 set_ime_cursor_area(&self, _position: Position, _size: Size) {}
fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> { fn set_ime_allowed(&self, allowed: bool) {
let mut current_caps = self.ime_capabilities.lock().unwrap(); if allowed {
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); self.app.show_soft_input(true);
}, } else {
ImeRequest::Update(_) => {
if current_caps.is_none() {
return Err(ImeRequestError::NotEnabled);
}
},
ImeRequest::Disable => {
*current_caps = None;
self.app.hide_soft_input(true); 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) {} fn set_ime_purpose(&self, _purpose: ImePurpose) {}
@@ -1034,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> { fn screen_size(app: &AndroidApp) -> PhysicalSize<u32> {
if let Some(native_window) = app.native_window() { if let Some(native_window) = app.native_window() {
PhysicalSize::new(native_window.width() as _, native_window.height() as _) 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::cell::{Cell, OnceCell, RefCell};
use std::mem; 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::sync::Arc;
use std::time::Instant; use std::time::Instant;
use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::NSNotification; use objc2_foundation::{MainThreadMarker, NSNotification};
use winit_common::core_foundation::{EventLoopProxy, MainRunLoop};
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 super::event_loop::{ActiveEventLoop, notify_windows_of_exit, stop_app_immediately}; use super::super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo};
use super::menu; use super::menu;
use super::observer::EventLoopWaker; 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)] #[derive(Debug)]
pub(super) struct AppState { pub(super) struct AppState {
@@ -25,7 +23,7 @@ pub(super) struct AppState {
activation_policy: Option<NSApplicationActivationPolicy>, activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool, default_menu: bool,
activate_ignoring_other_apps: bool, activate_ignoring_other_apps: bool,
run_loop: MainRunLoop, run_loop: RunLoop,
event_loop_proxy: Arc<EventLoopProxy>, event_loop_proxy: Arc<EventLoopProxy>,
event_handler: EventHandler, event_handler: EventHandler,
stop_on_launch: Cell<bool>, stop_on_launch: Cell<bool>,
@@ -47,10 +45,22 @@ pub(super) struct AppState {
// as such should be careful to not add fields that, in turn, strongly reference those. // as such should be careful to not add fields that, in turn, strongly reference those.
} }
// 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. // main thread.
static GLOBAL: MainThreadBound<OnceCell<Rc<AppState>>> = static GLOBAL: StaticMainThreadBound<OnceCell<Rc<AppState>>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() }); StaticMainThreadBound(OnceCell::new());
impl AppState { impl AppState {
pub(super) fn setup_global( pub(super) fn setup_global(
@@ -58,18 +68,14 @@ impl AppState {
activation_policy: Option<NSApplicationActivationPolicy>, activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool, default_menu: bool,
activate_ignoring_other_apps: bool, activate_ignoring_other_apps: bool,
) -> Option<Rc<Self>> { ) -> Rc<Self> {
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || { let this = Rc::new(AppState {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}));
let this = Rc::new(Self {
mtm, mtm,
activation_policy, activation_policy,
event_loop_proxy: Arc::new(EventLoopProxy::new()),
default_menu, default_menu,
activate_ignoring_other_apps, activate_ignoring_other_apps,
run_loop: MainRunLoop::get(mtm), run_loop: RunLoop::main(mtm),
event_loop_proxy,
event_handler: EventHandler::new(), event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false), stop_on_launch: Cell::new(false),
stop_before_wait: Cell::new(false), stop_before_wait: Cell::new(false),
@@ -85,7 +91,8 @@ impl AppState {
pending_redraw: RefCell::new(vec![]), 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> { pub fn get(mtm: MainThreadMarker) -> Rc<Self> {
@@ -99,6 +106,7 @@ impl AppState {
// NOTE: This notification will, globally, only be emitted once, // NOTE: This notification will, globally, only be emitted once,
// no matter how many `EventLoop`s the user creates. // no matter how many `EventLoop`s the user creates.
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) { pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
trace_scope!("NSApplicationDidFinishLaunchingNotification");
self.is_launched.set(true); self.is_launched.set(true);
let app = NSApplication::sharedApplication(self.mtm); let app = NSApplication::sharedApplication(self.mtm);
@@ -116,7 +124,7 @@ impl AppState {
// - https://github.com/rust-windowing/winit/issues/261 // - https://github.com/rust-windowing/winit/issues/261
// - https://github.com/rust-windowing/winit/issues/3958 // - https://github.com/rust-windowing/winit/issues/3958
let is_bundled = let is_bundled =
NSRunningApplication::currentApplication().bundleIdentifier().is_some(); unsafe { NSRunningApplication::currentApplication().bundleIdentifier().is_some() };
if !is_bundled { if !is_bundled {
app.setActivationPolicy(NSApplicationActivationPolicy::Regular); app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
} }
@@ -153,9 +161,8 @@ impl AppState {
} }
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) { pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
let app = NSApplication::sharedApplication(self.mtm); trace_scope!("NSApplicationWillTerminateNotification");
notify_windows_of_exit(&app); // TODO: Notify every window that it will be destroyed, like done in iOS?
self.event_handler.terminate();
self.internal_exit(); self.internal_exit();
} }
@@ -163,10 +170,10 @@ impl AppState {
/// of the given closure. /// of the given closure.
pub fn set_event_handler<R>( pub fn set_event_handler<R>(
&self, &self,
handler: impl ApplicationHandler, handler: &mut dyn ApplicationHandler,
closure: impl FnOnce() -> R, closure: impl FnOnce() -> R,
) -> R { ) -> R {
self.event_handler.set(Box::new(handler), closure) self.event_handler.set(handler, closure)
} }
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> { pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
@@ -201,6 +208,10 @@ impl AppState {
/// NOTE: that if the `NSApplication` has been launched then that state is preserved, /// NOTE: that if the `NSApplication` has been launched then that state is preserved,
/// and we won't need to re-launch the app if subsequent EventLoops are run. /// and we won't need to re-launch the app if subsequent EventLoops are run.
pub fn internal_exit(self: &Rc<Self>) { pub fn internal_exit(self: &Rc<Self>) {
self.with_handler(|app, event_loop| {
app.exiting(event_loop);
});
self.set_is_running(false); self.set_is_running(false);
self.set_stop_on_redraw(false); self.set_stop_on_redraw(false);
self.set_stop_before_wait(false); self.set_stop_before_wait(false);
@@ -263,7 +274,7 @@ impl AppState {
if !pending_redraw.contains(&window_id) { if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id); pending_redraw.push(window_id);
} }
self.run_loop.wake_up(); self.run_loop.wakeup();
} }
#[track_caller] #[track_caller]
@@ -306,10 +317,13 @@ impl AppState {
} }
// Called by RunLoopObserver after finishing waiting for new events // 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 // Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// (we have registered to observe all modes, including modal event loops). if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
if !self.event_handler.ready() || !self.is_running() {
return; return;
} }
@@ -335,13 +349,22 @@ impl AppState {
} }
// Called by RunLoopObserver before waiting for new events // 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 // Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// (we have registered to observe all modes, including modal event loops). // XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
if !self.event_handler.ready() || !self.is_running() { // we're about to return to the `CFRunLoop` to poll for new events?
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
return; 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()); let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw { for window_id in redraw {
self.with_handler(|app, event_loop| { self.with_handler(|app, event_loop| {
@@ -355,7 +378,6 @@ impl AppState {
if self.exiting() { if self.exiting() {
let app = NSApplication::sharedApplication(self.mtm); let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app); stop_app_immediately(&app);
notify_windows_of_exit(&app);
} }
if self.stop_before_wait.get() { if self.stop_before_wait.get() {

View File

@@ -0,0 +1,226 @@
use std::ffi::c_uchar;
use std::slice;
use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSObjectProtocol, NSPoint, NSSize,
NSString,
};
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>);
// 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: 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 bitmap = unsafe {
NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(
NSBitmapImageRep::alloc(),
std::ptr::null_mut::<*mut c_uchar>(),
width as isize,
height as isize,
8,
4,
true,
false,
NSDeviceRGBColorSpace,
width as isize * 4,
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.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);
Ok(NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot))
}
pub(crate) fn default_cursor() -> Retained<NSCursor> {
NSCursor::arrowCursor()
}
unsafe fn try_cursor_from_selector(sel: Sel) -> Option<Retained<NSCursor>> {
let cls = NSCursor::class();
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");
None
}
}
macro_rules! def_undocumented_cursor {
{$(
$(#[$($m:meta)*])*
fn $name:ident();
)*} => {$(
$(#[$($m)*])*
#[allow(non_snake_case)]
fn $name() -> Retained<NSCursor> {
unsafe { try_cursor_from_selector(sel!($name)).unwrap_or_else(|| default_cursor()) }
}
)*};
}
def_undocumented_cursor!(
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
fn _helpCursor();
fn _zoomInCursor();
fn _zoomOutCursor();
fn _windowResizeNorthEastCursor();
fn _windowResizeNorthWestCursor();
fn _windowResizeSouthEastCursor();
fn _windowResizeSouthWestCursor();
fn _windowResizeNorthEastSouthWestCursor();
fn _windowResizeNorthWestSouthEastCursor();
// While these two are available, the former just loads a white arrow,
// and the latter loads an ugly deflated beachball!
// pub fn _moveCursor();
// pub fn _waitCursor();
// An even more undocumented cursor...
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
fn busyButClickableCursor();
);
// Note that loading `busybutclickable` with this code won't animate
// the frames; instead you'll just get them all in a column.
unsafe fn load_webkit_cursor(name: &NSString) -> Retained<NSCursor> {
// Snatch a cursor from WebKit; They fit the style of the native
// cursors, and will seem completely standard to macOS users.
//
// https://stackoverflow.com/a/21786835/5435443
let root = ns_string!(
"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/\
HIServices.framework/Versions/A/Resources/cursors"
);
let cursor_path = root.stringByAppendingPathComponent(name);
let pdf_path = cursor_path.stringByAppendingPathComponent(ns_string!("cursor.pdf"));
let image = NSImage::initByReferencingFile(NSImage::alloc(), &pdf_path).unwrap();
// TODO: Handle PLists better
let info_path = cursor_path.stringByAppendingPathComponent(ns_string!("info.plist"));
let info: Retained<NSDictionary<NSObject, NSObject>> = unsafe {
msg_send_id![
<NSDictionary<NSObject, NSObject>>::class(),
dictionaryWithContentsOfFile: &*info_path,
]
};
let mut x = 0.0;
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.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()
}
}
let hotspot = NSPoint::new(x, y);
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
}
fn webkit_move() -> Retained<NSCursor> {
unsafe { load_webkit_cursor(ns_string!("move")) }
}
fn webkit_cell() -> Retained<NSCursor> {
unsafe { load_webkit_cursor(ns_string!("cell")) }
}
pub(crate) fn invisible_cursor() -> Retained<NSCursor> {
// 16x16 GIF data for invisible cursor
// You can reproduce this via ImageMagick.
// $ convert -size 16x16 xc:none cursor.gif
static CURSOR_BYTES: &[u8] = &[
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0e, 0x84, 0x8f, 0xa9, 0xcb, 0xed, 0x0f,
0xa3, 0x9c, 0xb4, 0xda, 0x8b, 0xb3, 0x3e, 0x05, 0x00, 0x3b,
];
fn new_invisible() -> Retained<NSCursor> {
// TODO: Consider using `dataWithBytesNoCopy:`
let data = NSData::with_bytes(CURSOR_BYTES);
let image = NSImage::initWithData(NSImage::alloc(), &data).unwrap();
let hotspot = NSPoint::new(0.0, 0.0);
NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot)
}
// Cache this for efficiency
static CURSOR: OnceLock<CustomCursor> = OnceLock::new();
CURSOR.get_or_init(|| CustomCursor(new_invisible())).0.clone()
}
pub(crate) fn cursor_from_icon(icon: CursorIcon) -> Retained<NSCursor> {
match icon {
CursorIcon::Default => default_cursor(),
CursorIcon::Pointer => NSCursor::pointingHandCursor(),
CursorIcon::Grab => NSCursor::openHandCursor(),
CursorIcon::Grabbing => NSCursor::closedHandCursor(),
CursorIcon::Text => NSCursor::IBeamCursor(),
CursorIcon::VerticalText => NSCursor::IBeamCursorForVerticalLayout(),
CursorIcon::Copy => NSCursor::dragCopyCursor(),
CursorIcon::Alias => NSCursor::dragLinkCursor(),
CursorIcon::NotAllowed | CursorIcon::NoDrop => NSCursor::operationNotAllowedCursor(),
CursorIcon::ContextMenu => NSCursor::contextualMenuCursor(),
CursorIcon::Crosshair => NSCursor::crosshairCursor(),
CursorIcon::EResize => NSCursor::resizeRightCursor(),
CursorIcon::NResize => NSCursor::resizeUpCursor(),
CursorIcon::WResize => NSCursor::resizeLeftCursor(),
CursorIcon::SResize => NSCursor::resizeDownCursor(),
CursorIcon::EwResize | CursorIcon::ColResize => NSCursor::resizeLeftRightCursor(),
CursorIcon::NsResize | CursorIcon::RowResize => NSCursor::resizeUpDownCursor(),
CursorIcon::Help => _helpCursor(),
CursorIcon::ZoomIn => _zoomInCursor(),
CursorIcon::ZoomOut => _zoomOutCursor(),
CursorIcon::NeResize => _windowResizeNorthEastCursor(),
CursorIcon::NwResize => _windowResizeNorthWestCursor(),
CursorIcon::SeResize => _windowResizeSouthEastCursor(),
CursorIcon::SwResize => _windowResizeSouthWestCursor(),
CursorIcon::NeswResize => _windowResizeNorthEastSouthWestCursor(),
CursorIcon::NwseResize => _windowResizeNorthWestSouthEastCursor(),
// This is the wrong semantics for `Wait`, but it's the same as
// what's used in Safari and Chrome.
CursorIcon::Wait | CursorIcon::Progress => busyButClickableCursor(),
CursorIcon::Move | CursorIcon::AllScroll => webkit_move(),
CursorIcon::Cell => webkit_cell(),
_ => default_cursor(),
}
}

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::rc::Retained;
use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType}; use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_core_foundation::{CFData, CFRetained}; use objc2_foundation::{run_on_main, NSPoint};
use objc2_foundation::NSPoint;
use smol_str::SmolStr; 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, Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode,
PhysicalKey, 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. /// Ignores ALL modifiers.
pub fn get_modifierless_char(scancode: u16) -> Key { pub fn get_modifierless_char(scancode: u16) -> Key {
let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else { 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"); tracing::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode)); return Key::Unidentified(NativeKey::MacOS(scancode));
}; }
let input_source = unsafe { CFRetained::from_raw(ptr) }; let layout_data =
ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData);
let layout_data = unsafe { if layout_data.is_null() {
ffi::TISGetInputSourceProperty(&input_source, ffi::kTISPropertyUnicodeKeyLayoutData) CFRelease(input_source as *mut c_void);
};
let Some(layout_data) = (unsafe { layout_data.cast::<CFData>().as_ref() }) else {
tracing::error!("`TISGetInputSourceProperty` returned null ptr"); tracing::error!("`TISGetInputSourceProperty` returned null ptr");
return Key::Unidentified(NativeKey::MacOS(scancode)); return Key::Unidentified(NativeKey::MacOS(scancode));
}; }
layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout;
let layout = layout_data.byte_ptr().cast(); }
let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() }); let keyboard_type = run_on_main(|_mtm| unsafe { ffi::LMGetKbdType() });
let mut result_len = 0; let mut result_len = 0;
let mut dead_keys = 0; let mut dead_keys = 0;
let modifiers = 0; let modifiers = 0;
let mut string = [0; 16];
let translate_result = unsafe { let translate_result = unsafe {
ffi::UCKeyTranslate( ffi::UCKeyTranslate(
layout, layout,
@@ -51,6 +59,9 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
string.as_mut_ptr(), string.as_mut_ptr(),
) )
}; };
unsafe {
CFRelease(input_source as *mut c_void);
}
if translate_result != 0 { if translate_result != 0 {
tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result); tracing::error!("`UCKeyTranslate` returned with the non-zero value: {}", translate_result);
return Key::Unidentified(NativeKey::MacOS(scancode)); return Key::Unidentified(NativeKey::MacOS(scancode));
@@ -67,7 +78,9 @@ pub fn get_modifierless_char(scancode: u16) -> Key {
// Ignores all modifiers except for SHIFT (yes, even ALT is ignored). // Ignores all modifiers except for SHIFT (yes, even ALT is ignored).
fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key { fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key {
let string = ns_event.charactersIgnoringModifiers().map(|s| s.to_string()).unwrap_or_default(); let string = unsafe { ns_event.charactersIgnoringModifiers() }
.map(|s| s.to_string())
.unwrap_or_default();
if string.is_empty() { if string.is_empty() {
// Probably a dead key // Probably a dead key
let first_char = modifierless_chars.chars().next(); let first_char = modifierless_chars.chars().next();
@@ -83,7 +96,7 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
use ElementState::{Pressed, Released}; use ElementState::{Pressed, Released};
let state = if is_press { Pressed } else { Released }; let state = if is_press { Pressed } else { Released };
let scancode = ns_event.keyCode(); let scancode = unsafe { ns_event.keyCode() };
let mut physical_key = scancode_to_physicalkey(scancode as u32); let mut physical_key = scancode_to_physicalkey(scancode as u32);
// NOTE: The logical key should heed both SHIFT and ALT if possible. // NOTE: The logical key should heed both SHIFT and ALT if possible.
@@ -93,7 +106,7 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
// * Pressing CTRL SHIFT A: logical key should also be "A" // * Pressing CTRL SHIFT A: logical key should also be "A"
// This is not easy to tease out of `NSEvent`, but we do our best. // This is not easy to tease out of `NSEvent`, but we do our best.
let characters = ns_event.characters().map(|s| s.to_string()).unwrap_or_default(); let characters = unsafe { ns_event.characters() }.map(|s| s.to_string()).unwrap_or_default();
let text_with_all_modifiers = if characters.is_empty() { let text_with_all_modifiers = if characters.is_empty() {
None None
} else { } else {
@@ -109,13 +122,13 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
// `get_modifierless_char/key_without_modifiers` ignores ALL modifiers. // `get_modifierless_char/key_without_modifiers` ignores ALL modifiers.
let key_without_modifiers = get_modifierless_char(scancode); let key_without_modifiers = get_modifierless_char(scancode);
let modifiers = ns_event.modifierFlags(); let modifiers = unsafe { ns_event.modifierFlags() };
let has_ctrl = modifiers.contains(NSEventModifierFlags::Control); let has_ctrl = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagControl);
let has_cmd = modifiers.contains(NSEventModifierFlags::Command); let has_cmd = modifiers.contains(NSEventModifierFlags::NSEventModifierFlagCommand);
let logical_key = match text_with_all_modifiers.as_ref() { 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 // Only checking for ctrl and cmd here, not checking for alt because we DO want to
// include its effect in the key. For example if -on the German layout- one // include its effect in the key. For example if -on the Germay layout- one
// presses alt+8, the logical key should be "{" // presses alt+8, the logical key should be "{"
// Also not checking if this is a release event because then this issue would // Also not checking if this is a release event because then this issue would
// still affect the key release. // still affect the key release.
@@ -149,8 +162,7 @@ pub(crate) fn create_key_event(ns_event: &NSEvent, is_press: bool, is_repeat: bo
repeat: is_repeat, repeat: is_repeat,
state, state,
text, text,
text_with_all_modifiers, platform_specific: KeyEventExtra { text_with_all_modifiers, key_without_modifiers },
key_without_modifiers,
} }
} }
@@ -160,34 +172,28 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
PhysicalKey::Unidentified(code) => return Key::Unidentified(code.into()), 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 { Key::Named(match code {
KeyCode::Enter => NamedKey::Enter, KeyCode::Enter => NamedKey::Enter,
KeyCode::Tab => NamedKey::Tab, KeyCode::Tab => NamedKey::Tab,
KeyCode::Space => return Key::Character(" ".into()), KeyCode::Space => NamedKey::Space,
KeyCode::Backspace => NamedKey::Backspace, KeyCode::Backspace => NamedKey::Backspace,
KeyCode::Escape => NamedKey::Escape, KeyCode::Escape => NamedKey::Escape,
KeyCode::MetaRight => NamedKey::Meta, KeyCode::SuperRight => NamedKey::Super,
KeyCode::MetaLeft => NamedKey::Meta, KeyCode::SuperLeft => NamedKey::Super,
KeyCode::ShiftLeft => NamedKey::Shift, KeyCode::ShiftLeft => NamedKey::Shift,
KeyCode::AltLeft => NamedKey::Alt, KeyCode::AltLeft => NamedKey::Alt,
KeyCode::ControlLeft => NamedKey::Control, KeyCode::ControlLeft => NamedKey::Control,
KeyCode::ShiftRight => NamedKey::Shift, KeyCode::ShiftRight => NamedKey::Shift,
KeyCode::AltRight => NamedKey::Alt, KeyCode::AltRight => NamedKey::Alt,
KeyCode::ControlRight => NamedKey::Control, KeyCode::ControlRight => NamedKey::Control,
KeyCode::CapsLock => NamedKey::CapsLock,
KeyCode::NumLock => NamedKey::NumLock, KeyCode::NumLock => NamedKey::NumLock,
KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp, KeyCode::AudioVolumeUp => NamedKey::AudioVolumeUp,
KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown, KeyCode::AudioVolumeDown => NamedKey::AudioVolumeDown,
KeyCode::AudioVolumeMute => NamedKey::AudioVolumeMute,
// Other numpad keys all generate text on macOS (if I understand correctly) // Other numpad keys all generate text on macOS (if I understand correctly)
KeyCode::NumpadEnter => NamedKey::Enter, KeyCode::NumpadEnter => NamedKey::Enter,
KeyCode::Fn => NamedKey::Fn,
KeyCode::F1 => NamedKey::F1, KeyCode::F1 => NamedKey::F1,
KeyCode::F2 => NamedKey::F2, KeyCode::F2 => NamedKey::F2,
KeyCode::F3 => NamedKey::F3, KeyCode::F3 => NamedKey::F3,
@@ -208,27 +214,17 @@ pub fn code_to_key(key: PhysicalKey, scancode: u16) -> Key {
KeyCode::F18 => NamedKey::F18, KeyCode::F18 => NamedKey::F18,
KeyCode::F19 => NamedKey::F19, KeyCode::F19 => NamedKey::F19,
KeyCode::F20 => NamedKey::F20, 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::Insert => NamedKey::Insert,
KeyCode::Home => NamedKey::Home, KeyCode::Home => NamedKey::Home,
KeyCode::PageUp => NamedKey::PageUp, KeyCode::PageUp => NamedKey::PageUp,
KeyCode::Delete => NamedKey::Delete, KeyCode::Delete => NamedKey::Delete,
KeyCode::End => NamedKey::End, KeyCode::End => NamedKey::End,
KeyCode::Help => NamedKey::Help,
KeyCode::PageDown => NamedKey::PageDown, KeyCode::PageDown => NamedKey::PageDown,
KeyCode::ArrowLeft => NamedKey::ArrowLeft, KeyCode::ArrowLeft => NamedKey::ArrowLeft,
KeyCode::ArrowRight => NamedKey::ArrowRight, KeyCode::ArrowRight => NamedKey::ArrowRight,
KeyCode::ArrowDown => NamedKey::ArrowDown, KeyCode::ArrowDown => NamedKey::ArrowDown,
KeyCode::ArrowUp => NamedKey::ArrowUp, KeyCode::ArrowUp => NamedKey::ArrowUp,
KeyCode::ContextMenu => NamedKey::ContextMenu,
KeyCode::Lang2 => NamedKey::Eisu,
KeyCode::Lang1 => NamedKey::KanjiMode,
_ => return Key::Unidentified(NativeKey::MacOS(scancode)), _ => return Key::Unidentified(NativeKey::MacOS(scancode)),
}) })
} }
@@ -240,8 +236,8 @@ pub fn code_to_location(key: PhysicalKey) -> KeyLocation {
}; };
match code { match code {
KeyCode::MetaRight => KeyLocation::Right, KeyCode::SuperRight => KeyLocation::Right,
KeyCode::MetaLeft => KeyLocation::Left, KeyCode::SuperLeft => KeyLocation::Left,
KeyCode::ShiftLeft => KeyLocation::Left, KeyCode::ShiftLeft => KeyLocation::Left,
KeyCode::AltLeft => KeyLocation::Left, KeyCode::AltLeft => KeyLocation::Left,
KeyCode::ControlLeft => KeyLocation::Left, KeyCode::ControlLeft => KeyLocation::Left,
@@ -300,38 +296,46 @@ const NX_DEVICERALTKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x000000
const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00002000); const NX_DEVICERCTLKEYMASK: NSEventModifierFlags = NSEventModifierFlags(0x00002000);
pub(super) fn lalt_pressed(event: &NSEvent) -> bool { pub(super) fn lalt_pressed(event: &NSEvent) -> bool {
event.modifierFlags().contains(NX_DEVICELALTKEYMASK) unsafe { event.modifierFlags() }.contains(NX_DEVICELALTKEYMASK)
} }
pub(super) fn ralt_pressed(event: &NSEvent) -> bool { pub(super) fn ralt_pressed(event: &NSEvent) -> bool {
event.modifierFlags().contains(NX_DEVICERALTKEYMASK) unsafe { event.modifierFlags() }.contains(NX_DEVICERALTKEYMASK)
} }
pub(super) fn event_mods(event: &NSEvent) -> Modifiers { pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
let flags = event.modifierFlags(); let flags = unsafe { event.modifierFlags() };
let mut state = ModifiersState::empty(); let mut state = ModifiersState::empty();
let mut pressed_mods = ModifiersKeys::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::LSHIFT, flags.contains(NX_DEVICELSHIFTKEYMASK));
pressed_mods.set(ModifiersKeys::RSHIFT, flags.contains(NX_DEVICERSHIFTKEYMASK)); 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::LCONTROL, flags.contains(NX_DEVICELCTLKEYMASK));
pressed_mods.set(ModifiersKeys::RCONTROL, flags.contains(NX_DEVICERCTLKEYMASK)); 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::LALT, flags.contains(NX_DEVICELALTKEYMASK));
pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK)); pressed_mods.set(ModifiersKeys::RALT, flags.contains(NX_DEVICERALTKEYMASK));
state.set(ModifiersState::META, flags.contains(NSEventModifierFlags::Command)); state.set(
pressed_mods.set(ModifiersKeys::LMETA, flags.contains(NX_DEVICELCMDKEYMASK)); ModifiersState::SUPER,
pressed_mods.set(ModifiersKeys::RMETA, flags.contains(NX_DEVICERCMDKEYMASK)); 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>> { pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
unsafe {
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2( NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
NSEventType::ApplicationDefined, NSEventType::ApplicationDefined,
NSPoint::new(0.0, 0.0), NSPoint::new(0.0, 0.0),
@@ -344,8 +348,9 @@ pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
0, 0,
) )
} }
}
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 { let code = match physical_key {
PhysicalKey::Code(code) => code, PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(_) => return None, PhysicalKey::Unidentified(_) => return None,
@@ -405,8 +410,8 @@ pub fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32> {
KeyCode::Backquote => Some(0x32), KeyCode::Backquote => Some(0x32),
KeyCode::Backspace => Some(0x33), KeyCode::Backspace => Some(0x33),
KeyCode::Escape => Some(0x35), KeyCode::Escape => Some(0x35),
KeyCode::MetaRight => Some(0x36), KeyCode::SuperRight => Some(0x36),
KeyCode::MetaLeft => Some(0x37), KeyCode::SuperLeft => Some(0x37),
KeyCode::ShiftLeft => Some(0x38), KeyCode::ShiftLeft => Some(0x38),
KeyCode::CapsLock => Some(0x39), KeyCode::CapsLock => Some(0x39),
KeyCode::AltLeft => Some(0x3a), KeyCode::AltLeft => Some(0x3a),
@@ -477,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: // Follows what Chromium and Firefox do:
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc // 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 // https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h
@@ -551,8 +556,8 @@ pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
0x33 => KeyCode::Backspace, 0x33 => KeyCode::Backspace,
// 0x34 => unknown, // kVK_Powerbook_KeypadEnter // 0x34 => unknown, // kVK_Powerbook_KeypadEnter
0x35 => KeyCode::Escape, 0x35 => KeyCode::Escape,
0x36 => KeyCode::MetaRight, 0x36 => KeyCode::SuperRight,
0x37 => KeyCode::MetaLeft, 0x37 => KeyCode::SuperLeft,
0x38 => KeyCode::ShiftLeft, 0x38 => KeyCode::ShiftLeft,
0x39 => KeyCode::CapsLock, 0x39 => KeyCode::CapsLock,
0x3a => KeyCode::AltLeft, 0x3a => KeyCode::AltLeft,

View File

@@ -1,38 +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::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use objc2::rc::{Retained, autoreleasepool}; use core_foundation::base::{CFIndex, CFRelease};
use objc2::runtime::ProtocolObject; use core_foundation::runloop::{
use objc2::{MainThreadMarker, available}; kCFRunLoopCommonModes, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopSourceContext,
CFRunLoopSourceCreate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
};
use objc2::rc::{autoreleasepool, Retained};
use objc2::{msg_send_id, sel, ClassType};
use objc2_app_kit::{ use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification, NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSWindow, NSApplicationWillTerminateNotification, NSWindow,
}; };
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopCommonModes}; use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject, NSObjectProtocol};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use rwh_06::HasDisplayHandle; use rwh_06::HasDisplayHandle;
use tracing::debug_span;
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver, tracing_observers};
use winit_common::foundation::create_observer;
use winit_core::application::ApplicationHandler;
use winit_core::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::app_state::AppState;
use super::cursor::CustomCursor; use super::cursor::CustomCursor;
use super::event::dummy_event; use super::event::dummy_event;
use super::monitor; use super::monitor;
use crate::ActivationPolicy; use super::observer::setup_control_flow_observers;
use crate::window::Window; 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)] #[derive(Debug)]
pub struct ActiveEventLoop { pub struct ActiveEventLoop {
@@ -65,29 +103,25 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_window( fn create_window(
&self, &self,
window_attributes: winit_core::window::WindowAttributes, window_attributes: crate::window::WindowAttributes,
) -> Result<Box<dyn winit_core::window::Window>, RequestError> { ) -> Result<Box<dyn crate::window::Window>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?)) Ok(Box::new(Window::new(self, window_attributes)?))
} }
fn create_custom_cursor( fn create_custom_cursor(
&self, &self,
source: CustomCursorSource, source: CustomCursorSource,
) -> Result<CoreCustomCursor, RequestError> { ) -> Result<RootCustomCursor, RequestError> {
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(source)?))) Ok(RootCustomCursor { inner: CustomCursor::new(source.inner)? })
} }
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> { fn available_monitors(&self) -> Box<dyn Iterator<Item = RootMonitorHandle>> {
Box::new( Box::new(monitor::available_monitors().into_iter().map(|inner| RootMonitorHandle { inner }))
monitor::available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
} }
fn primary_monitor(&self) -> Option<winit_core::monitor::MonitorHandle> { fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
let monitor = monitor::primary_monitor(); let monitor = monitor::primary_monitor();
Some(CoreMonitorHandle(Arc::new(monitor))) Some(RootMonitorHandle { inner: monitor })
} }
fn listen_device_events(&self, _allowed: DeviceEvents) {} fn listen_device_events(&self, _allowed: DeviceEvents) {}
@@ -95,8 +129,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn system_theme(&self) -> Option<Theme> { fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm); let app = NSApplication::sharedApplication(self.mtm);
// Dark appearance was introduced in macOS 10.14 if app.respondsToSelector(sel!(effectiveAppearance)) {
if available!(macos = 10.14) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else { } else {
Some(Theme::Light) Some(Theme::Light)
@@ -135,7 +168,6 @@ impl rwh_06::HasDisplayHandle for ActiveEventLoop {
} }
} }
#[derive(Debug)]
pub struct EventLoop { pub struct EventLoop {
/// Store a reference to the application for convenience. /// Store a reference to the application for convenience.
/// ///
@@ -145,24 +177,21 @@ pub struct EventLoop {
app_state: Rc<AppState>, app_state: Rc<AppState>,
window_target: ActiveEventLoop, window_target: ActiveEventLoop,
panic_info: Rc<PanicInfo>,
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated; // 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. // 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. // Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _did_finish_launching_observer: Retained<NSObject>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>, _will_terminate_observer: Retained<NSObject>,
_tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
_before_waiting_observer: MainRunLoopObserver,
_after_waiting_observer: MainRunLoopObserver,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct PlatformSpecificEventLoopAttributes { pub(crate) struct PlatformSpecificEventLoopAttributes {
pub activation_policy: Option<ActivationPolicy>, pub(crate) activation_policy: Option<ActivationPolicy>,
pub default_menu: bool, pub(crate) default_menu: bool,
pub activate_ignoring_other_apps: bool, pub(crate) activate_ignoring_other_apps: bool,
} }
impl Default for PlatformSpecificEventLoopAttributes { impl Default for PlatformSpecificEventLoopAttributes {
@@ -172,10 +201,22 @@ impl Default for PlatformSpecificEventLoopAttributes {
} }
impl EventLoop { impl EventLoop {
pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> { pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let mtm = MainThreadMarker::new() let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!"); .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 { let activation_policy = match attributes.activation_policy {
None => None, None => None,
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular), Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
@@ -188,16 +229,9 @@ impl EventLoop {
activation_policy, activation_policy,
attributes.default_menu, attributes.default_menu,
attributes.activate_ignoring_other_apps, attributes.activate_ignoring_other_apps,
) );
.ok_or_else(|| EventLoopError::RecreationAttempt)?;
// Initialize the application (if it has not already been). let center = unsafe { NSNotificationCenter::defaultCenter() };
let app = NSApplication::sharedApplication(mtm);
// Override `sendEvent:` on the application to forward to our application state.
override_send_event(&app);
let center = NSNotificationCenter::defaultCenter();
let weak_app_state = Rc::downgrade(&app_state); let weak_app_state = Rc::downgrade(&app_state);
let _did_finish_launching_observer = create_observer( let _did_finish_launching_observer = create_observer(
@@ -205,7 +239,6 @@ impl EventLoop {
// `applicationDidFinishLaunching:` // `applicationDidFinishLaunching:`
unsafe { NSApplicationDidFinishLaunchingNotification }, unsafe { NSApplicationDidFinishLaunchingNotification },
move |notification| { move |notification| {
let _entered = debug_span!("NSApplicationDidFinishLaunchingNotification").entered();
if let Some(app_state) = weak_app_state.upgrade() { if let Some(app_state) = weak_app_state.upgrade() {
app_state.did_finish_launching(notification); app_state.did_finish_launching(notification);
} }
@@ -218,55 +251,22 @@ impl EventLoop {
// `applicationWillTerminate:` // `applicationWillTerminate:`
unsafe { NSApplicationWillTerminateNotification }, unsafe { NSApplicationWillTerminateNotification },
move |notification| { move |notification| {
let _entered = debug_span!("NSApplicationWillTerminateNotification").entered();
if let Some(app_state) = weak_app_state.upgrade() { if let Some(app_state) = weak_app_state.upgrade() {
app_state.will_terminate(notification); app_state.will_terminate(notification);
} }
}, },
); );
let main_loop = MainRunLoop::get(mtm); let panic_info: Rc<PanicInfo> = Default::default();
let mode = unsafe { kCFRunLoopCommonModes }.unwrap(); setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
// Tracing observers have the lowest and highest orderings.
let _tracing_observers = tracing_observers(mtm).inspect(|(start, end)| {
main_loop.add_observer(start, mode);
main_loop.add_observer(end, mode);
});
let app_state_clone = Rc::clone(&app_state);
let _before_waiting_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::BeforeWaiting,
true,
// Queued with the second-lowest priority (tracing observers use the lowest) to ensure
// it is processed after other observers.
CFIndex::MAX - 1,
move |_| app_state_clone.cleared(),
);
main_loop.add_observer(&_before_waiting_observer, mode);
let app_state_clone = Rc::clone(&app_state);
let _after_waiting_observer = MainRunLoopObserver::new(
mtm,
CFRunLoopActivity::AfterWaiting,
true,
// Queued with the second-highest priority (tracing observers use the highest) to
// ensure it is processed before other observers.
CFIndex::MIN + 1,
move |_| app_state_clone.wakeup(),
);
main_loop.add_observer(&_after_waiting_observer, mode);
Ok(EventLoop { Ok(EventLoop {
app, app,
app_state: app_state.clone(), app_state: app_state.clone(),
window_target: ActiveEventLoop { app_state, mtm }, window_target: ActiveEventLoop { app_state, mtm },
panic_info,
_did_finish_launching_observer, _did_finish_launching_observer,
_will_terminate_observer, _will_terminate_observer,
_tracing_observers,
_before_waiting_observer,
_after_waiting_observer,
}) })
} }
@@ -274,16 +274,20 @@ impl EventLoop {
&self.window_target &self.window_target
} }
pub fn run_app<A: ApplicationHandler>(mut self, app: A) -> Result<(), EventLoopError> {
self.run_app_on_demand(app)
}
// NB: we don't base this on `pump_events` because for `MacOs` we can't support // NB: we don't base this on `pump_events` because for `MacOs` we can't support
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of // `pump_events` elegantly (we just ask to run the loop for a "short" amount of
// time and so a layered implementation would end up using a lot of CPU due to // time and so a layered implementation would end up using a lot of CPU due to
// redundant wake ups. // redundant wake ups.
pub fn run_app_on_demand<A: ApplicationHandler>( pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self, &mut self,
app: A, mut app: A,
) -> Result<(), EventLoopError> { ) -> Result<(), EventLoopError> {
self.app_state.clear_exit(); self.app_state.clear_exit();
self.app_state.set_event_handler(app, || { self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| { autoreleasepool(|_| {
// clear / normalize pump_events state // clear / normalize pump_events state
self.app_state.set_wait_timeout(None); self.app_state.set_wait_timeout(None);
@@ -297,8 +301,17 @@ impl EventLoop {
self.app_state.dispatch_init_events(); self.app_state.dispatch_init_events();
} }
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing. // SAFETY: We do not run the application re-entrantly
self.app.run(); 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() self.app_state.internal_exit()
}) })
@@ -310,9 +323,9 @@ impl EventLoop {
pub fn pump_app_events<A: ApplicationHandler>( pub fn pump_app_events<A: ApplicationHandler>(
&mut self, &mut self,
timeout: Option<Duration>, timeout: Option<Duration>,
app: A, mut app: A,
) -> PumpStatus { ) -> PumpStatus {
self.app_state.set_event_handler(app, || { self.app_state.set_event_handler(&mut app, || {
autoreleasepool(|_| { autoreleasepool(|_| {
// As a special case, if the application hasn't been launched yet then we at least // As a special case, if the application hasn't been launched yet then we at least
// run the loop until it has fully launched. // run the loop until it has fully launched.
@@ -320,7 +333,8 @@ impl EventLoop {
debug_assert!(!self.app_state.is_running()); debug_assert!(!self.app_state.is_running());
self.app_state.set_stop_on_launch(); 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 // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
// has launched // has launched
@@ -352,7 +366,17 @@ impl EventLoop {
}, },
} }
self.app_state.set_stop_on_redraw(true); 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() { if self.app_state.exiting() {
@@ -384,18 +408,87 @@ pub(super) fn stop_app_immediately(app: &NSApplication) {
}); });
} }
/// Tell all windows to close. /// Catches panics that happen inside `f` and when a panic
/// /// happens, stops the `sharedApplication`
/// This will synchronously trigger `WindowEvent::Destroyed` within #[inline]
/// `windowWillClose:`, giving the application one last chance to handle pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
/// those events. It doesn't matter if the user also ends up closing the mtm: MainThreadMarker,
/// windows in `Window`'s `Drop` impl, once a window has been closed once, it panic_info: Weak<PanicInfo>,
/// stays closed. f: F,
/// ) -> Option<R> {
/// This ensures that no windows linger on after the event loop has exited, match catch_unwind(f) {
/// see <https://github.com/rust-windowing/winit/issues/4135>. Ok(r) => Some(r),
pub(super) fn notify_windows_of_exit(app: &NSApplication) { Err(e) => {
for window in app.windows() { // It's important that we set the panic before requesting a `stop`
window.close(); // 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::rc::Retained;
use objc2::runtime::Sel; use objc2::runtime::Sel;
use objc2::{MainThreadMarker, sel}; use objc2::sel;
use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem}; use objc2_app_kit::{NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
use objc2_foundation::{NSBundle, NSProcessInfo, NSString, ns_string}; use objc2_foundation::{ns_string, MainThreadMarker, NSProcessInfo, NSString};
struct KeyEquivalent<'a> { struct KeyEquivalent<'a> {
key: &'a NSString, key: &'a NSString,
@@ -16,10 +16,7 @@ pub fn initialize(app: &NSApplication) {
menubar.addItem(&app_menu_item); menubar.addItem(&app_menu_item);
let app_menu = NSMenu::new(mtm); let app_menu = NSMenu::new(mtm);
let process_name = match NSBundle::mainBundle().name() { let process_name = NSProcessInfo::processInfo().processName();
Some(bundle_name) => bundle_name,
None => NSProcessInfo::processInfo().processName(),
};
// About menu item // About menu item
let about_item_title = ns_string!("About ").stringByAppendingString(&process_name); let about_item_title = ns_string!("About ").stringByAppendingString(&process_name);
@@ -51,7 +48,10 @@ pub fn initialize(app: &NSApplication) {
Some(sel!(hideOtherApplications:)), Some(sel!(hideOtherApplications:)),
Some(KeyEquivalent { Some(KeyEquivalent {
key: ns_string!("h"), key: ns_string!("h"),
masks: Some(NSEventModifierFlags::Option | NSEventModifierFlags::Command), masks: Some(
NSEventModifierFlags::NSEventModifierFlagOption
| NSEventModifierFlags::NSEventModifierFlagCommand,
),
}), }),
); );
@@ -82,7 +82,7 @@ pub fn initialize(app: &NSApplication) {
app_menu.addItem(&quit_item); app_menu.addItem(&quit_item);
app_menu_item.setSubmenu(Some(&app_menu)); app_menu_item.setSubmenu(Some(&app_menu));
app.setServicesMenu(Some(&services_menu)); unsafe { app.setServicesMenu(Some(&services_menu)) };
app.setMainMenu(Some(&menubar)); app.setMainMenu(Some(&menubar));
} }

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

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

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