Compare commits

...

75 Commits

Author SHA1 Message Date
Mads Marquart
2a391b348a Add EventLoopProxy::into_waker 2025-09-05 02:00:44 +02:00
moooozi
488c036a05 win32: disable DPI re-adjustments on Windows 11
For earlier Windows 10 builds (pre-22000), a workaround was necessary
to fix dragging window onto a monitor with different DPI. This commit makes
the old DPI workaround  to only apply conditionally on affected Windows versions.

Fixes #4041.
2025-08-24 12:57:31 +09:00
Tony
a4af50ec13 win32: refresh title bar on Window::set_theme 2025-08-23 21:55:54 +09:00
Tony
317d62fb93 win32: account for mouse wheel speed setting
Also adds a method to toggle this behavior during runtime.
2025-08-23 21:38:56 +09:00
dependabot[bot]
b13b39aa0b chore: bump ci deps versions 2025-08-23 21:19:46 +09:00
Sanjay
abea6e64e4 macOS: default menu uses bundle name
Use the bundle name in the default menu or fall back
to using the process name as before.
2025-08-23 20:35:45 +09:00
Jeremiah S
d6f7a28499 macOS: fix crash due during window drop
On macOS 26+ the window drop was leading to unwrap, since
events were coming after the window was already destroyed,
while it sounds rather strange, guard against such things just
in case.

Fixes #4333.
2025-08-15 09:52:23 +09:00
Kirill Chibisov
bd98561b38 chore: fix typos from recent typos-cli (#4329) 2025-08-12 16:30:34 +09:00
John Nunley
ca6f523924 m: remove self from CODEOWNERS
This is making official what's basically been the
case for the past few months at this point. I no
longer have the capacity to effectively review
and merge PRs for the Windows and X11 backends.

Thanks again for having me.

Signed-off-by: John Nunley <dev@notgull.net>
2025-08-04 02:50:06 +09:00
DorotaC
d7fdfb1bca winit-core: add Ime::DeleteSurroundingText API
This completes the basic API required for e.g. Wayland.
2025-08-02 19:17:27 +09:00
Kirill Chibisov
120f21a010 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2025-07-27 22:13:06 +09:00
Robert Wallis
5904529ba1 macOS: fix runtime crash on macos26 "type code 'q', but found 'Q'"
Fixes #4299.
2025-07-20 17:04:04 +09:00
DorotaC
e7a6034b55 winit-core: add surrounding_text for IME
Allow communicating surrounding text to IME to better handle user input
and account for content around for preedit.
2025-07-13 14:57:10 +09:00
DorotaC
eb66c25980 chore: fix typos
Latest typos found more issues.
2025-07-03 08:30:22 +09:00
DorotaC
0ccb8a9f87 example: fix toggling IME 2025-06-30 11:51:46 +09:00
DorotaC
abed32eb80 winit-core/window: wrap ImeCapabilities in struct
To prevent user from using `::all()` and thus writing not forward
compatible code wrap the bitflags struct and provide simpler interface
to it.
2025-06-29 13:53:47 +09:00
DorotaC
08907148ec winit-core/window: add Window::request_ime_update
Allow updating IME state atomically to make it easier for platforms
where it's atomic by its nature, like Wayland. The old API is marked
as deprecated and is routed to the new atomic API.

Co-authored-by: dcz <gilapfco.dcz@porcupinefactory.org>
2025-06-28 13:14:20 +09:00
Kirill Chibisov
fa0795a50c examples/application: fix running on wasm
Was using `time` unconditionally.
2025-06-24 19:15:46 +09:00
Enn3Developer
fe1eab07ae wayland: add xdg_toplevel_icon_v1 support
Closes: #3859.
2025-06-22 19:40:05 +09:00
Kirill Chibisov
552c7a6252 winit: silence wasm on nightly
The lint is needed for stable, but is no longer present on nightly, so
silence it for the time being.
2025-06-22 19:29:27 +09:00
Jeremy Soller
50c0180af3 winit-orbital: update to new path format 2025-06-17 14:46:09 +09:00
Mads Marquart
4f33643509 chore: make git checkout build on Windows with symlinks disabled 2025-06-10 07:37:36 +09:00
Kirill Chibisov
0b21c55b72 winit-core/as_any: fix Box<AsAny> casting
The casting was doing an incorrect check on the `ref` instead of
actually trying to downcast a ref as `cast_ref` does. So use `cast_ref`
to check whether we can safely `cast` to owned type.
2025-06-08 22:11:02 +09:00
Mads Marquart
e1bccb68d8 chore: use a shared version number for all winit-* crates 2025-06-08 09:22:58 +09:00
Mads Marquart
e540062ac0 iOS: Avoid RefCell and static mut (#4255)
* iOS: Refactor queued_gpu_redraws out from AppStateImpl

To allow AppStateImpl to be Copy, and to move redraws into the window in
the future.

* iOS AppState: Avoid RefCell and static mut

Instead, prefer Cell and Copy types, as those will never have crashes
on re-entrancy / if forgetting to make a state transition.
2025-06-07 23:16:41 +02:00
Martin Fischer
f1e0f6c646 icon: add PartialEq and Hash for RgbaIcon 2025-06-08 01:18:55 +09:00
Mads Marquart
3218316420 Document platform-specific modules 2025-06-07 22:43:38 +09:00
Mads Marquart
2900ecab93 winit-core/keyboard: use keyboard_types
Closes #2394.
2025-06-07 16:47:47 +09:00
Kirill Chibisov
3a84da6951 Move winit itself to crate
That way we use top-level Cargo.toml only for the workspace management
purposes.
2025-06-07 13:07:12 +09:00
Mads Marquart
478427b0bd Remove the need for cfg_aliases in winit-core (#4271) 2025-06-06 13:24:01 +02:00
Mads Marquart
b0f26c79ff Fix CI (#4270)
* Fix typos
* Remove OsError in winit-web
2025-06-05 13:10:30 +02:00
Kirill Chibisov
969237f422 Bump rustix to 1.0.0 2025-05-27 16:31:11 +09:00
Mads Marquart
e542a78deb Move Web backend to winit-web 2025-05-26 14:56:00 +09:00
Kirill Chibisov
2d4b9938f0 ci/deny: add rustix
Will take a while to move to 1.0 for everyone.
2025-05-26 14:56:00 +09:00
Mads Marquart
8ad016362a chore: move event loop recreation check into backends themselves 2025-05-26 13:48:52 +09:00
Mads Marquart
5f2c7350e9 Move AppKit (macOS) backend to winit-appkit (#4248) 2025-05-25 17:37:40 +02:00
Mads Marquart
256bbe949e Move X11 backend to winit-x11 (#4253) 2025-05-25 17:24:00 +02:00
Mads Marquart
1126e9ea2f Move Wayland backend to winit-wayland (#4252) 2025-05-25 16:48:07 +02:00
Mads Marquart
927af44aa4 Move UIKit backend to winit-uikit 2025-05-25 23:19:30 +09:00
Mads Marquart
0adc0898f0 Move shared code to a new crate winit-common 2025-05-25 20:41:28 +09:00
Mads Marquart
3b986f5583 Move Windows backend to winit-win32 2025-05-25 12:13:25 +09:00
Mads Marquart
b1f8d778a1 Move Android backend to winit-android (#4250) 2025-05-24 13:29:53 +02:00
Mads Marquart
04482d5a2e fix: Allow unknown bit-depth on macOS (#4190)
It is unclear what values CGDisplayModeCopyPixelEncoding is allowed to
return, so let's make sure to handle unknown cases.
2025-05-23 16:07:09 +02:00
Mads Marquart
3e50911adb macOS: Remove panic wrapper (#4147)
This is unnecessary nowadays, unwinding in CF observer callbacks is safe
(and is safe in Rust after the introduction of `extern "C-unwind"`).

Panicking elsewhere (such as in NSNotificationCenter callbacks or
delegate methods) _may_ still lead to an abort, if AppKit tries to catch
it with libc++, since Rust panics are not compatible with those.
That's "just" a quality-of-implementation detail of current Rust though,
not an inherent limitation, and should really be solved in rustc.
2025-05-23 15:53:12 +02:00
Evgeny
f51a470872 doc: add info on sticky vs toggle modifier behavior (#4251) 2025-05-22 21:57:10 +02:00
Mads Marquart
47b938dbe7 Split Orbital backend out into winit-orbital (#4243) 2025-05-21 13:12:55 +02:00
Kirill Chibisov
e2b883d215 Bump version on master
This commit does not represent a release and only synchronizes CHANGELOG
from the latest release.
2025-05-21 18:04:10 +09:00
Evgeny
38fd3c6a99 winit-core/keyboard: clarify modifier docs
Emphasize the difference between logical and physical state, reference sticky mods.
2025-05-21 17:40:28 +09:00
Mads Marquart
5190472bee chore: use workspace dependencies everywhere
To reduce the amount of duplication in 'Cargo.toml's when we split into
different crates.
2025-05-21 15:45:12 +09:00
Mads Marquart
eab03dca80 Move EventLoopExt* to winit-core (#4228)
* Move EventLoopExtPumpEvents and PumpStatus to winit-core
* Move EventLoopExtRunOnDemand to winit-core
2025-05-20 16:56:53 +02:00
Kirill Chibisov
59e3dda89f wayland: fix pump events's loop drop deadlock 2025-05-17 13:23:01 +09:00
Mads Marquart
c846f67bcb chore: import from winit-core instead of the top-level crate
Reproduce with:
find ./src/platform_impl -type f -exec sed -i '' 's/crate::/winit_core::/g' {} \;
find ./src/platform_impl -type f -exec sed -i '' 's/winit_core::platform/crate::platform/g' {} \;
find ./src/platform_impl -type f -exec sed -i '' 's/winit_core::dpi::/dpi::/g' {} \;
cargo +nightly fmt
2025-05-17 11:26:09 +09:00
Mads Marquart
03c01e038b chose: appease clippy 2025-05-17 10:56:51 +09:00
Varphone Wong
ed4ebd4242 windows: Fix crash in for Windows versions < 17763
In Windows versions < 17763, `GetProcAddress("#132")` from `uxtheme.dll`
also returns a non-null pointer. However, the function does not match
the expected `extern "system" fn() -> bool` prototype, which causes a
crash when it is called.

This fix ensures compatibility by adding proper checks to prevent such
crashes on older Windows versions.
2025-05-14 21:31:48 +09:00
Kirill Chibisov
b5921d89f2 winit-core: add top-level doc 2025-05-14 21:18:44 +09:00
Kirill Chibisov
b5a6a4e616 ci: test winit-core 2025-05-14 21:18:44 +09:00
Kirill Chibisov
9598eb371c winit-core: fix tests 2025-05-14 21:18:44 +09:00
Kirill Chibisov
634b9baea2 winit-core: drop all cfg except web 2025-05-14 21:18:44 +09:00
Kirill Chibisov
cf5e422dc8 winit-core: drop broken docs
Generally, winit-core doesn't know about underlying platforms, though,
some general information which will true for any implementation was
left in place.
2025-05-14 21:18:44 +09:00
Kirill Chibisov
276597e009 winit-core: cleanup event loop docs 2025-05-14 21:18:44 +09:00
Kirill Chibisov
c0b737de4a winit-core: move application 2025-05-14 21:18:44 +09:00
Kirill Chibisov
79fa4061cb winit-core: move event 2025-05-14 21:18:44 +09:00
Kirill Chibisov
056421546a winit-core: move ActiveEventLoop 2025-05-14 21:18:44 +09:00
Kirill Chibisov
b4c5b76155 winit-core: move window
Create `WindowAttributes` for respective platform specific window
attributes in `winit` due to move of `WindowAttributes`.
2025-05-14 21:18:44 +09:00
Kirill Chibisov
c8b9a86885 winit-core: partially split event_loop 2025-05-14 21:18:44 +09:00
Kirill Chibisov
fe2df61884 winit-core: move error 2025-05-14 21:18:44 +09:00
Kirill Chibisov
446482367b winit-core: move cursor 2025-05-14 21:18:44 +09:00
Kirill Chibisov
cbb29ab526 winit-core: move icon 2025-05-14 21:18:44 +09:00
Kirill Chibisov
a491c2abed winit-core: move keyboard 2025-05-14 21:18:44 +09:00
Kirill Chibisov
3142355417 winit-core: move monitor handle 2025-05-14 21:18:44 +09:00
Kirill Chibisov
3493a20173 winit-core: new crate + split out as_any 2025-05-14 21:18:44 +09:00
Kirill Chibisov
bf0bde8067 ci/deny: allow scripts in zerocopy 2025-05-14 21:18:44 +09:00
Kirill Chibisov
519947463f Bump MSRV to 1.80 2025-05-05 21:55:12 +09:00
Kirill Chibisov
8c36ed4900 x11: drop dead code
Fixes #4214.
2025-05-04 00:03:31 +09:00
Bruce Mitchener
7b2c9d42b4 Fix typos from updated typos tool (#4213) 2025-05-03 13:38:15 +02:00
275 changed files with 6809 additions and 6945 deletions

31
.github/CODEOWNERS vendored
View File

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

View File

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

View File

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

View File

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

View File

@@ -13,6 +13,13 @@ To save your time it's wise to check already opened [pull requests][prs] and
accepted, however larger new API proposals should go into the issue first. When
in doubt contact us on [Matrix][matrix] or via opening an issue.
### Windows
To run the examples on Windows, you must have symlinks enabled, see
[this][git-windows-symlinks].
[git-windows-symlinks]: https://gitforwindows.org/symbolic-links.html
### Submitting your work and handling review
All patches have to be sent on Github as [pull requests][prs]. To simplify your

View File

@@ -1,403 +1,104 @@
[package]
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
categories = ["gui"]
description = "Cross-platform window creation library."
documentation = "https://docs.rs/winit"
edition.workspace = true
include = [
"/build.rs",
"/docs",
"/examples",
"/FEATURES.md",
"/LICENSE",
"/src",
"!/src/platform_impl/web/script",
"/src/platform_impl/web/script/**/*.min.js",
"/tests",
]
keywords = ["windowing"]
license.workspace = true
name = "winit"
readme = "README.md"
repository.workspace = true
rust-version.workspace = true
version = "0.30.10"
[package.metadata.docs.rs]
features = [
"serde",
"mint",
# Enabled to get docs to compile
"android-native-activity",
]
# These are all tested in CI
rustdoc-args = ["--cfg", "docsrs"]
targets = [
# Windows
"i686-pc-windows-msvc",
"x86_64-pc-windows-msvc",
# macOS
"aarch64-apple-darwin",
"x86_64-apple-darwin",
# Unix (X11 & Wayland)
"i686-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
# iOS
"aarch64-apple-ios",
# Android
"aarch64-linux-android",
# Web
"wasm32-unknown-unknown",
]
# Features are documented in either `lib.rs` or under `winit::platform`.
[features]
android-game-activity = ["android-activity/game-activity"]
android-native-activity = ["android-activity/native-activity"]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
mint = ["dpi/mint"]
serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde", "dpi/serde", "bitflags/serde"]
wayland = [
"wayland-client",
"wayland-backend",
"wayland-protocols",
"wayland-protocols-plasma",
"sctk",
"ahash",
"memmap2",
]
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"]
wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"]
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
wayland-dlopen = ["wayland-backend/dlopen"]
x11 = ["x11-dl", "bytemuck", "percent-encoding", "xkbcommon-dl/x11", "x11rb"]
[build-dependencies]
cfg_aliases = "0.2.1"
[dependencies]
bitflags = "2"
cursor-icon = "1.1.0"
dpi = { version = "0.1.2", path = "dpi" }
rwh_06 = { package = "raw-window-handle", version = "0.6", features = ["std"] }
serde = { workspace = true, optional = true }
smol_str = "0.3"
tracing = { version = "0.1.40", default-features = false }
[dev-dependencies]
image = { version = "0.25.0", default-features = false, features = ["png"] }
tracing = { version = "0.1.40", default-features = false, features = ["log"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
softbuffer = { version = "0.4.6", default-features = false, features = [
"x11",
"x11-dlopen",
"wayland",
"wayland-dlopen",
] }
# Android
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0"
ndk = { version = "0.9.0", features = ["rwh_06"], default-features = false }
# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
block2 = "0.6.1"
dispatch2 = { version = "0.3.0", default-features = false, features = ["std", "objc2"] }
objc2 = "0.6.1"
# AppKit
[target.'cfg(target_os = "macos")'.dependencies]
objc2-app-kit = { version = "0.3.1", default-features = false, 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",
"NSToolbar",
"NSView",
"NSWindow",
"NSWindowScripting",
"NSWindowTabGroup",
] }
objc2-core-foundation = { version = "0.3.1", default-features = false, features = [
"std",
"block2",
"CFBase",
"CFCGTypes",
"CFData",
"CFRunLoop",
"CFString",
"CFUUID",
] }
objc2-core-graphics = { version = "0.3.1", default-features = false, features = [
"std",
"libc",
"CGDirectDisplay",
"CGDisplayConfiguration",
"CGDisplayFade",
"CGError",
"CGRemoteOperation",
"CGWindowLevel",
] }
objc2-core-video = { version = "0.3.1", default-features = false, features = [
"std",
"objc2-core-graphics",
"CVBase",
"CVReturn",
"CVDisplayLink",
] }
objc2-foundation = { version = "0.3.1", default-features = false, features = [
"std",
"block2",
"objc2-core-foundation",
"NSArray",
"NSAttributedString",
"NSData",
"NSDictionary",
"NSDistributedNotificationCenter",
"NSEnumerator",
"NSGeometry",
"NSKeyValueObserving",
"NSNotification",
"NSObjCRuntime",
"NSOperation",
"NSPathUtilities",
"NSProcessInfo",
"NSRunLoop",
"NSString",
"NSThread",
"NSValue",
] }
# UIKit
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2-core-foundation = { version = "0.3.1", default-features = false, features = [
"std",
"CFCGTypes",
"CFBase",
"CFRunLoop",
"CFString",
] }
objc2-foundation = { version = "0.3.1", default-features = false, features = [
"std",
"block2",
"objc2-core-foundation",
"NSArray",
"NSEnumerator",
"NSGeometry",
"NSObjCRuntime",
"NSOperation",
"NSString",
"NSThread",
"NSSet",
] }
objc2-ui-kit = { version = "0.3.1", default-features = false, features = [
"std",
"objc2-core-foundation",
"UIApplication",
"UIDevice",
"UIEvent",
"UIGeometry",
"UIGestureRecognizer",
"UITextInput",
"UITextInputTraits",
"UIOrientation",
"UIPanGestureRecognizer",
"UIPinchGestureRecognizer",
"UIResponder",
"UIRotationGestureRecognizer",
"UIScreen",
"UIScreenMode",
"UITapGestureRecognizer",
"UITouch",
"UITraitCollection",
"UIView",
"UIViewController",
"UIWindow",
] }
# Windows
[target.'cfg(target_os = "windows")'.dependencies]
unicode-segmentation = "1.7.1"
windows-sys = { version = "0.59.0", features = [
"Win32_Devices_HumanInterfaceDevice",
"Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_Media",
"Win32_System_Com_StructuredStorage",
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Ole",
"Win32_Security",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_System_WindowsProgramming",
"Win32_UI_Accessibility",
"Win32_UI_Controls",
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Input_Pointer",
"Win32_UI_Input_Touch",
"Win32_UI_Shell",
"Win32_UI_TextServices",
"Win32_UI_WindowsAndMessaging",
] }
# Linux
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
ahash = { version = "0.8.7", features = ["no-rng"], optional = true }
bytemuck = { version = "1.13.1", default-features = false, optional = true }
calloop = "0.13.0"
libc = "0.2.64"
memmap2 = { version = "0.9.0", optional = true }
percent-encoding = { version = "2.0", optional = true }
rustix = { version = "0.38.4", default-features = false, features = [
"std",
"system",
"thread",
"process",
] }
sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [
"calloop",
], optional = true }
sctk-adwaita = { version = "0.10.1", default-features = false, optional = true }
wayland-backend = { version = "0.3.10", default-features = false, features = [
"client_system",
], optional = true }
wayland-client = { version = "0.31.10", optional = true }
wayland-protocols = { version = "0.32.8", features = ["staging"], optional = true }
wayland-protocols-plasma = { version = "0.3.8", features = ["client"], optional = true }
x11-dl = { version = "2.19.1", optional = true }
x11rb = { version = "0.13.0", default-features = false, features = [
"allow-unsafe-code",
"cursor",
"dl-libxcb",
"randr",
"resource_manager",
"sync",
"xinput",
"xkb",
], optional = true }
xkbcommon-dl = "0.4.2"
# Orbital
[target.'cfg(target_os = "redox")'.dependencies]
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.5.7"
# Web
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3.70"
pin-project = "1"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
web-time = "1"
web_sys = { package = "web-sys", version = "0.3.70", features = [
"AbortController",
"AbortSignal",
"Blob",
"BlobPropertyBag",
"console",
"CssStyleDeclaration",
"Document",
"DomException",
"DomRect",
"DomRectReadOnly",
"Element",
"Event",
"EventTarget",
"FocusEvent",
"HtmlCanvasElement",
"HtmlElement",
"HtmlHtmlElement",
"HtmlImageElement",
"ImageBitmap",
"ImageBitmapOptions",
"ImageBitmapRenderingContext",
"ImageData",
"IntersectionObserver",
"IntersectionObserverEntry",
"KeyboardEvent",
"MediaQueryList",
"MessageChannel",
"MessagePort",
"Navigator",
"Node",
"OrientationLockType",
"OrientationType",
"PageTransitionEvent",
"Permissions",
"PermissionState",
"PermissionStatus",
"PointerEvent",
"PremultiplyAlpha",
"ResizeObserver",
"ResizeObserverBoxOptions",
"ResizeObserverEntry",
"ResizeObserverOptions",
"ResizeObserverSize",
"Screen",
"ScreenOrientation",
"Url",
"VisibilityState",
"WheelEvent",
"Window",
"Worker",
] }
[target.'cfg(all(target_family = "wasm", target_feature = "atomics"))'.dependencies]
atomic-waker = "1"
concurrent-queue = { version = "2", default-features = false }
[target.'cfg(target_family = "wasm")'.dev-dependencies]
console_error_panic_hook = "0.1"
tracing-web = "0.1"
wasm-bindgen-test = "0.3"
[[example]]
doc-scrape-examples = true
name = "window"
[[example]]
name = "child_window"
[workspace]
members = ["dpi"]
default-members = ["winit"]
members = ["dpi", "winit*"]
resolver = "2"
[workspace.package]
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/rust-windowing/winit"
rust-version = "1.73"
rust-version = "1.80"
version = "0.30.12"
[workspace.dependencies]
# Workspace dependencies.
# `winit` has no version here to allow using it in dev deps for docs.
winit = { path = "winit" }
winit-android = { version = "0.30.12", path = "winit-android" }
winit-appkit = { version = "0.30.12", path = "winit-appkit" }
winit-common = { version = "0.30.12", path = "winit-common" }
winit-core = { version = "0.30.12", path = "winit-core" }
winit-orbital = { version = "0.30.12", path = "winit-orbital" }
winit-uikit = { version = "0.30.12", path = "winit-uikit" }
winit-wayland = { version = "0.30.12", path = "winit-wayland", default-features = false }
winit-web = { version = "0.30.12", path = "winit-web" }
winit-win32 = { version = "0.30.12", path = "winit-win32" }
winit-x11 = { version = "0.30.12", path = "winit-x11" }
# Core dependencies.
bitflags = "2"
cfg_aliases = "0.2.1"
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"] }
smol_str = "0.3"
tracing = { version = "0.1.40", default-features = false }
# Dev dependencies.
image = { version = "0.25.0", default-features = false }
softbuffer = { version = "0.4.6", 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.1", default-features = false }
objc2-core-foundation = { version = "0.3.1", default-features = false }
objc2-core-graphics = { version = "0.3.1", default-features = false }
objc2-core-video = { version = "0.3.1", default-features = false }
objc2-foundation = { version = "0.3.1", default-features = false }
objc2-ui-kit = { version = "0.3.1", default-features = false }
# Windows dependencies.
unicode-segmentation = "1.7.1"
windows-sys = "0.59.0"
# Linux dependencies.
ahash = { version = "0.8.7", features = ["no-rng"] }
bytemuck = { version = "1.13.1", default-features = false }
calloop = "0.13.0"
libc = "0.2.64"
memmap2 = "0.9.0"
percent-encoding = "2.0"
rustix = { version = "1.0.7", default-features = false }
sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = [
"calloop",
] }
sctk-adwaita = { version = "0.10.1", default-features = false }
wayland-backend = { version = "0.3.10", default-features = false, features = ["client_system"] }
wayland-client = "0.31.10"
wayland-protocols = { version = "0.32.8", features = ["staging"] }
wayland-protocols-plasma = { version = "0.3.8", features = ["client"] }
x11-dl = "2.19.1"
x11rb = { version = "0.13.0", default-features = false }
xkbcommon-dl = "0.4.2"
# Orbital dependencies.
orbclient = { version = "0.3.47", default-features = false }
redox_syscall = "0.5.7"
# Web dependencies.
atomic-waker = "1"
concurrent-queue = { version = "2", default-features = false }
console_error_panic_hook = "0.1"
js-sys = "0.3.70"
pin-project = "1"
tracing-web = "0.1"
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = "0.4.43"
wasm-bindgen-test = "0.3"
web-time = "1"
web_sys = { package = "web-sys", version = "0.3.70" }

View File

@@ -8,7 +8,7 @@
```toml
[dependencies]
winit = "0.30.10"
winit = "0.30.12"
```
## [Documentation](https://docs.rs/winit)
@@ -33,13 +33,13 @@ Winit is designed to be a low-level brick in a hierarchy of libraries. Consequen
show something on the window you need to use the platform-specific getters provided by winit, or
another library.
## CONTRIBUING
## CONTRIBUTING
For contributing guidelines see [CONTRIBUTING.md](./CONTRIBUTING.md).
## MSRV Policy
This crate's Minimum Supported Rust Version (MSRV) is **1.73**. Changes to
This crate's Minimum Supported Rust Version (MSRV) is **1.80**. Changes to
the MSRV will be accompanied by a minor version bump.
As a **tentative** policy, the upper bound of the MSRV is given by the following

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -7,38 +7,44 @@ use std::fmt::Debug;
use std::num::NonZeroU32;
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::Arc;
use std::{fmt, mem};
#[cfg(all(not(android_platform), not(web_platform)))]
use std::time::Instant;
use std::{cmp, fmt, mem};
use ::tracing::{error, info};
use cursor_icon::CursorIcon;
use dpi::LogicalPosition;
#[cfg(not(android_platform))]
use rwh_06::{DisplayHandle, HasDisplayHandle};
#[cfg(not(android_platform))]
use softbuffer::{Context, Surface};
#[cfg(all(web_platform, not(android_platform)))]
use web_time::Instant;
use winit::application::ApplicationHandler;
use winit::cursor::{Cursor, CustomCursor, CustomCursorSource};
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
use winit::error::RequestError;
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::icon::RgbaIcon;
use winit::icon::{Icon, RgbaIcon};
use winit::keyboard::{Key, ModifiersState};
use winit::monitor::Fullscreen;
#[cfg(macos_platform)]
use winit::platform::macos::{
ApplicationHandlerExtMacOS, OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS,
};
use winit::platform::macos::{OptionAsAlt, WindowAttributesMacOS, WindowExtMacOS};
#[cfg(any(x11_platform, wayland_platform))]
use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
};
use winit::platform::startup_notify::{self, EventLoopExtStartupNotify, WindowExtStartupNotify};
#[cfg(wayland_platform)]
use winit::platform::wayland::{ActiveEventLoopExtWayland, WindowAttributesWayland};
#[cfg(web_platform)]
use winit::platform::web::{ActiveEventLoopExtWeb, WindowAttributesExtWeb};
use winit::platform::web::{ActiveEventLoopExtWeb, WindowAttributesWeb};
#[cfg(x11_platform)]
use winit::platform::x11::WindowAttributesExtX11;
use winit::platform::x11::{ActiveEventLoopExtX11, WindowAttributesX11};
use winit::window::{
Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Icon, ResizeDirection, Theme, Window,
WindowAttributes, WindowId,
CursorGrabMode, ImeCapabilities, ImeEnableRequest, ImePurpose, ImeRequestData,
ImeSurroundingText, ResizeDirection, Theme, Window, WindowAttributes, WindowId,
};
use winit_core::application::macos::ApplicationHandlerExtMacOS;
use winit_core::window::ImeRequest;
#[path = "util/tracing.rs"]
mod tracing;
@@ -48,6 +54,7 @@ mod fill;
/// The amount of points to around the window for drag resize direction calculations.
const BORDER_SIZE: f64 = 20.;
const IME_CURSOR_SIZE: PhysicalSize<u32> = PhysicalSize::new(20, 20);
fn main() -> Result<(), Box<dyn Error>> {
#[cfg(web_platform)]
@@ -149,43 +156,29 @@ impl Application {
.with_transparent(true)
.with_window_icon(Some(self.icon.clone()));
#[cfg(any(x11_platform, wayland_platform))]
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
info!("Using token {:?} to activate a window", token);
window_attributes = window_attributes.with_activation_token(token);
#[cfg(x11_platform)]
if event_loop.is_x11() {
window_attributes = window_attributes
.with_platform_attributes(Box::new(window_attributes_x11(event_loop)?));
}
#[cfg(x11_platform)]
match std::env::var("X11_VISUAL_ID") {
Ok(visual_id_str) => {
info!("Using X11 visual id {visual_id_str}");
let visual_id = visual_id_str.parse()?;
window_attributes = window_attributes.with_x11_visual(visual_id);
},
Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"),
}
#[cfg(x11_platform)]
match std::env::var("X11_SCREEN_ID") {
Ok(screen_id_str) => {
info!("Placing the window on X11 screen {screen_id_str}");
let screen_id = screen_id_str.parse()?;
window_attributes = window_attributes.with_x11_screen(screen_id);
},
Err(_) => info!(
"Set the X11_SCREEN_ID env variable to place the window on non-default screen"
),
#[cfg(wayland_platform)]
if event_loop.is_wayland() {
window_attributes = window_attributes
.with_platform_attributes(Box::new(window_attributes_wayland(event_loop)));
}
#[cfg(macos_platform)]
if let Some(tab_id) = _tab_id {
window_attributes = window_attributes.with_tabbing_identifier(&tab_id);
let window_attributes_macos =
Box::new(WindowAttributesMacOS::default().with_tabbing_identifier(&tab_id));
window_attributes = window_attributes.with_platform_attributes(window_attributes_macos);
}
#[cfg(web_platform)]
{
window_attributes = window_attributes.with_append(true);
window_attributes =
window_attributes.with_platform_attributes(Box::new(window_attributes_web()));
}
let window = event_loop.create_window(window_attributes)?;
@@ -525,7 +518,32 @@ impl ApplicationHandler for Application {
info!("Preedit: {}, with caret at {:?}", text, caret_pos);
},
Ime::Commit(text) => {
window.text_field_contents.0.push_str(&text);
window.text_field_contents.1 += text.len();
info!("Committed: {}", text);
let request_data = window.get_ime_update();
window.window.request_ime_update(ImeRequest::Update(request_data)).unwrap();
},
Ime::DeleteSurrounding { before_bytes, after_bytes } => {
let (text, cursor) = &window.text_field_contents;
// To anyone copying this, keep in mind that this doesn't take text selection
// into account. The deletion happens *around* the pre-edit,
// and may remove the whole selection or a part of it.
let delete_start = cursor.saturating_sub(before_bytes);
let delete_end = cmp::min(cursor.saturating_add(after_bytes), text.len());
if text.is_char_boundary(delete_start) && text.is_char_boundary(delete_end) {
let new_text = {
let mut t = String::from(&text[..delete_start]);
t.push_str(&text[delete_end..]);
t
};
window.text_field_contents = (new_text, delete_start);
info!("IME deleted bytes: {before_bytes}, {after_bytes}");
} else {
error!("Buggy IME tried to delete with indices not on char boundary.");
}
},
Ime::Disabled => info!("IME disabled for Window={window_id:?}"),
},
@@ -593,7 +611,6 @@ impl ApplicationHandler for Application {
}
}
#[cfg(target_os = "macos")]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
Some(self)
}
@@ -605,7 +622,6 @@ impl Drop for Application {
}
}
#[cfg(target_os = "macos")]
impl ApplicationHandlerExtMacOS for Application {
fn standard_key_binding(
&mut self,
@@ -619,8 +635,10 @@ impl ApplicationHandlerExtMacOS for Application {
/// State of the window.
struct WindowState {
/// IME input.
ime: bool,
ime_enabled: bool,
/// The contents of the emulated text field for IME purposes (not displayed).
/// (text, cursor position in bytes).
text_field_contents: (String, usize),
/// Render surface.
///
/// NOTE: This surface must be dropped before the `Window`.
@@ -634,7 +652,7 @@ struct WindowState {
animated_fill_color: bool,
/// The application start time. Used for color fill animation
#[cfg(not(android_platform))]
start_time: std::time::Instant,
start_time: Instant,
/// Redraw continuously
continuous_redraw: bool,
/// Cursor position over the window.
@@ -676,8 +694,19 @@ impl WindowState {
window.set_cursor(CURSORS[named_idx].into());
// Allow IME out of the box.
let ime = true;
window.set_ime_allowed(ime);
let request_data = ImeRequestData::default()
.with_purpose(ImePurpose::Normal)
.with_cursor_area(LogicalPosition { x: 0, y: 0 }.into(), IME_CURSOR_SIZE.into())
.with_surrounding_text(ImeSurroundingText::new(String::new(), 0, 0).unwrap());
let enable_request = ImeEnableRequest::new(
ImeCapabilities::new().with_purpose().with_cursor_area().with_surrounding_text(),
request_data,
)
.unwrap();
let enable_ime = ImeRequest::Enable(enable_request);
// Initial update
window.request_ime_update(enable_ime).unwrap();
let size = window.surface_size();
let mut state = Self {
@@ -693,8 +722,9 @@ impl WindowState {
animated_fill_color: false,
continuous_redraw: false,
#[cfg(not(android_platform))]
start_time: std::time::Instant::now(),
ime,
start_time: Instant::now(),
ime_enabled: true,
text_field_contents: (String::new(), 0),
cursor_position: Default::default(),
cursor_hidden: Default::default(),
modifiers: Default::default(),
@@ -708,12 +738,48 @@ impl WindowState {
Ok(state)
}
pub fn get_ime_update(&self) -> ImeRequestData {
let (text, cursor) = &self.text_field_contents;
// A rudimentary text field emulation: the caret moves right by a constant amount for each
// code point.
let text_before_caret = if text.is_char_boundary(*cursor) { &text[..*cursor] } else { "" };
let chars_before_caret = text_before_caret.chars().count();
let cursor_pos = LogicalPosition { x: 10 * chars_before_caret as u32, y: 0 }.into();
// Limit text field size
const MAX_BYTES: usize = ImeSurroundingText::MAX_TEXT_BYTES;
let minimal_offset = cursor / MAX_BYTES * MAX_BYTES;
let first_char_boundary =
(minimal_offset..*cursor).find(|off| text.is_char_boundary(*off)).unwrap_or(*cursor);
let last_char_boundary = (*cursor..(first_char_boundary + MAX_BYTES))
.rev()
.find(|off| text.is_char_boundary(*off))
.unwrap_or(*cursor);
let surrounding_text = &text[first_char_boundary..last_char_boundary];
let relative_cursor = cursor - first_char_boundary;
let surrounding_text =
ImeSurroundingText::new(surrounding_text.into(), relative_cursor, relative_cursor)
.expect("Bug in example: bad byte calculations");
ImeRequestData::default()
.with_purpose(ImePurpose::Normal)
.with_cursor_area(cursor_pos, IME_CURSOR_SIZE.into())
.with_surrounding_text(surrounding_text)
}
pub fn toggle_ime(&mut self) {
self.ime = !self.ime;
self.window.set_ime_allowed(self.ime);
if let Some(position) = self.ime.then_some(self.cursor_position).flatten() {
self.window.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into());
}
if self.ime_enabled {
self.window.request_ime_update(ImeRequest::Disable).expect("disable can not fail");
} else {
let enable_request = ImeEnableRequest::new(
ImeCapabilities::new().with_purpose().with_cursor_area().with_surrounding_text(),
self.get_ime_update(),
)
.unwrap();
self.window.request_ime_update(ImeRequest::Enable(enable_request)).unwrap();
};
self.ime_enabled = !self.ime_enabled;
}
pub fn minimize(&mut self) {
@@ -721,9 +787,15 @@ impl WindowState {
}
pub fn cursor_moved(&mut self, position: PhysicalPosition<f64>) {
// the IME really cares about the caret,
// but there's nothing else to demonstrate a position
self.cursor_position = Some(position);
if self.ime {
self.window.set_ime_cursor_area(position.into(), PhysicalSize::new(20, 20).into());
if self.ime_enabled {
let request_data =
ImeRequestData::default().with_cursor_area(position.into(), IME_CURSOR_SIZE.into());
self.window
.request_ime_update(ImeRequest::Update(request_data))
.expect("A capability was not initially declared");
}
}
@@ -1198,6 +1270,60 @@ fn mouse_button_to_string(button: MouseButton) -> &'static str {
}
}
#[cfg(web_platform)]
fn window_attributes_web() -> WindowAttributesWeb {
WindowAttributesWeb::default().with_append(true)
}
#[cfg(wayland_platform)]
fn window_attributes_wayland(event_loop: &dyn ActiveEventLoop) -> WindowAttributesWayland {
let mut window_attributes = WindowAttributesWayland::default();
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
info!("Using token {:?} to activate a window", token);
window_attributes = window_attributes.with_activation_token(token);
}
window_attributes
}
#[cfg(x11_platform)]
fn window_attributes_x11(
event_loop: &dyn ActiveEventLoop,
) -> Result<WindowAttributesX11, Box<dyn Error>> {
let mut window_attributes = WindowAttributesX11::default();
#[cfg(any(x11_platform, wayland_platform))]
if let Some(token) = event_loop.read_token_from_env() {
startup_notify::reset_activation_token_env();
info!("Using token {:?} to activate a window", token);
window_attributes = window_attributes.with_activation_token(token);
}
match std::env::var("X11_VISUAL_ID") {
Ok(visual_id_str) => {
info!("Using X11 visual id {visual_id_str}");
let visual_id = visual_id_str.parse()?;
window_attributes = window_attributes.with_x11_visual(visual_id);
},
Err(_) => info!("Set the X11_VISUAL_ID env variable to request specific X11 visual"),
}
match std::env::var("X11_SCREEN_ID") {
Ok(screen_id_str) => {
info!("Placing the window on X11 screen {screen_id_str}");
let screen_id = screen_id_str.parse()?;
window_attributes = window_attributes.with_x11_screen(screen_id);
},
Err(_) => {
info!("Set the X11_SCREEN_ID env variable to place the window on non-default screen")
},
}
Ok(window_attributes)
}
/// Cursor list to cycle through.
const CURSORS: &[CursorIcon] = &[
CursorIcon::Default,

View File

@@ -49,7 +49,7 @@ impl ApplicationHandler for Application {
| WindowEvent::DragEntered { .. }
| WindowEvent::DragMoved { .. }
| WindowEvent::DragDropped { .. } => {
println!("{:?}", event);
println!("{event:?}");
},
WindowEvent::RedrawRequested => {
let window = self.window.as_ref().unwrap();

View File

@@ -9,8 +9,8 @@ fn main() -> std::process::ExitCode {
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::pump_events::{EventLoopExtPumpEvents, PumpStatus};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]

View File

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

View File

@@ -23,8 +23,12 @@ mod platform {
use std::mem;
use std::mem::ManuallyDrop;
use std::num::NonZeroU32;
#[cfg(all(not(android_platform), not(web_platform)))]
use std::time::Instant;
use softbuffer::{Context, Surface};
#[cfg(all(web_platform, not(android_platform)))]
use web_time::Instant;
use winit::window::{Window, WindowId};
thread_local! {
@@ -105,7 +109,7 @@ mod platform {
}
#[allow(dead_code)]
pub fn fill_window_with_animated_color(window: &dyn Window, start: std::time::Instant) {
pub fn fill_window_with_animated_color(window: &dyn Window, start: Instant) {
let time = start.elapsed().as_secs_f32() * 1.5;
let blue = (time.sin() * 255.0) as u32;
let green = ((time.cos() * 255.0) as u32) << 8;

View File

@@ -6,7 +6,7 @@ use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
#[cfg(web_platform)]
use winit::platform::web::WindowAttributesExtWeb;
use winit::platform::web::WindowAttributesWeb;
use winit::window::{Window, WindowAttributes, WindowId};
#[path = "util/fill.rs"]
@@ -24,7 +24,8 @@ impl ApplicationHandler for App {
#[cfg(not(web_platform))]
let window_attributes = WindowAttributes::default();
#[cfg(web_platform)]
let window_attributes = WindowAttributes::default().with_append(true);
let window_attributes = WindowAttributes::default()
.with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true)));
self.window = match event_loop.create_window(window_attributes) {
Ok(window) => Some(window),
Err(err) => {

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -1,23 +0,0 @@
#[macro_use]
mod util;
mod app;
mod app_state;
mod cursor;
mod event;
mod event_loop;
mod ffi;
mod menu;
mod monitor;
mod observer;
mod view;
mod window;
mod window_delegate;
pub(crate) use self::event::{physicalkey_to_scancode, scancode_to_physicalkey};
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::window::Window;
pub(crate) use self::window_delegate::PlatformSpecificWindowAttributes;

View File

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

View File

@@ -1,25 +0,0 @@
#![allow(clippy::let_unit_value)]
mod app_state;
mod event_loop;
mod monitor;
mod view;
mod view_controller;
mod window;
use std::fmt;
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window};
#[derive(Debug)]
pub enum OsError {}
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "os error")
}
}

View File

@@ -1 +0,0 @@
pub mod xkb;

View File

@@ -1,30 +0,0 @@
//! Winit's Wayland backend.
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Proxy;
use crate::dpi::{LogicalSize, PhysicalSize};
use crate::window::WindowId;
mod event_loop;
mod output;
mod seat;
mod state;
mod types;
mod window;
pub use event_loop::{ActiveEventLoop, EventLoop};
pub use window::Window;
/// Get the WindowId out of the surface.
#[inline]
fn make_wid(surface: &WlSurface) -> WindowId {
WindowId::from_raw(surface.id().as_ptr() as usize)
}
/// The default routine does floor, but we need round on Wayland.
fn logical_to_physical_rounded(size: LogicalSize<u32>, scale_factor: f64) -> PhysicalSize<u32> {
let width = size.width as f64 * scale_factor;
let height = size.height as f64 * scale_factor;
(width.round(), height.round()).into()
}

View File

@@ -1,200 +0,0 @@
use std::ops::Deref;
use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
ContentHint, ContentPurpose, Event as TextInputEvent, ZwpTextInputV3,
};
use crate::event::{Ime, WindowEvent};
use crate::platform_impl::wayland;
use crate::platform_impl::wayland::state::WinitState;
use crate::window::ImePurpose;
#[derive(Debug)]
pub struct TextInputState {
text_input_manager: ZwpTextInputManagerV3,
}
impl TextInputState {
pub fn new(
globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> {
let text_input_manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
Ok(Self { text_input_manager })
}
}
impl Deref for TextInputState {
type Target = ZwpTextInputManagerV3;
fn deref(&self) -> &Self::Target {
&self.text_input_manager
}
}
impl Dispatch<ZwpTextInputManagerV3, GlobalData, WinitState> for TextInputState {
fn event(
_state: &mut WinitState,
_proxy: &ZwpTextInputManagerV3,
_event: <ZwpTextInputManagerV3 as Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
}
}
impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
fn event(
state: &mut WinitState,
text_input: &ZwpTextInputV3,
event: <ZwpTextInputV3 as Proxy>::Event,
data: &TextInputData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
let windows = state.windows.get_mut();
let mut text_input_data = data.inner.lock().unwrap();
match event {
TextInputEvent::Enter { surface } => {
let window_id = wayland::make_wid(&surface);
text_input_data.surface = Some(surface);
let mut window = match windows.get(&window_id) {
Some(window) => window.lock().unwrap(),
None => return,
};
if window.ime_allowed() {
text_input.enable();
text_input.set_content_type_by_purpose(window.ime_purpose());
text_input.commit();
state.events_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
}
window.text_input_entered(text_input);
},
TextInputEvent::Leave { surface } => {
text_input_data.surface = None;
// Always issue a disable.
text_input.disable();
text_input.commit();
let window_id = wayland::make_wid(&surface);
// XXX this check is essential, because `leave` could have a
// reference to nil surface...
let mut window = match windows.get(&window_id) {
Some(window) => window.lock().unwrap(),
None => return,
};
window.text_input_left(text_input);
state.events_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
},
TextInputEvent::PreeditString { text, cursor_begin, cursor_end } => {
let text = text.unwrap_or_default();
let cursor_begin = usize::try_from(cursor_begin)
.ok()
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
let cursor_end = usize::try_from(cursor_end)
.ok()
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
text_input_data.pending_preedit = Some(Preedit { text, cursor_begin, cursor_end })
},
TextInputEvent::CommitString { text } => {
text_input_data.pending_preedit = None;
text_input_data.pending_commit = text;
},
TextInputEvent::Done { .. } => {
let window_id = match text_input_data.surface.as_ref() {
Some(surface) => wayland::make_wid(surface),
None => return,
};
// Clear preedit, unless all we'll be doing next is sending a new preedit.
if text_input_data.pending_commit.is_some()
|| text_input_data.pending_preedit.is_none()
{
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
window_id,
);
}
// Send `Commit`.
if let Some(text) = text_input_data.pending_commit.take() {
state
.events_sink
.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
}
// Send preedit.
if let Some(preedit) = text_input_data.pending_preedit.take() {
let cursor_range =
preedit.cursor_begin.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
state.events_sink.push_window_event(
WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)),
window_id,
);
}
},
TextInputEvent::DeleteSurroundingText { .. } => {
// Not handled.
},
_ => {},
}
}
}
pub trait ZwpTextInputV3Ext {
fn set_content_type_by_purpose(&self, purpose: ImePurpose);
}
impl ZwpTextInputV3Ext for ZwpTextInputV3 {
fn set_content_type_by_purpose(&self, purpose: ImePurpose) {
let (hint, purpose) = match purpose {
ImePurpose::Normal => (ContentHint::None, ContentPurpose::Normal),
ImePurpose::Password => (ContentHint::SensitiveData, ContentPurpose::Password),
ImePurpose::Terminal => (ContentHint::None, ContentPurpose::Terminal),
};
self.set_content_type(hint, purpose);
}
}
/// The Data associated with the text input.
#[derive(Default)]
pub struct TextInputData {
inner: std::sync::Mutex<TextInputDataInner>,
}
#[derive(Default)]
pub struct TextInputDataInner {
/// The `WlSurface` we're performing input to.
surface: Option<WlSurface>,
/// The commit to submit on `done`.
pending_commit: Option<String>,
/// The preedit to submit on `done`.
pending_preedit: Option<Preedit>,
}
/// The state of the preedit.
struct Preedit {
text: String,
cursor_begin: Option<usize>,
cursor_end: Option<usize>,
}
delegate_dispatch!(WinitState: [ZwpTextInputManagerV3: GlobalData] => TextInputState);
delegate_dispatch!(WinitState: [ZwpTextInputV3: TextInputData] => TextInputState);

View File

@@ -1,68 +0,0 @@
use cursor_icon::CursorIcon;
use sctk::reexports::client::protocol::wl_shm::Format;
use sctk::shm::slot::{Buffer, SlotPool};
use crate::cursor::{CursorImage, CustomCursorProvider};
// Wrap in our own type to not impl trait on global type.
#[derive(Debug)]
pub struct WaylandCustomCursor(pub(crate) CursorImage);
impl CustomCursorProvider for WaylandCustomCursor {
fn is_animated(&self) -> bool {
false
}
}
#[derive(Debug)]
pub enum SelectedCursor {
Named(CursorIcon),
Custom(CustomCursor),
}
impl Default for SelectedCursor {
fn default() -> Self {
Self::Named(Default::default())
}
}
#[derive(Debug)]
pub struct CustomCursor {
pub buffer: Buffer,
pub w: i32,
pub h: i32,
pub hotspot_x: i32,
pub hotspot_y: i32,
}
impl CustomCursor {
pub(crate) fn new(pool: &mut SlotPool, image: &WaylandCustomCursor) -> Self {
let image = &image.0;
let (buffer, canvas) = pool
.create_buffer(
image.width as i32,
image.height as i32,
4 * (image.width as i32),
Format::Argb8888,
)
.unwrap();
for (canvas_chunk, rgba) in canvas.chunks_exact_mut(4).zip(image.rgba.chunks_exact(4)) {
// Alpha in buffer is premultiplied.
let alpha = rgba[3] as f32 / 255.;
let r = (rgba[0] as f32 * alpha) as u32;
let g = (rgba[1] as f32 * alpha) as u32;
let b = (rgba[2] as f32 * alpha) as u32;
let color = ((rgba[3] as u32) << 24) + (r << 16) + (g << 8) + b;
let array: &mut [u8; 4] = canvas_chunk.try_into().unwrap();
*array = color.to_le_bytes();
}
CustomCursor {
buffer,
w: image.width as i32,
h: image.height as i32,
hotspot_x: image.hotspot_x as i32,
hotspot_y: image.hotspot_y as i32,
}
}
}

View File

@@ -1,36 +0,0 @@
#![allow(clippy::assertions_on_constants)]
use super::*;
use crate::icon::{Pixel, RgbaIcon, PIXEL_SIZE};
impl Pixel {
pub fn to_packed_argb(&self) -> Cardinal {
let mut cardinal = 0;
assert!(CARDINAL_SIZE >= PIXEL_SIZE);
let as_bytes = &mut cardinal as *mut _ as *mut u8;
unsafe {
*as_bytes.offset(0) = self.b;
*as_bytes.offset(1) = self.g;
*as_bytes.offset(2) = self.r;
*as_bytes.offset(3) = self.a;
}
cardinal
}
}
impl RgbaIcon {
pub(crate) fn to_cardinals(&self) -> Vec<Cardinal> {
assert_eq!(self.rgba.len() % PIXEL_SIZE, 0);
let pixel_count = self.rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (self.width * self.height) as usize);
let mut data = Vec::with_capacity(pixel_count);
data.push(self.width as Cardinal);
data.push(self.height as Cardinal);
let pixels = self.rgba.as_ptr() as *const Pixel;
for pixel_index in 0..pixel_count {
let pixel = unsafe { &*pixels.add(pixel_index) };
data.push(pixel.to_packed_argb());
}
data
}
}

View File

@@ -1,10 +0,0 @@
use std::fmt;
#[derive(Debug)]
pub struct OsError(pub String);
impl fmt::Display for OsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

View File

@@ -1,525 +0,0 @@
use smol_str::SmolStr;
use crate::keyboard::{Key, KeyCode, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
impl Key {
pub(crate) fn from_key_attribute_value(kav: &str) -> Self {
Key::Named(match kav {
"Unidentified" => return Key::Unidentified(NativeKey::Web(SmolStr::new(kav))),
"Dead" => return Key::Dead(None),
"Alt" => NamedKey::Alt,
"AltGraph" => NamedKey::AltGraph,
"CapsLock" => NamedKey::CapsLock,
"Control" => NamedKey::Control,
"Fn" => NamedKey::Fn,
"FnLock" => NamedKey::FnLock,
"NumLock" => NamedKey::NumLock,
"ScrollLock" => NamedKey::ScrollLock,
"Shift" => NamedKey::Shift,
"Symbol" => NamedKey::Symbol,
"SymbolLock" => NamedKey::SymbolLock,
#[allow(deprecated)]
"Super" => NamedKey::Super,
#[allow(deprecated)]
"Hyper" => NamedKey::Hyper,
"Meta" => NamedKey::Meta,
"Enter" => NamedKey::Enter,
"Tab" => NamedKey::Tab,
"ArrowDown" => NamedKey::ArrowDown,
"ArrowLeft" => NamedKey::ArrowLeft,
"ArrowRight" => NamedKey::ArrowRight,
"ArrowUp" => NamedKey::ArrowUp,
"End" => NamedKey::End,
"Home" => NamedKey::Home,
"PageDown" => NamedKey::PageDown,
"PageUp" => NamedKey::PageUp,
"Backspace" => NamedKey::Backspace,
"Clear" => NamedKey::Clear,
"Copy" => NamedKey::Copy,
"CrSel" => NamedKey::CrSel,
"Cut" => NamedKey::Cut,
"Delete" => NamedKey::Delete,
"EraseEof" => NamedKey::EraseEof,
"ExSel" => NamedKey::ExSel,
"Insert" => NamedKey::Insert,
"Paste" => NamedKey::Paste,
"Redo" => NamedKey::Redo,
"Undo" => NamedKey::Undo,
"Accept" => NamedKey::Accept,
"Again" => NamedKey::Again,
"Attn" => NamedKey::Attn,
"Cancel" => NamedKey::Cancel,
"ContextMenu" => NamedKey::ContextMenu,
"Escape" => NamedKey::Escape,
"Execute" => NamedKey::Execute,
"Find" => NamedKey::Find,
"Help" => NamedKey::Help,
"Pause" => NamedKey::Pause,
"Play" => NamedKey::Play,
"Props" => NamedKey::Props,
"Select" => NamedKey::Select,
"ZoomIn" => NamedKey::ZoomIn,
"ZoomOut" => NamedKey::ZoomOut,
"BrightnessDown" => NamedKey::BrightnessDown,
"BrightnessUp" => NamedKey::BrightnessUp,
"Eject" => NamedKey::Eject,
"LogOff" => NamedKey::LogOff,
"Power" => NamedKey::Power,
"PowerOff" => NamedKey::PowerOff,
"PrintScreen" => NamedKey::PrintScreen,
"Hibernate" => NamedKey::Hibernate,
"Standby" => NamedKey::Standby,
"WakeUp" => NamedKey::WakeUp,
"AllCandidates" => NamedKey::AllCandidates,
"Alphanumeric" => NamedKey::Alphanumeric,
"CodeInput" => NamedKey::CodeInput,
"Compose" => NamedKey::Compose,
"Convert" => NamedKey::Convert,
"FinalMode" => NamedKey::FinalMode,
"GroupFirst" => NamedKey::GroupFirst,
"GroupLast" => NamedKey::GroupLast,
"GroupNext" => NamedKey::GroupNext,
"GroupPrevious" => NamedKey::GroupPrevious,
"ModeChange" => NamedKey::ModeChange,
"NextCandidate" => NamedKey::NextCandidate,
"NonConvert" => NamedKey::NonConvert,
"PreviousCandidate" => NamedKey::PreviousCandidate,
"Process" => NamedKey::Process,
"SingleCandidate" => NamedKey::SingleCandidate,
"HangulMode" => NamedKey::HangulMode,
"HanjaMode" => NamedKey::HanjaMode,
"JunjaMode" => NamedKey::JunjaMode,
"Eisu" => NamedKey::Eisu,
"Hankaku" => NamedKey::Hankaku,
"Hiragana" => NamedKey::Hiragana,
"HiraganaKatakana" => NamedKey::HiraganaKatakana,
"KanaMode" => NamedKey::KanaMode,
"KanjiMode" => NamedKey::KanjiMode,
"Katakana" => NamedKey::Katakana,
"Romaji" => NamedKey::Romaji,
"Zenkaku" => NamedKey::Zenkaku,
"ZenkakuHankaku" => NamedKey::ZenkakuHankaku,
"Soft1" => NamedKey::Soft1,
"Soft2" => NamedKey::Soft2,
"Soft3" => NamedKey::Soft3,
"Soft4" => NamedKey::Soft4,
"ChannelDown" => NamedKey::ChannelDown,
"ChannelUp" => NamedKey::ChannelUp,
"Close" => NamedKey::Close,
"MailForward" => NamedKey::MailForward,
"MailReply" => NamedKey::MailReply,
"MailSend" => NamedKey::MailSend,
"MediaClose" => NamedKey::MediaClose,
"MediaFastForward" => NamedKey::MediaFastForward,
"MediaPause" => NamedKey::MediaPause,
"MediaPlay" => NamedKey::MediaPlay,
"MediaPlayPause" => NamedKey::MediaPlayPause,
"MediaRecord" => NamedKey::MediaRecord,
"MediaRewind" => NamedKey::MediaRewind,
"MediaStop" => NamedKey::MediaStop,
"MediaTrackNext" => NamedKey::MediaTrackNext,
"MediaTrackPrevious" => NamedKey::MediaTrackPrevious,
"New" => NamedKey::New,
"Open" => NamedKey::Open,
"Print" => NamedKey::Print,
"Save" => NamedKey::Save,
"SpellCheck" => NamedKey::SpellCheck,
"Key11" => NamedKey::Key11,
"Key12" => NamedKey::Key12,
"AudioBalanceLeft" => NamedKey::AudioBalanceLeft,
"AudioBalanceRight" => NamedKey::AudioBalanceRight,
"AudioBassBoostDown" => NamedKey::AudioBassBoostDown,
"AudioBassBoostToggle" => NamedKey::AudioBassBoostToggle,
"AudioBassBoostUp" => NamedKey::AudioBassBoostUp,
"AudioFaderFront" => NamedKey::AudioFaderFront,
"AudioFaderRear" => NamedKey::AudioFaderRear,
"AudioSurroundModeNext" => NamedKey::AudioSurroundModeNext,
"AudioTrebleDown" => NamedKey::AudioTrebleDown,
"AudioTrebleUp" => NamedKey::AudioTrebleUp,
"AudioVolumeDown" => NamedKey::AudioVolumeDown,
"AudioVolumeUp" => NamedKey::AudioVolumeUp,
"AudioVolumeMute" => NamedKey::AudioVolumeMute,
"MicrophoneToggle" => NamedKey::MicrophoneToggle,
"MicrophoneVolumeDown" => NamedKey::MicrophoneVolumeDown,
"MicrophoneVolumeUp" => NamedKey::MicrophoneVolumeUp,
"MicrophoneVolumeMute" => NamedKey::MicrophoneVolumeMute,
"SpeechCorrectionList" => NamedKey::SpeechCorrectionList,
"SpeechInputToggle" => NamedKey::SpeechInputToggle,
"LaunchApplication1" => NamedKey::LaunchApplication1,
"LaunchApplication2" => NamedKey::LaunchApplication2,
"LaunchCalendar" => NamedKey::LaunchCalendar,
"LaunchContacts" => NamedKey::LaunchContacts,
"LaunchMail" => NamedKey::LaunchMail,
"LaunchMediaPlayer" => NamedKey::LaunchMediaPlayer,
"LaunchMusicPlayer" => NamedKey::LaunchMusicPlayer,
"LaunchPhone" => NamedKey::LaunchPhone,
"LaunchScreenSaver" => NamedKey::LaunchScreenSaver,
"LaunchSpreadsheet" => NamedKey::LaunchSpreadsheet,
"LaunchWebBrowser" => NamedKey::LaunchWebBrowser,
"LaunchWebCam" => NamedKey::LaunchWebCam,
"LaunchWordProcessor" => NamedKey::LaunchWordProcessor,
"BrowserBack" => NamedKey::BrowserBack,
"BrowserFavorites" => NamedKey::BrowserFavorites,
"BrowserForward" => NamedKey::BrowserForward,
"BrowserHome" => NamedKey::BrowserHome,
"BrowserRefresh" => NamedKey::BrowserRefresh,
"BrowserSearch" => NamedKey::BrowserSearch,
"BrowserStop" => NamedKey::BrowserStop,
"AppSwitch" => NamedKey::AppSwitch,
"Call" => NamedKey::Call,
"Camera" => NamedKey::Camera,
"CameraFocus" => NamedKey::CameraFocus,
"EndCall" => NamedKey::EndCall,
"GoBack" => NamedKey::GoBack,
"GoHome" => NamedKey::GoHome,
"HeadsetHook" => NamedKey::HeadsetHook,
"LastNumberRedial" => NamedKey::LastNumberRedial,
"Notification" => NamedKey::Notification,
"MannerMode" => NamedKey::MannerMode,
"VoiceDial" => NamedKey::VoiceDial,
"TV" => NamedKey::TV,
"TV3DMode" => NamedKey::TV3DMode,
"TVAntennaCable" => NamedKey::TVAntennaCable,
"TVAudioDescription" => NamedKey::TVAudioDescription,
"TVAudioDescriptionMixDown" => NamedKey::TVAudioDescriptionMixDown,
"TVAudioDescriptionMixUp" => NamedKey::TVAudioDescriptionMixUp,
"TVContentsMenu" => NamedKey::TVContentsMenu,
"TVDataService" => NamedKey::TVDataService,
"TVInput" => NamedKey::TVInput,
"TVInputComponent1" => NamedKey::TVInputComponent1,
"TVInputComponent2" => NamedKey::TVInputComponent2,
"TVInputComposite1" => NamedKey::TVInputComposite1,
"TVInputComposite2" => NamedKey::TVInputComposite2,
"TVInputHDMI1" => NamedKey::TVInputHDMI1,
"TVInputHDMI2" => NamedKey::TVInputHDMI2,
"TVInputHDMI3" => NamedKey::TVInputHDMI3,
"TVInputHDMI4" => NamedKey::TVInputHDMI4,
"TVInputVGA1" => NamedKey::TVInputVGA1,
"TVMediaContext" => NamedKey::TVMediaContext,
"TVNetwork" => NamedKey::TVNetwork,
"TVNumberEntry" => NamedKey::TVNumberEntry,
"TVPower" => NamedKey::TVPower,
"TVRadioService" => NamedKey::TVRadioService,
"TVSatellite" => NamedKey::TVSatellite,
"TVSatelliteBS" => NamedKey::TVSatelliteBS,
"TVSatelliteCS" => NamedKey::TVSatelliteCS,
"TVSatelliteToggle" => NamedKey::TVSatelliteToggle,
"TVTerrestrialAnalog" => NamedKey::TVTerrestrialAnalog,
"TVTerrestrialDigital" => NamedKey::TVTerrestrialDigital,
"TVTimer" => NamedKey::TVTimer,
"AVRInput" => NamedKey::AVRInput,
"AVRPower" => NamedKey::AVRPower,
"ColorF0Red" => NamedKey::ColorF0Red,
"ColorF1Green" => NamedKey::ColorF1Green,
"ColorF2Yellow" => NamedKey::ColorF2Yellow,
"ColorF3Blue" => NamedKey::ColorF3Blue,
"ColorF4Grey" => NamedKey::ColorF4Grey,
"ColorF5Brown" => NamedKey::ColorF5Brown,
"ClosedCaptionToggle" => NamedKey::ClosedCaptionToggle,
"Dimmer" => NamedKey::Dimmer,
"DisplaySwap" => NamedKey::DisplaySwap,
"DVR" => NamedKey::DVR,
"Exit" => NamedKey::Exit,
"FavoriteClear0" => NamedKey::FavoriteClear0,
"FavoriteClear1" => NamedKey::FavoriteClear1,
"FavoriteClear2" => NamedKey::FavoriteClear2,
"FavoriteClear3" => NamedKey::FavoriteClear3,
"FavoriteRecall0" => NamedKey::FavoriteRecall0,
"FavoriteRecall1" => NamedKey::FavoriteRecall1,
"FavoriteRecall2" => NamedKey::FavoriteRecall2,
"FavoriteRecall3" => NamedKey::FavoriteRecall3,
"FavoriteStore0" => NamedKey::FavoriteStore0,
"FavoriteStore1" => NamedKey::FavoriteStore1,
"FavoriteStore2" => NamedKey::FavoriteStore2,
"FavoriteStore3" => NamedKey::FavoriteStore3,
"Guide" => NamedKey::Guide,
"GuideNextDay" => NamedKey::GuideNextDay,
"GuidePreviousDay" => NamedKey::GuidePreviousDay,
"Info" => NamedKey::Info,
"InstantReplay" => NamedKey::InstantReplay,
"Link" => NamedKey::Link,
"ListProgram" => NamedKey::ListProgram,
"LiveContent" => NamedKey::LiveContent,
"Lock" => NamedKey::Lock,
"MediaApps" => NamedKey::MediaApps,
"MediaAudioTrack" => NamedKey::MediaAudioTrack,
"MediaLast" => NamedKey::MediaLast,
"MediaSkipBackward" => NamedKey::MediaSkipBackward,
"MediaSkipForward" => NamedKey::MediaSkipForward,
"MediaStepBackward" => NamedKey::MediaStepBackward,
"MediaStepForward" => NamedKey::MediaStepForward,
"MediaTopMenu" => NamedKey::MediaTopMenu,
"NavigateIn" => NamedKey::NavigateIn,
"NavigateNext" => NamedKey::NavigateNext,
"NavigateOut" => NamedKey::NavigateOut,
"NavigatePrevious" => NamedKey::NavigatePrevious,
"NextFavoriteChannel" => NamedKey::NextFavoriteChannel,
"NextUserProfile" => NamedKey::NextUserProfile,
"OnDemand" => NamedKey::OnDemand,
"Pairing" => NamedKey::Pairing,
"PinPDown" => NamedKey::PinPDown,
"PinPMove" => NamedKey::PinPMove,
"PinPToggle" => NamedKey::PinPToggle,
"PinPUp" => NamedKey::PinPUp,
"PlaySpeedDown" => NamedKey::PlaySpeedDown,
"PlaySpeedReset" => NamedKey::PlaySpeedReset,
"PlaySpeedUp" => NamedKey::PlaySpeedUp,
"RandomToggle" => NamedKey::RandomToggle,
"RcLowBattery" => NamedKey::RcLowBattery,
"RecordSpeedNext" => NamedKey::RecordSpeedNext,
"RfBypass" => NamedKey::RfBypass,
"ScanChannelsToggle" => NamedKey::ScanChannelsToggle,
"ScreenModeNext" => NamedKey::ScreenModeNext,
"Settings" => NamedKey::Settings,
"SplitScreenToggle" => NamedKey::SplitScreenToggle,
"STBInput" => NamedKey::STBInput,
"STBPower" => NamedKey::STBPower,
"Subtitle" => NamedKey::Subtitle,
"Teletext" => NamedKey::Teletext,
"VideoModeNext" => NamedKey::VideoModeNext,
"Wink" => NamedKey::Wink,
"ZoomToggle" => NamedKey::ZoomToggle,
"F1" => NamedKey::F1,
"F2" => NamedKey::F2,
"F3" => NamedKey::F3,
"F4" => NamedKey::F4,
"F5" => NamedKey::F5,
"F6" => NamedKey::F6,
"F7" => NamedKey::F7,
"F8" => NamedKey::F8,
"F9" => NamedKey::F9,
"F10" => NamedKey::F10,
"F11" => NamedKey::F11,
"F12" => NamedKey::F12,
"F13" => NamedKey::F13,
"F14" => NamedKey::F14,
"F15" => NamedKey::F15,
"F16" => NamedKey::F16,
"F17" => NamedKey::F17,
"F18" => NamedKey::F18,
"F19" => NamedKey::F19,
"F20" => NamedKey::F20,
"F21" => NamedKey::F21,
"F22" => NamedKey::F22,
"F23" => NamedKey::F23,
"F24" => NamedKey::F24,
"F25" => NamedKey::F25,
"F26" => NamedKey::F26,
"F27" => NamedKey::F27,
"F28" => NamedKey::F28,
"F29" => NamedKey::F29,
"F30" => NamedKey::F30,
"F31" => NamedKey::F31,
"F32" => NamedKey::F32,
"F33" => NamedKey::F33,
"F34" => NamedKey::F34,
"F35" => NamedKey::F35,
string => return Key::Character(SmolStr::new(string)),
})
}
}
impl PhysicalKey {
pub fn from_key_code_attribute_value(kcav: &str) -> Self {
PhysicalKey::Code(match kcav {
"Backquote" => KeyCode::Backquote,
"Backslash" => KeyCode::Backslash,
"BracketLeft" => KeyCode::BracketLeft,
"BracketRight" => KeyCode::BracketRight,
"Comma" => KeyCode::Comma,
"Digit0" => KeyCode::Digit0,
"Digit1" => KeyCode::Digit1,
"Digit2" => KeyCode::Digit2,
"Digit3" => KeyCode::Digit3,
"Digit4" => KeyCode::Digit4,
"Digit5" => KeyCode::Digit5,
"Digit6" => KeyCode::Digit6,
"Digit7" => KeyCode::Digit7,
"Digit8" => KeyCode::Digit8,
"Digit9" => KeyCode::Digit9,
"Equal" => KeyCode::Equal,
"IntlBackslash" => KeyCode::IntlBackslash,
"IntlRo" => KeyCode::IntlRo,
"IntlYen" => KeyCode::IntlYen,
"KeyA" => KeyCode::KeyA,
"KeyB" => KeyCode::KeyB,
"KeyC" => KeyCode::KeyC,
"KeyD" => KeyCode::KeyD,
"KeyE" => KeyCode::KeyE,
"KeyF" => KeyCode::KeyF,
"KeyG" => KeyCode::KeyG,
"KeyH" => KeyCode::KeyH,
"KeyI" => KeyCode::KeyI,
"KeyJ" => KeyCode::KeyJ,
"KeyK" => KeyCode::KeyK,
"KeyL" => KeyCode::KeyL,
"KeyM" => KeyCode::KeyM,
"KeyN" => KeyCode::KeyN,
"KeyO" => KeyCode::KeyO,
"KeyP" => KeyCode::KeyP,
"KeyQ" => KeyCode::KeyQ,
"KeyR" => KeyCode::KeyR,
"KeyS" => KeyCode::KeyS,
"KeyT" => KeyCode::KeyT,
"KeyU" => KeyCode::KeyU,
"KeyV" => KeyCode::KeyV,
"KeyW" => KeyCode::KeyW,
"KeyX" => KeyCode::KeyX,
"KeyY" => KeyCode::KeyY,
"KeyZ" => KeyCode::KeyZ,
"Minus" => KeyCode::Minus,
"Period" => KeyCode::Period,
"Quote" => KeyCode::Quote,
"Semicolon" => KeyCode::Semicolon,
"Slash" => KeyCode::Slash,
"AltLeft" => KeyCode::AltLeft,
"AltRight" => KeyCode::AltRight,
"Backspace" => KeyCode::Backspace,
"CapsLock" => KeyCode::CapsLock,
"ContextMenu" => KeyCode::ContextMenu,
"ControlLeft" => KeyCode::ControlLeft,
"ControlRight" => KeyCode::ControlRight,
"Enter" => KeyCode::Enter,
"MetaLeft" => KeyCode::MetaLeft,
"MetaRight" => KeyCode::MetaRight,
"ShiftLeft" => KeyCode::ShiftLeft,
"ShiftRight" => KeyCode::ShiftRight,
"Space" => KeyCode::Space,
"Tab" => KeyCode::Tab,
"Convert" => KeyCode::Convert,
"KanaMode" => KeyCode::KanaMode,
"Lang1" => KeyCode::Lang1,
"Lang2" => KeyCode::Lang2,
"Lang3" => KeyCode::Lang3,
"Lang4" => KeyCode::Lang4,
"Lang5" => KeyCode::Lang5,
"NonConvert" => KeyCode::NonConvert,
"Delete" => KeyCode::Delete,
"End" => KeyCode::End,
"Help" => KeyCode::Help,
"Home" => KeyCode::Home,
"Insert" => KeyCode::Insert,
"PageDown" => KeyCode::PageDown,
"PageUp" => KeyCode::PageUp,
"ArrowDown" => KeyCode::ArrowDown,
"ArrowLeft" => KeyCode::ArrowLeft,
"ArrowRight" => KeyCode::ArrowRight,
"ArrowUp" => KeyCode::ArrowUp,
"NumLock" => KeyCode::NumLock,
"Numpad0" => KeyCode::Numpad0,
"Numpad1" => KeyCode::Numpad1,
"Numpad2" => KeyCode::Numpad2,
"Numpad3" => KeyCode::Numpad3,
"Numpad4" => KeyCode::Numpad4,
"Numpad5" => KeyCode::Numpad5,
"Numpad6" => KeyCode::Numpad6,
"Numpad7" => KeyCode::Numpad7,
"Numpad8" => KeyCode::Numpad8,
"Numpad9" => KeyCode::Numpad9,
"NumpadAdd" => KeyCode::NumpadAdd,
"NumpadBackspace" => KeyCode::NumpadBackspace,
"NumpadClear" => KeyCode::NumpadClear,
"NumpadClearEntry" => KeyCode::NumpadClearEntry,
"NumpadComma" => KeyCode::NumpadComma,
"NumpadDecimal" => KeyCode::NumpadDecimal,
"NumpadDivide" => KeyCode::NumpadDivide,
"NumpadEnter" => KeyCode::NumpadEnter,
"NumpadEqual" => KeyCode::NumpadEqual,
"NumpadHash" => KeyCode::NumpadHash,
"NumpadMemoryAdd" => KeyCode::NumpadMemoryAdd,
"NumpadMemoryClear" => KeyCode::NumpadMemoryClear,
"NumpadMemoryRecall" => KeyCode::NumpadMemoryRecall,
"NumpadMemoryStore" => KeyCode::NumpadMemoryStore,
"NumpadMemorySubtract" => KeyCode::NumpadMemorySubtract,
"NumpadMultiply" => KeyCode::NumpadMultiply,
"NumpadParenLeft" => KeyCode::NumpadParenLeft,
"NumpadParenRight" => KeyCode::NumpadParenRight,
"NumpadStar" => KeyCode::NumpadStar,
"NumpadSubtract" => KeyCode::NumpadSubtract,
"Escape" => KeyCode::Escape,
"Fn" => KeyCode::Fn,
"FnLock" => KeyCode::FnLock,
"PrintScreen" => KeyCode::PrintScreen,
"ScrollLock" => KeyCode::ScrollLock,
"Pause" => KeyCode::Pause,
"BrowserBack" => KeyCode::BrowserBack,
"BrowserFavorites" => KeyCode::BrowserFavorites,
"BrowserForward" => KeyCode::BrowserForward,
"BrowserHome" => KeyCode::BrowserHome,
"BrowserRefresh" => KeyCode::BrowserRefresh,
"BrowserSearch" => KeyCode::BrowserSearch,
"BrowserStop" => KeyCode::BrowserStop,
"Eject" => KeyCode::Eject,
"LaunchApp1" => KeyCode::LaunchApp1,
"LaunchApp2" => KeyCode::LaunchApp2,
"LaunchMail" => KeyCode::LaunchMail,
"MediaPlayPause" => KeyCode::MediaPlayPause,
"MediaSelect" => KeyCode::MediaSelect,
"MediaStop" => KeyCode::MediaStop,
"MediaTrackNext" => KeyCode::MediaTrackNext,
"MediaTrackPrevious" => KeyCode::MediaTrackPrevious,
"Power" => KeyCode::Power,
"Sleep" => KeyCode::Sleep,
"AudioVolumeDown" => KeyCode::AudioVolumeDown,
"AudioVolumeMute" => KeyCode::AudioVolumeMute,
"AudioVolumeUp" => KeyCode::AudioVolumeUp,
"WakeUp" => KeyCode::WakeUp,
#[allow(deprecated)]
"Super" => KeyCode::Super,
#[allow(deprecated)]
"Hyper" => KeyCode::Hyper,
#[allow(deprecated)]
"Turbo" => KeyCode::Turbo,
"Abort" => KeyCode::Abort,
"Resume" => KeyCode::Resume,
"Suspend" => KeyCode::Suspend,
"Again" => KeyCode::Again,
"Copy" => KeyCode::Copy,
"Cut" => KeyCode::Cut,
"Find" => KeyCode::Find,
"Open" => KeyCode::Open,
"Paste" => KeyCode::Paste,
"Props" => KeyCode::Props,
"Select" => KeyCode::Select,
"Undo" => KeyCode::Undo,
"Hiragana" => KeyCode::Hiragana,
"Katakana" => KeyCode::Katakana,
"F1" => KeyCode::F1,
"F2" => KeyCode::F2,
"F3" => KeyCode::F3,
"F4" => KeyCode::F4,
"F5" => KeyCode::F5,
"F6" => KeyCode::F6,
"F7" => KeyCode::F7,
"F8" => KeyCode::F8,
"F9" => KeyCode::F9,
"F10" => KeyCode::F10,
"F11" => KeyCode::F11,
"F12" => KeyCode::F12,
"F13" => KeyCode::F13,
"F14" => KeyCode::F14,
"F15" => KeyCode::F15,
"F16" => KeyCode::F16,
"F17" => KeyCode::F17,
"F18" => KeyCode::F18,
"F19" => KeyCode::F19,
"F20" => KeyCode::F20,
"F21" => KeyCode::F21,
"F22" => KeyCode::F22,
"F23" => KeyCode::F23,
"F24" => KeyCode::F24,
"F25" => KeyCode::F25,
"F26" => KeyCode::F26,
"F27" => KeyCode::F27,
"F28" => KeyCode::F28,
"F29" => KeyCode::F29,
"F30" => KeyCode::F30,
"F31" => KeyCode::F31,
"F32" => KeyCode::F32,
"F33" => KeyCode::F33,
"F34" => KeyCode::F34,
"F35" => KeyCode::F35,
_ => return PhysicalKey::Unidentified(NativeKeyCode::Unidentified),
})
}
}

View File

@@ -1,44 +0,0 @@
// Brief introduction to the internals of the Web backend:
// The Web backend used to support both wasm-bindgen and stdweb as methods of binding to the
// environment. Because they are both supporting the same underlying APIs, the actual Web bindings
// are cordoned off into backend abstractions, which present the thinnest unifying layer possible.
//
// When adding support for new events or interactions with the browser, first consult trusted
// documentation (such as MDN) to ensure it is well-standardised and supported across many browsers.
// Once you have decided on the relevant Web APIs, add support to both backends.
//
// The backend is used by the rest of the module to implement Winit's business logic, which forms
// the rest of the code. 'device', 'error', 'monitor', and 'window' define Web-specific structures
// for winit's cross-platform structures. They are all relatively simple translations.
//
// The event_loop module handles listening for and processing events. 'Proxy' implements
// EventLoopProxy and 'WindowTarget' implements ActiveEventLoop. WindowTarget also handles
// registering the event handlers. The 'Execution' struct in the 'runner' module handles taking
// incoming events (from the registered handlers) and ensuring they are passed to the user in a
// compliant way.
// TODO: FP, remove when <https://github.com/rust-lang/rust-clippy/issues/12377> is fixed.
#![allow(clippy::empty_docs)]
mod r#async;
mod cursor;
mod error;
mod event;
mod event_loop;
mod keyboard;
mod lock;
mod main_thread;
mod monitor;
mod web_sys;
mod window;
pub(crate) use cursor::CustomCursorFuture;
pub(crate) use self::event_loop::{
ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes,
};
pub(crate) use self::monitor::{
HasMonitorPermissionFuture, MonitorHandle, MonitorPermissionFuture, OrientationLockFuture,
};
use self::web_sys as backend;
pub use self::window::{PlatformSpecificWindowAttributes, Window};

View File

@@ -1,126 +0,0 @@
use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX};
pub(crate) use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
pub(crate) use self::icon::{RaiiIcon, SelectedCursor};
pub(crate) use self::keyboard::{physicalkey_to_scancode, scancode_to_physicalkey};
pub(crate) use self::monitor::MonitorHandle;
pub(crate) use self::window::Window;
use crate::event::DeviceId;
use crate::icon::Icon;
use crate::platform::windows::{BackdropType, Color, CornerPreference};
#[derive(Clone, Debug)]
pub struct PlatformSpecificWindowAttributes {
pub owner: Option<HWND>,
pub menu: Option<HMENU>,
pub taskbar_icon: Option<Icon>,
pub no_redirection_bitmap: bool,
pub drag_and_drop: bool,
pub skip_taskbar: bool,
pub class_name: String,
pub decoration_shadow: bool,
pub backdrop_type: BackdropType,
pub clip_children: bool,
pub border_color: Option<Color>,
pub title_background_color: Option<Color>,
pub title_text_color: Option<Color>,
pub corner_preference: Option<CornerPreference>,
}
impl Default for PlatformSpecificWindowAttributes {
fn default() -> Self {
Self {
owner: None,
menu: None,
taskbar_icon: None,
no_redirection_bitmap: false,
drag_and_drop: true,
skip_taskbar: false,
class_name: "Window Class".to_string(),
decoration_shadow: false,
backdrop_type: BackdropType::default(),
clip_children: true,
border_color: None,
title_background_color: None,
title_text_color: None,
corner_preference: None,
}
}
}
unsafe impl Send for PlatformSpecificWindowAttributes {}
unsafe impl Sync for PlatformSpecificWindowAttributes {}
fn wrap_device_id(id: u32) -> DeviceId {
DeviceId::from_raw(id as i64)
}
#[inline(always)]
const fn get_xbutton_wparam(x: u32) -> u16 {
hiword(x)
}
#[inline(always)]
const fn get_x_lparam(x: u32) -> i16 {
loword(x) as _
}
#[inline(always)]
const fn get_y_lparam(x: u32) -> i16 {
hiword(x) as _
}
#[inline(always)]
pub(crate) const fn primarylangid(lgid: u16) -> u16 {
lgid & 0x3ff
}
#[inline(always)]
pub(crate) const fn loword(x: u32) -> u16 {
(x & 0xffff) as u16
}
#[inline(always)]
const fn hiword(x: u32) -> u16 {
((x >> 16) & 0xffff) as u16
}
#[inline(always)]
unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize {
#[cfg(target_pointer_width = "64")]
return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW(hwnd, nindex) };
#[cfg(target_pointer_width = "32")]
return unsafe {
windows_sys::Win32::UI::WindowsAndMessaging::GetWindowLongW(hwnd, nindex) as isize
};
}
#[inline(always)]
unsafe fn set_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX, dwnewlong: isize) -> isize {
#[cfg(target_pointer_width = "64")]
return unsafe {
windows_sys::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW(hwnd, nindex, dwnewlong)
};
#[cfg(target_pointer_width = "32")]
return unsafe {
windows_sys::Win32::UI::WindowsAndMessaging::SetWindowLongW(hwnd, nindex, dwnewlong as i32)
as isize
};
}
#[macro_use]
mod util;
mod dark_mode;
mod definitions;
mod dpi;
mod drop_handler;
mod event_loop;
mod icon;
mod ime;
mod keyboard;
mod keyboard_layout;
mod monitor;
pub(crate) mod raw_input;
mod window;
mod window_state;

35
winit-android/Cargo.toml Normal file
View File

@@ -0,0 +1,35 @@
[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"]

1
winit-android/LICENSE Symbolic link
View File

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

1
winit-android/README.md Symbolic link
View File

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

View File

@@ -1,4 +1,5 @@
use std::cell::Cell;
use std::fmt;
use std::hash::Hash;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
@@ -8,26 +9,26 @@ use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
use android_activity::{
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
};
use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use tracing::{debug, trace, warn};
use crate::application::ApplicationHandler;
use crate::cursor::Cursor;
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter};
use crate::event_loop::{
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{Cursor, CustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
use winit_core::event::{self, DeviceId, FingerId, Force, StartCause, SurfaceSizeWriter};
use winit_core::event_loop::pump_events::PumpStatus;
use winit_core::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use crate::platform::pump_events::PumpStatus;
use crate::window::{
self, CursorGrabMode, CustomCursor, CustomCursorSource, ImePurpose, ResizeDirection, Theme,
Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel,
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use winit_core::window::{
self, CursorGrabMode, ImeCapabilities, ImePurpose, ImeRequest, ImeRequestError,
ResizeDirection, Theme, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
WindowLevel,
};
mod keycodes;
use crate::keycodes;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
@@ -43,7 +44,7 @@ struct SharedFlagSetter {
flag: Arc<AtomicBool>,
}
impl SharedFlagSetter {
pub fn set(&self) -> bool {
fn set(&self) -> bool {
self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok()
}
}
@@ -58,21 +59,21 @@ struct SharedFlag {
// we just need to know at the start of a main loop iteration if a redraw
// was queued and be able to read and clear the state atomically)
impl SharedFlag {
pub fn new() -> Self {
fn new() -> Self {
Self { flag: Arc::new(AtomicBool::new(false)) }
}
pub fn setter(&self) -> SharedFlagSetter {
fn setter(&self) -> SharedFlagSetter {
SharedFlagSetter { flag: self.flag.clone() }
}
pub fn get_and_reset(&self) -> bool {
fn get_and_reset(&self) -> bool {
self.flag.swap(false, std::sync::atomic::Ordering::AcqRel)
}
}
#[derive(Clone)]
pub struct RedrawRequester {
struct RedrawRequester {
flag: SharedFlagSetter,
waker: AndroidAppWaker,
}
@@ -88,7 +89,7 @@ impl RedrawRequester {
RedrawRequester { flag: flag.setter(), waker }
}
pub fn request_redraw(&self) {
fn request_redraw(&self) {
if self.flag.set() {
// Only explicitly try to wake up the main loop when the flag
// value changes
@@ -99,7 +100,7 @@ impl RedrawRequester {
#[derive(Debug)]
pub struct EventLoop {
pub(crate) android_app: AndroidApp,
pub android_app: AndroidApp,
window_target: ActiveEventLoop,
redraw_flag: SharedFlag,
loop_running: bool, // Dispatched `NewEvents<Init>`
@@ -112,9 +113,9 @@ pub struct EventLoop {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) android_app: Option<AndroidApp>,
pub(crate) ignore_volume_keys: bool,
pub struct PlatformSpecificEventLoopAttributes {
pub android_app: Option<AndroidApp>,
pub ignore_volume_keys: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
@@ -127,9 +128,13 @@ impl Default for PlatformSpecificEventLoopAttributes {
const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0);
impl EventLoop {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
// For better cross-platformness.
return Err(EventLoopError::RecreationAttempt);
}
let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
Android",
@@ -159,7 +164,7 @@ impl EventLoop {
})
}
pub(crate) fn window_target(&self) -> &dyn RootActiveEventLoop {
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
&self.window_target
}
@@ -752,8 +757,9 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
pub struct PlatformSpecificWindowAttributes;
#[derive(Debug)]
pub(crate) struct Window {
pub struct Window {
app: AndroidApp,
ime_capabilities: Mutex<Option<ImeCapabilities>>,
redraw_requester: RedrawRequester,
}
@@ -764,14 +770,18 @@ impl Window {
) -> Result<Self, RequestError> {
// FIXME this ignores requested window attributes
Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() })
Ok(Self {
app: el.app.clone(),
ime_capabilities: Default::default(),
redraw_requester: el.redraw_requester.clone(),
})
}
pub fn config(&self) -> ConfigurationRef {
pub(crate) fn config(&self) -> ConfigurationRef {
self.app.config()
}
pub fn content_rect(&self) -> Rect {
pub(crate) fn content_rect(&self) -> Rect {
self.app.content_rect()
}
@@ -928,16 +938,37 @@ impl CoreWindow for Window {
fn set_window_level(&self, _level: WindowLevel) {}
fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
fn set_window_icon(&self, _window_icon: Option<winit_core::icon::Icon>) {}
fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
fn set_ime_allowed(&self, allowed: bool) {
if allowed {
self.app.show_soft_input(true);
} else {
self.app.hide_soft_input(true);
fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
let mut current_caps = self.ime_capabilities.lock().unwrap();
match request {
ImeRequest::Enable(enable) => {
let (capabilities, _) = enable.into_raw();
if current_caps.is_some() {
return Err(ImeRequestError::AlreadyEnabled);
}
*current_caps = Some(capabilities);
self.app.show_soft_input(true);
},
ImeRequest::Update(_) => {
if current_caps.is_none() {
return Err(ImeRequestError::NotEnabled);
}
},
ImeRequest::Disable => {
*current_caps = None;
self.app.hide_soft_input(true);
},
}
Ok(())
}
fn ime_capabilities(&self) -> Option<ImeCapabilities> {
*self.ime_capabilities.lock().unwrap()
}
fn set_ime_purpose(&self, _purpose: ImePurpose) {}
@@ -1000,16 +1031,6 @@ impl CoreWindow for Window {
}
}
#[derive(Default, Clone, Debug)]
pub struct OsError;
use std::fmt::{self, Display, Formatter};
impl Display for OsError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(fmt, "Android OS Error")
}
}
fn screen_size(app: &AndroidApp) -> PhysicalSize<u32> {
if let Some(native_window) = app.native_window() {
PhysicalSize::new(native_window.width() as _, native_window.height() as _)

View File

@@ -1,7 +1,8 @@
use android_activity::input::{KeyAction, KeyEvent, KeyMapChar, Keycode};
use android_activity::AndroidApp;
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
use winit_core::keyboard::{
Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey,
};
pub fn to_physical_key(keycode: Keycode) -> PhysicalKey {
PhysicalKey::Code(match keycode {

View File

@@ -62,17 +62,26 @@
//! If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building
//! with `cargo apk`, then the minimal changes would be:
//! 1. Remove `ndk-glue` from your `Cargo.toml`
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.10",
//! 2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.30.12",
//! features = [ "android-native-activity" ] }`
//! 3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc
//! macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize
//! 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};
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::window::{Window, WindowAttributes};
pub use crate::event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
PlatformSpecificWindowAttributes, Window,
};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {
@@ -80,12 +89,6 @@ pub trait EventLoopExtAndroid {
fn android_app(&self) -> &AndroidApp;
}
impl EventLoopExtAndroid for EventLoop {
fn android_app(&self) -> &AndroidApp {
&self.event_loop.android_app
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
@@ -99,30 +102,25 @@ pub trait WindowExtAndroid {
fn config(&self) -> ConfigurationRef;
}
impl WindowExtAndroid for dyn Window + '_ {
impl WindowExtAndroid for dyn CoreWindow + '_ {
fn content_rect(&self) -> Rect {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
let window = self.cast_ref::<Window>().unwrap();
window.content_rect()
}
fn config(&self) -> ConfigurationRef {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
let window = self.cast_ref::<Window>().unwrap();
window.config()
}
}
impl ActiveEventLoopExtAndroid for dyn ActiveEventLoop + '_ {
impl ActiveEventLoopExtAndroid for dyn CoreActiveEventLoop + '_ {
fn android_app(&self) -> &AndroidApp {
let event_loop = self.cast_ref::<crate::platform_impl::ActiveEventLoop>().unwrap();
let event_loop = self.cast_ref::<ActiveEventLoop>().unwrap();
&event_loop.app
}
}
/// Additional methods on [`WindowAttributes`] that are specific to Android.
pub trait WindowAttributesExtAndroid {}
impl WindowAttributesExtAndroid for WindowAttributes {}
pub trait EventLoopBuilderExtAndroid {
/// Associates the [`AndroidApp`] that was passed to `android_main()` with the event loop
///
@@ -135,18 +133,6 @@ pub trait EventLoopBuilderExtAndroid {
fn handle_volume_keys(&mut self) -> &mut Self;
}
impl EventLoopBuilderExtAndroid for EventLoopBuilder {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
}
fn handle_volume_keys(&mut self) -> &mut Self {
self.platform_specific.ignore_volume_keys = false;
self
}
}
/// Re-export of the `android_activity` API
///
/// Winit re-exports the `android_activity` API for convenience so that most
@@ -175,16 +161,5 @@ pub mod activity {
// feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page.
#[doc(no_inline)]
#[cfg(android_platform)]
pub use android_activity::*;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct Rect;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct ConfigurationRef;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct AndroidApp;
}

116
winit-appkit/Cargo.toml Normal file
View File

@@ -0,0 +1,116 @@
[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-common = { workspace = true, features = ["core-foundation", "event-handler"] }
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",
"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",
] }
[dev-dependencies]
winit.workspace = true
[package.metadata.docs.rs]
all-features = true
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin"]

1
winit-appkit/LICENSE Symbolic link
View File

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

1
winit-appkit/README.md Symbolic link
View File

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

View File

@@ -9,9 +9,9 @@ use objc2::runtime::{Imp, Sel};
use objc2::sel;
use objc2_app_kit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType};
use objc2_foundation::MainThreadMarker;
use winit_core::event::{DeviceEvent, ElementState};
use super::app_state::AppState;
use crate::event::{DeviceEvent, ElementState};
type SendEvent = extern "C-unwind" fn(&NSApplication, Sel, &NSEvent);

View File

@@ -1,6 +1,6 @@
use std::cell::{Cell, OnceCell, RefCell};
use std::mem;
use std::rc::{Rc, Weak};
use std::rc::Rc;
use std::sync::Arc;
use std::time::Instant;
@@ -8,16 +8,16 @@ use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
use objc2_foundation::NSNotification;
use winit_common::core_foundation::EventLoopProxy;
use winit_common::event_handler::EventHandler;
use winit_core::application::ApplicationHandler;
use winit_core::event::{StartCause, WindowEvent};
use winit_core::event_loop::ControlFlow;
use winit_core::window::WindowId;
use super::super::event_handler::EventHandler;
use super::super::event_loop_proxy::EventLoopProxy;
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::event_loop::{notify_windows_of_exit, stop_app_immediately, ActiveEventLoop};
use super::menu;
use super::observer::{EventLoopWaker, RunLoop};
use crate::application::ApplicationHandler;
use crate::event::{StartCause, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
#[derive(Debug)]
pub(super) struct AppState {
@@ -58,7 +58,7 @@ impl AppState {
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Rc<Self> {
) -> 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));
}));
@@ -85,8 +85,7 @@ impl AppState {
pending_redraw: RefCell::new(vec![]),
});
GLOBAL.get(mtm).set(this.clone()).expect("application state can only be set once");
this
GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this))
}
pub fn get(mtm: MainThreadMarker) -> Rc<Self> {
@@ -309,13 +308,9 @@ impl AppState {
}
// Called by RunLoopObserver after finishing waiting for new events
pub fn wakeup(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
pub fn wakeup(self: &Rc<Self>) {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
if !self.event_handler.ready() || !self.is_running() {
return;
}
@@ -341,15 +336,11 @@ impl AppState {
}
// Called by RunLoopObserver before waiting for new events
pub fn cleared(self: &Rc<Self>, panic_info: Weak<PanicInfo>) {
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
pub fn cleared(self: &Rc<Self>) {
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
// we're about to return to the `CFRunLoop` to poll for new events?
if panic_info.is_panicking() || !self.event_handler.ready() || !self.is_running() {
if !self.event_handler.ready() || !self.is_running() {
return;
}

View File

@@ -9,10 +9,8 @@ use objc2_app_kit::{NSBitmapImageRep, NSCursor, NSDeviceRGBColorSpace, NSImage};
use objc2_foundation::{
ns_string, NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSSize, NSString,
};
use crate::cursor::{CursorImage, CustomCursorProvider, CustomCursorSource};
use crate::error::{NotSupportedError, RequestError};
use crate::window::CursorIcon;
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>);
@@ -42,8 +40,8 @@ impl CustomCursor {
}
pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCursor>, RequestError> {
let width = cursor.width;
let height = cursor.height;
let width = cursor.width();
let height = cursor.height();
let bitmap = unsafe {
NSBitmapImageRep::initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(
@@ -60,15 +58,16 @@ pub(crate) fn cursor_from_image(cursor: &CursorImage) -> Result<Retained<NSCurso
32,
)
}.ok_or_else(|| os_error!("parent view should be installed in a window"))?;
let bitmap_data = unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.rgba.len()) };
bitmap_data.copy_from_slice(&cursor.rgba);
let bitmap_data =
unsafe { slice::from_raw_parts_mut(bitmap.bitmapData(), cursor.buffer().len()) };
bitmap_data.copy_from_slice(cursor.buffer());
let image = unsafe {
NSImage::initWithSize(NSImage::alloc(), NSSize::new(width.into(), height.into()))
};
unsafe { image.addRepresentation(&bitmap) };
let hotspot = NSPoint::new(cursor.hotspot_x as f64, cursor.hotspot_y as f64);
let hotspot = NSPoint::new(cursor.hotspot_x() as f64, cursor.hotspot_y() as f64);
Ok(NSCursor::initWithImage_hotSpot(NSCursor::alloc(), &image, hotspot))
}

View File

@@ -6,14 +6,14 @@ use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSEventSubtype, NSEventType};
use objc2_core_foundation::{CFData, CFRetained};
use objc2_foundation::NSPoint;
use smol_str::SmolStr;
use super::ffi;
use crate::event::{ElementState, KeyEvent, Modifiers};
use crate::keyboard::{
use winit_core::event::{ElementState, KeyEvent, Modifiers};
use winit_core::keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode,
PhysicalKey,
};
use super::ffi;
/// Ignores ALL modifiers.
pub fn get_modifierless_char(scancode: u16) -> Key {
let Some(ptr) = NonNull::new(unsafe { ffi::TISCopyCurrentKeyboardLayoutInputSource() }) else {
@@ -330,7 +330,7 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
pressed_mods.set(ModifiersKeys::LMETA, flags.contains(NX_DEVICELCMDKEYMASK));
pressed_mods.set(ModifiersKeys::RMETA, flags.contains(NX_DEVICERCMDKEYMASK));
Modifiers { state, pressed_mods }
Modifiers::new(state, pressed_mods)
}
pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
@@ -349,7 +349,7 @@ pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
}
}
pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32> {
pub fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32> {
let code = match physical_key {
PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(_) => return None,
@@ -481,7 +481,7 @@ pub(crate) fn physicalkey_to_scancode(physical_key: PhysicalKey) -> Option<u32>
}
}
pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
pub fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
// Follows what Chromium and Firefox do:
// https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc
// https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h

View File

@@ -1,8 +1,4 @@
use std::any::Any;
use std::cell::Cell;
use std::fmt;
use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe};
use std::rc::{Rc, Weak};
use std::rc::Rc;
use std::sync::Arc;
use std::time::{Duration, Instant};
@@ -15,61 +11,26 @@ use objc2_app_kit::{
};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use rwh_06::HasDisplayHandle;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, RequestError};
use winit_core::event_loop::pump_events::PumpStatus;
use winit_core::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::Theme;
use super::super::notification_center::create_observer;
use super::app::override_send_event;
use super::app_state::AppState;
use super::cursor::CustomCursor;
use super::event::dummy_event;
use super::monitor;
use super::notification_center::create_observer;
use super::observer::setup_control_flow_observers;
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, RequestError};
use crate::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform::macos::ActivationPolicy;
use crate::platform::pump_events::PumpStatus;
use crate::platform_impl::Window;
use crate::window::{CustomCursor as CoreCustomCursor, CustomCursorSource, Theme};
#[derive(Default)]
pub struct PanicInfo {
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
}
impl fmt::Debug for PanicInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PanicInfo").finish_non_exhaustive()
}
}
// WARNING:
// As long as this struct is used through its `impl`, it is UnwindSafe.
// (If `get_mut` is called on `inner`, unwind safety may get broken.)
impl UnwindSafe for PanicInfo {}
impl RefUnwindSafe for PanicInfo {}
impl PanicInfo {
pub fn is_panicking(&self) -> bool {
let inner = self.inner.take();
let result = inner.is_some();
self.inner.set(inner);
result
}
/// Overwrites the current state if the current state is not panicking
pub fn set_panic(&self, p: Box<dyn Any + Send + 'static>) {
if !self.is_panicking() {
self.inner.set(Some(p));
}
}
pub fn take(&self) -> Option<Box<dyn Any + Send + 'static>> {
self.inner.take()
}
}
use crate::window::Window;
use crate::ActivationPolicy;
#[derive(Debug)]
pub struct ActiveEventLoop {
@@ -102,8 +63,8 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_window(
&self,
window_attributes: crate::window::WindowAttributes,
) -> Result<Box<dyn crate::window::Window>, RequestError> {
window_attributes: winit_core::window::WindowAttributes,
) -> Result<Box<dyn winit_core::window::Window>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
@@ -122,7 +83,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
)
}
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
fn primary_monitor(&self) -> Option<winit_core::monitor::MonitorHandle> {
let monitor = monitor::primary_monitor();
Some(CoreMonitorHandle(Arc::new(monitor)))
}
@@ -182,7 +143,6 @@ pub struct EventLoop {
app_state: Rc<AppState>,
window_target: ActiveEventLoop,
panic_info: Rc<PanicInfo>,
// Since macOS 10.11, we no longer need to remove the observers before they are deallocated;
// the system instead cleans it up next time it would have posted a notification to it.
@@ -193,10 +153,10 @@ pub struct EventLoop {
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) activation_policy: Option<ActivationPolicy>,
pub(crate) default_menu: bool,
pub(crate) activate_ignoring_other_apps: bool,
pub struct PlatformSpecificEventLoopAttributes {
pub activation_policy: Option<ActivationPolicy>,
pub default_menu: bool,
pub activate_ignoring_other_apps: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
@@ -206,9 +166,7 @@ impl Default for PlatformSpecificEventLoopAttributes {
}
impl EventLoop {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("on macOS, `EventLoop` must be created on the main thread!");
@@ -224,7 +182,8 @@ impl EventLoop {
activation_policy,
attributes.default_menu,
attributes.activate_ignoring_other_apps,
);
)
.ok_or_else(|| EventLoopError::RecreationAttempt)?;
// Initialize the application (if it has not already been).
let app = NSApplication::sharedApplication(mtm);
@@ -258,14 +217,12 @@ impl EventLoop {
},
);
let panic_info: Rc<PanicInfo> = Default::default();
setup_control_flow_observers(mtm, Rc::downgrade(&panic_info));
setup_control_flow_observers(mtm);
Ok(EventLoop {
app,
app_state: app_state.clone(),
window_target: ActiveEventLoop { app_state, mtm },
panic_info,
_did_finish_launching_observer,
_will_terminate_observer,
})
@@ -305,15 +262,6 @@ impl EventLoop {
// NOTE: Make sure to not run the application re-entrantly, as that'd be confusing.
self.app.run();
// While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which
// will lead to us stopping the `NSApplication` and saving the
// `PanicInfo` so that we can resume the unwind at a controlled,
// safe point in time.
if let Some(panic) = self.panic_info.take() {
resume_unwind(panic);
}
self.app_state.internal_exit()
})
});
@@ -369,15 +317,6 @@ impl EventLoop {
self.app.run();
}
// While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which
// will lead to us stopping the application and saving the
// `PanicInfo` so that we can resume the unwind at a controlled,
// safe point in time.
if let Some(panic) = self.panic_info.take() {
resume_unwind(panic);
}
if self.app_state.exiting() {
self.app_state.internal_exit();
PumpStatus::Exit(0)
@@ -422,29 +361,3 @@ pub(super) fn notify_windows_of_exit(app: &NSApplication) {
window.close();
}
}
/// Catches panics that happen inside `f` and when a panic
/// happens, stops the `sharedApplication`
#[inline]
pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
mtm: MainThreadMarker,
panic_info: Weak<PanicInfo>,
f: F,
) -> Option<R> {
match catch_unwind(f) {
Ok(r) => Some(r),
Err(e) => {
// It's important that we set the panic before requesting a `stop`
// because some callback are still called during the `stop` message
// and we need to know in those callbacks if the application is currently
// panicking
{
let panic_info = panic_info.upgrade().unwrap();
panic_info.set_panic(e);
}
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
None
},
}
}

View File

@@ -11,8 +11,8 @@ 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.

View File

@@ -17,13 +17,12 @@
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is
//! to register your own application delegate, as outlined in the following example (see
//! `objc2-app-kit` for more detailed information).
#![cfg_attr(target_os = "macos", doc = "```")]
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
//! ```
//! use objc2::rc::Retained;
//! use objc2::runtime::ProtocolObject;
//! use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly};
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
//! use objc2_foundation::{NSArray, NSURL, NSObject, NSObjectProtocol};
//! use objc2_foundation::{NSArray, NSObject, NSObjectProtocol, NSURL};
//! use winit::event_loop::EventLoop;
//!
//! define_class!(
@@ -64,17 +63,40 @@
//! Ok(())
//! }
//! ```
#![cfg(target_vendor = "apple")] // TODO: Remove once `objc2` allows compiling on all platforms
#[macro_use]
mod util;
mod app;
mod app_state;
mod cursor;
mod event;
mod event_loop;
mod ffi;
mod menu;
mod monitor;
mod notification_center;
mod observer;
mod view;
mod window;
mod window_delegate;
use std::os::raw::c_void;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[doc(inline)]
pub use winit_core::application::macos::ApplicationHandlerExtMacOS;
use winit_core::event_loop::ActiveEventLoop;
use winit_core::monitor::MonitorHandle;
use winit_core::window::{PlatformWindowAttributes, Window};
use crate::application::ApplicationHandler;
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
use crate::monitor::MonitorHandle;
use crate::platform_impl::MonitorHandle as MacOsMonitorHandle;
use crate::window::{Window, WindowAttributes, WindowId};
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 {
@@ -170,109 +192,109 @@ pub trait WindowExtMacOS {
impl WindowExtMacOS for dyn Window + '_ {
#[inline]
fn simple_fullscreen(&self) -> bool {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
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::<crate::platform_impl::Window>().unwrap();
let window = self.cast_ref::<AppKitWindow>().unwrap();
window.maybe_wait_on_main(|w| w.unified_titlebar())
}
}
@@ -292,7 +314,7 @@ pub enum ActivationPolicy {
Prohibited,
}
/// Additional methods on [`WindowAttributes`] that are specific to MacOS.
/// Window attributes that are specific to MacOS.
///
/// **Note:** Properties dealing with the titlebar will be overwritten by the
/// [`WindowAttributes::with_decorations`] method:
@@ -301,127 +323,158 @@ pub enum ActivationPolicy {
/// - `with_titlebar_hidden`
/// - `with_titlebar_buttons_hidden`
/// - `with_fullsize_content_view`
pub trait WindowAttributesExtMacOS {
///
/// [`WindowAttributes::with_decorations`]: crate::window::WindowAttributes::with_decorations
#[derive(Clone, Debug, PartialEq)]
pub struct WindowAttributesMacOS {
pub(crate) movable_by_window_background: bool,
pub(crate) titlebar_transparent: bool,
pub(crate) title_hidden: bool,
pub(crate) titlebar_hidden: bool,
pub(crate) titlebar_buttons_hidden: bool,
pub(crate) fullsize_content_view: bool,
pub(crate) disallow_hidpi: bool,
pub(crate) has_shadow: bool,
pub(crate) accepts_first_mouse: bool,
pub(crate) tabbing_identifier: Option<String>,
pub(crate) option_as_alt: OptionAsAlt,
pub(crate) borderless_game: bool,
pub(crate) unified_titlebar: bool,
pub(crate) panel: bool,
}
impl WindowAttributesMacOS {
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
fn with_movable_by_window_background(self, movable_by_window_background: bool) -> Self;
#[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.
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
/// Hides the window title.
fn with_title_hidden(self, title_hidden: bool) -> Self;
#[inline]
pub fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
self.titlebar_transparent = titlebar_transparent;
self
}
/// Hides the window titlebar.
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
#[inline]
pub fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.titlebar_hidden = titlebar_hidden;
self
}
/// Hides the window titlebar buttons.
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self;
#[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.
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
fn with_has_shadow(self, has_shadow: bool) -> Self;
#[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.
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self;
#[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>
fn with_tabbing_identifier(self, identifier: &str) -> Self;
#[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.
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
#[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.
fn with_borderless_game(self, borderless_game: bool) -> Self;
#[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.
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
#[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
fn with_panel(self, panel: bool) -> Self;
#[inline]
pub fn with_panel(mut self, panel: bool) -> Self {
self.panel = panel;
self
}
}
impl WindowAttributesExtMacOS for WindowAttributes {
impl Default for WindowAttributesMacOS {
#[inline]
fn with_movable_by_window_background(mut self, movable_by_window_background: bool) -> Self {
self.platform_specific.movable_by_window_background = movable_by_window_background;
self
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,
}
}
}
#[inline]
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
self.platform_specific.titlebar_transparent = titlebar_transparent;
self
}
#[inline]
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
self.platform_specific.titlebar_hidden = titlebar_hidden;
self
}
#[inline]
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
self
}
#[inline]
fn with_title_hidden(mut self, title_hidden: bool) -> Self {
self.platform_specific.title_hidden = title_hidden;
self
}
#[inline]
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
self.platform_specific.fullsize_content_view = fullsize_content_view;
self
}
#[inline]
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
self.platform_specific.disallow_hidpi = disallow_hidpi;
self
}
#[inline]
fn with_has_shadow(mut self, has_shadow: bool) -> Self {
self.platform_specific.has_shadow = has_shadow;
self
}
#[inline]
fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
self
}
#[inline]
fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
self.platform_specific.tabbing_identifier.replace(tabbing_identifier.to_string());
self
}
#[inline]
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
self.platform_specific.option_as_alt = option_as_alt;
self
}
#[inline]
fn with_borderless_game(mut self, borderless_game: bool) -> Self {
self.platform_specific.borderless_game = borderless_game;
self
}
#[inline]
fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
self.platform_specific.unified_titlebar = unified_titlebar;
self
}
#[inline]
fn with_panel(mut self, panel: bool) -> Self {
self.platform_specific.panel = panel;
self
impl PlatformWindowAttributes for WindowAttributesMacOS {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> {
Box::from(self.clone())
}
}
@@ -480,26 +533,6 @@ pub trait EventLoopBuilderExtMacOS {
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
}
impl EventLoopBuilderExtMacOS for EventLoopBuilder {
#[inline]
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
self.platform_specific.activation_policy = Some(activation_policy);
self
}
#[inline]
fn with_default_menu(&mut self, enable: bool) -> &mut Self {
self.platform_specific.default_menu = enable;
self
}
#[inline]
fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
self.platform_specific.activate_ignoring_other_apps = ignore;
self
}
}
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
pub trait MonitorHandleExtMacOS {
/// Returns a pointer to the NSScreen representing this monitor.
@@ -508,7 +541,7 @@ pub trait MonitorHandleExtMacOS {
impl MonitorHandleExtMacOS for MonitorHandle {
fn ns_screen(&self) -> Option<*mut c_void> {
let monitor = self.cast_ref::<MacOsMonitorHandle>().unwrap();
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 _)
@@ -533,30 +566,26 @@ pub trait ActiveEventLoopExtMacOS {
impl ActiveEventLoopExtMacOS for dyn ActiveEventLoop + '_ {
fn hide_application(&self) {
let event_loop = self
.cast_ref::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
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::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
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::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
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::<crate::platform_impl::ActiveEventLoop>()
.expect("non macOS event loop on macOS");
let event_loop =
self.cast_ref::<AppKitActiveEventLoop>().expect("non macOS event loop on macOS");
event_loop.allows_automatic_window_tabbing()
}
}
@@ -580,52 +609,3 @@ pub enum OptionAsAlt {
#[default]
None,
}
/// Additional events on [`ApplicationHandler`] that are specific to macOS.
///
/// This can be registered with [`ApplicationHandler::macos_handler`].
pub trait ApplicationHandlerExtMacOS: ApplicationHandler {
/// The system interpreted a keypress as a standard key binding command.
///
/// Examples include inserting tabs and newlines, or moving the insertion point, see
/// [`NSStandardKeyBindingResponding`] for the full list of key bindings. They are often text
/// editing related.
///
/// This corresponds to the [`doCommandBySelector:`] method on `NSTextInputClient`.
///
/// The `action` parameter contains the string representation of the selector. Examples include
/// `"insertBacktab:"`, `"indent:"` and `"noop:"`.
///
/// # Example
///
/// ```ignore
/// impl ApplicationHandlerExtMacOS for App {
/// fn standard_key_binding(
/// &mut self,
/// event_loop: &dyn ActiveEventLoop,
/// window_id: WindowId,
/// action: &str,
/// ) {
/// match action {
/// "moveBackward:" => self.cursor.position -= 1,
/// "moveForward:" => self.cursor.position += 1,
/// _ => {} // Ignore other actions
/// }
/// }
/// }
/// ```
///
/// [`NSStandardKeyBindingResponding`]: https://developer.apple.com/documentation/appkit/nsstandardkeybindingresponding?language=objc
/// [`doCommandBySelector:`]: https://developer.apple.com/documentation/appkit/nstextinputclient/1438256-docommandbyselector?language=objc
#[doc(alias = "doCommandBySelector:")]
fn standard_key_binding(
&mut self,
event_loop: &dyn ActiveEventLoop,
window_id: WindowId,
action: &str,
) {
let _ = event_loop;
let _ = window_id;
let _ = action;
}
}

View File

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

View File

@@ -6,6 +6,7 @@ use std::ptr::NonNull;
use std::{fmt, ptr};
use dispatch2::run_on_main;
use dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use objc2::rc::Retained;
use objc2::MainThreadMarker;
use objc2_app_kit::NSScreen;
@@ -17,11 +18,10 @@ use objc2_core_graphics::{
use objc2_core_video::{kCVReturnSuccess, CVDisplayLink, CVTimeFlags};
use objc2_foundation::{ns_string, NSNumber, NSPoint, NSRect};
use tracing::warn;
use winit_core::monitor::{MonitorHandleProvider, VideoMode};
use super::ffi;
use super::util::cgerr;
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
use crate::monitor::{MonitorHandleProvider, VideoMode};
#[derive(Clone)]
pub struct VideoModeHandle {
@@ -66,27 +66,31 @@ impl VideoModeHandle {
refresh_rate_millihertz: Option<NonZeroU32>,
) -> Self {
unsafe {
// 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) {
32
NonZeroU16::new(32)
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
16
NonZeroU16::new(16)
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) {
30
NonZeroU16::new(30)
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO64BitDirectPixels) {
NonZeroU16::new(64)
} else {
unimplemented!()
warn!(?pixel_encoding, "unknown bit depth");
None
};
let mode = VideoMode {
size: PhysicalSize::new(
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,
bit_depth: NonZeroU16::new(bit_depth),
};
);
VideoModeHandle { mode, monitor: monitor.clone(), native_mode }
}

View File

@@ -1,3 +1,4 @@
// NOTE: This is symlinked to be contained in both the AppKit and UIKit implementations.
use std::ptr::NonNull;
use block2::RcBlock;
@@ -11,7 +12,7 @@ use objc2_foundation::{
///
/// 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(
pub(crate) fn create_observer(
center: &NSNotificationCenter,
name: &NSNotificationName,
handler: impl Fn(&NSNotification) + 'static,

View File

@@ -4,9 +4,7 @@
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
use std::cell::Cell;
use std::ffi::c_void;
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::ptr;
use std::rc::Weak;
use std::time::Instant;
use objc2::MainThreadMarker;
@@ -18,47 +16,18 @@ use objc2_core_foundation::{
use tracing::error;
use super::app_state::AppState;
use super::event_loop::{stop_app_on_panic, PanicInfo};
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
where
F: FnOnce(Weak<PanicInfo>) + UnwindSafe,
{
let info_from_raw = unsafe { Weak::from_raw(panic_info as *mut PanicInfo) };
// Asserting unwind safety on this type should be fine because `PanicInfo` is
// `RefUnwindSafe` and `Rc<T>` is `UnwindSafe` if `T` is `RefUnwindSafe`.
let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw));
// `from_raw` takes ownership of the data behind the pointer.
// But if this scope takes ownership of the weak pointer, then
// the weak pointer will get free'd at the end of the scope.
// However we want to keep that weak reference around after the function.
std::mem::forget(info_from_raw);
let mtm = MainThreadMarker::new().unwrap();
stop_app_on_panic(mtm, Weak::clone(&panic_info), move || {
let _ = &panic_info;
f(panic_info.0)
});
}
// begin is queued with the highest priority to ensure it is processed before other observers
extern "C-unwind" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
panic_info: *mut c_void,
_info: *mut c_void,
) {
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::AfterWaiting => {
// trace!("Triggered `CFRunLoopAfterWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).wakeup(panic_info);
// trace!("Completed `CFRunLoopAfterWaiting`");
},
_ => unreachable!(),
}
});
match activity {
CFRunLoopActivity::AfterWaiting => {
AppState::get(MainThreadMarker::new().unwrap()).wakeup();
},
_ => unreachable!(),
}
}
@@ -67,21 +36,14 @@ extern "C-unwind" fn control_flow_begin_handler(
extern "C-unwind" fn control_flow_end_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
panic_info: *mut c_void,
_info: *mut c_void,
) {
unsafe {
control_flow_handler(panic_info, |panic_info| {
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::BeforeWaiting => {
// trace!("Triggered `CFRunLoopBeforeWaiting`");
AppState::get(MainThreadMarker::new().unwrap()).cleared(panic_info);
// trace!("Completed `CFRunLoopBeforeWaiting`");
},
CFRunLoopActivity::Exit => (), /* unimplemented!(), // not expected to ever happen */
_ => unreachable!(),
}
});
match activity {
CFRunLoopActivity::BeforeWaiting => {
AppState::get(MainThreadMarker::new().unwrap()).cleared();
},
CFRunLoopActivity::Exit => (), // unimplemented!(), // not expected to ever happen
_ => unreachable!(),
}
}
@@ -180,11 +142,11 @@ impl RunLoop {
}
}
pub fn setup_control_flow_observers(mtm: MainThreadMarker, panic_info: Weak<PanicInfo>) {
pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
let run_loop = RunLoop::main(mtm);
unsafe {
let mut context = CFRunLoopObserverContext {
info: Weak::into_raw(panic_info) as *mut _,
info: ptr::null_mut(),
version: 0,
retain: None,
release: None,

View File

@@ -1,12 +1,16 @@
use objc2_core_graphics::CGError;
use tracing::trace;
use winit_core::error::OsError;
use crate::error::OsError;
macro_rules! os_error {
($error:expr) => {{
winit_core::error::OsError::new(line!(), file!(), $error)
}};
}
macro_rules! trace_scope {
($s:literal) => {
let _crate =
$crate::platform_impl::platform::appkit::util::TraceGuard::new(module_path!(), $s);
let _crate = $crate::util::TraceGuard::new(module_path!(), $s);
};
}

View File

@@ -4,6 +4,7 @@ use std::collections::{HashMap, VecDeque};
use std::ptr;
use std::rc::Rc;
use dpi::{LogicalPosition, LogicalSize};
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, Sel};
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker};
@@ -11,10 +12,17 @@ use objc2_app_kit::{
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
NSTrackingRectTag, NSView, NSWindow,
};
use objc2_core_foundation::CGRect;
use objc2_foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSNotFound, NSObject, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
};
use winit_core::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
};
use winit_core::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use winit_core::window::ImeCapabilities;
use super::app_state::AppState;
use super::cursor::{default_cursor, invisible_cursor};
@@ -23,13 +31,7 @@ use super::event::{
scancode_to_physicalkey,
};
use super::window::window_id;
use crate::dpi::{LogicalPosition, LogicalSize};
use crate::event::{
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
PointerKind, PointerSource, TouchPhase, WindowEvent,
};
use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey};
use crate::platform::macos::OptionAsAlt;
use crate::OptionAsAlt;
#[derive(Debug)]
struct CursorState {
@@ -124,7 +126,7 @@ pub struct ViewState {
/// True iff the application wants IME events.
///
/// Can be set using `set_ime_allowed`
ime_allowed: Cell<bool>,
ime_capabilities: Cell<Option<ImeCapabilities>>,
/// True if the current key event should be forwarded
/// to the application, even during IME
@@ -360,9 +362,16 @@ define_class!(
_actual_range: *mut NSRange,
) -> NSRect {
trace_scope!("firstRectForCharacterRange:actualRange:");
// Guard when the view is no longer in a window during teardown.
let Some(window) = (**self).window() else {
return CGRect::ZERO;
};
// Return value is expected to be in screen coordinates, so we need a conversion
let rect = NSRect::new(self.ivars().ime_position.get(), self.ivars().ime_size.get());
// Return value is expected to be in screen coordinates, so we need a conversion here
self.window().convertRectToScreen(self.convertRect_toView(rect, None))
let view_rect = self.convertRect_toView(rect, None);
window.convertRectToScreen(view_rect)
}
#[unsafe(method(insertText:replacementRange:))]
@@ -456,7 +465,7 @@ define_class!(
// we must send the `KeyboardInput` event during IME if it triggered
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input
// is not handled by IME and should be handled by the application)
if self.ivars().ime_allowed.get() {
if self.ivars().ime_capabilities.get().is_some() {
let events_for_nsview = NSArray::from_slice(&[&*event]);
unsafe { self.interpretKeyEvents(&events_for_nsview) };
@@ -797,7 +806,7 @@ impl WinitView {
tracking_rect: Default::default(),
ime_state: Default::default(),
input_source: Default::default(),
ime_allowed: Default::default(),
ime_capabilities: Default::default(),
forward_key_to_app: Default::default(),
marked_text: Default::default(),
accepts_first_mouse,
@@ -859,12 +868,13 @@ impl WinitView {
}
}
pub(super) fn set_ime_allowed(&self, ime_allowed: bool) {
if self.ivars().ime_allowed.get() == ime_allowed {
pub(super) fn set_ime_allowed(&self, capabilities: Option<ImeCapabilities>) {
if self.ivars().ime_capabilities.get().is_some() {
return;
}
self.ivars().ime_allowed.set(ime_allowed);
if self.ivars().ime_allowed.get() {
self.ivars().ime_capabilities.set(capabilities);
if capabilities.is_some() {
return;
}
@@ -877,6 +887,10 @@ impl WinitView {
}
}
pub(super) fn ime_capabilities(&self) -> Option<ImeCapabilities> {
self.ivars().ime_capabilities.get()
}
pub(super) fn set_ime_cursor_area(&self, position: NSPoint, size: NSSize) {
self.ivars().ime_position.set(position);
self.ivars().ime_size.set(size);
@@ -1096,7 +1110,7 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
// we're getting from the operating system, which makes it
// impossible to provide such events as extra in `KeyEvent`.
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Retained<NSEvent> {
let ev_mods = event_mods(event).state;
let ev_mods = event_mods(event).state();
let ignore_alt_characters = match option_as_alt {
OptionAsAlt::OnlyLeft if lalt_pressed(event) => true,
OptionAsAlt::OnlyRight if ralt_pressed(event) => true,

View File

@@ -8,15 +8,17 @@ use objc2::rc::{autoreleasepool, Retained};
use objc2::{define_class, MainThreadMarker, Message};
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
use objc2_foundation::NSObject;
use winit_core::cursor::Cursor;
use winit_core::error::RequestError;
use winit_core::icon::Icon;
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use winit_core::window::{
ImeCapabilities, ImeRequest, ImeRequestError, Theme, UserAttentionType, Window as CoreWindow,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
use super::event_loop::ActiveEventLoop;
use super::window_delegate::WindowDelegate;
use crate::error::RequestError;
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use crate::window::{
Cursor, Icon, ImePurpose, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes,
WindowButtons, WindowId, WindowLevel,
};
#[derive(Debug)]
pub(crate) struct Window {
@@ -91,7 +93,7 @@ impl rwh_06::HasWindowHandle for Window {
}
impl CoreWindow for Window {
fn id(&self) -> crate::window::WindowId {
fn id(&self) -> winit_core::window::WindowId {
self.maybe_wait_on_main(|delegate| delegate.id())
}
@@ -231,16 +233,12 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.set_window_icon(window_icon));
}
fn set_ime_cursor_area(&self, position: Position, size: Size) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_cursor_area(position, size));
fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
self.maybe_wait_on_main(|delegate| delegate.request_ime_update(request))
}
fn set_ime_allowed(&self, allowed: bool) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_allowed(allowed));
}
fn set_ime_purpose(&self, purpose: ImePurpose) {
self.maybe_wait_on_main(|delegate| delegate.set_ime_purpose(purpose));
fn ime_capabilities(&self) -> Option<ImeCapabilities> {
self.maybe_wait_on_main(|delegate| delegate.ime_capabilities())
}
fn focus_window(&self) {
@@ -279,7 +277,10 @@ impl CoreWindow for Window {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_position(position))
}
fn set_cursor_grab(&self, mode: crate::window::CursorGrabMode) -> Result<(), RequestError> {
fn set_cursor_grab(
&self,
mode: winit_core::window::CursorGrabMode,
) -> Result<(), RequestError> {
self.maybe_wait_on_main(|delegate| delegate.set_cursor_grab(mode))
}
@@ -293,7 +294,7 @@ impl CoreWindow for Window {
fn drag_resize_window(
&self,
direction: crate::window::ResizeDirection,
direction: winit_core::window::ResizeDirection,
) -> Result<(), RequestError> {
Ok(self.maybe_wait_on_main(|delegate| delegate.drag_resize_window(direction))?)
}

View File

@@ -6,6 +6,10 @@ use std::ptr;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
Position, Size,
};
use objc2::rc::{autoreleasepool, Retained};
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2::{
@@ -36,67 +40,25 @@ use objc2_foundation::{
NSRect, NSSize, NSString,
};
use tracing::{trace, warn};
use winit_core::cursor::Cursor;
use winit_core::error::{NotSupportedError, RequestError};
use winit_core::event::{SurfaceSizeWriter, WindowEvent};
use winit_core::icon::Icon;
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider};
use winit_core::window::{
CursorGrabMode, ImeCapabilities, ImeRequest, ImeRequestError, ResizeDirection, Theme,
UserAttentionType, WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
use super::app_state::AppState;
use super::cursor::{cursor_from_icon, CustomCursor};
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
use super::ffi;
use super::monitor::{self, flip_window_screen_coordinates, get_display_id, MonitorHandle};
use super::observer::RunLoop;
use super::util::cgerr;
use super::view::WinitView;
use super::window::{window_id, WinitPanel, WinitWindow};
use super::{ffi, MonitorHandle};
use crate::dpi::{
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
Position, Size,
};
use crate::error::{NotSupportedError, RequestError};
use crate::event::{SurfaceSizeWriter, WindowEvent};
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::window::{
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
#[derive(Clone, Debug, PartialEq)]
pub struct PlatformSpecificWindowAttributes {
pub movable_by_window_background: bool,
pub titlebar_transparent: bool,
pub title_hidden: bool,
pub titlebar_hidden: bool,
pub titlebar_buttons_hidden: bool,
pub fullsize_content_view: bool,
pub disallow_hidpi: bool,
pub has_shadow: bool,
pub accepts_first_mouse: bool,
pub tabbing_identifier: Option<String>,
pub option_as_alt: OptionAsAlt,
pub borderless_game: bool,
pub unified_titlebar: bool,
pub panel: bool,
}
impl Default for PlatformSpecificWindowAttributes {
#[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,
}
}
}
use crate::{OptionAsAlt, WindowAttributesMacOS, WindowExtMacOS};
#[derive(Debug)]
pub(crate) struct State {
@@ -552,6 +514,7 @@ impl Drop for WindowDelegate {
fn new_window(
app_state: &Rc<AppState>,
attrs: &WindowAttributes,
macos_attrs: &WindowAttributesMacOS,
mtm: MainThreadMarker,
) -> Option<Retained<NSWindow>> {
autoreleasepool(|_| {
@@ -592,9 +555,7 @@ fn new_window(
},
};
let mut masks = if (!attrs.decorations && screen.is_none())
|| attrs.platform_specific.titlebar_hidden
{
let mut masks = if (!attrs.decorations && screen.is_none()) || macos_attrs.titlebar_hidden {
// Resizable without a titlebar or borders
// if decorations is set to false, ignore pl_attrs
//
@@ -622,7 +583,7 @@ fn new_window(
masks &= !NSWindowStyleMask::Closable;
}
if attrs.platform_specific.fullsize_content_view {
if macos_attrs.fullsize_content_view {
// NOTE: If we decide to add an option to change this at runtime, we must emit a
// `SurfaceResized` event to let applications know that the safe area changed.
//
@@ -637,7 +598,7 @@ fn new_window(
// confusing issues with the window not being properly activated.
//
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
let window: Retained<NSWindow> = if attrs.platform_specific.panel {
let window: Retained<NSWindow> = if macos_attrs.panel {
masks |= NSWindowStyleMask::NonactivatingPanel;
let window: Option<Retained<WinitPanel>> = unsafe {
@@ -673,7 +634,7 @@ fn new_window(
window.setTitle(&NSString::from_str(&attrs.title));
window.setAcceptsMouseMovedEvents(true);
if let Some(identifier) = &attrs.platform_specific.tabbing_identifier {
if let Some(identifier) = &macos_attrs.tabbing_identifier {
window.setTabbingIdentifier(&NSString::from_str(identifier));
window.setTabbingMode(NSWindowTabbingMode::Preferred);
}
@@ -682,13 +643,13 @@ fn new_window(
window.setSharingType(NSWindowSharingType::None);
}
if attrs.platform_specific.titlebar_transparent {
if macos_attrs.titlebar_transparent {
window.setTitlebarAppearsTransparent(true);
}
if attrs.platform_specific.title_hidden {
if macos_attrs.title_hidden {
window.setTitleVisibility(NSWindowTitleVisibility::Hidden);
}
if attrs.platform_specific.titlebar_buttons_hidden {
if macos_attrs.titlebar_buttons_hidden {
for titlebar_button in &[
#[allow(deprecated)]
NSWindowFullScreenButton,
@@ -701,10 +662,10 @@ fn new_window(
}
}
}
if attrs.platform_specific.movable_by_window_background {
if macos_attrs.movable_by_window_background {
window.setMovableByWindowBackground(true);
}
if attrs.platform_specific.unified_titlebar {
if macos_attrs.unified_titlebar {
unsafe {
// The toolbar style is ignored if there is no toolbar, so it is
// necessary to add one.
@@ -719,7 +680,7 @@ fn new_window(
}
}
if !attrs.platform_specific.has_shadow {
if !macos_attrs.has_shadow {
window.setHasShadow(false);
}
if attrs.position.is_none() {
@@ -728,8 +689,8 @@ fn new_window(
let view = WinitView::new(
app_state,
attrs.platform_specific.accepts_first_mouse,
attrs.platform_specific.option_as_alt,
macos_attrs.accepts_first_mouse,
macos_attrs.option_as_alt,
mtm,
);
@@ -737,7 +698,7 @@ fn new_window(
// macos 10.14 and `true` after 10.15, we should set it to `YES` or `NO` to avoid
// always the default system value in favour of the user's code
#[allow(deprecated)]
view.setWantsBestResolutionOpenGLSurface(!attrs.platform_specific.disallow_hidpi);
view.setWantsBestResolutionOpenGLSurface(!macos_attrs.disallow_hidpi);
// On Mojave, views automatically become layer-backed shortly after being added to
// a window. Changing the layer-backedness of a view breaks the association between
@@ -785,13 +746,19 @@ fn new_window(
impl WindowDelegate {
pub(super) fn new(
app_state: &Rc<AppState>,
attrs: WindowAttributes,
mut attrs: WindowAttributes,
mtm: MainThreadMarker,
) -> Result<Retained<Self>, RequestError> {
let window = new_window(app_state, &attrs, mtm)
let macos_attrs = attrs
.platform
.take()
.and_then(|attrs| attrs.cast::<WindowAttributesMacOS>().ok())
.unwrap_or_default();
let window = new_window(app_state, &attrs, &macos_attrs, mtm)
.ok_or_else(|| os_error!("couldn't create `NSWindow`"))?;
match attrs.parent_window.map(|handle| handle.0) {
match attrs.parent_window() {
Some(rwh_06::RawWindowHandle::AppKit(handle)) => {
// SAFETY: Caller ensures the pointer is valid or NULL
// Unwrap is fine, since the pointer comes from `NonNull`.
@@ -843,7 +810,7 @@ impl WindowDelegate {
standard_frame: Cell::new(None),
is_simple_fullscreen: Cell::new(false),
saved_style: Cell::new(None),
is_borderless_game: Cell::new(attrs.platform_specific.borderless_game),
is_borderless_game: Cell::new(macos_attrs.borderless_game),
});
let delegate: Retained<WindowDelegate> = unsafe { msg_send![super(delegate), init] };
@@ -1707,26 +1674,50 @@ impl WindowDelegate {
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html
}
#[inline]
pub fn set_ime_cursor_area(&self, spot: Position, size: Size) {
let scale_factor = self.scale_factor();
let logical_spot = spot.to_logical(scale_factor);
let logical_spot = NSPoint::new(logical_spot.x, logical_spot.y);
pub fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
let current_caps = self.view().ime_capabilities();
let request_data = match request {
ImeRequest::Enable(enable) => {
let (capabilities, request_data) = enable.into_raw();
if current_caps.is_some() {
return Err(ImeRequestError::AlreadyEnabled);
}
self.view().set_ime_allowed(Some(capabilities));
request_data
},
ImeRequest::Update(request_data) => {
if current_caps.is_none() {
return Err(ImeRequestError::NotEnabled);
}
request_data
},
ImeRequest::Disable => {
self.view().set_ime_allowed(None);
return Ok(());
},
};
let size = size.to_logical(scale_factor);
let size = NSSize::new(size.width, size.height);
if let Some((spot, size)) = request_data.cursor_area {
if self.view().ime_capabilities().unwrap().cursor_area() {
let scale_factor = self.scale_factor();
let logical_spot = spot.to_logical(scale_factor);
let logical_spot = NSPoint::new(logical_spot.x, logical_spot.y);
self.view().set_ime_cursor_area(logical_spot, size);
let size = size.to_logical(scale_factor);
let size = NSSize::new(size.width, size.height);
self.view().set_ime_cursor_area(logical_spot, size);
} else {
warn!("discarding IME cursor area update without capability enabled.");
}
}
Ok(())
}
#[inline]
pub fn set_ime_allowed(&self, allowed: bool) {
self.view().set_ime_allowed(allowed);
pub fn ime_capabilities(&self) -> Option<ImeCapabilities> {
self.view().ime_capabilities()
}
#[inline]
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}
#[inline]
pub fn focus_window(&self) {
let mtm = MainThreadMarker::from(self);

42
winit-common/Cargo.toml Normal file
View File

@@ -0,0 +1,42 @@
[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:objc2", "dep:objc2-core-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 }
# CoreFoundation
objc2 = { workspace = true, optional = true }
objc2-core-foundation = { workspace = true, optional = true, features = [
"std",
"CFRunLoop",
"CFString",
] }
[package.metadata.docs.rs]
all-features = true

1
winit-common/LICENSE Symbolic link
View File

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

1
winit-common/README.md Symbolic link
View File

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

View File

@@ -1,12 +1,14 @@
use std::os::raw::c_void;
use std::ptr::NonNull;
use std::sync::Arc;
use std::task::{RawWaker, RawWakerVTable, Waker};
use objc2::MainThreadMarker;
use objc2_core_foundation::{
kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoop, CFRunLoopSource, CFRunLoopSourceContext,
Type,
};
use crate::event_loop::EventLoopProxyProvider;
use winit_core::event_loop::EventLoopProxyProvider;
/// A waker that signals a `CFRunLoopSource` on the main thread.
///
@@ -15,7 +17,7 @@ use crate::event_loop::EventLoopProxyProvider;
///
/// See <https://developer.apple.com/documentation/corefoundation/cfrunloopsource?language=objc>.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct EventLoopProxy {
pub struct EventLoopProxy {
source: CFRetained<CFRunLoopSource>,
/// Cached value of `CFRunLoopGetMain`.
main_loop: CFRetained<CFRunLoop>,
@@ -29,7 +31,7 @@ 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(crate) fn new<F: Fn() + 'static>(mtm: MainThreadMarker, signaller: F) -> Self {
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);
@@ -100,8 +102,7 @@ impl EventLoopProxy {
// FIXME(madsmtm): Use this on macOS too.
// More difficult there, since the user can re-start the event loop.
#[cfg_attr(target_os = "macos", allow(dead_code))]
pub(crate) fn invalidate(&self) {
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();
@@ -121,4 +122,54 @@ impl EventLoopProxyProvider for EventLoopProxy {
// main loop may be sleeping (and `CFRunLoopSourceSignal` won't wake it).
self.main_loop.wake_up();
}
fn waker(&self) -> Waker {
const VTABLE: RawWakerVTable =
RawWakerVTable::new(clone_waker, wake, wake_by_ref, drop_waker);
unsafe fn clone_waker(data: *const ()) -> RawWaker {
// SAFETY: The poiner came from `CFRunLoopSource` and is valid and non-null.
let source = unsafe { &*data.cast::<CFRunLoopSource>() };
// Increment reference count.
let source = source.retain();
// Pass ownership to the raw waker.
let data: *const CFRunLoopSource = CFRetained::into_raw(source).as_ptr();
RawWaker::new(data.cast(), &VTABLE)
}
unsafe fn wake(data: *const ()) {
unsafe { wake_by_ref(data) };
unsafe { drop_waker(data) };
}
unsafe fn wake_by_ref(data: *const ()) {
// SAFETY: The poiner came from `CFRunLoopSource` and is valid and non-null.
let source = unsafe { &*data.cast::<CFRunLoopSource>() };
// 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.
source.signal();
let main_loop = CFRunLoop::main().unwrap();
// 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).
main_loop.wake_up();
}
unsafe fn drop_waker(data: *const ()) {
let source = data.cast::<CFRunLoopSource>().cast_mut();
// SAFETY: The poiner came from `CFRunLoopSource` and is valid and non-null.
// We take ownership of a retain count here.
let _source = unsafe { CFRetained::from_raw(NonNull::new_unchecked(source)) };
}
let data: *const CFRunLoopSource = CFRetained::into_raw(self.source.clone()).as_ptr();
unsafe { Waker::from_raw(RawWaker::new(data.cast(), &VTABLE)) }
}
}

View File

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

View File

@@ -1,12 +1,12 @@
use std::cell::RefCell;
use std::{fmt, mem};
use crate::application::ApplicationHandler;
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(crate) struct EventHandler {
pub struct EventHandler {
/// This can be in the following states:
/// - Not registered by the event loop, or terminated (None).
/// - Present (Some(handler)).
@@ -26,7 +26,7 @@ impl fmt::Debug for EventHandler {
}
impl EventHandler {
pub(crate) fn new() -> Self {
pub fn new() -> Self {
Self { inner: RefCell::new(None) }
}
@@ -35,7 +35,7 @@ impl EventHandler {
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates
/// to store the handler in a thread local, such that it can be accessed
/// from within the closure.
pub(crate) fn set<'handler, R>(
pub fn set<'handler, R>(
&self,
app: Box<dyn ApplicationHandler + 'handler>,
closure: impl FnOnce() -> R,
@@ -104,16 +104,15 @@ impl EventHandler {
// soundness.
}
#[cfg(target_os = "macos")]
pub(crate) fn in_use(&self) -> bool {
pub fn in_use(&self) -> bool {
self.inner.try_borrow().is_err()
}
pub(crate) fn ready(&self) -> bool {
pub fn ready(&self) -> bool {
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
}
pub(crate) fn handle(&self, callback: impl FnOnce(&mut (dyn ApplicationHandler + '_))) {
pub fn handle(&self, callback: impl FnOnce(&mut (dyn ApplicationHandler + '_))) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(Some(ref mut user_app)) => {
// It is important that we keep the reference borrowed here,
@@ -137,7 +136,7 @@ impl EventHandler {
}
}
pub(crate) fn terminate(&self) {
pub fn terminate(&self) {
match self.inner.try_borrow_mut().as_deref_mut() {
Ok(data @ Some(_)) => {
let handler = data.take();

8
winit-common/src/lib.rs Normal file
View File

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

View File

@@ -4,20 +4,22 @@ use std::ffi::c_char;
use std::ops::Deref;
use std::ptr::{self, NonNull};
#[cfg(x11_platform)]
use winit_core::keyboard::{
Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey,
};
#[cfg(feature = "x11")]
use x11_dl::xlib_xcb::xcb_connection_t;
use xkb::XKB_MOD_INVALID;
use xkbcommon_dl::{
self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t,
xkb_layout_index_t, xkb_mod_index_t,
};
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
#[cfg(x11_platform)]
use crate::platform_impl::common::xkb::XKBXH;
use crate::platform_impl::common::xkb::{XkbContext, XKBH};
#[cfg(feature = "x11")]
use super::XKBXH;
use super::{XkbContext, XKBH};
/// Map the raw X11-style keycode to the `KeyCode` enum.
///
@@ -945,7 +947,7 @@ pub struct XkbKeymap {
}
impl XkbKeymap {
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
pub fn from_fd(context: &XkbContext, fd: OwnedFd, size: usize) -> Option<Self> {
let map = unsafe { MmapOptions::new().len(size).map_copy_read_only(&fd).ok()? };
@@ -962,7 +964,7 @@ impl XkbKeymap {
Some(Self::new_inner(keymap, 0))
}
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub fn from_x11_keymap(
context: &XkbContext,
xcb: *mut xcb_connection_t,
@@ -995,7 +997,7 @@ impl XkbKeymap {
Self { keymap, _mods_indices: mods_indices, _core_keyboard_id }
}
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub fn mods_indices(&self) -> ModsIndices {
self._mods_indices
}
@@ -1047,7 +1049,7 @@ impl Deref for XkbKeymap {
}
/// Modifier index in the keymap.
#[cfg_attr(not(x11_platform), allow(dead_code))]
#[cfg_attr(not(feature = "x11"), allow(dead_code))]
#[derive(Default, Debug, Clone, Copy)]
pub struct ModsIndices {
pub shift: Option<xkb_mod_index_t>,

View File

@@ -1,29 +1,27 @@
use std::ops::Deref;
use std::os::raw::c_char;
#[cfg(wayland_platform)]
#[cfg(feature = "wayland")]
use std::os::unix::io::OwnedFd;
use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::LazyLock;
use smol_str::SmolStr;
use tracing::warn;
use winit_core::event::{ElementState, KeyEvent};
use winit_core::keyboard::{Key, KeyLocation};
use xkbcommon_dl::{
self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle,
xkbcommon_handle, XkbCommon, XkbCommonCompose,
};
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
use crate::event::{ElementState, KeyEvent};
use crate::keyboard::{Key, KeyLocation};
use crate::utils::Lazy;
mod compose;
mod keymap;
mod state;
use compose::{ComposeStatus, XkbComposeState, XkbComposeTable};
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub use keymap::raw_keycode_to_physicalkey;
use keymap::XkbKeymap;
pub use keymap::{physicalkey_to_scancode, scancode_to_physicalkey};
@@ -32,10 +30,10 @@ pub use state::XkbState;
// TODO: Wire this up without using a static `AtomicBool`.
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle);
static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle);
static XKBH: LazyLock<&'static XkbCommon> = LazyLock::new(xkbcommon_handle);
static XKBCH: LazyLock<&'static XkbCommonCompose> = LazyLock::new(xkbcommon_compose_handle);
#[cfg(feature = "x11")]
static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
static XKBXH: LazyLock<&'static xkb::x11::XkbCommonX11> = LazyLock::new(xkbcommon_x11_handle);
#[inline(always)]
pub fn reset_dead_keys() {
@@ -51,7 +49,7 @@ pub enum Error {
#[derive(Debug)]
pub struct Context {
// NOTE: field order matters.
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub core_keyboard_id: i32,
state: Option<XkbState>,
keymap: Option<XkbKeymap>,
@@ -85,7 +83,7 @@ impl Context {
keymap: None,
compose_state1,
compose_state2,
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
core_keyboard_id: 0,
_compose_table: compose_table,
context,
@@ -126,23 +124,23 @@ impl Context {
self.keymap.as_mut()
}
#[cfg(wayland_platform)]
#[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() {
warn!("failed to update xkb keymap");
tracing::warn!("failed to update xkb keymap");
}
self.state = state;
self.keymap = keymap;
}
#[cfg(x11_platform)]
#[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() {
warn!("failed to update xkb keymap");
tracing::warn!("failed to update xkb keymap");
}
self.state = state;
self.keymap = keymap;
@@ -161,7 +159,7 @@ impl Context {
/// Key builder context with the user provided xkb state.
///
/// Should be used when the original context must not be altered.
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub fn key_context_with_state<'a>(
&'a mut self,
state: &'a mut XkbState,
@@ -325,7 +323,7 @@ impl<'a, 'b> KeyEventResults<'a, 'b> {
// The current behaviour makes it so composing a character overrides attempts to input a
// control character with the `Ctrl` key. We can potentially add a configuration option
// if someone specifically wants the oppsite behaviour.
// if someone specifically wants the opposite behaviour.
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
match self.composed_text() {
Ok(text) => text,

View File

@@ -4,16 +4,16 @@ use std::os::raw::c_char;
use std::ptr::NonNull;
use smol_str::SmolStr;
#[cfg(x11_platform)]
#[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,
};
use crate::platform_impl::common::xkb::keymap::XkbKeymap;
#[cfg(x11_platform)]
use crate::platform_impl::common::xkb::XKBXH;
use crate::platform_impl::common::xkb::{make_string_with, XKBH};
use super::keymap::XkbKeymap;
#[cfg(feature = "x11")]
use super::XKBXH;
use super::{make_string_with, XKBH};
#[derive(Debug)]
pub struct XkbState {
@@ -22,13 +22,13 @@ pub struct XkbState {
}
impl XkbState {
#[cfg(wayland_platform)]
#[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(x11_platform)]
#[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)
@@ -52,7 +52,7 @@ impl XkbState {
unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) }
}
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
@@ -62,7 +62,7 @@ impl XkbState {
}
}
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
@@ -72,7 +72,7 @@ impl XkbState {
}
}
#[cfg(x11_platform)]
#[cfg(feature = "x11")]
pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
unsafe {
(XKBH.xkb_state_serialize_mods)(
@@ -153,12 +153,13 @@ impl Drop for XkbState {
}
}
/// Represents the current state of the keyboard modifiers
/// 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 currently pressed, others are toggled
/// (like caps lock).
/// 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
@@ -177,13 +178,13 @@ pub struct ModifiersState {
pub num_lock: bool,
}
impl From<ModifiersState> for crate::keyboard::ModifiersState {
fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState {
let mut to_mods = crate::keyboard::ModifiersState::empty();
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
to_mods.set(crate::keyboard::ModifiersState::META, mods.logo);
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
}
}

39
winit-core/Cargo.toml Normal file
View File

@@ -0,0 +1,39 @@
[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"
readme = "README.md"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
[features]
serde = [
"dep:serde",
"bitflags/serde",
"cursor-icon/serde",
"dpi/serde",
"keyboard-types/serde",
"smol_str/serde",
]
[dependencies]
bitflags.workspace = true
cursor-icon.workspace = true
dpi.workspace = true
keyboard-types.workspace = true
rwh_06.workspace = true
serde = { workspace = true, optional = true }
smol_str.workspace = true
# `wasm32-unknown-unknown` and `wasm32-none`, but not `wasm32-wasi`.
[target.'cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))'.dependencies]
web-time.workspace = true
[dev-dependencies]
winit.workspace = true

1
winit-core/LICENSE Symbolic link
View File

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

1
winit-core/README.md Symbolic link
View File

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

View File

@@ -0,0 +1,52 @@
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

@@ -2,21 +2,11 @@
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
use crate::event_loop::ActiveEventLoop;
#[cfg(macos_platform)]
use crate::platform::macos::ApplicationHandlerExtMacOS;
use crate::window::WindowId;
pub mod macos;
/// The handler of application-level events.
///
/// See [the top-level docs] for example usage, and [`EventLoop::run_app`] for an overview of when
/// events are delivered.
///
/// This is [dropped] when the event loop is shut down. Note that this only works if you're passing
/// the entire state to [`EventLoop::run_app`] (passing `&mut app` won't work).
///
/// [the top-level docs]: crate
/// [`EventLoop::run_app`]: crate::event_loop::EventLoop::run_app
/// [dropped]: std::ops::Drop
pub trait ApplicationHandler {
/// Emitted when new events arrive from the OS to be processed.
///
@@ -135,8 +125,9 @@ pub trait ApplicationHandler {
/// use std::thread;
/// use std::time::Duration;
///
/// use winit::application::ApplicationHandler;
/// use winit::event_loop::{ActiveEventLoop, EventLoop};
/// use winit::event_loop::EventLoop;
/// use winit_core::application::ApplicationHandler;
/// use winit_core::event_loop::ActiveEventLoop;
///
/// struct MyApp {
/// receiver: mpsc::Receiver<u64>,
@@ -210,9 +201,7 @@ pub trait ApplicationHandler {
/// Emitted when the OS sends an event to a device.
///
/// For this to be called, it must be enabled with [`EventLoop::listen_device_events`].
///
/// [`EventLoop::listen_device_events`]: crate::event_loop::EventLoop::listen_device_events
/// Whether device events are delivered depends on the backend in use.
fn device_event(
&mut self,
event_loop: &dyn ActiveEventLoop,
@@ -350,9 +339,8 @@ pub trait ApplicationHandler {
/// The macOS-specific handler.
///
/// The return value from this should not change at runtime.
#[cfg(macos_platform)]
#[inline(always)]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
None
}
}
@@ -419,9 +407,8 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
(**self).memory_warning(event_loop);
}
#[cfg(macos_platform)]
#[inline]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
(**self).macos_handler()
}
}
@@ -488,9 +475,8 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
(**self).memory_warning(event_loop);
}
#[cfg(macos_platform)]
#[inline]
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
fn macos_handler(&mut self) -> Option<&mut dyn macos::ApplicationHandlerExtMacOS> {
(**self).macos_handler()
}
}

View File

@@ -1,32 +1,4 @@
// A poly-fill for `lazy_cell`
// Replace with std::sync::LazyLock when https://github.com/rust-lang/rust/issues/109736 is stabilized.
// This isn't used on every platform, which can come up as dead code warnings.
#![allow(dead_code)]
use std::any::Any;
use std::ops::Deref;
use std::sync::OnceLock;
pub(crate) struct Lazy<T> {
cell: OnceLock<T>,
init: fn() -> T,
}
impl<T> Lazy<T> {
pub const fn new(f: fn() -> T) -> Self {
Self { cell: OnceLock::new(), init: f }
}
}
impl<T> Deref for Lazy<T> {
type Target = T;
#[inline]
fn deref(&self) -> &'_ T {
self.cell.get_or_init(self.init)
}
}
// 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
@@ -59,6 +31,7 @@ impl<T: Any> AsAny for T {
}
}
#[macro_export]
macro_rules! impl_dyn_casting {
($trait:ident) => {
impl dyn $trait + '_ {
@@ -82,8 +55,7 @@ macro_rules! impl_dyn_casting {
///
/// 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>> {
let reference: &dyn std::any::Any = self.__as_any();
if reference.is::<T>() {
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())
@@ -95,4 +67,26 @@ macro_rules! impl_dyn_casting {
};
}
pub(crate) use impl_dyn_casting;
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

@@ -5,9 +5,10 @@ use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use cursor_icon::CursorIcon;
#[doc(inline)]
pub use cursor_icon::CursorIcon;
use crate::utils::{impl_dyn_casting, AsAny};
use crate::as_any::{impl_dyn_casting, AsAny};
/// The maximum width and height for a cursor when using [`CustomCursorSource::from_rgba`].
pub const MAX_CURSOR_SIZE: u16 = 2048;
@@ -50,10 +51,10 @@ impl From<CustomCursor> for Cursor {
/// # Example
///
/// ```no_run
/// # use winit::event_loop::ActiveEventLoop;
/// # use winit::window::Window;
/// # use winit_core::event_loop::ActiveEventLoop;
/// # use winit_core::window::Window;
/// # fn scope(event_loop: &dyn ActiveEventLoop, window: &dyn Window) {
/// use winit::window::CustomCursorSource;
/// use winit_core::cursor::CustomCursorSource;
///
/// let w = 10;
/// let h = 10;
@@ -75,7 +76,7 @@ impl From<CustomCursor> for Cursor {
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct CustomCursor(pub(crate) Arc<dyn CustomCursorProvider>);
pub struct CustomCursor(pub Arc<dyn CustomCursorProvider>);
pub trait CustomCursorProvider: AsAny + fmt::Debug + Send + Sync {
/// Whether a cursor was backed by animation.
@@ -235,7 +236,6 @@ impl fmt::Display for BadAnimation {
impl Error for BadAnimation {}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
#[allow(dead_code)]
pub struct CursorImage {
pub(crate) rgba: Vec<u8>,
pub(crate) width: u16,
@@ -277,6 +277,30 @@ impl CursorImage {
Ok(CursorImage { rgba, width, height, hotspot_x, hotspot_y })
}
pub fn buffer(&self) -> &[u8] {
self.rgba.as_slice()
}
pub fn buffer_mut(&mut self) -> &mut [u8] {
self.rgba.as_mut_slice()
}
pub fn width(&self) -> u16 {
self.width
}
pub fn height(&self) -> u16 {
self.height
}
pub fn hotspot_x(&self) -> u16 {
self.hotspot_x
}
pub fn hotspot_y(&self) -> u16 {
self.hotspot_y
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -297,4 +321,16 @@ impl CursorAnimation {
Ok(Self { duration, cursors })
}
pub fn duration(&self) -> Duration {
self.duration
}
pub fn cursors(&self) -> &[CustomCursor] {
self.cursors.as_slice()
}
pub fn into_raw(self) -> (Duration, Vec<CustomCursor>) {
(self.duration, self.cursors)
}
}

View File

@@ -19,7 +19,13 @@ pub enum EventLoopError {
impl fmt::Display for EventLoopError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RecreationAttempt => write!(f, "EventLoop can't be recreated"),
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),
@@ -100,7 +106,7 @@ pub struct NotSupportedError {
}
impl NotSupportedError {
pub(crate) fn new(reason: &'static str) -> Self {
pub fn new(reason: &'static str) -> Self {
Self { reason }
}
}
@@ -121,8 +127,7 @@ pub struct OsError {
}
impl OsError {
#[allow(dead_code)]
pub(crate) fn new(
pub fn new(
line: u32,
file: &'static str,
error: impl Into<Box<dyn Error + Send + Sync + 'static>>,

View File

@@ -1,22 +1,19 @@
//! The event enums and assorted supporting types.
use std::path::PathBuf;
use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))]
use std::time::Instant;
use dpi::{PhysicalPosition, PhysicalSize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
#[cfg(web_platform)]
use web_time::Instant;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::RequestError;
use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
#[cfg(doc)]
use crate::window::Window;
use crate::window::{ActivationToken, Theme};
use crate::Instant;
/// Describes the reason the event loop is resuming.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -46,10 +43,6 @@ pub enum StartCause {
#[derive(Debug, Clone, PartialEq)]
pub enum WindowEvent {
/// The activation token was delivered back and now could be used.
#[cfg_attr(not(any(x11_platform, wayland_platform)), allow(rustdoc::broken_intra_doc_links))]
/// Delivered in response to [`request_activation_token`].
///
/// [`request_activation_token`]: crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token
ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken },
/// The size of the window's surface has changed.
@@ -563,16 +556,14 @@ impl DeviceId {
/// Convert the [`DeviceId`] into the underlying integer.
///
/// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
#[allow(dead_code)]
pub(crate) const fn into_raw(self) -> i64 {
pub const fn into_raw(self) -> i64 {
self.0
}
/// Construct a [`DeviceId`] from the underlying integer.
///
/// This should only be called with integers returned from [`DeviceId::into_raw`].
#[allow(dead_code)]
pub(crate) const fn from_raw(id: i64) -> Self {
pub const fn from_raw(id: i64) -> Self {
Self(id)
}
}
@@ -588,16 +579,14 @@ impl FingerId {
/// Convert the [`FingerId`] into the underlying integer.
///
/// This is useful if you need to pass the ID across an FFI boundary, or store it in an atomic.
#[allow(dead_code)]
pub(crate) const fn into_raw(self) -> usize {
pub const fn into_raw(self) -> usize {
self.0
}
/// Construct a [`FingerId`] from the underlying integer.
///
/// This should only be called with integers returned from [`FingerId::into_raw`].
#[allow(dead_code)]
pub(crate) const fn from_raw(id: usize) -> Self {
pub const fn from_raw(id: usize) -> Self {
Self(id)
}
}
@@ -622,17 +611,8 @@ pub enum DeviceEvent {
/// ## Platform-specific
///
/// **Web:** Only returns raw data, not OS accelerated, if [`CursorGrabMode::Locked`] is used
/// and browser support is available, see
#[cfg_attr(
web_platform,
doc = "[`ActiveEventLoopExtWeb::is_cursor_lock_raw()`][crate::platform::web::ActiveEventLoopExtWeb::is_cursor_lock_raw()]."
)]
#[cfg_attr(
not(web_platform),
doc = "`ActiveEventLoopExtWeb::is_cursor_lock_raw()`."
)]
/// and browser support is available.
///
#[rustfmt::skip]
/// [`CursorGrabMode::Locked`]: crate::window::CursorGrabMode::Locked
PointerMotion {
/// (x, y) change in position in unspecified units.
@@ -769,8 +749,8 @@ pub struct KeyEvent {
/// done by ignoring events where this property is set.
///
/// ```no_run
/// use winit::event::{ElementState, KeyEvent, WindowEvent};
/// use winit::keyboard::{KeyCode, PhysicalKey};
/// use winit_core::event::{ElementState, KeyEvent, WindowEvent};
/// use winit_core::keyboard::{KeyCode, PhysicalKey};
/// # let window_event = WindowEvent::RedrawRequested; // To make the example compile
/// match window_event {
/// WindowEvent::KeyboardInput {
@@ -825,54 +805,59 @@ pub struct KeyEvent {
pub struct Modifiers {
pub(crate) state: ModifiersState,
// NOTE: Currently pressed modifiers keys.
// NOTE: Currently active modifiers keys (logically, but not necessarily physically, pressed).
//
// The field providing a metadata, it shouldn't be used as a source of truth.
pub(crate) pressed_mods: ModifiersKeys,
}
impl Modifiers {
/// The state of the modifiers.
/// Create a new modifiers from state and pressed mods.
pub fn new(state: ModifiersState, pressed_mods: ModifiersKeys) -> Self {
Self { state, pressed_mods }
}
/// The logical state of the modifiers.
pub fn state(&self) -> ModifiersState {
self.state
}
/// The state of the left shift key.
/// The logical state of the left shift key.
pub fn lshift_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LSHIFT)
}
/// The state of the right shift key.
/// The logical state of the right shift key.
pub fn rshift_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RSHIFT)
}
/// The state of the left alt key.
/// The logical state of the left alt key.
pub fn lalt_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LALT)
}
/// The state of the right alt key.
/// The logical state of the right alt key.
pub fn ralt_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RALT)
}
/// The state of the left control key.
/// The logical state of the left control key.
pub fn lcontrol_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LCONTROL)
}
/// The state of the right control key.
/// The logical state of the right control key.
pub fn rcontrol_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RCONTROL)
}
/// The state of the left super key.
/// The logical state of the left super key.
pub fn lsuper_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LMETA)
}
/// The state of the right super key.
/// The logical state of the right super key.
pub fn rsuper_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RMETA)
}
@@ -894,6 +879,8 @@ impl From<ModifiersState> for Modifiers {
/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
///
/// The `Ime` events must be applied in the order they arrive.
///
/// This is also called a "composition event".
///
/// Most keypresses using a latin-like keyboard layout simply generate a
@@ -950,7 +937,7 @@ pub enum Ime {
/// position. When it's `None`, the cursor should be hidden. When `String` is an empty string
/// this indicates that preedit was cleared.
///
/// The cursor position is byte-wise indexed.
/// The cursor position is byte-wise indexed, assuming UTF-8.
Preedit(String, Option<(usize, usize)>),
/// Notifies when text should be inserted into the editor widget.
@@ -958,6 +945,20 @@ pub enum Ime {
/// Right before this event winit will send empty [`Self::Preedit`] event.
Commit(String),
/// Delete text surrounding the cursor or selection.
///
/// This event does not affect either the pre-edit string.
/// This means that the application must first remove the pre-edit,
/// then execute the deletion, then insert the removed text back.
///
/// This event assumes text is stored in UTF-8.
DeleteSurrounding {
/// Bytes to remove before the selection
before_bytes: usize,
/// Bytes to remove after the selection
after_bytes: usize,
},
/// Notifies when the IME was disabled.
///
/// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or
@@ -1091,8 +1092,7 @@ pub struct SurfaceSizeWriter {
}
impl SurfaceSizeWriter {
#[cfg(not(orbital_platform))]
pub(crate) fn new(new_surface_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
pub fn new(new_surface_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
Self { new_surface_size }
}
@@ -1108,6 +1108,15 @@ impl SurfaceSizeWriter {
Err(RequestError::Ignored)
}
}
/// Get the currently stashed surface size.
pub fn surface_size(&self) -> Result<PhysicalSize<u32>, RequestError> {
if let Some(inner) = self.new_surface_size.upgrade() {
Ok(*inner.lock().unwrap())
} else {
Err(RequestError::Ignored)
}
}
}
impl PartialEq for SurfaceSizeWriter {
@@ -1122,7 +1131,8 @@ impl Eq for SurfaceSizeWriter {}
mod tests {
use std::collections::{BTreeSet, HashSet};
use crate::dpi::PhysicalPosition;
use dpi::PhysicalPosition;
use crate::event;
macro_rules! foreach_event {

View File

@@ -0,0 +1,300 @@
pub mod pump_events;
pub mod run_on_demand;
use std::fmt::{self, Debug};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::Waker;
use std::time::Duration;
use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
use crate::as_any::AsAny;
use crate::cursor::{CustomCursor, CustomCursorSource};
use crate::error::RequestError;
use crate::monitor::MonitorHandle;
use crate::window::{Theme, Window, WindowAttributes};
use crate::Instant;
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();
}
/// Get a [`Waker`] that calls [`wake_up`] on the proxy when awoken.
///
/// This may be useful to `async` code or otherwise interoperating with `std`.
pub fn waker(&self) -> Waker {
self.proxy.waker()
}
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);
/// See [`EventLoopProxy::waker`] for details.
fn waker(&self) -> Waker;
}
/// 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>,
}
impl OwnedDisplayHandle {
pub fn new(handle: Arc<dyn HasDisplayHandle>) -> Self {
Self { handle }
}
}
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
self.handle.display_handle()
}
}
impl fmt::Debug for OwnedDisplayHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OwnedDisplayHandle").finish_non_exhaustive()
}
}
impl PartialEq for OwnedDisplayHandle {
fn eq(&self, other: &Self) -> bool {
match (self.display_handle(), other.display_handle()) {
(Ok(lhs), Ok(rhs)) => lhs == rhs,
_ => false,
}
}
}
impl Eq for OwnedDisplayHandle {}
/// 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,8 +1,8 @@
use std::time::Duration;
use crate::application::ApplicationHandler;
use crate::event_loop::EventLoop;
#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these.
/// Additional methods on [`EventLoop`] for pumping events within an external event loop
pub trait EventLoopExtPumpEvents {
/// Pump the `EventLoop` to check for and dispatch pending events.
@@ -106,16 +106,6 @@ pub trait EventLoopExtPumpEvents {
) -> PumpStatus;
}
impl EventLoopExtPumpEvents for EventLoop {
fn pump_app_events<A: ApplicationHandler>(
&mut self,
timeout: Option<Duration>,
app: A,
) -> PumpStatus {
self.event_loop.pump_app_events(timeout, app)
}
}
/// The return status for `pump_events`
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum PumpStatus {

View File

@@ -1,11 +1,12 @@
use crate::application::ApplicationHandler;
use crate::error::EventLoopError;
use crate::event_loop::EventLoop;
#[cfg(doc)]
use crate::{
event_loop::ActiveEventLoop, platform::pump_events::EventLoopExtPumpEvents, window::Window,
event_loop::{pump_events::EventLoopExtPumpEvents, ActiveEventLoop},
window::Window,
};
#[allow(rustdoc::broken_intra_doc_links)] // FIXME(madsmtm): Fix these.
/// Additional methods on [`EventLoop`] to return control flow to the caller.
pub trait EventLoopExtRunOnDemand {
/// Run the application with the event loop on the calling thread.
@@ -31,13 +32,7 @@ pub trait EventLoopExtRunOnDemand {
/// # Caveats
/// - This extension isn't available on all platforms, since it's not always possible to return
/// to the caller (specifically this is impossible on iOS and Web - though with the Web
/// backend it is possible to use
#[cfg_attr(
web_platform,
doc = " [`EventLoopExtWeb::spawn_app()`][crate::platform::web::EventLoopExtWeb::spawn_app()]"
)]
#[cfg_attr(not(web_platform), doc = " `EventLoopExtWeb::spawn_app()`")]
/// [^1] more than once instead).
/// backend it is possible to use `EventLoopExtWeb::spawn_app()`[^1] more than once instead).
/// - No [`Window`] state can be carried between separate runs of the event loop.
///
/// You are strongly encouraged to use [`EventLoop::run_app()`] for portability, unless you
@@ -62,22 +57,3 @@ pub trait EventLoopExtRunOnDemand {
/// [`set_control_flow()`]: ActiveEventLoop::set_control_flow()
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError>;
}
impl EventLoopExtRunOnDemand for EventLoop {
fn run_app_on_demand<A: ApplicationHandler>(&mut self, app: A) -> Result<(), EventLoopError> {
self.event_loop.run_app_on_demand(app)
}
}
/// ```compile_fail
/// use winit::event_loop::EventLoop;
/// use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
///
/// let mut event_loop = EventLoop::new().unwrap();
/// event_loop.run_on_demand(|_, _| {
/// // Attempt to run the event loop re-entrantly; this must fail.
/// event_loop.run_on_demand(|_, _| {});
/// });
/// ```
#[allow(dead_code)]
fn test_run_on_demand_cannot_access_event_loop() {}

View File

@@ -1,30 +1,29 @@
use std::error::Error;
use std::ops::Deref;
use std::sync::Arc;
use std::{fmt, io, mem};
use crate::utils::{impl_dyn_casting, AsAny};
use crate::as_any::{impl_dyn_casting, AsAny};
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<u32>();
/// An icon used for the window titlebar, taskbar, etc.
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Icon(pub(crate) Arc<dyn IconProvider>);
pub struct Icon(pub Arc<dyn IconProvider>);
// TODO remove that once split.
pub trait IconProvider: AsAny + fmt::Debug + Send + Sync {}
impl_dyn_casting!(IconProvider);
impl Deref for Icon {
type Target = dyn IconProvider;
#[repr(C)]
#[derive(Debug)]
pub(crate) struct Pixel {
pub(crate) r: u8,
pub(crate) g: u8,
pub(crate) b: u8,
pub(crate) a: u8,
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
impl_dyn_casting!(IconProvider);
#[derive(Debug)]
/// An error produced when using [`RgbaIcon::new`] with invalid arguments.
pub enum BadIcon {
@@ -61,7 +60,7 @@ impl fmt::Display for BadIcon {
impl Error for BadIcon {}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RgbaIcon {
pub(crate) width: u32,
pub(crate) height: u32,

473
winit-core/src/keyboard.rs Normal file
View File

@@ -0,0 +1,473 @@
//! Types related to the keyboard.
use bitflags::bitflags;
pub use keyboard_types::{Code as KeyCode, Location as KeyLocation, NamedKey};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use smol_str::SmolStr;
/// Contains the platform-native physical key identifier
///
/// The exact values vary from platform to platform (which is part of why this is a per-platform
/// enum), but the values are primarily tied to the key's physical location on the keyboard.
///
/// This enum is primarily used to store raw keycodes when Winit doesn't map a given native
/// physical key identifier to a meaningful [`KeyCode`] variant. In the presence of identifiers we
/// haven't mapped for you yet, this lets you use use [`KeyCode`] to:
///
/// - Correctly match key press and release events.
/// - On non-Web platforms, support assigning keybinds to virtually any key through a UI.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NativeKeyCode {
Unidentified,
/// An Android "scancode".
Android(u32),
/// A macOS "scancode".
MacOS(u16),
/// A Windows "scancode".
Windows(u16),
/// An XKB "keycode".
Xkb(u32),
}
impl std::fmt::Debug for NativeKeyCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use NativeKeyCode::{Android, MacOS, Unidentified, Windows, Xkb};
let mut debug_tuple;
match self {
Unidentified => {
debug_tuple = f.debug_tuple("Unidentified");
},
Android(code) => {
debug_tuple = f.debug_tuple("Android");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
MacOS(code) => {
debug_tuple = f.debug_tuple("MacOS");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Windows(code) => {
debug_tuple = f.debug_tuple("Windows");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Xkb(code) => {
debug_tuple = f.debug_tuple("Xkb");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
}
debug_tuple.finish()
}
}
/// Contains the platform-native logical key identifier
///
/// Exactly what that means differs from platform to platform, but the values are to some degree
/// tied to the currently active keyboard layout. The same key on the same keyboard may also report
/// different values on different platforms, which is one of the reasons this is a per-platform
/// enum.
///
/// This enum is primarily used to store raw keysym when Winit doesn't map a given native logical
/// key identifier to a meaningful [`Key`] variant. This lets you use [`Key`], and let the user
/// define keybinds which work in the presence of identifiers we haven't mapped for you yet.
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum NativeKey {
Unidentified,
/// An Android "keycode", which is similar to a "virtual-key code" on Windows.
Android(u32),
/// A macOS "scancode". There does not appear to be any direct analogue to either keysyms or
/// "virtual-key" codes in macOS, so we report the scancode instead.
MacOS(u16),
/// A Windows "virtual-key code".
Windows(u16),
/// An XKB "keysym".
Xkb(u32),
/// A "key value string".
Web(SmolStr),
}
impl std::fmt::Debug for NativeKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use NativeKey::{Android, MacOS, Unidentified, Web, Windows, Xkb};
let mut debug_tuple;
match self {
Unidentified => {
debug_tuple = f.debug_tuple("Unidentified");
},
Android(code) => {
debug_tuple = f.debug_tuple("Android");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
MacOS(code) => {
debug_tuple = f.debug_tuple("MacOS");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Windows(code) => {
debug_tuple = f.debug_tuple("Windows");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Xkb(code) => {
debug_tuple = f.debug_tuple("Xkb");
debug_tuple.field(&format_args!("0x{code:04X}"));
},
Web(code) => {
debug_tuple = f.debug_tuple("Web");
debug_tuple.field(code);
},
}
debug_tuple.finish()
}
}
impl From<NativeKeyCode> for NativeKey {
#[inline]
fn from(code: NativeKeyCode) -> Self {
match code {
NativeKeyCode::Unidentified => NativeKey::Unidentified,
NativeKeyCode::Android(x) => NativeKey::Android(x),
NativeKeyCode::MacOS(x) => NativeKey::MacOS(x),
NativeKeyCode::Windows(x) => NativeKey::Windows(x),
NativeKeyCode::Xkb(x) => NativeKey::Xkb(x),
}
}
}
impl PartialEq<NativeKey> for NativeKeyCode {
#[allow(clippy::cmp_owned)] // uses less code than direct match; target is stack allocated
#[inline]
fn eq(&self, rhs: &NativeKey) -> bool {
NativeKey::from(*self) == *rhs
}
}
impl PartialEq<NativeKeyCode> for NativeKey {
#[inline]
fn eq(&self, rhs: &NativeKeyCode) -> bool {
rhs == self
}
}
/// Represents the location of a physical key.
///
/// Winit will not emit [`KeyCode::Unidentified`] when it cannot recognize the key, instead it will
/// emit [`PhysicalKey::Unidentified`] with additional data about the key.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PhysicalKey {
/// A known key code
Code(KeyCode),
/// This variant is used when the key cannot be translated to a [`KeyCode`]
///
/// The native keycode is provided (if available) so you're able to more reliably match
/// key-press and key-release events by hashing the [`PhysicalKey`]. It is also possible to use
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
Unidentified(NativeKeyCode),
}
impl From<KeyCode> for PhysicalKey {
#[inline]
fn from(code: KeyCode) -> Self {
PhysicalKey::Code(code)
}
}
impl From<PhysicalKey> for KeyCode {
#[inline]
fn from(key: PhysicalKey) -> Self {
match key {
PhysicalKey::Code(code) => code,
PhysicalKey::Unidentified(_) => KeyCode::Unidentified,
}
}
}
impl From<NativeKeyCode> for PhysicalKey {
#[inline]
fn from(code: NativeKeyCode) -> Self {
PhysicalKey::Unidentified(code)
}
}
impl PartialEq<KeyCode> for PhysicalKey {
#[inline]
fn eq(&self, rhs: &KeyCode) -> bool {
match self {
PhysicalKey::Code(ref code) => code == rhs,
_ => false,
}
}
}
impl PartialEq<PhysicalKey> for KeyCode {
#[inline]
fn eq(&self, rhs: &PhysicalKey) -> bool {
rhs == self
}
}
impl PartialEq<NativeKeyCode> for PhysicalKey {
#[inline]
fn eq(&self, rhs: &NativeKeyCode) -> bool {
match self {
PhysicalKey::Unidentified(ref code) => code == rhs,
_ => false,
}
}
}
impl PartialEq<PhysicalKey> for NativeKeyCode {
#[inline]
fn eq(&self, rhs: &PhysicalKey) -> bool {
rhs == self
}
}
/// Key represents the meaning of a keypress.
///
/// This is a superset of the UI Events Specification's [`KeyboardEvent.key`] with
/// additions:
/// - All simple variants are wrapped under the `Named` variant
/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`.
/// - The `Dead` variant here, can specify the character which is inserted when pressing the
/// dead-key twice.
///
/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Key<Str = SmolStr> {
/// A simple (unparameterised) action
Named(NamedKey),
/// A key string that corresponds to the character typed by the user, taking into account the
/// users current locale setting, and any system-level keyboard mapping overrides that are in
/// effect.
Character(Str),
/// This variant is used when the key cannot be translated to any other variant.
///
/// The native key is provided (if available) in order to allow the user to specify keybindings
/// for keys which are not defined by this API, mainly through some sort of UI.
Unidentified(NativeKey),
/// Contains the text representation of the dead-key when available.
///
/// ## Platform-specific
/// - **Web:** Always contains `None`
Dead(Option<char>),
}
impl From<NamedKey> for Key {
#[inline]
fn from(action: NamedKey) -> Self {
Key::Named(action)
}
}
impl From<NativeKey> for Key {
#[inline]
fn from(code: NativeKey) -> Self {
Key::Unidentified(code)
}
}
impl<Str> PartialEq<NamedKey> for Key<Str> {
#[inline]
fn eq(&self, rhs: &NamedKey) -> bool {
match self {
Key::Named(ref a) => a == rhs,
_ => false,
}
}
}
impl<Str: PartialEq<str>> PartialEq<str> for Key<Str> {
#[inline]
fn eq(&self, rhs: &str) -> bool {
match self {
Key::Character(ref s) => s == rhs,
_ => false,
}
}
}
impl<Str: PartialEq<str>> PartialEq<&str> for Key<Str> {
#[inline]
fn eq(&self, rhs: &&str) -> bool {
self == *rhs
}
}
impl<Str> PartialEq<NativeKey> for Key<Str> {
#[inline]
fn eq(&self, rhs: &NativeKey) -> bool {
match self {
Key::Unidentified(ref code) => code == rhs,
_ => false,
}
}
}
impl<Str> PartialEq<Key<Str>> for NativeKey {
#[inline]
fn eq(&self, rhs: &Key<Str>) -> bool {
rhs == self
}
}
impl Key<SmolStr> {
/// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on
/// `Key`. All other variants remain unchanged.
pub fn as_ref(&self) -> Key<&str> {
match self {
Key::Named(a) => Key::Named(*a),
Key::Character(ch) => Key::Character(ch.as_str()),
Key::Dead(d) => Key::Dead(*d),
Key::Unidentified(u) => Key::Unidentified(u.clone()),
}
}
}
impl Key {
/// Convert a key to its approximate textual equivalent.
///
/// # Examples
///
/// ```
/// # #[cfg(target_family = "wasm")]
/// # wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
/// # #[cfg_attr(target_family = "wasm", wasm_bindgen_test::wasm_bindgen_test)]
/// # fn main() {
/// use winit_core::keyboard::{Key, NamedKey};
///
/// assert_eq!(Key::Character("a".into()).to_text(), Some("a"));
/// assert_eq!(Key::Named(NamedKey::Enter).to_text(), Some("\r"));
/// assert_eq!(Key::Named(NamedKey::F20).to_text(), None);
/// # }
/// ```
pub fn to_text(&self) -> Option<&str> {
match self {
Key::Named(action) => match action {
NamedKey::Enter => Some("\r"),
NamedKey::Backspace => Some("\x08"),
NamedKey::Tab => Some("\t"),
NamedKey::Escape => Some("\x1b"),
_ => None,
},
Key::Character(ch) => Some(ch.as_str()),
_ => None,
}
}
}
bitflags! {
/// Represents the current logical state of the keyboard modifiers
///
/// Each flag represents a modifier and is set if this modifier is active.
///
/// Note that the modifier key can be physically released with the modifier
/// still being marked as active, as in the case of sticky modifiers.
/// See [`ModifiersKeyState`] for more details on what "sticky" means.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ModifiersState: u32 {
/// The "shift" key.
const SHIFT = 0b100;
/// The "control" key.
const CONTROL = 0b100 << 3;
/// The "alt" key.
const ALT = 0b100 << 6;
/// This is the "windows" key on PC and "command" key on Mac.
const META = 0b100 << 9;
#[deprecated = "use META instead"]
const SUPER = Self::META.bits();
}
}
impl ModifiersState {
/// Returns whether the shift modifier is active.
pub fn shift_key(&self) -> bool {
self.intersects(Self::SHIFT)
}
/// Returns whether the control modifier is active.
pub fn control_key(&self) -> bool {
self.intersects(Self::CONTROL)
}
/// Returns whether the alt modifier is active.
pub fn alt_key(&self) -> bool {
self.intersects(Self::ALT)
}
/// Returns whether the meta modifier is active.
pub fn meta_key(&self) -> bool {
self.intersects(Self::META)
}
}
/// The logical state of the particular modifiers key.
///
/// NOTE: while the modifier can only be in a binary active/inactive state, it might be helpful to
/// note the context re. how its state changes by physical key events.
///
/// `↓` / `↑` denote physical press/release[^1]:
///
/// | Type | Activated | Deactivated | Comment |
/// | ----------------- | :-----------------: | :---------: | ------- |
/// | __Regular__ | `↓` | `↑` | Active while being held |
/// | __Sticky__ | `↓` | `↓` unless lock is enabled<br>`↓`/`↑`[^2] __non__-sticky key | Temporarily "stuck"; other `Sticky` keys have no effect |
/// | __Sticky Locked__ | `↓` <br>if `Sticky` | `↓` | Similar to `Toggle`, but deactivating `↓` turns on `Regular` effect |
/// | __Toggle__ | `↓` | `↓` | `↑` from the activating `↓` has no effect |
///
/// `Sticky` effect avoids the need to press and hold multiple modifiers for a single shortcut and
/// is usually a platform-wide option that affects modifiers _commonly_ used in shortcuts:
/// <kbd>Shift</kbd>, <kbd>Control</kbd>, <kbd>Alt</kbd>, <kbd>Meta</kbd>.
///
/// `Toggle` type is typically a property of a modifier, for example, <kbd>Caps Lock</kbd>.
///
/// These active states are __not__ differentiated here.
///
/// [^1]: For virtual/on-screen keyboards physical press/release can be a mouse click or a finger tap or a voice command.
/// [^2]: platform-dependent
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ModifiersKeyState {
/// The particular modifier is active or logically, but not necessarily physically, pressed.
Pressed,
/// The state of the key is unknown.
///
/// Can include cases when the key is active or logically pressed, for example, when a sticky
/// **Shift** is active, the OS might not preserve information that it was activated by
/// RightShift, so the state of [`ModifiersKeys::RSHIFT`] will be unknown while the state
/// of [`ModifiersState::SHIFT`] will be active.
#[default]
Unknown,
}
// NOTE: the exact modifier key is not used to represent modifiers state in the
// first place due to a fact that modifiers state could be changed without any
// key being pressed and on some platforms like Wayland/X11 which key resulted
// in modifiers change is hidden, also, not that it really matters.
//
// The reason this API is even exposed is mostly to provide a way for users
// to treat modifiers differently based on their position, which is required
// on macOS due to their AltGr/Option situation.
bitflags! {
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ModifiersKeys: u8 {
const LSHIFT = 0b0000_0001;
const RSHIFT = 0b0000_0010;
const LCONTROL = 0b0000_0100;
const RCONTROL = 0b0000_1000;
const LALT = 0b0001_0000;
const RALT = 0b0010_0000;
const LMETA = 0b0100_0000;
const RMETA = 0b1000_0000;
#[deprecated = "use LMETA instead"]
const LSUPER = Self::LMETA.bits();
#[deprecated = "use RMETA instead"]
const RSUPER = Self::RMETA.bits();
}
}

29
winit-core/src/lib.rs Normal file
View File

@@ -0,0 +1,29 @@
//! # Core types for Winit
//!
//! Platform-agnostic types and traits useful when implementing Winit backends,
//! or otherwise interfacing with Winit from library code.
//!
//! See the [`winit`] crate for the full user-facing API.
//!
//! [`winit`]: https://docs.rs/winit
#[macro_use]
pub mod as_any;
pub mod cursor;
#[macro_use]
pub mod error;
pub mod application;
pub mod event;
pub mod event_loop;
pub mod icon;
pub mod keyboard;
pub mod monitor;
pub mod window;
// `Instant` is not actually available on `wasm32-unknown-unknown`, the `std` implementation there
// is a stub. And `wasm32-none` doesn't even have `std`. Instead, we use `web_time::Instant`.
#[cfg(not(all(target_family = "wasm", any(target_os = "unknown", target_os = "none"))))]
pub(crate) use std::time::Instant;
#[cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))]
pub(crate) use web_time::Instant;

View File

@@ -11,8 +11,9 @@ use std::num::{NonZeroU16, NonZeroU32};
use std::ops::Deref;
use std::sync::Arc;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::utils::{impl_dyn_casting, AsAny};
use dpi::{PhysicalPosition, PhysicalSize};
use crate::as_any::{impl_dyn_casting, AsAny};
/// Handle to a monitor.
///
@@ -28,24 +29,13 @@ use crate::utils::{impl_dyn_casting, AsAny};
///
/// ## Platform-specific
///
/// **Web:** A [`MonitorHandle`] created without
#[cfg_attr(
web_platform,
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
/// **Web:** A [`MonitorHandle`] created without `detailed monitor permissions`
/// will always represent the current monitor the browser window is in instead of a specific
/// monitor. See
#[cfg_attr(
web_platform,
doc = "[`MonitorHandleExtWeb::is_detailed()`][crate::platform::web::MonitorHandleExtWeb::is_detailed]"
)]
#[cfg_attr(not(web_platform), doc = "`MonitorHandleExtWeb::is_detailed()`")]
/// to check.
/// monitor.
///
/// [`Window`]: crate::window::Window
#[derive(Debug, Clone)]
pub struct MonitorHandle(pub(crate) Arc<dyn MonitorHandleProvider>);
pub struct MonitorHandle(pub Arc<dyn MonitorHandleProvider>);
impl Deref for MonitorHandle {
type Target = dyn MonitorHandleProvider;
@@ -87,14 +77,10 @@ pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
///
/// Returns `None` if the monitor doesn't exist anymore or the name couldn't be obtained.
///
///
/// ## Platform-specific
///
/// **Web:** Always returns [`None`] without
#[cfg_attr(
web_platform,
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
/// **Web:** Always returns [`None`] without `detailed monitor permissions`.
fn name(&self) -> Option<Cow<'_, str>>;
/// Returns the top-left corner position of the monitor in desktop coordinates.
@@ -105,12 +91,7 @@ pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
///
/// ## Platform-specific
///
/// **Web:** Always returns [`None`] without
#[cfg_attr(
web_platform,
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = "detailed monitor permissions.")]
/// **Web:** Always returns [`None`] without `detailed monitor permissions`.
fn position(&self) -> Option<PhysicalPosition<i32>>;
/// Returns the scale factor of the underlying monitor. To map logical pixels to physical
@@ -118,19 +99,9 @@ pub trait MonitorHandleProvider: AsAny + fmt::Debug + Send + Sync {
///
/// See the [`dpi`] module for more information.
///
/// ## Platform-specific
///
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **Wayland:** May differ from [`Window::scale_factor`].
/// - **Android:** Always returns 1.0.
/// - **Web:** Always returns `0.0` without
#[cfg_attr(
web_platform,
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = " detailed monitor permissions.")]
/// - **Web:** Always returns `0.0` without `detailed_monitor_permissions`.
///
#[rustfmt::skip]
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
fn scale_factor(&self) -> f64;
@@ -161,6 +132,14 @@ pub struct VideoMode {
}
impl VideoMode {
pub fn new(
size: PhysicalSize<u32>,
bit_depth: Option<NonZeroU16>,
refresh_rate_millihertz: Option<NonZeroU32>,
) -> Self {
Self { size, bit_depth, refresh_rate_millihertz }
}
/// Returns the resolution of this video mode. This **must not** be used to create your
/// rendering surface. Use [`Window::surface_size()`] instead.
///
@@ -172,6 +151,13 @@ impl VideoMode {
/// Returns the bit depth of this video mode, as in how many bits you have
/// available per color. This is generally 24 bits or 32 bits on modern
/// systems, depending on whether the alpha channel is counted or not.
///
/// # Platform-specific
///
/// - **macOS**: Video modes do not control the bit depth of the monitor, so this often defaults
/// to 32.
/// - **iOS**: Always returns `None`.
/// - **Wayland**: Always returns `None`.
pub fn bit_depth(&self) -> Option<NonZeroU16> {
self.bit_depth
}

View File

@@ -1,18 +1,19 @@
//! The [`Window`] struct and associated types.
//! The [`Window`] trait and associated types.
use std::fmt;
#[doc(inline)]
pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError};
use bitflags::bitflags;
use cursor_icon::CursorIcon;
use dpi::{
LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use crate::cursor::{BadImage, Cursor, CustomCursor, CustomCursorSource, MAX_CURSOR_SIZE};
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::as_any::AsAny;
use crate::cursor::Cursor;
use crate::error::RequestError;
pub use crate::icon::{BadIcon, Icon};
use crate::icon::Icon;
use crate::monitor::{Fullscreen, MonitorHandle};
use crate::platform_impl::PlatformSpecificWindowAttributes;
use crate::utils::{impl_dyn_casting, AsAny};
/// Identifier of a window. Unique for each window.
///
@@ -46,7 +47,8 @@ impl fmt::Debug for WindowId {
}
/// Attributes used when creating a window.
#[derive(Debug, Clone)]
#[derive(Debug)]
#[non_exhaustive]
pub struct WindowAttributes {
pub surface_size: Option<Size>,
pub min_surface_size: Option<Size>,
@@ -69,53 +71,9 @@ pub struct WindowAttributes {
pub cursor: Cursor,
pub(crate) parent_window: Option<SendSyncRawWindowHandle>,
pub fullscreen: Option<Fullscreen>,
// Platform-specific configuration.
#[allow(dead_code)]
pub(crate) platform_specific: PlatformSpecificWindowAttributes,
pub platform: Option<Box<dyn PlatformWindowAttributes>>,
}
impl Default for WindowAttributes {
#[inline]
fn default() -> WindowAttributes {
WindowAttributes {
surface_size: None,
min_surface_size: None,
max_surface_size: None,
surface_resize_increments: None,
position: None,
resizable: true,
enabled_buttons: WindowButtons::all(),
title: "winit window".to_owned(),
maximized: false,
fullscreen: None,
visible: true,
transparent: false,
blur: false,
decorations: true,
window_level: Default::default(),
window_icon: None,
preferred_theme: None,
content_protected: false,
cursor: Cursor::default(),
parent_window: None,
active: true,
platform_specific: Default::default(),
}
}
}
/// Wrapper for [`rwh_06::RawWindowHandle`] for [`WindowAttributes::parent_window`].
///
/// # Safety
///
/// The user has to account for that when using [`WindowAttributes::with_parent_window()`],
/// which is `unsafe`.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SendSyncRawWindowHandle(pub(crate) rwh_06::RawWindowHandle);
unsafe impl Send for SendSyncRawWindowHandle {}
unsafe impl Sync for SendSyncRawWindowHandle {}
impl WindowAttributes {
/// Get the parent window stored on the attributes.
pub fn parent_window(&self) -> Option<&rwh_06::RawWindowHandle> {
@@ -411,8 +369,94 @@ impl WindowAttributes {
self.parent_window = parent_window.map(SendSyncRawWindowHandle);
self
}
/// Set the platform specific opaque attribute object.
///
/// The interpretation will depend on the underlying backend that will be used.
#[inline]
pub fn with_platform_attributes(mut self, platform: Box<dyn PlatformWindowAttributes>) -> Self {
self.platform = Some(platform);
self
}
}
impl Clone for WindowAttributes {
fn clone(&self) -> Self {
Self {
surface_size: self.surface_size,
min_surface_size: self.min_surface_size,
max_surface_size: self.max_surface_size,
surface_resize_increments: self.surface_resize_increments,
position: self.position,
resizable: self.resizable,
enabled_buttons: self.enabled_buttons,
title: self.title.clone(),
maximized: self.maximized,
visible: self.visible,
transparent: self.transparent,
blur: self.blur,
decorations: self.decorations,
window_icon: self.window_icon.clone(),
preferred_theme: self.preferred_theme,
content_protected: self.content_protected,
window_level: self.window_level,
active: self.active,
cursor: self.cursor.clone(),
parent_window: self.parent_window.clone(),
fullscreen: self.fullscreen.clone(),
platform: self.platform.as_ref().map(|platform| platform.box_clone()),
}
}
}
impl Default for WindowAttributes {
#[inline]
fn default() -> WindowAttributes {
WindowAttributes {
enabled_buttons: WindowButtons::all(),
title: String::from("winit window"),
decorations: true,
resizable: true,
visible: true,
active: true,
surface_resize_increments: Default::default(),
content_protected: Default::default(),
min_surface_size: Default::default(),
max_surface_size: Default::default(),
preferred_theme: Default::default(),
parent_window: Default::default(),
surface_size: Default::default(),
window_level: Default::default(),
window_icon: Default::default(),
transparent: Default::default(),
fullscreen: Default::default(),
maximized: Default::default(),
position: Default::default(),
platform: Default::default(),
cursor: Cursor::default(),
blur: Default::default(),
}
}
}
/// Wrapper for [`rwh_06::RawWindowHandle`] for [`WindowAttributes::parent_window`].
///
/// # Safety
///
/// The user has to account for that when using [`WindowAttributes::with_parent_window()`],
/// which is `unsafe`.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SendSyncRawWindowHandle(pub(crate) rwh_06::RawWindowHandle);
unsafe impl Send for SendSyncRawWindowHandle {}
unsafe impl Sync for SendSyncRawWindowHandle {}
pub trait PlatformWindowAttributes: AsAny + std::fmt::Debug + Send + Sync {
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes>;
}
impl_dyn_casting!(PlatformWindowAttributes);
/// Represents a window.
///
/// The window is closed when dropped.
@@ -540,7 +584,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// APIs and software rendering.
///
/// ```no_run
/// # use winit::window::Window;
/// # use winit_core::window::Window;
/// # fn swap_buffers() {}
/// # fn scope(window: &dyn Window) {
/// // Do the actual drawing with OpenGL.
@@ -583,7 +627,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
///
/// This may also be useful for figuring out the size of the window's decorations (such as
/// buttons, title, etc.), but may also not correspond to that (e.g. if the title bar is made
/// transparent using [`with_titlebar_transparent`] on macOS, or your are drawing window
/// transparent on macOS, or your are drawing window
/// decorations yourself).
///
/// This may be negative.
@@ -592,15 +636,6 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// as the window itself, this simply returns `(0, 0)`.
///
/// [`outer_position`]: Self::outer_position
#[cfg_attr(
macos_platform,
doc = "[`with_titlebar_transparent`]: \
crate::platform::macos::WindowAttributesExtMacOS::with_titlebar_transparent"
)]
#[cfg_attr(
not(macos_platform),
doc = "[`with_titlebar_transparent`]: #only-available-on-macos"
)]
fn surface_position(&self) -> PhysicalPosition<i32>;
/// The position of the top-left hand corner of the window relative to the top-left hand corner
@@ -625,8 +660,8 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// This automatically un-maximizes the window if it's maximized.
///
/// ```no_run
/// # use winit::dpi::{LogicalPosition, PhysicalPosition};
/// # use winit::window::Window;
/// # use dpi::{LogicalPosition, PhysicalPosition};
/// # use winit_core::window::Window;
/// # fn scope(window: &dyn Window) {
/// // Specify the position in logical dimensions like this:
/// window.set_outer_position(LogicalPosition::new(400.0, 200.0).into());
@@ -683,8 +718,8 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// The request could automatically un-maximize the window if it's maximized.
///
/// ```no_run
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::Window;
/// # use dpi::{LogicalSize, PhysicalSize};
/// # use winit_core::window::Window;
/// # fn scope(window: &dyn Window) {
/// // Specify the size in logical dimensions like this:
/// let _ = window.request_surface_size(LogicalSize::new(400.0, 200.0).into());
@@ -735,12 +770,12 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
///
/// - **Android / Orbital / Wayland / Windows / X11:** Unimplemented, returns `(0, 0, 0, 0)`.
///
/// ## Examples
/// ## Example
///
/// Convert safe area insets to a size and a position.
///
/// ```
/// use winit::dpi::{PhysicalPosition, PhysicalSize};
/// use dpi::{PhysicalPosition, PhysicalSize};
///
/// # let surface_size = dpi::PhysicalSize::new(0, 0);
/// # #[cfg(requires_window)]
@@ -760,8 +795,8 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// Sets a minimum dimensions of the window's surface.
///
/// ```no_run
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::Window;
/// # use dpi::{LogicalSize, PhysicalSize};
/// # use winit_core::window::Window;
/// # fn scope(window: &dyn Window) {
/// // Specify the size in logical dimensions like this:
/// window.set_min_surface_size(Some(LogicalSize::new(400.0, 200.0).into()));
@@ -779,8 +814,8 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// Sets a maximum dimensions of the window's surface.
///
/// ```no_run
/// # use winit::dpi::{LogicalSize, PhysicalSize};
/// # use winit::window::Window;
/// # use dpi::{LogicalSize, PhysicalSize};
/// # use winit_core::window::Window;
/// # fn scope(window: &dyn Window) {
/// // Specify the size in logical dimensions like this:
/// window.set_max_surface_size(Some(LogicalSize::new(400.0, 200.0).into()));
@@ -963,14 +998,8 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// The dock and the menu bar are disabled in exclusive fullscreen mode.
/// - **Wayland:** Does not support exclusive fullscreen mode and will no-op a request.
/// - **Windows:** Screen saver is disabled in fullscreen mode.
/// - **Android / Orbital:** Unsupported.
/// - **Web:** Passing a [`MonitorHandle`] or [`VideoMode`] that was not created with
#[cfg_attr(
web_platform,
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]"
)]
#[cfg_attr(not(web_platform), doc = " detailed monitor permissions")]
/// or calling without a [transient activation] does nothing.
/// - **Web:** Passing a [`MonitorHandle`] or [`VideoMode`] that was not created with detailed
/// monitor permissions or calling without a [transient activation] does nothing.
///
/// [transient activation]: https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation
/// [`VideoMode`]: crate::monitor::VideoMode
@@ -1015,18 +1044,20 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// Sets the window icon.
///
/// On Windows and X11, this is typically the small icon in the top-left
/// On Windows, Wayland and X11, this is typically the small icon in the top-left
/// corner of the titlebar.
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Wayland / macOS / Orbital:** Unsupported.
/// - **iOS / Android / Web / / macOS / Orbital:** Unsupported.
///
/// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
///
/// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM.
/// That said, it's usually in the same ballpark as on Windows.
///
/// - **Wayland:** The compositor needs to implement `xdg_toplevel_icon`.
fn set_window_icon(&self, window_icon: Option<Icon>);
/// Set the IME cursor editing area, where the `position` is the top left corner of that area
@@ -1045,8 +1076,8 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// ## Example
///
/// ```no_run
/// # use winit::dpi::{LogicalPosition, PhysicalPosition, LogicalSize, PhysicalSize};
/// # use winit::window::Window;
/// # use dpi::{LogicalPosition, PhysicalPosition, LogicalSize, PhysicalSize};
/// # use winit_core::window::Window;
/// # fn scope(window: &dyn Window) {
/// // Specify the position in logical dimensions like this:
/// window.set_ime_cursor_area(
@@ -1068,13 +1099,20 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
///
/// [chinese]: https://support.apple.com/guide/chinese-input-method/use-the-candidate-window-cim12992/104/mac/12.0
/// [japanese]: https://support.apple.com/guide/japanese-input-method/use-the-candidate-window-jpim10262/6.3/mac/12.0
fn set_ime_cursor_area(&self, position: Position, size: Size);
#[deprecated = "use Window::request_ime_update instead"]
fn set_ime_cursor_area(&self, position: Position, size: Size) {
if self.ime_capabilities().map(|caps| caps.cursor_area()).unwrap_or(false) {
let _ = self.request_ime_update(ImeRequest::Update(
ImeRequestData::default().with_cursor_area(position, size),
));
}
}
/// Sets whether the window should get IME events
///
/// When IME is allowed, the window will receive [`Ime`] events, and during the
/// preedit phase the window will NOT get [`KeyboardInput`] events. The window
/// should allow IME while it is expecting text input.
/// should allow IME when it is expecting text input.
///
/// When IME is not allowed, the window won't receive [`Ime`] events, and will
/// receive [`KeyboardInput`] events for every keypress instead. Not allowing
@@ -1092,14 +1130,97 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
///
/// [`Ime`]: crate::event::WindowEvent::Ime
/// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput
fn set_ime_allowed(&self, allowed: bool);
#[deprecated = "use Window::request_ime_update instead"]
fn set_ime_allowed(&self, allowed: bool) {
let action = if allowed {
let position = LogicalPosition::new(0, 0);
let size = LogicalSize::new(0, 0);
let ime_caps = ImeCapabilities::new().with_purpose().with_cursor_area();
let request_data = ImeRequestData {
purpose: Some(ImePurpose::Normal),
// WARNING: there's nothing sensible to use here by default.
cursor_area: Some((position.into(), size.into())),
..ImeRequestData::default()
};
// Enable all capabilities to reflect the old behavior.
ImeRequest::Enable(ImeEnableRequest::new(ime_caps, request_data).unwrap())
} else {
ImeRequest::Disable
};
let _ = self.request_ime_update(action);
}
/// Sets the IME purpose for the window using [`ImePurpose`].
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Windows / X11 / macOS / Orbital:** Unsupported.
fn set_ime_purpose(&self, purpose: ImePurpose);
#[deprecated = "use Window::request_ime_update instead"]
fn set_ime_purpose(&self, purpose: ImePurpose) {
if self.ime_capabilities().map(|caps| caps.purpose()).unwrap_or(false) {
let _ = self.request_ime_update(ImeRequest::Update(ImeRequestData {
purpose: Some(purpose),
..ImeRequestData::default()
}));
}
}
/// Atomically apply request to IME.
///
/// For details consult [`ImeRequest`] and [`ImeCapabilities`].
///
/// Input methods allows the user to compose text without using a keyboard. Requesting one may
/// be beneficial for touch screen environments or ones where, for example, East Asian scripts
/// may be entered.
///
/// If the focus within the application changes from one logical text input area to another, the
/// application should inform the IME of the switch by disabling the IME and enabling it again
/// in the other area.
///
/// IME is **not** enabled by default.
///
/// ## Example
///
/// ```no_run
/// # use dpi::{Position, Size};
/// # use winit_core::window::{Window, ImePurpose, ImeRequest, ImeCapabilities, ImeRequestData, ImeEnableRequest};
/// # fn scope(window: &dyn Window, cursor_pos: Position, cursor_size: Size) {
/// // Clear previous state by switching off IME
/// window.request_ime_update(ImeRequest::Disable).expect("Disable cannot fail");
///
/// let ime_caps = ImeCapabilities::new().with_cursor_area().with_purpose();
/// let request_data = ImeRequestData::default()
/// .with_purpose(ImePurpose::Normal)
/// .with_cursor_area(cursor_pos, cursor_size);
/// let enable_ime = ImeEnableRequest::new(ime_caps, request_data.clone()).unwrap();
/// window.request_ime_update(ImeRequest::Enable(enable_ime)).expect("Enabling may fail if IME is not supported");
///
/// // Update the current state
/// window
/// .request_ime_update(ImeRequest::Update(request_data.clone()))
/// .expect("will fail if it's not enabled or ime is not supported");
///
/// // Update the current state
/// window
/// .request_ime_update(ImeRequest::Update(
/// request_data.with_cursor_area(cursor_pos, cursor_size),
/// ))
/// .expect("Can fail - we didn't submit a cursor position initially");
///
/// // Switch off IME
/// window.request_ime_update(ImeRequest::Disable).expect("Disable cannot fail");
/// # }
/// ```
fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError>;
/// Return enabled by the client [`ImeCapabilities`] for this window.
///
/// When the IME is not yet enabled it'll return `None`.
///
/// By default IME is disabled, thus will return `None`.
fn ime_capabilities(&self) -> Option<ImeCapabilities>;
/// Brings the window to the front and sets input focus. Has no effect if the window is
/// already in focus, minimized, or not visible.
@@ -1188,8 +1309,8 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// Changes the position of the cursor in window coordinates.
///
/// ```no_run
/// # use winit::dpi::{LogicalPosition, PhysicalPosition};
/// # use winit::window::Window;
/// # use dpi::{LogicalPosition, PhysicalPosition};
/// # use winit_core::window::Window;
/// # fn scope(window: &dyn Window) {
/// // Specify the position in logical dimensions like this:
/// window.set_cursor_position(LogicalPosition::new(400.0, 200.0).into());
@@ -1207,12 +1328,12 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// Set grabbing [mode][CursorGrabMode] on the cursor preventing it from leaving the window.
///
/// # Example
/// ## Example
///
/// First try confining the cursor, and if that fails, try locking it instead.
///
/// ```no_run
/// # use winit::window::{CursorGrabMode, Window};
/// # use winit_core::window::{CursorGrabMode, Window};
/// # fn scope(window: &dyn Window) {
/// window
/// .set_cursor_grab(CursorGrabMode::Confined)
@@ -1292,17 +1413,6 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// This is the same as [`ActiveEventLoop::available_monitors`], and is provided for
/// convenience.
///
///
/// ## Platform-specific
///
/// **Web:** Only returns the current monitor without
#[cfg_attr(
web_platform,
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform,)), doc = "detailed monitor permissions.")]
///
#[rustfmt::skip]
/// [`ActiveEventLoop::available_monitors`]: crate::event_loop::ActiveEventLoop::available_monitors
fn available_monitors(&self) -> Box<dyn Iterator<Item = MonitorHandle>>;
@@ -1315,14 +1425,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
/// ## Platform-specific
///
/// - **Wayland:** Always returns `None`.
/// - **Web:** Always returns `None` without
#[cfg_attr(
web_platform,
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(web_platform), doc = " detailed monitor permissions.")]
///
#[rustfmt::skip]
/// [`ActiveEventLoop::primary_monitor`]: crate::event_loop::ActiveEventLoop::primary_monitor
fn primary_monitor(&self) -> Option<MonitorHandle>;
@@ -1333,14 +1436,6 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle;
}
impl dyn Window + '_ {
/// Create a new [`WindowAttributes`] which allows modifying the window's attributes before
/// creation.
pub fn default_attributes() -> WindowAttributes {
WindowAttributes::default()
}
}
impl_dyn_casting!(Window);
impl PartialEq for dyn Window + '_ {
@@ -1528,6 +1623,366 @@ impl Default for ImePurpose {
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum ImeSurroundingTextError {
/// Text exceeds 4000 bytes
TextTooLong,
/// Cursor not on a code point boundary, or past the end of text.
CursorBadPosition,
/// Anchor not on a code point boundary, or past the end of text.
AnchorBadPosition,
}
/// Defines the text surrounding the caret
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ImeSurroundingText {
/// An excerpt of the text present in the text input field, excluding preedit.
text: String,
/// The position of the caret, in bytes from the beginning of the string
cursor: usize,
/// The position of the other end of selection, in bytes.
/// With no selection, it should be the same as the cursor.
anchor: usize,
}
impl ImeSurroundingText {
/// The maximum size of the text excerpt.
pub const MAX_TEXT_BYTES: usize = 4000;
/// Defines the text surrounding the cursor and the selection within it.
///
/// `text`: An excerpt of the text present in the text input field, excluding preedit.
/// It must be limited to 4000 bytes due to backend constraints.
/// `cursor`: The position of the caret, in bytes from the beginning of the string.
/// `anchor: The position of the other end of selection, in bytes.
/// With no selection, it should be the same as the cursor.
///
/// This may fail if the byte indices don't fall on code point boundaries,
/// or if the text is too long.
///
/// ## Examples:
///
/// A text field containing `foo|bar` where `|` denotes the caret would correspond to a value
/// obtained by:
///
/// ```
/// # use winit_core::window::ImeSurroundingText;
/// let s = ImeSurroundingText::new("foobar".into(), 3, 3).unwrap();
/// ```
///
/// Because preedit is excluded from the text string, a text field containing `foo[baz|]bar`
/// where `|` denotes the caret and [baz|] is the preedit would be created in exactly the same
/// way.
pub fn new(
text: String,
cursor: usize,
anchor: usize,
) -> Result<Self, ImeSurroundingTextError> {
let text = if text.len() < 4000 {
text
} else {
return Err(ImeSurroundingTextError::TextTooLong);
};
let cursor = if text.is_char_boundary(cursor) && cursor <= text.len() {
cursor
} else {
return Err(ImeSurroundingTextError::CursorBadPosition);
};
let anchor = if text.is_char_boundary(anchor) && anchor <= text.len() {
anchor
} else {
return Err(ImeSurroundingTextError::AnchorBadPosition);
};
Ok(Self { text, cursor, anchor })
}
/// Consumes the object, releasing the text string only.
/// Use this call in the backend to avoid an extra clone when submitting the surrounding text.
pub fn into_text(self) -> String {
self.text
}
pub fn text(&self) -> &str {
&self.text
}
pub fn cursor(&self) -> usize {
self.cursor
}
pub fn anchor(&self) -> usize {
self.anchor
}
}
/// Request to send to IME.
#[derive(Debug, PartialEq, Clone)]
pub enum ImeRequest {
/// Enable the IME with the [`ImeCapabilities`] and [`ImeRequestData`] as initial state. When
/// the [`ImeRequestData`] is **not** matching capabilities fully, the default values will be
/// used instead.
///
/// **Requesting to update data matching not enabled capabilities will result in update
/// being ignored.** The winit backend in such cases is recommended to log a warning. This
/// appiles to both [`ImeRequest::Enable`] and [`ImeRequest::Update`]. For details on
/// capabilities refer to [`ImeCapabilities`].
///
/// To update the [`ImeCapabilities`], the IME must be disabled and then re-enabled.
Enable(ImeEnableRequest),
/// Update the state of already enabled IME. Issuing this request before [`ImeRequest::Enable`]
/// will result in error.
Update(ImeRequestData),
/// Disable the IME.
///
/// **The disable request can not fail**.
Disable,
}
/// Initial IME request.
#[derive(Debug, Clone, PartialEq)]
pub struct ImeEnableRequest {
capabilities: ImeCapabilities,
request_data: ImeRequestData,
}
impl ImeEnableRequest {
/// Create request for the [`ImeRequest::Enable`]
///
/// This will return [`None`] if some capability was requested but its initial value was not
/// set by the user or value was set by the user, but capability not requested.
pub fn new(capabilities: ImeCapabilities, request_data: ImeRequestData) -> Option<Self> {
if capabilities.cursor_area() ^ request_data.cursor_area.is_some() {
return None;
}
if capabilities.purpose() ^ request_data.purpose.is_some() {
return None;
}
if capabilities.surrounding_text() ^ request_data.surrounding_text.is_some() {
return None;
}
Some(Self { capabilities, request_data })
}
/// [`ImeCapabilities`] to enable.
pub const fn capabilities(&self) -> &ImeCapabilities {
&self.capabilities
}
/// Request data attached to request.
pub const fn request_data(&self) -> &ImeRequestData {
&self.request_data
}
/// Destruct [`ImeEnableRequest`] into its raw parts.
pub fn into_raw(self) -> (ImeCapabilities, ImeRequestData) {
(self.capabilities, self.request_data)
}
}
/// IME capabilities supported by client.
///
/// For example, if the client doesn't support [`ImeCapabilities::cursor_area()`], then not enabling
/// it will make IME hide the popup window instead of placing it arbitrary over the
/// client's window surface.
///
/// When the capability is not enabled or not supported by the IME, trying to update its'
/// corresponding data with [`ImeRequest`] will be ignored.
///
/// New capabilities may be added to this struct in the future.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ImeCapabilities(ImeCapabilitiesFlags);
impl ImeCapabilities {
/// Returns a new empty set of capabilities.
pub fn new() -> Self {
Self::default()
}
/// Marks `purpose` as supported.
///
/// For more details see [`ImeRequestData::with_purpose`].
pub const fn with_purpose(self) -> Self {
Self(self.0.union(ImeCapabilitiesFlags::PURPOSE))
}
/// Marks `purpose` as unsupported.
///
/// For more details see [`ImeRequestData::with_purpose`].
pub const fn without_purpose(self) -> Self {
Self(self.0.difference(ImeCapabilitiesFlags::PURPOSE))
}
/// Returns `true` if `purpose` is supported.
pub const fn purpose(&self) -> bool {
self.0.contains(ImeCapabilitiesFlags::PURPOSE)
}
/// Marks `cursor_area` as supported.
///
/// For more details see [`ImeRequestData::with_cursor_area`].
pub const fn with_cursor_area(self) -> Self {
Self(self.0.union(ImeCapabilitiesFlags::CURSOR_AREA))
}
/// Marks `cursor_area` as unsupported.
///
/// For more details see [`ImeRequestData::with_cursor_area`].
pub const fn without_cursor_area(self) -> Self {
Self(self.0.difference(ImeCapabilitiesFlags::CURSOR_AREA))
}
/// Returns `true` if `cursor_area` is supported.
pub const fn cursor_area(&self) -> bool {
self.0.contains(ImeCapabilitiesFlags::CURSOR_AREA)
}
/// Marks `surrounding_text` as supported.
///
/// For more details see [`ImeRequestData::with_surrounding_text`].
pub const fn with_surrounding_text(self) -> Self {
Self(self.0.union(ImeCapabilitiesFlags::SURROUNDING_TEXT))
}
/// Marks `surrounding_text` as unsupported.
///
/// For more details see [`ImeRequestData::with_surrounding_text`].
pub const fn without_surrounding_text(self) -> Self {
Self(self.0.difference(ImeCapabilitiesFlags::SURROUNDING_TEXT))
}
/// Returns `true` if `surrounding_text` is supported.
pub const fn surrounding_text(&self) -> bool {
self.0.contains(ImeCapabilitiesFlags::SURROUNDING_TEXT)
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub(crate) struct ImeCapabilitiesFlags : u8 {
/// Client supports setting IME purpose.
const PURPOSE = 1 << 0;
/// Client supports reporting cursor area for IME popup to
/// appear.
const CURSOR_AREA = 1 << 1;
/// Client supports reporting the text around the caret
const SURROUNDING_TEXT = 1 << 2;
}
}
/// The [`ImeRequest`] data to communicate to system's IME.
///
/// This applies multiple IME state properties at once.
/// Fields set to `None` are not updated and the previously sent
/// value is reused.
#[non_exhaustive]
#[derive(Debug, PartialEq, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ImeRequestData {
/// Text input purpose.
///
/// To support updating it, enable [`ImeCapabilities::PURPOSE`].
pub purpose: Option<ImePurpose>,
/// The IME cursor area which should not be covered by the input method popup.
///
/// To support updating it, enable [`ImeCapabilities::CURSOR_AREA`].
pub cursor_area: Option<(Position, Size)>,
/// The text surrounding the caret
///
/// To support updating it, enable [`ImeCapabilities::SURROUNDING_TEXT`].
pub surrounding_text: Option<ImeSurroundingText>,
}
impl ImeRequestData {
/// Sets the purpose hint of the current text input.
pub fn with_purpose(self, purpose: ImePurpose) -> Self {
Self { purpose: Some(purpose), ..self }
}
/// Sets the IME cursor editing area.
///
/// The `position` is the top left corner of that area
/// in surface coordinates and `size` is the size of this area starting from the position. An
/// example of such area could be a input field in the UI or line in the editor.
///
/// The windowing system could place a candidate box close to that area, but try to not obscure
/// the specified area, so the user input to it stays visible.
///
/// The candidate box is the window / popup / overlay that allows you to select the desired
/// characters. The look of this box may differ between input devices, even on the same
/// platform.
///
/// (Apple's official term is "candidate window", see their [chinese] and [japanese] guides).
///
/// ## Example
///
/// ```no_run
/// # use dpi::{LogicalPosition, PhysicalPosition, LogicalSize, PhysicalSize};
/// # use winit_core::window::ImeRequestData;
/// # fn scope(ime_request_data: ImeRequestData) {
/// // Specify the position in logical dimensions like this:
/// let ime_request_data = ime_request_data.with_cursor_area(
/// LogicalPosition::new(400.0, 200.0).into(),
/// LogicalSize::new(100, 100).into(),
/// );
///
/// // Or specify the position in physical dimensions like this:
/// let ime_request_data = ime_request_data.with_cursor_area(
/// PhysicalPosition::new(400, 200).into(),
/// PhysicalSize::new(100, 100).into(),
/// );
/// # }
/// ```
///
/// ## Platform-specific
///
/// - **iOS / Android / Web / Orbital:** Unsupported.
///
/// [chinese]: https://support.apple.com/guide/chinese-input-method/use-the-candidate-window-cim12992/104/mac/12.0
/// [japanese]: https://support.apple.com/guide/japanese-input-method/use-the-candidate-window-jpim10262/6.3/mac/12.0
pub fn with_cursor_area(self, position: Position, size: Size) -> Self {
Self { cursor_area: Some((position, size)), ..self }
}
/// Describes the text surrounding the caret.
///
/// The IME can then continue providing suggestions for the continuation of the existing text,
/// as well as can erase text more accurately, for example glyphs composed of multiple code
/// points.
pub fn with_surrounding_text(self, surrounding_text: ImeSurroundingText) -> Self {
Self { surrounding_text: Some(surrounding_text), ..self }
}
}
/// Error from sending request to IME with
/// [`Window::request_ime_update`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ImeRequestError {
/// IME is not yet enabled.
NotEnabled,
/// IME is already enabled.
AlreadyEnabled,
/// Not supported.
NotSupported,
}
impl fmt::Display for ImeRequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ImeRequestError::NotEnabled => write!(f, "ime is not enabled."),
ImeRequestError::AlreadyEnabled => write!(f, "ime is already enabled."),
ImeRequestError::NotSupported => write!(f, "ime is not supported."),
}
}
}
impl std::error::Error for ImeRequestError {}
/// An opaque token used to activate the [`Window`].
///
/// [`Window`]: crate::window::Window
@@ -1546,12 +2001,7 @@ impl ActivationToken {
/// only result in the side effect of the operation involving it being ignored (e.g. window
/// won't get focused automatically), but won't yield any errors.
///
/// To obtain a valid token, use
#[cfg_attr(
any(x11_platform, wayland_platform),
doc = " [`request_activation_token`](crate::platform::startup_notify::WindowExtStartupNotify::request_activation_token)."
)]
#[cfg_attr(not(any(x11_platform, wayland_platform)), doc = " `request_activation_token`.")]
/// To obtain a valid token consult the backend implementation.
pub fn from_raw(token: String) -> Self {
Self { token }
}
@@ -1560,4 +2010,91 @@ impl ActivationToken {
pub fn into_raw(self) -> String {
self.token
}
/// Get a reference to a raw token.
pub fn as_raw(&self) -> &str {
&self.token
}
}
#[cfg(test)]
mod tests {
use dpi::{LogicalPosition, LogicalSize, Position, Size};
use super::{
ImeCapabilities, ImeEnableRequest, ImeRequestData, ImeSurroundingText,
ImeSurroundingTextError,
};
use crate::window::ImePurpose;
#[test]
fn ime_initial_request_caps_match() {
let position: Position = LogicalPosition::new(0, 0).into();
let size: Size = LogicalSize::new(0, 0).into();
assert!(ImeEnableRequest::new(
ImeCapabilities::new().with_cursor_area(),
ImeRequestData::default()
)
.is_none());
assert!(ImeEnableRequest::new(
ImeCapabilities::new().with_purpose(),
ImeRequestData::default()
)
.is_none());
assert!(ImeEnableRequest::new(
ImeCapabilities::new().with_cursor_area(),
ImeRequestData::default().with_purpose(ImePurpose::Normal)
)
.is_none());
assert!(ImeEnableRequest::new(
ImeCapabilities::new(),
ImeRequestData::default()
.with_purpose(ImePurpose::Normal)
.with_cursor_area(position, size)
)
.is_none());
assert!(ImeEnableRequest::new(
ImeCapabilities::new().with_cursor_area(),
ImeRequestData::default()
.with_purpose(ImePurpose::Normal)
.with_cursor_area(position, size)
)
.is_none());
assert!(ImeEnableRequest::new(
ImeCapabilities::new().with_cursor_area(),
ImeRequestData::default().with_cursor_area(position, size)
)
.is_some());
assert!(ImeEnableRequest::new(
ImeCapabilities::new().with_purpose().with_cursor_area(),
ImeRequestData::default()
.with_purpose(ImePurpose::Normal)
.with_cursor_area(position, size)
)
.is_some());
let text: &[u8] = ['a' as u8; 8000].as_slice();
let text = std::str::from_utf8(text).unwrap();
assert_eq!(
ImeSurroundingText::new(text.into(), 0, 0),
Err(ImeSurroundingTextError::TextTooLong),
);
assert_eq!(
ImeSurroundingText::new("short".into(), 110, 0),
Err(ImeSurroundingTextError::CursorBadPosition),
);
assert_eq!(
ImeSurroundingText::new("граница".into(), 1, 0),
Err(ImeSurroundingTextError::CursorBadPosition),
);
}
}

25
winit-orbital/Cargo.toml Normal file
View File

@@ -0,0 +1,25 @@
[package]
description = "Winit's Orbital/Redox backend"
documentation = "https://docs.rs/winit-orbital"
edition.workspace = true
license.workspace = true
name = "winit-orbital"
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
orbclient.workspace = true
redox_syscall.workspace = true

1
winit-orbital/LICENSE Symbolic link
View File

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

1
winit-orbital/README.md Symbolic link
View File

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

View File

@@ -1,6 +1,8 @@
use std::cell::Cell;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc, Mutex};
use std::task::Waker;
use std::time::Instant;
use std::{iter, mem, slice};
@@ -10,24 +12,23 @@ use orbclient::{
MoveEvent, QuitEvent, ResizeEvent, ScrollEvent, TextInputEvent,
};
use smol_str::SmolStr;
use super::{PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket, WindowProperties};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event::{self, Ime, Modifiers, StartCause};
use crate::event_loop::{
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
use winit_core::event::{self, Ime, Modifiers, StartCause};
use winit_core::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider,
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::keyboard::{
use winit_core::keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NamedKey, NativeKey, NativeKeyCode,
PhysicalKey,
};
use crate::platform_impl::Window;
use crate::window::{
CustomCursor as RootCustomCursor, CustomCursorSource, Theme, Window as CoreWindow, WindowId,
};
use winit_core::window::{Theme, Window as CoreWindow, WindowId};
use crate::window::Window;
use crate::{RedoxSocket, TimeSocket, WindowProperties};
fn convert_scancode(scancode: u8) -> (PhysicalKey, Option<NamedKey>) {
// Key constants from https://docs.rs/orbclient/latest/orbclient/event/index.html
@@ -268,7 +269,7 @@ impl EventState {
pressed_mods
.set(ModifiersKeys::RMETA, self.keyboard.contains(KeyboardModifierState::RMETA));
Modifiers { state, pressed_mods }
Modifiers::new(state, pressed_mods)
}
}
@@ -276,11 +277,16 @@ impl EventState {
pub struct EventLoop {
windows: Vec<(Arc<RedoxSocket>, EventState)>,
window_target: ActiveEventLoop,
user_events_receiver: mpsc::Receiver<()>,
}
impl EventLoop {
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
pub fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
static EVENT_LOOP_CREATED: AtomicBool = AtomicBool::new(false);
if EVENT_LOOP_CREATED.swap(true, Ordering::Relaxed) {
// For better cross-platformness.
return Err(EventLoopError::RecreationAttempt);
}
// NOTE: Create a channel which can hold only one event to automatically _squash_ user
// events.
let (user_events_sender, user_events_receiver) = mpsc::sync_channel(1);
@@ -571,7 +577,7 @@ impl EventLoop {
i += 1;
}
while self.user_events_receiver.try_recv().is_ok() {
if self.wake_up.swap(false, Ordering::Relaxed) {
app.proxy_wake_up(&self.window_target);
}
@@ -660,17 +666,26 @@ impl EventLoop {
#[derive(Debug)]
pub struct EventLoopProxy {
user_events_sender: mpsc::SyncSender<()>,
pub(super) wake_socket: TimeSocket,
waker: Waker,
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
// When we fail to send the event it means that we haven't woken up to read the previous
// event.
if self.user_events_sender.try_send(()).is_ok() {
self.wake_socket.wake().unwrap();
}
self.waker.wake_by_ref();
}
fn into_waker(self) -> Waker {
self.waker
}
}
impl std::task::Wake for TimeSocket {
fn wake(self: Arc<TimeSocket>) {
TimeSocket::wake(&*self).unwrap();
}
fn wake_by_ref(self: &Arc<TimeSocket>) {
TimeSocket::wake(&**self).unwrap();
}
}
@@ -694,19 +709,16 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn create_window(
&self,
window_attributes: crate::window::WindowAttributes,
window_attributes: winit_core::window::WindowAttributes,
) -> Result<Box<dyn CoreWindow>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
_: CustomCursorSource,
) -> Result<RootCustomCursor, RequestError> {
fn create_custom_cursor(&self, _: CustomCursorSource) -> Result<CustomCursor, RequestError> {
Err(NotSupportedError::new("create_custom_cursor is not supported").into())
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = crate::monitor::MonitorHandle>> {
fn available_monitors(&self) -> Box<dyn Iterator<Item = winit_core::monitor::MonitorHandle>> {
Box::new(iter::empty())
}
@@ -714,7 +726,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
None
}
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
fn primary_monitor(&self) -> Option<winit_core::monitor::MonitorHandle> {
None
}
@@ -761,3 +773,6 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct PlatformSpecificEventLoopAttributes {}

View File

@@ -1,12 +1,20 @@
#![cfg(target_os = "redox")]
//! # Orbital / Redox OS
//!
//! Redox OS has some functionality not yet present that will be implemented
//! when its orbital display server provides it.
use std::{fmt, str};
pub(crate) use self::event_loop::{ActiveEventLoop, EventLoop};
pub use self::window::Window;
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
mod event_loop;
mod window;
macro_rules! os_error {
($error:expr) => {{
winit_core::error::OsError::new(line!(), file!(), $error)
}};
}
pub mod event_loop;
pub mod window;
#[derive(Debug)]
struct RedoxSocket {
@@ -15,7 +23,7 @@ struct RedoxSocket {
impl RedoxSocket {
fn event() -> syscall::Result<Self> {
Self::open_raw("event:")
Self::open_raw("/scheme/event")
}
fn orbital(properties: &WindowProperties<'_>) -> syscall::Result<Self> {
@@ -62,11 +70,11 @@ impl Drop for RedoxSocket {
}
#[derive(Debug)]
pub struct TimeSocket(RedoxSocket);
struct TimeSocket(RedoxSocket);
impl TimeSocket {
fn open() -> syscall::Result<Self> {
RedoxSocket::open_raw("time:4").map(Self)
RedoxSocket::open_raw("/scheme/time/4").map(Self)
}
// Read current time.
@@ -88,12 +96,6 @@ impl TimeSocket {
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowAttributes;
struct WindowProperties<'a> {
flags: &'a str,
x: i32,
@@ -121,7 +123,7 @@ impl fmt::Display for WindowProperties<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"orbital:{}/{}/{}/{}/{}/{}",
"/scheme/orbital/{}/{}/{}/{}/{}/{}",
self.flags, self.x, self.y, self.w, self.h, self.title
)
}

View File

@@ -2,13 +2,14 @@ use std::collections::VecDeque;
use std::iter;
use std::sync::{Arc, Mutex};
use super::event_loop::EventLoopProxy;
use super::{ActiveEventLoop, RedoxSocket, WindowProperties};
use crate::cursor::Cursor;
use crate::dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use crate::error::{NotSupportedError, RequestError};
use crate::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use crate::window::{self, ImePurpose, Window as CoreWindow, WindowId};
use dpi::{PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
use winit_core::cursor::Cursor;
use winit_core::error::{NotSupportedError, RequestError};
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
use winit_core::window::{self, Window as CoreWindow, WindowId};
use crate::event_loop::{ActiveEventLoop, EventLoopProxy};
use crate::{RedoxSocket, WindowProperties};
// These values match the values uses in the `window_new` function in orbital:
// https://gitlab.redox-os.org/redox-os/orbital/-/blob/master/src/scheme.rs
@@ -160,6 +161,10 @@ impl CoreWindow for Window {
WindowId::from_raw(self.window_socket.fd)
}
fn ime_capabilities(&self) -> Option<window::ImeCapabilities> {
None
}
#[inline]
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
None
@@ -351,16 +356,11 @@ impl CoreWindow for Window {
}
#[inline]
fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
fn set_window_icon(&self, _window_icon: Option<winit_core::icon::Icon>) {}
#[inline]
fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
#[inline]
fn set_ime_allowed(&self, _allowed: bool) {}
#[inline]
fn set_ime_purpose(&self, _purpose: ImePurpose) {}
fn request_ime_update(&self, _: window::ImeRequest) -> Result<(), window::ImeRequestError> {
Err(window::ImeRequestError::NotSupported)
}
#[inline]
fn focus_window(&self) {}
@@ -485,3 +485,6 @@ impl Drop for Window {
self.event_loop_proxy.wake_socket.wake().unwrap();
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PlatformSpecificWindowAttributes;

76
winit-uikit/Cargo.toml Normal file
View File

@@ -0,0 +1,76 @@
[package]
description = "Winit's UIKit (iOS/tvOS/visionOS) backend"
documentation = "https://docs.rs/winit-uikit"
edition.workspace = true
license.workspace = true
name = "winit-uikit"
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-common = { workspace = true, features = ["core-foundation", "event-handler"] }
winit-core.workspace = true
# Platform-specific
[target.'cfg(target_vendor = "apple")'.dependencies]
block2.workspace = true
dispatch2.workspace = true
objc2.workspace = true
objc2-core-foundation = { workspace = true, features = [
"std",
"CFCGTypes",
"CFBase",
"CFRunLoop",
"CFString",
] }
objc2-foundation = { workspace = true, features = [
"std",
"block2",
"objc2-core-foundation",
"NSArray",
"NSEnumerator",
"NSGeometry",
"NSObjCRuntime",
"NSOperation",
"NSString",
"NSThread",
"NSSet",
] }
objc2-ui-kit = { workspace = true, features = [
"std",
"objc2-core-foundation",
"UIApplication",
"UIDevice",
"UIEvent",
"UIGeometry",
"UIGestureRecognizer",
"UITextInput",
"UITextInputTraits",
"UIOrientation",
"UIPanGestureRecognizer",
"UIPinchGestureRecognizer",
"UIResponder",
"UIRotationGestureRecognizer",
"UIScreen",
"UIScreenMode",
"UITapGestureRecognizer",
"UITouch",
"UITraitCollection",
"UIView",
"UIViewController",
"UIWindow",
] }
[package.metadata.docs.rs]
all-features = true
targets = ["aarch64-apple-ios", "aarch64-apple-ios-macabi"]

1
winit-uikit/LICENSE Symbolic link
View File

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

1
winit-uikit/README.md Symbolic link
View File

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

View File

@@ -1,13 +1,14 @@
#![deny(unused_results)]
use std::cell::{OnceCell, RefCell, RefMut};
use std::cell::{Cell, OnceCell};
use std::collections::HashSet;
use std::os::raw::c_void;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::{mem, ptr};
use std::{fmt, ptr};
use dispatch2::MainThreadBound;
use dpi::PhysicalSize;
use objc2::rc::Retained;
use objc2::MainThreadMarker;
use objc2_core_foundation::{
@@ -15,16 +16,15 @@ use objc2_core_foundation::{
CGSize,
};
use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView};
use winit_common::core_foundation::EventLoopProxy;
use winit_common::event_handler::EventHandler;
use winit_core::application::ApplicationHandler;
use winit_core::event::{StartCause, SurfaceSizeWriter, WindowEvent};
use winit_core::event_loop::ControlFlow;
use winit_core::window::WindowId;
use super::super::event_handler::EventHandler;
use super::super::event_loop_proxy::EventLoopProxy;
use super::window::WinitUIWindow;
use super::ActiveEventLoop;
use crate::application::ApplicationHandler;
use crate::dpi::PhysicalSize;
use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent};
use crate::event_loop::ControlFlow;
use crate::window::WindowId;
use crate::event_loop::ActiveEventLoop;
use crate::window::WinitUIWindow;
macro_rules! bug {
($($msg:tt)*) => {
@@ -32,12 +32,6 @@ macro_rules! bug {
};
}
macro_rules! bug_assert {
($test:expr, $($msg:tt)*) => {
assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*))
};
}
/// Get the global event handler for the application.
///
/// This is stored separately from AppState, since AppState needs to be accessible while the handler
@@ -71,131 +65,90 @@ impl EventWrapper {
}
// this is the state machine for the app lifecycle
#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl {
Initial {
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
},
ProcessingEvents {
queued_gpu_redraws: HashSet<Retained<WinitUIWindow>>,
active_control_flow: ControlFlow,
},
ProcessingRedraws {
active_control_flow: ControlFlow,
},
Waiting {
start: Instant,
},
Initial,
ProcessingEvents { active_control_flow: ControlFlow },
ProcessingRedraws { active_control_flow: ControlFlow },
Waiting { start: Instant },
PollFinished,
Terminated,
}
pub(crate) struct AppState {
// This should never be `None`, except for briefly during a state transition.
app_state: Option<AppStateImpl>,
control_flow: ControlFlow,
state: Cell<AppStateImpl>,
control_flow: Cell<ControlFlow>,
waker: EventLoopWaker,
event_loop_proxy: Arc<EventLoopProxy>,
queued_events: Vec<EventWrapper>,
queued_events: Cell<Vec<EventWrapper>>,
queued_gpu_redraws: Cell<HashSet<Retained<WinitUIWindow>>>,
}
impl fmt::Debug for AppState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AppState")
.field("control_flow", &self.control_flow)
.field("waker", &self.waker)
.field("event_loop_proxy", &self.event_loop_proxy)
.field("queued_events", &"Cell<...>")
.field("queued_gpu_redraws", &"Cell<...>")
.finish_non_exhaustive()
}
}
// SAFETY: Creating `MainThreadBound` in a `const` context,
// where there is no concept of the main thread.
static GLOBAL: MainThreadBound<OnceCell<AppState>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
impl AppState {
pub(crate) fn get_mut(mtm: MainThreadMarker) -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs.
// must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
#[allow(unknown_lints)] // New lint below
#[allow(static_mut_refs)] // TODO: Use `MainThreadBound` instead.
let mut guard = unsafe { APP_STATE.borrow_mut() };
if guard.is_none() {
#[inline(never)]
#[cold]
fn init_guard(guard: &mut RefMut<'static, Option<AppState>>, mtm: MainThreadMarker) {
let waker = EventLoopWaker::new(CFRunLoop::main().unwrap());
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}));
**guard = Some(AppState {
app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }),
control_flow: ControlFlow::default(),
waker,
event_loop_proxy,
queued_events: Vec::new(),
});
}
init_guard(&mut guard, mtm);
}
RefMut::map(guard, |state| state.as_mut().unwrap())
pub(crate) fn setup_global(mtm: MainThreadMarker) -> bool {
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm }));
}));
GLOBAL
.get(mtm)
.set(Self {
state: Cell::new(AppStateImpl::Initial),
control_flow: Cell::new(ControlFlow::default()),
waker: EventLoopWaker::new(CFRunLoop::main().unwrap()),
event_loop_proxy,
queued_events: Cell::new(Vec::new()),
queued_gpu_redraws: Cell::new(HashSet::new()),
})
.is_ok()
}
fn state(&self) -> &AppStateImpl {
match &self.app_state {
Some(ref state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn state_mut(&mut self) -> &mut AppStateImpl {
match &mut self.app_state {
Some(ref mut state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn take_state(&mut self) -> AppStateImpl {
match self.app_state.take() {
Some(state) => state,
None => bug!("`AppState` previously failed a state transition"),
}
}
fn set_state(&mut self, new_state: AppStateImpl) {
bug_assert!(
self.app_state.is_none(),
"attempted to set an `AppState` without calling `take_state` first {:?}",
self.app_state
);
self.app_state = Some(new_state)
}
fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl {
match &mut self.app_state {
Some(ref mut state) => mem::replace(state, new_state),
None => bug!("`AppState` previously failed a state transition"),
}
pub(crate) fn get(mtm: MainThreadMarker) -> &'static Self {
GLOBAL.get(mtm).get().expect("tried to get application state before it was registered")
}
fn has_launched(&self) -> bool {
!matches!(self.state(), AppStateImpl::Initial { .. })
!matches!(self.state.get(), AppStateImpl::Initial)
}
fn has_terminated(&self) -> bool {
matches!(self.state(), AppStateImpl::Terminated)
matches!(self.state.get(), AppStateImpl::Terminated)
}
fn did_finish_launching_transition(&mut self) {
let queued_gpu_redraws = match self.take_state() {
AppStateImpl::Initial { queued_gpu_redraws } => queued_gpu_redraws,
fn did_finish_launching_transition(&self) {
match self.state.get() {
AppStateImpl::Initial => {},
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
active_control_flow: self.control_flow,
queued_gpu_redraws,
});
}
self.state
.set(AppStateImpl::ProcessingEvents { active_control_flow: self.control_flow.get() });
}
fn wakeup_transition(&mut self) -> Option<StartCause> {
fn wakeup_transition(&self) -> Option<StartCause> {
// before `AppState::did_finish_launching` is called, pretend there is no running
// event loop.
if !self.has_launched() || self.has_terminated() {
return None;
}
let start_cause = match (self.control_flow, self.take_state()) {
let start_cause = match (self.control_flow.get(), self.state.get()) {
(ControlFlow::Poll, AppStateImpl::PollFinished) => StartCause::Poll,
(ControlFlow::Wait, AppStateImpl::Waiting { start }) => {
StartCause::WaitCancelled { start, requested_resume: None }
@@ -210,66 +163,61 @@ impl AppState {
s => bug!("`EventHandler` unexpectedly woke up {:?}", s),
};
self.set_state(AppStateImpl::ProcessingEvents {
queued_gpu_redraws: Default::default(),
active_control_flow: self.control_flow,
});
self.state
.set(AppStateImpl::ProcessingEvents { active_control_flow: self.control_flow.get() });
Some(start_cause)
}
fn main_events_cleared_transition(&mut self) -> HashSet<Retained<WinitUIWindow>> {
let (queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents { queued_gpu_redraws, active_control_flow } => {
(queued_gpu_redraws, active_control_flow)
},
fn main_events_cleared_transition(&self) {
let active_control_flow = match self.state.get() {
AppStateImpl::ProcessingEvents { active_control_flow } => active_control_flow,
s => bug!("unexpected state {:?}", s),
};
self.set_state(AppStateImpl::ProcessingRedraws { active_control_flow });
queued_gpu_redraws
self.state.set(AppStateImpl::ProcessingRedraws { active_control_flow });
}
fn events_cleared_transition(&mut self) {
fn events_cleared_transition(&self) {
if !self.has_launched() || self.has_terminated() {
return;
}
let old = match self.take_state() {
let old = match self.state.get() {
AppStateImpl::ProcessingRedraws { active_control_flow } => active_control_flow,
s => bug!("unexpected state {:?}", s),
};
let new = self.control_flow;
let new = self.control_flow.get();
match (old, new) {
(ControlFlow::Wait, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start });
self.state.set(AppStateImpl::Waiting { start });
self.waker.stop()
},
(ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant))
if old_instant == new_instant =>
{
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start });
self.state.set(AppStateImpl::Waiting { start });
},
(_, ControlFlow::Wait) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start });
self.state.set(AppStateImpl::Waiting { start });
self.waker.stop()
},
(_, ControlFlow::WaitUntil(new_instant)) => {
let start = Instant::now();
self.set_state(AppStateImpl::Waiting { start });
self.state.set(AppStateImpl::Waiting { start });
self.waker.start_at(new_instant)
},
// Unlike on macOS, handle Poll to Poll transition here to call the waker
(_, ControlFlow::Poll) => {
self.set_state(AppStateImpl::PollFinished);
self.state.set(AppStateImpl::PollFinished);
self.waker.start()
},
}
}
fn terminated_transition(&mut self) {
match self.replace_state(AppStateImpl::Terminated) {
fn terminated_transition(&self) {
match self.state.replace(AppStateImpl::Terminated) {
AppStateImpl::ProcessingEvents { .. } => {},
s => bug!("terminated while not processing events {:?}", s),
}
@@ -279,26 +227,27 @@ impl AppState {
&self.event_loop_proxy
}
pub(crate) fn set_control_flow(&mut self, control_flow: ControlFlow) {
self.control_flow = control_flow;
pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) {
self.control_flow.set(control_flow);
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.control_flow
self.control_flow.get()
}
}
pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Retained<WinitUIWindow>) {
let mut this = AppState::get_mut(mtm);
match this.state_mut() {
&mut AppStateImpl::Initial { ref mut queued_gpu_redraws, .. }
| &mut AppStateImpl::ProcessingEvents { ref mut queued_gpu_redraws, .. } => {
let this = AppState::get(mtm);
match this.state.get() {
AppStateImpl::Initial | AppStateImpl::ProcessingEvents { .. } => {
let mut queued_gpu_redraws = this.queued_gpu_redraws.take();
let _ = queued_gpu_redraws.insert(window);
this.queued_gpu_redraws.set(queued_gpu_redraws);
},
s @ &mut AppStateImpl::ProcessingRedraws { .. }
| s @ &mut AppStateImpl::Waiting { .. }
| s @ &mut AppStateImpl::PollFinished => bug!("unexpected state {:?}", s),
&mut AppStateImpl::Terminated => {
s @ AppStateImpl::ProcessingRedraws { .. }
| s @ AppStateImpl::Waiting { .. }
| s @ AppStateImpl::PollFinished => bug!("unexpected state {:?}", s),
AppStateImpl::Terminated => {
panic!("Attempt to create a `Window` after the app has terminated")
},
}
@@ -313,14 +262,10 @@ pub(crate) fn launch<R>(
}
pub fn did_finish_launching(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let this = AppState::get(mtm);
this.waker.start();
// have to drop RefMut because the window setup code below can trigger new events
drop(this);
AppState::get_mut(mtm).did_finish_launching_transition();
this.did_finish_launching_transition();
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, StartCause::Init));
get_handler(mtm).handle(|app| app.can_create_surfaces(&ActiveEventLoop { mtm }));
@@ -329,12 +274,11 @@ pub fn did_finish_launching(mtm: MainThreadMarker) {
// AppState::did_finish_launching handles the special transition `Init`
pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let this = AppState::get(mtm);
let cause = match this.wakeup_transition() {
None => return,
Some(cause) => cause,
};
drop(this);
get_handler(mtm).handle(|app| app.new_events(&ActiveEventLoop { mtm }, cause));
handle_nonuser_events(mtm, []);
@@ -348,19 +292,20 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
mtm: MainThreadMarker,
events: I,
) {
let mut this = AppState::get_mut(mtm);
let this = AppState::get(mtm);
if this.has_terminated() {
return;
}
if !get_handler(mtm).ready() {
// Prevent re-entrancy; queue the events up for once we're done handling the event instead.
this.queued_events.extend(events);
let mut queued_events = this.queued_events.take();
queued_events.extend(events);
this.queued_events.set(queued_events);
return;
}
let processing_redraws = matches!(this.state(), AppStateImpl::ProcessingRedraws { .. });
drop(this);
let processing_redraws = matches!(this.state.get(), AppStateImpl::ProcessingRedraws { .. });
for event in events {
if !processing_redraws && event.is_redraw() {
@@ -375,12 +320,10 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
}
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = mem::take(&mut this.queued_events);
let queued_events = this.queued_events.take();
if queued_events.is_empty() {
break;
}
drop(this);
for event in queued_events {
if !processing_redraws && event.is_redraw() {
@@ -397,19 +340,16 @@ pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
}
fn handle_user_events(mtm: MainThreadMarker) {
let this = AppState::get_mut(mtm);
if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) {
let this = AppState::get(mtm);
if matches!(this.state.get(), AppStateImpl::ProcessingRedraws { .. }) {
bug!("user events attempted to be sent out while `ProcessingRedraws`");
}
drop(this);
loop {
let mut this = AppState::get_mut(mtm);
let queued_events = mem::take(&mut this.queued_events);
let queued_events = this.queued_events.take();
if queued_events.is_empty() {
break;
}
drop(this);
for event in queued_events {
handle_wrapped_event(mtm, event);
@@ -434,28 +374,23 @@ pub(crate) fn send_occluded_event_for_all_windows(application: &UIApplication, o
}
pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(mtm);
let this = AppState::get(mtm);
if !this.has_launched() || this.has_terminated() {
return;
}
match this.state_mut() {
match this.state.get() {
AppStateImpl::ProcessingEvents { .. } => {},
_ => bug!("`ProcessingRedraws` happened unexpectedly"),
};
drop(this);
handle_user_events(mtm);
let mut this = AppState::get_mut(mtm);
let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
})
.collect();
drop(this);
this.main_events_cleared_transition();
let queued_gpu_redraws = this.queued_gpu_redraws.take();
let redraw_events = queued_gpu_redraws.into_iter().map(|window| EventWrapper::Window {
window_id: window.id(),
event: WindowEvent::RedrawRequested,
});
handle_nonuser_events(mtm, redraw_events);
get_handler(mtm).handle(|app| app.about_to_wait(&ActiveEventLoop { mtm }));
@@ -463,7 +398,7 @@ pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
}
pub fn handle_events_cleared(mtm: MainThreadMarker) {
AppState::get_mut(mtm).events_cleared_transition();
AppState::get(mtm).events_cleared_transition();
}
pub(crate) fn handle_resumed(mtm: MainThreadMarker) {
@@ -496,11 +431,10 @@ pub(crate) fn terminated(application: &UIApplication) {
}
handle_nonuser_events(mtm, events);
let mut this = AppState::get_mut(mtm);
let this = AppState::get(mtm);
this.terminated_transition();
// Prevent EventLoopProxy from firing again.
this.event_loop_proxy.invalidate();
drop(this);
get_handler(mtm).terminate();
}
@@ -541,6 +475,7 @@ fn get_view_and_screen_frame(window: &WinitUIWindow) -> (Retained<UIView>, CGRec
(view, screen_frame)
}
#[derive(Debug)]
struct EventLoopWaker {
timer: CFRetained<CFRunLoopTimer>,
}
@@ -574,15 +509,15 @@ impl EventLoopWaker {
}
}
fn stop(&mut self) {
fn stop(&self) {
self.timer.set_next_fire_date(f64::MAX);
}
fn start(&mut self) {
fn start(&self) {
self.timer.set_next_fire_date(f64::MIN);
}
fn start_at(&mut self, instant: Instant) {
fn start_at(&self, instant: Instant) {
let now = Instant::now();
if now >= instant {
self.start();

View File

@@ -16,33 +16,35 @@ use objc2_ui_kit::{
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen,
};
use rwh_06::HasDisplayHandle;
use super::super::notification_center::create_observer;
use super::app_state::{send_occluded_event_for_all_windows, AppState};
use super::{app_state, monitor, MonitorHandle};
use crate::application::ApplicationHandler;
use crate::error::{EventLoopError, NotSupportedError, RequestError};
use crate::event_loop::{
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
use winit_core::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use crate::monitor::MonitorHandle as CoreMonitorHandle;
use crate::platform_impl::Window;
use crate::window::{CustomCursor, CustomCursorSource, Theme, Window as CoreWindow};
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::{Theme, Window as CoreWindow};
use super::app_state::{send_occluded_event_for_all_windows, AppState};
use super::notification_center::create_observer;
use crate::monitor::MonitorHandle;
use crate::window::Window;
use crate::{app_state, monitor};
#[derive(Debug)]
pub(crate) struct ActiveEventLoop {
pub struct ActiveEventLoop {
pub(super) mtm: MainThreadMarker,
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> CoreEventLoopProxy {
CoreEventLoopProxy::new(AppState::get_mut(self.mtm).event_loop_proxy().clone())
CoreEventLoopProxy::new(AppState::get(self.mtm).event_loop_proxy().clone())
}
fn create_window(
&self,
window_attributes: crate::window::WindowAttributes,
window_attributes: winit_core::window::WindowAttributes,
) -> Result<Box<dyn CoreWindow>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
@@ -62,7 +64,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
)
}
fn primary_monitor(&self) -> Option<crate::monitor::MonitorHandle> {
fn primary_monitor(&self) -> Option<winit_core::monitor::MonitorHandle> {
#[allow(deprecated)]
let monitor = MonitorHandle::new(UIScreen::mainScreen(self.mtm));
Some(CoreMonitorHandle(Arc::new(monitor)))
@@ -71,7 +73,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
fn listen_device_events(&self, _allowed: DeviceEvents) {}
fn set_control_flow(&self, control_flow: ControlFlow) {
AppState::get_mut(self.mtm).set_control_flow(control_flow)
AppState::get(self.mtm).set_control_flow(control_flow)
}
fn system_theme(&self) -> Option<Theme> {
@@ -79,7 +81,7 @@ impl RootActiveEventLoop for ActiveEventLoop {
}
fn control_flow(&self) -> ControlFlow {
AppState::get_mut(self.mtm).control_flow()
AppState::get(self.mtm).control_flow()
}
fn exit(&self) {
@@ -137,22 +139,16 @@ pub struct EventLoop {
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {}
pub struct PlatformSpecificEventLoopAttributes {}
impl EventLoop {
pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop, EventLoopError> {
pub fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<EventLoop, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
static mut SINGLETON_INIT: bool = false;
unsafe {
assert!(
!SINGLETON_INIT,
"Only one `EventLoop` is supported on iOS. `EventLoopProxy` might be helpful"
);
SINGLETON_INIT = true;
if !AppState::setup_global(mtm) {
// Required, AppState is global state, and event loop can only be run once.
return Err(EventLoopError::RecreationAttempt);
}
// this line sets up the main run loop before `UIApplicationMain`

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