Compare commits

..

17 Commits

Author SHA1 Message Date
John Nunley
d28c867b61 chore: Rebase on latest master
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:41 -08:00
John Nunley
cfd35d2708 ci: Missed a spot
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:11 -08:00
John Nunley
1b9c576f53 ci: Use -p option in CI
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:10 -08:00
John Nunley
52532d2579 chore: Fix failing CI
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:10 -08:00
John Nunley
4b28f10470 chore: fmt and doc fixes
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:10 -08:00
John Nunley
daf1304879 chore: Housekeeping
- Update CHANGELOG
- Add documentation to items I forgot to document
- Trait implementations

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:02:08 -08:00
John Nunley
c6e57430bb chore: Move over ControlFlow
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:01:49 -08:00
John Nunley
284f4c0558 feat: Move Event to winit-core
This requires a lot of restructuring on the backend side, as we need to
accommodate for backends not being able to construct these types
implicitly. In addition, we also need to expose some new APIs on these
types to make them usable.

Several breaking changes occurred in this commit.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:01:49 -08:00
John Nunley
859a6f109c feat: Move DeviceId to winit-core
This requires a similar restructuring as with WindowId.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:01:49 -08:00
John Nunley
a232f7bc77 feat: Move WindowId to winit-core
This commit moves WindowId to winit-core and replaces its inner type
with a platform-agnostic `u64`. This required some changes to many of
the backend in order to accommodate this new system.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 19:01:46 -08:00
John Nunley
48831f7910 feat: Re-export cursor types in winit-core/window
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:06 -08:00
John Nunley
114e6999d3 chore: Move parts of window.rs to winit-core
As winit no longer owns some of the types this requires some adjustments
in the Wayland backend.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:06 -08:00
John Nunley
2f197315d9 chore: Move most of keyboard.rs to winit-core
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:06 -08:00
John Nunley
557464489e chore: Move dpi.rs to winit-core wholesale
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:06 -08:00
John Nunley
623b3e5070 chore: Move part of error.rs to winit-core
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:06 -08:00
John Nunley
a97255e0fc chore: Bring up winit-core crate
Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:59:05 -08:00
John Nunley
3a817647d5 chore: Move winit files to winit subfolder
This will allow us to split off crates from the main winit crate.

Signed-off-by: John Nunley <dev@notgull.net>
2024-02-06 18:58:48 -08:00
429 changed files with 42880 additions and 46986 deletions

View File

@@ -1,9 +1,2 @@
# Allow rust-analyzer and local `cargo doc` invocations to pick up unreleased changelog entries [alias]
# run-wasm = ["run", "--release", "--package", "run-wasm", "--"]
# Note that these flags are (intentionally) not included when building from the downloaded crate.
[build]
rustdocflags = ["--cfg=unreleased_changelogs"]
rustflags = ["--cfg=unreleased_changelogs"]
[target.wasm32-unknown-unknown]
runner = "wasm-bindgen-test-runner"

View File

@@ -1,8 +0,0 @@
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
# chore(rustfmt): use nightly
7b0c7b6cb2c62767ca0c73c857b299883f55a883
# Rustfmt: use `group_imports`
2665c120981af548433645c6383b3580dd8f8fc4
# Use Taplo for TOML formatting
3398ebe467c43ccfd91916c5b81ff3c68f598556

36
.github/CODEOWNERS vendored
View File

@@ -1,26 +1,34 @@
# Android # Android
/winit-android @MarijnS95 /src/platform/android.rs @msiglreith @MarijnS95
/src/platform_impl/android @msiglreith @MarijnS95
# Apple (AppKit + UIKit) # iOS
/winit-appkit @madsmtm /src/platform/ios.rs @madsmtm
/winit-uikit @madsmtm /src/platform_impl/ios @madsmtm
/winit-common/src/core_foundation @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
# macOS
/src/platform/macos.rs @madsmtm
/src/platform_impl/macos @madsmtm
# 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 @msiglreith
/src/platform_impl/windows @msiglreith
# Orbital (Redox OS) # Orbital (Redox OS)
/winit-orbital @jackpot51 /src/platform/orbital.rs @jackpot51
/src/platform_impl/orbital @jackpot51

View File

@@ -1,4 +0,0 @@
---
name: Blank Issue
about: Create a blank issue.
---

View File

@@ -1,36 +0,0 @@
name: Android bug
description: Create an Android-specific bug report
labels:
- B - bug
- DS - android
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: description
attributes:
label: Description
description: Description of the problem you're having
validations:
required: true
- type: textarea
id: device
attributes:
label: Device and Android version
description: Which devices and Android versions are you seeing the problem on?
placeholder: |
Samsung Galaxy Z running Android Pie (API level 28),
Samsung Galaxy Z running Android 14 (API level 34),
Pixel 8 running Android 14 (API level 34)
validations:
required: true
- type: textarea
id: winit-version
attributes:
label: Winit version
description: What version of Winit are you using?
placeholder: 0.29.11
validations:
required: true

View File

@@ -1,37 +0,0 @@
name: macOS bug
description: Create a macOS-specific bug report
labels:
- B - bug
- DS - appkit
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: description
attributes:
label: Description
description: Description of the problem you're having
validations:
required: true
- type: textarea
id: os-version
attributes:
label: macOS version
description: What version of macOS are you using? Please paste in the output of `sw_vers`.
placeholder: |
ProductName: macOS
ProductVersion: 14.2.1
BuildVersion: 23C71
render: shell
validations:
required: true
- type: textarea
id: winit-version
attributes:
label: Winit version
description: What version of Winit are you using?
placeholder: 0.29.11
validations:
required: true

View File

@@ -1,37 +0,0 @@
name: iOS bug
description: Create an iOS/UIKit-specific bug report
labels:
- B - bug
- DS - uikit
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: description
attributes:
label: Description
description: Description of the problem you're having
validations:
required: true
- type: textarea
id: device
attributes:
label: Device and iOS version
description: Which devices and iOS versions are you seeing the problem on?
placeholder: |
iPhone 15 running iOS 14.0,
iPhone 15 running iOS 17.0,
MacBook Pro M2 Mac Catalyst running macOS 14.2,
iPhone simulator running iOS 17.0
validations:
required: true
- type: textarea
id: winit-version
attributes:
label: Winit version
description: What version of Winit are you using?
placeholder: 0.29.11
validations:
required: true

View File

@@ -1,41 +0,0 @@
name: Wayland bug
description: Create a Wayland-specific bug report
labels:
- B - bug
- DS - wayland
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: description
attributes:
label: Description
description: Description of the problem you're having
validations:
required: true
- type: textarea
id: debug
attributes:
label: Debugging output
description: Output of a binary run with `WAYLAND_DEBUG=1`
placeholder: |
[1234.5678] -> wl_display@1.get_registry(new id wl_registry@2)
[1234.5678] -> wl_display@1.sync(new id wl_callback@3)
...
render: shell
- type: checkboxes
attributes:
label: Window isn't shown unless you draw
options:
- label: I understand that windows aren't shown on Wayland unless I draw and present to them.
required: true
- type: textarea
id: winit-version
attributes:
label: Winit version
description: What version of Winit are you using?
placeholder: 0.29.11
validations:
required: true

View File

@@ -1,51 +0,0 @@
name: Web bug
description: Create a Web-specific bug report
labels:
- B - bug
- DS - web
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: description
attributes:
label: Description
description: Description of the problem you're having
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: Tested browsers
description: What browsers are you seeing the problem on?
options:
- Firefox
- Chrome
- Microsoft Edge
- Safari 13
- Safari 14
- Safari 15
- Safari 16
- Safari 17
- Safari (newer than listed)
multiple: true
validations:
required: true
- type: textarea
id: device
attributes:
label: Tested devices
description: Which device(s) are you using?
placeholder: 'iPhone 15, Lenovo ThinkPad X1, MacBook Pro M2, Samsung Galaxy Z, ...'
validations:
required: true
- type: textarea
id: winit-version
attributes:
label: Winit version
description: What version of Winit are you using?
placeholder: 0.29.11
validations:
required: true

View File

@@ -1,35 +0,0 @@
name: Windows bug
description: Create a Windows-specific bug report
labels:
- B - bug
- DS - win32
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: description
attributes:
label: Description
description: Description of the problem you're having
validations:
required: true
- type: textarea
id: os-version
attributes:
label: Windows version
description: What version of Windows are you using? Please paste in the output of the `ver` command.
placeholder: |
Microsoft Windows [Version 10.0.19042.2251]
render: shell
validations:
required: true
- type: textarea
id: winit-version
attributes:
label: Winit version
description: What version of Winit are you using?
placeholder: 0.29.11
validations:
required: true

View File

@@ -1,32 +0,0 @@
name: X11 bug
description: Create a X11-specific bug report
labels:
- B - bug
- DS - x11
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: description
attributes:
label: Description
description: Description of the problem you're having
validations:
required: true
- type: textarea
id: os-info
attributes:
label: OS and window mananger
description: Which operating system and window manager are you using?
validations:
required: true
- type: textarea
id: winit-version
attributes:
label: Winit version
description: What version of Winit are you using?
placeholder: 0.29.11
validations:
required: true

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Question
url: https://matrix.to/#/#rust-windowing:matrix.org
about: Please ask questions on the Matrix channel.

View File

@@ -1,26 +0,0 @@
name: Feature request
description: Propose a new feature
labels:
- S - enhancement
body:
- type: textarea
id: description
attributes:
label: Description
description: Description of the problem does this solve or what need does it fill? Please be mindful of the [scope](https://github.com/rust-windowing/winit/blob/master/FEATURES.md) of Winit.
validations:
required: true
- type: dropdown
id: platforms
attributes:
label: Relevant platforms
description: On which platforms is this feature relevant?
options:
- Windows
- macOS
- Wayland
- X11
- Web
- iOS
- Android
multiple: true

View File

@@ -1,4 +1,5 @@
- [ ] Tested on all platforms changed - [ ] Tested on all platforms changed
- [ ] Added an entry to the `changelog` module if knowledge of this change could be valuable to users - [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior - [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
- [ ] Created or updated an example program if it would help users understand this functionality - [ ] Created or updated an example program if it would help users understand this functionality
- [ ] Updated [feature matrix](https://github.com/rust-windowing/winit/blob/master/FEATURES.md), if new features were added or implemented

View File

@@ -1,22 +0,0 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
groups:
github-actions:
patterns:
- "*"
- package-ecosystem: npm
directory: src/platform_impl/web/script
schedule:
interval: daily
groups:
github-actions:
patterns:
- '*'
labels:
- "DS - web"

View File

@@ -10,44 +10,13 @@ jobs:
name: Check formatting name: Check formatting
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@stable
with: with:
components: rustfmt components: rustfmt
- name: Check Formatting - name: Check Formatting
run: cargo fmt -- --check run: cargo fmt -- --check
taplo:
name: Taplo
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: taiki-e/checkout-action@v1
- name: Install Taplo
uses: taiki-e/install-action@v2
with:
tool: taplo-cli
- name: Run Taplo
run: taplo fmt --check
typos:
name: Check for typos
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: taiki-e/checkout-action@v1
- uses: taiki-e/install-action@v2
with:
tool: typos-cli
- name: run typos
run: typos
- name: Typos info
if: failure()
run: |
echo 'To fix typos, please run `typos -w`'
echo 'To check for a diff, run `typos`'
echo 'You can find typos here: https://crates.io/crates/typos'
tests: tests:
name: Test ${{ matrix.toolchain }} ${{ matrix.platform.name }} name: Test ${{ matrix.toolchain }} ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }} runs-on: ${{ matrix.platform.os }}
@@ -55,7 +24,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
toolchain: [stable, nightly, '1.85'] toolchain: [stable, nightly, '1.70.0']
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,41 +35,27 @@ 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: '--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', target: x86_64-apple-darwin, os: macos-latest, }
- { name: 'macOS Aarch64', target: aarch64-apple-darwin, os: macos-latest, }
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, } - { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, } - { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
- { 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
- toolchain: nightly
platform: { name: 'Web' }
# 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.70.0'
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
- toolchain: '1.85'
platform: { name: 'Redox OS' }
include: include:
- toolchain: '1.85' - toolchain: '1.70.0'
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'
platform: { name: 'Web', target: wasm32-unknown-unknown, os: ubuntu-latest, test-options: -Zdoctest-xcompile }
- toolchain: 'nightly' - toolchain: 'nightly'
platform: { platform: {
name: 'Web Atomic', name: 'web Atomic',
target: wasm32-unknown-unknown, target: wasm32-unknown-unknown,
os: ubuntu-latest, os: ubuntu-latest,
options: '-Zbuild-std=panic_abort,std', options: '-Zbuild-std=panic_abort,std',
test-options: -Zdoctest-xcompile, rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
rustflags: '-Ctarget-feature=+atomics,+bulk-memory', components: rust-src,
components: rust-src,
} }
env: env:
@@ -110,21 +65,19 @@ jobs:
# Faster compilation and error on warnings # Faster compilation and error on warnings
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}' RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
RUSTDOCFLAGS: ${{ matrix.platform.rustflags }}
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }} OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
TEST_OPTIONS: ${{ matrix.platform.test-options }}
CMD: ${{ matrix.platform.cmd }} CMD: ${{ matrix.platform.cmd }}
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: actions/checkout@v3
- name: Restore cache of cargo folder - name: Restore cache of cargo folder
# We use `restore` and later `save`, so that we can create the key after # We use `restore` and later `save`, so that we can create the key after
# 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@v5 uses: actions/cache/restore@v3
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 +89,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
- 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 +98,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@v5 uses: actions/cache@v3
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,11 +113,6 @@ 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
if: contains(matrix.platform.target, 'wasm32') && matrix.toolchain == 'nightly'
with:
tool: wasm-bindgen-cli
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }} toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
@@ -177,111 +125,51 @@ jobs:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}' RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
- name: Build crate - name: Build crate
run: cargo $CMD build $OPTIONS run: cargo $CMD build -p winit $OPTIONS
- name: Test winit core
run: cargo test -p winit-core
- name: Test winit Android
if: contains(matrix.platform.target, 'android')
run: cargo $CMD test -p winit-android --features native-activity --no-run
- name: Test winit Common (EventHandler)
run: cargo $CMD test -p winit-common --features event-handler --no-run
- name: Test winit Common (CF)
if: contains(matrix.platform.target, 'apple')
run: cargo $CMD test -p winit-common --features core-foundation --no-run
- name: Test winit Common (XKB)
if: contains(matrix.platform.target, 'linux-gnu')
run: cargo $CMD test -p winit-common --features xkb,x11,wayland --no-run
- name: Test winit AppKit
if: contains(matrix.platform.target, 'macos')
run: cargo $CMD test -p winit-appkit $OPTIONS
- name: Test winit Orbital
if: contains(matrix.platform.target, 'redox')
run: cargo test -p winit-orbital
- name: Test winit UIKit
if: contains(matrix.platform.target, 'ios')
# TODO: Run on Simulator
run: cargo $CMD test -p winit-uikit $OPTIONS --no-run
- name: Test winit Web
if: contains(matrix.platform.target, 'wasm')
run: cargo $CMD test -p winit-web $OPTIONS --no-run
- name: Test winit Win32
if: contains(matrix.platform.target, 'windows')
run: cargo $CMD test -p winit-win32 $OPTIONS
- name: Test winit X11
if: contains(matrix.platform.target, 'linux-gnu')
run: cargo $CMD test -p winit-x11 --target=${{ matrix.platform.target }}
- name: Test winit Wayland
if: contains(matrix.platform.target, 'linux-gnu')
run: cargo $CMD test -p winit-wayland --target=${{ matrix.platform.target }}
# Test only on Linux x86_64, so we avoid spending unnecessary CI hours.
- name: Test dpi crate
if: >
contains(matrix.platform.name, 'Linux 64bit') &&
matrix.toolchain != '1.85'
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.70.0'
run: cargo $CMD test --no-run $OPTIONS run: cargo $CMD test -p winit --no-run $OPTIONS
- name: Run tests - name: Run tests
if: > if: >
!contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.85' matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS run: cargo $CMD test $OPTIONS
- name: Lint with clippy - name: Lint with clippy
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features') if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
run: cargo clippy --all-targets $OPTIONS $TEST_OPTIONS -- -Dwarnings run: cargo clippy --all-targets $OPTIONS -- -Dwarnings
- 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.70.0'
run: cargo $CMD test --no-run $OPTIONS $TEST_OPTIONS --features serde run: cargo $CMD test --no-run $OPTIONS -p winit --features serde
- name: Run tests with serde enabled - name: Run tests with serde enabled
if: > if: >
!contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'android') &&
!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'ios') &&
(!contains(matrix.platform.target, 'wasm32') || matrix.toolchain == 'nightly') && !contains(matrix.platform.target, 'wasm32') &&
!contains(matrix.platform.target, 'redox') && !contains(matrix.platform.target, 'redox') &&
matrix.toolchain != '1.85' matrix.toolchain != '1.70.0'
run: cargo $CMD test $OPTIONS $TEST_OPTIONS --features serde run: cargo $CMD test $OPTIONS --features serde
- name: Check docs.rs documentation - name: Check docs.rs documentation
if: matrix.toolchain == 'nightly' if: matrix.toolchain == 'nightly'
run: cargo doc --no-deps $OPTIONS --features=serde,mint,android-native-activity run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
env: env:
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs --cfg=unreleased_changelogs' RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs'
# See restore step above # See restore step above
- name: Save cache of cargo folder - name: Save cache of cargo folder
uses: actions/cache/save@v5 uses: actions/cache/save@v3
with: with:
path: | path: |
~/.cargo/registry/index/ ~/.cargo/registry/index/
@@ -290,45 +178,26 @@ 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: x86_64-apple-darwin }
- { name: 'Redox OS', target: x86_64-unknown-redox }
- { name: 'web', target: wasm32-unknown-unknown }
- { name: 'Windows', target: x86_64-pc-windows-gnu }
steps: steps:
- uses: taiki-e/checkout-action@v1 - uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v2 - uses: EmbarkStudios/cargo-deny-action@v1
with: with:
command: check
log-level: error log-level: error
arguments: --all-features --target ${{ matrix.platform.target }}
eslint:
name: ESLint
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./winit-web/src/script
steps:
- uses: taiki-e/checkout-action@v1
- name: Setup NPM
run: npm install
- name: Run ESLint
run: npx eslint@9.38.0
swc:
name: Minimize JavaScript
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./winit-web/src/script
steps:
- uses: taiki-e/checkout-action@v1
- name: Install SWC
run: sudo npm i -g @swc/cli
- name: Run SWC
run: |
swc . --ignore node_modules,**/*.d.ts --only **/*.ts -d . --out-file-extension min.js
- name: Check for diff
run: |
[[ -z $(git status -s) ]]

View File

@@ -17,9 +17,9 @@ jobs:
contents: read contents: read
pages: write pages: write
id-token: write id-token: write
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
@@ -27,12 +27,12 @@ jobs:
- name: Run Rustdoc - name: Run Rustdoc
env: env:
RUSTDOCFLAGS: --crate-version master --cfg=docsrs --cfg=unreleased_changelogs RUSTDOCFLAGS: --crate-version master --cfg=docsrs
run: | run: |
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=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v6 uses: actions/configure-pages@v4
- 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@v4 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

6
.gitignore vendored
View File

@@ -3,8 +3,8 @@ target/
rls/ rls/
.vscode/ .vscode/
*~ *~
*.wasm
*.ts
*.js
#*# #*#
.DS_Store .DS_Store
# NPM package used to run ESLint.
/src/platform_impl/web/script/node_modules
/src/platform_impl/web/script/package-lock.json

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
# Code of Conduct
The `rust-windowing` project adheres to the [Rust Code of Conduct]. This
describes the minimum behavior expected from all contributors.
[Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct

View File

@@ -1,162 +1,71 @@
# Contribution Guidelines # Winit Contributing Guidelines
This document contains guidelines for contributing code to winit. It has to be ## Scope
followed in order for your patch to be approved and applied. [See `FEATURES.md`](./FEATURES.md). When requesting or implementing a new Winit feature, you should
consider whether or not it's directly related to window creation or input handling. If it isn't, it
may be worth creating a separate crate that extends Winit's API to add that functionality.
## Contributing
Anyone can contribute to winit, however given that it's a cross platform ## Reporting an issue
windowing toolkit getting certain changes incorporated could be challenging.
To save your time it's wise to check already opened [pull requests][prs] and When reporting an issue, in order to help the maintainers understand what the problem is, please make
[issues][issues]. In general, bug fixes and missing implementations are always your description of the issue as detailed as possible:
accepted, however larger new API proposals should go into the issue first. When
in doubt contact us on [Matrix][matrix] or via opening an issue.
### Windows - if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
reproduce the issue, ideally by providing a minimal program exhibiting the problem
- if it is a feature request, please provide a clear argumentation about why you believe this feature
should be supported by winit
To run the examples on Windows, you must have symlinks enabled, see ## Making a pull request
[this][git-windows-symlinks].
[git-windows-symlinks]: https://gitforwindows.org/symbolic-links.html When making a code contribution to winit, before opening your pull request, please make sure that:
### Submitting your work and handling review - your patch builds with Winit's minimal supported rust version - Rust 1.70.
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
were not tested, and what should be tested, so that a maintainer or another contributor can test them
- you updated any relevant documentation in winit
- you left comments in your code explaining any part that is not straightforward, so that the
maintainers and future contributors don't have to try to guess what your code is supposed to do
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users.
All patches have to be sent on Github as [pull requests][prs]. To simplify your You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will
life during review it's recommended to check the "give contributors write access handle those for you when merging (see below).
to the branch" checkbox. - if your PR affects the platform compatibility of one or more features or adds another feature, the
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
should be updated.
We use unstable Rustfmt options across the project, so please run Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
`cargo +nightly fmt` before submitting your work. If you are unable to do so, is that a PR must be approved by at least two maintainers of winit before being merged, including
the maintainers can do it for you before merging, just state so in your pull at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
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. Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in
`CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the
"give contributors write access to the branch" checkbox when creating the PR.
[toolchains]: https://rust-lang.github.io/rustup/concepts/toolchains.html ## Maintainers & Testers
#### Handling review The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
During the review process certain events could require an action from your side, If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
common patterns and reactions are described below.
_Event:_ The CI fails to build, but it looks like it is not your fault. Not ## Release process
communicating so could result in maintainers not looking into your patch,
since they may assume that you're still working on it.\
_Desired behaviour:_ Write a message saying roughly the following "The CI
failure is unrelated", so that the maintainers will fix it for you.
_Event:_ Maintainer requested changes to your PR.\
_Desired behavior:_ Once you address the request, you should re-request a review
with GitHub's UI. If you don't agree with what maintainer suggested, you
should state your objections and re-request the review. That will indicate that
the ball is on maintainer's side.
_Event:_ You've opened a PR, but maintainer shortly after commented that they
want to work on that themselves.\
_Desired behavior:_ Discuss with the maintainer regarding their plans if they
were not outlined in the initial response, because such response means that they
are not interested in reviewing your code. Such thing could happen when
underestimating complexity of the task you're solving or when your patch
mandate certain downstream designs. In general, the maintainer will likely
close your PR in order to prevent work being done on it.
[prs]: https://github.com/rust-windowing/winit/pulls
[issues]: https://github.com/rust-windowing/winit/issues
[matrix]: https://matrix.to/#/#rust-windowing:matrix.org
## Maintainers
Winit has plenty of maintainers with different backgrounds, different time
available to work on Winit, and reasons to be winit maintainer in the first
place. To ensure that Winit's code quality does not decrease over time and to
make it easier to teach new maintainers the "winit way of doing things" the
common policies and routines are defined in this section.
The current maintainers for each platform are listed in [this file][CODEOWNERS].
### Contributions handling
The maintainers must ensure that the external contributions meet Winit's
quality standards. If it's not, it **is the maintainer's responsibility** to
bring it on par, which includes:
- Ensure that formatting is consistent and `CHANGELOG` messages are clear
for the end users.
- Improve the commit message, so it'll be easier for other maintainers to
understand the motivation without going through all the discussions on the
particular patch/PR.
- Ensure that the proposed patch doesn't break platform parity. If the
breakage is desired by contributor, an issue should be opened to discuss
with other maintainers before merging.
- Always fix CI issues before merging if they don't originate from the
submitted work.
However, maintainers should give some leeway for external contributors, so they
don't feel discouraged contributing, for example:
- Suggest a patch to resolve style issues, if it's the only issue with the
submitted work. Keep in mind that pushing the resolution yourself is not
desired, because the contributor might not agree with what you did.
- Be more explicit on how things should be done if you don't like the
approach.
- Suggest to finish the PR for them if they're absent for a while and you need
the proposed changes to move forward with something. In such cases the
maintainer must preserve attribution with `Co-authored-by`, `Suggested-by`,
or keep the original committer.
- Rebase their work for them when massive changes to the Winit codebase were
introduced.
When reviewing code of other maintainers all of the above is on the maintainer
who submitted the patch. Interested maintainers could help push the work over
the finish line, but teaching other maintainers should be preferred.
For a _regular_ contributor to winit, the maintainer should slowly start
requiring contributor to match *maintainer* quality standards when writing
patches and commit messages.
### Contributing
When submitting a patch, the maintainer should follow the general contributing
guidelines, however greater attention to detail is expected in this case.
To make life simpler for other maintainers it's suggested to create your branch
under the project repository instead of your own fork. The naming scheme is
`github_user_name/branch_name`. Doing so will make your work easier to rebase
for other maintainers when you're absent.
### Administrative Actions
Some things (such as changing required CI steps, adding contributors, ...)
require administrative permissions. If you don't have those, ask about the
change in an issue. If you have the permissions, discuss it with at least one
other admin before making the change.
### Release process
Given that winit is a widely used library, we should be able to make a patch Given that winit is a widely used library, we should be able to make a patch
releases at any time we want without blocking the development of new features. releases at any time we want without blocking the development of new features.
To achieve these goals, a new branch is created for every new release. Releases To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch.
and later patch releases are committed and tagged in this branch.
The exact steps for an exemplary `0.2.0` release might look like this: The exact steps for an exemplary `0.2.0` release might look like this:
1. Initially, the version on the latest master is `0.1.0` 1. Initially, the version on the latest master is `0.1.0`
2. A new `v0.2.x` branch is created for the release 2. A new `v0.2.x` branch is created for the release
3. Update released `cfg_attr` in `src/changelog/mod.rs` to `v0.2.md` 3. In the branch, the version is bumped to `v0.2.0`
4. Move entries from `src/changelog/unreleased.md` into 4. The new commit in the branch is tagged `v0.2.0`
`src/changelog/v0.2.md` 5. The version is pushed to crates.io
5. In the branch, the version is bumped to `v0.2.0` 6. A GitHub release is created for the `v0.2.0` tag
6. The new commit in the branch is tagged `v0.2.0` 7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated
7. The version is pushed to crates.io
8. A GitHub release is created for the `v0.2.0` tag
9. On master, the version is bumped to `0.2.0`, and the changelog is updated
When doing a patch release, the process is similar: When doing a patch release, the process is similar:
1. Initially, the version of the latest release is `0.2.0` 1. Initially, the version of the latest release is `0.2.0`
2. Checkout the `v0.2.x` branch 2. Checkout the `v0.2.x` branch
3. Cherry-pick the required non-breaking changes into the `v0.2.x` 3. Cherry-pick the required non-breaking changes into the `v0.2.x`
4. Follow steps 4-9 of the regular release example 4. Follow steps 3-7 of the regular release example
[CODEOWNERS]: .github/CODEOWNERS

View File

@@ -1,97 +1,16 @@
[workspace] [workspace]
default-members = ["winit"] members = [
members = ["dpi", "winit*"] "run-wasm",
"winit",
"winit-core"
]
resolver = "2" resolver = "2"
[workspace.package]
edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/rust-windowing/winit"
rust-version = "1.85"
version = "0.31.0-beta.2"
[workspace.dependencies] [workspace.dependencies]
# Workspace dependencies.
# `winit` has no version here to allow using it in dev deps for docs.
winit = { path = "winit" }
winit-android = { version = "=0.31.0-beta.2", path = "winit-android" }
winit-appkit = { version = "=0.31.0-beta.2", path = "winit-appkit" }
winit-common = { version = "=0.31.0-beta.2", path = "winit-common" }
winit-core = { version = "=0.31.0-beta.2", path = "winit-core" }
winit-orbital = { version = "=0.31.0-beta.2", path = "winit-orbital" }
winit-uikit = { version = "=0.31.0-beta.2", path = "winit-uikit" }
winit-wayland = { version = "=0.31.0-beta.2", path = "winit-wayland", default-features = false }
winit-web = { version = "=0.31.0-beta.2", path = "winit-web" }
winit-win32 = { version = "=0.31.0-beta.2", path = "winit-win32" }
winit-x11 = { version = "=0.31.0-beta.2", path = "winit-x11" }
# Core dependencies.
bitflags = "2" bitflags = "2"
cfg_aliases = "0.2.1" cfg_aliases = "0.2.0"
cursor-icon = "1.1.0" cursor-icon = "1.1.0"
dpi = { version = "0.1.2", path = "dpi" }
keyboard-types = "0.8.0"
mint = "0.5.6"
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
serde = { version = "1", features = ["serde_derive"] } serde = { version = "1", features = ["serde_derive"] }
smol_str = "0.3" smol_str = "0.2.0"
tracing = { version = "0.1.40", default-features = false }
# Dev dependencies.
image = { version = "0.25.0", default-features = false }
softbuffer = { version = "0.4.8", default-features = false, features = [
"x11",
"x11-dlopen",
"wayland",
"wayland-dlopen",
] }
tracing-subscriber = "0.3.18"
# Android dependencies.
android-activity = "0.6.0"
ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
# Apple dependencies.
block2 = "0.6.1"
dispatch2 = { version = "0.3.0", default-features = false, features = ["std", "objc2"] }
objc2 = { version = "0.6.1", features = ["relax-sign-encoding"] }
objc2-app-kit = { version = "0.3.2", default-features = false }
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.
unicode-segmentation = "1.7.1"
windows-sys = "0.61"
# Linux dependencies.
bytemuck = { version = "1.13.1", default-features = false }
calloop = "0.14.3"
foldhash = { version = "0.2.0", default-features = false, features = ["std"] }
libc = "0.2.64"
memmap2 = "0.9.0"
percent-encoding = "2.0"
rustix = { version = "1.0.7", default-features = false }
x11-dl = "2.19.1"
x11rb = { version = "0.13.0", default-features = false }
xkbcommon-dl = "0.4.2"
# Orbital dependencies.
libredox = "0.1.12"
orbclient = { version = "0.3.47", default-features = false }
redox_event = { package = "redox_event", version = "0.4.5" }
# Web dependencies.
atomic-waker = "1"
concurrent-queue = { version = "2", default-features = false }
console_error_panic_hook = "0.1"
js-sys = "0.3.70"
pin-project = "1"
tracing-web = "0.1"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
wasm-bindgen-test = "0.3"
web-time = "1" web-time = "1"
web_sys = { package = "web-sys", version = "0.3.70" } winit-core = { path = "./winit-core", default-features = false, features = ["std"] }

View File

@@ -3,8 +3,8 @@
Winit aims to expose an interface that abstracts over window creation and input handling and can Winit aims to expose an interface that abstracts over window creation and input handling and can
be used to create both games and applications. It supports the following main graphical platforms: be used to create both games and applications. It supports the following main graphical platforms:
- Desktop - Desktop
- Windows - Windows 7+ (10+ is tested regularly)
- macOS - macOS 10.7+ (10.14+ is tested regularly)
- Unix - Unix
- via X11 - via X11
- via Wayland - via Wayland
@@ -13,6 +13,9 @@ be used to create both games and applications. It supports the following main gr
- iOS - iOS
- Android - Android
- Web - Web
- Chrome
- Firefox
- Safari 13.1+
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
aim to support every single feature of every platform, but rather to abstract over the common features aim to support every single feature of every platform, but rather to abstract over the common features
@@ -47,3 +50,206 @@ through the implementation work necessary to function on all platforms. When one
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature. gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
If that gets accepted, the platform-specific functions get deprecated and become permanently If that gets accepted, the platform-specific functions get deprecated and become permanently
exposed through the core, cross-platform API. exposed through the core, cross-platform API.
# Features
## Extending this section
If your PR makes notable changes to Winit's features, please update this section as follows:
- If your PR adds a new feature, add a brief description to the relevant section. If the feature is a core
feature, add a row to the feature matrix and describe what platforms the feature has been implemented on.
- If your PR begins a new API rework, add a row to the `Pending API Reworks` table. If the PR implements the
API rework on all relevant platforms, please move it to the `Completed API Reworks` table.
- If your PR implements an already-existing feature on a new platform, either mark the feature as *completed*,
or mark it as *mostly completed* and link to an issue describing the problems with the implementation.
## Core
### Windowing
- **Window initialization**: Winit allows the creation of a window
- **Providing pointer to init OpenGL**: Winit provides the necessary pointers to initialize a working opengl context
- **Providing pointer to init Vulkan**: Same as OpenGL but for Vulkan
- **Window decorations**: The windows created by winit are properly decorated, and the decorations can
be deactivated
- **Window decorations toggle**: Decorations can be turned on or off after window creation
- **Window resizing**: The windows created by winit can be resized and generate the appropriate events
when they are. The application can precisely control its window size if desired.
- **Window resize increments**: When the window gets resized, the application can choose to snap the window's
size to specific values.
- **Window transparency**: Winit allows the creation of windows with a transparent background.
- **Window maximization**: The windows created by winit can be maximized upon creation.
- **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after
creation.
- **Window minimization**: The windows created by winit can be minimized after creation.
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
creation.
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
for fullscreen windows and, if applicable, captures the monitor for exclusive
use by this application.
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
get drawn above their owner.
### System Information
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth).
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
- **Cursor image**: Changing the cursor to your own image.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events.
- **Touch pressure**: Touch events contain information about the amount of force being applied.
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
- **Keyboard events**: Properly processing keyboard events using the user-specified keymap and
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
## Platform
### Windows
* Setting the name of the internal window class
* Setting the taskbar icon
* Setting the parent window
* Setting a menu bar
* `WS_EX_NOREDIRECTIONBITMAP` support
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
* Changing a system-drawn backdrop
* Setting the window border color
* Setting the title bar background color
* Setting the title color
* Setting the corner rounding preference
### macOS
* Window activation policy
* Window movable by background
* Transparent titlebar
* Hidden titlebar
* Hidden titlebar buttons
* Full-size content view
* Accepts first mouse
* Set a preferred theme and get current theme.
### Unix
* Window urgency
* X11 Window Class
* X11 Override Redirect Flag
* GTK Theme Variant
* Base window size
* Setting the X11 parent window
### iOS
* `winit` has a minimum OS requirement of iOS 8
* Get the `UIScreen` object pointer
* Setting the `UIView` hidpi factor
* Valid orientations
* Home indicator visibility
* Status bar visibility and style
* Deferring system gestures
* Getting the device idiom
* Getting the preferred video mode
### Web
* Get if the systems preferred color scheme is "dark"
## Usability
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
## Compatibility Matrix
Legend:
- ✔️: Works as intended
- ▢: Mostly works, but some bugs are known
- ❌: Missing feature or large bugs making it unusable
- **N/A**: Not applicable for this platform
- ❓: Unknown status
### Windowing
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** |
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
### Pending API Reworks
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
### Completed API Reworks
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
[#165]: https://github.com/rust-windowing/winit/issues/165
[#219]: https://github.com/rust-windowing/winit/issues/219
[#242]: https://github.com/rust-windowing/winit/issues/242
[#306]: https://github.com/rust-windowing/winit/issues/306
[#315]: https://github.com/rust-windowing/winit/issues/315
[#319]: https://github.com/rust-windowing/winit/issues/319
[#33]: https://github.com/rust-windowing/winit/issues/33
[#459]: https://github.com/rust-windowing/winit/issues/459
[#5]: https://github.com/rust-windowing/winit/issues/5
[#63]: https://github.com/rust-windowing/winit/issues/63
[#720]: https://github.com/rust-windowing/winit/issues/720
[#721]: https://github.com/rust-windowing/winit/issues/721
[#750]: https://github.com/rust-windowing/winit/issues/750
[#753]: https://github.com/rust-windowing/winit/issues/753
[#804]: https://github.com/rust-windowing/winit/issues/804

141
README.md
View File

@@ -2,13 +2,13 @@
[![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit) [![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit)
[![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit) [![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit)
[![UNSTABLE docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=UNSTABLE%20docs [![Master Docs](https://img.shields.io/github/actions/workflow/status/rust-windowing/winit/docs.yml?branch=master&label=master%20docs
)](https://rust-windowing.github.io/winit/winit/index.html) )](https://rust-windowing.github.io/winit/winit/index.html)
[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions) [![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions)
```toml ```toml
[dependencies] [dependencies]
winit = "0.31.0-beta.2" winit = "0.29.10"
``` ```
## [Documentation](https://docs.rs/winit) ## [Documentation](https://docs.rs/winit)
@@ -19,9 +19,9 @@ For features _outside_ the scope of winit, see [Are we GUI Yet?](https://arewegu
## Contact Us ## Contact Us
Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. Join us in our [![Matrix](https://img.shields.io/badge/Matrix-%23rust--windowing%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [![Libera.Chat](https://img.shields.io/badge/libera.chat-%23winit-red.svg)](https://web.libera.chat/#winit).
The maintainers have a meeting every friday at UTC 15. The meeting notes can be found [here](https://hackmd.io/@winit-meetings). The maintainers have a meeting every friday at UTC 14. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
## Usage ## Usage
@@ -33,13 +33,17 @@ 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 ### Cargo Features
For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md). Winit provides the following features, which can be enabled in your `Cargo.toml` file:
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
* `x11` (enabled by default): On Unix platform, compiles with the X11 backend
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
* `mint`: Enables mint (math interoperability standard types) conversions.
## 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.70**. 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
@@ -54,15 +58,12 @@ Where `sid` is the current version of `rustc` provided by [Debian Sid], and
[Debian Sid]: https://packages.debian.org/sid/rustc [Debian Sid]: https://packages.debian.org/sid/rustc
An exception is made for the Android platform, where a higher Rust version The exception is for the Android platform, where a higher Rust version
must be used for certain Android features. In this case, the MSRV will be must be used for certain Android features. In this case, the MSRV will be
capped at the latest stable version of Rust minus three. This inconsistency is capped at the latest stable version of Rust minus three. This inconsistency is
not reflected in Cargo metadata, as it is not powerful enough to expose this not reflected in Cargo metadata, as it is not powerful enough to expose this
restriction. restriction.
Redox OS is also not covered by this MSRV policy, as it requires a Rust nightly
toolchain to compile.
All crates in the [`rust-windowing`] organizations have the All crates in the [`rust-windowing`] organizations have the
same MSRV policy. same MSRV policy.
@@ -70,10 +71,118 @@ 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. #### Wayland
### Repository License Note that windows don't appear on Wayland until you draw/present to them.
Note that the license in `LICENSE` doesn't apply in full to the DPI package [./dpi](./dpi). #### Web
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 --> To run the web example: `cargo run-wasm --example web`
Winit supports compiling to the `wasm32-unknown-unknown` target with `web-sys`.
On the web platform, a Winit window is backed by a `<canvas>` element. You can
either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
create a `<canvas>` element which you can then retrieve][web canvas getter] and
insert it into the DOM yourself.
For the example code using Winit on Web, check out the [web example]. For
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
book].
[web with_canvas]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowBuilderExtWebSys.html#tymethod.with_canvas
[web canvas getter]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowExtWebSys.html#tymethod.canvas
[web example]: ./examples/web.rs
[Rust and WebAssembly book]: https://rustwasm.github.io/book/
#### Android
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/latest/ndk/) crate.
Native Android applications need some form of "glue" crate that is responsible
for defining the main entry point for your Rust application as well as tracking
various life-cycle events and synchronizing with the main JVM thread.
Winit uses the [android-activity](https://github.com/rib/android-activity) as a
glue crate (prior to `0.28` it used
[ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)).
The version of the glue crate that your application depends on _must_ match the
version that Winit depends on because the glue crate is responsible for your
application's main entry point. If Cargo resolves multiple versions, they will
clash.
`winit` glue compatibility table:
| winit | ndk-glue |
| :---: | :--------------------------: |
| 0.29 | `android-activity = "0.5"` |
| 0.28 | `android-activity = "0.4"` |
| 0.27 | `ndk-glue = "0.7"` |
| 0.26 | `ndk-glue = "0.5"` |
| 0.25 | `ndk-glue = "0.3"` |
| 0.24 | `ndk-glue = "0.2"` |
The recommended way to avoid a conflict with the glue version is to avoid explicitly
depending on the `android-activity` crate, and instead consume the API that
is re-exported by Winit under `winit::platform::android::activity::*`
Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
```toml
[lib]
name = "main"
crate-type = ["cdylib"]
```
All Android applications are based on an `Activity` subclass, and the
`android-activity` crate is designed to support different choices for this base
class. Your application _must_ specify the base class it needs via a feature flag:
| Base Class | Feature Flag | Notes |
| :--------------: | :---------------: | :-----: |
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
[`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
[agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
[Gradle]: https://developer.android.com/studio/build
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
##### Converting from `ndk-glue` to `android-activity`
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
1. Remove `ndk-glue` from your `Cargo.toml`
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.10", features = [ "android-native-activity" ] }`
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
#### MacOS
A lot of functionality expects the application to be ready before you start
doing anything; this includes creating windows, fetching monitors, drawing,
and so on, see issues [#2238], [#2051] and [#2087].
If you encounter problems, you should try doing your initialization inside
`Event::Resumed`.
#### iOS
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
by all UI-related code (see issue [#1705]). It would be best to consider creating your windows
inside `Event::Resumed`.
[#2238]: https://github.com/rust-windowing/winit/issues/2238
[#2051]: https://github.com/rust-windowing/winit/issues/2051
[#2087]: https://github.com/rust-windowing/winit/issues/2087
[#1705]: https://github.com/rust-windowing/winit/issues/1705
#### Redox OS
Redox OS has some functionality not yet present that will be implemented when
its orbital display server provides it.

View File

@@ -1,27 +1,15 @@
# 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 = "web_sys::window", reason = "is not available in every context" },
{ 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 = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ allow-invalid = true, path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" }, { path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ allow-invalid = true, path = "web_sys::MouseEvent::buttons", reason = "Use `backend::event::cursor_buttons()` to avoid wrong conversions" }, { path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
{ allow-invalid = true, path = "web_sys::MouseEvent::button", reason = "Use `backend::event::cursor_button()` to avoid wrong conversions" }, { path = "icrate::AppKit::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 = "web_sys::PointerEvent::pointer_type", reason = "Use `WebPointerType` to emit warnings" }, { path = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
{ allow-invalid = true, path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" },
{ allow-invalid = true, path = "web_sys::window", reason = "is not available in every context" },
] ]

100
deny.toml
View File

@@ -1,61 +1,65 @@
# 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 --all-features --log-level error --target aarch64-apple-ios check
[graph] # Note: running just `cargo deny check` without a `--target` will result in
all-features = true # false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
exclude-dev = true
targets = [ targets = [
"aarch64-apple-darwin", { triple = "aarch64-apple-ios" },
"aarch64-apple-ios", { triple = "aarch64-linux-android" },
"aarch64-linux-android", { triple = "i686-pc-windows-gnu" },
"i686-pc-windows-gnu", { triple = "i686-pc-windows-msvc" },
"i686-pc-windows-msvc", { triple = "i686-unknown-linux-gnu" },
"i686-unknown-linux-gnu", { triple = "wasm32-unknown-unknown" },
{ triple = "wasm32-unknown-unknown", features = [ { triple = "x86_64-apple-darwin" },
"atomics", { triple = "x86_64-apple-ios" },
] }, { triple = "x86_64-pc-windows-gnu" },
"x86_64-apple-darwin", { triple = "x86_64-pc-windows-msvc" },
"x86_64-apple-ios", { triple = "x86_64-unknown-linux-gnu" },
"x86_64-pc-windows-gnu", { triple = "x86_64-unknown-redox" },
"x86_64-pc-windows-msvc",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-redox",
] ]
[licenses]
allow = [ [advisories]
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) vulnerability = "deny"
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) unmaintained = "warn"
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) yanked = "deny"
"ISC", # https://tldrlegal.com/license/isc-license ignore = []
"MIT", # https://tldrlegal.com/license/mit-license
"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
private = { ignore = true }
[bans] [bans]
multiple-versions = "deny" multiple-versions = "deny"
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
deny = []
skip = [ skip = [
{ 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" }, { name = "raw-window-handle" }, # we intentionally have multiple versions of this
{ crate = "thiserror@1.0", reason = "dep of `ndk` crate, yet to be updated" }, { name = "bitflags" }, # the ecosystem is in the process of migrating.
{ crate = "thiserror-impl@1.0", reason = "dep of `thiserror`" }, { name = "libloading" }, # x11rb uses a different version until the next update
{ crate = "objc2@0.5", reason = "used by crossfont" },
{ crate = "objc2-foundation@0.2", reason = "used by crossfont" },
] ]
skip-tree = [ skip-tree = [
{ crate = "windows-sys", reason = "foundational but bumps fairly often, nothing we can do about it not having a shared version" }, { name = "run-wasm", version = "0.1.0" }
] ]
[bans.build]
bypass = [ [licenses]
{ crate = "android-activity", allow-globs = ["android-games-sdk/import-games-sdk.sh"] }, private = { ignore = true }
{ crate = "freetype-sys", allow-globs = ["freetype2/*"] }, unlicensed = "deny"
# `crossfont` still depends (partially transitively) on `winapi`. allow-osi-fsf-free = "neither"
{ crate = "winapi-i686-pc-windows-gnu", allow-globs = ["lib/lib*.a"] }, confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
{ crate = "winapi-x86_64-pc-windows-gnu", allow-globs = ["lib/lib*.a"] }, copyleft = "deny"
allow = [
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
"ISC", # https://tldrlegal.com/license/-isc-license
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
"MIT-0", # https://choosealicense.com/licenses/mit-0/
"MIT", # https://tldrlegal.com/license/mit-license
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
] ]
include-archives = true
interpreted = "deny"

11
docs/res/ATTRIBUTION.md Normal file
View File

@@ -0,0 +1,11 @@
# 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.

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

@@ -1,25 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
Please keep one empty line before and after all headers. (This is required for
`git` to produce a conflict when a release is made while a PR is open and the
PR's changelog entry would go into the wrong section).
And please only add new entries to the top of this list, right below the `#
Unreleased` header.
## Unreleased
## 0.1.2
- Added `Insets`, `LogicalInsets` and `PhysicalInsets` types.
- Make `no_std` compatible. If you use this functionality, DPI's license has changed.
## 0.1.1
- Derive `Debug`, `Copy`, `Clone`, `PartialEq`, `Serialize`, `Deserialize` traits for `PixelUnit`.
## 0.1.0
- Add `LogicalUnit`, `PhysicalUnit` and `PixelUnit` types and related functions.

View File

@@ -1,46 +0,0 @@
[package]
categories = ["gui"]
description = "Types for handling UI scaling"
edition.workspace = true
keywords = ["DPI", "HiDPI", "scale-factor"]
# N.B. This is "AND", because of the imported libm code.
license = "Apache-2.0 AND MIT"
name = "dpi"
repository.workspace = true
rust-version.workspace = true
version = "0.1.2"
[features]
default = ["std"]
mint = ["dep:mint"]
serde = ["dep:serde"]
# Access mathematical functions using the standard library implementations
std = []
[dependencies]
mint = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
[package.metadata.docs.rs]
features = ["mint", "serde"]
# 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",
]

View File

@@ -1 +0,0 @@
../LICENSE

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.

File diff suppressed because it is too large Load Diff

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

8
run-wasm/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "run-wasm"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
cargo-run-wasm = "0.2.0"

3
run-wasm/src/main.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }");
}

View File

@@ -1,20 +1,3 @@
comment_width = 100 force_explicit_abi=true
condense_wildcard_suffixes = true use_field_init_shorthand=true
error_on_unformatted = true # merge_imports=true
format_code_in_doc_comments = true
format_macro_bodies = true
format_macro_matchers = true
format_strings = true
group_imports = "StdExternalCrate"
hex_literal_case = "Lower"
imports_granularity = "Module"
match_block_trailing_comma = true
newline_style = "Unix"
normalize_comments = true
normalize_doc_attributes = true
overflow_delimited_expr = true
# Some macros break with this.
reorder_impl_items = false
use_field_init_shorthand = true
use_small_heuristics = "Max"
wrap_comments = true

View File

@@ -1,5 +0,0 @@
[formatting]
allowed_blank_lines = 1
column_width = 100
indent_string = ' '
reorder_keys = true

36
tests/send_objects.rs Normal file
View File

@@ -0,0 +1,36 @@
#[allow(dead_code)]
fn needs_send<T: Send>() {}
#[test]
fn event_loop_proxy_send() {
#[allow(dead_code)]
fn is_send<T: 'static + Send>() {
// ensures that `winit::EventLoopProxy` implements `Send`
needs_send::<winit::event_loop::EventLoopProxy<T>>();
}
}
#[test]
fn window_send() {
// ensures that `winit::Window` implements `Send`
needs_send::<winit::window::Window>();
}
#[test]
fn window_builder_send() {
needs_send::<winit::window::WindowBuilder>();
}
#[test]
fn ids_send() {
// ensures that the various `..Id` types implement `Send`
needs_send::<winit::window::WindowId>();
needs_send::<winit::event::DeviceId>();
needs_send::<winit::monitor::MonitorHandle>();
}
#[test]
fn custom_cursor_send() {
needs_send::<winit::window::CustomCursorBuilder>();
needs_send::<winit::window::CustomCursor>();
}

View File

@@ -1,10 +1,12 @@
#![cfg(feature = "serde")] #![cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use winit::cursor::CursorIcon; use winit::{
use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase}; event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase},
use winit::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey, PhysicalKey}; keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey, PhysicalKey},
window::CursorIcon,
};
#[allow(dead_code)] #[allow(dead_code)]
fn needs_serde<S: Serialize + Deserialize<'static>>() {} fn needs_serde<S: Serialize + Deserialize<'static>>() {}

19
tests/sync_object.rs Normal file
View File

@@ -0,0 +1,19 @@
#[allow(dead_code)]
fn needs_sync<T: Sync>() {}
#[test]
fn window_sync() {
// ensures that `winit::Window` implements `Sync`
needs_sync::<winit::window::Window>();
}
#[test]
fn window_builder_sync() {
needs_sync::<winit::window::WindowBuilder>();
}
#[test]
fn custom_cursor_sync() {
needs_sync::<winit::window::CustomCursorBuilder>();
needs_sync::<winit::window::CustomCursor>();
}

View File

@@ -1,10 +0,0 @@
# documentation: https://github.com/crate-ci/typos/blob/master/docs/reference.md
[default.extend-identifiers]
TME_LEAVE = "TME_LEAVE" # From windows_sys::Win32::UI::Input::KeyboardAndMouse::TME_LEAVE
XF86_Calculater = "XF86_Calculater" # From xkbcommon_dl::keysyms::XF86_Calculater
ptd = "ptd" # From windows_sys::Win32::System::Com::FORMATETC { ptd, ..}
requestor = "requestor" # From x11_dl::xlib::XSelectionEvent { requestor ..}
[files]
extend-exclude = ["*.drawio"]

View File

@@ -1,35 +0,0 @@
[package]
description = "Winit's Android backend"
documentation = "https://docs.rs/winit-android"
edition.workspace = true
license.workspace = true
name = "winit-android"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[features]
game-activity = ["android-activity/game-activity"]
native-activity = ["android-activity/native-activity"]
serde = ["dep:serde", "bitflags/serde", "smol_str/serde", "dpi/serde", "winit-core/serde"]
[dependencies]
bitflags.workspace = true
dpi.workspace = true
rwh_06.workspace = true
serde = { workspace = true, optional = true }
smol_str.workspace = true
tracing.workspace = true
winit-core.workspace = true
# Platform-specific
[target.'cfg(target_os = "android")'.dependencies]
android-activity.workspace = true
ndk.workspace = true
[dev-dependencies]
winit.workspace = true
[package.metadata.docs.rs]
features = ["serde", "native-activity"]
targets = ["aarch64-linux-android"]

View File

@@ -1 +0,0 @@
../LICENSE

View File

@@ -1 +0,0 @@
../README.md

File diff suppressed because it is too large Load Diff

View File

@@ -1,165 +0,0 @@
//! # Android
//!
//! The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/) crate.
//!
//! Native Android applications need some form of "glue" crate that is responsible
//! for defining the main entry point for your Rust application as well as tracking
//! various life-cycle events and synchronizing with the main JVM thread.
//!
//! Winit uses the [android-activity](https://docs.rs/android-activity/) as a
//! glue crate (prior to `0.28` it used
//! [ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)).
//!
//! The version of the glue crate that your application depends on _must_ match the
//! version that Winit depends on because the glue crate is responsible for your
//! application's main entry point. If Cargo resolves multiple versions, they will
//! clash.
//!
//! `winit` glue compatibility table:
//!
//! | winit | ndk-glue |
//! | :---: | :--------------------------: |
//! | 0.30 | `android-activity = "0.6"` |
//! | 0.29 | `android-activity = "0.5"` |
//! | 0.28 | `android-activity = "0.4"` |
//! | 0.27 | `ndk-glue = "0.7"` |
//! | 0.26 | `ndk-glue = "0.5"` |
//! | 0.25 | `ndk-glue = "0.3"` |
//! | 0.24 | `ndk-glue = "0.2"` |
//!
//! The recommended way to avoid a conflict with the glue version is to avoid explicitly
//! depending on the `android-activity` crate, and instead consume the API that
//! is re-exported by Winit under `winit::platform::android::activity::*`
//!
//! Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
//!
//! ```toml
//! [lib]
//! name = "main"
//! crate-type = ["cdylib"]
//! ```
//!
//! All Android applications are based on an `Activity` subclass, and the
//! `android-activity` crate is designed to support different choices for this base
//! class. Your application _must_ specify the base class it needs via a feature flag:
//!
//! | Base Class | Feature Flag | Notes |
//! | :--------------: | :---------------: | :-----: |
//! | `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
//! | [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
//!
//! [`GameActivity`]: https://developer.android.com/games/agdk/game-activity
//! [`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
//! [`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
//! [agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games
//! [agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
//! [Gradle]: https://developer.android.com/studio/build
//!
//! For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
//!
//! ## Converting from `ndk-glue` to `android-activity`
//!
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
//! with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version =
//! "0.31.0-beta.2", features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
//! logging as above).
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your
//! event loop (as shown above).
#![cfg(target_os = "android")]
mod event_loop;
mod keycodes;
use winit_core::event_loop::ActiveEventLoop as CoreActiveEventLoop;
use winit_core::window::Window as CoreWindow;
use self::activity::{AndroidApp, ConfigurationRef, Rect};
pub use crate::event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
PlatformSpecificWindowAttributes, Window,
};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
fn android_app(&self) -> &AndroidApp;
}
/// Additional methods on [`Window`] that are specific to Android.
pub trait WindowExtAndroid {
fn content_rect(&self) -> Rect;
fn config(&self) -> ConfigurationRef;
}
impl WindowExtAndroid for dyn CoreWindow + '_ {
fn content_rect(&self) -> Rect {
let window = self.cast_ref::<Window>().unwrap();
window.content_rect()
}
fn config(&self) -> ConfigurationRef {
let window = self.cast_ref::<Window>().unwrap();
window.config()
}
}
impl ActiveEventLoopExtAndroid for dyn CoreActiveEventLoop + '_ {
fn android_app(&self) -> &AndroidApp {
let event_loop = self.cast_ref::<ActiveEventLoop>().unwrap();
&event_loop.app
}
}
pub trait EventLoopBuilderExtAndroid {
/// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop
///
/// This must be called on Android since the [`AndroidApp`] is not global state.
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self;
/// Calling this will mark the volume keys to be manually handled by the application
///
/// Default is to let the operating system handle the volume keys
fn handle_volume_keys(&mut self) -> &mut Self;
}
/// Re-export of the `android_activity` API
///
/// Winit re-exports the `android_activity` API for convenience so that most
/// applications can rely on the Winit crate to resolve the required version of
/// `android_activity` and avoid any chance of a conflict between Winit and the
/// application crate.
///
/// Unlike most libraries there can only be a single implementation
/// of the `android_activity` glue crate linked with an application because
/// it is responsible for the application's `android_main()` entry point.
///
/// Since Winit depends on a specific version of `android_activity` the simplest
/// way to avoid creating a conflict is for applications to avoid explicitly
/// depending on the `android_activity` crate, and instead consume the API that
/// is re-exported by Winit.
///
/// For compatibility applications should then import the [`AndroidApp`] type for
/// their `android_main(app: AndroidApp)` function like:
/// ```rust
/// #[cfg(target_os = "android")]
/// use winit::platform::android::activity::AndroidApp;
/// ```
pub mod activity {
// We enable the `"native-activity"` feature just so that we can build the
// docs, but it'll be very confusing for users to see the docs with that
// feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page.
#[doc(no_inline)]
pub use android_activity::*;
}

View File

@@ -1,117 +0,0 @@
[package]
description = "Winit's Appkit / macOS backend"
documentation = "https://docs.rs/winit-appkit"
edition.workspace = true
license.workspace = true
name = "winit-appkit"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[features]
serde = ["dep:serde", "bitflags/serde", "smol_str/serde", "dpi/serde"]
[dependencies]
bitflags.workspace = true
dpi.workspace = true
rwh_06.workspace = true
serde = { workspace = true, optional = true }
smol_str.workspace = true
tracing.workspace = true
winit-core.workspace = true
# Platform-specific
[target.'cfg(target_vendor = "apple")'.dependencies]
block2.workspace = true
dispatch2 = { workspace = true, features = ["std", "objc2"] }
objc2.workspace = true
objc2-app-kit = { workspace = true, features = [
"std",
"objc2-core-foundation",
"NSAppearance",
"NSApplication",
"NSBitmapImageRep",
"NSButton",
"NSColor",
"NSControl",
"NSCursor",
"NSDragging",
"NSEvent",
"NSGraphics",
"NSGraphicsContext",
"NSImage",
"NSImageRep",
"NSMenu",
"NSMenuItem",
"NSOpenGLView",
"NSPanel",
"NSPasteboard",
"NSResponder",
"NSRunningApplication",
"NSScreen",
"NSTextInputClient",
"NSTextInputContext",
"NSTrackingArea",
"NSToolbar",
"NSView",
"NSWindow",
"NSWindowScripting",
"NSWindowTabGroup",
] }
objc2-core-foundation = { workspace = true, features = [
"std",
"block2",
"CFBase",
"CFCGTypes",
"CFData",
"CFRunLoop",
"CFString",
"CFUUID",
] }
objc2-core-graphics = { workspace = true, features = [
"std",
"libc",
"CGDirectDisplay",
"CGDisplayConfiguration",
"CGDisplayFade",
"CGError",
"CGRemoteOperation",
"CGWindowLevel",
] }
objc2-core-video = { workspace = true, features = [
"std",
"objc2-core-graphics",
"CVBase",
"CVReturn",
"CVDisplayLink",
] }
objc2-foundation = { workspace = true, features = [
"std",
"block2",
"objc2-core-foundation",
"NSArray",
"NSAttributedString",
"NSData",
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSGeometry",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSOperation",
"NSPathUtilities",
"NSProcessInfo",
"NSRunLoop",
"NSString",
"NSThread",
"NSValue",
] }
winit-common = { workspace = true, features = ["core-foundation", "event-handler", "foundation"] }
[dev-dependencies]
winit.workspace = true
[package.metadata.docs.rs]
all-features = true
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin"]

View File

@@ -1 +0,0 @@
../LICENSE

View File

@@ -1 +0,0 @@
../README.md

View File

@@ -1,189 +0,0 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::rc::Rc;
use std::{mem, ptr};
use dispatch2::MainThreadBound;
use objc2::runtime::{Imp, Sel};
use objc2::sel;
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::MainThreadMarker;
use tracing::trace_span;
use winit_core::event::{DeviceEvent, ElementState};
use super::app_state::AppState;
type SendEvent = extern "C-unwind" fn(&NSApplication, Sel, &NSEvent);
static ORIGINAL: MainThreadBound<Cell<Option<SendEvent>>> = {
// SAFETY: Creating in a `const` context, where there is no concept of the main thread.
MainThreadBound::new(Cell::new(None), unsafe { MainThreadMarker::new_unchecked() })
};
extern "C-unwind" fn send_event(app: &NSApplication, sel: Sel, event: &NSEvent) {
// This can be a bit noisy, since `event` is fairly large. Note that you can use
// `RUST_LOG='trace,winit_appkit::app=warn'` if you're debugging and want TRACE-level logs but
// not this.
let _entered = trace_span!("sendEvent:", ?event).entered();
let mtm = MainThreadMarker::from(app);
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
// Overriding `sendEvent:` fixes that. (https://stackoverflow.com/a/15294196)
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
//
// For posterity, there are some undocumented event types
// (https://github.com/servo/cocoa-rs/issues/155)
// but that doesn't really matter here.
let event_type = event.r#type();
let modifier_flags = event.modifierFlags();
if event_type == NSEventType::KeyUp && modifier_flags.contains(NSEventModifierFlags::Command) {
if let Some(key_window) = app.keyWindow() {
key_window.sendEvent(event);
}
return;
}
// Events are generally scoped to the window level, so the best way
// to get device events is to listen for them on NSApplication.
let app_state = AppState::get(mtm);
maybe_dispatch_device_event(&app_state, event);
let original = ORIGINAL.get(mtm).get().expect("no existing sendEvent: handler set");
original(app, sel, event)
}
/// Override the [`sendEvent:`][NSApplication::sendEvent] method on the given application class.
///
/// The previous implementation created a subclass of [`NSApplication`], however we would like to
/// give the user full control over their `NSApplication`, so we override the method here using
/// method swizzling instead.
///
/// This _should_ also allow two versions of Winit to exist in the same application.
///
/// See the following links for more info on method swizzling:
/// - <https://nshipster.com/method-swizzling/>
/// - <https://spin.atomicobject.com/method-swizzling-objective-c/>
/// - <https://web.archive.org/web/20130308110627/http://cocoadev.com/wiki/MethodSwizzling>
///
/// NOTE: This function assumes that the passed in application object is the one returned from
/// [`NSApplication::sharedApplication`], i.e. the one and only global shared application object.
/// For testing though, we allow it to be a different object.
pub(crate) fn override_send_event(global_app: &NSApplication) {
let mtm = MainThreadMarker::from(global_app);
let class = global_app.class();
let method =
class.instance_method(sel!(sendEvent:)).expect("NSApplication must have sendEvent: method");
// SAFETY: Converting our `sendEvent:` implementation to an IMP.
let overridden = unsafe { mem::transmute::<SendEvent, Imp>(send_event) };
// If we've already overridden the method, don't do anything.
if ptr::fn_addr_eq(overridden, method.implementation()) {
return;
}
// SAFETY: Our implementation has:
// 1. The same signature as `sendEvent:`.
// 2. Does not impose extra safety requirements on callers.
let original = unsafe { method.set_implementation(overridden) };
// SAFETY: This is the actual signature of `sendEvent:`.
let original = unsafe { mem::transmute::<Imp, SendEvent>(original) };
// NOTE: If NSApplication was safe to use from multiple threads, then this would potentially be
// a (checked) race-condition, since one could call `sendEvent:` before the original had been
// stored here.
//
// It is only usable from the main thread, however, so we're good!
ORIGINAL.get(mtm).set(Some(original));
}
fn maybe_dispatch_device_event(app_state: &Rc<AppState>, event: &NSEvent) {
let event_type = event.r#type();
#[allow(non_upper_case_globals)]
match event_type {
NSEventType::MouseMoved
| NSEventType::LeftMouseDragged
| NSEventType::OtherMouseDragged
| NSEventType::RightMouseDragged => {
let delta_x = event.deltaX() as f64;
let delta_y = 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 = 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 = 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,
});
});
},
_ => (),
}
}
#[cfg(test)]
mod tests {
use objc2::rc::Retained;
use objc2::{ClassType, define_class, msg_send};
use objc2_app_kit::NSResponder;
use objc2_foundation::NSObject;
use super::*;
#[test]
fn test_override() {
// FIXME(madsmtm): Ensure this always runs (maybe use cargo-nextest or `--test-threads=1`?)
let Some(mtm) = MainThreadMarker::new() else { return };
// Create a new application, without making it the shared application.
let app = NSApplication::new(mtm);
override_send_event(&app);
// Test calling twice works.
override_send_event(&app);
// FIXME(madsmtm): Can't test this yet, need some way to mock AppState.
// let event = super::super::event::dummy_event().unwrap();
// app.sendEvent(&event)
}
#[test]
fn test_custom_class() {
let Some(_mtm) = MainThreadMarker::new() else { return };
define_class!(
#[unsafe(super(NSApplication, NSResponder, NSObject))]
#[name = "TestApplication"]
pub(super) struct TestApplication;
impl TestApplication {
#[unsafe(method(sendEvent:))]
fn send_event(&self, _event: &NSEvent) {
todo!()
}
}
);
let app: Retained<TestApplication> = unsafe { msg_send![TestApplication::class(), new] };
override_send_event(&app);
}
}

View File

@@ -1,381 +0,0 @@
use std::cell::{Cell, OnceCell, RefCell};
use std::mem;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Instant;
use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::NSNotification;
use winit_common::core_foundation::{EventLoopProxy, 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::menu;
use super::observer::EventLoopWaker;
#[derive(Debug)]
pub(super) struct AppState {
mtm: MainThreadMarker,
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
run_loop: MainRunLoop,
event_loop_proxy: Arc<EventLoopProxy>,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
stop_before_wait: Cell<bool>,
stop_after_wait: Cell<bool>,
stop_on_redraw: Cell<bool>,
/// Whether `applicationDidFinishLaunching:` has been run or not.
is_launched: Cell<bool>,
/// Whether an `EventLoop` is currently running.
is_running: Cell<bool>,
/// Whether the user has requested the event loop to exit.
exit: Cell<bool>,
control_flow: Cell<ControlFlow>,
waker: RefCell<EventLoopWaker>,
start_time: Cell<Option<Instant>>,
wait_timeout: Cell<Option<Instant>>,
pending_redraw: RefCell<Vec<WindowId>>,
// NOTE: This is strongly referenced by our `NSWindowDelegate` and our `NSView` subclass, and
// as such should be careful to not add fields that, in turn, strongly reference those.
}
// SAFETY: Creating `MainThreadBound` in a `const` context, where there is no concept of the
// main thread.
static GLOBAL: MainThreadBound<OnceCell<Rc<AppState>>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
impl AppState {
pub(super) fn setup_global(
mtm: MainThreadMarker,
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Option<Rc<Self>> {
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}));
let this = Rc::new(Self {
mtm,
activation_policy,
default_menu,
activate_ignoring_other_apps,
run_loop: MainRunLoop::get(mtm),
event_loop_proxy,
event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false),
stop_before_wait: Cell::new(false),
stop_after_wait: Cell::new(false),
stop_on_redraw: Cell::new(false),
is_launched: Cell::new(false),
is_running: Cell::new(false),
exit: Cell::new(false),
control_flow: Cell::new(ControlFlow::default()),
waker: RefCell::new(EventLoopWaker::new()),
start_time: Cell::new(None),
wait_timeout: Cell::new(None),
pending_redraw: RefCell::new(vec![]),
});
GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this))
}
pub fn get(mtm: MainThreadMarker) -> Rc<Self> {
GLOBAL
.get(mtm)
.get()
.expect("tried to get application state before it was registered")
.clone()
}
// NOTE: This notification will, globally, only be emitted once,
// no matter how many `EventLoop`s the user creates.
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
self.is_launched.set(true);
let app = NSApplication::sharedApplication(self.mtm);
// We need to delay setting the activation policy and activating the app
// until `applicationDidFinishLaunching` has been called. Otherwise the
// menu bar is initially unresponsive on macOS 10.15.
if let Some(activation_policy) = self.activation_policy {
app.setActivationPolicy(activation_policy);
} else {
// If no activation policy is explicitly provided, and the application
// is bundled, do not set the activation policy at all, to allow the
// package manifest to define the behavior via LSUIElement.
//
// See:
// - https://github.com/rust-windowing/winit/issues/261
// - https://github.com/rust-windowing/winit/issues/3958
let is_bundled =
NSRunningApplication::currentApplication().bundleIdentifier().is_some();
if !is_bundled {
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
}
}
#[allow(deprecated)]
app.activateIgnoringOtherApps(self.activate_ignoring_other_apps);
if self.default_menu {
// The menubar initialization should be before the `NewEvents` event, to allow
// overriding of the default menu even if it's created
menu::initialize(&app);
}
self.waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
// If the application is being launched via `EventLoop::pump_app_events()` then we'll
// want to stop the app once it is launched (and return to the external loop)
//
// In this case we still want to consider Winit's `EventLoop` to be "running",
// so we call `start_running()` above.
if self.stop_on_launch.get() {
// NOTE: the original idea had been to only stop the underlying `RunLoop`
// for the app but that didn't work as expected (`-[NSApplication run]`
// effectively ignored the attempt to stop the RunLoop and re-started it).
//
// So we return from `pump_events` by stopping the application.
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
}
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
let app = NSApplication::sharedApplication(self.mtm);
notify_windows_of_exit(&app);
self.event_handler.terminate();
self.internal_exit();
}
/// Place the event handler in the application state for the duration
/// of the given closure.
pub fn set_event_handler<R>(
&self,
handler: impl ApplicationHandler,
closure: impl FnOnce() -> R,
) -> R {
self.event_handler.set(Box::new(handler), closure)
}
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
&self.event_loop_proxy
}
/// If `pump_events` is called to progress the event loop then we
/// bootstrap the event loop via `-[NSApplication run]` but will use
/// `CFRunLoopRunInMode` for subsequent calls to `pump_events`.
pub fn set_stop_on_launch(&self) {
self.stop_on_launch.set(true);
}
pub fn set_stop_before_wait(&self, value: bool) {
self.stop_before_wait.set(value)
}
pub fn set_stop_after_wait(&self, value: bool) {
self.stop_after_wait.set(value)
}
pub fn set_stop_on_redraw(&self, value: bool) {
self.stop_on_redraw.set(value)
}
pub fn set_wait_timeout(&self, value: Option<Instant>) {
self.wait_timeout.set(value)
}
/// Clears the `running` state and resets the `control_flow` state when an `EventLoop` exits.
///
/// NOTE: that if the `NSApplication` has been launched then that state is preserved,
/// and we won't need to re-launch the app if subsequent EventLoops are run.
pub fn internal_exit(self: &Rc<Self>) {
self.set_is_running(false);
self.set_stop_on_redraw(false);
self.set_stop_before_wait(false);
self.set_stop_after_wait(false);
self.set_wait_timeout(None);
}
pub fn is_launched(&self) -> bool {
self.is_launched.get()
}
pub fn set_is_running(&self, value: bool) {
self.is_running.set(value)
}
pub fn is_running(&self) -> bool {
self.is_running.get()
}
pub fn exit(&self) {
self.exit.set(true)
}
pub fn clear_exit(&self) {
self.exit.set(false)
}
pub fn exiting(&self) -> bool {
self.exit.get()
}
pub fn set_control_flow(&self, value: ControlFlow) {
self.control_flow.set(value)
}
pub fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub fn handle_redraw(self: &Rc<Self>, window_id: WindowId) {
// Redraw request might come out of order from the OS.
// -> Don't go back into the event handler when our callstack originates from there
if !self.event_handler.in_use() {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
});
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested
// events as a way to ensure that `pump_events` can't block an external loop
// indefinitely
if self.stop_on_redraw.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
}
}
pub fn queue_redraw(&self, window_id: WindowId) {
let mut pending_redraw = self.pending_redraw.borrow_mut();
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
self.run_loop.wake_up();
}
#[track_caller]
pub fn maybe_queue_with_handler(
self: &Rc<Self>,
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop) + 'static,
) {
// Most programmer actions in AppKit (e.g. change window fullscreen, set focused, etc.)
// result in an event being queued, and applied at a later point.
//
// However, it is not documented which actions do this, and which ones are done immediately,
// so to make sure that we don't encounter re-entrancy issues, we first check if we're
// currently handling another event, and if we are, we queue the event instead.
if !self.event_handler.in_use() {
self.with_handler(callback);
} else {
tracing::debug!("had to queue event since another is currently being handled");
let this = Rc::clone(self);
self.run_loop.queue_closure(move || {
this.with_handler(callback);
});
}
}
#[track_caller]
fn with_handler(
self: &Rc<Self>,
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop),
) {
let event_loop = ActiveEventLoop { app_state: Rc::clone(self), mtm: self.mtm };
self.event_handler.handle(|app| callback(app, &event_loop));
}
/// dispatch `NewEvents(Init)` + `Resumed`
pub fn dispatch_init_events(self: &Rc<Self>) {
self.with_handler(|app, event_loop| app.new_events(event_loop, StartCause::Init));
// NB: For consistency all platforms must call `can_create_surfaces` even though macOS
// applications don't themselves have a formal surface destroy/create lifecycle.
self.with_handler(|app, event_loop| app.can_create_surfaces(event_loop));
}
// Called by RunLoopObserver after finishing waiting for new events
pub fn wakeup(self: &Rc<Self>) {
// 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 !self.event_handler.ready() || !self.is_running() {
return;
}
if self.stop_after_wait.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
let start = self.start_time.get().unwrap();
let cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
ControlFlow::WaitUntil(requested_resume) => {
if Instant::now() >= requested_resume {
StartCause::ResumeTimeReached { start, requested_resume }
} else {
StartCause::WaitCancelled { start, requested_resume: Some(requested_resume) }
}
},
};
self.with_handler(|app, event_loop| app.new_events(event_loop, cause));
}
// Called by RunLoopObserver before waiting for new events
pub fn cleared(self: &Rc<Self>) {
// 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 !self.event_handler.ready() || !self.is_running() {
return;
}
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
});
}
self.with_handler(|app, event_loop| {
app.about_to_wait(event_loop);
});
if self.exiting() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
notify_windows_of_exit(&app);
}
if self.stop_before_wait.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
self.start_time.set(Some(Instant::now()));
let wait_timeout = self.wait_timeout.get(); // configured by pump_events
let app_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Instant::now()),
ControlFlow::WaitUntil(instant) => Some(instant),
};
self.waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
}
}
/// Returns the minimum `Option<Instant>`, taking into account that `None`
/// equates to an infinite timeout, not a zero timeout (so can't just use
/// `Option::min`)
fn min_timeout(a: Option<Instant>, b: Option<Instant>) -> Option<Instant> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}

View File

@@ -1,366 +0,0 @@
use std::ffi::c_uchar;
use std::slice;
use std::sync::OnceLock;
use objc2::rc::Retained;
use objc2::runtime::Sel;
use objc2::{AllocAnyThread, ClassType, available, msg_send, sel};
use objc2_app_kit::{
NSBitmapImageRep, NSCursor, NSCursorFrameResizeDirections, NSCursorFrameResizePosition,
NSDeviceRGBColorSpace, NSImage,
};
use objc2_foundation::{
NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString, ns_string,
};
use winit_core::cursor::{CursorIcon, CursorImage, CustomCursorProvider, CustomCursorSource};
use winit_core::error::{NotSupportedError, RequestError};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CustomCursor(pub(crate) Retained<NSCursor>);
impl CustomCursorProvider for CustomCursor {
fn is_animated(&self) -> bool {
false
}
}
// SAFETY: NSCursor is immutable and thread-safe
// TODO(madsmtm): Put this logic in objc2-app-kit itself
unsafe impl Send for CustomCursor {}
unsafe impl Sync for CustomCursor {}
impl CustomCursor {
pub(crate) fn new(cursor: CustomCursorSource) -> Result<CustomCursor, RequestError> {
let cursor = match cursor {
CustomCursorSource::Image(cursor_image) => cursor_image,
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
return Err(NotSupportedError::new("unsupported cursor kind").into());
},
};
cursor_from_image(&cursor).map(Self)
}
}
pub(crate) fn 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.buffer().len()) };
bitmap_data.copy_from_slice(cursor.buffer());
let image = NSImage::initWithSize(NSImage::alloc(), NSSize::new(width.into(), height.into()));
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 unsafe { msg_send![cls, respondsToSelector: sel] } {
let cursor: Retained<NSCursor> = unsafe { msg_send![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"));
#[allow(deprecated)]
let info: Retained<NSDictionary<NSObject, NSObject>> =
unsafe { NSDictionary::dictionaryWithContentsOfFile(&info_path) }.unwrap();
let mut x = 0.0;
if let Some(n) = info.objectForKey(ns_string!("hotx")) {
if let Ok(n) = n.downcast::<NSNumber>() {
x = n.as_cgfloat();
}
}
let mut y = 0.0;
if let Some(n) = info.objectForKey(ns_string!("hoty")) {
if let Ok(n) = n.downcast::<NSNumber>() {
y = n.as_cgfloat();
}
}
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()
}
#[allow(deprecated)]
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 => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Right,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeRightCursor()
}
},
CursorIcon::NResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Top,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeUpCursor()
}
},
CursorIcon::WResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Left,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeLeftCursor()
}
},
CursorIcon::SResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Bottom,
NSCursorFrameResizeDirections::Outward,
)
} else {
NSCursor::resizeDownCursor()
}
},
CursorIcon::EwResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Right,
NSCursorFrameResizeDirections::All,
)
} else {
NSCursor::resizeLeftRightCursor()
}
},
CursorIcon::NsResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::Top,
NSCursorFrameResizeDirections::All,
)
} else {
NSCursor::resizeUpDownCursor()
}
},
CursorIcon::NeResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopRight,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeNorthEastCursor()
}
},
CursorIcon::NwResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopLeft,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeNorthWestCursor()
}
},
CursorIcon::SeResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::BottomRight,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeSouthEastCursor()
}
},
CursorIcon::SwResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::BottomLeft,
NSCursorFrameResizeDirections::Outward,
)
} else {
_windowResizeSouthWestCursor()
}
},
CursorIcon::NeswResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopRight,
NSCursorFrameResizeDirections::All,
)
} else {
_windowResizeNorthEastSouthWestCursor()
}
},
CursorIcon::NwseResize => {
if available!(macos = 15.0) {
NSCursor::frameResizeCursorFromPosition_inDirections(
NSCursorFrameResizePosition::TopLeft,
NSCursorFrameResizeDirections::All,
)
} else {
_windowResizeNorthWestSouthEastCursor()
}
},
CursorIcon::ColResize => {
if available!(macos = 15.0) {
NSCursor::columnResizeCursor()
} else {
NSCursor::resizeLeftRightCursor()
}
},
CursorIcon::RowResize => {
if available!(macos = 15.0) {
NSCursor::rowResizeCursor()
} else {
NSCursor::resizeUpDownCursor()
}
},
CursorIcon::ZoomIn => {
if available!(macos = 15.0) {
NSCursor::zoomInCursor()
} else {
_zoomInCursor()
}
},
CursorIcon::ZoomOut => {
if available!(macos = 15.0) {
NSCursor::zoomOutCursor()
} else {
_zoomOutCursor()
}
},
CursorIcon::Help => _helpCursor(),
// 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,401 +0,0 @@
use std::rc::Rc;
use std::sync::Arc;
use std::time::{Duration, Instant};
use objc2::rc::{Retained, autoreleasepool};
use objc2::runtime::ProtocolObject;
use objc2::{MainThreadMarker, available};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
NSApplicationWillTerminateNotification, NSWindow,
};
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopCommonModes};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
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::app_state::AppState;
use super::cursor::CustomCursor;
use super::event::dummy_event;
use super::monitor;
use crate::ActivationPolicy;
use crate::window::Window;
#[derive(Debug)]
pub struct ActiveEventLoop {
pub(super) app_state: Rc<AppState>,
pub(super) mtm: MainThreadMarker,
}
impl ActiveEventLoop {
pub(crate) fn hide_application(&self) {
NSApplication::sharedApplication(self.mtm).hide(None)
}
pub(crate) fn hide_other_applications(&self) {
NSApplication::sharedApplication(self.mtm).hideOtherApplications(None)
}
pub(crate) fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
NSWindow::setAllowsAutomaticWindowTabbing(enabled, self.mtm)
}
pub(crate) fn allows_automatic_window_tabbing(&self) -> bool {
NSWindow::allowsAutomaticWindowTabbing(self.mtm)
}
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> CoreEventLoopProxy {
CoreEventLoopProxy::new(self.app_state.event_loop_proxy().clone())
}
fn create_window(
&self,
window_attributes: winit_core::window::WindowAttributes,
) -> Result<Box<dyn winit_core::window::Window>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
source: CustomCursorSource,
) -> Result<CoreCustomCursor, RequestError> {
Ok(CoreCustomCursor(Arc::new(CustomCursor::new(source)?)))
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Box::new(
monitor::available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
}
fn primary_monitor(&self) -> Option<winit_core::monitor::MonitorHandle> {
let monitor = monitor::primary_monitor();
Some(CoreMonitorHandle(Arc::new(monitor)))
}
fn listen_device_events(&self, _allowed: DeviceEvents) {}
fn system_theme(&self) -> Option<Theme> {
let app = NSApplication::sharedApplication(self.mtm);
// Dark appearance was introduced in macOS 10.14
if available!(macos = 10.14) {
Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance()))
} else {
Some(Theme::Light)
}
}
fn set_control_flow(&self, control_flow: ControlFlow) {
self.app_state.set_control_flow(control_flow)
}
fn control_flow(&self) -> ControlFlow {
self.app_state.control_flow()
}
fn exit(&self) {
self.app_state.exit()
}
fn exiting(&self) -> bool {
self.app_state.exiting()
}
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
}
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
#[derive(Debug)]
pub struct EventLoop {
/// Store a reference to the application for convenience.
///
/// We intentionally don't store `WinitApplication` since we want to have
/// the possibility of swapping that out at some point.
app: Retained<NSApplication>,
app_state: Rc<AppState>,
window_target: ActiveEventLoop,
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
// the system instead cleans it up next time it would have posted a notification to it.
//
// Though we do still need to keep the observers around to prevent them from being deallocated.
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_tracing_observers: Option<(MainRunLoopObserver, MainRunLoopObserver)>,
_before_waiting_observer: MainRunLoopObserver,
_after_waiting_observer: MainRunLoopObserver,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct PlatformSpecificEventLoopAttributes {
pub activation_policy: Option<ActivationPolicy>,
pub default_menu: bool,
pub activate_ignoring_other_apps: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
fn default() -> Self {
Self { activation_policy: None, default_menu: true, activate_ignoring_other_apps: true }
}
}
impl EventLoop {
pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!");
let activation_policy = match attributes.activation_policy {
None => None,
Some(ActivationPolicy::Regular) => Some(NSApplicationActivationPolicy::Regular),
Some(ActivationPolicy::Accessory) => Some(NSApplicationActivationPolicy::Accessory),
Some(ActivationPolicy::Prohibited) => Some(NSApplicationActivationPolicy::Prohibited),
};
let app_state = AppState::setup_global(
mtm,
activation_policy,
attributes.default_menu,
attributes.activate_ignoring_other_apps,
)
.ok_or_else(|| EventLoopError::RecreationAttempt)?;
// Initialize the application (if it has not already been).
let app = NSApplication::sharedApplication(mtm);
// Override `sendEvent:` on the application to forward to our application state.
override_send_event(&app);
let center = NSNotificationCenter::defaultCenter();
let weak_app_state = Rc::downgrade(&app_state);
let _did_finish_launching_observer = create_observer(
&center,
// `applicationDidFinishLaunching:`
unsafe { NSApplicationDidFinishLaunchingNotification },
move |notification| {
let _entered = debug_span!("NSApplicationDidFinishLaunchingNotification").entered();
if let Some(app_state) = weak_app_state.upgrade() {
app_state.did_finish_launching(notification);
}
},
);
let weak_app_state = Rc::downgrade(&app_state);
let _will_terminate_observer = create_observer(
&center,
// `applicationWillTerminate:`
unsafe { NSApplicationWillTerminateNotification },
move |notification| {
let _entered = debug_span!("NSApplicationWillTerminateNotification").entered();
if let Some(app_state) = weak_app_state.upgrade() {
app_state.will_terminate(notification);
}
},
);
let main_loop = MainRunLoop::get(mtm);
let mode = unsafe { kCFRunLoopCommonModes }.unwrap();
// 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 {
app,
app_state: app_state.clone(),
window_target: ActiveEventLoop { app_state, mtm },
_did_finish_launching_observer,
_will_terminate_observer,
_tracing_observers,
_before_waiting_observer,
_after_waiting_observer,
})
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
&self.window_target
}
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
// time and so a layered implementation would end up using a lot of CPU due to
// redundant wake ups.
pub fn run_app_on_demand<A: ApplicationHandler>(
&mut self,
app: A,
) -> Result<(), EventLoopError> {
self.app_state.clear_exit();
self.app_state.set_event_handler(app, || {
autoreleasepool(|_| {
// clear / normalize pump_events state
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(false);
self.app_state.set_stop_after_wait(false);
self.app_state.set_stop_on_redraw(false);
if self.app_state.is_launched() {
debug_assert!(!self.app_state.is_running());
self.app_state.set_is_running(true);
self.app_state.dispatch_init_events();
}
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
self.app.run();
self.app_state.internal_exit()
})
});
Ok(())
}
pub fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: A,
) -> PumpStatus {
self.app_state.set_event_handler(app, || {
autoreleasepool(|_| {
// As a special case, if the application hasn't been launched yet then we at least
// run the loop until it has fully launched.
if !self.app_state.is_launched() {
debug_assert!(!self.app_state.is_running());
self.app_state.set_stop_on_launch();
self.app.run();
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application
// has launched
} else if !self.app_state.is_running() {
// Even though the application may have been launched, it's possible we aren't
// running if the `EventLoop` was run before and has since
// exited. This indicates that we just starting to re-run
// the same `EventLoop` again.
self.app_state.set_is_running(true);
self.app_state.dispatch_init_events();
} else {
// Only run for as long as the given `Duration` allows so we don't block the
// external loop.
match timeout {
Some(Duration::ZERO) => {
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(true);
},
Some(duration) => {
self.app_state.set_stop_before_wait(false);
let timeout = Instant::now() + duration;
self.app_state.set_wait_timeout(Some(timeout));
self.app_state.set_stop_after_wait(true);
},
None => {
self.app_state.set_wait_timeout(None);
self.app_state.set_stop_before_wait(false);
self.app_state.set_stop_after_wait(true);
},
}
self.app_state.set_stop_on_redraw(true);
self.app.run();
}
if self.app_state.exiting() {
self.app_state.internal_exit();
PumpStatus::Exit(0)
} else {
PumpStatus::Continue
}
})
})
}
}
pub(crate) struct OwnedDisplayHandle;
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
pub(super) fn stop_app_immediately(app: &NSApplication) {
autoreleasepool(|_| {
app.stop(None);
// To stop event loop immediately, we need to post some event here.
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
app.postEvent_atStart(&dummy_event().unwrap(), true);
});
}
/// Tell all windows to close.
///
/// This will synchronously trigger `WindowEvent::Destroyed` within
/// `windowWillClose:`, giving the application one last chance to handle
/// those events. It doesn't matter if the user also ends up closing the
/// windows in `Window`'s `Drop` impl, once a window has been closed once, it
/// stays closed.
///
/// This ensures that no windows linger on after the event loop has exited,
/// see <https://github.com/rust-windowing/winit/issues/4135>.
pub(super) fn notify_windows_of_exit(app: &NSApplication) {
for window in app.windows() {
window.close();
}
}

View File

@@ -1,89 +0,0 @@
// TODO: Upstream these
#![allow(non_upper_case_globals)]
use std::ffi::c_void;
use objc2::ffi::NSInteger;
use objc2::runtime::AnyObject;
use objc2_core_foundation::{CFString, CFUUID, cf_type};
use objc2_core_graphics::CGDirectDisplayID;
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
// `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")]
unsafe extern "C" {
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> *mut CFUUID;
pub fn CGDisplayGetDisplayIDFromUUID(uuid: &CFUUID) -> CGDirectDisplayID;
}
#[link(name = "CoreGraphics", kind = "framework")]
unsafe extern "C" {
// 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;
}
#[repr(transparent)]
pub struct TISInputSource(std::ffi::c_void);
cf_type!(
unsafe impl 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")]
unsafe extern "C" {
pub static kTISPropertyUnicodeKeyLayoutData: &'static CFString;
#[allow(non_snake_case)]
pub fn TISGetInputSourceProperty(
inputSource: &TISInputSource,
propertyKey: &CFString,
) -> *mut c_void;
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut TISInputSource;
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;
}

View File

@@ -1,610 +0,0 @@
//! # macOS / AppKit
//!
//! Winit has [the same macOS version requirements as `rustc`][rustc-macos-version], and is tested
//! once in a while on as low as macOS 10.14.
//!
//! [rustc-macos-version]: https://doc.rust-lang.org/rustc/platform-support/apple-darwin.html#os-version
//!
//! ## Custom `NSApplicationDelegate`
//!
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
//! though, you might want to do more niche stuff, such as [handle when the user re-activates the
//! application][reopen]. Such functionality is not exposed directly in Winit, since it would
//! increase the API surface by quite a lot.
//!
//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc
//!
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is
//! to register your own application delegate, as outlined in the following example (see
//! `objc2-app-kit` for more detailed information).
//! ```
//! use objc2::rc::Retained;
//! use objc2::runtime::ProtocolObject;
//! use objc2::{DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSObject, NSObjectProtocol, NSURL};
//! use winit::event_loop::EventLoop;
//!
//! define_class!(
//! #[unsafe(super(NSObject))]
//! #[thread_kind = MainThreadOnly]
//! #[name = "AppDelegate"]
//! struct AppDelegate;
//!
//! unsafe impl NSObjectProtocol for AppDelegate {}
//!
//! unsafe impl NSApplicationDelegate for AppDelegate {
//! #[unsafe(method(application:openURLs:))]
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
//! // Note: To specifically get `application:openURLs:` to work, you _might_
//! // have to bundle your application. This is not done in this example.
//! println!("open urls: {application:?}, {urls:?}");
//! }
//! }
//! );
//!
//! impl AppDelegate {
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
//! unsafe { msg_send![super(Self::alloc(mtm).set_ivars(())), init] }
//! }
//! }
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let event_loop = EventLoop::new()?;
//!
//! let mtm = MainThreadMarker::new().unwrap();
//! let delegate = AppDelegate::new(mtm);
//! // Important: Call `sharedApplication` after `EventLoop::new`,
//! // doing it before is not yet supported.
//! let app = NSApplication::sharedApplication(mtm);
//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
//!
//! // event_loop.run_app(&mut my_app);
//! Ok(())
//! }
//! ```
#![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;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[doc(inline)]
pub use winit_core::application::macos::ApplicationHandlerExtMacOS;
use winit_core::event_loop::ActiveEventLoop;
use winit_core::monitor::MonitorHandle;
use winit_core::window::{PlatformWindowAttributes, Window};
pub use self::event::{physicalkey_to_scancode, scancode_to_physicalkey};
use self::event_loop::ActiveEventLoop as AppKitActiveEventLoop;
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
use self::monitor::MonitorHandle as AppKitMonitorHandle;
use self::window::Window as AppKitWindow;
/// Additional methods on [`Window`] that are specific to MacOS.
pub trait WindowExtMacOS {
/// Returns whether or not the window is in simple fullscreen mode.
fn simple_fullscreen(&self) -> bool;
/// Toggles a fullscreen mode that doesn't require a new macOS space.
/// Returns a boolean indicating whether the transition was successful (this
/// won't work if the window was already in the native fullscreen).
///
/// This is how fullscreen used to work on macOS in versions before Lion.
/// And allows the user to have a fullscreen window without using another
/// space or taking control over the entire monitor.
///
/// Make sure you only draw your important content inside the safe area so that it does not
/// overlap with the notch on newer devices, see [`Window::safe_area`] for details.
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
/// Returns whether or not the window has shadow.
fn has_shadow(&self) -> bool;
/// Sets whether or not the window has shadow.
fn set_has_shadow(&self, has_shadow: bool);
/// Group windows together by using the same tabbing identifier.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
fn set_tabbing_identifier(&self, identifier: &str);
/// Returns the window's tabbing identifier.
fn tabbing_identifier(&self) -> String;
/// Select next tab.
fn select_next_tab(&self);
/// Select previous tab.
fn select_previous_tab(&self);
/// Select the tab with the given index.
///
/// Will no-op when the index is out of bounds.
fn select_tab_at_index(&self, index: usize);
/// Get the number of tabs in the window tab group.
fn num_tabs(&self) -> usize;
/// Get the window's edit state.
///
/// # Examples
///
/// ```ignore
/// WindowEvent::CloseRequested => {
/// if window.is_document_edited() {
/// // Show the user a save pop-up or similar
/// } else {
/// // Close the window
/// drop(window);
/// }
/// }
/// ```
fn is_document_edited(&self) -> bool;
/// Put the window in a state which indicates a file save is required.
fn set_document_edited(&self, edited: bool);
/// Set option as alt behavior as described in [`OptionAsAlt`].
///
/// This will ignore diacritical marks and accent characters from
/// being processed as received characters. Instead, the input
/// device's raw character will be placed in event queues with the
/// Alt modifier set.
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt);
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
fn option_as_alt(&self) -> OptionAsAlt;
/// Disable the Menu Bar and Dock in Simple or Borderless Fullscreen mode. Useful for games.
/// The effect is applied when [`WindowExtMacOS::set_simple_fullscreen`] or
/// [`Window::set_fullscreen`] is called.
fn set_borderless_game(&self, borderless_game: bool);
/// Getter for the [`WindowExtMacOS::set_borderless_game`].
fn is_borderless_game(&self) -> bool;
/// Makes the titlebar bigger, effectively adding more space around the
/// window controls if the titlebar is invisible.
fn set_unified_titlebar(&self, unified_titlebar: bool);
/// Getter for the [`WindowExtMacOS::set_unified_titlebar`].
fn unified_titlebar(&self) -> bool;
}
impl WindowExtMacOS for dyn Window + '_ {
#[inline]
fn simple_fullscreen(&self) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.simple_fullscreen())
}
#[inline]
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
}
#[inline]
fn has_shadow(&self) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.has_shadow())
}
#[inline]
fn set_has_shadow(&self, has_shadow: bool) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(move |w| w.set_has_shadow(has_shadow));
}
#[inline]
fn set_tabbing_identifier(&self, identifier: &str) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
}
#[inline]
fn tabbing_identifier(&self) -> String {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.tabbing_identifier())
}
#[inline]
fn select_next_tab(&self) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.select_next_tab());
}
#[inline]
fn select_previous_tab(&self) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.select_previous_tab());
}
#[inline]
fn select_tab_at_index(&self, index: usize) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(move |w| w.select_tab_at_index(index));
}
#[inline]
fn num_tabs(&self) -> usize {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.num_tabs())
}
#[inline]
fn is_document_edited(&self) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.is_document_edited())
}
#[inline]
fn set_document_edited(&self, edited: bool) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(move |w| w.set_document_edited(edited));
}
#[inline]
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(move |w| w.set_option_as_alt(option_as_alt));
}
#[inline]
fn option_as_alt(&self) -> OptionAsAlt {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.option_as_alt())
}
#[inline]
fn set_borderless_game(&self, borderless_game: bool) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game))
}
#[inline]
fn is_borderless_game(&self) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.is_borderless_game())
}
#[inline]
fn set_unified_titlebar(&self, unified_titlebar: bool) {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar))
}
#[inline]
fn unified_titlebar(&self) -> bool {
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.unified_titlebar())
}
}
/// Corresponds to `NSApplicationActivationPolicy`.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ActivationPolicy {
/// Corresponds to `NSApplicationActivationPolicyRegular`.
#[default]
Regular,
/// Corresponds to `NSApplicationActivationPolicyAccessory`.
Accessory,
/// Corresponds to `NSApplicationActivationPolicyProhibited`.
Prohibited,
}
/// Window attributes that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the
/// [`WindowAttributes::with_decorations`] method:
/// - `with_titlebar_transparent`
/// - `with_title_hidden`
/// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
///
/// [`WindowAttributes::with_decorations`]: crate::window::WindowAttributes::with_decorations
#[derive(Clone, Debug, PartialEq)]
pub struct WindowAttributesMacOS {
pub(crate) movable_by_window_background: bool,
pub(crate) titlebar_transparent: bool,
pub(crate) title_hidden: bool,
pub(crate) titlebar_hidden: bool,
pub(crate) titlebar_buttons_hidden: bool,
pub(crate) fullsize_content_view: bool,
pub(crate) disallow_hidpi: bool,
pub(crate) has_shadow: bool,
pub(crate) accepts_first_mouse: bool,
pub(crate) tabbing_identifier: Option<String>,
pub(crate) option_as_alt: OptionAsAlt,
pub(crate) borderless_game: bool,
pub(crate) unified_titlebar: bool,
pub(crate) panel: bool,
}
impl WindowAttributesMacOS {
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
#[inline]
pub fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.movable_by_window_background = movable_by_window_background;
self
}
/// Makes the titlebar transparent and allows the content to appear behind it.
#[inline]
pub fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
self.titlebar_transparent = titlebar_transparent;
self
}
/// Hides the window titlebar.
#[inline]
pub fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.titlebar_hidden = titlebar_hidden;
self
}
/// Hides the window titlebar buttons.
#[inline]
pub fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
self.titlebar_buttons_hidden = titlebar_buttons_hidden;
self
}
/// Hides the window title.
#[inline]
pub fn with_title_hidden(mut self, title_hidden: bool) -> Self {
self.title_hidden = title_hidden;
self
}
/// Makes the window content appear behind the titlebar.
#[inline]
pub fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
self.fullsize_content_view = fullsize_content_view;
self
}
#[inline]
pub fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
self.disallow_hidpi = disallow_hidpi;
self
}
#[inline]
pub fn with_has_shadow(mut self, has_shadow: bool) -> Self {
self.has_shadow = has_shadow;
self
}
/// Window accepts click-through mouse events.
#[inline]
pub fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.accepts_first_mouse = accepts_first_mouse;
self
}
/// Defines the window tabbing identifier.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
#[inline]
pub fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.tabbing_identifier.replace(tabbing_identifier.to_string());
self
}
/// Set how the <kbd>Option</kbd> keys are interpreted.
///
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
#[inline]
pub fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.option_as_alt = option_as_alt;
self
}
/// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
#[inline]
pub fn with_borderless_game(mut self, borderless_game: bool) -> Self {
self.borderless_game = borderless_game;
self
}
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
#[inline]
pub fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
self.unified_titlebar = unified_titlebar;
self
}
/// Use [`NSPanel`] window with [`NonactivatingPanel`] window style mask instead of
/// [`NSWindow`].
///
/// [`NSWindow`]: https://developer.apple.com/documentation/appkit/NSWindow?language=objc
/// [`NSPanel`]: https://developer.apple.com/documentation/appkit/NSPanel?language=objc
/// [`NonactivatingPanel`]: https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.struct/nonactivatingpanel?language=objc
#[inline]
pub fn with_panel(mut self, panel: bool) -> Self {
self.panel = panel;
self
}
}
impl Default for WindowAttributesMacOS {
#[inline]
fn default() -> Self {
Self {
movable_by_window_background: false,
titlebar_transparent: false,
title_hidden: false,
titlebar_hidden: false,
titlebar_buttons_hidden: false,
fullsize_content_view: false,
disallow_hidpi: false,
has_shadow: true,
accepts_first_mouse: true,
tabbing_identifier: None,
option_as_alt: Default::default(),
borderless_game: false,
unified_titlebar: false,
panel: false,
}
}
}
impl PlatformWindowAttributes for WindowAttributesMacOS {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> {
Box::from(self.clone())
}
}
pub trait EventLoopBuilderExtMacOS {
/// Sets the activation policy for the application. If used, this will override
/// any relevant settings provided in the package manifest.
/// For instance, `with_activation_policy(ActivationPolicy::Regular)` will prevent
/// the application from running as an "agent", even if LSUIElement is set to true.
///
/// If unused, the Winit will honor the package manifest.
///
/// # Example
///
/// Set the activation policy to "accessory".
///
/// ```
/// use winit::event_loop::EventLoop;
/// #[cfg(target_os = "macos")]
/// use winit::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
///
/// let mut builder = EventLoop::builder();
/// #[cfg(target_os = "macos")]
/// builder.with_activation_policy(ActivationPolicy::Accessory);
/// # if false { // We can't test this part
/// let event_loop = builder.build();
/// # }
/// ```
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self;
/// Used to control whether a default menubar menu is created.
///
/// Menu creation is enabled by default.
///
/// # Example
///
/// Disable creating a default menubar.
///
/// ```
/// use winit::event_loop::EventLoop;
/// #[cfg(target_os = "macos")]
/// use winit::platform::macos::EventLoopBuilderExtMacOS;
///
/// let mut builder = EventLoop::builder();
/// #[cfg(target_os = "macos")]
/// builder.with_default_menu(false);
/// # if false { // We can't test this part
/// let event_loop = builder.build();
/// # }
/// ```
fn with_default_menu(&mut self, enable: bool) -> &mut Self;
/// Used to prevent the application from automatically activating when launched if
/// another application is already active.
///
/// The default behavior is to ignore other applications and activate when launched.
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
}
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
pub trait MonitorHandleExtMacOS {
/// Returns a pointer to the NSScreen representing this monitor.
fn ns_screen(&self) -> Option<*mut c_void>;
}
impl MonitorHandleExtMacOS for MonitorHandle {
fn ns_screen(&self) -> Option<*mut c_void> {
let monitor = self.cast_ref::<AppKitMonitorHandle>().unwrap();
// SAFETY: We only use the marker to get a pointer
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
monitor.ns_screen(mtm).map(|s| objc2::rc::Retained::as_ptr(&s) as _)
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to macOS.
pub trait ActiveEventLoopExtMacOS {
/// Hide the entire application. In most applications this is typically triggered with
/// Command-H.
fn hide_application(&self);
/// Hide the other applications. In most applications this is typically triggered with
/// Command+Option-H.
fn hide_other_applications(&self);
/// Set whether the system can automatically organize windows into tabs.
///
/// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
fn set_allows_automatic_window_tabbing(&self, enabled: bool);
/// Returns whether the system can automatically organize windows into tabs.
fn allows_automatic_window_tabbing(&self) -> bool;
}
impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ {
fn hide_application(&self) {
let event_loop =
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
event_loop.hide_application()
}
fn hide_other_applications(&self) {
let event_loop =
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
event_loop.hide_other_applications()
}
fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
let event_loop =
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
event_loop.set_allows_automatic_window_tabbing(enabled);
}
fn allows_automatic_window_tabbing(&self) -> bool {
let event_loop =
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
event_loop.allows_automatic_window_tabbing()
}
}
/// Option as alt behavior.
///
/// The default is `None`.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OptionAsAlt {
/// The left `Option` key is treated as `Alt`.
OnlyLeft,
/// The right `Option` key is treated as `Alt`.
OnlyRight,
/// Both `Option` keys are treated as `Alt`.
Both,
/// No special handling is applied for `Option` key.
#[default]
None,
}

View File

@@ -1,402 +0,0 @@
#![allow(clippy::unnecessary_cast)]
use std::collections::VecDeque;
use std::num::{NonZeroU16, NonZeroU32};
use std::ptr::NonNull;
use std::{fmt, ptr};
use dispatch2::run_on_main;
use dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use objc2::MainThreadMarker;
use objc2::rc::Retained;
use objc2_app_kit::NSScreen;
use objc2_core_foundation::{CFArray, CFRetained, CFUUID};
use objc2_core_graphics::{
CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyAllDisplayModes, CGDisplayCopyDisplayMode,
CGDisplayMode, CGDisplayModelNumber, CGGetActiveDisplayList, CGMainDisplayID,
};
use objc2_core_video::{CVDisplayLink, CVTimeFlags, kCVReturnSuccess};
use objc2_foundation::{NSNumber, NSPoint, NSRect, ns_string};
use tracing::warn;
use winit_core::monitor::{MonitorHandleProvider, VideoMode};
use super::ffi;
use super::util::cgerr;
#[derive(Clone)]
pub struct VideoModeHandle {
pub(crate) mode: VideoMode,
pub(crate) monitor: MonitorHandle,
pub(crate) native_mode: NativeDisplayMode,
}
impl PartialEq for VideoModeHandle {
fn eq(&self, other: &Self) -> bool {
self.monitor == other.monitor && self.mode == other.mode
}
}
impl Eq for VideoModeHandle {}
impl std::hash::Hash for VideoModeHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
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("mode", &self.mode)
.field("monitor", &self.monitor)
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NativeDisplayMode(pub CFRetained<CGDisplayMode>);
unsafe impl Send for NativeDisplayMode {}
unsafe impl Sync for NativeDisplayMode {}
impl VideoModeHandle {
fn new(
monitor: MonitorHandle,
native_mode: NativeDisplayMode,
refresh_rate_millihertz: Option<NonZeroU32>,
) -> Self {
// The bit-depth is basically always 32 since macOS 10.12.
#[allow(deprecated)]
let pixel_encoding =
CGDisplayMode::pixel_encoding(Some(&native_mode.0)).unwrap().to_string();
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
NonZeroU16::new(32)
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
NonZeroU16::new(16)
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) {
NonZeroU16::new(30)
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO64BitDirectPixels) {
NonZeroU16::new(64)
} else {
warn!(?pixel_encoding, "unknown bit depth");
None
};
let mode = VideoMode::new(
PhysicalSize::new(
CGDisplayMode::pixel_width(Some(&native_mode.0)) as u32,
CGDisplayMode::pixel_height(Some(&native_mode.0)) as u32,
),
bit_depth,
refresh_rate_millihertz,
);
Self { mode, monitor: monitor.clone(), native_mode }
}
}
/// `CGDirectDisplayID` is documented as:
/// > a framebuffer, a color correction (gamma) table, and possibly an attached monitor.
///
/// That is, it doesn't actually represent the monitor itself. Instead, we use the UUID of the
/// monitor, as retrieved from `CGDisplayCreateUUIDFromDisplayID` (this makes the monitor ID stable,
/// even across reboots and video mode changes).
///
/// NOTE: I'd be perfectly valid to store `[u8; 16]` in here instead, we only store `CFUUID` to
/// avoid having to re-create it when we want to fetch the display ID.
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct MonitorHandle(CFRetained<CFUUID>);
impl MonitorHandle {
/// Internal comparisons of [`MonitorHandle`]s are done first requesting a UUID for the handle.
fn uuid(&self) -> u128 {
u128::from_ne_bytes(self.0.uuid_bytes().into())
}
fn display_id(&self) -> CGDirectDisplayID {
unsafe { ffi::CGDisplayGetDisplayIDFromUUID(&self.0) }
}
#[track_caller]
pub(crate) fn new(display_id: CGDirectDisplayID) -> Option<Self> {
// kCGNullDirectDisplay
if display_id == 0 {
// `CGDisplayCreateUUIDFromDisplayID` checks kCGNullDirectDisplay internally.
warn!("constructing monitor from invalid display ID 0; falling back to main monitor");
}
// SAFETY: Valid to call.
let ptr = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(display_id) };
let ptr = NonNull::new(ptr)?;
// SAFETY: `CGDisplayCreateUUIDFromDisplayID` is a "create" function, so the pointer has
// +1 retain count.
let uuid = unsafe { CFRetained::from_raw(ptr) };
Some(Self(uuid))
}
fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
let current_display_mode =
NativeDisplayMode(CGDisplayCopyDisplayMode(self.display_id()).unwrap());
refresh_rate_millihertz(self.display_id(), &current_display_mode)
}
pub fn video_mode_handles(&self) -> impl Iterator<Item = VideoModeHandle> + 'static {
let refresh_rate_millihertz = self.refresh_rate_millihertz();
let monitor = self.clone();
let array = unsafe { CGDisplayCopyAllDisplayModes(self.display_id(), None) };
let modes = if let Some(array) = array {
// SAFETY: `CGDisplayCopyAllDisplayModes` is documented to return an array of
// display modes.
unsafe { CFRetained::cast_unchecked::<CFArray<CGDisplayMode>>(array) }
} else {
// Occasionally, certain CalDigit Thunderbolt Hubs report a spurious monitor during
// sleep/wake/cycling monitors. It tends to have null or 1 video mode only.
// See <https://github.com/bevyengine/bevy/issues/17827>.
warn!(monitor = ?self, "failed to get a list of display modes");
CFArray::empty()
};
modes.into_iter().map(move |mode| {
let cg_refresh_rate_hertz = CGDisplayMode::refresh_rate(Some(&mode));
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0.0 {
NonZeroU32::new((cg_refresh_rate_hertz * 1000.0).round() 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 = self.uuid();
NSScreen::screens(mtm).into_iter().find(|screen| {
let other_native_id = get_display_id(screen);
if let Some(other) = MonitorHandle::new(other_native_id) {
uuid == other.uuid()
} else {
// Display ID was just fetched from live NSScreen, but can still result in `None`
// with certain Thunderbolt docked monitors.
warn!(other_native_id, "comparing against screen with invalid display ID");
false
}
})
}
}
impl MonitorHandleProvider for MonitorHandle {
fn id(&self) -> u128 {
self.uuid()
}
fn native_id(&self) -> u64 {
self.display_id() as _
}
// TODO: Be smarter about this:
//
// <https://github.com/glfw/glfw/blob/57cbded0760a50b9039ee0cb3f3c14f60145567c/src/cocoa_monitor.m#L44-L126>
fn name(&self) -> Option<std::borrow::Cow<'_, str>> {
let screen_num = CGDisplayModelNumber(self.display_id());
Some(format!("Monitor #{screen_num}").into())
}
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 = CGDisplayBounds(self.display_id());
let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y);
Some(position.to_physical(self.scale_factor()))
}
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 current_video_mode(&self) -> Option<VideoMode> {
let mode = NativeDisplayMode(CGDisplayCopyDisplayMode(self.display_id()).unwrap());
let refresh_rate_millihertz = refresh_rate_millihertz(self.display_id(), &mode);
Some(VideoModeHandle::new(self.clone(), mode, refresh_rate_millihertz).mode)
}
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
Box::new(self.video_mode_handles().map(|mode| mode.mode))
}
}
pub fn available_monitors() -> VecDeque<MonitorHandle> {
let mut expected_count = 0;
let res = cgerr(unsafe { CGGetActiveDisplayList(0, ptr::null_mut(), &mut expected_count) });
if res.is_err() {
return VecDeque::with_capacity(0);
}
let mut displays: Vec<CGDirectDisplayID> = vec![0; expected_count as usize];
let mut actual_count = 0;
let res = cgerr(unsafe {
CGGetActiveDisplayList(expected_count, displays.as_mut_ptr(), &mut actual_count)
});
displays.truncate(actual_count as usize);
if res.is_err() {
return VecDeque::with_capacity(0);
}
let mut monitors = VecDeque::with_capacity(displays.len());
for display in displays {
// Display ID just fetched from `CGGetActiveDisplayList`, should be fine to unwrap.
monitors.push_back(MonitorHandle::new(display).expect("invalid display ID"));
}
monitors
}
pub fn primary_monitor() -> MonitorHandle {
// Display ID just fetched from `CGMainDisplayID`, should be fine to unwrap.
MonitorHandle::new(CGMainDisplayID()).expect("invalid display ID")
}
impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MonitorHandle")
.field("name", &self.name())
.field("uuid", &self.uuid())
.field("display_id", &self.display_id())
.field("position", &self.position())
.field("scale_factor", &self.scale_factor())
.finish_non_exhaustive()
}
}
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
//
// The value from @"NSScreenNumber" in deviceDescription is guaranteed
// to be an NSNumber. See documentation for details:
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
let obj = device_description
.objectForKey(key)
.expect("failed getting screen display id from device description")
.downcast::<NSNumber>()
.expect("NSScreenNumber must be NSNumber");
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 = CGDisplayBounds(CGMainDisplayID()).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> {
let refresh_rate = CGDisplayMode::refresh_rate(Some(&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();
#[allow(deprecated)]
if unsafe { CVDisplayLink::create_with_cg_display(id, NonNull::from(&mut display_link)) }
!= kCVReturnSuccess
{
return None;
}
let display_link = unsafe { CFRetained::from_raw(NonNull::new(display_link).unwrap()) };
#[allow(deprecated)]
let time = display_link.nominal_output_video_refresh_period();
// This value is indefinite if an invalid display link was specified
if time.flags & CVTimeFlags::IsIndefinite.0 != 0 {
return None;
}
(time.timeScale as i64)
.checked_div(time.timeValue)
.map(|v| (v * 1000) as u32)
.and_then(NonZeroU32::new)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uuid_stable() {
let primary_id = CGMainDisplayID();
let handle_a = MonitorHandle::new(primary_id).unwrap();
let handle_b = MonitorHandle::new(primary_id).unwrap();
assert_eq!(handle_a, handle_b);
assert_eq!(handle_a.display_id(), handle_b.display_id());
assert_eq!(handle_a.uuid(), handle_b.uuid());
let handle_a = primary_monitor();
let handle_b = primary_monitor();
assert_eq!(handle_a, handle_b);
assert_eq!(handle_a.display_id(), handle_b.display_id());
assert_eq!(handle_a.uuid(), handle_b.uuid());
}
/// Test the MonitorHandle::new fallback.
#[test]
fn monitorhandle_from_zero() {
let primary_id = CGMainDisplayID();
let handle0 = MonitorHandle::new(0).unwrap();
let handle1 = MonitorHandle::new(primary_id).unwrap();
assert_eq!(handle0, handle1);
assert_eq!(handle0.display_id(), handle1.display_id());
assert_eq!(handle0.uuid(), handle1.uuid());
}
#[test]
fn from_invalid_id() {
// Assume there are never this many monitors connected.
assert!(MonitorHandle::new(10000).is_none());
}
/// Test that calling `CGDisplayGetDisplayIDFromUUID` on an invalid UUID returns an invalid
/// display ID.
#[test]
fn invalid_monitor_handle() {
// `CGMainDisplayID` must be called to avoid:
// ```
// Assertion failed: (did_initialize), function CGS_REQUIRE_INIT, file CGInitialization.c, line 44.
// ```
// See https://github.com/JXA-Cookbook/JXA-Cookbook/issues/27#issuecomment-277517668
let _ = CGMainDisplayID();
let handle = MonitorHandle(CFUUID::new(None).unwrap());
assert_eq!(handle.display_id(), 0);
}
}

View File

@@ -1,87 +0,0 @@
use std::ffi::c_void;
use std::ptr;
use std::time::Instant;
use objc2_core_foundation::{
CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopTimer, kCFRunLoopCommonModes,
};
#[derive(Debug)]
pub struct EventLoopWaker {
timer: CFRetained<CFRunLoopTimer>,
/// 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) {
self.timer.invalidate();
}
}
impl EventLoopWaker {
pub(crate) fn new() -> Self {
extern "C-unwind" fn wakeup_main_loop(_timer: *mut CFRunLoopTimer, _info: *mut c_void) {}
unsafe {
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
// It is initially setup with a first fire time really far into the
// future, but that gets changed to fire immediately in did_finish_launching
let timer = CFRunLoopTimer::new(
None,
f64::MAX,
0.000_000_1,
0,
0,
Some(wakeup_main_loop),
ptr::null_mut(),
)
.unwrap();
CFRunLoop::main().unwrap().add_timer(Some(&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;
self.timer.set_next_fire_date(f64::MAX);
}
}
pub fn start(&mut self) {
if self.next_fire_date != Some(self.start_instant) {
self.next_fire_date = Some(self.start_instant);
self.timer.set_next_fire_date(f64::MIN);
}
}
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);
let current = CFAbsoluteTimeGetCurrent();
let duration = instant - now;
let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0
+ duration.as_secs() as f64;
self.timer.set_next_fire_date(current + fsecs);
}
},
None => {
self.stop();
},
}
}
}

View File

@@ -1,11 +0,0 @@
use objc2_core_graphics::CGError;
use winit_core::error::OsError;
macro_rules! os_error {
($error:expr) => {{ winit_core::error::OsError::new(line!(), file!(), $error) }};
}
#[track_caller]
pub(crate) fn cgerr(err: CGError) -> Result<(), OsError> {
if err == CGError::Success { Ok(()) } else { Err(os_error!(format!("CGError {err:?}"))) }
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,386 +0,0 @@
#![allow(clippy::unnecessary_cast)]
use std::sync::Arc;
use dispatch2::MainThreadBound;
use dpi::{Position, Size};
use objc2::rc::{Retained, autoreleasepool};
use objc2::{MainThreadMarker, Message, define_class};
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
use objc2_foundation::NSObject;
use tracing::trace_span;
use winit_core::cursor::Cursor;
use winit_core::error::RequestError;
use winit_core::icon::Icon;
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use winit_core::window::{
ImeCapabilities, ImeRequest, ImeRequestError, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
#[derive(Debug)]
pub(crate) struct Window {
window: MainThreadBound<Retained<NSWindow>>,
/// The window only keeps a weak reference to this, so we must keep it around here.
delegate: MainThreadBound<Retained<WindowDelegate>>,
}
impl Window {
pub(crate) fn new(
window_target: &ActiveEventLoop,
attributes: WindowAttributes,
) -> Result<Self, RequestError> {
let mtm = window_target.mtm;
let delegate =
autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?;
Ok(Window {
window: MainThreadBound::new(delegate.window().retain(), mtm),
delegate: MainThreadBound::new(delegate, mtm),
})
}
pub(crate) fn maybe_wait_on_main<R: Send>(
&self,
f: impl FnOnce(&WindowDelegate) -> R + Send,
) -> R {
self.delegate.get_on_main(|delegate| f(delegate))
}
#[inline]
pub(crate) fn raw_window_handle_rwh_06(
&self,
) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
if let Some(mtm) = MainThreadMarker::new() {
Ok(self.delegate.get(mtm).raw_window_handle_rwh_06())
} else {
Err(rwh_06::HandleError::Unavailable)
}
}
#[inline]
pub(crate) fn raw_display_handle_rwh_06(
&self,
) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
Ok(rwh_06::RawDisplayHandle::AppKit(rwh_06::AppKitDisplayHandle::new()))
}
}
impl Drop for Window {
fn drop(&mut self) {
// Restore the video mode.
if matches!(self.fullscreen(), Some(Fullscreen::Exclusive(_, _))) {
self.set_fullscreen(None);
}
self.window.get_on_main(|window| autoreleasepool(|_| window.close()))
}
}
impl rwh_06::HasDisplayHandle for Window {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_display_handle_rwh_06()?;
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
impl rwh_06::HasWindowHandle for Window {
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
let raw = self.raw_window_handle_rwh_06()?;
unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) }
}
}
impl CoreWindow for Window {
fn id(&self) -> winit_core::window::WindowId {
self.maybe_wait_on_main(|delegate| delegate.id())
}
fn scale_factor(&self) -> f64 {
self.maybe_wait_on_main(|delegate| delegate.scale_factor())
}
fn request_redraw(&self) {
self.maybe_wait_on_main(|delegate| delegate.request_redraw());
}
fn pre_present_notify(&self) {
self.maybe_wait_on_main(|delegate| delegate.pre_present_notify());
}
fn reset_dead_keys(&self) {
self.maybe_wait_on_main(|delegate| delegate.reset_dead_keys());
}
fn surface_position(&self) -> dpi::PhysicalPosition<i32> {
self.maybe_wait_on_main(|delegate| delegate.surface_position())
}
fn outer_position(&self) -> Result<dpi::PhysicalPosition<i32>, RequestError> {
self.maybe_wait_on_main(|delegate| delegate.outer_position())
}
fn set_outer_position(&self, position: Position) {
self.maybe_wait_on_main(|delegate| delegate.set_outer_position(position));
}
fn surface_size(&self) -> dpi::PhysicalSize<u32> {
self.maybe_wait_on_main(|delegate| delegate.surface_size())
}
fn request_surface_size(&self, size: Size) -> Option<dpi::PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.request_surface_size(size))
}
fn outer_size(&self) -> dpi::PhysicalSize<u32> {
self.maybe_wait_on_main(|delegate| delegate.outer_size())
}
fn safe_area(&self) -> dpi::PhysicalInsets<u32> {
self.maybe_wait_on_main(|delegate| delegate.safe_area())
}
fn set_min_surface_size(&self, min_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_min_surface_size(min_size))
}
fn set_max_surface_size(&self, max_size: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_max_surface_size(max_size));
}
fn surface_resize_increments(&self) -> Option<dpi::PhysicalSize<u32>> {
self.maybe_wait_on_main(|delegate| delegate.surface_resize_increments())
}
fn set_surface_resize_increments(&self, increments: Option<Size>) {
self.maybe_wait_on_main(|delegate| delegate.set_surface_resize_increments(increments));
}
fn set_title(&self, title: &str) {
self.maybe_wait_on_main(|delegate| delegate.set_title(title));
}
fn set_transparent(&self, transparent: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_transparent(transparent));
}
fn set_blur(&self, blur: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_blur(blur));
}
fn set_visible(&self, visible: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_visible(visible));
}
fn is_visible(&self) -> Option<bool> {
self.maybe_wait_on_main(|delegate| delegate.is_visible())
}
fn set_resizable(&self, resizable: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_resizable(resizable))
}
fn is_resizable(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_resizable())
}
fn set_enabled_buttons(&self, buttons: WindowButtons) {
self.maybe_wait_on_main(|delegate| delegate.set_enabled_buttons(buttons))
}
fn enabled_buttons(&self) -> WindowButtons {
self.maybe_wait_on_main(|delegate| delegate.enabled_buttons())
}
fn set_minimized(&self, minimized: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_minimized(minimized));
}
fn is_minimized(&self) -> Option<bool> {
self.maybe_wait_on_main(|delegate| delegate.is_minimized())
}
fn set_maximized(&self, maximized: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_maximized(maximized));
}
fn is_maximized(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_maximized())
}
fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
self.maybe_wait_on_main(|delegate| delegate.set_fullscreen(fullscreen))
}
fn fullscreen(&self) -> Option<Fullscreen> {
self.maybe_wait_on_main(|delegate| delegate.fullscreen())
}
fn set_decorations(&self, decorations: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_decorations(decorations));
}
fn is_decorated(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.is_decorated())
}
fn set_window_level(&self, level: WindowLevel) {
self.maybe_wait_on_main(|delegate| delegate.set_window_level(level));
}
fn set_window_icon(&self, window_icon: Option<Icon>) {
self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon));
}
fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
self.maybe_wait_on_main(|delegate| delegate.request_ime_update(request))
}
fn ime_capabilities(&self) -> Option<ImeCapabilities> {
self.maybe_wait_on_main(|delegate| delegate.ime_capabilities())
}
fn focus_window(&self) {
self.maybe_wait_on_main(|delegate| delegate.focus_window());
}
fn has_focus(&self) -> bool {
self.maybe_wait_on_main(|delegate| delegate.has_focus())
}
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
self.maybe_wait_on_main(|delegate| delegate.request_user_attention(request_type));
}
fn set_theme(&self, theme: Option<Theme>) {
self.maybe_wait_on_main(|delegate| delegate.set_theme(theme));
}
fn theme(&self) -> Option<Theme> {
self.maybe_wait_on_main(|delegate| delegate.theme())
}
fn set_content_protected(&self, protected: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_content_protected(protected));
}
fn title(&self) -> String {
self.maybe_wait_on_main(|delegate| delegate.title())
}
fn set_cursor(&self, cursor: Cursor) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor(cursor));
}
fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position))
}
fn set_cursor_grab(
&self,
mode: winit_core::window::CursorGrabMode,
) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode))
}
fn set_cursor_visible(&self, visible: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_visible(visible))
}
fn drag_window(&self) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.drag_window())
}
fn drag_resize_window(
&self,
direction: winit_core::window::ResizeDirection,
) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?)
}
fn show_window_menu(&self, position: Position) {
self.maybe_wait_on_main(|delegate| delegate.show_window_menu(position))
}
fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_hittest(hittest));
Ok(())
}
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.current_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
})
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
self.maybe_wait_on_main(|delegate| {
Box::new(
delegate
.available_monitors()
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
})
}
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
self.maybe_wait_on_main(|delegate| {
delegate.primary_monitor().map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
})
}
fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
self
}
}
define_class!(
#[unsafe(super(NSWindow, NSResponder, NSObject))]
#[name = "WinitWindow"]
#[derive(Debug)]
pub struct WinitWindow;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitWindow {
#[unsafe(method(canBecomeMainWindow))]
fn can_become_main_window(&self) -> bool {
let _entered = trace_span!("canBecomeMainWindow").entered();
true
}
#[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool {
let _entered = trace_span!("canBecomeKeyWindow").entered();
true
}
}
);
define_class!(
#[unsafe(super(NSPanel, NSWindow, NSResponder, NSObject))]
#[name = "WinitPanel"]
#[derive(Debug)]
pub struct WinitPanel;
/// This documentation attribute makes rustfmt work for some reason?
impl WinitPanel {
// although NSPanel can become key window
// it doesn't if window doesn't have NSWindowStyleMask::Titled
#[unsafe(method(canBecomeKeyWindow))]
fn can_become_key_window(&self) -> bool {
let _entered = trace_span!("canBecomeKeyWindow").entered();
true
}
}
);
pub(super) fn window_id(window: &NSWindow) -> WindowId {
WindowId::from_raw(window as *const _ as usize)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +0,0 @@
[package]
description = "Winit implementation helpers"
documentation = "https://docs.rs/winit-common"
edition.workspace = true
license.workspace = true
name = "winit-common"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[features]
# Event Handler
event-handler = []
# XKB
wayland = ["dep:memmap2"]
x11 = ["xkbcommon-dl?/x11", "dep:x11-dl"]
xkb = ["dep:xkbcommon-dl", "dep:smol_str"]
# CoreFoundation
core-foundation = ["dep:block2", "dep:objc2", "dep:objc2-core-foundation"]
# Foundation
foundation = ["dep:block2", "dep:objc2", "dep:objc2-foundation"]
[dependencies]
smol_str = { workspace = true, optional = true }
tracing.workspace = true
winit-core.workspace = true
# XKB
memmap2 = { workspace = true, optional = true }
x11-dl = { workspace = true, optional = true }
xkbcommon-dl = { workspace = true, optional = true }
# Foundation / CoreFoundation
block2 = { workspace = true, optional = true }
objc2 = { workspace = true, optional = true }
objc2-core-foundation = { workspace = true, optional = true, features = [
"std",
"block2",
"objc2",
"CFRunLoop",
"CFString",
] }
objc2-foundation = { workspace = true, optional = true, features = [
"std",
"block2",
"NSNotification",
"NSString",
"NSOperation",
] }
[package.metadata.docs.rs]
all-features = true

View File

@@ -1 +0,0 @@
../LICENSE

View File

@@ -1 +0,0 @@
../README.md

View File

@@ -1,122 +0,0 @@
use std::os::raw::c_void;
use std::sync::Arc;
use objc2::MainThreadMarker;
use objc2_core_foundation::{
CFIndex, CFRetained, CFRunLoop, CFRunLoopSource, CFRunLoopSourceContext, kCFRunLoopCommonModes,
};
use winit_core::event_loop::EventLoopProxyProvider;
/// A waker that signals a `CFRunLoopSource` on the main thread.
///
/// We use this to integrate with the system as cleanly as possible (instead of e.g. keeping an
/// atomic around that we check on each iteration of the event loop).
///
/// See <https://developer.apple.com/documentation/corefoundation/cfrunloopsource?language=objc>.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EventLoopProxy {
source: CFRetained<CFRunLoopSource>,
/// Cached value of `CFRunLoopGetMain`.
main_loop: CFRetained<CFRunLoop>,
}
// FIXME(madsmtm): Mark `CFRunLoopSource` + `CFRunLoop` as `Send` + `Sync`.
unsafe impl Send for EventLoopProxy {}
unsafe impl Sync for EventLoopProxy {}
impl EventLoopProxy {
/// Create a new proxy, registering it to be performed on the main thread.
///
/// The provided closure should call `proxy_wake_up` on the application.
pub fn new<F: Fn() + 'static>(mtm: MainThreadMarker, signaller: F) -> Self {
// We use an `Arc` here to make sure that the reference-counting of the signal container is
// atomic (`Retained`/`CFRetained` would be valid alternatives too).
let signaller = Arc::new(signaller);
unsafe extern "C-unwind" fn retain<F>(info: *const c_void) -> *const c_void {
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
unsafe { Arc::increment_strong_count(info.cast::<F>()) };
info
}
unsafe extern "C-unwind" fn release<F>(info: *const c_void) {
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
unsafe { Arc::decrement_strong_count(info.cast::<F>()) };
}
// Pointer equality / hashing.
extern "C-unwind" fn equal(info1: *const c_void, info2: *const c_void) -> u8 {
(info1 == info2) as u8
}
extern "C-unwind" fn hash(info: *const c_void) -> usize {
info as usize
}
// Call the provided closure.
unsafe extern "C-unwind" fn perform<F: Fn()>(info: *mut c_void) {
// SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below.
let signaller = unsafe { &*info.cast::<F>() };
(signaller)();
}
// Fire last.
let order = CFIndex::MAX - 1;
// This is marked `mut` to match the signature of `CFRunLoopSourceCreate`, but the
// information is copied, and not actually mutated.
let mut context = CFRunLoopSourceContext {
version: 0,
// This is retained on creation.
info: Arc::as_ptr(&signaller) as *mut c_void,
retain: Some(retain::<F>),
release: Some(release::<F>),
copyDescription: None,
equal: Some(equal),
hash: Some(hash),
schedule: None,
cancel: None,
perform: Some(perform::<F>),
};
// SAFETY: The normal callbacks are thread-safe (`retain`/`release` use atomics, and
// `equal`/`hash` only access a pointer).
//
// Note that the `perform` callback isn't thread-safe (we don't have `F: Send + Sync`), but
// that's okay, since we are on the main thread, and the source is only added to the main
// run loop (below), and hence only performed there.
//
// Keeping the closure alive beyond this scope is fine, because `F: 'static`.
let source = unsafe {
let _ = mtm;
CFRunLoopSource::new(None, order, &mut context).unwrap()
};
// Register the source to be performed on the main thread.
let main_loop = CFRunLoop::main().unwrap();
unsafe { main_loop.add_source(Some(&source), kCFRunLoopCommonModes) };
Self { source, main_loop }
}
// FIXME(madsmtm): Use this on macOS too.
// More difficult there, since the user can re-start the event loop.
pub fn invalidate(&self) {
// NOTE: We do NOT fire this on `Drop`, since we want the proxy to be cloneable, such that
// we only need to register a single source even if there's multiple proxies in use.
self.source.invalidate();
}
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
// Signal the source, which ends up later invoking `perform` on the main thread.
//
// Multiple signals in quick succession are automatically coalesced into a single signal.
self.source.signal();
// Let the main thread know there's a new event.
//
// This is required since we may be (probably are) running on a different thread, and the
// main loop may be sleeping (and `CFRunLoopSourceSignal` won't wake it).
self.main_loop.wake_up();
}
}

View File

@@ -1,136 +0,0 @@
//! Various utilities for interfacing with the main run loop.
//!
//! These allow for using closures without the `Send + Sync` requirement that is otherwise required
//! when interfacing with run loops.
//!
//! See also <https://github.com/madsmtm/objc2/issues/696> for work on this at a lower level.
use std::cell::Cell;
use objc2::MainThreadMarker;
use objc2_core_foundation::{CFRetained, CFRunLoop, CFRunLoopMode, kCFRunLoopDefaultMode};
use tracing::{Span, error};
use super::MainRunLoopObserver;
/// Wrapper around [`CFRunLoop::main`].
#[derive(Debug)]
pub struct MainRunLoop {
/// This is on the main thread.
_mtm: MainThreadMarker,
/// A cached reference to the main run loop.
main_run_loop: CFRetained<CFRunLoop>,
}
impl MainRunLoop {
/// Get the main run loop.
pub fn get(_mtm: MainThreadMarker) -> Self {
let main_run_loop = CFRunLoop::main().unwrap();
Self { _mtm, main_run_loop }
}
/// Get a reference to the underlying [`CFRunLoop`].
pub fn run_loop(&self) -> &CFRunLoop {
&self.main_run_loop
}
/// Wake the main run loop.
pub fn wake_up(&self) {
self.main_run_loop.wake_up();
}
/// 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:]` to queue a closure to run
/// the next time runloop sources are processed.
///
/// 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) {
// We use this to run a closure asynchronously at a later point, so it also makes sense to
// re-enter the current span when running the queued closure.
let span = Span::current();
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
// Running this block happens inside a `CFRunLoopSource`, but the spans that we emit for
// that are (intentionally) overwritten by entering the span from before.
let _enter = span.enter();
debug_assert!(MainThreadMarker::new().is_some());
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.unwrap() };
let _ = self._mtm;
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
//
// Additionally, we have a `MainThreadMarker` here, which means we know we're on the main
// thread. We also know that the run loop is the main-thread run loop, so scheduling a
// non-`Send` block to that is allowed.
unsafe { self.main_run_loop.perform_block(Some(mode), Some(&block)) }
}
/// Add an observer to the main run loop.
pub fn add_observer(&self, observer: &MainRunLoopObserver, mode: &CFRunLoopMode) {
// Accessing the `MainObserver`'s observer is fine here, since we're adding it to the main
// run loop (which is on the same thread that the observer was created on).
self.main_run_loop.add_observer(Some(&observer.observer), Some(mode));
}
/// Remove an observer from the main run loop.
///
/// This is also done automatically when the [`MainRunLoopObserver`] is dropped.
pub fn remove_observer(&self, observer: &MainRunLoopObserver, mode: &CFRunLoopMode) {
// Same as in `add_observer`, accessing the main loop's observer is fine.
self.main_run_loop.add_observer(Some(&observer.observer), Some(mode));
}
}

View File

@@ -1,55 +0,0 @@
use block2::RcBlock;
use objc2::MainThreadMarker;
use objc2_core_foundation::{
CFIndex, CFRetained, CFRunLoopActivity, CFRunLoopObserver, kCFAllocatorDefault,
};
/// A [`CFRunLoopObserver`] on the main thread.
///
/// This type has "ownership" semantics, and invalidates the observer when dropped.
#[derive(Debug)]
pub struct MainRunLoopObserver {
/// This must be private, otherwise the user might add it to an arbitrary run loop (but this
/// observer is not designed to only be on the main thread).
pub(crate) observer: CFRetained<CFRunLoopObserver>,
}
impl MainRunLoopObserver {
/// Create a new run loop observer that observes the main run loop.
pub fn new(
mtm: MainThreadMarker,
activities: CFRunLoopActivity,
repeats: bool,
// The lower the value, the sooner this will run (inverse of a "priority").
order: CFIndex,
callback: impl Fn(CFRunLoopActivity) + 'static,
) -> Self {
let block = RcBlock::new(move |_: *mut _, activity| {
debug_assert!(MainThreadMarker::new().is_some());
callback(activity)
});
let _ = mtm;
// SAFETY: The callback is not Send + Sync, which would normally be unsound, but since we
// restrict the callback to only ever be on the main thread (by taking `MainThreadMarker`,
// and in `MainRunLoop::add_observer`), the callback doesn't have to be thread safe.
let observer = unsafe {
CFRunLoopObserver::with_handler(
kCFAllocatorDefault,
activities.0,
repeats,
order,
Some(&block),
)
}
.unwrap();
Self { observer }
}
}
impl Drop for MainRunLoopObserver {
fn drop(&mut self) {
self.observer.invalidate();
}
}

View File

@@ -1,14 +0,0 @@
//! Various wrappers around [`CFRunLoop`][objc2_core_foundation::CFRunLoop].
//!
//! See Apple's documentation on Run Loops for details:
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
mod event_loop_proxy;
mod main_run_loop;
mod main_run_loop_observer;
mod tracing_observers;
pub use self::event_loop_proxy::*;
pub use self::main_run_loop::*;
pub use self::main_run_loop_observer::*;
pub use self::tracing_observers::*;

View File

@@ -1,146 +0,0 @@
use std::cell::RefCell;
use std::rc::Rc;
use objc2::MainThreadMarker;
use objc2_core_foundation::{CFIndex, CFRunLoop, CFRunLoopActivity, kCFRunLoopDefaultMode};
use tracing::span::EnteredSpan;
use tracing::{Level, error, field, span_enabled, trace_span};
use crate::core_foundation::MainRunLoopObserver;
/// Create two run loop observers that add TRACE-level [spans][tracing::span].
///
/// This is useful when debugging run loops, it makes it easier to see in which run loop activity an
/// event is triggered inside (if any).
///
/// When debugging these interactions, it can also be useful to configure your tracing subscriber
/// with `.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE)` to emit events upon
/// entering and exiting these stages.
pub fn tracing_observers(
mtm: MainThreadMarker,
) -> Option<(MainRunLoopObserver, MainRunLoopObserver)> {
// Observers are a bit costly, so don't create them if the tracing-level for this module is
// configured to disable them.
if !span_enabled!(Level::TRACE) {
return None;
}
/// The state that we think the runloop is currently in.
///
/// The order of activities we observe if waiting twice looks something like:
/// - CFRunLoopActivity::Entry
/// - CFRunLoopActivity::BeforeTimers
/// - CFRunLoopActivity::BeforeSources
/// - CFRunLoopActivity::BeforeWaiting
/// - CFRunLoopActivity::AfterWaiting
/// - CFRunLoopActivity::BeforeTimers
/// - CFRunLoopActivity::BeforeSources
/// - CFRunLoopActivity::BeforeWaiting
/// - CFRunLoopActivity::AfterWaiting
/// - CFRunLoopActivity::Exit
///
/// And if not waiting, it looks something like:
/// - CFRunLoopActivity::Entry
/// - CFRunLoopActivity::BeforeTimers
/// - CFRunLoopActivity::BeforeSources
/// - CFRunLoopActivity::Exit
#[derive(Default)]
#[allow(unused)] // EnteredSpans are kept around
enum RunLoopState {
/// Currently processing `Entry`/`Exit` observers.
#[default]
Entered,
/// Currently processing timers or `BeforeTimers` observers.
Timers(EnteredSpan),
/// Currently processing sources or `BeforeSources` observers.
Sources(EnteredSpan),
/// Currently waiting or processing `BeforeWaiting`/`AfterWaiting` observers.
Waiting(EnteredSpan),
}
// A list of currently entered (outer) spans and their state.
//
// This is a list because runloops can be run recursively.
let spans: Rc<RefCell<Vec<(EnteredSpan, RunLoopState)>>> = Rc::new(RefCell::new(Vec::new()));
let spans_clone = Rc::clone(&spans);
// An observer at the start of run loop activities.
let activities = CFRunLoopActivity::Entry
| CFRunLoopActivity::BeforeTimers
| CFRunLoopActivity::BeforeSources
| CFRunLoopActivity::BeforeWaiting;
let start = MainRunLoopObserver::new(mtm, activities, true, CFIndex::MIN, move |activity| {
match activity {
// Add an outer span for each runloop iteration.
CFRunLoopActivity::Entry => {
let span = trace_span!("inside runloop", mode = field::Empty);
// Get the mode dynamically, the observer may added to multiple different modes.
let mode = CFRunLoop::current().unwrap().current_mode().unwrap();
// Mode isn't interesting if it's the default mode.
if &*mode != unsafe { kCFRunLoopDefaultMode }.unwrap() {
span.record("mode", field::display(mode));
}
let entered = span.entered();
spans.borrow_mut().push((entered, RunLoopState::Entered));
},
// Add inner spans that help inspecting the state the runloop is in.
CFRunLoopActivity::BeforeTimers => {
if let Some((_, state)) = spans.borrow_mut().last_mut() {
*state = RunLoopState::Entered; // Drop any previous spans.
*state = RunLoopState::Timers(trace_span!("processing timers").entered());
} else {
error!("unbalanced observer invocations");
}
},
CFRunLoopActivity::BeforeSources => {
if let Some((_, state)) = spans.borrow_mut().last_mut() {
*state = RunLoopState::Entered; // Drop any previous spans.
*state = RunLoopState::Sources(trace_span!("processing sources").entered());
} else {
error!("unbalanced observer invocations");
}
},
CFRunLoopActivity::BeforeWaiting => {
if let Some((_, state)) = spans.borrow_mut().last_mut() {
*state = RunLoopState::Entered; // Drop any previous spans.
*state = RunLoopState::Waiting(trace_span!("waiting").entered());
} else {
error!("unbalanced observer invocations");
}
},
activity => unreachable!("unexpected activity: {activity:?}"),
}
});
// An observer at the end of run loop activities.
let activities = CFRunLoopActivity::AfterWaiting | CFRunLoopActivity::Exit;
let end = MainRunLoopObserver::new(mtm, activities, true, CFIndex::MAX, move |activity| {
match activity {
CFRunLoopActivity::AfterWaiting => {
if let Some((_, state)) = spans_clone.borrow_mut().last_mut() {
// Transition from the waiting state to the initial state.
*state = RunLoopState::Entered;
} else {
error!("unbalanced observer invocations");
}
},
CFRunLoopActivity::Exit => {
if let Some((span, state)) = spans_clone.borrow_mut().pop() {
drop(state); // Explicitly exit and drop inner span.
drop(span); // Explicitly exit and drop outer span.
} else {
error!("unbalanced observer invocations");
}
},
activity => unreachable!("unexpected activity: {activity:?}"),
}
});
Some((start, end))
}

View File

@@ -1,159 +0,0 @@
use std::cell::RefCell;
use std::{fmt, mem};
use winit_core::application::ApplicationHandler;
/// A helper type for storing a reference to `ApplicationHandler`, allowing interior mutable access
/// to it within the execution of a closure.
#[derive(Default)]
pub struct EventHandler {
/// This can be in the following states:
/// - Not registered by the event loop, or terminated (None).
/// - Present (Some(handler)).
/// - Currently executing the handler / in use (RefCell borrowed).
inner: RefCell<Option<Box<dyn ApplicationHandler + 'static>>>,
}
impl fmt::Debug for EventHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = match self.inner.try_borrow().as_deref() {
Ok(Some(_)) => "<available>",
Ok(None) => "<not set>",
Err(_) => "<in use>",
};
f.debug_struct("EventHandler").field("state", &state).finish_non_exhaustive()
}
}
impl EventHandler {
pub fn new() -> Self {
Self { inner: RefCell::new(None) }
}
/// Set the event loop handler for the duration of the given closure.
///
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates
/// to store the handler in a thread local, such that it can be accessed
/// from within the closure.
pub fn set<'handler, R>(
&self,
app: Box<dyn ApplicationHandler + 'handler>,
closure: impl FnOnce() -> R,
) -> R {
// SAFETY: We extend the lifetime of the handler here so that we can
// store it in `EventHandler`'s `RefCell`.
//
// This is sound, since we make sure to unset the handler again at the
// end of this function, and as such the lifetime isn't actually
// extended beyond `'handler`.
let handler = unsafe {
mem::transmute::<
Box<dyn ApplicationHandler + 'handler>,
Box<dyn ApplicationHandler + 'static>,
>(app)
};
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(_)) => {
unreachable!("tried to set handler while another was already set");
},
Ok(data @ None) => {
*data = Some(handler);
},
Err(_) => {
unreachable!("tried to set handler that is currently in use");
},
}
struct ClearOnDrop<'a>(&'a EventHandler);
impl Drop for ClearOnDrop<'_> {
fn drop(&mut self) {
match self.0.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => {
let handler = data.take();
// Explicitly `Drop` the application handler.
drop(handler);
},
Ok(None) => {
// Allowed, happens if the handler was cleared manually
// elsewhere (such as in `applicationWillTerminate:`).
},
// We use `eprintln!` here over `tracing::error!`, since we're going to abort
// immediately after this, and it'd be annoying for the user if they didn't get
// any feedback on that if they don't have a tracing subscriber.
#[allow(clippy::disallowed_macros)]
Err(_) => {
// Note: This is not expected to ever happen, this
// module generally controls the `RefCell`, and
// prevents it from ever being borrowed outside of it.
//
// But if it _does_ happen, it is a serious error, and
// we must abort the process, it'd be unsound if we
// weren't able to unset the handler.
eprintln!("tried to clear handler that is currently in use");
std::process::abort();
},
}
}
}
let _clear_on_drop = ClearOnDrop(self);
// Note: The RefCell should not be borrowed while executing the
// closure, that'd defeat the whole point.
closure()
// `_clear_on_drop` will be dropped here, or when unwinding, ensuring
// soundness.
}
pub fn in_use(&self) -> bool {
self.inner.try_borrow().is_err()
}
pub fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}
pub fn handle(&self, callback: impl FnOnce(&mut (dyn ApplicationHandler + '_))) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(user_app)) => {
// It is important that we keep the reference borrowed here,
// so that `in_use` can properly detect that the handler is
// still in use.
//
// If the handler unwinds, the `RefMut` will ensure that the
// handler is no longer borrowed.
callback(&mut **user_app);
},
Ok(None) => {
// `NSApplication`, our app state and this handler are all
// global state and so it's not impossible that we could get
// an event after the application has exited the `EventLoop`.
tracing::error!("tried to run event handler, but no handler was set");
},
Err(_) => {
// Prevent re-entrancy.
panic!("tried to handle event while another event is currently being handled");
},
}
}
pub fn terminate(&self) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => {
let handler = data.take();
// Explicitly `Drop` the application handler.
drop(handler);
},
Ok(None) => {
// When terminating, we expect the application handler to still be registered.
tracing::error!("tried to clear handler, but no handler was set");
},
Err(_) => {
panic!("tried to clear handler while an event is currently being handled");
},
}
}
}

View File

@@ -1,3 +0,0 @@
mod notification_center;
pub use self::notification_center::*;

View File

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

View File

@@ -1,10 +0,0 @@
//! Winit implementation helpers.
#[cfg(feature = "core-foundation")]
pub mod core_foundation;
#[cfg(feature = "event-handler")]
pub mod event_handler;
#[cfg(feature = "foundation")]
pub mod foundation;
#[cfg(feature = "xkb")]
pub mod xkb;

View File

@@ -1,124 +0,0 @@
//! XKB compose handling.
use std::env;
use std::ffi::CString;
use std::ops::Deref;
use std::os::unix::ffi::OsStringExt;
use std::ptr::NonNull;
use smol_str::SmolStr;
use xkbcommon_dl::{
xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags,
xkb_compose_status, xkb_compose_table, xkb_keysym_t,
};
use super::{XKBCH, XkbContext};
#[derive(Debug)]
pub struct XkbComposeTable {
table: NonNull<xkb_compose_table>,
}
impl XkbComposeTable {
pub fn new(context: &XkbContext) -> Option<Self> {
let locale = env::var_os("LC_ALL")
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LC_CTYPE"))
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LANG"))
.and_then(|v| if v.is_empty() { None } else { Some(v) })
.unwrap_or_else(|| "C".into());
let locale = CString::new(locale.into_vec()).unwrap();
let table = unsafe {
(XKBCH.xkb_compose_table_new_from_locale)(
context.as_ptr(),
locale.as_ptr(),
xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
)
};
let table = NonNull::new(table)?;
Some(Self { table })
}
/// Create new state with the given compose table.
pub fn new_state(&self) -> Option<XkbComposeState> {
let state = unsafe {
(XKBCH.xkb_compose_state_new)(
self.table.as_ptr(),
xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
)
};
let state = NonNull::new(state)?;
Some(XkbComposeState { state })
}
}
impl Deref for XkbComposeTable {
type Target = NonNull<xkb_compose_table>;
fn deref(&self) -> &Self::Target {
&self.table
}
}
impl Drop for XkbComposeTable {
fn drop(&mut self) {
unsafe {
(XKBCH.xkb_compose_table_unref)(self.table.as_ptr());
}
}
}
#[derive(Debug)]
pub struct XkbComposeState {
state: NonNull<xkb_compose_state>,
}
impl XkbComposeState {
pub fn get_string(&mut self, scratch_buffer: &mut Vec<u8>) -> Option<SmolStr> {
super::make_string_with(scratch_buffer, |ptr, len| unsafe {
(XKBCH.xkb_compose_state_get_utf8)(self.state.as_ptr(), ptr, len)
})
}
#[inline]
pub fn feed(&mut self, keysym: xkb_keysym_t) -> ComposeStatus {
let feed_result = unsafe { (XKBCH.xkb_compose_state_feed)(self.state.as_ptr(), keysym) };
match feed_result {
xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored,
xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => {
ComposeStatus::Accepted(self.status())
},
}
}
#[inline]
pub fn reset(&mut self) {
unsafe {
(XKBCH.xkb_compose_state_reset)(self.state.as_ptr());
}
}
#[inline]
pub fn status(&mut self) -> xkb_compose_status {
unsafe { (XKBCH.xkb_compose_state_get_status)(self.state.as_ptr()) }
}
}
impl Drop for XkbComposeState {
fn drop(&mut self) {
unsafe {
(XKBCH.xkb_compose_state_unref)(self.state.as_ptr());
};
}
}
#[derive(Copy, Clone, Debug)]
pub enum ComposeStatus {
Accepted(xkb_compose_status),
Ignored,
None,
}

View File

@@ -1,415 +0,0 @@
use std::ops::Deref;
use std::os::raw::c_char;
#[cfg(feature = "wayland")]
use std::os::unix::io::OwnedFd;
use std::ptr::{self, NonNull};
use std::sync::LazyLock;
use std::sync::atomic::{AtomicBool, Ordering};
use smol_str::SmolStr;
use winit_core::event::{ElementState, KeyEvent};
use winit_core::keyboard::{Key, KeyLocation};
use xkbcommon_dl::{
self as xkb, XkbCommon, XkbCommonCompose, xkb_compose_status, xkb_context, xkb_context_flags,
xkbcommon_compose_handle, xkbcommon_handle,
};
#[cfg(feature = "x11")]
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
mod compose;
mod keymap;
mod state;
use compose::{ComposeStatus, XkbComposeState, XkbComposeTable};
use keymap::XkbKeymap;
#[cfg(feature = "x11")]
pub use keymap::raw_keycode_to_physicalkey;
pub use keymap::{physicalkey_to_scancode, scancode_to_physicalkey};
pub use state::XkbState;
// TODO: Wire this up without using a static `AtomicBool`.
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
static XKBH: LazyLock<&'static XkbCommon> = LazyLock::new(xkbcommon_handle);
static XKBCH: LazyLock<&'static XkbCommonCompose> = LazyLock::new(xkbcommon_compose_handle);
#[cfg(feature = "x11")]
static XKBXH: LazyLock<&'static xkb::x11::XkbCommonX11> = LazyLock::new(xkbcommon_x11_handle);
#[inline(always)]
pub fn reset_dead_keys() {
RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
}
#[derive(Debug)]
pub enum Error {
/// libxkbcommon is not available
XKBNotFound,
}
#[derive(Debug)]
pub struct Context {
// NOTE: field order matters.
#[cfg(feature = "x11")]
pub core_keyboard_id: i32,
state: Option<XkbState>,
keymap: Option<XkbKeymap>,
compose_state1: Option<XkbComposeState>,
compose_state2: Option<XkbComposeState>,
_compose_table: Option<XkbComposeTable>,
context: XkbContext,
scratch_buffer: Vec<u8>,
}
impl Context {
pub fn new() -> Result<Self, Error> {
if xkb::xkbcommon_option().is_none() {
return Err(Error::XKBNotFound);
}
let context = XkbContext::new()?;
let mut compose_table = XkbComposeTable::new(&context);
let mut compose_state1 = compose_table.as_ref().and_then(|table| table.new_state());
let mut compose_state2 = compose_table.as_ref().and_then(|table| table.new_state());
// Disable compose if anything compose related failed to initialize.
if compose_table.is_none() || compose_state1.is_none() || compose_state2.is_none() {
compose_state2 = None;
compose_state1 = None;
compose_table = None;
}
Ok(Self {
state: None,
keymap: None,
compose_state1,
compose_state2,
#[cfg(feature = "x11")]
core_keyboard_id: 0,
_compose_table: compose_table,
context,
scratch_buffer: Vec::with_capacity(8),
})
}
#[cfg(feature = "x11")]
pub fn from_x11_xkb(xcb: *mut xcb_connection_t) -> Result<Self, Error> {
let result = unsafe {
(XKBXH.xkb_x11_setup_xkb_extension)(
xcb,
1,
2,
xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
};
if result != 1 {
return Err(Error::XKBNotFound);
}
let mut this = Self::new()?;
this.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(xcb) };
this.set_keymap_from_x11(xcb);
Ok(this)
}
pub fn state_mut(&mut self) -> Option<&mut XkbState> {
self.state.as_mut()
}
pub fn keymap_mut(&mut self) -> Option<&mut XkbKeymap> {
self.keymap.as_mut()
}
#[cfg(feature = "wayland")]
pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) {
let keymap = XkbKeymap::from_fd(&self.context, fd, size);
let state = keymap.as_ref().and_then(XkbState::new_wayland);
if keymap.is_none() || state.is_none() {
tracing::warn!("failed to update xkb keymap");
}
self.state = state;
self.keymap = keymap;
}
#[cfg(feature = "x11")]
pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) {
let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id);
let state = keymap.as_ref().and_then(|keymap| XkbState::new_x11(xcb, keymap));
if keymap.is_none() || state.is_none() {
tracing::warn!("failed to update xkb keymap");
}
self.state = state;
self.keymap = keymap;
}
/// Key builder context with the user provided xkb state.
pub fn key_context(&mut self) -> Option<KeyContext<'_>> {
let state = self.state.as_mut()?;
let keymap = self.keymap.as_mut()?;
let compose_state1 = self.compose_state1.as_mut();
let compose_state2 = self.compose_state2.as_mut();
let scratch_buffer = &mut self.scratch_buffer;
Some(KeyContext { state, keymap, compose_state1, compose_state2, scratch_buffer })
}
/// Key builder context with the user provided xkb state.
///
/// Should be used when the original context must not be altered.
#[cfg(feature = "x11")]
pub fn key_context_with_state<'a>(
&'a mut self,
state: &'a mut XkbState,
) -> Option<KeyContext<'a>> {
let keymap = self.keymap.as_mut()?;
let compose_state1 = self.compose_state1.as_mut();
let compose_state2 = self.compose_state2.as_mut();
let scratch_buffer = &mut self.scratch_buffer;
Some(KeyContext { state, keymap, compose_state1, compose_state2, scratch_buffer })
}
}
pub struct KeyContext<'a> {
pub state: &'a mut XkbState,
pub keymap: &'a mut XkbKeymap,
compose_state1: Option<&'a mut XkbComposeState>,
compose_state2: Option<&'a mut XkbComposeState>,
scratch_buffer: &'a mut Vec<u8>,
}
impl KeyContext<'_> {
pub fn process_key_event(
&mut self,
keycode: u32,
state: ElementState,
repeat: bool,
) -> KeyEvent {
let mut event =
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
let physical_key = keymap::raw_keycode_to_physicalkey(keycode);
let (logical_key, location) = event.key();
let text = event.text();
let (key_without_modifiers, _) = event.key_without_modifiers();
let text_with_all_modifiers = event.text_with_all_modifiers();
KeyEvent {
physical_key,
logical_key,
text,
location,
state,
repeat,
text_with_all_modifiers,
key_without_modifiers,
}
}
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
self.scratch_buffer.clear();
self.scratch_buffer.reserve(8);
loop {
let bytes_written = unsafe {
(XKBH.xkb_keysym_to_utf8)(
keysym,
self.scratch_buffer.as_mut_ptr().cast(),
self.scratch_buffer.capacity(),
)
};
if bytes_written == 0 {
return None;
} else if bytes_written == -1 {
self.scratch_buffer.reserve(8);
} else {
unsafe { self.scratch_buffer.set_len(bytes_written.try_into().unwrap()) };
break;
}
}
// Remove the null-terminator
self.scratch_buffer.pop();
byte_slice_to_smol_str(self.scratch_buffer)
}
}
struct KeyEventResults<'a, 'b> {
context: &'a mut KeyContext<'b>,
keycode: u32,
keysym: u32,
compose: ComposeStatus,
}
impl<'a, 'b> KeyEventResults<'a, 'b> {
fn new(context: &'a mut KeyContext<'b>, keycode: u32, compose: bool) -> Self {
let keysym = context.state.get_one_sym_raw(keycode);
let compose = if let Some(state) = context.compose_state1.as_mut().filter(|_| compose) {
if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
state.reset();
context.compose_state2.as_mut().unwrap().reset();
}
state.feed(keysym)
} else {
ComposeStatus::None
};
KeyEventResults { context, keycode, keysym, compose }
}
pub fn key(&mut self) -> (Key, KeyLocation) {
let (key, location) = match self.keysym_to_key(self.keysym) {
Ok(known) => return known,
Err(undefined) => undefined,
};
if let ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSING) = self.compose {
let compose_state = self.context.compose_state2.as_mut().unwrap();
// When pressing a dead key twice, the non-combining variant of that character will
// be produced. Since this function only concerns itself with a single keypress, we
// simulate this double press here by feeding the keysym to the compose state
// twice.
compose_state.feed(self.keysym);
if matches!(compose_state.feed(self.keysym), ComposeStatus::Accepted(_)) {
// Extracting only a single `char` here *should* be fine, assuming that no
// dead key's non-combining variant ever occupies more than one `char`.
let text = compose_state.get_string(self.context.scratch_buffer);
let key = Key::Dead(text.and_then(|s| s.chars().next()));
(key, location)
} else {
(key, location)
}
} else {
let key = self
.composed_text()
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
.map(Key::Character)
.unwrap_or(key);
(key, location)
}
}
pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
// This will become a pointer to an array which libxkbcommon owns, so we don't need to
// deallocate it.
let layout = self.context.state.layout(self.keycode);
let keysym = self.context.keymap.first_keysym_by_level(layout, self.keycode);
match self.keysym_to_key(keysym) {
Ok((key, location)) => (key, location),
Err((key, location)) => {
let key =
self.context.keysym_to_utf8_raw(keysym).map(Key::Character).unwrap_or(key);
(key, location)
},
}
}
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
let location = keymap::keysym_location(keysym);
let key = keymap::keysym_to_key(keysym);
if matches!(key, Key::Unidentified(_)) { Err((key, location)) } else { Ok((key, location)) }
}
pub fn text(&mut self) -> Option<SmolStr> {
self.composed_text().unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
}
// The current behaviour makes it so composing a character overrides attempts to input a
// control character with the `Ctrl` key. We can potentially add a configuration option
// if someone specifically wants the opposite behaviour.
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
match self.composed_text() {
Ok(text) => text,
Err(_) => self.context.state.get_utf8_raw(self.keycode, self.context.scratch_buffer),
}
}
fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
match self.compose {
ComposeStatus::Accepted(status) => match status {
xkb_compose_status::XKB_COMPOSE_COMPOSED => {
let state = self.context.compose_state1.as_mut().unwrap();
Ok(state.get_string(self.context.scratch_buffer))
},
xkb_compose_status::XKB_COMPOSE_COMPOSING
| xkb_compose_status::XKB_COMPOSE_CANCELLED => Ok(None),
xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()),
},
_ => Err(()),
}
}
}
#[derive(Debug)]
pub struct XkbContext {
context: NonNull<xkb_context>,
}
impl XkbContext {
pub fn new() -> Result<Self, Error> {
let context = unsafe { (XKBH.xkb_context_new)(xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
let context = match NonNull::new(context) {
Some(context) => context,
None => return Err(Error::XKBNotFound),
};
Ok(Self { context })
}
}
impl Drop for XkbContext {
fn drop(&mut self) {
unsafe {
(XKBH.xkb_context_unref)(self.context.as_ptr());
}
}
}
impl Deref for XkbContext {
type Target = NonNull<xkb_context>;
fn deref(&self) -> &Self::Target {
&self.context
}
}
/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
/// `xkb_state_key_get_utf8`.
fn make_string_with<F>(scratch_buffer: &mut Vec<u8>, mut f: F) -> Option<SmolStr>
where
F: FnMut(*mut c_char, usize) -> i32,
{
let size = f(ptr::null_mut(), 0);
if size == 0 {
return None;
}
let size = usize::try_from(size).unwrap();
scratch_buffer.clear();
// The allocated buffer must include space for the null-terminator.
scratch_buffer.reserve(size + 1);
unsafe {
let written = f(scratch_buffer.as_mut_ptr().cast(), scratch_buffer.capacity());
if usize::try_from(written).unwrap() != size {
// This will likely never happen.
return None;
}
scratch_buffer.set_len(size);
};
byte_slice_to_smol_str(scratch_buffer)
}
// NOTE: This is track_caller so we can have more informative line numbers when logging
#[track_caller]
fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
std::str::from_utf8(bytes)
.map(SmolStr::new)
.map_err(|e| {
tracing::warn!("UTF-8 received from libxkbcommon ({:?}) was invalid: {e}", bytes)
})
.ok()
}

View File

@@ -1,190 +0,0 @@
//! XKB state.
use std::os::raw::c_char;
use std::ptr::NonNull;
use smol_str::SmolStr;
#[cfg(feature = "x11")]
use x11_dl::xlib_xcb::xcb_connection_t;
use xkbcommon_dl::{
self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component,
};
#[cfg(feature = "x11")]
use super::XKBXH;
use super::keymap::XkbKeymap;
use super::{XKBH, make_string_with};
#[derive(Debug)]
pub struct XkbState {
state: NonNull<xkb_state>,
modifiers: ModifiersState,
}
impl XkbState {
#[cfg(feature = "wayland")]
pub fn new_wayland(keymap: &XkbKeymap) -> Option<Self> {
let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?;
Some(Self::new_inner(state))
}
#[cfg(feature = "x11")]
pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option<Self> {
let state = unsafe {
(XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id)
};
let state = NonNull::new(state)?;
Some(Self::new_inner(state))
}
fn new_inner(state: NonNull<xkb_state>) -> Self {
let modifiers = ModifiersState::default();
let mut this = Self { state, modifiers };
this.reload_modifiers();
this
}
pub fn get_one_sym_raw(&mut self, keycode: xkb_keycode_t) -> xkb_keysym_t {
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.state.as_ptr(), keycode) }
}
pub fn layout(&mut self, key: xkb_keycode_t) -> xkb_layout_index_t {
unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) }
}
#[cfg(feature = "x11")]
pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
self.state.as_ptr(),
xkb_state_component::XKB_STATE_MODS_DEPRESSED,
)
}
}
#[cfg(feature = "x11")]
pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
self.state.as_ptr(),
xkb_state_component::XKB_STATE_MODS_LATCHED,
)
}
}
#[cfg(feature = "x11")]
pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
self.state.as_ptr(),
xkb_state_component::XKB_STATE_MODS_LOCKED,
)
}
}
pub fn get_utf8_raw(
&mut self,
keycode: xkb_keycode_t,
scratch_buffer: &mut Vec<u8>,
) -> Option<SmolStr> {
make_string_with(scratch_buffer, |ptr, len| unsafe {
(XKBH.xkb_state_key_get_utf8)(self.state.as_ptr(), keycode, ptr, len)
})
}
pub fn modifiers(&self) -> ModifiersState {
self.modifiers
}
pub fn update_modifiers(
&mut self,
mods_depressed: u32,
mods_latched: u32,
mods_locked: u32,
depressed_group: u32,
latched_group: u32,
locked_group: u32,
) {
let mask = unsafe {
(XKBH.xkb_state_update_mask)(
self.state.as_ptr(),
mods_depressed,
mods_latched,
mods_locked,
depressed_group,
latched_group,
locked_group,
)
};
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
// Effective value of mods have changed, we need to update our state.
self.reload_modifiers();
}
}
/// Reload the modifiers.
fn reload_modifiers(&mut self) {
self.modifiers.ctrl = self.mod_name_is_active(xkb::XKB_MOD_NAME_CTRL);
self.modifiers.alt = self.mod_name_is_active(xkb::XKB_MOD_NAME_ALT);
self.modifiers.shift = self.mod_name_is_active(xkb::XKB_MOD_NAME_SHIFT);
self.modifiers.caps_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_CAPS);
self.modifiers.logo = self.mod_name_is_active(xkb::XKB_MOD_NAME_LOGO);
self.modifiers.num_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_NUM);
}
/// Check if the modifier is active within xkb.
fn mod_name_is_active(&mut self, name: &[u8]) -> bool {
unsafe {
(XKBH.xkb_state_mod_name_is_active)(
self.state.as_ptr(),
name.as_ptr() as *const c_char,
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
) > 0
}
}
}
impl Drop for XkbState {
fn drop(&mut self) {
unsafe {
(XKBH.xkb_state_unref)(self.state.as_ptr());
}
}
}
/// Represents the current logical state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
///
/// For some modifiers, this means that the key is logically pressed, others are toggled (like Caps
/// Lock). But physically the key can be in any state (for example, released Shift when
/// it's active as a sticky modifier or released Caps Lock when it's toggled on)
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ModifiersState {
/// The "control" key
pub ctrl: bool,
/// The "alt" key
pub alt: bool,
/// The "shift" key
pub shift: bool,
/// The "Caps lock" key
pub caps_lock: bool,
/// The "logo" key
///
/// Also known as the "windows" key on most keyboards
pub logo: bool,
/// The "Num lock" key
pub num_lock: bool,
}
impl From<ModifiersState> for winit_core::keyboard::ModifiersState {
fn from(mods: ModifiersState) -> winit_core::keyboard::ModifiersState {
let mut to_mods = winit_core::keyboard::ModifiersState::empty();
to_mods.set(winit_core::keyboard::ModifiersState::SHIFT, mods.shift);
to_mods.set(winit_core::keyboard::ModifiersState::CONTROL, mods.ctrl);
to_mods.set(winit_core::keyboard::ModifiersState::ALT, mods.alt);
to_mods.set(winit_core::keyboard::ModifiersState::META, mods.logo);
to_mods
}
}

View File

@@ -1,39 +1,32 @@
[package] [package]
authors = ["The winit contributors", "Kirill Chibisov <contact@kchibisov.com>"]
categories = ["gui"]
description = "winit core API."
documentation = "https://docs.rs/winit-core"
edition.workspace = true
keywords = ["windowing"]
license.workspace = true
name = "winit-core" name = "winit-core"
version = "0.1.0"
authors = ["The winit contributors"]
description = "Base types for windowing libraries"
edition = "2021"
keywords = ["windowing"]
license = "Apache-2.0"
readme = "README.md" readme = "README.md"
repository.workspace = true repository = "https://github.com/rust-windowing/winit"
rust-version.workspace = true documentation = "https://docs.rs/winit"
version.workspace = true categories = ["gui"]
rust-version = "1.70.0"
[features] [features]
serde = [ default = ["std"]
"dep:serde", std = ["alloc"]
"bitflags/serde", alloc = []
"cursor-icon/serde", serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"]
"dpi/serde",
"keyboard-types/serde",
"smol_str/serde",
]
[dependencies] [dependencies]
bitflags.workspace = true bitflags.workspace = true
cursor-icon.workspace = true cursor-icon.workspace = true
dpi.workspace = true mint = { version = "0.5.6", optional = true }
keyboard-types.workspace = true
rwh_06.workspace = true
serde = { workspace = true, optional = true } serde = { workspace = true, optional = true }
smol_str.workspace = true smol_str.workspace = true
# `wasm32-unknown-unknown` and `wasm32-none`, but not `wasm32-wasi`. [target.'cfg(target_family = "wasm")'.dependencies]
[target.'cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))'.dependencies]
web-time.workspace = true web-time.workspace = true
[dev-dependencies] [build-dependencies]
winit.workspace = true cfg_aliases.workspace = true

View File

@@ -1 +0,0 @@
../LICENSE

View File

@@ -1 +0,0 @@
../README.md

3
winit-core/README.md Normal file
View File

@@ -0,0 +1,3 @@
# winit-core - Base types for a windowing system
TODO

24
winit-core/build.rs Normal file
View File

@@ -0,0 +1,24 @@
use cfg_aliases::cfg_aliases;
fn main() {
// The script doesn't depend on our code
println!("cargo:rerun-if-changed=build.rs");
// Setup cfg aliases
cfg_aliases! {
// Systems.
android_platform: { target_os = "android" },
web_platform: { all(target_family = "wasm", target_os = "unknown") },
macos_platform: { target_os = "macos" },
ios_platform: { target_os = "ios" },
windows_platform: { target_os = "windows" },
apple: { any(target_os = "ios", target_os = "macos") },
free_unix: { all(unix, not(apple), not(android_platform), not(target_os = "emscripten")) },
redox: { target_os = "redox" },
// Native displays.
x11_platform: { all(feature = "x11", free_unix, not(redox)) },
wayland_platform: { all(feature = "wayland", free_unix, not(redox)) },
orbital_platform: { redox },
}
}

View File

@@ -1,52 +0,0 @@
use crate::application::ApplicationHandler;
use crate::event_loop::ActiveEventLoop;
use crate::window::WindowId;
/// 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;
}
}

View File

@@ -1,482 +0,0 @@
//! End user application handling.
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::event_loop::ActiveEventLoop;
use crate::window::WindowId;
pub mod macos;
/// The handler of application-level events.
pub trait ApplicationHandler {
/// Emitted when new events arrive from the OS to be processed.
///
/// This is a useful place to put code that should be done before you start processing
/// events, such as updating frame timing information for benchmarking or checking the
/// [`StartCause`] to see if a timer set by
/// [`ControlFlow::WaitUntil`][crate::event_loop::ControlFlow::WaitUntil] has elapsed.
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
let _ = (event_loop, cause);
}
/// Emitted when the application has been resumed.
///
/// See [`suspended()`][Self::suspended].
///
/// ## Platform-specific
///
/// ### iOS
///
/// On iOS, the [`resumed()`] method is called in response to an [`applicationDidBecomeActive`]
/// callback which means the application is about to transition from the inactive to active
/// state (according to the [iOS application lifecycle]).
///
/// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
///
/// ### Web
///
/// On Web, the [`resumed()`] method is called in response to a [`pageshow`] event if the
/// page is being restored from the [`bfcache`] (back/forward cache) - an in-memory cache
/// that stores a complete snapshot of a page (including the JavaScript heap) as the user is
/// navigating away.
///
/// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event
/// [`bfcache`]: https://web.dev/bfcache/
///
/// ### Android
///
/// On Android, the [`resumed()`] method is called when the `Activity` is (again, if after a
/// prior [`suspended()`]) being displayed to the user. This is a good place to begin drawing
/// visual elements, running animations, etc. It is driven by Android's [`onStart()`] method.
///
/// [`onStart()`]: https://developer.android.com/reference/android/app/Activity#onStart()
///
/// ### Others
///
/// **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
///
/// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended()
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted from the point onwards the application should create render surfaces.
///
/// See [`destroy_surfaces()`].
///
/// ## Portability
///
/// It's recommended that applications should only initialize their render surfaces after the
/// [`can_create_surfaces()`] method is called. Some systems (specifically Android) won't allow
/// applications to create a render surface until that point.
///
/// For consistency, all platforms call this method even if they don't themselves have a formal
/// surface destroy/create lifecycle. For systems without a surface destroy/create lifecycle the
/// [`can_create_surfaces()`] event is always emitted after the [`StartCause::Init`] event.
///
/// Applications should be able to gracefully handle back-to-back [`can_create_surfaces()`] and
/// [`destroy_surfaces()`] calls.
///
/// ## Platform-specific
///
/// ### Android
///
/// On Android, the [`can_create_surfaces()`] method is called when a new [`NativeWindow`]
/// (native [`Surface`]) is created which backs the application window. This is expected to
/// closely correlate with the [`onStart`] lifecycle event which typically results in a surface
/// to be created after the app becomes visible.
///
/// Applications that need to run on Android must wait until they have received a surface before
/// they will be able to create a render surface (such as an `EGLSurface`, [`VkSurfaceKHR`]
/// or [`wgpu::Surface`]) which depend on having a [`NativeWindow`]. Applications must handle
/// [`destroy_surfaces()`], where their render surfaces are invalid and should be dropped.
///
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
/// [`onStart`]: https://developer.android.com/reference/android/app/Activity#onStart()
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop);
/// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
///
/// Multiple calls to the aforementioned method will be merged, and will only wake the event
/// loop once; however, due to the nature of multi-threading some wake ups may appear
/// spuriously. For these reasons, you should not rely on the number of times that this was
/// called.
///
/// The order in which this is emitted in relation to other events is not guaranteed. The time
/// at which this will be emitted is not guaranteed, only that it will happen "soon". That is,
/// there may be several executions of the event loop, including multiple redraws to windows,
/// between [`EventLoopProxy::wake_up()`] being called and the event being delivered.
///
/// [`EventLoopProxy::wake_up()`]: crate::event_loop::EventLoopProxy::wake_up
///
/// # Example
///
/// Use a [`std::sync::mpsc`] channel to handle events from a different thread.
///
/// ```no_run
/// use std::sync::mpsc;
/// use std::thread;
/// use std::time::Duration;
///
/// use winit::event_loop::EventLoop;
/// use winit_core::application::ApplicationHandler;
/// use winit_core::event_loop::ActiveEventLoop;
///
/// struct MyApp {
/// receiver: mpsc::Receiver<u64>,
/// }
///
/// impl ApplicationHandler for MyApp {
/// # fn window_event(
/// # &mut self,
/// # _event_loop: &dyn ActiveEventLoop,
/// # _window_id: winit::window::WindowId,
/// # _event: winit::event::WindowEvent,
/// # ) {
/// # }
/// #
/// # fn can_create_surfaces(&mut self, _event_loop: &dyn ActiveEventLoop) {}
/// #
/// fn proxy_wake_up(&mut self, _event_loop: &dyn ActiveEventLoop) {
/// // Iterate current events, since wake-ups may have been merged.
/// //
/// // Note: We take care not to use `recv` or `iter` here, as those are blocking,
/// // and that would be bad for performance and might lead to a deadlock.
/// for i in self.receiver.try_iter() {
/// println!("received: {i}");
/// }
/// }
///
/// // Rest of `ApplicationHandler`
/// }
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let event_loop = EventLoop::new()?;
///
/// let (sender, receiver) = mpsc::channel();
///
/// // Send an event in a loop
/// let proxy = event_loop.create_proxy();
/// let background_thread = thread::spawn(move || {
/// let mut i = 0;
/// loop {
/// println!("sending: {i}");
/// if sender.send(i).is_err() {
/// // Stop sending once the receiver is dropped
/// break;
/// }
/// // Trigger the wake-up _after_ we placed the event in the channel.
/// // Otherwise, `proxy_wake_up` might be triggered prematurely.
/// proxy.wake_up();
/// i += 1;
/// thread::sleep(Duration::from_secs(1));
/// }
/// });
///
/// event_loop.run_app(MyApp { receiver })?;
///
/// background_thread.join().unwrap();
///
/// Ok(())
/// }
/// ```
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted when the OS sends an event to a winit window.
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
);
/// Emitted when the OS sends an event to a device.
///
/// Whether device events are delivered depends on the backend in use.
fn device_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>,
event: DeviceEvent,
) {
let _ = (event_loop, device_id, event);
}
/// Emitted when the event loop is about to block and wait for new events.
///
/// Most applications shouldn't need to hook into this event since there is no real relationship
/// between how often the event loop needs to wake up and the dispatching of any specific
/// events.
///
/// High frequency event sources, such as input devices could potentially lead to lots of wake
/// ups and also lots of corresponding `AboutToWait` events.
///
/// This is not an ideal event to drive application rendering from and instead applications
/// should render in response to [`WindowEvent::RedrawRequested`] events.
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted when the application has been suspended.
///
/// See [`resumed()`][Self::resumed].
///
/// ## Platform-specific
///
/// ### iOS
///
/// On iOS, the [`suspended()`] method is called in response to an
/// [`applicationWillResignActive`] callback which means that the application is about to
/// transition from the active to inactive state (according to the [iOS application lifecycle]).
///
/// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
///
/// ### Web
///
/// On Web, the [`suspended()`] method is called in response to a [`pagehide`] event if the
/// page is being stored in the [`bfcache`] (back/forward cache) - an in-memory cache that
/// stores a complete snapshot of a page (including the JavaScript heap) as the user is
/// navigating away.
///
/// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
/// [`bfcache`]: https://web.dev/bfcache/
///
/// ### Android
///
/// On Android, the [`suspended()`] method is called when the `Activity` is no longer visible
/// to the user. This is a good place to stop refreshing UI, running animations and other visual
/// things. It is driven by Android's [`onStop()`] method.
///
/// After this event the application either receives [`resumed()`] again, or will exit.
///
/// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop()
///
/// ### Others
///
/// **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
///
/// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended()
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted when the application must destroy its render surfaces.
///
/// See [`can_create_surfaces()`] for more details.
///
/// ## Platform-specific
///
/// ### Android
///
/// On Android, the [`destroy_surfaces()`] method is called when the application's
/// [`NativeWindow`] (native [`Surface`]) is destroyed. This is expected to closely correlate
/// with the [`onStop`] lifecycle event which typically results in the surface to be destroyed
/// after the app becomes invisible.
///
/// Applications that need to run on Android should assume their [`NativeWindow`] has been
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
///
/// When receiving [`destroy_surfaces()`] Android applications should drop all render surfaces
/// before the event callback completes, which may be re-created when the application next
/// receives [`can_create_surfaces()`].
///
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
/// [`onStop`]: https://developer.android.com/reference/android/app/Activity#onStop()
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// ### Others
///
/// - **iOS / macOS / Orbital / Wayland / Web / Windows / X11:** Unsupported.
///
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// Emitted when the application has received a memory warning.
///
/// ## Platform-specific
///
/// ### Android
///
/// On Android, the `MemoryWarning` event is sent when [`onLowMemory`] was called. The
/// application must [release memory] or risk being killed.
///
/// [`onLowMemory`]: https://developer.android.com/reference/android/app/Application.html#onLowMemory()
/// [release memory]: https://developer.android.com/topic/performance/memory#release
///
/// ### iOS
///
/// On iOS, the `MemoryWarning` event is emitted in response to an
/// [`applicationDidReceiveMemoryWarning`] callback. The application must free as much
/// memory as possible or risk being terminated, see [how to respond to memory warnings].
///
/// [`applicationDidReceiveMemoryWarning`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni
/// [how to respond to memory warnings]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle/responding_to_memory_warnings
///
/// ### Others
///
/// - **macOS / Orbital / Wayland / Web / Windows:** Unsupported.
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
/// The macOS-specific handler.
///
/// The return value from this should not change at runtime.
#[inline(always)]
fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
None
}
}
#[deny(clippy::missing_trait_methods)]
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
#[inline]
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
}
#[inline]
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).resumed(event_loop);
}
#[inline]
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).can_create_surfaces(event_loop);
}
#[inline]
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
}
#[inline]
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
(**self).window_event(event_loop, window_id, event);
}
#[inline]
fn device_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>,
event: DeviceEvent,
) {
(**self).device_event(event_loop, device_id, event);
}
#[inline]
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).about_to_wait(event_loop);
}
#[inline]
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).suspended(event_loop);
}
#[inline]
fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).destroy_surfaces(event_loop);
}
#[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop);
}
#[inline]
fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
(**self).macos_handler()
}
}
#[deny(clippy::missing_trait_methods)]
impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
#[inline]
fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) {
(**self).new_events(event_loop, cause);
}
#[inline]
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).resumed(event_loop);
}
#[inline]
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).can_create_surfaces(event_loop);
}
#[inline]
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).proxy_wake_up(event_loop);
}
#[inline]
fn window_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
(**self).window_event(event_loop, window_id, event);
}
#[inline]
fn device_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
device_id: Option<DeviceId>,
event: DeviceEvent,
) {
(**self).device_event(event_loop, device_id, event);
}
#[inline]
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).about_to_wait(event_loop);
}
#[inline]
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).suspended(event_loop);
}
#[inline]
fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).destroy_surfaces(event_loop);
}
#[inline]
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
(**self).memory_warning(event_loop);
}
#[inline]
fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
(**self).macos_handler()
}
}

View File

@@ -1,92 +0,0 @@
use std::any::Any;
// NOTE: This is `pub`, but isn't actually exposed outside the crate.
// NOTE: Marked as `#[doc(hidden)]` and underscored, because they can be quite difficult to use
// correctly, see discussion in #4160.
// FIXME: Remove and replace with a coercion once rust-lang/rust#65991 is in MSRV (1.86).
#[doc(hidden)]
pub trait AsAny: Any {
#[doc(hidden)]
fn __as_any(&self) -> &dyn Any;
#[doc(hidden)]
fn __as_any_mut(&mut self) -> &mut dyn Any;
#[doc(hidden)]
fn __into_any(self: Box<Self>) -> Box<dyn Any>;
}
impl<T: Any> AsAny for T {
#[inline(always)]
fn __as_any(&self) -> &dyn Any {
self
}
#[inline(always)]
fn __as_any_mut(&mut self) -> &mut dyn Any {
self
}
#[inline(always)]
fn __into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
#[macro_export]
macro_rules! impl_dyn_casting {
($trait:ident) => {
impl dyn $trait + '_ {
/// Downcast to the backend concrete type.
///
/// Returns `None` if the object was not from that backend.
pub fn cast_ref<T: $trait>(&self) -> Option<&T> {
let this: &dyn std::any::Any = self.__as_any();
this.downcast_ref::<T>()
}
/// Mutable downcast to the backend concrete type.
///
/// Returns `None` if the object was not from that backend.
pub fn cast_mut<T: $trait>(&mut self) -> Option<&mut T> {
let this: &mut dyn std::any::Any = self.__as_any_mut();
this.downcast_mut::<T>()
}
/// Owned downcast to the backend concrete type.
///
/// Returns `Err` with `self` if the object was not from that backend.
pub fn cast<T: $trait>(self: Box<Self>) -> Result<Box<T>, Box<Self>> {
if self.cast_ref::<T>().is_some() {
let this: Box<dyn std::any::Any> = self.__into_any();
// Unwrap is okay, we just checked the type of `self` is `T`.
Ok(this.downcast::<T>().unwrap())
} else {
Err(self)
}
}
}
};
}
pub use impl_dyn_casting;
#[cfg(test)]
mod tests {
use super::AsAny;
struct Foo;
trait FooTrait: AsAny {}
impl FooTrait for Foo {}
impl_dyn_casting!(FooTrait);
#[test]
fn dyn_casting() {
let foo_owned: Box<dyn FooTrait> = Box::new(Foo);
assert!(foo_owned.cast::<Foo>().is_ok());
let mut foo = Foo;
let foo_ref: &mut dyn FooTrait = &mut foo;
assert!((foo_ref).cast_ref::<Foo>().is_some());
assert!((&&&&foo_ref).cast_ref::<Foo>().is_some());
assert!(foo_ref.cast_mut::<Foo>().is_some());
}
}

View File

@@ -1,336 +0,0 @@
use core::fmt;
use std::error::Error;
use std::hash::Hash;
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
#[doc(inline)]
pub use cursor_icon::CursorIcon;
use crate::as_any::AsAny;
/// The maximum width and height for a cursor when using [`CustomCursorSource::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048;
const PIXEL_SIZE: usize = 4;
/// See [`Window::set_cursor()`][crate::window::Window::set_cursor] for more details.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Cursor {
Icon(CursorIcon),
Custom(CustomCursor),
}
impl Default for Cursor {
fn default() -> Self {
Self::Icon(CursorIcon::default())
}
}
impl From<CursorIcon> for Cursor {
fn from(icon: CursorIcon) -> Self {
Self::Icon(icon)
}
}
impl From<CustomCursor> for Cursor {
fn from(custom: CustomCursor) -> Self {
Self::Custom(custom)
}
}
/// Use a custom image as a cursor (mouse pointer).
///
/// Is guaranteed to be cheap to clone.
///
/// ## Platform-specific
///
/// **Web**: Some browsers have limits on cursor sizes usually at 128x128.
///
/// # Example
///
/// ```no_run
/// # use winit_core::event_loop::ActiveEventLoop;
/// # use winit_core::window::Window;
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) {
/// use winit_core::cursor::CustomCursorSource;
///
/// let w = 10;
/// let h = 10;
/// let rgba = vec![255; (w * h * 4) as usize];
///
/// #[cfg(not(target_family = "wasm"))]
/// let source = CustomCursorSource::from_rgba(rgba, w, h, w / 2, h / 2).unwrap();
///
/// #[cfg(target_family = "wasm")]
/// let source = CustomCursorSource::Url {
/// url: String::from("http://localhost:3000/cursor.png"),
/// hotspot_x: 0,
/// hotspot_y: 0,
/// };
///
/// if let Ok(custom_cursor) = event_loop.create_custom_cursor(source) {
/// window.set_cursor(custom_cursor.clone().into());
/// }
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct CustomCursor(pub Arc<dyn CustomCursorProvider>);
pub trait CustomCursorProvider: AsAny + fmt::Debug + Send + Sync {
/// Whether a cursor was backed by animation.
fn is_animated(&self) -> bool;
}
impl PartialEq for CustomCursor {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for CustomCursor {}
impl Hash for CustomCursor {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.0).hash(state);
}
}
impl Deref for CustomCursor {
type Target = dyn CustomCursorProvider;
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl_dyn_casting!(CustomCursorProvider);
/// Source for [`CustomCursor`].
///
/// See [`CustomCursor`] for more details.
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum CustomCursorSource {
/// Cursor that is backed by RGBA image.
///
/// See [CustomCursorSource::from_rgba] for more.
///
/// ## Platform-specific
///
/// - **iOS / Android / Orbital:** Unsupported
Image(CursorImage),
/// Animated cursor.
///
/// See [CustomCursorSource::from_animation] for more.
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported
Animation(CursorAnimation),
/// Creates a new cursor from a URL pointing to an image.
/// It uses the [url css function](https://developer.mozilla.org/en-US/docs/Web/CSS/url),
/// but browser support for image formats is inconsistent. Using [PNG] is recommended.
///
/// [PNG]: https://en.wikipedia.org/wiki/PNG
///
/// ## Platform-specific
///
/// - **iOS / Android / Wayland / Windows / X11 / macOS / Orbital:** Unsupported
Url { hotspot_x: u16, hotspot_y: u16, url: String },
}
impl CustomCursorSource {
/// Creates a new cursor from an rgba buffer.
///
/// The alpha channel is assumed to be **not** premultiplied.
pub fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
CursorImage::from_rgba(rgba, width, height, hotspot_x, hotspot_y).map(Self::Image)
}
/// Crates a new animated cursor from multiple [`CustomCursor`]s
/// Supplied `cursors` can't be empty or other animations.
pub fn from_animation(
duration: Duration,
cursors: Vec<CustomCursor>,
) -> Result<Self, BadAnimation> {
CursorAnimation::new(duration, cursors).map(Self::Animation)
}
}
/// An error produced when using [`CustomCursorSource::from_rgba`] with invalid arguments.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BadImage {
/// Produced when the image dimensions are larger than [`MAX_CURSOR_SIZE`]. This doesn't
/// guarantee that the cursor will work, but should avoid many platform and device specific
/// limits.
TooLarge { width: u16, height: u16 },
/// 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: u16, height: u16, width_x_height: u64, pixel_count: u64 },
/// Produced when the hotspot is outside the image bounds
HotspotOutOfBounds { width: u16, height: u16, hotspot_x: u16, hotspot_y: u16 },
}
impl fmt::Display for BadImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BadImage::TooLarge { width, height } => write!(
f,
"The specified dimensions ({width:?}x{height:?}) are too large. The maximum is \
{MAX_CURSOR_SIZE:?}x{MAX_CURSOR_SIZE:?}.",
),
BadImage::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.",
),
BadImage::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:?}.",
)
},
BadImage::HotspotOutOfBounds { width, height, hotspot_x, hotspot_y } => write!(
f,
"The specified hotspot ({hotspot_x:?}, {hotspot_y:?}) is outside the image bounds \
({width:?}x{height:?}).",
),
}
}
}
impl Error for BadImage {}
/// An error produced when using [`CustomCursorSource::from_animation`] with invalid arguments.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BadAnimation {
/// Produced when no cursors were supplied.
Empty,
/// Produced when a supplied cursor is an animation.
Animation,
}
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 {}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub struct CursorImage {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u16,
pub(crate) height: u16,
pub(crate) hotspot_x: u16,
pub(crate) hotspot_y: u16,
}
impl CursorImage {
pub(crate) fn from_rgba(
rgba: Vec<u8>,
width: u16,
height: u16,
hotspot_x: u16,
hotspot_y: u16,
) -> Result<Self, BadImage> {
if width > MAX_CURSOR_SIZE || height > MAX_CURSOR_SIZE {
return Err(BadImage::TooLarge { width, height });
}
if rgba.len() % PIXEL_SIZE != 0 {
return Err(BadImage::ByteCountNotDivisibleBy4 { byte_count: rgba.len() });
}
let pixel_count = (rgba.len() / PIXEL_SIZE) as u64;
let width_x_height = width as u64 * height as u64;
if pixel_count != width_x_height {
return Err(BadImage::DimensionsVsPixelCount {
width,
height,
width_x_height,
pixel_count,
});
}
if hotspot_x >= width || hotspot_y >= height {
return Err(BadImage::HotspotOutOfBounds { 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] {
self.rgba.as_mut_slice()
}
pub fn width(&self) -> u16 {
self.width
}
pub fn height(&self) -> u16 {
self.height
}
pub fn hotspot_x(&self) -> u16 {
self.hotspot_x
}
pub fn hotspot_y(&self) -> u16 {
self.hotspot_y
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CursorAnimation {
pub(crate) duration: Duration,
pub(crate) cursors: Vec<CustomCursor>,
}
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)
}
}

1004
winit-core/src/dpi.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,145 +1,54 @@
use std::error::Error; //! Common error types.
use std::fmt::{self, Display};
/// A general error that may occur while running or creating use std::{error, fmt};
/// the event loop.
#[derive(Debug)]
#[non_exhaustive]
pub enum EventLoopError {
/// The event loop can't be re-created.
RecreationAttempt,
/// Application has exit with an error status.
ExitFailure(i32),
/// Got unspecified OS-specific error during the request.
Os(OsError),
/// Creating the event loop with the requested configuration is not supported.
NotSupported(NotSupportedError),
}
impl fmt::Display for EventLoopError { /// The error type for when the requested operation is not supported by the backend.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[derive(Clone)]
match self {
Self::RecreationAttempt => {
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::ExitFailure(status) => write!(f, "Exit Failure: {status}"),
Self::NotSupported(err) => err.fmt(f),
}
}
}
impl Error for EventLoopError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::Os(err) = self { err.source() } else { None }
}
}
impl From<OsError> for EventLoopError {
fn from(value: OsError) -> Self {
Self::Os(value)
}
}
impl From<NotSupportedError> for EventLoopError {
fn from(value: NotSupportedError) -> Self {
Self::NotSupported(value)
}
}
/// A general error that may occur during a request to the windowing system.
#[derive(Debug)]
#[non_exhaustive]
pub enum RequestError {
/// The request is not supported.
NotSupported(NotSupportedError),
/// The request was ignored by the operating system.
Ignored,
/// Got unspecified OS specific error during the request.
Os(OsError),
}
impl Display for RequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotSupported(err) => err.fmt(f),
Self::Ignored => write!(f, "The request was ignored"),
Self::Os(err) => err.fmt(f),
}
}
}
impl Error for RequestError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Self::Os(err) = self { err.source() } else { None }
}
}
impl From<NotSupportedError> for RequestError {
fn from(value: NotSupportedError) -> Self {
Self::NotSupported(value)
}
}
impl From<OsError> for RequestError {
fn from(value: OsError) -> Self {
Self::Os(value)
}
}
/// The requested operation is not supported.
#[derive(Debug)]
pub struct NotSupportedError { pub struct NotSupportedError {
/// The reason why a certain operation is not supported. _marker: (),
reason: &'static str, }
impl Default for NotSupportedError {
fn default() -> Self {
Self::new()
}
} }
impl NotSupportedError { impl NotSupportedError {
pub fn new(reason: &'static str) -> Self { /// Create a new [`NotSupportedError`].
Self { reason } #[inline]
pub fn new() -> NotSupportedError {
NotSupportedError { _marker: () }
}
}
impl fmt::Debug for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("NotSupportedError").finish()
} }
} }
impl fmt::Display for NotSupportedError { impl fmt::Display for NotSupportedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Operation is not supported: {}", self.reason) f.pad("the requested operation is not supported by Winit")
}
}
impl Error for NotSupportedError {}
/// Unclassified error from the OS.
#[derive(Debug)]
pub struct OsError {
line: u32,
file: &'static str,
error: Box<dyn Error + Send + Sync + 'static>,
}
impl OsError {
pub fn new(
line: u32,
file: &'static str,
error: impl Into<Box<dyn Error + Send + Sync + 'static>>,
) -> Self {
Self { line, file, error: error.into() }
} }
} }
impl Display for OsError { impl error::Error for NotSupportedError {}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(&format!("os error at {}:{}: {}", self.file, self.line, self.error))
}
}
impl Error for OsError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self.error.as_ref())
}
}
#[allow(unused_macros)] #[cfg(test)]
macro_rules! os_error { mod tests {
($error:expr) => {{ crate::error::OsError::new(line!(), file!(), $error) }}; #![allow(clippy::redundant_clone)]
use super::*;
// Eat attributes for testing
#[test]
fn ensure_fmt_does_not_panic() {
let _ = format!(
"{:?}, {}",
NotSupportedError::new(),
NotSupportedError::new().clone()
);
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
//! Types needed to define the event loop.
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(web_platform))]
use std::time::{Duration, Instant};
#[cfg(web_platform)]
use web_time::{Duration, Instant};
/// 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 scenarious 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)]
pub struct AsyncRequestSerial {
serial: u64,
}
impl AsyncRequestSerial {
/// Get the next serial in the sequence.
pub fn get() -> Self {
static CURRENT_SERIAL: AtomicU64 = AtomicU64::new(0);
// NOTE: we rely on wrap around here, while the user may just request
// in the loop u64::MAX times that's issue is considered on them.
let serial = CURRENT_SERIAL.fetch_add(1, Ordering::Relaxed);
Self { serial }
}
}
/// Set through [`EventLoopWindowTarget::set_control_flow()`].
///
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
///
/// Defaults to [`Wait`].
///
/// [`Wait`]: Self::Wait
///
/// [`EventLoopWindowTarget::set_control_flow()`]: https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopWindowTarget.html#method.set_control_flow
/// [`Event::AboutToWait`]: crate::event::Event::AboutToWait
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// 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,
}
}
}

View File

@@ -1,291 +0,0 @@
pub mod never_return;
pub mod pump_events;
pub mod register;
pub mod run_on_demand;
use std::fmt::{self, Debug};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
use crate::Instant;
use crate::as_any::AsAny;
use crate::cursor::{CustomCursor, CustomCursorSource};
use crate::error::RequestError;
use crate::monitor::MonitorHandle;
use crate::window::{Theme, Window, WindowAttributes};
pub trait ActiveEventLoop: AsAny + fmt::Debug {
/// 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 `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 `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;
/// Stop the event loop.
///
/// ## Platform-specific
///
/// ### iOS
///
/// It is not possible to programmatically exit/quit an application on iOS, so this function is
/// a no-op there. See also [this technical Q&A][qa1561].
///
/// [qa1561]: https://developer.apple.com/library/archive/qa/qa1561/_index.html
fn exit(&self);
/// Returns whether the [`ActiveEventLoop`] is about to stop.
///
/// Set by [`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()
}
}
impl_dyn_casting!(ActiveEventLoop);
/// Control the [`ActiveEventLoop`], possibly from a different thread, without referencing it
/// directly.
#[derive(Clone, Debug)]
pub struct EventLoopProxy {
pub(crate) proxy: Arc<dyn EventLoopProxyProvider>,
}
impl EventLoopProxy {
/// Wake up the [`ActiveEventLoop`], 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`]: crate::application::ApplicationHandler::proxy_wake_up
/// [`ApplicationHandler::proxy_wake_up()`]: crate::application::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 fn new(proxy: Arc<dyn EventLoopProxyProvider>) -> Self {
Self { proxy }
}
}
pub trait EventLoopProxyProvider: Send + Sync + Debug {
/// See [`EventLoopProxy::wake_up`] for details.
fn wake_up(&self);
}
/// 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 underlying event loop 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 + Send + Sync>,
}
impl OwnedDisplayHandle {
pub fn new(handle: Arc<dyn HasDisplayHandle + Send + Sync>) -> 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 {}
/// 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,
}
}
}
/// 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 {
pub 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 }
}
}

View File

@@ -1,14 +0,0 @@
use crate::application::ApplicationHandler;
/// Additional methods on `EventLoop` for platforms whose run method never return.
pub trait EventLoopExtNeverReturn {
/// Run the event loop and call `process::exit` once finished.
///
/// ## Platform-specific
///
/// - **iOS**: This registers the callbacks with the system and calls `UIApplicationMain`.
/// - **macOS**: Unimplemented (TODO: Should call `NSApplicationMain`).
/// - **Android/Orbital/Wayland/Windows/X11**: Unsupported.
/// - **Web**: Impossible to support properly.
fn run_app_never_return<A: ApplicationHandler + 'static>(self, app: A) -> !;
}

View File

@@ -1,116 +0,0 @@
use std::time::Duration;
use crate::application::ApplicationHandler;
#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these.
/// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents {
/// Pump the `EventLoop` to check for and dispatch pending events.
///
/// This API is designed to enable applications to integrate Winit into an
/// external event loop, for platforms that can support this.
///
/// The given `timeout` limits how long it may block waiting for new events.
///
/// Passing a `timeout` of `Some(Duration::ZERO)` would ensure your external
/// event loop is never blocked but you would likely need to consider how
/// to throttle your own external loop.
///
/// Passing a `timeout` of `None` means that it may wait indefinitely for new
/// events before returning control back to the external loop.
///
/// **Note:** This is not a portable API, and its usage involves a number of
/// caveats and trade offs that should be considered before using this API!
///
/// You almost certainly shouldn't use this API, unless you absolutely know it's
/// the only practical option you have.
///
/// ## Synchronous events
///
/// Some events _must_ only be handled synchronously via the closure that
/// is passed to Winit so that the handler will also be synchronized with
/// the window system and operating system.
///
/// This is because some events are driven by a window system callback
/// where the window systems expects the application to have handled the
/// event before returning.
///
/// **These events can not be buffered and handled outside of the closure
/// passed to Winit.**
///
/// As a general rule it is not recommended to ever buffer events to handle
/// them outside of the closure passed to Winit since it's difficult to
/// provide guarantees about which events are safe to buffer across all
/// operating systems.
///
/// Notable events that will certainly create portability problems if
/// buffered and handled outside of Winit include:
/// - `RedrawRequested` events, used to schedule rendering.
///
/// macOS for example uses a `drawRect` callback to drive rendering
/// within applications and expects rendering to be finished before
/// the `drawRect` callback returns.
///
/// For portability it's strongly recommended that applications should
/// keep their rendering inside the closure provided to Winit.
/// - Any lifecycle events, such as `Suspended` / `Resumed`.
///
/// The handling of these events needs to be synchronized with the
/// operating system and it would never be appropriate to buffer a
/// notification that your application has been suspended or resumed and
/// then handled that later since there would always be a chance that
/// other lifecycle events occur while the event is buffered.
///
/// ## Supported Platforms
///
/// - Windows
/// - Linux
/// - MacOS
/// - Android
///
/// ## Unsupported Platforms
///
/// - **Web:** This API is fundamentally incompatible with the event-based way in which Web
/// browsers work because it's not possible to have a long-running external loop that would
/// block the browser and there is nothing that can be polled to ask for new new events.
/// Events are delivered via callbacks based on an event loop that is internal to the browser
/// itself.
/// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so
/// there's no way to support the same approach to polling as on MacOS.
///
/// ## Platform-specific
///
/// - **Windows**: The implementation will use `PeekMessage` when checking for window messages
/// to avoid blocking your external event loop.
///
/// - **MacOS**: The implementation works in terms of stopping the global application whenever
/// the application `RunLoop` indicates that it is preparing to block and wait for new events.
///
/// This is very different to the polling APIs that are available on other
/// platforms (the lower level polling primitives on MacOS are private
/// implementation details for `NSApplication` which aren't accessible to
/// application developers)
///
/// It's likely this will be less efficient than polling on other OSs and
/// it also means the `NSApplication` is stopped while outside of the Winit
/// event loop - and that's observable (for example to crates like `rfd`)
/// because the `NSApplication` is global state.
///
/// If you render outside of Winit you are likely to see window resizing artifacts
/// since MacOS expects applications to render synchronously during any `drawRect`
/// callback.
fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: A,
) -> PumpStatus;
}
/// The return status for `pump_events`
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum PumpStatus {
/// Continue running external loop.
Continue,
/// Exit external loop.
Exit(i32),
}

View File

@@ -1,17 +0,0 @@
use crate::application::ApplicationHandler;
/// Additional methods on `EventLoop` that registers it with the system event loop.
pub trait EventLoopExtRegister {
/// Initialize and register the application with the system's event loop, such that the
/// callbacks will be run later.
///
/// ## Platform-specific
///
/// - **Web**: 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.
/// - **iOS/macOS**: Unimplemented.
/// - **Android/Orbital/Wayland/Windows/X11**: Unsupported.
fn register_app<A: ApplicationHandler + 'static>(self, app: A);
}

View File

@@ -1,54 +0,0 @@
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
#[cfg(doc)]
use crate::{
event_loop::{ActiveEventLoop, pump_events::EventLoopExtPumpEvents},
window::Window,
};
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand {
/// Run the application with the event loop on the calling thread.
///
/// 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
/// `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
/// Winit application, but internally each instantiation may re-use some common window
/// system resources, such as a display server connection.
///
/// This API is not designed to run an event loop in bursts that you can exit from and return
/// to while maintaining the full state of your application. (If you need something like this
/// you can look at the [`EventLoopExtPumpEvents::pump_app_events()`] API)
///
/// Each time `run_app_on_demand` is called the startup sequence of `init`, followed by
/// `resume` is being preserved.
///
/// See the [`set_control_flow()`] docs on how to change the event loop's behavior.
///
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return
/// to the caller (specifically this is impossible on iOS and Web).
/// - 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
/// specifically need the ability to re-run a single event loop more than once
///
/// # Supported Platforms
/// - Windows
/// - Linux
/// - macOS
/// - Android
///
/// # Unsupported Platforms
/// - **Web:** This API is fundamentally incompatible with the event-based way in which Web
/// browsers work because it's not possible to have a long-running external loop that would
/// block the browser and there is nothing that can be polled to ask for new events. Events
/// are delivered via callbacks based on an event loop that is internal to the browser itself.
/// - **iOS:** It's not possible to stop and start an `UIApplication` repeatedly on iOS.
///
/// [`exit()`]: ActiveEventLoop::exit()
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;
}

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